"""Rendering HTML per i 3 tab di output (Evidenziato / Anonimizzato / Report)."""
import html
import re as _re
from presidio_analyzer import RecognizerResult
from config import LABEL_IT, SEVERITY_COLORS, SEVERITY_LABELS, SEVERITY_ORDER, get_severity
_BOX = (
"border:1px solid #e5e7eb; border-radius:10px; "
"padding:20px 24px; background:#ffffff; "
"min-height:200px; box-sizing:border-box;"
)
_FONT = "Arial, Helvetica, sans-serif"
_TEXT = f"font-family:{_FONT}; font-size:0.95em; line-height:1.95; white-space:pre-wrap; word-wrap:break-word; color:#111827;"
_PH_RE = _re.compile(r"\[[A-Z_]+(?:_\d+)?\]")
def _src(r: RecognizerResult) -> str:
p = (r.recognition_metadata or {}).get("source_priority", -1)
return {0: "REGEX", 1: "NER", 2: "GLINER"}.get(p, "?")
def _boosted(r: RecognizerResult) -> bool:
return bool((r.recognition_metadata or {}).get("post_boost", False))
def _agree(r: RecognizerResult) -> int:
return int((r.recognition_metadata or {}).get("cross_layer_agreement", 0))
def _badge(r: RecognizerResult) -> str:
label = LABEL_IT.get(r.entity_type, r.entity_type)
return f"{label}+" if _boosted(r) else label
def _box(inner: str) -> str:
return f'
{inner}
'
def _empty(msg: str) -> str:
return _box(f'{msg}
')
# ---------------------------------------------------------------------------
# Tab 1 – Testo evidenziato
# ---------------------------------------------------------------------------
def render_highlighted_text(text: str, entities: list[RecognizerResult]) -> str:
if not text:
return _empty("Inserisci del testo per iniziare.")
if not entities:
return _box(f'{html.escape(text)}
')
parts: list[str] = []
cursor = 0
for r in sorted(entities, key=lambda r: r.start):
if r.start > cursor:
parts.append(html.escape(text[cursor:r.start]))
original = text[r.start:r.end]
sev = get_severity(r.entity_type)
c = SEVERITY_COLORS[sev]
badge = _badge(r)
boost = _boosted(r)
an = _agree(r)
tip = f"Score: {r.score:.2f} · {_src(r)}"
if an >= 2:
tip += f" · ⚡ {an} livelli concordano"
if boost:
tip += " · ✓ Doppio match regex"
border_b = f"border-bottom:2px solid {c['border']};" if boost else ""
parts.append(
f''
f'{html.escape(original)}'
f'{html.escape(badge)}'
)
cursor = r.end
if cursor < len(text):
parts.append(html.escape(text[cursor:]))
return _box(f'{"".join(parts)}
')
# ---------------------------------------------------------------------------
# Tab 2 – Testo anonimizzato
# ---------------------------------------------------------------------------
def render_anonymized_text(anon_text: str) -> str:
if not anon_text:
return _empty("Nessun risultato.")
def _hl(m: _re.Match) -> str:
return (
f'{html.escape(m.group())}'
)
return _box(f'{_PH_RE.sub(_hl, html.escape(anon_text))}
')
# ---------------------------------------------------------------------------
# Tab 3 – Report categorizzato
# ---------------------------------------------------------------------------
def _card(sev: str, count: int) -> str:
c = SEVERITY_COLORS[sev]
return (
f''
f'
Gravità {SEVERITY_LABELS[sev]}
'
f'
{count}
'
)
def render_categorized_report(text: str, entities: list[RecognizerResult]) -> str:
if not entities:
return _empty("Nessuna entità rilevata sopra lo score minimo.")
by_sev: dict[str, list[RecognizerResult]] = {s: [] for s in SEVERITY_ORDER}
for r in entities:
by_sev[get_severity(r.entity_type)].append(r)
parts: list[str] = [f'']
parts.append('
')
for sev in SEVERITY_ORDER:
parts.append(_card(sev, len(by_sev[sev])))
parts.append('
')
for sev in SEVERITY_ORDER:
items = by_sev[sev]
if not items:
continue
c = SEVERITY_COLORS[sev]
parts.append(
f'
'
f'Gravità {SEVERITY_LABELS[sev]} '
f'· {len(items)} occorrenze
'
)
by_type: dict[str, list[RecognizerResult]] = {}
for r in items:
by_type.setdefault(r.entity_type, []).append(r)
for entity_type, results in by_type.items():
label = LABEL_IT.get(entity_type, entity_type)
parts.append(
f'
'
f'
{html.escape(label)}'
f'
{len(results)} occ.'
f'
'
)
for r in results:
original = text[r.start:r.end]
boost_tag = (
'✓ boost'
if _boosted(r) else ""
)
agree_n = _agree(r)
agree_tag = (
f'⚡ x{agree_n}'
if agree_n >= 2 else ""
)
parts.append(
f'- '
f'
{html.escape(original)}'
f''
f'[{html.escape(_badge(r))}] score {r.score:.2f} · {_src(r)}'
f'{boost_tag}{agree_tag} '
)
parts.append('
')
parts.append('
')
return _box("".join(parts))