feat(detect): paranames loader + fallback étendu cross-validation

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) <noreply@anthropic.com>
This commit is contained in:
2026-06-02 15:48:54 +02:00
parent bf268bac12
commit 3bd38c6cdb

View File

@@ -187,6 +187,47 @@ _INSEE_PRENOMS_SET: set = set() # uppercase sans accents
_INSEE_COMMUNES: set = set() # uppercase _INSEE_COMMUNES: set = set() # uppercase
_INSEE_NOMS_FAMILLE: set = set() # uppercase sans accents _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: def _normalize_nfkd_upper(s: str) -> str:
"""Supprime les accents et met en majuscules (pour matching INSEE).""" """Supprime les accents et met en majuscules (pour matching INSEE)."""
@@ -2355,7 +2396,12 @@ def _cross_validate_name_candidates(
tok_lower = tok.lower() tok_lower = tok.lower()
is_ner_confirmed = tok_upper in ner_confirmed_tokens 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 is_stopword = tok_lower in medical_stopwords
strength = cand.context_strength strength = cand.context_strength