Retour Dom : le sous-onglet Masquage séparé créait de la confusion. Le
masquage fait partie de la manière d'anonymiser associée au profil.
- Retrait du sous-onglet « Administration > Masquage » (_SUBTABS, builder,
méthode _build_masquage).
- Section « Profils > Masquage » enrichie : masque manuel requis, template
de masque (lié au profil édité), bouton « Ouvrir l'éditeur de masque »
(fenêtre dédiée) + dossier des templates, et apparence du masque
(couleur, style des marqueurs + aperçu, marges H/V, coins arrondis).
- Le template enregistré depuis l'éditeur remplit désormais le champ
Template du profil (preferred_manual_mask_template via _pro_template_var).
- Profils devient le centre des réglages métier (général/masquage/mots/
moteurs/règles). Réglages inchangé (pas de pastilles, pas de grosse
refonte). Nettoyage du code mort (_REPLACEMENT_CODES, _HELP_MASQUAGE).
261 tests unit OK (0 régression), self-test OK, nav 4 sous-onglets + éditeur
de masque depuis Profils + thème OK. Préserve 72841ed/GO Qwen. Aucun build/
push sans GO Dom.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retour Dom : remplacer la page vitrine par un vrai éditeur de profils.
- gui_v6/profile_editor.py : couche logique (build_profile_spec,
profile_is_editable runtime vs defaut, list_profile_choices, slug_for_copy,
save/set_default/delete) au-dessus de profile_defaults — persistance dans
config/profiles.yml.
- gui_v6/editable_list.py : EditableTermList (tableau scrollable de termes,
ajout/suppression, pas de pastilles) — reste lisible à 50+ termes.
- tab_config : sous-onglet « 👤 Profils » réintroduit comme éditeur — menu
déroulant « Profil à modifier », boutons Nouveau / Dupliquer / Enregistrer /
Annuler / Définir par défaut, sections Identité, Masquage (require_manual_mask,
template), Moteurs (force_disable_vlm), Mots (à masquer/conserver/ignorer
éditables), Règles « à venir ». Profils défaut = lecture seule (dupliquer
pour modifier). Confirmation non bloquante (pas de modale).
- Réglages : bouton « ✏️ Modifier le profil… » → ouvre Profils sur le profil
actif. Pas de pastilles inline.
Persiste : label, description, require_manual_mask, force_disable_vlm,
preferred_manual_mask_template, param_lists (3 listes). 260 tests unit OK
(0 régression), self-test OK, nav 5 sous-onglets + thème OK. Préserve
1bbe70a/d30f7b7. Aucun build/push sans GO Dom.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le module usage_telemetry est maintenant réellement branché : la GUI V6
envoie les statistiques au portail après chaque run (les stats web
restaient vides sans cela).
- processing_runner : RunSummary porte une liste DocResult (ordinal,
page_count via page_count_for, status, duration_ms, extension) — peuplée
dans la boucle. Aucun nom/chemin de fichier.
- usage_telemetry : report_run_summary(summary, base_url, license_ref,
machine_id, session, ...) construit le payload depuis le RunSummary et
l'envoie (non bloquant). N'envoie RIEN sans license_ref. Spool JSONL si
échec réseau.
- tab_usage : _finish() déclenche l'envoi en thread daemon (jamais bloquant
pour l'UI ni le run).
- app : fournit le reporter à UsageTab avec le contexte licence (base_url du
LicenseClient, license_ref via local_status, machine_id, app_version).
Tests : RunSummary.documents peuplé (0 chemin) ; report_run_summary (payload
correct, réseau KO → spool sans crash, pas d'envoi sans licence) ; _finish
appelle le reporter. 252 tests unit OK (0 régression), self-test OK.
V5/moteur/app_aivanov intacts, 0 dépendance. Aucun build/push sans GO Dom.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retour Dom après validation visuelle : simplifier.
- Réglages > Listes locales : suppression des pastilles de termes et des
éditeurs inline (_compact_tag_editor). Remplacés par un texte court +
compteurs (À conserver/À masquer/À ignorer du profil actif) + bouton
« Ouvrir le tableau des termes » qui ouvre DIRECTEMENT TermsTableWindow.
- Retrait du bouton « Voir le profil » (son rôle = accéder au tableau).
- Retrait du sous-onglet « Profils » (doublon non câblé) : _SUBTABS,
builders, _build_profils/_rebuild_profils. Les helpers profil
(_active_profile_summary/_open_terms_table) sont conservés pour Réglages.
- Nettoyage du code mort associé : _compact_tag_editor, constantes
_PRESERVE_TERMS/_MASK_TERMS/_STOPWORDS, textes d'aide qui référençaient
l'onglet Profils.
Chemin utilisateur : Administration > Réglages > Ouvrir le tableau des
termes. 247 tests unit OK (0 régression), self-test OK. Préserve a9e8b2c
(thème, bêta, aide ?, fenêtre tableau). Aucun build/push sans GO Dom.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase A de la mission télémétrie d'usage par client.
- gui_v6/usage_telemetry.py :
- page_count_for(path) : PDF→fitz, image→1, autres→None ; best-effort, ne
lève jamais, ne lit que l'extension (jamais le nom).
- build_usage_payload(...) : compteurs (document/succeeded/failed/total_pages)
+ documents filtrés aux seules clés autorisées (ordinal/page_count/status/
duration_ms/extension) → aucun nom/chemin de fichier ne peut fuir.
- UsageTelemetryClient(session injectée) : report() non bloquant (capture
tout, False en cas d'échec réseau) vers POST /api/v1/usage/report.
- spool JSONL local (spool_payload/flush_spool) pour rejouer les échecs.
Module isolé, non câblé au runner pour l'instant (le branchement fin-de-run
viendra après le backend, hors validation visuelle GUI en cours). Aucun
build/push sans GO Dom. 10 tests unitaires (payload sans nom de fichier,
réseau indispo ne crashe pas, compteurs, page_count PDF mockable).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cinq retours utilisateur sur l'exécutable Windows GUI V6.
- Thème : `_render()` vidait les widgets mais conservait le cache
`_tab_frames`/`_visible_tab` → l'onglet Utilisation se vidait (TclError
sur widget détruit) au changement de thème. Reset du cache dans
`_render()` → onglet actif recréé proprement.
- Onglet principal « Configuration » → « Administration » (clé interne
inchangée).
- Sous-onglet « Règles 2 » → « Règles » (le « 2 » était un badge non
câblé).
- Actions de maquette non câblées (Partage Export/Import, Règles Nouvelle
règle/Recharger/Tester/Fermer) désactivées + suffixe « (à venir) » via
`_mockup_button` : plus aucune action morte qui semble fonctionner.
- Aide « ? » restaurée (façon V5) : `ui_kit.HelpButton`/`help_button`
réutilisable ouvrant une fenêtre d'aide en français simple, posée sur
Utilisation, Administration (Réglages/Masquage/Partage/Règles) et
À propos. Partage : phrase visible + aide expliquant qu'on partage les
réglages, jamais les documents patients.
`tests/unit/test_gui_v6_app_shell.py` : régression thème, libellés,
présence d'aide, navigation. 228 tests unit OK (0 régression), self-test
GUI V6 OK. V5/moteur/app_aivanov non touchés, aucune dépendance ajoutée.
Verdict Qwen requis avant push/build/diffusion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remplace l'éditeur de masquage encastré dans l'onglet Configuration —
jugé inutilisable par Dom (document trop à l'étroit, non défilable) —
par une fenêtre dédiée où le document est majoritaire et réellement
navigable.
- gui_v6/mask_editor_model.py : couche logique pure (rectangles par
page, conversions écran↔PDF, hit-test, sérialisation template)
testable sans display ; réutilise MaskRect/Template de
pdf_mask_designer → format de template inchangé (compat moteur).
- gui_v6/mask_editor_window.py : MaskEditorWindow (CTkToplevel)
redimensionnable — canvas + scrollbars H+V câblées + molette (le
manque qui rendait l'éditeur inutilisable), zoom + ajuster
largeur/page, navigation pages, rectangles au glisser-déposer,
sélection (clic) + suppression (Suppr / clic-droit), templates
JSON/YAML, mode aperçu d'exemple sans PDF.
- tab_config.py : l'onglet Masquage lance la fenêtre dédiée ; retrait
du canvas encastré et de ~290 lignes de code mort associé.
- tests/unit/test_gui_v6_mask_editor.py : 13 tests logique + 3 smoke
headless (scrollbars, ajout/sélection/suppression, save/load
roundtrip, câblage onglet→fenêtre).
Sans nouvelle dépendance. V5, moteur et app_aivanov non touchés.
221 tests unit OK (0 régression), self-test GUI V6 OK.
Verdict Qwen requis avant push/build/diffusion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Trois détecteurs simples « layout/context-aware » (chantier v11.5 P0),
validés par 2 revues Codex + 10 tests adversariaux Qwen, 0 régression :
- RE_ADRESSE réécrit en grammaire de tokens (_RE_VOIE_TYPE + _RE_VOIE_TOKEN) :
capture initiales (« J. Loeb »), voies commémoratives à chiffres
(« 8 Mai 1945 »), apostrophes ' et ’, bornage à la ligne courante,
arrêt sur point post-mot (anti-débordement clinique).
- _mask_ville_gazetteers : retourne toujours un tuple (texte, liste) même
sans Aho-Corasick ; masque les communes Saint/St/Sainte/Ste multi-mots à
espaces (« St Martin de Hinx ») entièrement, sans exiger de contexte géo.
- DATE_NAISSANCE retiré de la propagation globale + DATE_NAISSANCE_GLOBAL
ajouté aux skip vector/raster : on ne masque plus une date nue sur tout le
document. La DDN reste masquée en contexte fort, page par page. Les dates
cliniques identiques à la DDN hors contexte sont préservées.
tests/unit/test_p0_layout_detectors.py : 38 tests dédiés (matrice adresse
générique, anti-FP, communes Saint, propagation DDN, 10 tests adversariaux
Qwen). Suite tests/unit complète : 147 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Configure numerical library and torch threading for H1, keep raster threading/timing instrumentation, remove CONCERTATION from forced masks after real PDF FP testing, and record coordination archive state.
Application du stash@{0} resté en WIP depuis le 27/04 :
"On main: wip-gui-profils-masque-manuel-build-windows-2026-04-27"
## Apport
- Pseudonymisation_Gui_V5.py (+1208 lignes) : profils, panneau paramètres
avancés, éditeur de masques intégré, gestion whitelist/blacklist
- launcher.py (+315) : splash natif PyInstaller, single-instance,
téléchargement modèles
- anonymisation_onefile.spec : config PyInstaller mise à jour
- pdf_mask_designer.py (+114) : éditeur de masques amélioré
- config_defaults.py (+23) : constantes nouvelles
- tests/unit/test_config_externalization.py (+12) : tests config
- .gitignore (+5)
## Pourquoi
La version courante de la GUI sur la branche feature manquait :
- L'éditeur de masques
- Les profils
- Le panneau paramètres avancés
- Le splash natif au démarrage
Aucun conflit avec mes 10 commits Q-1 (pas de chevauchement de fichiers).
## Validation
75 passed, 10 xfailed sur pytest tests/unit/.
## Note
Le stash reste disponible dans `git stash list` jusqu'à drop explicite.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Trois fixes qui font passer 009_multi_etablissements en vert et
ferment la liste des fuites identifiées par la couche 2.
#3 — `Centre Hospitalier Universitaire de Bordeaux` coupé sur deux lignes
Nouveau pattern `RE_ETAB_LINEBREAK` (strict) en pré-passe sur la page
entière, juste avant le découpage en lignes. Match `<TYPE>\n<suite>`
avec :
- TYPE limité (Centre Hospitalier, Hôpital, Clinique, Polyclinique,
CHU, CHRU, CHS) ;
- un seul `\n` autorisé entre TYPE et suite ;
- la suite démarre obligatoirement par un connecteur typique
(Universitaire, de, d', du, des, la, le, les) puis UN nom propre.
Évite le FP `CENTRE HOSPITALIER COTE BASQUE\nService d'anesthésie`
(le `\n` n'est pas immédiat après le type, donc pas de match).
#4 — `CHCB` en fin de phrase suivi de ` ;`
`_kv_value_only_mask` splittait `transféré au CHCB pour la rééducation ;`
sur le `;` du `SPLITTER` (`\s*[:|;\t]\s*`), produisant une value vide.
La key contenait CHCB mais n'était passée qu'à `_mask_critical_in_key`
qui ne couvre pas les force_terms admin_rules.
Fix : fallback sur `_mask_line_by_regex(line)` (qui appelle
`_apply_overrides` → force_terms) si la value est vide ou la key
dépasse 5 mots (heuristique narrative).
#5 — `Biarritz` non masqué après `[ETABLISSEMENT] à Biarritz`
`_mask_ville_gazetteers` skippait par sécurité toute ville détectée
juste après un placeholder établissement précédé de `de/du/d'/à`. Le
`à` était inclus pour éviter les FP, mais c'est la préposition de
LOCALISATION par excellence : `Clinique Aguilera à Biarritz` perd
Biarritz à tort. Restreint le skip à `de/du/d'` (qui sont des parties
de nom d'établissement type `CHU de Bordeaux`). `à` reste actif.
Couche 2 entièrement verte : 73 passed, 0 xfailed (avant : 72 + 1
xfailed). KNOWN_FAILURES vidé. La gate pytest est désormais le
contrat de non-régression sur 10 documents complets.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Étend `RE_RPPS` pour tolérer 0 à 3 mots qualificateurs entre `RPPS`
et le séparateur `:` ou `-`. Couvre les variantes observées :
- RPPS prescripteur :
- RPPS du médecin signataire :
- RPPS de garde -
- N° RPPS :
Si un qualificateur est présent, le séparateur (`:` ou `-`) devient
obligatoire pour éviter d'aspirer du narratif (faux positif type
"Le RPPS est consulté pour vérifier 12345678901 dans la base").
La lambda `_repl_rpps` reconstruit `RPPS : [RPPS]` en sortie : le
qualificateur est consommé mais perdu (pas de fuite, choix cosmétique).
Cas 005_bacterio_complete passe désormais (retiré de KNOWN_FAILURES).
La fuite `10101010101` derrière `RPPS prescripteur :` est masquée.
Cohérent avec le cadrage section 10.1 (règle cœur générique
applicable à tout établissement de santé français — pas de
spécificité locale).
Tests : 72 passed, 1 xfailed (avant : 71 passed, 2 xfailed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Trois nouveaux patterns cœur dans `_mask_structured_line` pour des
labels génériques qui n'étaient pas couverts par le pipeline kv_value
(le split key:value laissait fuir la valeur quand le label dépassait
les patterns existants `RE_EXTRACT_NOM_NAISSANCE`, `RE_EXTRACT_PRENOM`,
`RE_EXTRACT_VILLE_RESIDENCE`).
`RE_LABEL_NOM_VARIANTES` capture :
- Nom de jeune fille / de famille / de naissance(.)
- Nom d'usage / Nom marital / Nom marié
`RE_LABEL_PRENOM` capture :
- Prénom : / Prénoms : / Prénom de naissance / utilisé(e) / usuel
- Capture jusqu'à fin de ligne pour les énumérations virgulées
(Prénoms : Sabine, Marie → tout masqué).
`RE_LABEL_VILLE` capture :
- Ville : / Ville de résidence : / Ville de naissance :
- Capture jusqu'à fin de ligne (gère "Saint-Jean-de-Luz",
"Saint-Denis (974)", composés multi-tokens).
Effets de bord positifs :
- Le bug "Saint-Jean-de-Luz → [ETABLISSEMENT]-de-Luz" est corrigé :
le matcher `RE_LABEL_VILLE` masque toute la valeur en `[VILLE]`
AVANT que le gazetteer FINESS Aho-Corasick ne grignote "Saint-Jean".
Cas 006_trackare_soignants et 008_anesthesie_complete : alignement
des expected.txt sur cette amélioration.
Choix d'architecture (cf cadrage docs/cadrage-projet-anonymisation.md
section 10.1) : ces labels sont des règles cœur génériques applicables
à tout établissement de santé français. Légitimes en hardcodé. Les
patterns layout-specific (Bordeaux suffixe, CHCB en fin de phrase,
email cassé par force_term) seront branchés via admin_rules dans
l'étape suivante.
Cas 010_fiche_admission_minimale passe désormais (retiré de
KNOWN_FAILURES). Le xfail strict aurait signalé xpass.
Tests : 9 passed, 2 xfailed (avant : 8 passed, 3 xfailed sur
test_synthetic_review).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Couche 2 (revue humaine sur documents complets) : ajout de 6 cas
synthétiques pour atteindre la cible cadrage produit (10 cas).
Cas ajoutés :
- 005_bacterio_complete : layout BACTERIO N° venue rejeté avant IPP
+ RPPS prescripteur (pattern qualifié non détecté).
- 006_trackare_soignants : export Trackare avec activités HH:MM NOM,
Note IDE/médicale, Signé — médicament greedy.
- 007_lettre_sortie_complete : courrier médecin→médecin, multi-villes,
email institutionnel @chcb.fr (cassé par le force_term CHCB).
- 008_anesthesie_complete : protocole anesthésique avec molécules
BDPM, prénoms basques rares (Maddi, Pantxoa).
- 009_multi_etablissements : 3 établissements distincts (CHCB, CHU
Bordeaux, Clinique Aguilera), prénoms basques avec ñ (Beñat).
- 010_fiche_admission_minimale : fiche administrative dense, labels
variés (Nom de jeune fille :, Prénom :, Ville :, Mutuelle :).
Gate pytest (tests/unit/test_synthetic_review.py) :
- vérifie l'inventaire (10 cas) et fait passer chaque cas via run_case.
- 3 cas marqués xfail(strict=True) pour révéler 9 fuites de PII et
2 patterns partiels que le moteur ne couvre pas aujourd'hui :
* 005 — RPPS avec qualificateur (RPPS prescripteur :)
* 009 — Bordeaux résiduel après [ETAB], CHCB en fin de phrase,
Biarritz sur ligne Ville :, ñ qui casse Beñat → [NOM]ñat
* 010 — Nom de jeune fille / Prénom / Ville sans label "Patient :",
NIR au format espacé partiellement consommé en TEL,
numéro de mutuelle MGEN non couvert
- xfail strict force pytest à signaler un xpass quand un fix passe :
rappel automatique de retirer l'entrée de KNOWN_FAILURES.
Le runner tools/run_synthetic_review_corpus.py reste utilisable en
direct (sortie diff/audit/summary) pour la revue humaine. Les sorties
actual/ sont gitignorées (régénérées à chaque exécution).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Script demo_evaluation.py montrant tous les outils
- Correction test flottant dans test_quality_evaluator.py
- Installation pytest/pytest-cov
- Tous les tests passent (16/16)