---
from: qwen
to: dom
date: 2026-05-29T10:00:00+02:00
topic: review-pseudocode-Q1-quarantaine
status: open
references:
  - file: inbox/for-dom/2026-05-29_claude_pseudocode-Q1-quarantaine.md
  - file: anonymizer_core_refactored_onnx.py
  - decision: decisions/2026-05-28_dom_no-ui-changes.md
  - decision: decisions/2026-05-28_dom_mvp-livraison-mardi.md
priority: blocker
---

# Review critique du pseudo-code Q-1 (rédigé par Claude)

## Contexte

Claude a rédigé le pseudo-code Q-1 en mon absence (Plan B). J'ai relu le fichier en détail et comparé avec le code source actuel. Voici ma review structurée.

---

## 1. Inventaire des `except: pass` — ✅ BON mais INCOMPLET

Claude en identifie **13** comme critiques. J'en ai compté **~40** `except Exception` dans le core, dont **~20 `pass` purs**. L'inventaire de Claude couvre les chemins de rédaction et d'extraction, mais **il manque des cas critiques sur le rescan et la propagation**.

### Cas manqués par Claude

| # | Fichier:ligne | Fonction | Contexte | Pourquoi critique |
|---|---|---|---|---|
| A | `:4291` | `process_pdf` | `selective_rescan()` | Le rescan de sécurité est dans un `try/except: pass`. Si le rescan rate, des PII résiduelles passent **sans vérification**. C'est le dernier garde-fou avant la sortie. → **Q-DOC** |
| B | `:2720`-`2730` | `_mask_line_by_line` | Filtrage stopwords NER | Les tokens filtrés par stopwords sont silencieux. Si un nom INSEE est dans les stopwords (comme `grand` — voir analyse régression ci-dessous), il passe sans trace. → **L** (mais avec compteur de tokens filtrés) |
| C | `:3857` | `_search_whole_word` | `page.get_text("words")` | Si `get_text("words")` échoue sur une page (PDF corrompu), les rectangles ne sont pas trouvés mais le PDF sort quand même. → **Q-PDF** |
| D | `:4034` | `redact_pdf_raster` | `import re as _re` + OCR words | Bloc entier de traitement OCR/raster dans `try/except: pass`. Si le raster rate, le PDF de sortie n'a pas les masques raster. → **Q-PDF** |
| E | `:1490` | `_mask_line_by_content` | Regex recompilées inline | Les `re.compile()` inline peuvent lever `re.error` sur des patterns mal formés. Actuellement silents. → **L** (warning + skip pattern) |

**Recommandation** : Ajouter A (rescan) et D (raster) comme **Q-PDF/Q-DOC** dans l'inventaire. B et C comme **L** avec compteur.

---

## 2. Mapping action L / Q-PDF / Q-DOC — ✅ PERTINENT avec réserves

### Décision A (texte Q-PDF : output_dir uniquement)

✅ **D'accord avec Claude.** Le texte sort dans `output_dir`, pas de doublon dans `quarantaine/`. L'`INDEX.md` fait le lien. Moins de confusion, un seul emplacement de vérité pour chaque artefact.

### Décision B (fallback raster si vector rate)

✅ **D'accord, mais avec une condition.** Si le vector rate et que le raster réussit :
- Le PDF raster est généré (mais qualité moindre)
- Le flag `partial` reste levé avec raison `pdf_vector_fallback_to_raster`
- L'`INDEX.md` note que le PDF est en qualité raster (prévention opérateur)

C'est un compromis qualité/robustesse acceptable.

### Décision C (seuils 50/3)

⚠️ **À valider sur le corpus.** 50 caractères pour le pré-flight me semble **trop bas**. Un PDF d'une page avec juste un en-tête peut faire 50 chars et contenir des PII non détectées. Je proposerais :

- `SEUIL_TEXTE_MINI = 100` — sous ce seuil, c'est soit un PDF vide, soit un scan non-OCRisé
- `SEUIL_RESCAN_RESIDUEL = 0` — tolérance zéro pour les PII résiduelles après rescan. Si le rescan en trouve ≥ 1 → quarantaine. Le seuil de 3 est trop permissif pour un objectif 99% RGPD.

### Ajout : seuil de confiance NER global

Manque un garde-fou sur la **confiance NER moyenne**. Si le NER retourne une confiance moyenne < 0.5 sur toutes les pages, c'est un signal que le modèle ne fonctionne pas sur ce document (format atypique, langue, etc.). → Flag quarantaine avec raison `ner_low_confidence`.

---

## 3. Structure dossier quarantaine — ✅ BONNE avec un ajustement

La structure proposée est bonne. Un ajustement :

**Ajouter `quarantaine/<docname>.pseudonymise.txt` en copie pour les Q-PDF (partial).**

Raison : si l'opérateur veut inspecter le texte d'un document dont le PDF a échoué, il ne devrait pas avoir à faire la navette entre `output_dir/` et `quarantaine/`. La quarantaine doit être **autoportante** — tout ce qui concerne un document en anomalie est dans un seul dossier.

