diff --git a/scripts/templates/dashboard.html.j2 b/scripts/templates/dashboard.html.j2 index 2106d120..3a560fc5 100644 --- a/scripts/templates/dashboard.html.j2 +++ b/scripts/templates/dashboard.html.j2 @@ -581,33 +581,44 @@ function renderModelRow(m) { return ``; }).join(''); - // Per-level inline badges next to the model_type. L3 surfaces all three - // pass/xfail/skip outcomes; L2/L4/L5 surface only xfail because their - // dot already conveys pass/skip unambiguously and xfail is the only - // state that is otherwise visually identical to a fail. - let l2Badge = ''; - if (m.l2_status === 'xfail') { - l2Badge = ` XFail`; - } else if (m.l2_status === 'xfail_graph_only') { - l2Badge = ` XFail`; - } - // Positive states (L3 parity pass, L5 e2e verified) are intentionally NOT - // badged here: they would duplicate the Confidence column and the L1-L5 dot - // strip. Only exception states (xfail / skip) get inline badges, since those - // are the signals not otherwise distinguishable at a glance. - let l3Badge = ''; - if (m.l3_status === 'xfail') { - l3Badge = ` XFail`; - } else if (m.l3_status === 'skip') { - l3Badge = ` Skip`; - } - let l4Badge = ''; - if (m.l4_status === 'xfail') { - l4Badge = ` XFail`; - } - let l5Badge = ''; - if (m.l5_status === 'xfail') { - l5Badge = ` XFail`; + // Per-level inline badge next to the model_type. To avoid duplicated + // badges (e.g. an L3 Skip alongside an L5 XFail for the same model), we + // emit at most ONE badge per row, picking the highest-level non-pass + // status. Higher-level outcomes supersede lower-level ones because the + // confidence column already reflects the highest attained level — the + // badge calls out the most consequential outstanding issue. + // + // Precedence: walk L5 → L2 and emit the first xfail/skip encountered. + // Both xfail and skip are reportable at every level (each level can be + // skipped or marked xfail independently). Within a level, xfail and + // skip are mutually exclusive in the data model, so we just check each. + // L2 also admits "xfail_graph_only" (parse passes, full-graph build + // xfails). Positive states (L3 parity pass, L5 e2e verified) are + // intentionally NOT badged: they would duplicate the Confidence column + // and the L1-L5 dot strip. Only exception states (xfail / skip) get + // inline badges, since those are the signals not otherwise + // distinguishable at a glance. + const xfailBadge = (level, reason) => + ` XFail`; + const skipBadge = (level, reason) => + ` Skip`; + let statusBadge = ''; + for (const [level, status, reason] of [ + [5, m.l5_status, m.l5_reason], + [4, m.l4_status, m.l4_reason], + [3, m.l3_status, m.l3_reason], + [2, m.l2_status, m.l2_reason], + ]) { + if (status === 'xfail' || status === 'xfail_graph_only') { + statusBadge = (level === 2 && status === 'xfail_graph_only') + ? ` XFail` + : xfailBadge(level, reason); + break; + } + if (status === 'skip') { + statusBadge = skipBadge(level, reason); + break; + } } // min_token_match_ratio badge @@ -626,7 +637,7 @@ function renderModelRow(m) { // Use single-quoted HTML attributes so JSON.stringify's double quotes don't // break attribute parsing when this string is set via innerHTML. let row = ` - ${esc(m.model_type)}${l2Badge}${l3Badge}${l4Badge}${l5Badge}${ratioBadge} + ${esc(m.model_type)}${statusBadge}${ratioBadge} ${esc(m.category)} ${esc(m.module_class)} ${LEVEL_LABELS[m.confidence_level]}