feat: externalisation des listes — stop-words et villes modifiables sans code

Toutes les listes de règles sont maintenant modifiables sans toucher
au code Python :

Fichiers de données (data/) :
  - stopwords_manuels.txt : 1307 termes médicaux/techniques
  - villes_blacklist.txt : 117 communes à ne pas matcher
  - medicaments_stopwords.txt : 7312 médicaments BDPM (existant)
  - Chargés automatiquement au démarrage

Config YAML (dictionnaires.yml) :
  - additional_stopwords : mots supplémentaires par établissement
  - additional_villes_blacklist : villes supplémentaires
  - whitelist_phrases : phrases à ne jamais anonymiser
  - force_mask_terms : mots à toujours masquer

Chaîne de chargement : code dur → fichiers data/ → YAML config
Les 3 niveaux se cumulent (union).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 07:45:42 +02:00
parent b2ee6ad835
commit ac5c35ae2d
4 changed files with 1479 additions and 0 deletions

View File

@@ -204,6 +204,14 @@ _VILLE_BLACKLIST = {
# Parties du corps homonymes de communes (FP "prurit invalidant (COU, décolleté)") # Parties du corps homonymes de communes (FP "prurit invalidant (COU, décolleté)")
"COU", "DOS", "SEIN", "BRAS", "COU", "DOS", "SEIN", "BRAS",
} }
# Enrichissement depuis fichier externe (modifiable sans toucher au code)
_villes_bl_file = Path(__file__).parent / "data" / "villes_blacklist.txt"
if _villes_bl_file.exists():
for _line in _villes_bl_file.read_text(encoding="utf-8").splitlines():
_w = _line.strip()
if _w and not _w.startswith("#"):
_VILLE_BLACKLIST.add(_w)
log.info("Villes blacklist chargées : %d entrées", len(_VILLE_BLACKLIST))
try: try:
import ahocorasick as _ahocorasick import ahocorasick as _ahocorasick
@@ -772,6 +780,17 @@ _MEDICAL_STOP_WORDS_SET = {
# Enrichissement automatique avec les ~4000 noms de médicaments d'edsnlp # Enrichissement automatique avec les ~4000 noms de médicaments d'edsnlp
_MEDICAL_STOP_WORDS_SET.update(_load_edsnlp_drug_names()) _MEDICAL_STOP_WORDS_SET.update(_load_edsnlp_drug_names())
# Enrichissement depuis fichier externe (modifiable sans toucher au code)
_stopwords_file = Path(__file__).parent / "data" / "stopwords_manuels.txt"
if _stopwords_file.exists():
_sw_count = 0
for _line in _stopwords_file.read_text(encoding="utf-8").splitlines():
_w = _line.strip()
if _w and not _w.startswith("#"):
_MEDICAL_STOP_WORDS_SET.add(_w)
_sw_count += 1
log.info("Stop-words manuels chargés : %d mots depuis %s", _sw_count, _stopwords_file.name)
# Enrichissement BDPM : ~7300 noms commerciaux + DCI/substances actives # Enrichissement BDPM : ~7300 noms commerciaux + DCI/substances actives
_bdpm_path = Path(__file__).parent / "data" / "bdpm" / "medicaments_stopwords.txt" _bdpm_path = Path(__file__).parent / "data" / "bdpm" / "medicaments_stopwords.txt"
if _bdpm_path.exists(): if _bdpm_path.exists():
@@ -1053,6 +1072,22 @@ def load_dictionaries(config_path: Optional[Path]) -> Dict[str, Any]:
cfg[k] = v cfg[k] = v
except Exception: except Exception:
pass pass
# Charger les stop-words et villes supplémentaires depuis le YAML
extra_sw = cfg.get("additional_stopwords", [])
if extra_sw:
for w in extra_sw:
if w and str(w).strip():
_MEDICAL_STOP_WORDS_SET.add(str(w).strip().lower())
log.info("Stop-words YAML supplémentaires : %d", len(extra_sw))
extra_villes = cfg.get("additional_villes_blacklist", [])
if extra_villes:
for v in extra_villes:
if v and str(v).strip():
_VILLE_BLACKLIST.add(str(v).strip().upper())
log.info("Villes blacklist YAML supplémentaires : %d", len(extra_villes))
return cfg return cfg
# ----------------- Extraction ----------------- # ----------------- Extraction -----------------

View File

@@ -60,6 +60,18 @@ whitelist_phrases:
- "date de sortie" - "date de sortie"
- "date d'admission" - "date d'admission"
- "code postal" - "code postal"
# Mots supplémentaires à ne jamais masquer comme noms de personnes
# (complète les 9000+ stop-words intégrés)
additional_stopwords: []
# Exemple :
# - "votre_mot"
# Villes supplémentaires à ne jamais matcher comme lieux
# (complète les 115+ villes blacklistées intégrées)
additional_villes_blacklist: []
# Exemple :
# - "VOTRE_VILLE"
flags: flags:
case_insensitive: true case_insensitive: true
unicode_word_boundaries: true unicode_word_boundaries: true

1311
data/stopwords_manuels.txt Normal file

File diff suppressed because it is too large Load Diff

121
data/villes_blacklist.txt Normal file
View File

@@ -0,0 +1,121 @@
# Villes/communes à ne jamais matcher comme noms de lieux
# (homonymes de mots courants, parties du corps, etc.)
# Total : 117 entrées
AGEN
AIRE
ALBI
ANNE
AUCH
BARRES
BEAUNE
BILLE
BLOIS
BOIS
BOURG
BRAS
BREST
CENTRE
CERGY
CHAISE
CHARGE
COEUR
CONTRE
CORPS
COU
COURANT
COURS
CREIL
CROIX
DOLE
DOS
EST
EUROPE
EVIAN
FAUX
FOIX
FORT
FOSSES
FRANCE
GARDES
GIEN
GIVET
GRAND
GRAY
HYERES
ISLE
JEAN
JOUE
LACS
LAON
LENS
LIGNE
LIGNES
LONG
LUNEL
LURE
MAISON
MARCHE
MARIE
MARS
MARSA
MAURE
MEAUX
MENDE
MENTON
MERE
MONT
MORET
MOULIN
MURET
MURS
Médecin courant
NICE
NORD
NUITS
ONDRES
ORANGE
OUEST
OUST
PARIS
PAUL
PIERRE
PLACE
PLAN
PONT
PORT
PREY
PRISON
PUITS
QUATRE
RANS
RECY
REDON
REZE
RICHE
ROMANS
ROUGE
SAINT
SALLE
SALON
SARE
SEIN
SENS
SERVICE
SETE
SIGNES
SORE
SOURCE
SUD
TOURS
TRANS
VALLEE
VAUX
VEBRE
VERS
VERT
VIENNE
VILLE
VIRE
VITRE
prurit invalidant (COU, décolleté)