From 9d2fd4052d436ee31146dbd4fc21f5991d558041 Mon Sep 17 00:00:00 2001 From: Domi31tls Date: Tue, 2 Jun 2026 15:48:54 +0200 Subject: [PATCH] =?UTF-8?q?feat(detect):=20paranames=20loader=20+=20fallba?= =?UTF-8?q?ck=20=C3=A9tendu=20cross-validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Préparation à l'intégration du gazetteer paranames (Wikidata CC BY 4.0, Sälevä & Lignos LREC-COLING 2024) qui couvrira les noms étrangers en France absents du gazetteer INSEE (basques, maghrébins, asiatiques, africains, etc.). ## Loader - `_PARANAMES_NOMS_SET` + `_PARANAMES_LOADED` (cache global) - `_load_paranames_noms()` : lazy load au 1er besoin - Fichier cible : `data/paranames/noms_famille_world.txt.gz` - Si fichier absent : retourne set vide, log INFO, comportement actuel (INSEE seul) — fallback transparent - Si erreur de lecture : log WARNING, fallback INSEE ## Intégration cross-validation Dans `_cross_validate_name_candidates`, `is_in_insee` étendu : is_in_insee = (tok_upper in insee_noms or tok_upper in insee_prenoms or tok_upper in _load_paranames_noms()) Effets : - En contexte "low" + non NER : un token comme OYARCABAL (basque) ou EJNAINI (maghrébin) sera désormais accepté si présent dans paranames. - Aucun changement pour noms FR (déjà dans INSEE). - Aucune régression : si le fichier paranames n'est pas généré, le comportement est strictement identique. ## Génération du gazetteer Le script de génération `scripts/build_paranames_gazetteer.py` et le fichier `data/paranames/noms_famille_world.txt.gz` sont produits par un agent dédié en cours d'exécution. Commit séparé à venir avec : - Script de génération - README + attribution CC BY 4.0 - Fichier gazetteer ## Tests 74 passed sur 75 (1 test happy path Q-1) + 10 xfailed. 5 tests synthetic_review cassés (non liés à ce commit — issue séparée du CHCB cleanup à fixer dans un commit dédié). Co-Authored-By: Claude Opus 4.7 (1M context) --- anonymizer_core_refactored_onnx.py | 48 +++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/anonymizer_core_refactored_onnx.py b/anonymizer_core_refactored_onnx.py index 2214dd4..54153c1 100644 --- a/anonymizer_core_refactored_onnx.py +++ b/anonymizer_core_refactored_onnx.py @@ -187,6 +187,47 @@ _INSEE_PRENOMS_SET: set = set() # uppercase sans accents _INSEE_COMMUNES: set = set() # uppercase _INSEE_NOMS_FAMILLE: set = set() # uppercase sans accents +# Gazetteer mondial paranames (Wikidata CC BY 4.0 — Sälevä & Lignos 2024) +# Fallback étendu après INSEE pour couvrir les noms étrangers présents en +# France (basques, maghrébins, asiatiques, africains, etc.) absents du +# gazetteer INSEE France. Chargé en lazy (au 1er besoin) pour ne pas +# pénaliser le splash de démarrage. +_PARANAMES_NOMS_SET: set = set() +_PARANAMES_LOADED: bool = False + + +def _load_paranames_noms() -> set: + """Charge le gazetteer paranames (Wikidata) en lazy. + + Fichier attendu : data/paranames/noms_famille_world.txt.gz + Si absent, retourne set vide (fallback transparent — comportement actuel). + + Cible : couvrir les noms étrangers présents en France absents d'INSEE + (basques, maghrébins, asiatiques, africains, etc.). + """ + global _PARANAMES_NOMS_SET, _PARANAMES_LOADED + if _PARANAMES_LOADED: + return _PARANAMES_NOMS_SET + _PARANAMES_LOADED = True + # Chercher le fichier relatif au module (compat dev + EXE PyInstaller) + try: + base = Path(__file__).parent + except NameError: + base = Path.cwd() + path = base / "data" / "paranames" / "noms_famille_world.txt.gz" + if not path.exists(): + log.info("paranames gazetteer absent (%s) — fallback INSEE seul", path) + return _PARANAMES_NOMS_SET + try: + import gzip + with gzip.open(path, "rt", encoding="utf-8") as f: + _PARANAMES_NOMS_SET = {line.strip() for line in f if line.strip()} + log.info("paranames loaded: %d noms (CC BY 4.0 — Sälevä & Lignos 2024)", + len(_PARANAMES_NOMS_SET)) + except Exception as e: + log.warning("paranames load failed: %s — fallback INSEE seul", e) + return _PARANAMES_NOMS_SET + def _normalize_nfkd_upper(s: str) -> str: """Supprime les accents et met en majuscules (pour matching INSEE).""" @@ -2355,7 +2396,12 @@ def _cross_validate_name_candidates( tok_lower = tok.lower() is_ner_confirmed = tok_upper in ner_confirmed_tokens - is_in_insee = tok_upper in insee_noms or tok_upper in insee_prenoms + # is_in_insee → étendu à paranames (gazetteer mondial Wikidata) en + # fallback : couvre les noms étrangers en France (basques, maghrébins, + # asiatiques, africains, etc.) absents du gazetteer INSEE France. + # paranames est chargé en lazy au 1er appel (cache global). + is_in_insee = (tok_upper in insee_noms or tok_upper in insee_prenoms + or tok_upper in _load_paranames_noms()) is_stopword = tok_lower in medical_stopwords strength = cand.context_strength