Amélioration majeure de l'anonymisation regex : trackare, noms composés, faux positifs
- Parseur trackare spécifique (détection par contenu, extraction structurée des PII) - Support format "Dr X. NOM" et "Mme X. NOM" (initiales + noms composés avec tiret) - Détection noms personnel médical (Aide, Cadre Infirmier, etc.) - Masquage RPPS, établissements (EHPAD/SSR/USLD standalone), lieux de naissance - Stop words médicaux enrichis (~270 entrées : DCI, spécialités, termes contextuels) - Détection compagnon (noms adjacents à des noms connus dans le texte brut) - Protection noms composés (JEAN-PIERRE traité comme un tout, pas JEAN + PIERRE) - Nettoyage codes postaux orphelins, téléphones fragmentés/partiels - Désactivation masquage dates génériques, AGE avec contexte obligatoire - GUI : extraction OGC depuis le nom du répertoire parent, incrustation sur les pages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -399,7 +399,7 @@ class App:
|
||||
|
||||
self._folder_text_lbl = tk.Label(
|
||||
self._folder_inner,
|
||||
text="Cliquez pour choisir un dossier contenant vos PDF",
|
||||
text="Cliquez pour choisir un dossier (tous les PDF seront recherchés récursivement)",
|
||||
font=self._f_body, bg=CLR_CARD_BG, fg=CLR_TEXT_SECONDARY,
|
||||
)
|
||||
self._folder_text_lbl.pack(pady=(4, 0))
|
||||
@@ -427,14 +427,15 @@ class App:
|
||||
|
||||
tk.Label(
|
||||
info_inner,
|
||||
text="Les deux formats sont générés automatiquement :",
|
||||
text="Paramètres de traitement :",
|
||||
font=self._f_body_bold, bg=CLR_BLUE_LIGHT, fg=CLR_TEXT, anchor="w",
|
||||
).pack(fill=tk.X)
|
||||
|
||||
tk.Label(
|
||||
info_inner,
|
||||
text=("\u2022 PDF Image — sécurité maximale, chaque page en image, aucun texte résiduel\n"
|
||||
"\u2022 PDF Anonymisé — structure préservée comme l'original, fichier léger"),
|
||||
text=("\u2022 Recherche récursive de tous les PDF dans les sous-dossiers\n"
|
||||
"\u2022 Sortie PDF Image (raster) — sécurité maximale, aucun texte résiduel\n"
|
||||
"\u2022 Résultats dans le dossier « anonymise/ » à la racine"),
|
||||
font=self._f_card_desc, bg=CLR_BLUE_LIGHT, fg=CLR_TEXT_SECONDARY,
|
||||
anchor="w", justify=tk.LEFT,
|
||||
).pack(fill=tk.X, pady=(4, 0))
|
||||
@@ -589,10 +590,10 @@ class App:
|
||||
if not folder:
|
||||
return
|
||||
|
||||
# Compter les PDF
|
||||
# Compter les PDF (récursif)
|
||||
pdf_count = 0
|
||||
try:
|
||||
pdf_count = len([p for p in Path(folder).glob("*.pdf") if p.is_file()])
|
||||
pdf_count = len([p for p in Path(folder).rglob("*.pdf") if p.is_file()])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -620,7 +621,7 @@ class App:
|
||||
bg=CLR_CARD_BG, fg=CLR_TEXT, anchor="w",
|
||||
).pack(fill=tk.X)
|
||||
|
||||
suffix = "PDF trouvé" if pdf_count <= 1 else "PDF trouvés"
|
||||
suffix = "PDF trouvé (récursif)" if pdf_count <= 1 else "PDF trouvés (récursif)"
|
||||
tk.Label(
|
||||
info_frame, text=f"{pdf_count} {suffix}",
|
||||
font=self._f_small, bg=CLR_CARD_BG, fg=CLR_TEXT_SECONDARY, anchor="w",
|
||||
@@ -648,11 +649,11 @@ class App:
|
||||
)
|
||||
return
|
||||
|
||||
pdfs = sorted([p for p in folder.glob("*.pdf") if p.is_file()])
|
||||
pdfs = sorted([p for p in folder.rglob("*.pdf") if p.is_file()])
|
||||
if not pdfs:
|
||||
messagebox.showwarning(
|
||||
"Aucun PDF",
|
||||
"Le dossier sélectionné ne contient aucun fichier PDF.",
|
||||
"Aucun fichier PDF trouvé\n(recherche récursive dans les sous-dossiers).",
|
||||
)
|
||||
return
|
||||
|
||||
@@ -663,7 +664,7 @@ class App:
|
||||
|
||||
def _worker(self, folder: Path, pdfs: List[Path]):
|
||||
try:
|
||||
outdir = folder / "pseudonymise"
|
||||
outdir = folder / "anonymise"
|
||||
outdir.mkdir(exist_ok=True)
|
||||
ok = ko = 0
|
||||
global_counts: Dict[str, int] = {}
|
||||
@@ -681,15 +682,21 @@ class App:
|
||||
if use_ner and NerThresholds and not (EdsPseudoManager and isinstance(active, EdsPseudoManager)):
|
||||
thresholds = NerThresholds(self.th_per, self.th_org, self.th_loc, 0.85)
|
||||
|
||||
# Extraire le numéro OGC du nom du répertoire parent
|
||||
# Ex: "257_23209962" → OGC = "257"
|
||||
parent_name = pdf.parent.name
|
||||
ogc = parent_name.split("_")[0] if "_" in parent_name else None
|
||||
|
||||
outputs = core.process_pdf(
|
||||
pdf_path=pdf,
|
||||
out_dir=outdir,
|
||||
make_vector_redaction=True,
|
||||
make_vector_redaction=False,
|
||||
also_make_raster_burn=True,
|
||||
config_path=Path(self.cfg_path.get()),
|
||||
use_hf=use_ner,
|
||||
ner_manager=active,
|
||||
ner_thresholds=thresholds,
|
||||
ogc_label=ogc,
|
||||
)
|
||||
self.queue.put(UiMessage(kind=MsgType.LOG, text=f"\u2713 {pdf.name}"))
|
||||
for k, v in outputs.items():
|
||||
@@ -822,15 +829,15 @@ class App:
|
||||
def _show_help(self):
|
||||
messagebox.showinfo(
|
||||
"Comment ça marche ?",
|
||||
"1) Choisissez le dossier contenant vos fichiers PDF.\n\n"
|
||||
"1) Choisissez le dossier racine contenant vos fichiers PDF.\n\n"
|
||||
"2) Cliquez sur « Lancer la pseudonymisation ».\n\n"
|
||||
"Deux fichiers sont générés pour chaque PDF :\n"
|
||||
" \u2022 PDF Image : chaque page devient une image avec les\n"
|
||||
" données masquées. Sécurité maximale.\n"
|
||||
" \u2022 PDF Anonymisé : structure préservée comme l'original,\n"
|
||||
" fichier léger et texte sélectionnable.\n\n"
|
||||
"Les résultats apparaissent dans un sous-dossier\n"
|
||||
"« pseudonymise » à côté de vos originaux.",
|
||||
"Tous les fichiers PDF sont traités\n"
|
||||
"(recherche récursive dans les sous-dossiers).\n\n"
|
||||
"Un PDF Image (raster) est généré pour chaque fichier :\n"
|
||||
"chaque page devient une image avec les données masquées.\n"
|
||||
"Sécurité maximale, aucun texte résiduel.\n\n"
|
||||
"Les résultats sont regroupés à plat dans le dossier\n"
|
||||
"« anonymise/ » à la racine du dossier sélectionné.",
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user