Contre-argument de Claude (pas de doublon) : valide, mais la copie est cheap (texte) et la clarté opérationnelle prime.

---

## 4. Pseudo-code `process_pdf` — ✅ COUVERTURE BONNE

Le pseudo-code couvre les chemins principaux. **Deux gaps :**

### Gap 1 : pas de gestion du cas `used_ocr = True`

Quand l'OCR est activé (`used_ocr=True`), la confiance de l'OCR n'est pas vérifiée. docTR peut retourner un texte de très mauvaise qualité sur un scan flou. Il faudrait un flag `ocr_low_quality` si le ratio mots détectés / surface page est trop faible.

### Gap 2 : pas de gestion des tables

Les `tables_lines` extraites par PyMuPDF ne passent pas par le pré-flight. Si les tables sont vides mais que le texte principal est OK, le doc sort avec des tables non-anonymisées. → Ajouter un check `if tables_lines and sum(len(t) for t in tables_lines) == 0: log.warning("empty tables extracted")`.

---

## 5. B-1 Métadonnées — ✅ EXCELLENT

L'approche metadata dans `.audit.jsonl` + XMP PDF est la bonne. **Un ajout critique :**

⚠️ **Ne JAMAIS copier les métadonnées source du PDF dans la sortie.** Le pseudo-code de Claude le mentionne (§5.2), mais c'est à renforcer avec un `doc.metadata.clear()` explicite avant `doc.save()`. Les PDF Trackare/CHUXX contiennent souvent le nom du patient dans `author` ou `title`.

---

## 6. B-3 Pré-flight — ✅ BON avec seuil ajusté

Voir §2C ci-dessus. Je recommande `SEUIL_TEXTE_MINI = 100` au lieu de 50.

---

## 7. Helper `_count_residual_pii` — ⚠️ INSUFFISANT

La version de Claude ne couvre que EMAIL, TEL, NIR, IBAN, et noms INSEE. **Il manque :**

- FINESS (8-9 chiffres)
- IPP (alphanumérique)
- OGC (format spécifique)
- Dates de naissance
- Adresses (pattern Rue + CP + Ville)
- RPPS (10 chiffres)

**Recommandation** : réutiliser les regex du leak_scanner (`evaluation/leak_scanner.py`) qui sont déjà complètes. Pas de réinvention.

---

## 8. Tests — ✅ LES TESTS XFAIL EXISTENT

Claude mentionne `test_q1_quarantine.py` avec 10 tests xfail. Je les ai vus dans `tests/unit/`. Bonne base.

**Tests manquants à ajouter :**

1. `test_quarantine_index_md_format` — vérifier que INDEX.md est bien généré avec le bon format
2. `test_errors_log_json_lines` — vérifier que chaque ligne de errors.log est un JSON valide
3. `test_doc_log_per_document` — vérifier que chaque doc a son fichier .log
4. `test_xmp_metadata_no_source_leak` — vérifier que les métadonnées PDF source ne sont pas copiées
5. `test_preflight_text_too_short_boundary` — tester à 49, 50, 51, 100 chars

---

## 9. Résumé des décisions pour Dom

| Décision | Proposition Claude | Avis Qwen | Recommandation finale |
|---|---|---|---|
| A — Texte Q-PDF | output_dir uniquement | ✅ D'accord | output_dir uniquement |
| B — Fallback raster | Oui, si vector rate | ✅ D'accord, avec flag | Oui, flag `pdf_vector_fallback_to_raster` |
| C — Seuil texte mini | 50 chars | ⚠️ Trop bas | **100 chars** |
| C — Seuil rescan | 3 PII résiduelles | ⚠️ Trop permissif | **0 PII résiduelles** (tolérance zéro) |
| D — Copie texte en quarantaine | Non | ⚠️ D'accord partiel | **Oui, copie pour autoportance** |
| E — Inventaire except:pass | 13 cas | ⚠️ Incomplet | **+5 cas manqués** (rescan, raster, stopwords, search, regex inline) |
| F — Métadonnées source PDF | Ne pas copier | ✅ D'accord | **+ clear explicite des metadata source** |

---

## 10. Ordre de travail révisé

1. **Matin** : `quarantine.py` + 5 cas manquants dans l'inventaire — 1h30
2. **Matin** : `redact_pdf_vector:3938` raise au lieu de pass — 30 min
3. **Matin** : `process_pdf:4655` try/flag — 1h
4. **Matin** : Pré-flight B-3 (seuil 100) — 30 min
5. **Après-midi** : Rescan check + `_count_residual_pii` (réutiliser leak_scanner) — 1h30
6. **Après-midi** : XMP metadata + clear source — 30 min
7. **Après-midi** : Metadata `.audit.jsonl` — 30 min
8. **Après-midi** : DocLogger — 30 min
9. **Soir** : Tests — 2h

**Total estimé : ~8h** (au lieu de 4-6h initiales — le gap vient des 5 cas manquants).

---

— Qwen
