feat(gui): confirmation explicite avant anonymisation regex-only (NER off)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 19:03:16 +02:00
parent 880a75873d
commit 416b347d7f
2 changed files with 69 additions and 1 deletions

View File

@@ -20,6 +20,31 @@ from gui_v6 import ui_kit
from gui_v6.config_state import ConfigState, default_profile_key, list_profile_keys from gui_v6.config_state import ConfigState, default_profile_key, list_profile_keys
from manual_masking import ensure_mask_templates_dir, list_mask_templates, mask_template_label from manual_masking import ensure_mask_templates_dir, list_mask_templates, mask_template_label
NER_DISABLE_WARNING = (
"Vous allez désactiver le moteur d'intelligence artificielle "
"(CamemBERT-bio).\n\n"
"Sans lui, la détection des NOMS de personnes repose uniquement sur des "
"règles (expressions régulières) : des noms peuvent rester EN CLAIR dans "
"les documents.\n\n"
"Pour un usage médical, garder ce moteur activé est fortement recommandé.\n\n"
"Confirmer la désactivation ?"
)
def confirm_ner_disable(asker) -> bool:
"""Décision de désactivation du NER.
``asker`` est une fonction ``() -> bool`` (ex. ``messagebox.askyesno``),
injectée pour rester testable sans display. Retourne True si l'utilisateur
CONFIRME la désactivation (regex-only). Toute erreur de l'asker est traitée
comme un refus (sens sûr : le NER reste actif).
"""
try:
return bool(asker())
except Exception:
return False
_SUBTABS = [ _SUBTABS = [
("reg", "⚙️ Réglages"), ("reg", "⚙️ Réglages"),
("pro", "👤 Profils"), ("pro", "👤 Profils"),
@@ -891,7 +916,19 @@ class ConfigTab(ctk.CTkFrame):
setattr(self._state, field_name, bool(toggle.get())) setattr(self._state, field_name, bool(toggle.get()))
def _on_ner(self) -> None: def _on_ner(self) -> None:
self._state.use_local_ner = self._tog_ner.get() new_value = self._tog_ner.get()
if not new_value:
confirmed = confirm_ner_disable(
lambda: messagebox.askyesno(
"Moteur de détection", NER_DISABLE_WARNING, icon="warning"
)
)
if not confirmed:
# Refus : rétablir l'affichage du switch et garder le NER actif.
self._tog_ner.var.set(True)
self._state.use_local_ner = True
return
self._state.use_local_ner = new_value
def _on_eds(self) -> None: def _on_eds(self) -> None:
self._state.enable_eds = self._tog_eds.get() self._state.enable_eds = self._tog_eds.get()

View File

@@ -0,0 +1,31 @@
"""Confirmation avant de désactiver le NER (regex-only) — outil médical.
Pur : la décision est isolée dans ``confirm_ner_disable(asker)`` ; ``asker`` est
injecté (pas de messagebox réel, pas de display).
"""
from __future__ import annotations
from gui_v6.tabs.tab_config import NER_DISABLE_WARNING, confirm_ner_disable
def test_confirm_true_when_user_accepts():
assert confirm_ner_disable(lambda: True) is True
def test_confirm_false_when_user_declines():
assert confirm_ner_disable(lambda: False) is False
def test_confirm_false_when_asker_raises():
def boom():
raise RuntimeError("Tk indisponible")
# Sens sûr : une erreur de dialogue ne désactive jamais le NER.
assert confirm_ner_disable(boom) is False
def test_warning_text_is_explicit_for_medical_use():
txt = NER_DISABLE_WARNING.lower()
# L'avertissement DOIT nommer la dégradation : règles/regex + risque noms.
assert "règles" in txt or "regex" in txt
assert "nom" in txt
assert "recommand" in txt # « fortement recommandé »