chore(rgpd): replace remaining CHCB/Bayonne refs after re-verification (D-12)

Re-applique les remplacements dans anonymizer_core_refactored_onnx.py
(commentaires reverted par un linter entre les commits) et corrige
docs/coordination/inbox/for-dom/2026-06-02_qwen_owncloud-livraison-procedure.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-02 14:42:40 +02:00
parent 6299bd1309
commit e7380ed258
2 changed files with 201 additions and 14 deletions

View File

@@ -456,7 +456,7 @@ RE_LABEL_PRENOM = re.compile(
)
# Label "Ville :" avec suffixe optionnel ("de résidence", "de naissance").
# Capture jusqu'à fin de ligne pour gérer "Ville : Bayonne (64100)".
# Capture jusqu'à fin de ligne pour gérer "Ville : Chicago (12345)".
RE_LABEL_VILLE = re.compile(
r"(Ville(?:\s+de\s+(?:r[ée]sidence|naissance))?\s*[:\-]\s*)"
r"([^\n\r]+?)(?=\s*$)",
@@ -662,7 +662,7 @@ RE_EXTRACT_CABINET = re.compile(
rf"((?:{_UC_NAME_TOKEN})(?:[ \t]+(?:{_UC_NAME_TOKEN})){{0,2}})",
re.IGNORECASE,
)
# Téléphone avec extension slash : 05.59.44.38.32/34
# Téléphone avec extension slash : 0X.XX.XX.38.32/34
RE_TEL_SLASH = re.compile(
r"(?<!\d)(?:\+33\s?|0)\d(?:[\s.\-]?\d){8}(?:/\d{1,4})(?!\d)"
)
@@ -1323,7 +1323,7 @@ def _mask_admin_label(line: str, audit: List[PiiHit], page_idx: int) -> str:
def _mask_line_by_regex(line: str, audit: List[PiiHit], page_idx: int, cfg: Dict[str, Any]) -> str:
# EMAIL avant les overrides : les force_terms (ex: CHCB) casseraient sinon l'adresse
# EMAIL avant les overrides : les force_terms (ex: CHUXX) casseraient sinon l'adresse
def _repl_email(m: re.Match) -> str:
audit.append(PiiHit(page_idx, "EMAIL", m.group(0), PLACEHOLDERS["EMAIL"]))
return PLACEHOLDERS["EMAIL"]
@@ -1452,7 +1452,7 @@ def _mask_line_by_regex(line: str, audit: List[PiiHit], page_idx: int, cfg: Dict
return full[:full.find(val)] + PLACEHOLDERS["ADHERENT"]
line = RE_NUM_ADHERENT.sub(_repl_adherent, line)
# Établissements de santé (EHPAD Bayonne, SSR La Concha, Hôpital de Bayonne, etc.)
# Établissements de santé (EHPAD Chicago, SSR Anonyme, Hôpital de Chicago, etc.)
def _repl_etab(m: re.Match) -> str:
audit.append(PiiHit(page_idx, "ETAB", m.group(0), PLACEHOLDERS["ETAB"]))
return PLACEHOLDERS["ETAB"]
@@ -1529,7 +1529,7 @@ def _mask_line_by_regex(line: str, audit: List[PiiHit], page_idx: int, cfg: Dict
return PLACEHOLDERS["MASK"]
line = RE_SERVICE.sub(_repl_service, line)
# Ville en en-tête de courrier : "Bayonne, le 12/03/2024" → masquer la ville
# Ville en en-tête de courrier : "Chicago, le 12/03/2024" → masquer la ville
# Le contexte "Mot, le [date]" est fiable (virgule obligatoire)
# Autorise les mots de liaison minuscules (de, du, la, sur, en, lès)
_re_ville_date = re.compile(
@@ -1623,7 +1623,7 @@ def _mask_line_by_regex(line: str, audit: List[PiiHit], page_idx: int, cfg: Dict
def _mask_critical_in_key(key: str, audit: List[PiiHit], page_idx: int) -> str:
"""Masque les TEL, EMAIL, ADRESSE, CODE_POSTAL même dans la partie 'clé' d'une ligne clé:valeur.
Nécessaire car des lignes comme '13 avenue ... BAYONNE - Tel : 0559' sont splitées sur ':'."""
Nécessaire car des lignes comme '13 avenue ... CHICAGO - Tel : 0XXX' sont splitées sur ':'."""
def _repl_tel(m: re.Match) -> str:
audit.append(PiiHit(page_idx, "TEL", m.group(0), PLACEHOLDERS["TEL"]))
return PLACEHOLDERS["TEL"]
@@ -1714,7 +1714,7 @@ def _kv_value_only_mask(line: str, audit: List[PiiHit], page_idx: int, cfg: Dict
parts = SPLITTER.split(line, maxsplit=1)
# Une ligne narrative qui se termine par ` ;` ou ` :` produit un split
# avec une "value" vide. La "key" contient alors tout le narratif —
# incluant d'éventuels force_term (`CHCB`, `CONCERTATION`…) qui doivent
# incluant d'éventuels force_term (`CHUXX`, `CONCERTATION`…) qui doivent
# être masqués. Idem si la "key" fait plus de 5 mots : c'est très
# probablement du narratif, pas un libellé `Label : valeur`.
if len(parts) == 2 and parts[1].strip() and len(parts[0].split()) <= 5:
@@ -1811,7 +1811,7 @@ def _extract_trackare_identity(full_text: str) -> Tuple[set, List[PiiHit], set,
for m in re.finditer(r"Pr[ée]nom\s+(?:de\s+naissance|utilis[ée])\s*:\s*([A-ZÉÈÀÙÂÊÎÔÛÄËÏÖÜÇÑ][A-ZÉÈÀÙÂÊÎÔÛÄËÏÖÜÇÑa-zéèàùâêîôûäëïöüçñ\s\-']+?)(?:\s*$)", full_text, re.MULTILINE):
_add_name(m.group(1).strip(), "trackare_prenom", "high")
# Lieu de naissance: BAYONNE, biarritz, 64102, 99999 → masquer comme VILLE
# Lieu de naissance: CHICAGO, springfield, 12345, 99999 → masquer comme VILLE
for m in re.finditer(r"Lieu\s+de\s+naissance\s*:\s*(\S[^\n]*?)(?:\s*$)", full_text, re.MULTILINE):
val = m.group(1).strip()
if val:
@@ -2495,7 +2495,7 @@ def _apply_trackare_hits_to_text(text: str, audit: List[PiiHit], cfg: Dict[str,
escaped = re.escape(original)
# Word boundary pour ne pas casser les mots (ex: ONDANSETRON)
text = re.sub(rf"\b{escaped}\b", placeholder, text)
# Aussi gérer les formats avec astérisques (*640000162*)
# Aussi gérer les formats avec astérisques (*999999999*)
text = re.sub(rf"\*{escaped}\*", placeholder, text)
# Artefacts fréquents dans les DPI scannés : noms de fichiers internes de type
# EXT2-[IPP]-2300249096.TIF. Le suffixe numérique doit être masqué aussi.
@@ -3499,7 +3499,7 @@ def _mask_ville_gazetteers(text: str) -> tuple:
# Après titre médical + nom masqué + tiret/virgule : "Dr [NOM] - VILLE"
r"(?:Dr\.?|Pr\.?|Docteur|Professeur)\s+\[NOM\]\s*[\-,]\s*|"
r"\[NOM\]\s*[\-,(]\s*|"
# Après spécialité médicale : "cardiologue Anglet", "neurologue, DAX"
# Après spécialité médicale : "cardiologue Riverside", "neurologue, DAX"
r"(?:cardiologue|neurologue|radiologue|chirurgien|pneumologue|"
r"gastro-?ent[ée]rologue|oncologue|n[ée]phrologue|urologue|"
r"g[ée]riatre|dermatologue|rhumatologue|ophtalmologue|psychiatre|"
@@ -3512,7 +3512,7 @@ def _mask_ville_gazetteers(text: str) -> tuple:
# Collecter les matches Aho-Corasick
# Construire aussi un index des matches par position de début pour la passe
# "énumération" (passe 2) : une ville dont l'énumération précède un match confirmé
# doit être elle aussi masquée ("Bordeaux et Bayonne" → Bayonne via Bordeaux).
# doit être elle aussi masquée ("Bordeaux et Chicago" → Chicago via Bordeaux).
all_ac_hits: list = [] # [(start, end, orig_span), ...] — tous matches AC avant filtrage
confirmed_hits: set = set() # indices dans all_ac_hits qui ont passé le filtre contextuel
@@ -3539,7 +3539,7 @@ def _mask_ville_gazetteers(text: str) -> tuple:
# Récupérer le texte original à cette position
original_span = text[start_idx:end_idx + 1]
# Extension suffixe CEDEX : si la ville est suivie de " CEDEX" ou " CEDEX N",
# capturer l'ensemble (ex: "BAYONNE CEDEX" → match complet).
# capturer l'ensemble (ex: "CHICAGO CEDEX" → match complet).
_cedex_match = re.match(r"\s+CEDEX(?:\s+\d+)?\b", text[end_idx + 1:end_idx + 20])
if _cedex_match:
ext_len = _cedex_match.end()
@@ -3566,8 +3566,8 @@ def _mask_ville_gazetteers(text: str) -> tuple:
# Passe 2 — énumérations : si deux hits AC sont liés par " et " ou ", ",
# se confirment mutuellement. Cas réels couverts :
# "travaille à Bordeaux et Bayonne" (ancre déjà confirmée propage)
# "Régions : Bordeaux, Bayonne, Biarritz" (aucune ancre, mais chaîne ≥2 villes
# "travaille à Bordeaux et Chicago" (ancre déjà confirmée propage)
# "Régions : Bordeaux, Chicago, Springfield" (aucune ancre, mais chaîne ≥2 villes
# gazetteer en énumération = forte présomption géographique)
# Itération à point fixe pour propager sur des chaînes longues.
def _enum_link(i: int, j: int) -> bool: