diff --git a/anonymizer_core_refactored_onnx.py b/anonymizer_core_refactored_onnx.py index ab70195..be4f5f3 100644 --- a/anonymizer_core_refactored_onnx.py +++ b/anonymizer_core_refactored_onnx.py @@ -710,6 +710,10 @@ RE_VENUE_SEJOUR = re.compile( r"|num[ée]ro\s+de\s+(?:venue|séjour))\s*[:\-]?\s*(\d{4,})", re.IGNORECASE, ) +RE_SCAN_FILENAME_ARTIFACT = re.compile( + r"\b([A-Z]{2,}\d*-)(\[[A-Z_]+\]|[A-Za-z0-9]{6,})-(\d{6,})(\.(?:TIF|TIFF|PDF|JPG|JPEG|PNG))\b", + re.IGNORECASE, +) @dataclass class PiiHit: @@ -2344,6 +2348,17 @@ def _apply_trackare_hits_to_text(text: str, audit: List[PiiHit], cfg: Dict[str, text = re.sub(rf"\b{escaped}\b", placeholder, text) # Aussi gérer les formats avec astérisques (*640000162*) 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. + def _repl_scan_artifact(m: re.Match) -> str: + middle = m.group(2) + if middle.startswith("[") and middle.endswith("]"): + middle_masked = middle + else: + middle_masked = PLACEHOLDERS["IPP"] + return f"{m.group(1)}{middle_masked}-{PLACEHOLDERS['DOSSIER']}{m.group(4)}" + + text = RE_SCAN_FILENAME_ARTIFACT.sub(_repl_scan_artifact, text) return text @@ -2472,6 +2487,14 @@ def anonymise_document_regex(pages_text: List[str], tables_lines: List[List[str] ) for m in _RE_VENUE_REVERSE.finditer(full_raw): audit.append(PiiHit(-1, "NDA", m.group(1), PLACEHOLDERS["NDA"])) + # Variante BACTERIO observée en production : "N° venue" puis DDN / nom, puis + # numéro de venue juste avant la ligne IPP. + _RE_VENUE_BEFORE_IPP = re.compile( + r"N[°o]?\s*venue\s*[:\-]?\s*\n(?:[^\n]*\n){0,3}\s*(\d{6,10})\s*\n\s*(?:I\.?P\.?P\.?|IPP)\s*[:\-]?", + re.IGNORECASE, + ) + for m in _RE_VENUE_BEFORE_IPP.finditer(full_raw): + audit.append(PiiHit(-1, "NDA", m.group(1), PLACEHOLDERS["NDA"])) # Phase 0i : règles d'administration actives sur identifiants. _apply_admin_identifier_hits(full_raw, audit, cfg) @@ -4209,6 +4232,17 @@ def process_pdf( return prefix + PLACEHOLDERS["NOM"] + "/" + PLACEHOLDERS["NOM"] final_text = _RE_REF_INITIALS.sub(_clean_ref_initials, final_text) + # 3e) Layout BACTERIO résiduel : le numéro de venue peut survivre s'il est + # rejeté plusieurs lignes après le libellé, juste avant "IPP : [IPP]". + _RE_FINAL_VENUE_BEFORE_IPP = re.compile( + r"(N[°o]?\s*venue\s*:\s*\n(?:[^\n]*\n){0,6}?)(\d{6,10})(\s*\n\s*IPP\s*:\s*\[IPP\])", + re.IGNORECASE, + ) + def _clean_final_venue_before_ipp(m): + anon.audit.append(PiiHit(-1, "NDA", m.group(2), PLACEHOLDERS["NDA"])) + return m.group(1) + PLACEHOLDERS["NDA"] + m.group(3) + final_text = _RE_FINAL_VENUE_BEFORE_IPP.sub(_clean_final_venue_before_ipp, final_text) + # 4) Consolidation : propager les PII détectés sur toutes les pages (page=-1) # pour que la redaction PDF les cherche partout (sidebar répété, etc.)