docs(beta): plan 1b v3 — re-revue Qwen GO (boucle adversariale 2/2) + 3 correctifs

Re-revue Qwen de l'approche dérivation = GO (dérivation jugée supérieure à la table
figée). Vérif exhaustive maps : 7 VLM + 7 EDS + 8 regex + _GLOBAL dynamique = 22+ kinds
couverts. Mon reverse-map VLM + fallback EDS confirmés sûrs (pas de fuite croisée).
3 correctifs intégrés : (A) site manquant RE_TRACKARE_IAO_MULTILINE_VALUE l.3102 (NOM
Trackare) → Task 3 ; (B) doc convention admin_rules (kind=clé placeholder, branche 3) ;
(C) seuil quarantaine adaptatif 0→1 si catégories décochées (anti fragment TEL). Caveat
UX CP documenté. Convergence Claude+Qwen. Exécution = GO Dom après relecture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-25 23:02:44 +02:00
parent c7c3a86910
commit c77082409d

View File

@@ -12,7 +12,7 @@
- VLM : `VLM_CATEGORY_MAP` (vlm_manager.py:51) `label→(kind, placeholder)` — source de vérité (Qwen ratait `VLM_CP`). - VLM : `VLM_CATEGORY_MAP` (vlm_manager.py:51) `label→(kind, placeholder)` — source de vérité (Qwen ratait `VLM_CP`).
- `_GLOBAL` : `PiiHit(kind=f"{kind}_GLOBAL")` (core:5286) pour `_CRITICAL_PII_TYPES` (core:5245) — **plusieurs** kinds, pas seulement NIR/ADHERENT. - `_GLOBAL` : `PiiHit(kind=f"{kind}_GLOBAL")` (core:5286) pour `_CRITICAL_PII_TYPES` (core:5245) — **plusieurs** kinds, pas seulement NIR/ADHERENT.
- Burn : `_VECTOR/_RASTER_SKIP_KINDS` (core:4564/4723) excluent déjà `EDS_SECU/EDS_TEL/EDS_DATE_NAISSANCE` du PDF. - Burn : `_VECTOR/_RASTER_SKIP_KINDS` (core:4564/4723) excluent déjà `EDS_SECU/EDS_TEL/EDS_DATE_NAISSANCE` du PDF.
- **Décision CP/ZIP** : code postal (`VLM_CP`, `EDS_ZIP`, placeholder `CODE_POSTAL`) = **PAS** dans les 7 toggles → **toujours masqué** (conservateur ; « Adresses » révèle la voie, pas le CP). À confirmer Dom. - **Décision CP/ZIP** : code postal (`VLM_CP`, `EDS_ZIP`, placeholder `CODE_POSTAL`) = **PAS** dans les 7 toggles → **toujours masqué** (conservateur ; « Adresses » révèle la voie, pas le CP). Qwen d'accord (RGPD : le CP identifie le lieu de soin/résidence). **Caveat UX à documenter** : décocher « Adresses » donne un rendu type « 12 rue X [CODE_POSTAL] Ville » (voie en clair, CP masqué) — assumé. À confirmer Dom.
**Référence spec :** `docs/superpowers/specs/2026-06-25-gui-v6-beta-prod-design.md` (chantier D, P1-2, D2/D3 : pas de plancher dur ; `EMAIL/IBAN/IPP/VILLE/FAX/CODE_POSTAL` non toggleables = toujours masqués). **Référence spec :** `docs/superpowers/specs/2026-06-25-gui-v6-beta-prod-design.md` (chantier D, P1-2, D2/D3 : pas de plancher dur ; `EMAIL/IBAN/IPP/VILLE/FAX/CODE_POSTAL` non toggleables = toujours masqués).
@@ -78,7 +78,7 @@ def test_filter_audit_drops_only_disabled():
- [ ] **Step 2 — Run, expect FAIL.** - [ ] **Step 2 — Run, expect FAIL.**
- [ ] **Step 3 — Implement** (after `PLACEHOLDERS`/`CRITICAL_PII_KEYS` ~l.610). Lire `vlm_manager.VLM_CATEGORY_MAP` et `eds_pseudo_manager.EDS_LABEL_MAP` au moment de l'implémentation pour confirmer les noms ; importer ces deux modules en tête du core (imports déjà présents pour VLM ? sinon import paresseux dans le helper). - [ ] **Step 3 — Implement** (after `PLACEHOLDERS`/`CRITICAL_PII_KEYS` ~l.610). Lire `vlm_manager.VLM_CATEGORY_MAP` et `eds_pseudo_manager.EDS_LABEL_MAP` au moment de l'implémentation pour confirmer les noms ; importer ces deux modules en tête du core (imports déjà présents pour VLM ? sinon import paresseux dans le helper). **Convention admin_rules (vérifiée Qwen — documenter, pas de branche supplémentaire)** : `_apply_admin_identifier_hits` (~l.1376) émet des kinds = **clés de `PLACEHOLDERS`** (ex. "NOM", "NIR", "TEL") → captés par la branche 3 (placeholder-self). Un admin_rule à kind custom hors `PLACEHOLDERS``None` → toujours masqué (conservateur, sûr).
```python ```python
# 7 catégories toggleables ↔ type de placeholder. Tout autre placeholder → None (masqué). # 7 catégories toggleables ↔ type de placeholder. Tout autre placeholder → None (masqué).
@@ -149,7 +149,7 @@ Add `disabled_kinds: set = None` kwarg to `process_pdf` (~l.4973). After `cfg =
- [ ] **Step 1 — Failing test.** Refactor inline patterns into `_build_residual_patterns(disabled_kinds)` and test : labels include {NIR,EMAIL,IBAN,TEL} when none disabled ; NIR absent when `{"NIR"}` (EMAIL/IBAN restent) ; TEL absent when `{"TEL"}` ; **et** quand NIR disabled, le pattern TEL ne matche PAS un NIR en clair (test : `_build_residual_patterns({"NIR"})` appliqué à « 1 85 05 74 123 456 78 » → 0 match). - [ ] **Step 1 — Failing test.** Refactor inline patterns into `_build_residual_patterns(disabled_kinds)` and test : labels include {NIR,EMAIL,IBAN,TEL} when none disabled ; NIR absent when `{"NIR"}` (EMAIL/IBAN restent) ; TEL absent when `{"TEL"}` ; **et** quand NIR disabled, le pattern TEL ne matche PAS un NIR en clair (test : `_build_residual_patterns({"NIR"})` appliqué à « 1 85 05 74 123 456 78 » → 0 match).
- [ ] **Step 2 — Run, expect FAIL.** - [ ] **Step 2 — Run, expect FAIL.**
- [ ] **Step 3 — Implement.** `_build_residual_patterns(disabled)` : EMAIL+IBAN toujours ; NIR si `"NIR" not in disabled` ; TEL si `"TEL" not in disabled` ; **quand NIR disabled, le pattern TEL doit exclure les spans de type NIR** (13-15 chiffres groupés) — soit en pré-masquant les spans NIR-like, soit en bornant le pattern TEL. Gate la branche INSEE-names (~l.5470) sous `"NOM" not in disabled`. Brancher `cfg.get("disabled_kinds")`. - [ ] **Step 3 — Implement.** `_build_residual_patterns(disabled)` : EMAIL+IBAN toujours ; NIR si `"NIR" not in disabled` ; TEL si `"TEL" not in disabled` ; **quand NIR disabled, le pattern TEL doit exclure les spans de type NIR** (13-15 chiffres groupés) — soit en pré-masquant les spans NIR-like, soit en bornant le pattern TEL. Gate la branche INSEE-names (~l.5470) sous `"NOM" not in disabled`. Brancher `cfg.get("disabled_kinds")`. **Seuil adaptatif (suggestion re-revue Qwen)** : `SEUIL_RESCAN_RESIDUEL=0` est trop strict quand des catégories sont décochées (un fragment de 8 chiffres peut matcher le pattern TEL résiduel → quarantaine injustifiée) ; passer le seuil à **1 si `disabled_kinds` non vide**, 0 sinon (préserve la rigueur quand tout est activé).
- [ ] **Step 4 — Run, expect PASS.** - [ ] **Step 4 — Run, expect PASS.**
- [ ] **Step 5 — Non-régression :** `.venv/bin/pytest tests/unit/ -q`. - [ ] **Step 5 — Non-régression :** `.venv/bin/pytest tests/unit/ -q`.
- [ ] **Step 6 — Commit :** `git commit -m "feat(core): coordination quarantaine résiduelle NIR/TEL décochés (P1-2/F-4)"` - [ ] **Step 6 — Commit :** `git commit -m "feat(core): coordination quarantaine résiduelle NIR/TEL décochés (P1-2/F-4)"`
@@ -165,7 +165,7 @@ Add `disabled_kinds: set = None` kwarg to `process_pdf` (~l.4973). After `cfg =
- KV distincts (F-2) : `_mask_structured_line` (~2042→ADHERENT/NOM), `_mask_critical_in_key` (~2004→TEL/ADRESSE), `_apply_admin_identifier_hits` (~1376→dynamique : OK via default-deny si kind mappable). - KV distincts (F-2) : `_mask_structured_line` (~2042→ADHERENT/NOM), `_mask_critical_in_key` (~2004→TEL/ADRESSE), `_apply_admin_identifier_hits` (~1376→dynamique : OK via default-deny si kind mappable).
- Noms : `_apply_extracted_names` (~2809→early-return si NOM off). - Noms : `_apply_extracted_names` (~2809→early-return si NOM off).
- **NER — INTRA-BOUCLE par placeholder (F-5, point le plus fragile)** : `_mask_with_hf` (~3136) et `_mask_with_eds_pseudo` (~3208) — **NE PAS** sauter la fonction entière (perdrait ETAB/VILLE) ; sauter **par hit** selon `_category_of(kind)`/placeholder. - **NER — INTRA-BOUCLE par placeholder (F-5, point le plus fragile)** : `_mask_with_hf` (~3136) et `_mask_with_eds_pseudo` (~3208) — **NE PAS** sauter la fonction entière (perdrait ETAB/VILLE) ; sauter **par hit** selon `_category_of(kind)`/placeholder.
- Trackare (F-2) : `_apply_trackare_hits_to_text` (~2909→NIR/DOSSIER) — gate NIR. - Trackare (F-2) : `_apply_trackare_hits_to_text` (~2909→NIR/DOSSIER) — gate NIR ; **`RE_TRACKARE_IAO_MULTILINE_VALUE` (~3102→NOM_FORCE) — gate NOM** (site ajouté re-revue Qwen ; sinon NOM faussé sur docs Trackare IAO).
- selective_rescan (~4159) : DATE_NAISSANCE(4203), ADRESSE(4205-4207), ETAB(4229-4251), ADHERENT(4200-4201), TEL(4191-4193), NIR(4187-4188). - selective_rescan (~4159) : DATE_NAISSANCE(4203), ADRESSE(4205-4207), ETAB(4229-4251), ADHERENT(4200-4201), TEL(4191-4193), NIR(4187-4188).
- Phase-0 multiline : DATE_NAISSANCE(~3014), NIR(~3034). - Phase-0 multiline : DATE_NAISSANCE(~3014), NIR(~3034).
- **Propagation globale step 4e (F-2 #1, F-4)** : boucle `_CRITICAL_PII_TYPES`/`{kind}_GLOBAL` (~5279-5286) — ne pas propager une catégorie désactivée. - **Propagation globale step 4e (F-2 #1, F-4)** : boucle `_CRITICAL_PII_TYPES`/`{kind}_GLOBAL` (~5279-5286) — ne pas propager une catégorie désactivée.