From a138b267381f0da963abd6b3bbea5ce7c48309a0 Mon Sep 17 00:00:00 2001 From: Domi31tls Date: Thu, 26 Feb 2026 17:22:38 +0100 Subject: [PATCH] =?UTF-8?q?Fix=20faux=20positifs=20PDF=20(EDS=5FTEL,=20EDS?= =?UTF-8?q?=5FVILLE)=20+=20d=C3=A9tection=20noms=20Notes=20IDE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Skip EDS_TEL dans PDF (valeurs Pouls détectées comme N° de téléphone) - Ajout EDS_VILLE au whole-word matching (évite "GEL" dans "GELULE") - Filtre stop words étendu à EDS_HOPITAL et EDS_VILLE dans la détection NER - Détection noms soignants dans "Note IDE\nPrenom NOM" (BARGAIN, LACOTE, etc.) - Stop words : semaine, jour, matin, soir, nuit, midi Co-Authored-By: Claude Opus 4.6 --- anonymizer_core_refactored_onnx.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/anonymizer_core_refactored_onnx.py b/anonymizer_core_refactored_onnx.py index 0baf6bb..9626308 100644 --- a/anonymizer_core_refactored_onnx.py +++ b/anonymizer_core_refactored_onnx.py @@ -301,6 +301,8 @@ _MEDICAL_STOP_WORDS_SET = { "axa", "ttt", "anionique", "abdomino", "cod", "omi", "urg", "med", "10mg", "20mg", "40mg", "100mg", "300ui", "500ml", "innohep", "coaprovel", "actiskenan", "simvastatine", "forlax", + # Mots temporels / contextuels détectés comme EDS_HOPITAL + "semaine", "jour", "matin", "soir", "nuit", "midi", # Mots clés de contexte document "compétences", "maladies", "inflammatoires", "systémiques", "rares", "fret", "fax", "contexte", "résultat", "resultat", "résultats", "resultats", @@ -888,11 +890,19 @@ def _extract_trackare_identity(full_text: str) -> Tuple[set, List[PiiHit]]: if m.group(2): _add_name(m.group(2)) - # --- Noms soignants dans les Notes d'évolution --- - # Pattern: "Note d'évolution PRENOM NOM" ou "NOM HH:MM texte..." - for m in re.finditer(r"Note\s+d'[ée]volution\s+([A-ZÉÈÀÙÂÊÎÔÛ][a-zéèàùâêîôû]+)\s+([A-ZÉÈÀÙÂÊÎÔÛ]{2,})", full_text): - _add_name(m.group(1)) - _add_name(m.group(2)) + # --- Noms soignants dans les Notes d'évolution / Notes IDE / Notes médicales --- + # Pattern: "Note IDE\nPrenom NOM" ou "Note d'évolution\nPrenom NOM" + for m in re.finditer( + r"Note\s+(?:IDE|AS|d'[ée]volution|m[ée]dicale|kin[ée])\s*\n\s*" + r"([A-ZÉÈÀÙÂÊÎÔÛa-zéèàùâêîôû][a-zéèàùâêîôûäëïöüç]+)\s+" + r"([A-ZÉÈÀÙÂÊÎÔÛ][A-ZÉÈÀÙÂÊÎÔÛa-zéèàùâêîôûäëïöüç\-]+)", + full_text + ): + prenom, nom = m.group(1), m.group(2) + if prenom.lower() not in _MEDICAL_STOP_WORDS_SET: + _add_name(prenom) + if nom.lower() not in _MEDICAL_STOP_WORDS_SET: + _add_name(nom) # --- Noms soignants multi-lignes : "Prénom\nNOM" dans les tableaux de prescriptions/soins --- for m in re.finditer( @@ -1174,7 +1184,7 @@ def _mask_with_eds_pseudo(text: str, ents: List[Dict[str, Any]], cfg: Dict[str, continue # Filtrer les faux positifs NOM/PRENOM (médicaments, acronymes médicaux) label = e.get("entity_group", "EDS") - if label in ("NOM", "PRENOM"): + if label in ("NOM", "PRENOM", "HOPITAL", "VILLE"): if w.lower() in _MEDICAL_STOP_WORDS_SET: continue # Filtrer aussi les tokens multi-mots dont un composant est un stop word @@ -1309,10 +1319,10 @@ def redact_pdf_vector(original_pdf: Path, audit: List[PiiHit], out_pdf: Path) -> by_page.setdefault(h.page, []).append(h) # Kinds à ne pas chercher dans le PDF (dates masquées uniquement dans le texte, # pas dans le PDF où elles rendent les tableaux illisibles) - _VECTOR_SKIP_KINDS = {"EDS_DATE", "EDS_DATE_NAISSANCE", "EDS_SECU"} + _VECTOR_SKIP_KINDS = {"EDS_DATE", "EDS_DATE_NAISSANCE", "EDS_SECU", "EDS_TEL"} # Kinds dont les tokens courts (< 5) risquent le substring matching via page.search_for() _VECTOR_SHORT_TOKEN_KINDS = {"NOM_GLOBAL", "NOM_EXTRACTED", "EDS_NOM", "EDS_PRENOM", - "EDS_HOPITAL", "ETAB", "ETAB_GLOBAL"} + "EDS_HOPITAL", "EDS_VILLE", "ETAB", "ETAB_GLOBAL"} for pno in range(len(doc)): page = doc[pno] hits = by_page.get(pno, []) + by_page.get(-1, []) @@ -1365,9 +1375,9 @@ def redact_pdf_raster(original_pdf: Path, audit: List[PiiHit], out_pdf: Path, dp for pno in range(len(doc)): page = doc[pno] rects = [] - _RASTER_SKIP_KINDS = {"EDS_DATE", "EDS_DATE_NAISSANCE", "EDS_SECU"} + _RASTER_SKIP_KINDS = {"EDS_DATE", "EDS_DATE_NAISSANCE", "EDS_SECU", "EDS_TEL"} _RASTER_SHORT_TOKEN_KINDS = {"NOM_GLOBAL", "NOM_EXTRACTED", "EDS_NOM", "EDS_PRENOM", - "EDS_HOPITAL", "ETAB", "ETAB_GLOBAL"} + "EDS_HOPITAL", "EDS_VILLE", "ETAB", "ETAB_GLOBAL"} hits = [x for x in audit if x.page in {pno, -1}] for h in hits: token = h.original.strip()