feat(gui): câbler les 7 toggles catégories au moteur (P1-2)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-26 11:26:47 +02:00
parent daec1f53bd
commit bf832e12f0
5 changed files with 228 additions and 14 deletions

View File

@@ -9,10 +9,36 @@ from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Callable, List, Optional
from typing import Callable, FrozenSet, List, Optional
from gui_v6.engine_bridge import EngineSettings
# Mapping centralisé champ ConfigState → CATÉGORIE moteur (Plan 1b / P1-2).
#
# Les 7 catégories doivent matcher EXACTEMENT le set accepté par
# ``anonymizer_core_refactored_onnx.process_pdf(disabled_kinds=...)`` :
# {"NOM", "DATE_NAISSANCE", "ETAB", "ADRESSE", "NIR", "TEL", "ADHERENT"}.
#
# Sémantique des booléens ``detect_*`` : True = « détecter cette catégorie »
# (= masquer, comportement par défaut). False = laisser en clair → la catégorie
# entre dans ``disabled_kinds``. Note : CODE_POSTAL suit le toggle ADRESSE côté
# moteur (décision Dom 2026-06-26), aucun toggle dédié n'est exposé.
#
# L'ordre suit les 7 lignes de ``tab_config._DETECTION_OPTIONS`` :
# Noms/prénoms · Dates de naissance · Établissements · Adresses/CP ·
# N° sécurité sociale · Téléphones/e-mails · N° adhérent mutuelle.
CATEGORY_FIELDS = {
"detect_nom": "NOM",
"detect_date_naissance": "DATE_NAISSANCE",
"detect_etab": "ETAB",
"detect_adresse": "ADRESSE",
"detect_nir": "NIR",
"detect_tel": "TEL",
"detect_adherent": "ADHERENT",
}
# Catégories canoniques (ordre = ordre des toggles UI).
DETECTION_CATEGORIES = tuple(CATEGORY_FIELDS.values())
@dataclass
class ConfigState:
@@ -33,6 +59,26 @@ class ConfigState:
mask_margin_y: int = 1
mask_rounded_corners: bool = False
# 7 toggles « Données à détecter » — tous ON par défaut (zéro changement).
detect_nom: bool = True
detect_date_naissance: bool = True
detect_etab: bool = True
detect_adresse: bool = True
detect_nir: bool = True
detect_tel: bool = True
detect_adherent: bool = True
def disabled_kinds(self) -> FrozenSet[str]:
"""Set des CATÉGORIES décochées (laissées en clair).
Défaut (tous les toggles ON) ⇒ ``frozenset()`` (no-op moteur).
"""
return frozenset(
category
for field_name, category in CATEGORY_FIELDS.items()
if not getattr(self, field_name)
)
def to_engine_settings(self, config_path: Optional[Path] = None) -> EngineSettings:
return EngineSettings(
make_vector_redaction=False,
@@ -43,6 +89,7 @@ class ConfigState:
enable_gliner=self.enable_gliner,
ogc_label=self.ogc_label,
profile=self.profile,
disabled_kinds=self.disabled_kinds(),
)

View File

@@ -19,10 +19,10 @@ Aucune logique de détection ici : on orchestre uniquement.
from __future__ import annotations
import logging
from dataclasses import dataclass
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any, Callable, Dict, Optional
from typing import Any, Callable, Dict, FrozenSet, Optional
from engine_capabilities import capabilities_map
@@ -61,6 +61,9 @@ class EngineSettings:
enable_gliner: bool = False
ogc_label: Optional[str] = None
profile: Optional[str] = None
# Plan 1b (P1-2) — set des CATÉGORIES laissées en clair (toggles décochés).
# Vide par défaut ⇒ aucun changement de comportement (tout est masqué).
disabled_kinds: FrozenSet[str] = field(default_factory=frozenset)
def _default_factories() -> dict[str, ManagerFactory]:
@@ -206,6 +209,8 @@ def build_engine_kwargs(
"also_make_raster_burn": settings.also_make_raster_burn,
"config_path": settings.config_path,
"ogc_label": settings.ogc_label,
# Plan 1b (P1-2) — catégories décochées laissées en clair (set vide = no-op).
"disabled_kinds": frozenset(settings.disabled_kinds or ()),
}
if managers is not None and settings.use_local_ner:
kwargs.update(managers.as_kwargs())

View File

@@ -26,14 +26,17 @@ _SUBTABS = [
("shr", "🔄 Partage"),
]
# Chaque ligne = (libellé, aide, champ ConfigState). Le champ relie le toggle
# à la catégorie moteur (cf. gui_v6.config_state.CATEGORY_FIELDS). ON = détecter
# (masquer) ; OFF = laisser en clair (entre dans disabled_kinds).
_DETECTION_OPTIONS = [
("Noms et prénoms", "Annuaire + IA"),
("Dates de naissance", "Contexte naissance"),
("Établissements", "FINESS + contexte"),
("Adresses / CP", "Voie, ville, code"),
("N° sécurité sociale", "NIR"),
("Téléphones / e-mails", "Contact"),
("N° adhérent mutuelle", "Identifiant local"),
("Noms et prénoms", "Annuaire + IA", "detect_nom"),
("Dates de naissance", "Contexte naissance", "detect_date_naissance"),
("Établissements", "FINESS + contexte", "detect_etab"),
("Adresses / CP", "Voie, ville, code", "detect_adresse"),
("N° sécurité sociale", "NIR", "detect_nir"),
("Téléphones / e-mails", "Contact", "detect_tel"),
("N° adhérent mutuelle", "Identifiant local", "detect_adherent"),
]
_MASK_COLORS = [
@@ -353,8 +356,20 @@ class ConfigTab(ctk.CTkFrame):
help_text=_HELP_DONNEES_DETECTER, help_title="Données à détecter",
)
det.pack(fill="both", expand=True)
for label, hint in _DETECTION_OPTIONS:
self._mini_toggle(det, label, hint, value=True).pack(fill="x", padx=12, pady=1)
# Les 7 toggles « Données à détecter » sont câblés sur les booléens
# detect_* de ConfigState (lecture initiale + écriture au changement).
# ON = détecter/masquer ; OFF = laisser en clair (→ disabled_kinds).
self._detect_toggles: dict[str, object] = {}
for label, hint, field_name in _DETECTION_OPTIONS:
toggle = self._mini_toggle(
det,
label,
hint,
value=bool(getattr(self._state, field_name)),
command=lambda f=field_name: self._on_detect_toggle(f),
)
toggle.pack(fill="x", padx=12, pady=1)
self._detect_toggles[field_name] = toggle
ner = ui_kit.Card(
cols[1], p, title="🧠 Moteurs et masques",
@@ -865,6 +880,16 @@ class ConfigTab(ctk.CTkFrame):
def _on_profile(self, value: str) -> None:
self._state.profile = value
def _on_detect_toggle(self, field_name: str) -> None:
"""Recopie l'état d'un toggle « Données à détecter » dans ConfigState.
ON = détecter (masquer) ; OFF = laisser en clair. ``disabled_kinds()``
de ConfigState dérive ensuite le set des catégories désactivées.
"""
toggle = self._detect_toggles.get(field_name)
if toggle is not None:
setattr(self._state, field_name, bool(toggle.get()))
def _on_ner(self) -> None:
self._state.use_local_ner = self._tog_ner.get()