fix(gui): clarifier aide et disponibilite moteurs
Passe theme clair, libelles utilisateur, aides conteneurs, recherche de mise a jour et indication honnete des moteurs optionnels non embarques. Tests GUI unitaires: 126 passed.
This commit is contained in:
@@ -9,7 +9,6 @@ Partage/Règles.
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
from tkinter import filedialog, messagebox
|
||||
|
||||
@@ -28,7 +27,7 @@ _SUBTABS = [
|
||||
]
|
||||
|
||||
_DETECTION_OPTIONS = [
|
||||
("Noms et prénoms", "Gazetteers + IA"),
|
||||
("Noms et prénoms", "Annuaire + IA"),
|
||||
("Dates de naissance", "Contexte naissance"),
|
||||
("Établissements", "FINESS + contexte"),
|
||||
("Adresses / CP", "Voie, ville, code"),
|
||||
@@ -47,6 +46,10 @@ _MASK_COLORS = [
|
||||
|
||||
MANUAL_MASK_NONE_LABEL = "Aucun masque manuel"
|
||||
|
||||
MINI_TOGGLE_HEIGHT = 46
|
||||
MINI_TOGGLE_LABEL_FONT_SIZE = 12
|
||||
MINI_TOGGLE_HINT_FONT_SIZE = 11
|
||||
|
||||
# Textes d'aide « ? » (français simple, pour utilisateurs non informaticiens).
|
||||
_HELP_REGLAGES = (
|
||||
"Réglages de l'anonymisation.\n\n"
|
||||
@@ -95,6 +98,57 @@ _HELP_LISTES = (
|
||||
"Pour une liste longue, ouvrez le tableau des termes : "
|
||||
"il reste lisible et permet la recherche."
|
||||
)
|
||||
_HELP_DONNEES_DETECTER = (
|
||||
"Cette zone indique les familles de données que le profil cherche à anonymiser : "
|
||||
"noms, dates de naissance, établissements, adresses, identifiants, téléphones et e-mails.\n\n"
|
||||
"Ces options décrivent le périmètre fonctionnel attendu. Les règles exactes restent "
|
||||
"contrôlées par le moteur et par le profil actif."
|
||||
)
|
||||
_HELP_MOTEURS_MASQUES = (
|
||||
"Cette zone regroupe les moteurs de détection et les réglages de masque manuel.\n\n"
|
||||
"Le masque manuel sert aux zones fixes d'un document que le texte ne suffit pas à détecter "
|
||||
"correctement : logo, en-tête, coordonnées, bloc institutionnel ou tampon scanné.\n\n"
|
||||
"Si « Masque manuel obligatoire » est actif, le profil impose cette étape de contrôle "
|
||||
"avant de considérer le traitement complet."
|
||||
)
|
||||
_HELP_PROFIL_CHOIX = (
|
||||
"Choisissez ici le profil à modifier.\n\n"
|
||||
"Les profils livrés par défaut sont en lecture seule pour éviter une modification accidentelle. "
|
||||
"Dupliquez un profil pour créer une version adaptée à votre établissement."
|
||||
)
|
||||
_HELP_PROFIL_IDENTITE = (
|
||||
"Nom et description visibles dans l'interface.\n\n"
|
||||
"Utilisez un nom simple que les utilisateurs comprendront, par exemple « Standard local », "
|
||||
"« Recherche » ou « Diffusion externe prudente »."
|
||||
)
|
||||
_HELP_PROFIL_MASQUAGE = (
|
||||
"Cette zone règle les masques propres au profil.\n\n"
|
||||
"Masquage manuel obligatoire : le profil impose une vérification avec un masque de zones fixes "
|
||||
"avant le traitement. C'est utile pour les documents qui ont toujours les mêmes zones sensibles "
|
||||
"au même endroit : logos, en-têtes, coordonnées, tampons ou blocs scannés.\n\n"
|
||||
"Template de masque préféré : modèle proposé automatiquement par ce profil. "
|
||||
"L'éditeur de masque permet de créer ou ajuster ces zones visuellement."
|
||||
)
|
||||
_HELP_PROFIL_MOTEURS = (
|
||||
"Cette zone précise les moteurs utilisés par le profil.\n\n"
|
||||
"CamemBERT-bio est le moteur standard. Les moteurs optionnels ne sont proposés que s'ils sont "
|
||||
"réellement embarqués dans cette version. Le moteur VLM concerne surtout les documents images."
|
||||
)
|
||||
_HELP_PROFIL_MOTS = (
|
||||
"Ces listes appartiennent au profil.\n\n"
|
||||
"À masquer : termes à remplacer systématiquement.\n"
|
||||
"À conserver : termes à ne jamais masquer, même s'ils ressemblent à des noms.\n"
|
||||
"À ignorer : mots qui ne doivent pas déclencher de détection.\n\n"
|
||||
"Pour de longues listes, utilisez le tableau des termes afin de rechercher et vérifier plus facilement."
|
||||
)
|
||||
_HELP_EXPORT_CONFIG = (
|
||||
"Exporte uniquement les réglages de l'application : profils, listes locales, règles et style de masque.\n\n"
|
||||
"Les documents patients, résultats d'anonymisation et audits ne sont pas exportés."
|
||||
)
|
||||
_HELP_IMPORT_CONFIG = (
|
||||
"Importe des réglages reçus d'un administrateur ou d'un autre poste.\n\n"
|
||||
"L'import ne lit pas de documents patients. Vérifiez toujours le profil actif après import."
|
||||
)
|
||||
|
||||
CONFIG_MOCKUP_SECTIONS = {
|
||||
"reglages": [
|
||||
@@ -293,12 +347,18 @@ class ConfigTab(ctk.CTkFrame):
|
||||
|
||||
cols = self._columns(parent, 3, gap=8, height=455)
|
||||
|
||||
det = ui_kit.Card(cols[0], p, title="🔍 Données à détecter")
|
||||
det = ui_kit.Card(
|
||||
cols[0], p, title="🔍 Données à détecter",
|
||||
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)
|
||||
|
||||
ner = ui_kit.Card(cols[1], p, title="🧠 Moteurs et masques")
|
||||
ner = ui_kit.Card(
|
||||
cols[1], p, title="🧠 Moteurs et masques",
|
||||
help_text=_HELP_MOTEURS_MASQUES, help_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))
|
||||
@@ -359,13 +419,13 @@ class ConfigTab(ctk.CTkFrame):
|
||||
mask_actions = ctk.CTkFrame(ner, fg_color="transparent")
|
||||
mask_actions.pack(fill="x", padx=12, pady=(0, 12))
|
||||
ui_kit.secondary_button(mask_actions, p, "🔄 Actualiser", command=self._refresh_manual_mask_templates).pack(
|
||||
side="left", fill="x", expand=True, padx=(0, 4)
|
||||
)
|
||||
ui_kit.secondary_button(mask_actions, p, "📁 Dossier", command=self._open_templates_dir).pack(
|
||||
side="left", fill="x", expand=True, padx=(4, 0)
|
||||
side="left", fill="x", expand=True
|
||||
)
|
||||
|
||||
terms = ui_kit.Card(cols[2], p, title="✅ Listes locales")
|
||||
terms = ui_kit.Card(
|
||||
cols[2], p, title="✅ Listes locales",
|
||||
help_text=_HELP_LISTES, help_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))
|
||||
@@ -438,7 +498,10 @@ class ConfigTab(ctk.CTkFrame):
|
||||
"Profils d'anonymisation",
|
||||
)
|
||||
|
||||
bar = ui_kit.Card(parent, p, title="👤 Profil à modifier")
|
||||
bar = ui_kit.Card(
|
||||
parent, p, title="👤 Profil à modifier",
|
||||
help_text=_HELP_PROFIL_CHOIX, help_title="Profil à modifier",
|
||||
)
|
||||
bar.pack(fill="x", pady=(0, 8))
|
||||
top = ctk.CTkFrame(bar, fg_color="transparent")
|
||||
top.pack(fill="x", padx=12, pady=(0, 4))
|
||||
@@ -465,7 +528,10 @@ class ConfigTab(ctk.CTkFrame):
|
||||
cols = self._columns(parent, 2, gap=8)
|
||||
left, right = cols[0], cols[1]
|
||||
|
||||
ident = ui_kit.Card(left, p, title="🏷️ Identité")
|
||||
ident = ui_kit.Card(
|
||||
left, p, title="🏷️ Identité",
|
||||
help_text=_HELP_PROFIL_IDENTITE, help_title="Identité du profil",
|
||||
)
|
||||
ident.pack(fill="x", pady=(0, 8))
|
||||
self._pro_label_var = ctk.StringVar()
|
||||
self._pro_label_entry = ctk.CTkEntry(ident, textvariable=self._pro_label_var, fg_color=p["btn_sec_bg"], border_color=p["btn_sec_border"], text_color=p["text"], height=30)
|
||||
@@ -476,7 +542,10 @@ class ConfigTab(ctk.CTkFrame):
|
||||
ctk.CTkLabel(ident, text="Description", text_color=p["text_muted"], font=ui_kit.font(11), anchor="w").pack(fill="x", padx=12, pady=(0, 2))
|
||||
self._pro_desc_entry.pack(fill="x", padx=12, pady=(0, 12))
|
||||
|
||||
mask = ui_kit.Card(left, p, title="⬛ Masquage")
|
||||
mask = ui_kit.Card(
|
||||
left, p, title="⬛ Masquage",
|
||||
help_text=_HELP_PROFIL_MASQUAGE, help_title="Masquage manuel",
|
||||
)
|
||||
mask.pack(fill="x", pady=(0, 8))
|
||||
self._pro_require_mask_var = ctk.BooleanVar(value=False)
|
||||
self._pro_require_switch = ctk.CTkSwitch(mask, text="Masque manuel obligatoire", variable=self._pro_require_mask_var, progress_color=p["primary"], text_color=p["text"], font=ui_kit.font(12))
|
||||
@@ -488,7 +557,6 @@ class ConfigTab(ctk.CTkFrame):
|
||||
mask_actions = ctk.CTkFrame(mask, fg_color="transparent")
|
||||
mask_actions.pack(fill="x", padx=12, pady=(0, 6))
|
||||
ui_kit.secondary_button(mask_actions, p, "🖊 Ouvrir l'éditeur de masque", command=self._open_full_mask_editor).pack(side="left")
|
||||
ui_kit.secondary_button(mask_actions, p, "📁 Dossier", command=self._open_templates_dir).pack(side="left", padx=(6, 0))
|
||||
|
||||
# Apparence du masque (couleur / style / marges) — réglage global appliqué aux PDF.
|
||||
ctk.CTkLabel(mask, text="Apparence du masque", text_color=p["text_muted"], font=ui_kit.font(11, "bold"), anchor="w").pack(fill="x", padx=12, pady=(6, 2))
|
||||
@@ -518,7 +586,10 @@ class ConfigTab(ctk.CTkFrame):
|
||||
variable=self._mask_rounded_var, command=self._on_rounded_corners,
|
||||
).pack(fill="x", padx=12, pady=(2, 12))
|
||||
|
||||
eng = ui_kit.Card(left, p, title="🧠 Moteurs")
|
||||
eng = ui_kit.Card(
|
||||
left, p, title="🧠 Moteurs",
|
||||
help_text=_HELP_PROFIL_MOTEURS, help_title="Moteurs du profil",
|
||||
)
|
||||
eng.pack(fill="x")
|
||||
self._pro_disable_vlm_var = ctk.BooleanVar(value=False)
|
||||
self._pro_vlm_switch = ctk.CTkSwitch(eng, text="Désactiver le moteur VLM (images)", variable=self._pro_disable_vlm_var, progress_color=p["primary"], text_color=p["text"], font=ui_kit.font(12))
|
||||
@@ -532,7 +603,10 @@ class ConfigTab(ctk.CTkFrame):
|
||||
moteurs_note = "CamemBERT-bio (standard) toujours actif ; EDS-Pseudo / GLiNER non embarqués dans cette version."
|
||||
ctk.CTkLabel(eng, text=moteurs_note, text_color=p["text_muted"], font=ui_kit.font(11), anchor="w", wraplength=300, justify="left").pack(fill="x", padx=12, pady=(0, 12))
|
||||
|
||||
words = ui_kit.Card(right, p, title="📝 Mots du profil")
|
||||
words = ui_kit.Card(
|
||||
right, p, title="📝 Mots du profil",
|
||||
help_text=_HELP_PROFIL_MOTS, help_title="Mots du profil",
|
||||
)
|
||||
words.pack(fill="both", expand=True)
|
||||
self._pro_term_lists = {
|
||||
"blacklist": EditableTermList(words, p, title="À masquer", height=104),
|
||||
@@ -542,7 +616,10 @@ class ConfigTab(ctk.CTkFrame):
|
||||
for tl in self._pro_term_lists.values():
|
||||
tl.pack(fill="x", padx=12, pady=(0, 8))
|
||||
|
||||
rules = ui_kit.Card(parent, p, title="🛡️ Règles du profil")
|
||||
rules = ui_kit.Card(
|
||||
parent, p, title="🛡️ Règles du profil",
|
||||
help_text=_HELP_REGLES, help_title="Règles du profil",
|
||||
)
|
||||
rules.pack(fill="x", pady=(8, 0))
|
||||
rules_intro = ctk.CTkFrame(rules, fg_color="transparent")
|
||||
rules_intro.pack(fill="x", padx=12, pady=(0, 2))
|
||||
@@ -740,12 +817,18 @@ class ConfigTab(ctk.CTkFrame):
|
||||
"À quoi sert le Partage ?",
|
||||
)
|
||||
cols = self._columns(parent, 2, gap=8, height=180)
|
||||
export = ui_kit.Card(cols[0], p, title="📤 Exporter la configuration")
|
||||
export = ui_kit.Card(
|
||||
cols[0], p, title="📤 Exporter la configuration",
|
||||
help_text=_HELP_EXPORT_CONFIG, help_title="Exporter la configuration",
|
||||
)
|
||||
export.pack(fill="both", expand=True)
|
||||
self._note(export, "Listes locales, règles admin, style de masquage et template actif.")
|
||||
self._mockup_button(export, "⬇ Exporter (.json)").pack(anchor="w", padx=12, pady=(0, 12))
|
||||
|
||||
import_card = ui_kit.Card(cols[1], p, title="📥 Importer une configuration")
|
||||
import_card = ui_kit.Card(
|
||||
cols[1], p, title="📥 Importer une configuration",
|
||||
help_text=_HELP_IMPORT_CONFIG, help_title="Importer une configuration",
|
||||
)
|
||||
import_card.pack(fill="both", expand=True)
|
||||
self._note(import_card, "Fusionne la configuration reçue avec vos réglages locaux.")
|
||||
self._mockup_button(import_card, "⬆ Importer (.json)").pack(anchor="w", padx=12, pady=(0, 12))
|
||||
@@ -820,10 +903,7 @@ class ConfigTab(ctk.CTkFrame):
|
||||
|
||||
def _open_templates_dir(self) -> None:
|
||||
path = ensure_mask_templates_dir(_app_base_dir())
|
||||
try:
|
||||
webbrowser.open(path.as_uri())
|
||||
except Exception:
|
||||
messagebox.showinfo("Dossier modèles", str(path))
|
||||
messagebox.showinfo("Dossier modèles", str(path))
|
||||
|
||||
# -- callbacks masquage ----------------------------------------------
|
||||
|
||||
@@ -962,15 +1042,27 @@ class ConfigTab(ctk.CTkFrame):
|
||||
def _mini_toggle(self, parent, label: str, hint: str, value: bool = True, variable=None,
|
||||
command=None, disabled: bool = False, disabled_hint: str | None = None):
|
||||
p = self._p
|
||||
row = ctk.CTkFrame(parent, fg_color="transparent", height=34)
|
||||
row = ctk.CTkFrame(parent, fg_color="transparent", height=MINI_TOGGLE_HEIGHT)
|
||||
row.pack_propagate(False)
|
||||
left = ctk.CTkFrame(row, fg_color="transparent")
|
||||
left.pack(side="left", fill="x", expand=True)
|
||||
left.pack(side="left", fill="both", expand=True, pady=(3, 2))
|
||||
lbl_color = p["text_muted"] if disabled else p["text"]
|
||||
ctk.CTkLabel(left, text=label, text_color=lbl_color, font=ui_kit.font(12), anchor="w").pack(anchor="w")
|
||||
ctk.CTkLabel(
|
||||
left,
|
||||
text=label,
|
||||
text_color=lbl_color,
|
||||
font=ui_kit.font(MINI_TOGGLE_LABEL_FONT_SIZE, "bold"),
|
||||
anchor="w",
|
||||
).pack(anchor="w")
|
||||
shown_hint = disabled_hint if (disabled and disabled_hint) else hint
|
||||
if shown_hint:
|
||||
ctk.CTkLabel(left, text=shown_hint, text_color=p["text_muted"], font=ui_kit.font(10), anchor="w").pack(anchor="w")
|
||||
ctk.CTkLabel(
|
||||
left,
|
||||
text=shown_hint,
|
||||
text_color=p["text_muted"] if disabled else p["text_dim"],
|
||||
font=ui_kit.font(MINI_TOGGLE_HINT_FONT_SIZE),
|
||||
anchor="w",
|
||||
).pack(anchor="w", pady=(1, 0))
|
||||
# Moteur indisponible : on force l'état à False (jamais « coché mais absent »).
|
||||
if disabled and variable is None:
|
||||
value = False
|
||||
@@ -980,7 +1072,7 @@ class ConfigTab(ctk.CTkFrame):
|
||||
switch = ctk.CTkSwitch(row, text="", variable=var, command=command, progress_color=p["primary"], width=38)
|
||||
if disabled:
|
||||
switch.configure(state="disabled")
|
||||
switch.pack(side="right", padx=(6, 0))
|
||||
switch.pack(side="right", padx=(8, 0), pady=(8, 0))
|
||||
row.var = var # type: ignore[attr-defined]
|
||||
row.switch = switch # type: ignore[attr-defined]
|
||||
row.get = lambda: bool(var.get()) # type: ignore[attr-defined]
|
||||
|
||||
Reference in New Issue
Block a user