import { useState } from "react"; import type { ClusterTrace, DebugSummary } from "@/api/types"; import { useCatalogStore } from "@/store/catalogStore"; import { useSessionStore } from "@/store/sessionStore"; import { llmAlertMessage } from "@/utils/llmAlert"; type Cat = "kept" | "below" | "overlap"; function debugCategory(c: ClusterTrace): Cat { if (c.kept) return "kept"; return c.passed_threshold ? "overlap" : "below"; } const CATS: { key: Cat; label: string }[] = [ { key: "kept", label: "Mantenute" }, { key: "below", label: "Sotto soglia" }, { key: "overlap", label: "Scartate (overlap)" }, ]; function LlmSummaryChip({ summary }: { summary: DebugSummary }) { if (summary.llm_status === "unavailable") { return ( ⚠ Verifica LLM non disponibile ); } if (summary.llm_status === "active") { return ( Giudice LLM: {summary.llm_scored}/{summary.total_clusters} valutate,{" "} {summary.llm_rejected} bocciate ); } if (summary.llm_status === "simulated") { return ( Giudice LLM: SIMULATO — {summary.llm_prompts} prompt costruiti, 0 valutazioni ); } return null; } function DebugCard({ c, colors, llmActive, llmSimulated, }: { c: ClusterTrace; colors: Record; llmActive: boolean; llmSimulated: boolean; }) { const stateClass = c.kept ? "dbg-kept" : c.passed_threshold ? "dbg-overlap" : "dbg-below"; const statusLabel = c.kept ? "MANTENUTA" : c.passed_threshold ? "SCARTATA (overlap)" : "SOTTO SOGLIA"; const hasVote = c.llm_vote !== null && c.llm_vote !== undefined; const dominio = c.label || c.entity_type; const llmCell = (p: ClusterTrace["contributions"][number]) => { if (!p.is_representative) return —; if (hasVote) { return c.llm_vote! >= 0 ? ( {c.llm_vote!.toFixed(2)} ) : ( −1 ); } if (llmSimulated) { return ( sim ); } if (!llmActive) { return ( — ); } const naTitle = debugCategory(c) === "overlap" ? "Non sottoposta al giudice: scartata per sovrapposizione prima della verifica" : "Il giudice non ha restituito un punteggio per questa frase (risposta non interpretata)"; return ( n/d ); }; return (
{c.text} {c.severity} {c.label} [{c.start}–{c.end}] {statusLabel} {c.discarded_reason && {c.discarded_reason}}
{c.llm_prompt !== null && c.llm_prompt !== undefined && (
prompt LLM (grezzo)
{c.llm_prompt}
)}
Score: {c.base_score.toFixed(2)} base {c.agreement_boost > 0 && ( <> {" "} +{c.agreement_boost.toFixed(2)} accordo ( {c.agreement_count} layer) )}{" "} ={" "} {hasVote ? ( <> {(c.base_score + c.agreement_boost).toFixed(2)} {" "} (score layer) ) : ( {c.final_score.toFixed(2)} )} (soglia {c.threshold.toFixed(2)})
{hasVote && (
= 0 ? "llm-confirmed" : "llm-rejected"}`}> Score LLM{" "} {c.llm_vote! >= 0 ? ( <> ✓ confermata — è un riferimento del dominio {dominio}, score attinenza{" "} {c.llm_vote!.toFixed(2)} ) : ( <> ✗ bocciata — NON è un riferimento del dominio {dominio}{" "} (score −1) )} {" "} → score finale {c.final_score.toFixed(2)}
)} {c.contributions.map((p, i) => ( {llmCell(p)} ))}
Processo Layer Tipo Score LLM score Validato Sorgente
{p.is_representative && ( )} {p.process} {p.layer} {p.entity_type} {p.score.toFixed(2)}{p.validated ? : "—"} {p.source || "—"}
); } export function DebugView() { const data = useSessionStore((s) => s.debug); const colors = useCatalogStore((s) => s.severityColors); const [cat, setCat] = useState("kept"); if (!data) { return (

Premi "Traccia di Debug" per ispezionare ogni frase catturata: quale processo l'ha rilevata, lo score, i processi concordanti, i boost e l'esito finale.

); } const { clusters, summary, threshold, method } = data; if (!clusters || clusters.length === 0) { return (

Nessuna frase catturata da alcun processo.

); } const obscuredNoScore = clusters.filter( (c) => c.kept && (c.llm_vote === null || c.llm_vote === undefined), ).length; const banner = llmAlertMessage({ status: summary.llm_status, scored: summary.llm_scored, total: summary.total_clusters, obscured_without_score: obscuredNoScore, }); const byCat: Record = { kept: [], below: [], overlap: [] }; for (const c of clusters) byCat[debugCategory(c)].push(c); for (const k of Object.keys(byCat) as Cat[]) { byCat[k].sort((a, b) => a.start - b.start || a.end - b.end); } return (
{banner && (
{banner.text}
)}
Metodologia: {method} Soglia: {threshold.toFixed(2)} {summary.total_clusters} frasi catturate {summary.kept} mantenute {summary.below_threshold} sotto soglia {summary.dropped_overlap} scartate per overlap
{CATS.map((c) => ( ))}
{byCat[cat].length ? (
{byCat[cat].map((c, i) => ( ))}
) : (

Nessuna voce in questa categoria.

)}
); }