diff --git a/anonymizer_core_refactored_onnx.py b/anonymizer_core_refactored_onnx.py index 45c09e5..3318d60 100644 --- a/anonymizer_core_refactored_onnx.py +++ b/anonymizer_core_refactored_onnx.py @@ -538,6 +538,7 @@ _MEDICAL_STOP_WORDS_SET = { # FP audit OGC 17 CRH "mode", "retraitee", "retraité", "retraitée", "régression", "regression", "tel", "strasbourg", "bordeaux", "toulouse", "paris", "lyon", "marseille", "bayonne", "anglet", + "saint-palais", "tarnos", "hendaye", "dax", "orthez", "oloron", "pau", "cambo", # Spécialités/services récurrents comme FP NOM "cancérologie", "cancerologie", "réanimation", "reanimation", "urologie", "néphrologie", "nephrologie", "hématologie", "hematologie", @@ -630,6 +631,11 @@ _MEDICAL_STOP_WORDS_SET = { "bronchite", "accueil", "cadre", "transfert", "relecture", "examens", "traitements", "traitement", "infectiologie", "cancérologie", "cancerologie", "maternité", "orale", "sachet", "absence", + # FP audit 30 fichiers Phase 2 (mars 2026) + "bouffee", "bouffée", "discontinue", "respimat", "lyoc", + "probnp", "pro-bnp", "nt-probnp", + "bpco", "colle", "gsc", "masse", + "selle", "selles", } # Enrichissement automatique avec les ~4000 noms de médicaments d'edsnlp _MEDICAL_STOP_WORDS_SET.update(_load_edsnlp_drug_names()) @@ -790,9 +796,10 @@ RE_ETABLISSEMENT = re.compile( r")", ) RE_HOPITAL_VILLE = re.compile( + r"(? str: + ville = m.group(2).strip() + audit.append(PiiHit(page_idx, "VILLE", ville, PLACEHOLDERS["VILLE"])) + return m.group(1) + PLACEHOLDERS["VILLE"] + m.group(3) + line = _re_ville_date.sub(_repl_ville_date, line) + # Champs structurés : Lieu de naissance, Ville de résidence (masquage direct, sans filtre stop words) _re_lieu = re.compile(r"(Lieu\s+de\s+naissance\s*:\s*)(\S.+)") def _repl_lieu(m: re.Match) -> str: @@ -1341,10 +1365,17 @@ def _extract_trackare_identity(full_text: str) -> Tuple[set, List[PiiHit]]: force_names: set = set() # noms issus de contextes structurés (DR., Signé, etc.) → bypass stop words def _add_name(s: str): - for tok in s.split(): + s = s.strip() + parts = s.split() + for tok in parts: tok = tok.strip(" .-'(),") if len(tok) >= 2 and tok[0].isupper(): names.add(tok) + # Garder aussi le nom composé complet (DI LULLO, LE MOIGNE, etc.) + if len(parts) >= 2: + compound = " ".join(t.strip(" .-'(),") for t in parts if len(t.strip(" .-'(),")) >= 2) + if len(compound) >= 5: + names.add(compound) # Termes non-noms fréquents dans les contextes Signé/DR./Note d'évolution _FORCE_EXCLUDE = _MEDICATION_WHITELIST | { @@ -1429,15 +1460,29 @@ def _extract_trackare_identity(full_text: str) -> Tuple[set, List[PiiHit]]: # --- Contacts structurés --- # Pattern: Relation NOM PRENOM [ADRESSE] [TEL] + # Accepte les minuscules (Trackare écrit parfois "Conjoint vandestock michele") + # Capture jusqu'à 3 tokens pour les noms composés (le moigne christophe) for m in re.finditer( r"(?:Conjoint|Concubin|Epoux|Epouse|Parent|Père|Mère|Fils|Fille|Frère|Soeur|Tuteur)\s+" r"([A-ZÉÈÀÙÂÊÎÔÛa-zéèàùâêîôû][A-ZÉÈÀÙÂÊÎÔÛa-zéèàùâêîôûä\-']+)" + r"(?:\s+([A-ZÉÈÀÙÂÊÎÔÛa-zéèàùâêîôû][A-ZÉÈÀÙÂÊÎÔÛa-zéèàùâêîôûä\-']+))?" r"(?:\s+([A-ZÉÈÀÙÂÊÎÔÛa-zéèàùâêîôû][A-ZÉÈÀÙÂÊÎÔÛa-zéèàùâêîôûä\-']+))?", full_text, ): - _add_name(m.group(1)) - if m.group(2): - _add_name(m.group(2)) + contact_parts = [g.strip(" .-'(),") for g in (m.group(1), m.group(2), m.group(3)) if g] + # Ajouter chaque token >= 3 chars (pas les articles courts comme "le", "di") + for tok in contact_parts: + if len(tok) >= 3 and tok.lower() not in _MEDICAL_STOP_WORDS_SET: + names.add(tok) + if tok[0].islower(): + names.add(tok.capitalize()) + # Ajouter aussi le composé complet (pour "le moigne", "di lullo") + if len(contact_parts) >= 2: + compound = " ".join(contact_parts) + if len(compound) >= 5: + names.add(compound) + # Version capitalisée pour propagation + names.add(" ".join(t.capitalize() for t in compound.split())) # --- Prescripteurs / Exécutants (trackare) --- for m in re.finditer( @@ -1592,7 +1637,16 @@ def _extract_document_names(full_text: str, cfg: Dict[str, Any]) -> Tuple[set, s names: set = set() force_names: set = set() + def _add_compound(match_str: str): + """Ajoute le nom composé complet en plus des tokens individuels (DI LULLO, LE MOIGNE).""" + parts = [t.strip(" .-'") for t in match_str.split() if len(t.strip(" .-'")) >= 2] + if len(parts) >= 2: + compound = " ".join(parts) + if len(compound) >= 5: + names.add(compound) + def _add_tokens(match_str: str): + _add_compound(match_str) for token in match_str.split(): token = token.strip(" .-'") if len(token) < 3: @@ -1605,6 +1659,7 @@ def _extract_document_names(full_text: str, cfg: Dict[str, Any]) -> Tuple[set, s def _add_tokens_force_all(match_str: str): """Bypass stop words pour TOUS les tokens (contexte Patient: très fiable).""" + _add_compound(match_str) for token in match_str.split(): token = token.strip(" .-'") if len(token) < 2: @@ -1616,6 +1671,7 @@ def _extract_document_names(full_text: str, cfg: Dict[str, Any]) -> Tuple[set, s def _add_tokens_force_first(match_str): """Comme _add_tokens mais force le 1er token (contexte Dr/Mme fort).""" + _add_compound(match_str) tokens = match_str.split() for i, token in enumerate(tokens): token = token.strip(" .-'")