fix(detect): masquer artefacts noms de fichiers DPI et variante BACTERIO N° venue

- RE_SCAN_FILENAME_ARTIFACT : masque le suffixe numérique des noms de
  fichiers internes type EXT2-[IPP]-2300249096.TIF qui fuyaient en sortie.
- _RE_VENUE_BEFORE_IPP : variante BACTERIO observée en production où
  le N° venue est rejeté plusieurs lignes après le libellé, juste
  avant IPP. Détection en phase 0i.
- _RE_FINAL_VENUE_BEFORE_IPP : nettoyage final pour le résiduel du
  même layout BACTERIO si le numéro a survécu jusqu'à process_pdf.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-27 16:17:36 +02:00
parent bc24a21fea
commit 7403811c62

View File

@@ -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.)