feat(q1): E - B-3 preflight text too short, quarantine direct

Étape E du sprint Q-1 — B-3 pré-flight.

Si extract_text_with_fallback_ocr retourne moins de SEUIL_TEXTE_MINI
(=100) caractères :
- log.warning systématique
- Si quarantine_mgr fourni : flag preflight_text_too_short (severity=full),
  copie du PDF original dans quarantine_dir/ pour ré-essai manuel
- Return {} (pas de sortie texte/audit/PDF pour ce doc)

Couvre les cas : scan non-OCRisé, PDF vide, OCR raté.

Évite le pire scénario : un opérateur qui croit que son document est
anonymisé alors qu'aucune PII n'a même été détectée parce qu'il n'y
avait pas de texte à traiter.

Rétro-compat préservée : sans quarantine_mgr, le comportement reste
"return {}" + log au lieu du silence (toujours strictement meilleur).

Risque appelants : un caller qui suppose la présence des clés "text"/
"audit" dans le retour doit gérer le cas dict vide. À voir au runtime.

Ref: docs/coordination/inbox/for-dom/2026-05-29_consolide_pseudocode-Q1-v2.md §8

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 21:39:47 +02:00
parent 0d20d131ee
commit 217fc75983

View File

@@ -4279,6 +4279,30 @@ def process_pdf(
cfg = load_dictionaries(config_path) cfg = load_dictionaries(config_path)
pages_text, tables_lines, ocr_used, ocr_word_map = extract_text_with_fallback_ocr(pdf_path) pages_text, tables_lines, ocr_used, ocr_word_map = extract_text_with_fallback_ocr(pdf_path)
# Q-1 B-3 : pré-flight texte vide. Si moins de SEUIL_TEXTE_MINI caractères
# extraits, c'est probablement un scan non-OCRisé ou un document corrompu.
# On NE traite PAS — quarantaine totale, le doc original est copié pour
# ré-essai manuel.
extracted_chars = sum(len(p) for p in pages_text)
if extracted_chars < SEUIL_TEXTE_MINI:
log.warning("Preflight failed for %s: only %d chars extracted (seuil=%d)",
pdf_path.name, extracted_chars, SEUIL_TEXTE_MINI)
if quarantine_mgr is not None:
quarantine_mgr.flag(
doc_name=pdf_path.stem,
reason="preflight_text_too_short",
detail=f"Only {extracted_chars} chars extracted from {len(pages_text)} pages (seuil={SEUIL_TEXTE_MINI})",
severity="full",
extracted_chars=extracted_chars,
)
try:
quarantine_mgr.quarantine_dir.mkdir(parents=True, exist_ok=True)
shutil.copy(pdf_path, quarantine_mgr.quarantine_dir / pdf_path.name)
except Exception as copy_err:
log.warning("Could not copy original PDF to quarantine for %s: %s",
pdf_path.name, copy_err)
return {}
# 1) Regex rules + NER-first cross-validation # 1) Regex rules + NER-first cross-validation
# Passer les NER managers pour que anonymise_document_regex exécute le NER # Passer les NER managers pour que anonymise_document_regex exécute le NER
# sur le texte original (non masqué) et valide les noms extraits par regex # sur le texte original (non masqué) et valide les noms extraits par regex