codebook / potato /templates /admin /judge_alignment.html
davidjurgens's picture
Deploy: Potato — Codebook Annotation
aceb1b2 verified
Raw
History Blame Contribute Delete
7.77 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LLM-Judge ↔ Human Alignment</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='admin.css') }}">
<style>
/* Judge-alignment report. Inherits the Potato admin design system
(.dashboard-card, design tokens); only the metric/heatmap typography
is local. */
.ja-meta { color: var(--muted-foreground); font-size: 0.875rem;
margin: 0 0 1.5rem 0; max-width: 72ch; line-height: 1.6; }
.ja-card { margin-bottom: 1.5rem; }
.ja-card-header { display: flex; justify-content: space-between;
align-items: baseline; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; }
.ja-card-header h2 { margin: 0; font-size: 1.125rem; font-weight: 600; color: var(--heading-color); }
.ja-metrics { display: flex; gap: 2rem; flex-wrap: wrap; margin-bottom: 1rem; }
.ja-metric { display: flex; flex-direction: column; }
.ja-metric .label { font-size: 0.75rem; color: var(--muted-foreground);
text-transform: uppercase; letter-spacing: 0.03em; }
.ja-metric .value { font-size: 1.5rem; font-weight: 700; color: var(--heading-color); }
.ja-kappa-tag { display: inline-flex; align-items: center; gap: 0.3rem;
padding: 0.125rem 0.5rem; border-radius: var(--radius);
font-size: 0.75rem; font-weight: 600; background: var(--muted); color: var(--muted-foreground); }
/* Graded-but-muted κ band cue. The interpretation word is the primary (non-color)
channel; the leading glyph + subtle tint add a scannable, AA-safe second channel.
κ bands are technical, not good/bad value judgements, so tints stay neutral-warm. */
.ja-kappa-tag::before { content: "◐"; font-size: 0.9em; }
.ja-kappa-tag.band-none::before { content: "○"; }
.ja-kappa-tag.band-low::before { content: "◔"; }
.ja-kappa-tag.band-mid::before { content: "◑"; }
.ja-kappa-tag.band-high::before { content: "●"; }
.ja-kappa-tag.band-low { background: hsl(35 60% 92%); color: hsl(35 60% 30%); }
.ja-kappa-tag.band-mid { background: hsl(45 55% 90%); color: hsl(40 55% 28%); }
.ja-kappa-tag.band-high { background: hsl(95 35% 90%); color: hsl(120 35% 26%); }
.ja-table-wrap { overflow-x: auto; }
table.ja-table { border-collapse: collapse; width: 100%; font-size: 0.875rem; margin-top: 0.5rem; }
td.ja-reason { max-width: 42ch; }
.ja-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
table.ja-table th, table.ja-table td { border: 1px solid var(--border); padding: 0.4rem 0.6rem; text-align: left; }
table.ja-table th { background: var(--muted); font-weight: 600; }
.ja-confusion td.diag { font-weight: 700; }
.ja-confusion td.count { text-align: center; }
.ja-section-label { font-size: 0.8rem; font-weight: 600; color: var(--muted-foreground);
text-transform: uppercase; letter-spacing: 0.03em; margin: 1.25rem 0 0.4rem; }
.ja-empty { color: var(--muted-foreground); font-style: italic; }
.ja-versions { font-size: 0.85rem; }
.ja-versions li { margin-bottom: 0.25rem; }
.ja-mono { font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); }
</style>
</head>
<body>
<main class="dashboard" role="main">
<h1>LLM-Judge ↔ Human Alignment</h1>
<p class="ja-meta">
How well does the configured LLM judge agree with human gold labels?
Cohen's κ and the confusion matrix are computed over instances that have
both a human label and a judge verdict. Inspect disagreements, edit the
judge rubric, and re-run to calibrate. Current prompt version:
<span class="ja-mono">{{ report.prompt_version or "—" }}</span>.
</p>
{% if report.prompt_versions %}
<section class="dashboard-card ja-card" aria-label="Prompt versions">
<div class="ja-card-header"><h2>Prompt versions</h2></div>
<ul class="ja-versions">
{% for v in report.prompt_versions %}
<li>
<span class="ja-mono">{{ v.prompt_version }}</span>
mean κ {{ '%.3f'|format(v.mean_kappa) if v.mean_kappa is not none else 'n/a' }},
{{ v.n_predictions }} prediction(s)
{% if v.prompt_version == report.prompt_version %}<strong>(current)</strong>{% endif %}
</li>
{% endfor %}
</ul>
</section>
{% endif %}
{% if not report.per_schema %}
<p class="ja-empty">No judge predictions yet. Configure <code>judge_alignment</code>
and POST to <code>/admin/api/judge-alignment/run</code> to generate verdicts.</p>
{% endif %}
{% for schema, r in report.per_schema.items() %}
<section class="dashboard-card ja-card" aria-label="Alignment for {{ schema }}">
{% set band = 'band-none' if r.kappa is none else ('band-low' if r.kappa < 0.2 else ('band-mid' if r.kappa < 0.6 else 'band-high')) %}
<div class="ja-card-header">
<h2>{{ schema }}</h2>
<span class="ja-kappa-tag {{ band }}" title="Cohen's κ interpretation (Landis–Koch)">{{ r.interpretation }}</span>
</div>
<div class="ja-metrics">
<div class="ja-metric">
<span class="label">Cohen's κ</span>
<span class="value">{{ '%.3f'|format(r.kappa) if r.kappa is not none else '—' }}</span>
</div>
<div class="ja-metric">
<span class="label">Agreement</span>
<span class="value">{{ (r.agreement_rate * 100)|round(1) }}%</span>
</div>
<div class="ja-metric">
<span class="label">Compared (n)</span>
<span class="value">{{ r.n }}</span>
</div>
<div class="ja-metric">
<span class="label">Disagreements</span>
<span class="value">{{ r.disagreements|length }}</span>
</div>
</div>
{% if r.confusion %}
<div class="ja-section-label" id="conf-{{ schema }}">Confusion (rows = human, columns = judge)</div>
{% set cols = [] %}
{% for h, js in r.confusion.items() %}{% for j in js.keys() %}{% if j not in cols %}{% set _ = cols.append(j) %}{% endif %}{% endfor %}{% endfor %}
<div class="ja-table-wrap">
<table class="ja-table ja-confusion" aria-labelledby="conf-{{ schema }}">
<caption class="ja-sr-only">Confusion matrix for {{ schema }}: rows are human gold labels, columns are judge labels.</caption>
<thead><tr><th scope="col">human \ judge</th>{% for c in cols %}<th scope="col">{{ c }}</th>{% endfor %}</tr></thead>
<tbody>
{% for h, js in r.confusion.items() %}
<tr><th scope="row">{{ h }}</th>
{% for c in cols %}
<td class="count {{ 'diag' if c == h else '' }}">{{ js.get(c, 0) }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% if r.disagreements %}
<div class="ja-section-label">Disagreements</div>
<div class="ja-table-wrap">
<table class="ja-table">
<thead><tr><th scope="col">Instance</th><th scope="col">Human</th><th scope="col">Judge</th><th scope="col">Conf.</th><th scope="col">Judge reasoning</th></tr></thead>
<tbody>
{% for d in r.disagreements %}
<tr>
<td class="ja-mono">{{ d.instance_id }}</td>
<td>{{ d.human_label }}</td>
<td>{{ d.judge_label }}</td>
<td>{{ '%.2f'|format(d.judge_confidence) if d.judge_confidence is not none else '—' }}</td>
<td class="ja-reason">{{ d.reasoning }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</section>
{% endfor %}
</main>
</body>
</html>