"""Registros de autoridade (ISAAR(CPF)) para produtores — elemento 2.1 e 8.1. Junta as grafias de `dc.contributor` ("Malta, Augusto", "Malta, Augusto, 1864-1957", "Marc Ferrez" e "Ferrez, Marc") numa forma autorizada, com as variantes como remissivas (proc. 8.1.4). """ from __future__ import annotations import re import unicodedata from collections import Counter, defaultdict ANONIMO = re.compile(r'(?i)^\s*(an[oô]nimo|autoria n[aã]o identificada|s/\s*autor|sem autor|desconhecid[oa]|' r'n[aã]o identificad[oa]|autor n[aã]o identificado|autor desconhecido)\s*\.?$') COLETIVA = re.compile(r'(?i)\b(escola|officina|oficina|photographia|fotografia|photo|foto|studio|est[uú]dio|atelier|' r'casa|companhia|cia\.?|museu|instituto|minist[eé]rio|servi[cç]o|diretoria|departamento|' r'comiss[aã]o|biblioteca|arquivo|editora|lith|litografia|tipografia|typ|&|irm[aã]os|' r'sociedade|empresa|jornal|revista|ag[eê]ncia|governo|prefeitura|marinha|ex[eé]rcito|' r'for[cç]a|aeron[aá]utica|avia[cç][aã]o|universidade|fundação)\b') DATAS = re.compile(r',?\s*((?:ca\.\s*|fl\.\s*|m\.\s*|n\.\s*)?\d{4}\??(?:\s*-\s*(?:ca\.\s*)?\d{0,4}\??)?)\.?\s*$') PARTICULAS = {'de', 'da', 'do', 'das', 'dos', 'e', 'von', 'van', 'du', 'del', 'di'} def _chave(s: str) -> str: s = ''.join(c for c in unicodedata.normalize('NFD', s.lower()) if unicodedata.category(c) != 'Mn') s = re.sub(r'\(.*?\)', '', s) return re.sub(r'[^\w,]+', ' ', s).strip(' ,') def _separa_datas(nome: str) -> tuple[str, str | None]: nome = nome.strip().rstrip('.') if m := DATAS.search(nome): if ',' in nome[:m.start()] or m[1].startswith(('fl', 'm.', 'n.', 'ca')): return nome[:m.start()].strip(' ,'), m[1] return nome, None def _inverte(nome: str) -> str | None: """'Marc Ferrez' → 'Ferrez, Marc'. Só para nomes de pessoa sem vírgula.""" partes = nome.split() if len(partes) < 2 or ',' in nome: return None i = len(partes) - 1 while i > 1 and partes[i - 1].lower() in PARTICULAS: i -= 1 return f"{' '.join(partes[i:])}, {' '.join(partes[:i])}" def periodo(datas: str | None) -> tuple[int, int] | None: """Anos prováveis de atividade: fl. A-B, ou nascimento+15 até a morte.""" if not datas: return None anos = [int(a) for a in re.findall(r'\d{4}', datas)] if not anos: return None if datas.startswith('fl'): return (anos[0], anos[-1]) if len(anos) > 1 else (anos[0], anos[0]) if datas.startswith('m.'): return None if len(anos) == 2: return anos[0] + 15, anos[1] return None class Autoridades: def __init__(self): self.contagem: Counter = Counter() def registra(self, nome: str): if nome and nome.strip() and not ANONIMO.match(nome): self.contagem[nome.strip()] += 1 def resolve(self): """Agrupa as grafias. Depois disso `forma(nome)` devolve o id da autoridade.""" grupos: dict[str, Counter] = defaultdict(Counter) for nome, n in self.contagem.items(): base, _ = _separa_datas(nome) grupos[_chave(base)][nome] += n # grafias diretas ("Marc Ferrez") que batem com uma invertida já conhecida for k in list(grupos): if ',' in k: continue nome = next(iter(grupos[k])) if COLETIVA.search(nome): continue inv = _inverte(_separa_datas(nome)[0]) if inv and _chave(inv) in grupos and _chave(inv) != k: grupos[_chave(inv)].update(grupos.pop(k)) self.registros: dict[str, dict] = {} self._id_de: dict[str, str] = {} for k, variantes in sorted(grupos.items(), key=lambda kv: -sum(kv[1].values())): # forma autorizada: a variante mais completa (com datas) e mais usada # (datas com início e fim > só uma data > nenhuma), depois frequência def peso(v): dt = _separa_datas(v)[1] or '' return (len(re.findall(r'\d{4}', dt)), variantes[v], v) forma = max(variantes, key=peso) forma = forma.strip().rstrip('.') base, datas = _separa_datas(forma) tipo = 'entidade coletiva' if COLETIVA.search(base) else ('pessoa' if ',' in base else 'pessoa ou entidade') if re.match(r'(?i)fam[ií]lia\b', base): tipo = 'família' aid = 'A' + str(len(self.registros) + 1).zfill(4) self.registros[aid] = { 'id': aid, 'forma_autorizada': forma, 'nome': base, 'datas_existencia': datas, 'tipo': tipo, 'variantes': sorted(v for v in variantes if v.strip().rstrip('.') != forma), 'ocorrencias': sum(variantes.values()), 'periodo_atividade': periodo(datas), 'fontes': [], } for v in variantes: self._id_de[v] = aid def id(self, nome: str) -> str | None: return self._id_de.get((nome or '').strip()) def __getitem__(self, aid: str) -> dict: return self.registros[aid]