Anonymise les références aux entités réelles (CHCB, villes basques, Saint-Denis, Réunion, etc.) dans la documentation projet, les maquettes HTML/Python, les notes de coordination et les audits. Conserve docs/coordination/decisions/2026-06-02_dom_mvp-pivots-strategiques.md (table de mapping de référence) et docs/coordination/inbox/for-claude/ intacts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
915 lines
57 KiB
Python
915 lines
57 KiB
Python
#!/usr/bin/env python3
|
|
"""Génère ui_mockup_v6.html — logo embarqué en base64, JS sans apostrophes dans les strings."""
|
|
import base64
|
|
from pathlib import Path
|
|
|
|
LOGO_PATH = Path(__file__).parent.parent / "assets" / "logo_header.png"
|
|
OUT_PATH = Path(__file__).parent / "ui_mockup_v6.html"
|
|
|
|
logo_b64 = base64.b64encode(LOGO_PATH.read_bytes()).decode()
|
|
LOGO_SRC = "data:image/png;base64," + logo_b64
|
|
|
|
HTML = r"""<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>aivanonym v6 — Prototype UI</title>
|
|
<style>
|
|
:root{
|
|
--bg:#1a1a2e;--card:#16213e;--card-border:#0f3460;
|
|
--primary:#e94560;--primary-dim:#c73652;--accent:#f5a623;
|
|
--text:#e0e0e0;--text-dim:#9ca3af;--text-muted:#6b7280;
|
|
--success:#10b981;--warning:#f59e0b;--danger:#ef4444;--blue:#3b82f6;
|
|
--radius:8px;--shadow:0 2px 12px rgba(0,0,0,.4);
|
|
--divider:rgba(255,255,255,.06);--btn-sec-bg:rgba(255,255,255,.08);--btn-sec-border:rgba(255,255,255,.14);
|
|
}
|
|
/* ── CLAIR : fond gris moyen, cartes blanches, bordures visibles ── */
|
|
.theme-light{
|
|
--bg:#cdd2da;--card:#ffffff;--card-border:#9aa3b0;
|
|
--primary:#c93050;--primary-dim:#a82545;--accent:#b45309;
|
|
--text:#0d1117;--text-dim:#1f2937;--text-muted:#374151;
|
|
--success:#047857;--warning:#b45309;--danger:#b91c1c;--blue:#1d4ed8;
|
|
--shadow:0 2px 14px rgba(0,0,0,.22);
|
|
--divider:rgba(0,0,0,.09);--btn-sec-bg:rgba(0,0,0,.07);--btn-sec-border:#9aa3b0;
|
|
}
|
|
/* ── MÉDICAL : fond bleu structuré, cartes légèrement teintées ── */
|
|
.theme-medical{
|
|
--bg:#b8ceea;--card:#eef5ff;--card-border:#6897ca;
|
|
--primary:#1a56db;--primary-dim:#1340b0;--accent:#0369a1;
|
|
--text:#071427;--text-dim:#0f2a4a;--text-muted:#1e3a5f;
|
|
--success:#166534;--warning:#92400e;--danger:#991b1b;--blue:#1e40af;
|
|
--shadow:0 2px 14px rgba(0,50,160,.18);
|
|
--divider:rgba(0,50,160,.09);--btn-sec-bg:rgba(0,50,160,.07);--btn-sec-border:#6897ca;
|
|
}
|
|
/* ── NEUTRE sombre : inchangé, contraste déjà correct ── */
|
|
.theme-neutral{
|
|
--bg:#1f2937;--card:#374151;--card-border:#6b7280;
|
|
--primary:#818cf8;--primary-dim:#6366f1;--accent:#fbbf24;
|
|
--text:#f9fafb;--text-dim:#e5e7eb;--text-muted:#d1d5db;
|
|
--success:#34d399;--warning:#fbbf24;--danger:#f87171;--blue:#60a5fa;
|
|
--shadow:0 2px 12px rgba(0,0,0,.45);
|
|
--divider:rgba(255,255,255,.08);--btn-sec-bg:rgba(255,255,255,.08);--btn-sec-border:#6b7280;
|
|
}
|
|
*{box-sizing:border-box;margin:0;padding:0}
|
|
body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(--text);font-size:14px;display:flex;justify-content:center;min-height:100vh}
|
|
.app-shell{width:780px;min-height:820px;display:flex;flex-direction:column;background:var(--bg);box-shadow:0 0 40px rgba(0,0,0,.5)}
|
|
@media(max-width:800px){.app-shell{width:100%;min-height:100vh}}
|
|
/* HEADER */
|
|
.header{background:var(--card);border-bottom:3px solid var(--primary);padding:10px 20px;display:flex;align-items:center;gap:12px;flex-shrink:0}
|
|
.header img{height:34px;width:auto}
|
|
.hv{margin-left:auto;font-size:11px;color:var(--text-muted);background:rgba(128,128,128,.12);padding:3px 8px;border-radius:4px}
|
|
/* TABS */
|
|
.tabs-bar{display:flex;background:var(--card);border-bottom:1px solid var(--card-border);padding:0 20px;flex-shrink:0;gap:2px}
|
|
.tab-btn{padding:9px 16px;cursor:pointer;border:none;background:none;color:var(--text-dim);font-size:13px;font-weight:500;border-bottom:3px solid transparent;margin-bottom:-1px;transition:color .15s;white-space:nowrap}
|
|
.tab-btn:hover{color:var(--text)}
|
|
.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600}
|
|
/* CONTENT */
|
|
.content{flex:1;overflow-y:auto;padding:20px}
|
|
.tab-pane{display:none}.tab-pane.active{display:block}
|
|
/* CARD */
|
|
.card{background:var(--card);border:1px solid var(--card-border);border-radius:var(--radius);padding:18px;margin-bottom:14px;box-shadow:var(--shadow)}
|
|
.ct{font-size:12px;font-weight:600;color:var(--text-dim);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px;display:flex;align-items:center;gap:8px}
|
|
.hbtn{margin-left:auto;cursor:pointer;font-size:16px;background:none;border:none;color:var(--text-muted);transition:color .15s;line-height:1;flex-shrink:0}
|
|
.hbtn:hover{color:var(--primary)}
|
|
/* DROP ZONE */
|
|
.dz{border:2px dashed var(--card-border);border-radius:var(--radius);padding:28px 16px;text-align:center;cursor:pointer;transition:border-color .2s,background .2s}
|
|
.dz:hover,.dz.over{border-color:var(--primary);background:rgba(233,69,96,.06)}
|
|
.dz-icon{font-size:32px;margin-bottom:8px}
|
|
.dz-txt{font-size:14px;margin-bottom:3px}
|
|
.dz-sub{font-size:12px;color:var(--text-muted)}
|
|
.dz-acts{display:flex;gap:8px;justify-content:center;margin-top:12px}
|
|
.file-list{margin-top:10px;display:flex;flex-direction:column;gap:5px}
|
|
.fi{display:flex;align-items:center;gap:8px;background:var(--divider);border-radius:6px;padding:7px 10px;border:1px solid var(--btn-sec-border)}
|
|
.fn{flex:1;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
.fs{font-size:11px;color:var(--text-muted);flex-shrink:0}
|
|
.fx{background:none;border:none;cursor:pointer;color:var(--text-muted);font-size:15px;padding:0 3px}
|
|
.fx:hover{color:var(--danger)}
|
|
/* FORMAT */
|
|
.fmt-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
|
.fmt-card{border:2px solid var(--card-border);border-radius:var(--radius);padding:14px;text-align:center;cursor:pointer;transition:all .15s}
|
|
.fmt-card.on{border-color:var(--primary);background:rgba(233,69,96,.07)}
|
|
.fi2{font-size:22px;margin-bottom:6px}
|
|
.fn2{font-size:13px;font-weight:600}
|
|
.fs2{font-size:11px;color:var(--text-muted);margin-top:2px}
|
|
/* THEME */
|
|
.theme-row{display:flex;gap:8px;flex-wrap:wrap}
|
|
.tp{padding:6px 14px;border-radius:99px;border:2px solid var(--card-border);cursor:pointer;font-size:12px;font-weight:500;background:none;color:var(--text-dim);transition:all .15s}
|
|
.tp:hover{border-color:var(--primary);color:var(--primary)}
|
|
.tp.on{border-color:var(--primary);background:var(--primary);color:#fff}
|
|
/* BUTTONS */
|
|
.btn{display:inline-flex;align-items:center;gap:5px;padding:8px 16px;border-radius:var(--radius);border:none;cursor:pointer;font-size:13px;font-weight:600;transition:all .15s}
|
|
.bp{background:var(--primary);color:#fff}
|
|
.bp:hover{background:var(--primary-dim);transform:translateY(-1px)}
|
|
.bs{background:var(--btn-sec-bg);color:var(--text);border:1px solid var(--btn-sec-border)}
|
|
.bs:hover{background:rgba(128,128,128,.2)}
|
|
.bsu{background:var(--success);color:#fff}
|
|
.blg{padding:11px 24px;font-size:14px}
|
|
.btn:disabled{opacity:.4;cursor:not-allowed;transform:none!important}
|
|
.brow{display:flex;justify-content:flex-end;gap:8px;margin-bottom:14px}
|
|
/* PROGRESS */
|
|
.psec{display:none}.psec.vis{display:block}
|
|
.ptrack{background:var(--divider);border:1px solid var(--btn-sec-border);border-radius:99px;height:9px;overflow:hidden;margin:8px 0}
|
|
.pfill{height:100%;background:linear-gradient(90deg,var(--primary),var(--accent));border-radius:99px;transition:width .4s ease}
|
|
.plbl{display:flex;justify-content:space-between;font-size:12px;color:var(--text-muted)}
|
|
.psteps{display:flex;gap:5px;margin-top:10px;flex-wrap:wrap}
|
|
.sp{padding:3px 9px;border-radius:99px;font-size:11px;background:rgba(128,128,128,.1);color:var(--text-muted)}
|
|
.sp.done{background:rgba(16,185,129,.15);color:var(--success)}
|
|
.sp.act{background:rgba(233,69,96,.15);color:var(--primary);font-weight:600}
|
|
.log{background:var(--divider);border:1px solid var(--card-border);border-radius:6px;padding:8px;font-family:monospace;font-size:11px;color:var(--text-dim);height:90px;overflow-y:auto;line-height:1.6;margin-top:10px}
|
|
.lok{color:var(--success)}
|
|
/* RESULTS */
|
|
.rgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:12px}
|
|
.sc{background:var(--btn-sec-bg);border:1px solid var(--btn-sec-border);border-radius:var(--radius);padding:12px;text-align:center}
|
|
.sv{font-size:24px;font-weight:700;color:var(--primary)}
|
|
.sl{font-size:11px;color:var(--text-muted);margin-top:2px}
|
|
.qbar{display:flex;align-items:center;gap:10px;margin-bottom:10px}
|
|
.qs{font-size:30px;font-weight:800;color:var(--success)}
|
|
.qg{font-size:16px;font-weight:700;color:var(--success)}
|
|
/* SUB-TABS */
|
|
.stabs{display:flex;gap:2px;border-bottom:1px solid var(--card-border);margin-bottom:16px}
|
|
.stab{padding:7px 14px;border:none;background:none;color:var(--text-dim);font-size:13px;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;transition:color .15s}
|
|
.stab:hover{color:var(--text)}
|
|
.stab.on{color:var(--primary);border-bottom-color:var(--primary);font-weight:600}
|
|
.spane{display:none}.spane.on{display:block}
|
|
/* SETTINGS */
|
|
.scols{display:grid;grid-template-columns:1fr 1fr;gap:14px}
|
|
@media(max-width:600px){.scols{grid-template-columns:1fr}}
|
|
.srow{display:flex;align-items:center;justify-content:space-between;padding:9px 0;border-bottom:1px solid var(--divider);gap:12px}
|
|
.slbl{font-size:13px}
|
|
.shint{font-size:11px;color:var(--text-muted);margin-top:2px}
|
|
/* TOGGLE */
|
|
.tog{position:relative;display:inline-block;width:38px;height:21px;flex-shrink:0}
|
|
.tog input{opacity:0;width:0;height:0}
|
|
.tsl{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:rgba(128,128,128,.25);border-radius:99px;transition:.2s}
|
|
.tsl:before{content:'';position:absolute;width:15px;height:15px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}
|
|
.tog input:checked+.tsl{background:var(--primary)}
|
|
.tog input:checked+.tsl:before{transform:translateX(17px)}
|
|
/* TAGS */
|
|
.tagrow{display:flex;gap:7px;margin-bottom:9px}
|
|
.taginput{flex:1;background:var(--btn-sec-bg);border:1px solid var(--btn-sec-border);border-radius:6px;padding:6px 10px;color:var(--text);font-size:13px;outline:none}
|
|
.taginput:focus{border-color:var(--primary)}
|
|
.tagcloud{display:flex;flex-wrap:wrap;gap:5px;min-height:28px}
|
|
.tag{display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border-radius:99px;font-size:12px}
|
|
.tw{background:rgba(16,185,129,.12);color:var(--success);border:1px solid rgba(16,185,129,.25)}
|
|
.tb{background:rgba(233,69,96,.1);color:var(--primary);border:1px solid rgba(233,69,96,.2)}
|
|
.tx{cursor:pointer;font-size:13px;opacity:.6}
|
|
.tx:hover{opacity:1}
|
|
/* SWATCHES */
|
|
.swrow{display:flex;gap:8px;flex-wrap:wrap;margin-top:6px}
|
|
.sw{width:30px;height:30px;border-radius:6px;cursor:pointer;border:3px solid transparent;transition:border-color .15s}
|
|
.sw.on{border-color:var(--primary)}
|
|
/* MASK PREVIEW */
|
|
.mprev{background:var(--divider);border:1px solid var(--card-border);border-radius:6px;padding:10px 14px;font-size:13px;margin-top:10px;line-height:2}
|
|
.mb{background:var(--primary);color:var(--primary);padding:0 6px;border-radius:3px}
|
|
.ms2{color:var(--text-muted)}
|
|
.mn{background:#000;color:#000;padding:0 6px;border-radius:2px}
|
|
/* RULES TABLE */
|
|
.rtbl{width:100%;border-collapse:collapse;font-size:12px}
|
|
.rtbl th{text-align:left;padding:7px 9px;font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);border-bottom:1px solid var(--card-border)}
|
|
.rtbl td{padding:8px 9px;border-bottom:1px solid rgba(128,128,128,.07);vertical-align:middle}
|
|
.rtbl tr:hover td{background:rgba(128,128,128,.04)}
|
|
.rst{display:inline-block;padding:2px 7px;border-radius:99px;font-size:10px;font-weight:600}
|
|
.ract{background:rgba(16,185,129,.15);color:var(--success)}
|
|
.rcand{background:rgba(245,158,11,.15);color:var(--warning)}
|
|
.rtyp{font-size:10px;padding:2px 5px;border-radius:4px;background:rgba(128,128,128,.12);color:var(--text-dim)}
|
|
/* ABOUT */
|
|
.agrid{display:grid;grid-template-columns:1fr 1fr;gap:14px}
|
|
.ai{display:flex;gap:9px}
|
|
.ak{font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em}
|
|
.av{font-size:13px;font-weight:600;margin-top:2px}
|
|
/* NOTE */
|
|
.note{font-size:11px;color:var(--text-muted);font-style:italic;padding:5px 9px;background:rgba(59,130,246,.08);border-left:3px solid var(--blue);border-radius:0 4px 4px 0;margin-bottom:12px}
|
|
/* MODAL */
|
|
.mo{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.65);z-index:1000;align-items:center;justify-content:center}
|
|
.mo.open{display:flex}
|
|
.mbox{background:var(--card);border:1px solid var(--card-border);border-radius:12px;padding:24px;max-width:460px;width:90%;box-shadow:0 8px 40px rgba(0,0,0,.5);position:relative}
|
|
.mtit{font-size:15px;font-weight:700;margin-bottom:12px}
|
|
.mbody{font-size:13px;line-height:1.75;color:var(--text-dim)}
|
|
.mbody strong{color:var(--text)}
|
|
.mbody code{background:rgba(128,128,128,.15);padding:1px 5px;border-radius:4px;font-size:12px}
|
|
.mcls{position:absolute;top:14px;right:14px;background:none;border:none;font-size:20px;cursor:pointer;color:var(--text-muted)}
|
|
.mcls:hover{color:var(--text)}
|
|
/* PDF MASK EDITOR */
|
|
.me-panel{display:none;margin-top:14px;border:1px solid var(--card-border);border-radius:var(--radius);overflow:hidden}
|
|
.me-panel.open{display:block}
|
|
.me-toolbar{background:rgba(0,0,0,.15);border-bottom:1px solid var(--card-border);padding:8px 10px;display:flex;flex-wrap:wrap;gap:6px;align-items:center}
|
|
.me-sep{width:1px;height:24px;background:var(--card-border);margin:0 2px;flex-shrink:0}
|
|
.me-canvas{background:rgba(0,0,0,.25);min-height:320px;position:relative;overflow:hidden;display:flex;align-items:center;justify-content:center}
|
|
.me-canvas-inner{position:relative;display:inline-block;box-shadow:0 2px 16px rgba(0,0,0,.5)}
|
|
.me-canvas-inner img{display:block;max-width:100%}
|
|
.me-overlay{position:absolute;top:0;left:0;width:100%;height:100%;cursor:crosshair}
|
|
.me-mask-rect{position:absolute;background:rgba(0,0,0,.85);border:1px solid rgba(255,0,0,.4);cursor:pointer}
|
|
.me-mask-rect:hover{border-color:var(--danger);background:#000}
|
|
.me-hint{color:var(--text-muted);font-size:12px;text-align:center;padding:20px}
|
|
.me-hint span{font-size:32px;display:block;margin-bottom:8px}
|
|
.me-status{background:rgba(0,0,0,.15);border-top:1px solid var(--card-border);padding:5px 10px;font-size:11px;color:var(--text-muted);display:flex;gap:12px}
|
|
/* TOOLTIP */
|
|
[title]{position:relative}
|
|
/* native title is enough for mockup */
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="app-shell" id="shell">
|
|
|
|
<!-- HEADER -->
|
|
<div class="header">
|
|
<img src="LOGO_PLACEHOLDER" alt="aivanonym">
|
|
<div class="hv">v6.0 · prototype</div>
|
|
</div>
|
|
|
|
<!-- ONGLETS PRINCIPAUX (3 : Utilisation / Configuration / À propos) -->
|
|
<div class="tabs-bar">
|
|
<button class="tab-btn active" onclick="ST('use',this)">📄 Utilisation</button>
|
|
<button class="tab-btn" onclick="ST('cfg',this)">⚙️ Configuration</button>
|
|
<button class="tab-btn" onclick="ST('about',this)">ℹ️ À propos</button>
|
|
</div>
|
|
|
|
<div class="content">
|
|
|
|
<!-- ═══ UTILISATION ═══ -->
|
|
<div class="tab-pane active" id="tab-use">
|
|
|
|
<div class="card">
|
|
<div class="ct">🎨 Apparence <button class="hbtn" onclick="H('theme')" title="Aide sur le thème">❓</button></div>
|
|
<div class="theme-row">
|
|
<button class="tp on" onclick="TH('',this)" title="Thème sombre">🌙 Sombre</button>
|
|
<button class="tp" onclick="TH('light',this)" title="Thème clair">☀️ Clair</button>
|
|
<button class="tp" onclick="TH('medical',this)" title="Thème hospitalier">🏥️ Médical</button>
|
|
<button class="tp" onclick="TH('neutral',this)" title="Thème neutre gris">🌿 Neutre</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="ct">📂 Documents à anonymiser <button class="hbtn" onclick="H('fich')" title="Aide sur les fichiers">❓</button></div>
|
|
<div id="dropzone" class="dz" onclick="PICK()"
|
|
ondragover="event.preventDefault();this.classList.add('over')"
|
|
ondragleave="this.classList.remove('over')"
|
|
ondrop="DROP(event)">
|
|
<div class="dz-icon">⬆️</div>
|
|
<div class="dz-txt">Glissez-déposez vos fichiers ici</div>
|
|
<div class="dz-sub">PDF · Word · Images · Texte</div>
|
|
<div class="dz-acts">
|
|
<button class="btn bs" onclick="event.stopPropagation();PICK()" title="Choisir des fichiers individuels">📄 Fichiers</button>
|
|
<button class="btn bs" onclick="event.stopPropagation();PICKF()" title="Traiter tous les fichiers d'un dossier">📂 Dossier entier</button>
|
|
</div>
|
|
</div>
|
|
<div class="file-list" id="flist"></div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="ct">💾 Format de sortie <button class="hbtn" onclick="H('fmt')" title="Aide sur les formats">❓</button></div>
|
|
<div class="fmt-grid">
|
|
<div class="fmt-card on" onclick="this.classList.toggle('on')" title="Exporter un PDF avec les zones masquées en noir">
|
|
<div class="fi2">📄</div><div class="fn2">PDF anonymisé</div><div class="fs2">Zones noircies</div>
|
|
</div>
|
|
<div class="fmt-card on" onclick="this.classList.toggle('on')" title="Exporter un fichier texte avec les PII remplacés par des codes">
|
|
<div class="fi2">📝</div><div class="fn2">Texte .txt</div><div class="fs2">Mots remplacés par [NOM]…</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="brow">
|
|
<button class="btn bs" onclick="CLR()" title="Vider la liste de fichiers">✖ Effacer</button>
|
|
<button class="btn bp blg" id="btnGo" onclick="GO()" title="Lancer le traitement d'anonymisation">▶ Lancer l'anonymisation</button>
|
|
</div>
|
|
|
|
<div class="card psec" id="psec">
|
|
<div class="ct">⌛ Traitement en cours…</div>
|
|
<div class="plbl"><span id="pf">Fichier 1 / 3</span><span id="pp">0 %</span></div>
|
|
<div class="ptrack"><div class="pfill" id="pb" style="width:0%"></div></div>
|
|
<div class="psteps">
|
|
<span class="sp act" id="s0">📖 Extraction</span>
|
|
<span class="sp" id="s1">🧠 Détection</span>
|
|
<span class="sp" id="s2">🔒 Masquage</span>
|
|
<span class="sp" id="s3">📄 PDF final</span>
|
|
</div>
|
|
<div class="log" id="logb"></div>
|
|
<div style="text-align:right;margin-top:9px">
|
|
<button class="btn bs" onclick="STOP()" title="Arrêter le traitement en cours">⏹ Arrêter</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card psec" id="rsec">
|
|
<div class="ct">✅ Résultats</div>
|
|
<div class="rgrid">
|
|
<div class="sc"><div class="sv">3</div><div class="sl">Documents</div></div>
|
|
<div class="sc"><div class="sv">142</div><div class="sl">PII masqués</div></div>
|
|
<div class="sc"><div class="sv">4s</div><div class="sl">Durée</div></div>
|
|
<div class="sc"><div class="sv" style="color:var(--success)">A+</div><div class="sl">Qualité</div></div>
|
|
</div>
|
|
<div class="qbar">
|
|
<div class="qs">100.0</div>
|
|
<div><div class="qg">/ 100 · A+</div><div style="font-size:11px;color:var(--text-muted)">Aucune fuite détectée</div></div>
|
|
</div>
|
|
<div class="log">
|
|
<span class="lok">✓</span> CR_23456.pdf → 47 PII masqués<br>
|
|
<span class="lok">✓</span> CRO_81234.pdf → 38 PII masqués<br>
|
|
<span class="lok">✓</span> LETTRE_SORTIE.pdf → 57 PII masqués
|
|
</div>
|
|
<div style="display:flex;gap:8px;margin-top:12px;flex-wrap:wrap">
|
|
<button class="btn bsu" onclick="alert('Ouverture du dossier de sortie')" title="Ouvrir le dossier contenant les fichiers anonymisés">📁 Ouvrir le dossier</button>
|
|
<button class="btn bs" onclick="RRES()" title="Revenir au début pour traiter d'autres fichiers">🔄 Nouveau traitement</button>
|
|
</div>
|
|
</div>
|
|
</div><!-- /use -->
|
|
|
|
<!-- ═══ CONFIGURATION ═══ -->
|
|
<div class="tab-pane" id="tab-cfg">
|
|
<div class="stabs">
|
|
<button class="stab on" onclick="SS('reg',this)">⚙️ Réglages</button>
|
|
<button class="stab" onclick="SS('msk',this)">🎭 Masquage</button>
|
|
<button class="stab" onclick="SS('shr',this)">🔄 Partage</button>
|
|
<button class="stab" onclick="SS('rul',this)">🛡️ Règles <span style="display:inline-block;background:var(--primary);color:#fff;font-size:10px;padding:1px 5px;border-radius:9px;margin-left:4px">2</span></button>
|
|
</div>
|
|
|
|
<!-- RÉGLAGES -->
|
|
<div class="spane on" id="sp-reg">
|
|
<div class="scols">
|
|
<div>
|
|
<div class="card">
|
|
<div class="ct">🔍 Données à détecter <button class="hbtn" onclick="H('det')" title="Aide sur la détection">❓</button></div>
|
|
<div class="srow"><div><div class="slbl">Noms et prénoms</div><div class="shint">Gazetteers INSEE · CamemBERT</div></div><label class="tog"><input type="checkbox" checked><span class="tsl"></span></label></div>
|
|
<div class="srow"><div><div class="slbl">Dates de naissance</div><div class="shint">Uniquement la date de naissance</div></div><label class="tog"><input type="checkbox" checked><span class="tsl"></span></label></div>
|
|
<div class="srow"><div><div class="slbl">Etablissements</div><div class="shint">Répertoire FINESS + contexte</div></div><label class="tog"><input type="checkbox" checked><span class="tsl"></span></label></div>
|
|
<div class="srow"><div><div class="slbl">Adresses et codes postaux</div></div><label class="tog"><input type="checkbox" checked><span class="tsl"></span></label></div>
|
|
<div class="srow"><div><div class="slbl">N° sécurité sociale</div></div><label class="tog"><input type="checkbox" checked><span class="tsl"></span></label></div>
|
|
<div class="srow"><div><div class="slbl">Téléphones et e-mails</div></div><label class="tog"><input type="checkbox" checked><span class="tsl"></span></label></div>
|
|
<div class="srow"><div><div class="slbl">N° adhérent mutuelle</div></div><label class="tog"><input type="checkbox" checked><span class="tsl"></span></label></div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="ct">🧠 Moteurs NER <button class="hbtn" onclick="H('ner')" title="Aide sur les moteurs IA">❓</button></div>
|
|
<div class="srow"><div><div class="slbl">CamemBERT-bio <span style="font-size:10px;color:var(--success)">RAPIDE</span></div><div class="shint">~10 ms/doc · F1 = 0.963</div></div><label class="tog"><input type="checkbox" checked><span class="tsl"></span></label></div>
|
|
<div class="srow"><div><div class="slbl">EDS-Pseudo <span style="font-size:10px;color:var(--blue)">PRECIS</span></div><div class="shint">~200 ms/doc · médical français</div></div><label class="tog"><input type="checkbox" checked><span class="tsl"></span></label></div>
|
|
<div class="srow"><div><div class="slbl">GLiNER <span style="font-size:10px;color:var(--text-muted)">OPTIONNEL</span></div><div class="shint">~95 ms/doc · vote croisé</div></div><label class="tog"><input type="checkbox"><span class="tsl"></span></label></div>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="card">
|
|
<div class="ct">✅ Termes à toujours conserver <button class="hbtn" onclick="H('wl')" title="Aide liste blanche">❓</button></div>
|
|
<div class="note">Ces termes ne seront <strong>jamais masqués</strong>, même s’ils ressemblent à un nom propre.</div>
|
|
<div class="tagrow"><input class="taginput" id="wIn" placeholder="Ex : FUROSEMIDE…" onkeydown="if(event.key==='Enter')AT('w')"><button class="btn bs" onclick="AT('w')" title="Ajouter ce terme à la liste blanche">+ Ajouter</button></div>
|
|
<div class="tagcloud" id="wTags">
|
|
<span class="tag tw">FUROSEMIDE <span class="tx" onclick="this.parentElement.remove()">×</span></span>
|
|
<span class="tag tw">rééducation fonctionnelle <span class="tx" onclick="this.parentElement.remove()">×</span></span>
|
|
<span class="tag tw">classification internationale <span class="tx" onclick="this.parentElement.remove()">×</span></span>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="ct">🚫 Termes à toujours masquer <button class="hbtn" onclick="H('bl')" title="Aide liste noire">❓</button></div>
|
|
<div class="note">Ces termes seront <strong>toujours masqués</strong>, même sans contexte médical autour.</div>
|
|
<div class="tagrow"><input class="taginput" id="bIn" placeholder="Ex : CHUXX, Dr Dupont…" onkeydown="if(event.key==='Enter')AT('b')"><button class="btn bs" onclick="AT('b')" title="Ajouter ce terme à la liste noire">+ Ajouter</button></div>
|
|
<div class="tagcloud" id="bTags">
|
|
<span class="tag tb">CHUXX <span class="tx" onclick="this.parentElement.remove()">×</span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- MASQUAGE -->
|
|
<div class="spane" id="sp-msk">
|
|
<div class="scols">
|
|
<div>
|
|
<div class="card">
|
|
<div class="ct">⬛ Couleur de masquage (PDF) <button class="hbtn" onclick="H('col')" title="Aide couleur de masquage">❓</button></div>
|
|
<div class="note">Couleur des rectangles dans le PDF final.</div>
|
|
<div class="swrow">
|
|
<div class="sw on" style="background:#000" onclick="SW(this)" title="Noir (standard officiel)"></div>
|
|
<div class="sw" style="background:#1a1a2e" onclick="SW(this)" title="Bleu nuit"></div>
|
|
<div class="sw" style="background:#374151" onclick="SW(this)" title="Gris foncé"></div>
|
|
<div class="sw" style="background:#92400e" onclick="SW(this)" title="Marron"></div>
|
|
<div class="sw" style="background:#1e3a5f" onclick="SW(this)" title="Bleu marine"></div>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="ct">🏷️ Style des marqueurs (texte) <button class="hbtn" onclick="H('sty')" title="Aide style de marqueurs">❓</button></div>
|
|
<div style="display:flex;flex-direction:column;gap:8px;margin-bottom:10px">
|
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer" title="Remplace par [NOM], [DATE_NAISSANCE], etc.">
|
|
<input type="radio" name="ms" value="b" checked onchange="UP()">
|
|
Crochets — <code style="color:var(--primary)">[NOM]</code>
|
|
</label>
|
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer" title="Remplace par des astérisques discrets">
|
|
<input type="radio" name="ms" value="s" onchange="UP()">
|
|
Etoiles — <code style="color:var(--text-muted)">***</code>
|
|
</label>
|
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer" title="Remplace par des blocs noirs comme dans un PDF">
|
|
<input type="radio" name="ms" value="n" onchange="UP()">
|
|
Noirci — <span style="background:#000;color:#000;padding:0 8px;border-radius:2px">NOM</span>
|
|
</label>
|
|
</div>
|
|
<div id="mprev" class="mprev"></div>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="card">
|
|
<div class="ct">📐 Epaisseur du masque <button class="hbtn" onclick="H('ep')" title="Aide épaisseur">❓</button></div>
|
|
<div class="note">Marge autour du texte masqué (en points).</div>
|
|
<div class="srow"><div><div class="slbl">Marge horizontale</div></div><input type="range" min="0" max="6" value="2" style="width:120px;accent-color:var(--primary)" title="Régler la marge gauche-droite du masque"></div>
|
|
<div class="srow"><div><div class="slbl">Marge verticale</div></div><input type="range" min="0" max="6" value="1" style="width:120px;accent-color:var(--primary)" title="Régler la marge haut-bas du masque"></div>
|
|
<div class="srow"><div><div class="slbl">Coins arrondis</div></div><label class="tog"><input type="checkbox"><span class="tsl"></span></label></div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="ct">🔒 Codes de remplacement <button class="hbtn" onclick="H('ph')" title="Aide codes de remplacement">❓</button></div>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:5px;font-size:12px">
|
|
<div style="color:var(--text-muted)">Nom/Prénom</div><div style="color:var(--primary);font-weight:600">[NOM]</div>
|
|
<div style="color:var(--text-muted)">Date naissance</div><div style="color:var(--primary);font-weight:600">[DATE_NAISSANCE]</div>
|
|
<div style="color:var(--text-muted)">Etablissement</div><div style="color:var(--primary);font-weight:600">[ETABLISSEMENT]</div>
|
|
<div style="color:var(--text-muted)">Adresse</div><div style="color:var(--primary);font-weight:600">[ADRESSE]</div>
|
|
<div style="color:var(--text-muted)">Téléphone</div><div style="color:var(--primary);font-weight:600">[TEL]</div>
|
|
<div style="color:var(--text-muted)">N° sécu</div><div style="color:var(--primary);font-weight:600">[NIR]</div>
|
|
<div style="color:var(--text-muted)">IPP</div><div style="color:var(--primary);font-weight:600">[IPP]</div>
|
|
<div style="color:var(--text-muted)">Email</div><div style="color:var(--primary);font-weight:600">[EMAIL]</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ÉDITEUR DE MASQUES PDF -->
|
|
<div class="card" style="margin-top:0">
|
|
<div class="ct">
|
|
🏠 Masques de zones fixes (logos, en-têtes)
|
|
<button class="hbtn" onclick="H('medit')" title="Aide éditeur de masques">❓</button>
|
|
<button class="btn bp" style="margin-left:8px;padding:5px 12px;font-size:12px" onclick="METOG()" id="meBtnOpen" title="Ouvrir l'éditeur pour dessiner des zones à masquer sur un PDF modèle">
|
|
🖉 Ouvrir l'éditeur de masques
|
|
</button>
|
|
</div>
|
|
<div class="note">
|
|
Dessinez des rectangles sur un PDF modèle pour masquer systématiquement les logos, en-têtes ou zones fixes —
|
|
indépendamment de l’OCR.
|
|
</div>
|
|
|
|
<!-- Panneau éditeur (togglable) -->
|
|
<div class="me-panel" id="mePanel">
|
|
|
|
<!-- Barre d'outils -->
|
|
<div class="me-toolbar">
|
|
<button class="btn bs" style="padding:5px 10px;font-size:12px" onclick="ME_open()" title="Charger un PDF pour définir les zones à masquer">
|
|
📄 Ouvrir PDF…
|
|
</button>
|
|
|
|
<div class="me-sep"></div>
|
|
|
|
<button class="btn bs" style="padding:5px 8px;font-size:13px;min-width:30px" onclick="ME_zoom(-1)" title="Zoom arrière">−</button>
|
|
<span id="meZoomLbl" style="font-size:12px;color:var(--text-muted);min-width:38px;text-align:center">100%</span>
|
|
<button class="btn bs" style="padding:5px 8px;font-size:13px;min-width:30px" onclick="ME_zoom(1)" title="Zoom avant">+</button>
|
|
|
|
<div class="me-sep"></div>
|
|
|
|
<span style="font-size:12px;color:var(--text-muted)">Template :</span>
|
|
<input id="meTplName" type="text" value="template_masques" style="width:130px;background:rgba(128,128,128,.12);border:1px solid var(--card-border);border-radius:5px;padding:4px 7px;color:var(--text);font-size:12px;outline:none" title="Nom du template de masques">
|
|
<button class="btn bs" style="padding:5px 10px;font-size:12px" onclick="ME_save()" title="Sauvegarder ce template de masques dans un fichier">💾 Sauver</button>
|
|
<button class="btn bs" style="padding:5px 10px;font-size:12px" onclick="ME_load()" title="Charger un template de masques existant">📁 Charger</button>
|
|
|
|
<div class="me-sep"></div>
|
|
|
|
<button class="btn bs" style="padding:5px 10px;font-size:12px;color:var(--danger)" onclick="ME_clearPage()" title="Effacer tous les masques de la page actuelle">
|
|
🗑 Effacer page
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Zone de dessin -->
|
|
<div class="me-canvas" id="meCanvas">
|
|
<div id="meHint" class="me-hint">
|
|
<span>📄</span>
|
|
Ouvrez un PDF pour commencer à dessiner des zones de masquage.<br>
|
|
<span style="font-size:11px">Cliquez-glissez pour tracer un rectangle • Clic sur un masque pour le supprimer</span>
|
|
</div>
|
|
<div id="meImgWrap" class="me-canvas-inner" style="display:none">
|
|
<img id="meImg" src="" alt="page PDF">
|
|
<canvas id="meOverlay" class="me-overlay"
|
|
onmousedown="ME_mdown(event)"
|
|
onmousemove="ME_mmove(event)"
|
|
onmouseup="ME_mup(event)">
|
|
</canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Barre d'action inférieure -->
|
|
<div class="me-toolbar" style="border-top:1px solid var(--card-border);border-bottom:none;justify-content:space-between">
|
|
<div style="display:flex;gap:6px;align-items:center">
|
|
<span style="font-size:12px;color:var(--text-muted)">DPI raster :</span>
|
|
<input id="meDpi" type="number" value="200" min="72" max="600" step="10"
|
|
style="width:58px;background:rgba(128,128,128,.12);border:1px solid var(--card-border);border-radius:5px;padding:4px 6px;color:var(--text);font-size:12px;outline:none;text-align:center"
|
|
title="Résolution de rendu (200 DPI recommandé pour la précision des masques)">
|
|
<button class="btn bs" style="padding:5px 10px;font-size:12px" onclick="ME_preview()" title="Prévisualiser le résultat du masquage sur la page courante">
|
|
👁 Prévisualiser
|
|
</button>
|
|
</div>
|
|
<button class="btn bp" style="padding:6px 14px;font-size:12px" onclick="ME_apply()" title="Appliquer ce template aux documents du traitement en cours">
|
|
▶ Appliquer le template
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Barre de statut -->
|
|
<div class="me-status">
|
|
<span id="meStat">Aucun PDF chargé</span>
|
|
<span id="meMaskCount">0 masque(s)</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div><!-- /sp-msk -->
|
|
|
|
<!-- PARTAGE -->
|
|
<div class="spane" id="sp-shr">
|
|
<div class="card">
|
|
<div class="ct">📤 Exporter la configuration <button class="hbtn" onclick="H('exp')" title="Aide export">❓</button></div>
|
|
<div class="note">Génère un fichier .json avec vos listes, à envoyer par e-mail à d’autres établissements.</div>
|
|
<button class="btn bs" onclick="alert('Export JSON simulé')" title="Télécharger votre configuration en fichier .json">⬇ Exporter (.json)</button>
|
|
</div>
|
|
<div class="card">
|
|
<div class="ct">📥 Importer une configuration <button class="hbtn" onclick="H('imp')" title="Aide import">❓</button></div>
|
|
<div class="note">Importez un fichier reçu. Vos réglages locaux ne seront pas supprimés.</div>
|
|
<button class="btn bs" onclick="alert('Import simulé')" title="Charger un fichier .json reçu par e-mail">⬆ Importer (.json)</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- RÈGLES (sous-onglet) -->
|
|
<div class="spane" id="sp-rul">
|
|
<div class="card">
|
|
<div class="ct">🛡️ Règles actives <button class="hbtn" onclick="H('rul')" title="Aide sur les règles">❓</button></div>
|
|
<div class="note">Ces règles adaptent le moteur à votre établissement. Chaque règle est validée avant activation.</div>
|
|
<table class="rtbl">
|
|
<thead><tr><th>Label</th><th>Type</th><th>Cible → Résultat</th><th>Statut</th><th></th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>Masquer le sigle CHUXX</td>
|
|
<td><span class="rtyp">exact</span></td>
|
|
<td><code>CHUXX</code> → <code style="color:var(--primary)">[MASK]</code></td>
|
|
<td><span class="rst ract">Actif</span></td>
|
|
<td><button class="btn bs" style="padding:3px 8px;font-size:11px" onclick="SIM('CHUXX')" title="Tester cette règle sur un texte libre">▶ Tester</button></td>
|
|
</tr>
|
|
<tr>
|
|
<td>Préserver “classification internationale”</td>
|
|
<td><span class="rtyp">preserve</span></td>
|
|
<td>conservé tel quel</td>
|
|
<td><span class="rst ract">Actif</span></td>
|
|
<td><button class="btn bs" style="padding:3px 8px;font-size:11px" onclick="SIM('classification')" title="Tester cette règle sur un texte libre">▶ Tester</button></td>
|
|
</tr>
|
|
<tr>
|
|
<td>Identifier N° 1234567</td>
|
|
<td><span class="rtyp">norm-id</span></td>
|
|
<td><code>N° 1234567</code> → <code style="color:var(--primary)">[NDA]</code></td>
|
|
<td><span class="rst rcand">Candidat</span></td>
|
|
<td><button class="btn bs" style="padding:3px 8px;font-size:11px" onclick="SIM('1234567')" title="Tester cette règle sur un texte libre">▶ Tester</button></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<div style="margin-top:12px;display:flex;gap:8px">
|
|
<button class="btn bp" onclick="alert('Editeur de règles (à venir)')" title="Créer une nouvelle règle d'administration">+ Nouvelle règle</button>
|
|
<button class="btn bs" title="Recharger les règles depuis la configuration">🔄 Recharger</button>
|
|
</div>
|
|
</div>
|
|
<div class="card" id="simcard" style="display:none">
|
|
<div class="ct">🧪 Testeur de règle</div>
|
|
<label style="font-size:12px;color:var(--text-muted);display:block;margin-bottom:5px">Texte de test</label>
|
|
<textarea id="simtxt" rows="3" style="width:100%;background:rgba(0,0,0,.2);border:1px solid var(--card-border);border-radius:6px;padding:8px;color:var(--text);font-size:13px;resize:vertical;outline:none"></textarea>
|
|
<div style="display:flex;gap:8px;margin:10px 0">
|
|
<button class="btn bp" onclick="RSIM()" title="Exécuter la simulation sur le texte saisi">▶ Tester</button>
|
|
<button class="btn bs" onclick="document.getElementById('simcard').style.display='none'" title="Fermer le testeur">✖ Fermer</button>
|
|
</div>
|
|
<div id="simout" style="display:none;background:rgba(0,0,0,.2);border:1px solid var(--card-border);border-radius:6px;padding:10px;font-size:13px;line-height:1.8"></div>
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- /cfg -->
|
|
|
|
<!-- ═══ À PROPOS ═══ -->
|
|
<div class="tab-pane" id="tab-about">
|
|
<div class="card">
|
|
<div class="ct">ℹ️ Informations</div>
|
|
<div class="agrid">
|
|
<div class="ai"><span style="font-size:20px">🏷️</span><div><div class="ak">Version</div><div class="av">v6.0 (prototype)</div></div></div>
|
|
<div class="ai"><span style="font-size:20px">📅</span><div><div class="ak">Build</div><div class="av">2026-04-28</div></div></div>
|
|
<div class="ai"><span style="font-size:20px">🧠</span><div><div class="ak">Moteurs NER</div><div class="av">CamemBERT · EDS-Pseudo · GLiNER</div></div></div>
|
|
<div class="ai"><span style="font-size:20px">🔒</span><div><div class="ak">Traitement</div><div class="av">100 % local — aucune donnée transmise</div></div></div>
|
|
<div class="ai"><span style="font-size:20px">📚</span><div><div class="ak">Gazetteers</div><div class="av">INSEE 219K · FINESS 108K · BDPM 7K</div></div></div>
|
|
<div class="ai"><span style="font-size:20px">📁</span><div><div class="ak">Formats</div><div class="av">PDF · DOCX · ODT · RTF · TXT · Images</div></div></div>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="ct">📊 Dernière session</div>
|
|
<div class="qbar">
|
|
<div class="qs">100.0</div>
|
|
<div><div class="qg">/ 100 · A+</div><div style="font-size:11px;color:var(--text-muted)">22 PDFs · 0 fuite détectée</div></div>
|
|
</div>
|
|
</div>
|
|
</div><!-- /about -->
|
|
|
|
</div><!-- /content -->
|
|
</div><!-- /shell -->
|
|
|
|
<!-- MODAL AIDE -->
|
|
<div class="mo" id="mo" onclick="if(event.target===this)CH()">
|
|
<div class="mbox">
|
|
<button class="mcls" onclick="CH()">×</button>
|
|
<div class="mtit" id="mt"></div>
|
|
<div class="mbody" id="mb"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Navigation onglets principaux
|
|
function ST(n,b){
|
|
document.querySelectorAll(".tab-pane").forEach(function(p){p.classList.remove("active");});
|
|
document.querySelectorAll(".tab-btn").forEach(function(x){x.classList.remove("active");});
|
|
document.getElementById("tab-"+n).classList.add("active");
|
|
b.classList.add("active");
|
|
}
|
|
// Sous-onglets configuration
|
|
function SS(n,b){
|
|
document.querySelectorAll(".spane").forEach(function(p){p.classList.remove("on");});
|
|
document.querySelectorAll(".stab").forEach(function(x){x.classList.remove("on");});
|
|
document.getElementById("sp-"+n).classList.add("on");
|
|
b.classList.add("on");
|
|
}
|
|
// Themes
|
|
function TH(t,b){
|
|
document.getElementById("shell").className="app-shell"+(t?" theme-"+t:"");
|
|
document.querySelectorAll(".tp").forEach(function(x){x.classList.remove("on");});
|
|
b.classList.add("on");
|
|
}
|
|
// Fichiers
|
|
var SAMP=[{n:"CR_23456.pdf",s:"142 Ko"},{n:"CRO_81234.pdf",s:"98 Ko"},{n:"LETTRE_SORTIE.pdf",s:"64 Ko"}];
|
|
var _f=[];
|
|
function RF(f){
|
|
_f=f;
|
|
var h="";
|
|
for(var i=0;i<f.length;i++){
|
|
h+="<div class=\"fi\" id=\"fi"+i+"\"><span style=\"font-size:16px\">📄</span><span class=\"fn\">"+f[i].n+"</span><span class=\"fs\">"+f[i].s+"</span><button class=\"fx\" onclick=\"document.getElementById('fi"+i+"').remove()\">×</button></div>";
|
|
}
|
|
document.getElementById("flist").innerHTML=h;
|
|
}
|
|
function PICK(){RF(SAMP);}
|
|
function PICKF(){RF(SAMP.concat([{n:"ANAPATH.pdf",s:"211 Ko"},{n:"BACTERIO.docx",s:"34 Ko"}]));}
|
|
function DROP(e){e.preventDefault();document.getElementById("dropzone").classList.remove("over");RF(SAMP);}
|
|
function CLR(){_f=[];document.getElementById("flist").innerHTML="";RRES();}
|
|
// Progression
|
|
var _t=null;
|
|
var STP=["s0","s1","s2","s3"];
|
|
var LG=[
|
|
["lok","OK Lecture CR_23456.pdf"],
|
|
["","- Extraction texte..."],
|
|
["lok","OK 47 entites detectees"],
|
|
["","- Masquage..."],
|
|
["lok","OK CR_23456.pdf termine"],
|
|
["","- CRO_81234.pdf..."],
|
|
["lok","OK CRO_81234.pdf termine"],
|
|
["","- LETTRE_SORTIE.pdf..."],
|
|
["lok","OK 142 PII masques au total"]
|
|
];
|
|
function GO(){
|
|
if(!_f.length)PICK();
|
|
document.getElementById("psec").classList.add("vis");
|
|
document.getElementById("rsec").classList.remove("vis");
|
|
document.getElementById("btnGo").disabled=true;
|
|
document.getElementById("logb").innerHTML="";
|
|
for(var i=0;i<STP.length;i++) document.getElementById(STP[i]).className="sp";
|
|
document.getElementById(STP[0]).className="sp act";
|
|
var p=0,si=0,li=0;
|
|
var lb=document.getElementById("logb");
|
|
_t=setInterval(function(){
|
|
p+=5; if(p>100)p=100;
|
|
document.getElementById("pb").style.width=p+"%";
|
|
document.getElementById("pp").textContent=p+" %";
|
|
document.getElementById("pf").textContent="Fichier "+Math.min(3,Math.ceil(p/34))+" / 3";
|
|
var ns=Math.min(STP.length-1,Math.floor(p/26));
|
|
if(ns!==si){document.getElementById(STP[si]).className="sp done";si=ns;document.getElementById(STP[si]).className="sp act";}
|
|
if(li<LG.length&&Math.random()>.5){
|
|
lb.innerHTML+="<span class=\""+LG[li][0]+"\">"+LG[li][1]+"</span>\n";
|
|
lb.scrollTop=lb.scrollHeight; li++;
|
|
}
|
|
if(p>=100){
|
|
clearInterval(_t);
|
|
for(var j=0;j<STP.length;j++){document.getElementById(STP[j]).className="sp done";}
|
|
setTimeout(function(){
|
|
document.getElementById("psec").classList.remove("vis");
|
|
document.getElementById("rsec").classList.add("vis");
|
|
document.getElementById("btnGo").disabled=false;
|
|
},600);
|
|
}
|
|
},100);
|
|
}
|
|
function STOP(){if(_t)clearInterval(_t);document.getElementById("psec").classList.remove("vis");document.getElementById("btnGo").disabled=false;}
|
|
function RRES(){document.getElementById("rsec").classList.remove("vis");document.getElementById("psec").classList.remove("vis");document.getElementById("pb").style.width="0%";}
|
|
// Tags
|
|
function AT(t){
|
|
var inp=document.getElementById(t==="w"?"wIn":"bIn");
|
|
var v=inp.value.trim(); if(!v)return;
|
|
var c=document.getElementById(t==="w"?"wTags":"bTags");
|
|
var s=document.createElement("span");
|
|
s.className="tag "+(t==="w"?"tw":"tb");
|
|
s.innerHTML=v+" <span class=\"tx\" onclick=\"this.parentElement.remove()\">×</span>";
|
|
c.appendChild(s); inp.value="";
|
|
}
|
|
// Swatches
|
|
function SW(el){document.querySelectorAll(".sw").forEach(function(s){s.classList.remove("on");});el.classList.add("on");}
|
|
// Preview masquage
|
|
function UP(){
|
|
var v=document.querySelector("input[name='ms']:checked").value;
|
|
var nm,nd,ne;
|
|
if(v==="b"){nm="<span class=\"mb\">[NOM]</span>";nd="<span class=\"mb\">[DATE_NAISSANCE]</span>";ne="<span class=\"mb\">[ETABLISSEMENT]</span>";}
|
|
else if(v==="s"){nm="<span class=\"ms2\">***</span>";nd="<span class=\"ms2\">***</span>";ne="<span class=\"ms2\">***</span>";}
|
|
else{nm="<span class=\"mn\">████</span>";nd="<span class=\"mn\">████</span>";ne="<span class=\"mn\">████</span>";}
|
|
document.getElementById("mprev").innerHTML="Patient : "+nm+"<br>Né le "+nd+"<br>Service du "+ne;
|
|
}
|
|
UP();
|
|
// Simulateur de regle
|
|
var _rt="";
|
|
function SIM(t){
|
|
_rt=t;
|
|
document.getElementById("simcard").style.display="block";
|
|
document.getElementById("simout").style.display="none";
|
|
document.getElementById("simtxt").value="Dossier "+t+" -- suivi au "+t+" de Chicago.\nCode CIM retient la classification internationale G56.8.";
|
|
document.getElementById("simcard").scrollIntoView({behavior:"smooth"});
|
|
}
|
|
function RSIM(){
|
|
var txt=document.getElementById("simtxt").value;
|
|
var re=new RegExp(_rt.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"gi");
|
|
var out=txt.replace(re,function(m){return "<mark style=\"background:var(--primary);color:white;padding:1px 5px;border-radius:3px\">[MASK]</mark>";});
|
|
out=out.replace(/\n/g,"<br>");
|
|
var el=document.getElementById("simout");
|
|
el.innerHTML=out; el.style.display="block";
|
|
}
|
|
|
|
// ── ÉDITEUR DE MASQUES PDF ──────────────────────────────────────────────────
|
|
var _meZoom=1, _meMasks=[], _meDrawing=false, _meStart=null, _meTmpRect=null;
|
|
|
|
function METOG(){
|
|
var p=document.getElementById("mePanel");
|
|
var b=document.getElementById("meBtnOpen");
|
|
if(p.classList.contains("open")){
|
|
p.classList.remove("open");
|
|
b.innerHTML="🖉 Ouvrir l'éditeur de masques";
|
|
} else {
|
|
p.classList.add("open");
|
|
b.innerHTML="× Fermer l'éditeur";
|
|
p.scrollIntoView({behavior:"smooth",block:"nearest"});
|
|
}
|
|
}
|
|
|
|
function ME_open(){
|
|
// Simule le chargement d'un PDF : affiche une image de placeholder
|
|
var w=document.getElementById("meImgWrap");
|
|
var img=document.getElementById("meImg");
|
|
var hint=document.getElementById("meHint");
|
|
var cv=document.getElementById("meOverlay");
|
|
// Page simulée (rectangle blanc avec texte simulé)
|
|
var c2=document.createElement("canvas");
|
|
c2.width=595; c2.height=842;
|
|
var ctx=c2.getContext("2d");
|
|
ctx.fillStyle="#ffffff"; ctx.fillRect(0,0,595,842);
|
|
ctx.fillStyle="#e5e7eb"; ctx.fillRect(0,0,595,90);
|
|
ctx.fillStyle="#9ca3af"; ctx.font="bold 18px sans-serif"; ctx.fillText("EN-TETE ETABLISSEMENT [LOGO]",30,40);
|
|
ctx.fillStyle="#6b7280"; ctx.font="13px sans-serif";
|
|
ctx.fillText("Service de cardiologie | Tel : 05.59.XX.XX.XX",30,68);
|
|
ctx.fillStyle="#111827"; ctx.font="13px sans-serif";
|
|
ctx.fillText("Patient : Dupont Jean Ne le : 12/03/1955",30,130);
|
|
ctx.fillText("IPP : 1234567 NDA : 8901234",30,155);
|
|
ctx.fillText("Motif : Insuffisance cardiaque decompensee.",30,200);
|
|
ctx.fillText("Traitement : FUROSEMIDE 40mg, BISOPROLOL 5mg.",30,225);
|
|
ctx.fillStyle="#6b7280"; ctx.font="11px sans-serif";
|
|
ctx.fillText("Signe : Dr Martin RPPS 12345678 | mmartin@chuxx.fr",30,790);
|
|
img.src=c2.toDataURL();
|
|
img.onload=function(){
|
|
cv.width=img.naturalWidth;
|
|
cv.height=img.naturalHeight;
|
|
cv.style.width=Math.round(img.naturalWidth*_meZoom)+"px";
|
|
cv.style.height=Math.round(img.naturalHeight*_meZoom)+"px";
|
|
ME_redraw();
|
|
};
|
|
hint.style.display="none";
|
|
w.style.display="inline-block";
|
|
document.getElementById("meStat").textContent="page_modele.pdf — page 1/1";
|
|
ME_redraw();
|
|
}
|
|
|
|
function ME_zoom(d){
|
|
_meZoom=Math.min(3,Math.max(0.3,_meZoom+d*0.15));
|
|
document.getElementById("meZoomLbl").textContent=Math.round(_meZoom*100)+"%";
|
|
var img=document.getElementById("meImg");
|
|
var cv=document.getElementById("meOverlay");
|
|
img.style.width=Math.round(img.naturalWidth*_meZoom)+"px";
|
|
cv.style.width=Math.round(img.naturalWidth*_meZoom)+"px";
|
|
cv.style.height=Math.round(img.naturalHeight*_meZoom)+"px";
|
|
ME_redraw();
|
|
}
|
|
|
|
function _meCoord(e){
|
|
var cv=document.getElementById("meOverlay");
|
|
var r=cv.getBoundingClientRect();
|
|
return {x:(e.clientX-r.left)/_meZoom, y:(e.clientY-r.top)/_meZoom};
|
|
}
|
|
|
|
function ME_mdown(e){
|
|
_meDrawing=true;
|
|
_meStart=_meCoord(e);
|
|
}
|
|
function ME_mmove(e){
|
|
if(!_meDrawing)return;
|
|
ME_redraw();
|
|
var cur=_meCoord(e);
|
|
var cv=document.getElementById("meOverlay");
|
|
var ctx=cv.getContext("2d");
|
|
ctx.save();
|
|
ctx.scale(_meZoom,_meZoom);
|
|
ctx.fillStyle="rgba(0,0,0,0.55)";
|
|
ctx.strokeStyle="rgba(233,69,96,0.8)";
|
|
ctx.lineWidth=1.5/_meZoom;
|
|
var x=Math.min(_meStart.x,cur.x), y=Math.min(_meStart.y,cur.y);
|
|
var w=Math.abs(cur.x-_meStart.x), h=Math.abs(cur.y-_meStart.y);
|
|
ctx.fillRect(x,y,w,h);
|
|
ctx.strokeRect(x,y,w,h);
|
|
ctx.restore();
|
|
}
|
|
function ME_mup(e){
|
|
if(!_meDrawing)return;
|
|
_meDrawing=false;
|
|
var cur=_meCoord(e);
|
|
var x=Math.min(_meStart.x,cur.x), y=Math.min(_meStart.y,cur.y);
|
|
var w=Math.abs(cur.x-_meStart.x), h=Math.abs(cur.y-_meStart.y);
|
|
if(w>4&&h>4){
|
|
_meMasks.push({x:Math.round(x),y:Math.round(y),w:Math.round(w),h:Math.round(h)});
|
|
ME_updCount();
|
|
}
|
|
ME_redraw();
|
|
}
|
|
function ME_redraw(){
|
|
var cv=document.getElementById("meOverlay");
|
|
if(!cv.width)return;
|
|
var ctx=cv.getContext("2d");
|
|
ctx.clearRect(0,0,cv.width,cv.height);
|
|
ctx.save();
|
|
ctx.scale(_meZoom,_meZoom);
|
|
ctx.fillStyle="rgba(0,0,0,0.82)";
|
|
ctx.strokeStyle="rgba(233,69,96,0.6)";
|
|
ctx.lineWidth=1/_meZoom;
|
|
for(var i=0;i<_meMasks.length;i++){
|
|
var m=_meMasks[i];
|
|
ctx.fillRect(m.x,m.y,m.w,m.h);
|
|
ctx.strokeRect(m.x,m.y,m.w,m.h);
|
|
ctx.fillStyle="rgba(233,69,96,0.7)";
|
|
ctx.font="10px sans-serif";
|
|
ctx.fillText("x",m.x+m.w/2-4,m.y+m.h/2+4);
|
|
ctx.fillStyle="rgba(0,0,0,0.82)";
|
|
}
|
|
ctx.restore();
|
|
}
|
|
function ME_clearPage(){
|
|
_meMasks=[];
|
|
ME_redraw();
|
|
ME_updCount();
|
|
}
|
|
function ME_updCount(){
|
|
document.getElementById("meMaskCount").textContent=_meMasks.length+" masque(s)";
|
|
}
|
|
function ME_save(){
|
|
var name=document.getElementById("meTplName").value||"template_masques";
|
|
alert("Template \""+name+"\" sauvegardé ("+_meMasks.length+" masque(s)).\n\nFichier : config/mask_templates/"+name+".json");
|
|
}
|
|
function ME_load(){
|
|
alert("Sélection du fichier template simulée.\nDans l'application réelle : ouverture d'un sélecteur de fichier .json");
|
|
}
|
|
function ME_preview(){
|
|
var dpi=parseInt(document.getElementById("meDpi").value)||200;
|
|
alert("Prévisualisation raster à "+dpi+" DPI\nMasques appliqués : "+_meMasks.length+"\n\nDans l'application réelle : rendu de la page avec les zones noircies.");
|
|
}
|
|
function ME_apply(){
|
|
var name=document.getElementById("meTplName").value||"template_masques";
|
|
if(_meMasks.length===0){alert("Aucun masque défini. Dessinez au moins une zone.");return;}
|
|
alert("Template \""+name+"\" activé pour le prochain traitement.\n"+_meMasks.length+" zone(s) seront masquées systématiquement.");
|
|
}
|
|
|
|
// Aide contextuelle
|
|
var HELP={
|
|
"theme":["Apparence","Choisissez le thème visuel adapté à votre environnement.<br><br><strong>Sombre</strong> — fond foncé, idéal en luminosité réduite.<br><strong>Clair</strong> — fond blanc, pour écrans bien éclairés.<br><strong>Médical</strong> — bleu et blanc, proche des interfaces hospitalières.<br><strong>Neutre</strong> — tons gris discrets."],
|
|
"fich":["Documents","Glissez-déposez vos fichiers dans la zone, ou cliquez sur <strong>Fichiers</strong>.<br><br>Cliquez sur <strong>Dossier entier</strong> pour traiter automatiquement tous les documents d'un répertoire.<br><br><strong>Formats acceptés :</strong> PDF, Word (.docx), ODT, RTF, Texte, JPEG, PNG, TIFF."],
|
|
"fmt":["Format de sortie","<strong>PDF anonymisé</strong> — le document avec les informations recouvertes de bandes noires. Recommandé pour l'archivage.<br><br><strong>Texte .txt</strong> — le contenu avec les données remplacées par des codes comme <code>[NOM]</code>, <code>[DATE_NAISSANCE]</code>. Utile pour l'analyse."],
|
|
"det":["Détection","Ces options contrôlent <strong>ce qui est recherché</strong> lors du traitement.<br><br>Désactiver une catégorie peut laisser passer des données personnelles. <strong>En cas de doute, laissez tout activé.</strong>"],
|
|
"ner":["Moteurs NER","Le logiciel utilise plusieurs moteurs d'intelligence artificielle qui <strong>se complètent mutuellement</strong>.<br><br><strong>CamemBERT-bio</strong> — très rapide, entraîné sur des dossiers médicaux français.<br><strong>EDS-Pseudo</strong> — plus lent mais très précis sur le vocabulaire clinique.<br><strong>GLiNER</strong> — optionnel, apporte un vote croisé. Désactivez-le pour accélérer."],
|
|
"wl":["Termes à conserver","Ces termes ne seront <strong>jamais masqués</strong>, même s'ils ressemblent à un nom propre.<br><br>Exemples :<br>• <code>FUROSEMIDE</code> — médicament, pas un patient<br>• <code>classification internationale</code> — formulation médicale<br><br>Ajoutez ici tout terme incorrectement masqué."],
|
|
"bl":["Termes à toujours masquer","Ces termes seront <strong>systématiquement masqués</strong>, peu importe leur contexte.<br><br>Exemples :<br>• <code>CHUXX</code> — sigle de l'établissement<br>• <code>Dr Dupont</code> — médecin à masquer dans tous les documents<br><br>Idéal pour les identifiants locaux non reconnus automatiquement."],
|
|
"col":["Couleur de masquage","Choisissez la couleur des rectangles qui recouvrent les données dans le PDF.<br><br>Le <strong>noir</strong> est la norme pour les documents officiels. Les autres couleurs facilitent la relecture lors de la validation."],
|
|
"sty":["Style des marqueurs","Dans le fichier texte, chaque donnée masquée est remplacée par un marqueur.<br><br><strong>Crochets [NOM]</strong> — explicite sur le type de donnée (recommandé).<br><strong>Etoiles ***</strong> — plus discret.<br><strong>Noirci</strong> — imite visuellement le masquage PDF."],
|
|
"ep":["Epaisseur du masque","La marge détermine de combien le rectangle dépasse le texte.<br><br>Une marge de <strong>2 points</strong> est suffisante pour la plupart des PDF. Augmentez si des lettres dépassent légèrement du masque."],
|
|
"ph":["Codes de remplacement","Dans le texte anonymisé, chaque donnée est remplacée par un code entre crochets indiquant sa nature.<br><br>Cela permet de savoir <em>ce qui a été masqué</em> sans révéler <em>ce que c'était</em>."],
|
|
"medit":["Éditeur de masques de zones","Cet outil permet de <strong>masquer des zones fixes</strong> qui apparaissent au même endroit sur tous les documents d'un modèle.<br><br>Exemples : logo de l'établissement en haut de page, tampon de signature, en-tête avec numéro de fax.<br><br><strong>Comment utiliser :</strong><br>1. Cliquez <em>Ouvrir PDF</em> pour charger un document modèle.<br>2. Cliquez-glissez pour tracer des rectangles sur les zones à masquer.<br>3. Donnez un nom au template et cliquez <em>Sauver</em>.<br>4. Cliquez <em>Appliquer le template</em> pour l'activer sur les prochains traitements."],
|
|
"exp":["Exporter","Génère un fichier <strong>.json</strong> contenant vos listes personnalisées.<br><br>Envoyez ce fichier par e-mail à d'autres établissements pour partager votre configuration."],
|
|
"imp":["Importer","Importez un fichier .json reçu d'un autre établissement.<br><br>La configuration est <strong>fusionnée</strong> avec la vôtre — vos réglages locaux ne sont pas supprimés."],
|
|
"rul":["Règles administrables","Ces règles adaptent le moteur à votre établissement.<br><br><strong>Actif</strong> — appliquée à chaque traitement.<br><strong>Candidat</strong> — en attente de validation qualité.<br><strong>Brouillon</strong> — en cours de création, non appliquée.<br><br>Cliquez sur <strong>Tester</strong> pour simuler l'effet d'une règle avant de l'activer."]
|
|
};
|
|
function H(k){
|
|
var h=HELP[k]||["Aide","Information non disponible."];
|
|
document.getElementById("mt").innerHTML=h[0];
|
|
document.getElementById("mb").innerHTML=h[1];
|
|
document.getElementById("mo").classList.add("open");
|
|
}
|
|
function CH(){document.getElementById("mo").classList.remove("open");}
|
|
document.addEventListener("keydown",function(e){if(e.key==="Escape")CH();});
|
|
</script>
|
|
</body>
|
|
</html>"""
|
|
|
|
HTML = HTML.replace("LOGO_PLACEHOLDER", LOGO_SRC)
|
|
OUT_PATH.write_text(HTML, encoding="utf-8")
|
|
print("OK — {} ({} Ko)".format(OUT_PATH, OUT_PATH.stat().st_size // 1024))
|