"""État de configuration de la GUI V6 (G3-B), testable sans display ni fichiers. Détient les options simples exposées par l'onglet Configuration et sait produire des :class:`EngineSettings`. La résolution des profils s'appuie sur ``profile_defaults`` (jamais modifié) avec injection possible pour les tests. """ from __future__ import annotations from dataclasses import dataclass from pathlib import Path 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: """Options de configuration utilisateur (réglages simples G3-B).""" profile: Optional[str] = None raster_burn: bool = True use_local_ner: bool = True enable_eds: bool = False enable_gliner: bool = False output_dir: Optional[Path] = None ogc_label: Optional[str] = None manual_mask_required: bool = False manual_mask_template: Optional[Path] = None mask_color: str = "#000000" mask_marker_style: str = "brackets" mask_margin_x: int = 2 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, also_make_raster_burn=self.raster_burn, config_path=config_path, use_local_ner=self.use_local_ner, enable_eds=self.enable_eds, enable_gliner=self.enable_gliner, ogc_label=self.ogc_label, profile=self.profile, disabled_kinds=self.disabled_kinds(), ) def list_profile_keys(lister: Optional[Callable[[], dict]] = None) -> List[str]: """Liste triée des clés de profils. Best-effort : [] si indisponible.""" if lister is None: from profile_defaults import list_effective_profiles lister = list_effective_profiles try: return sorted(lister().keys()) except Exception: return [] def default_profile_key(getter: Optional[Callable[[], str]] = None) -> Optional[str]: """Clé du profil par défaut, ou None si indisponible.""" if getter is None: from profile_defaults import get_default_profile_key getter = get_default_profile_key try: return getter() except Exception: return None