feat(gui): addenda Dom GUI V6 — sous-onglet Profils, libellés, aide, bêta
Suite des retours Dom sur la GUI V6 (par-dessus 6a0a581).
Addendum Profils / Réglages :
- Nouveau sous-onglet Administration « 👤 Profils » : le profil actif devient
un objet lisible (nom, description, masque requis, template, listes locales
avec compteurs) — données réelles lues depuis profile_defaults.
- Fenêtre « Tableau des termes » (terms_table_window.py) : table scrollable
avec recherche/filtre, colonnes Type/Terme/Source ; reste lisible à 50+
termes. Ajouter/éditer/supprimer désactivés « (à venir) » (écriture par
profil non câblée).
- Réglages : « Profil métier » → « Profil d'anonymisation », « Sortie… » →
« Dossier de sortie… » (+ infobulle), hints moteurs (standard/optionnel/
plus lent), bouton « Voir le profil », « Ouvrir le tableau des termes ».
- Aide « ? » + infobulles (ui_kit.attach_tooltip) près des éléments ambigus.
- profile_view.py : logique pure (résumé profil + lignes du tableau),
testable sans display.
Addendum bêta : en-tête « aivanonym » + badge « bêta », titre fenêtre
« … — bêta ». Détail version conservé dans À propos.
tests/unit/test_gui_v6_profiles.py + ajouts shell. 237 tests unit OK
(228 → 237, 0 régression), self-test GUI V6 OK, navigation des 5 sous-onglets
+ thème OK. V5/moteur/app_aivanov/profile_defaults non touchés, 0 dépendance.
Aucun build/push sans GO Dom — validation visuelle Dom attendue.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ from manual_masking import ensure_mask_templates_dir, list_mask_templates, mask_
|
||||
|
||||
_SUBTABS = [
|
||||
("reg", "⚙️ Réglages"),
|
||||
("pro", "👤 Profils"),
|
||||
("msk", "🎭 Masquage"),
|
||||
("shr", "🔄 Partage"),
|
||||
("rul", "🛡️ Règles"),
|
||||
@@ -65,7 +66,7 @@ MANUAL_MASK_NONE_LABEL = "Aucun masque manuel"
|
||||
# Textes d'aide « ? » (français simple, pour utilisateurs non informaticiens).
|
||||
_HELP_REGLAGES = (
|
||||
"Réglages de l'anonymisation.\n\n"
|
||||
"• Profil métier : choisit un jeu de réglages adapté à votre service.\n"
|
||||
"• Profil d'anonymisation : choisit un jeu de réglages adapté à votre usage.\n"
|
||||
"• Moteurs NER : les modèles qui détectent les noms et données personnelles.\n"
|
||||
"• Données à détecter : ce qui sera masqué (noms, dates de naissance, etc.).\n"
|
||||
"• Listes locales : vos termes à toujours masquer ou toujours conserver.\n\n"
|
||||
@@ -95,10 +96,33 @@ _HELP_REGLES = (
|
||||
"Cette section est en cours de finalisation : les actions marquées « à venir » "
|
||||
"ne sont pas encore disponibles."
|
||||
)
|
||||
_HELP_PROFIL = (
|
||||
"Un profil d'anonymisation regroupe tous les réglages adaptés à un usage "
|
||||
"(ex. : interne standard, diffusion prudente, recherche…).\n\n"
|
||||
"Il définit les moteurs utilisés, les données détectées, les termes à conserver "
|
||||
"ou à masquer, et si un masque manuel est requis.\n\n"
|
||||
"Choisissez un profil ici, et consultez son détail dans l'onglet « Profils »."
|
||||
)
|
||||
_HELP_MOTEURS = (
|
||||
"Les moteurs détectent les données personnelles.\n\n"
|
||||
"• CamemBERT-bio : moteur standard, rapide et fiable — activé par défaut.\n"
|
||||
"• EDS-Pseudo et GLiNER : optionnels. Ils renforcent la détection mais sont "
|
||||
"plus lents et ne sont pas toujours installés sur le poste.\n\n"
|
||||
"Si tout n'est pas coché, c'est que les moteurs optionnels ne sont pas requis "
|
||||
"par le profil ou pas disponibles."
|
||||
)
|
||||
_HELP_LISTES = (
|
||||
"Les listes locales personnalisent la détection pour votre établissement :\n\n"
|
||||
"• À conserver : termes à ne jamais masquer (vocabulaire métier).\n"
|
||||
"• À masquer : termes à toujours masquer (sigles, en-têtes…).\n"
|
||||
"• À ignorer : mots à ne pas considérer.\n\n"
|
||||
"Pour une liste longue, ouvrez le tableau des termes (onglet « Profils ») : "
|
||||
"il reste lisible et permet la recherche."
|
||||
)
|
||||
|
||||
CONFIG_MOCKUP_SECTIONS = {
|
||||
"reglages": [
|
||||
"Profil métier",
|
||||
"Profil d'anonymisation",
|
||||
"Moteurs NER",
|
||||
"Données à détecter",
|
||||
"Termes à toujours conserver",
|
||||
@@ -202,6 +226,7 @@ class ConfigTab(ctk.CTkFrame):
|
||||
|
||||
builders = {
|
||||
"reg": self._build_reglages,
|
||||
"pro": self._build_profils,
|
||||
"msk": self._build_masquage,
|
||||
"shr": self._build_partage,
|
||||
"rul": self._build_regles,
|
||||
@@ -251,7 +276,7 @@ class ConfigTab(ctk.CTkFrame):
|
||||
current = self._state.profile or default_profile_key() or (profiles[0] if profiles else "")
|
||||
self._state.profile = current or None
|
||||
|
||||
ctk.CTkLabel(top, text="Profil métier", text_color=p["text_dim"], font=ui_kit.font(11, "bold")).pack(
|
||||
ctk.CTkLabel(top, text="Profil d'anonymisation", text_color=p["text_dim"], font=ui_kit.font(11, "bold")).pack(
|
||||
side="left", padx=(12, 8), pady=10
|
||||
)
|
||||
self._profile_menu = ctk.CTkOptionMenu(
|
||||
@@ -268,9 +293,15 @@ class ConfigTab(ctk.CTkFrame):
|
||||
if current:
|
||||
self._profile_menu.set(current)
|
||||
self._profile_menu.pack(side="left", pady=10)
|
||||
ui_kit.help_button(top, p, _HELP_PROFIL, title="Profil d'anonymisation").pack(side="left", padx=(6, 0), pady=10)
|
||||
ui_kit.secondary_button(top, p, "👤 Voir le profil", command=lambda: self._show_sub("pro")).pack(
|
||||
side="left", padx=(10, 4), pady=10
|
||||
)
|
||||
|
||||
ui_kit.secondary_button(top, p, "📁 Sortie…", command=self._pick_output).pack(
|
||||
side="left", padx=(12, 6), pady=10
|
||||
sortie = ui_kit.secondary_button(top, p, "📁 Dossier de sortie…", command=self._pick_output)
|
||||
sortie.pack(side="left", padx=(6, 6), pady=10)
|
||||
ui_kit.attach_tooltip(
|
||||
sortie, "Dossier où seront écrits les documents anonymisés.\nRéglage local de traitement (pas une règle du profil)."
|
||||
)
|
||||
self._out_label = ctk.CTkLabel(
|
||||
top,
|
||||
@@ -289,16 +320,22 @@ class ConfigTab(ctk.CTkFrame):
|
||||
|
||||
ner = ui_kit.Card(cols[1], p, title="🧠 Moteurs et masques")
|
||||
ner.pack(fill="both", expand=True)
|
||||
hint_row = ctk.CTkFrame(ner, fg_color="transparent")
|
||||
hint_row.pack(fill="x", padx=12, pady=(0, 2))
|
||||
ctk.CTkLabel(
|
||||
hint_row, text="Pourquoi pas tout coché ?", text_color=p["text_muted"], font=ui_kit.font(11), anchor="w"
|
||||
).pack(side="left")
|
||||
ui_kit.help_button(hint_row, p, _HELP_MOTEURS, title="Moteurs de détection").pack(side="right")
|
||||
self._tog_ner = self._mini_toggle(
|
||||
ner, "CamemBERT-bio", "rapide · F1 0.963", value=self._state.use_local_ner, command=self._on_ner
|
||||
ner, "CamemBERT-bio", "standard · rapide · F1 0.963", value=self._state.use_local_ner, command=self._on_ner
|
||||
)
|
||||
self._tog_ner.pack(fill="x", padx=12, pady=1)
|
||||
self._tog_eds = self._mini_toggle(
|
||||
ner, "EDS-Pseudo", "médical français", value=self._state.enable_eds, command=self._on_eds
|
||||
ner, "EDS-Pseudo", "optionnel · médical français · plus lent", value=self._state.enable_eds, command=self._on_eds
|
||||
)
|
||||
self._tog_eds.pack(fill="x", padx=12, pady=1)
|
||||
self._tog_gli = self._mini_toggle(
|
||||
ner, "GLiNER", "vote croisé", value=self._state.enable_gliner, command=self._on_gliner
|
||||
ner, "GLiNER", "optionnel · vote croisé · plus lent", value=self._state.enable_gliner, command=self._on_gliner
|
||||
)
|
||||
self._tog_gli.pack(fill="x", padx=12, pady=1)
|
||||
self._mini_toggle(
|
||||
@@ -340,9 +377,115 @@ class ConfigTab(ctk.CTkFrame):
|
||||
|
||||
terms = ui_kit.Card(cols[2], p, title="✅ Listes locales")
|
||||
terms.pack(fill="both", expand=True)
|
||||
terms_help = ctk.CTkFrame(terms, fg_color="transparent")
|
||||
terms_help.pack(fill="x", padx=12, pady=(0, 2))
|
||||
ctk.CTkLabel(
|
||||
terms_help, text="Termes propres à votre établissement", text_color=p["text_muted"], font=ui_kit.font(11), anchor="w"
|
||||
).pack(side="left")
|
||||
ui_kit.help_button(terms_help, p, _HELP_LISTES, title="Listes locales").pack(side="right")
|
||||
self._compact_tag_editor(terms, "Termes à conserver", "Ex : FUROSEMIDE", _PRESERVE_TERMS, "keep")
|
||||
self._compact_tag_editor(terms, "Termes à masquer", "Ex : CHUXX", _MASK_TERMS, "mask")
|
||||
self._compact_tag_editor(terms, "Mots à ignorer", "Ex : prescription", _STOPWORDS, "stop")
|
||||
ctk.CTkButton(
|
||||
terms,
|
||||
text="📋 Ouvrir le tableau des termes",
|
||||
command=lambda: self._show_sub("pro"),
|
||||
fg_color=p["btn_sec_bg"],
|
||||
hover_color=p["card_border"],
|
||||
text_color=p["text"],
|
||||
border_color=p["btn_sec_border"],
|
||||
border_width=1,
|
||||
corner_radius=8,
|
||||
height=30,
|
||||
font=ui_kit.font(12),
|
||||
).pack(fill="x", padx=12, pady=(6, 12))
|
||||
|
||||
# -- Profils ----------------------------------------------------------
|
||||
|
||||
def _active_profile_dict(self) -> dict:
|
||||
try:
|
||||
from profile_defaults import list_effective_profiles
|
||||
|
||||
key = self._state.profile or default_profile_key()
|
||||
if not key:
|
||||
return {}
|
||||
return list_effective_profiles().get(key, {}) or {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def _active_profile_summary(self):
|
||||
from gui_v6.profile_view import summarize_profile
|
||||
|
||||
key = self._state.profile or default_profile_key() or ""
|
||||
return summarize_profile(key, self._active_profile_dict())
|
||||
|
||||
def _open_terms_table(self) -> None:
|
||||
from gui_v6.profile_view import profile_term_rows
|
||||
from gui_v6.terms_table_window import TermsTableWindow
|
||||
|
||||
summary = self._active_profile_summary()
|
||||
rows = profile_term_rows(self._active_profile_dict())
|
||||
TermsTableWindow(self.winfo_toplevel(), self._p, rows, profile_label=summary.label)
|
||||
|
||||
def _rebuild_profils(self) -> None:
|
||||
panel = self._panels.get("pro")
|
||||
if panel is None:
|
||||
return
|
||||
for child in panel.winfo_children():
|
||||
child.destroy()
|
||||
self._build_profils(panel)
|
||||
|
||||
def _build_profils(self, parent) -> None:
|
||||
p = self._p
|
||||
self._section_intro(
|
||||
parent,
|
||||
"Un profil regroupe tous les réglages d'anonymisation. Voici le profil actif.",
|
||||
_HELP_PROFIL,
|
||||
"Création / modification d'un profil d'anonymisation",
|
||||
)
|
||||
summary = self._active_profile_summary()
|
||||
|
||||
card = ui_kit.Card(parent, p, title=f"👤 {summary.label}")
|
||||
card.pack(fill="x", pady=(0, 8))
|
||||
if summary.description:
|
||||
self._note(card, summary.description)
|
||||
grid = ctk.CTkFrame(card, fg_color="transparent")
|
||||
grid.pack(fill="x", padx=12, pady=(0, 10))
|
||||
infos = [
|
||||
("Masque manuel requis", "Oui" if summary.require_manual_mask else "Non"),
|
||||
("Template de masque", summary.mask_template or "—"),
|
||||
("Moteur VLM (images)", "désactivé" if summary.disable_vlm else "selon réglages"),
|
||||
]
|
||||
for idx, (key, val) in enumerate(infos):
|
||||
ctk.CTkLabel(grid, text=key, text_color=p["text_muted"], font=ui_kit.font(11), anchor="w").grid(
|
||||
row=idx, column=0, sticky="w", pady=1
|
||||
)
|
||||
ctk.CTkLabel(grid, text=val, text_color=p["text"], font=ui_kit.font(11, "bold"), anchor="w").grid(
|
||||
row=idx, column=1, sticky="w", padx=(12, 0), pady=1
|
||||
)
|
||||
grid.grid_columnconfigure(1, weight=1)
|
||||
|
||||
lists_card = ui_kit.Card(parent, p, title="✅ Listes locales du profil")
|
||||
lists_card.pack(fill="x", pady=(0, 8))
|
||||
chips = ctk.CTkFrame(lists_card, fg_color="transparent")
|
||||
chips.pack(fill="x", padx=12, pady=(0, 8))
|
||||
for label, count in summary.list_counts.items():
|
||||
ctk.CTkLabel(
|
||||
chips,
|
||||
text=f"{label} : {count}",
|
||||
text_color=p["text"],
|
||||
fg_color=p["divider"],
|
||||
corner_radius=8,
|
||||
font=ui_kit.font(11, "bold"),
|
||||
).pack(side="left", padx=(0, 8), ipadx=8, ipady=3)
|
||||
ui_kit.primary_button(lists_card, p, "📋 Ouvrir le tableau des termes", command=self._open_terms_table).pack(
|
||||
anchor="w", padx=12, pady=(0, 12)
|
||||
)
|
||||
|
||||
create = ui_kit.Card(parent, p, title="🧩 Créer / modifier un profil")
|
||||
create.pack(fill="x")
|
||||
self._note(create, "La création et la modification de profils seront disponibles prochainement.")
|
||||
self._mockup_button(create, "+ Nouveau profil").pack(anchor="w", padx=12, pady=(0, 12))
|
||||
|
||||
# -- Masquage ---------------------------------------------------------
|
||||
|
||||
@@ -548,6 +691,7 @@ class ConfigTab(ctk.CTkFrame):
|
||||
|
||||
def _on_profile(self, value: str) -> None:
|
||||
self._state.profile = value
|
||||
self._rebuild_profils()
|
||||
|
||||
def _on_ner(self) -> None:
|
||||
self._state.use_local_ner = self._tog_ner.get()
|
||||
|
||||
Reference in New Issue
Block a user