Files
TheTomandClaude Opus 4.7 728cd8479b fix(refract): skipped axes don't inflate composite + report engine name
Two reader-facing fixes from AJ's screenshot (2026-05-02 X thread):

1. --skip-gtm / --skip-kld previously fed a stub score=100 to
   composite_score, so a single real KLD axis of 99.47 produced a
   misleading composite of 99.74 EXCELLENT. The skipped axis now
   contributes None to composite_score() and is excluded from the
   harmonic mean. CompositeScore.gtm_score / .kld_score are now
   Optional[float]. Text + HTML reports render "n/a" with a
   "skipped" badge for skipped axes instead of fake 100/EXCELLENT.
   The "harmonic mean of N axes" gloss reflects the real count.
   Diagnostics blocks (GTM diagnostics, KLD diagnostics) are
   replaced with a single "axis was skipped" line when the axis
   wasn't measured.

2. The HTML "Run details → Environment" card now surfaces the
   backend name (llama.cpp / MLX / vLLM / SGLang) plus engine
   version fields (llama_cpp_commit, mlx_lm_version, vllm_version,
   sglang_url, served_model_id) when the active backend populates
   them. Previously a report didn't say which engine ran it, which
   made cross-engine comparison reports ambiguous in the inbox.

Tests added (test_score.py):
- composite_score(gtm=None, kld=99.47) returns ~99.47 (not 99.74)
- composite_score(gtm=62.40, kld=None) returns ~62.40
- composite_score(None, None) returns 0.0 FAIL (no crash)
- 3-axis skip case computes correctly
- interpret_pattern handles None
- text_report renders "n/a" + "harmonic mean of 1 axes" on skip-gtm

Refract test count: 410 -> 416.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: tturney1@gmail.com <tturney1@gmail.com>
2026-05-02 10:46:01 -05:00

1286 lines
51 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""HTML report rendering for REFRACT (v0.3.3 redesign).
Single self-contained HTML page. Uses Geist (Google Fonts) with system
fallbacks; works offline (falls back to system fonts) but looks
significantly better online.
Design language: muted neutrals, semantic colour tokens per band,
pill badges, dense-but-clean tables, dark code blocks.
Layout:
1. Masthead: brand mark + model + run metadata
2. Stats strip: composite + 4 axes in a single 5-column row
3. Diagnosis: summary callout + numbered findings
4. Per-axis breakdown: dense table; R-NIAH and PLAD details inline
5. Run details: 3-card grid (Model / Hardware / Environment)
6. Reproduce: dark code block with syntax-highlighted flags
7. Raw JSON in a styled `<details>`
8. Footer: "what is this?" + GitHub link + version info
"""
from __future__ import annotations
import datetime as _dt
import html as _html
import json as _json
import math
import os
import platform
import re as _re
import shlex
import subprocess
import sys
from dataclasses import asdict
from pathlib import Path
from typing import Optional
from .axes.gtm import GTMResult
from .axes.kld import KLDResult
from .axes.plad import PLADResult
from .axes.rniah import RNIAHResult
from .axes.trajectory import TrajectoryResult
from .score import CompositeScore, MIN_FLOOR, band, interpret_pattern
# Pretty band names shown to users (Excellent vs EXCELLENT).
_BAND_PRETTY = {
"EXCELLENT": "Excellent",
"PASS": "Pass",
"DEGRADED": "Degraded",
"FAIL": "Fail",
}
# CSS class mapping used in the badge / fill colour.
_BAND_CLASS = {
"EXCELLENT": "green",
"PASS": "green",
"DEGRADED": "amber",
"FAIL": "red",
}
_BAND_PROSE = {
"EXCELLENT": "Indistinguishable from the reference. Safe to deploy.",
"PASS": "Minor drift; safe to deploy in most uses.",
"DEGRADED": "Visible drift. Audit on your workload before deploying.",
"FAIL": "Material quality loss. Treat as broken.",
}
_AXIS_PROSE = {
"gtm": "Token-level agreement with the fp16 reference (greedy decode).",
"trajectory": "Token-level agreement with the fp16 reference (decode-time IDs).",
"kld": "Distribution-level divergence from the fp16 reference (corpus KLD).",
"rniah": "Long-context retrieval quality vs the reference (NIAH at multiple lengths).",
"plad": "Robustness to small prompt changes vs the reference (typo / case / punct / paraphrase).",
}
# Full-name → letter (axis A/B/C/D) and short label.
_AXIS_LETTER = {"gtm": "A", "trajectory": "A", "kld": "B",
"rniah": "C", "plad": "D"}
_AXIS_SHORT = {"gtm": "GTM", "trajectory": "Trajectory", "kld": "KLD@D",
"rniah": "R-NIAH", "plad": "PLAD"}
_AXIS_FULL = {
"gtm": "Greedy Trajectory Match",
"trajectory": "Greedy Trajectory Match (decode-time IDs)",
"kld": "KL Divergence at the Decoder",
"rniah": "Retrieval Needle-In-A-Haystack",
"plad": "Perturbation-Locality Aware Drift",
}
# --------------------------------------------------------------------------
# Hardware + model metadata helpers
# --------------------------------------------------------------------------
def _hardware_metadata() -> dict:
info: dict = {
"system": platform.system(),
"platform": platform.platform(),
"machine": platform.machine(),
"python": platform.python_version(),
}
if info["system"] == "Darwin":
try:
chip = subprocess.run(
["sysctl", "-n", "machdep.cpu.brand_string"],
capture_output=True, text=True, timeout=2,
).stdout.strip()
if chip:
info["chip"] = chip
except Exception:
pass
try:
memsize = subprocess.run(
["sysctl", "-n", "hw.memsize"],
capture_output=True, text=True, timeout=2,
).stdout.strip()
if memsize:
# Binary GiB (Apple's marketing convention) — 137438953472
# bytes / 1024**3 = 128 GiB exact.
info["ram_gb"] = round(int(memsize) / 1024**3, 1)
except Exception:
pass
# Pretty platform string: "macOS 26.4 arm64" instead of full
# "macOS-26.4.1-arm64-arm-64bit-Mach-O".
try:
mac_ver = platform.mac_ver()[0]
if mac_ver:
info["platform_pretty"] = f"macOS {mac_ver} {info['machine']}"
except Exception:
pass
elif info["system"] == "Linux":
try:
with open("/proc/cpuinfo") as f:
for line in f:
if line.startswith("model name"):
info["chip"] = line.split(":", 1)[1].strip()
break
except Exception:
pass
try:
with open("/proc/meminfo") as f:
for line in f:
if line.startswith("MemTotal:"):
kb = int(line.split()[1])
info["ram_gb"] = round(kb / 1024 / 1024, 1)
break
except Exception:
pass
try:
out = subprocess.run(
["nvidia-smi", "--query-gpu=name,memory.total",
"--format=csv,noheader,nounits"],
capture_output=True, text=True, timeout=3,
).stdout.strip()
if out:
info["nvidia_gpus"] = [
{"name": parts[0].strip(),
"memory_mb": int(parts[1].strip())}
for line in out.splitlines()
for parts in [line.split(",")] if len(parts) >= 2
]
except Exception:
pass
return info
def _model_metadata(model_path: Path) -> dict:
info: dict = {"path": str(model_path), "name": model_path.name}
if not model_path.exists():
return info
if model_path.is_file():
info["size_gb"] = round(model_path.stat().st_size / 1024**3, 2)
info["format"] = "gguf" if model_path.suffix == ".gguf" else "file"
elif model_path.is_dir():
total = 0
for ext in ("*.safetensors", "*.bin", "*.npz"):
for f in model_path.glob(ext):
total += f.stat().st_size
info["size_gb"] = round(total / 1024**3, 2)
info["format"] = "directory"
config_path = model_path / "config.json"
if config_path.exists():
try:
cfg = _json.loads(config_path.read_text())
for k in (
"model_type", "architectures", "hidden_size",
"num_hidden_layers", "num_attention_heads",
"num_key_value_heads", "max_position_embeddings",
"vocab_size", "head_dim",
):
if k in cfg:
info[k] = cfg[k]
except Exception:
pass
return info
def _repro_command(raw_json: dict | None, model: str,
reference_label: str, candidate_label: str,
has_rniah: bool, has_plad: bool) -> str:
"""Return the shell-escaped command that produced this report.
Priority: JSON's repro_command (v0.3.2+) > sanitized sys.argv (when
looks like a refract CLI run) > synthesized stand-in.
"""
if raw_json and raw_json.get("repro_command"):
return raw_json["repro_command"]
home = os.path.expanduser("~")
def _sanitize(arg: str) -> str:
if home and arg.startswith(home + os.sep):
return "~" + arg[len(home):]
return arg
if any("refract" in a for a in sys.argv):
return " ".join(shlex.quote(_sanitize(a)) for a in sys.argv)
model_short = Path(model).name
cmd = [
"python3", "-m", "refract.cli", "score",
"--model", shlex.quote(model_short),
"--reference", shlex.quote(reference_label),
"--candidate", shlex.quote(candidate_label),
"--prompts", "refract/prompts/v0.1.jsonl",
]
if has_rniah or has_plad:
cmd.append("--full")
if has_rniah:
cmd.extend(["--rniah-up-to", "16384"])
cmd.extend([
"--json-out", "report.json",
"--html-out", "report.html",
])
return " ".join(cmd)
# --------------------------------------------------------------------------
# HTML rendering helpers
# --------------------------------------------------------------------------
def _esc(s) -> str:
return _html.escape("" if s is None else str(s))
def _badge(b: str, override_label: Optional[str] = None) -> str:
cls = _BAND_CLASS.get(b, "gray")
label = override_label or _BAND_PRETTY.get(b, b)
return f'<span class="badge {cls}">{_esc(label)}</span>'
def _meter(score: float, b: str) -> str:
pct = max(0.0, min(100.0, float(score)))
# Empty band string ("") = explicit gray (used for low-confidence axes).
cls = _BAND_CLASS.get(b, "gray") if b else "gray"
return (
f'<div class="meter"><div class="fill" '
f'style="width:{pct:.1f}%; background:var(--{cls});"></div></div>'
)
def _mini_meter(score: float, b: str) -> str:
pct = max(0.0, min(100.0, float(score)))
cls = _BAND_CLASS.get(b, "gray")
return (
f'<div class="mini-meter"><div class="fill" '
f'style="width:{pct:.1f}%; background:var(--{cls});"></div></div>'
)
def _highlight_repro(cmd: str) -> str:
"""Wrap --flags, args, and <placeholders> in spans for syntax colour."""
parts = []
for tok in cmd.split():
if tok.startswith("--") or tok.startswith("-") and len(tok) > 1 and not tok[1].isdigit():
parts.append(f'<span class="flag">{_esc(tok)}</span>')
elif tok.startswith("<") and tok.endswith(">"):
parts.append(f'<span class="placeholder">{_esc(tok)}</span>')
elif "&lt;" in _esc(tok) and "&gt;" in _esc(tok):
parts.append(f'<span class="placeholder">{_esc(tok)}</span>')
else:
parts.append(f'<span class="arg">{_esc(tok)}</span>')
return " ".join(parts)
def _report_id() -> str:
"""Timestamp-based short ID, e.g. '#0430-1537'."""
now = _dt.datetime.now()
return f"#{now.strftime('%m%d-%H%M')}"
def _axis_letter_chip(letter: str) -> str:
return f'<span class="letter">{_esc(letter)}</span>'
def _stat_block(name: str, score: Optional[float], *, is_composite: bool = False,
low_confidence: bool = False) -> str:
# When score is None the axis was skipped (--skip-gtm / --skip-kld).
# Render explicit "n/a" instead of a fake 100/EXCELLENT.
if score is None and not is_composite:
letter = _AXIS_LETTER.get(name, "")
short = _AXIS_SHORT.get(name, name)
label_html = (
f'<div class="label"><span class="axis-letter">{_esc(letter)}</span>'
f'{_esc(short)}</div>'
)
value_html = (
f'<div class="value-row">'
f'<div class="value" style="color: var(--fg-faint);" '
f'title="axis was skipped via --skip-{name} — not measured">n/a</div>'
f'</div>'
)
return (
f'<div class="stat skipped">'
f'{label_html}{value_html}'
f'<div class="badge-row">{_badge("", "Skipped")}</div>'
f'</div>'
)
b = band(score)
cls = "stat composite" if is_composite else "stat"
if is_composite:
label_html = '<div class="label">Composite score</div>'
value_html = (
f'<div class="value-row"><div class="value">{score:.2f}</div>'
f'<div class="delta">/ 100</div></div>'
)
else:
letter = _AXIS_LETTER.get(name, "")
short = _AXIS_SHORT.get(name, name)
label_html = (
f'<div class="label"><span class="axis-letter">{_esc(letter)}</span>'
f'{_esc(short)}</div>'
)
# v0.3.3: when an axis flags itself low-confidence, the score is
# mathematically true but uninformative (e.g. R-NIAH=100 when base
# also fails everywhere — measures "candidate matches base", not
# real retrieval). Suppress the headline number and use "—" with
# the real value in a tooltip to discourage misreading.
if low_confidence:
value_html = (
f'<div class="value-row">'
f'<div class="value" style="color: var(--fg-faint);" '
f'title="raw score {score:.2f} — uninformative because base '
f'also fails at most cells">—</div></div>'
)
else:
value_html = f'<div class="value-row"><div class="value">{score:.2f}</div></div>'
if low_confidence:
badge = _badge("", "Low confidence")
else:
badge = _badge(b)
return (
f'<div class="{cls}">'
f'{label_html}{value_html}'
f'<div class="badge-row">{badge}</div>'
f'</div>'
)
def _findings(diagnosis: list[str]) -> str:
if not diagnosis:
return ""
items = []
for i, sentence in enumerate(diagnosis, 1):
# Bold-up the leading clause if there's a colon / period.
m = _re.match(r"([^.:]+[.:])\s+(.*)", sentence)
if m:
head, tail = m.group(1), m.group(2)
inner = f"<strong>{_esc(head)}</strong> {_esc(tail)}"
else:
inner = _esc(sentence)
items.append(
f'<div class="finding">'
f'<span class="num">{i:02d}</span>'
f'<p>{inner}</p>'
f'</div>'
)
return f'<div class="findings">{"".join(items)}</div>'
def _axis_row(name: str, score: Optional[float], *, low_confidence: bool = False) -> str:
if score is None:
# Skipped axis: explicit n/a row instead of fake 100/EXCELLENT.
empty_meter = '<div class="meter-cell"><div class="meter"></div></div>'
return (
f'<div class="axis-row skipped">'
f'<div class="key">{_axis_letter_chip(_AXIS_LETTER.get(name, ""))}'
f'{_esc(_AXIS_SHORT.get(name, name))}</div>'
f'<div class="name">{_esc(_AXIS_FULL.get(name, name))}'
f'<span class="desc">skipped via --skip-{name}; not measured</span></div>'
f'<div class="score" style="color: var(--fg-faint);">n/a</div>'
f'<div class="badge-cell">{_badge("", "Skipped")}</div>'
f'{empty_meter}'
f'</div>'
)
b = band(score)
if low_confidence:
badge = _badge("", "Low confidence")
meter_b = "" # gray fill
score_html = (
f'<div class="score" style="color: var(--fg-faint);" '
f'title="raw score {score:.2f} — uninformative">—</div>'
)
else:
badge = _badge(b)
meter_b = b
score_html = f'<div class="score">{score:.2f}</div>'
return (
f'<div class="axis-row">'
f'<div class="key">{_axis_letter_chip(_AXIS_LETTER.get(name, ""))}'
f'{_esc(_AXIS_SHORT.get(name, name))}</div>'
f'<div class="name">{_esc(_AXIS_FULL.get(name, name))}'
f'<span class="desc">{_esc(_AXIS_PROSE.get(name, ""))}</span></div>'
f'{score_html}'
f'<div class="badge-cell">{badge}</div>'
f'{_meter(score, meter_b)}'
f'</div>'
)
def _rniah_low_confidence(rniah: RNIAHResult) -> bool:
"""Mirror of the JSON `confidence: low` guard: True when base_acc
averaged across cells is below 0.2 (model isn't engaging the task)."""
if not rniah.cells:
return False
base_avg = sum(c.base_acc for c in rniah.cells) / len(rniah.cells)
return base_avg < 0.2
def _rniah_matrix_detail(rniah: RNIAHResult) -> str:
if not rniah.cells:
return ""
low_conf = _rniah_low_confidence(rniah)
warning_html = ""
if low_conf:
base_avg = sum(c.base_acc for c in rniah.cells) / len(rniah.cells)
warning_html = (
'<div class="summary-box amber" style="margin-bottom: 14px;">'
'<div class="icon">!</div>'
'<div class="body">'
'<div class="title">Low confidence — score is noise floor.</div>'
f'<div class="desc">fp16 baseline retrieves at only '
f'{base_avg:.0%} of cells on average. With the reference '
f'failing this often, R-NIAH = {rniah.score:.2f} measures '
f'"candidate matches base" rather than real retrieval '
f'capability. Try lower --rniah-up-to or a model with '
f'longer effective context.</div>'
'</div></div>'
)
lengths = sorted({c.length for c in rniah.cells})
positions = sorted({c.position for c in rniah.cells})
head = "<tr><th>length \\ position</th>" + "".join(
f'<th>{p:.2f}</th>' for p in positions
) + "</tr>"
rows = []
for length in lengths:
tds = []
for pos in positions:
cell = next(
(c for c in rniah.cells
if c.length == length and c.position == pos), None,
)
if cell is None:
tds.append('<td class="cell-na" title="cell not run">—</td>')
continue
base, cand = cell.base_acc, cell.cand_acc
if base == 0.0:
# Both base and candidate fail at this cell. Showing the
# literal "0.00 / 0.00" reads as a real result; show a
# cleaner "n/a" with a tooltip explaining why.
tds.append(
'<td class="cell-na" '
'title="fp16 baseline does not retrieve at this cell — '
'uninformative for the candidate vs base comparison">n/a</td>'
)
continue
if cell.degradation > 0:
cls = "cell-fail"
title = (f"candidate retrieves at {cand:.2f}, "
f"base at {base:.2f} — degradation {cell.degradation:.2f}")
else:
cls = "cell-pass"
title = (f"candidate retrieves at {cand:.2f}, "
f"base at {base:.2f} — match")
tds.append(
f'<td class="{cls}" title="{_esc(title)}">'
f'{cand:.2f} / {base:.2f}</td>'
)
rows.append(f"<tr><th>{length}</th>{''.join(tds)}</tr>")
return (
f'<div class="axis-detail-row">'
f'<div class="detail-inner">'
f'{warning_html}'
f'<div class="detail-label">R-NIAH per-cell '
f'<span class="legend">cand / base retrieval rate · '
f'green = match · red = candidate worse · '
f'n/a = base also fails at this cell</span></div>'
f'<table class="matrix"><thead>{head}</thead>'
f'<tbody>{"".join(rows)}</tbody></table>'
f'</div></div>'
)
def _plad_table_detail(plad: PLADResult) -> str:
rows = []
for pert, score in plad.per_perturbation_score.items():
if not isinstance(score, (int, float)) or math.isnan(score):
rows.append(
f'<tr>'
f'<td class="label">{_esc(pert)}</td>'
f'<td class="score-cell"><span class="skipped">skipped</span></td>'
f'<td class="band-cell">{_badge("", "N/A")}</td>'
f'<td class="meter-cell"></td>'
f'<td class="note-cell">didn\'t apply on these prompts</td>'
f'</tr>'
)
continue
b = band(score)
rows.append(
f'<tr>'
f'<td class="label">{_esc(pert)}</td>'
f'<td class="score-cell">{score:.2f}</td>'
f'<td class="band-cell">{_badge(b)}</td>'
f'<td class="meter-cell">{_mini_meter(score, b)}</td>'
f'<td class="note-cell"></td>'
f'</tr>'
)
return (
f'<div class="axis-detail-row">'
f'<div class="detail-inner">'
f'<div class="detail-label">PLAD per-perturbation</div>'
f'<table class="plad-table"><thead>'
f'<tr><th>perturbation</th><th>score</th><th>band</th>'
f'<th>distribution</th><th></th></tr></thead>'
f'<tbody>{"".join(rows)}</tbody></table>'
f'</div></div>'
)
def _summary_box(composite: CompositeScore) -> str:
b = composite.band
cls = _BAND_CLASS.get(b, "gray")
icon_text = {"EXCELLENT": "✓", "PASS": "✓", "DEGRADED": "!", "FAIL": "!"}.get(b, "·")
title = _BAND_PROSE.get(b, "")
# Build a one-liner about which axes drove the band.
bits = []
for axis_name, score in (
("Trajectory", composite.gtm_score),
("KLD", composite.kld_score),
("R-NIAH", composite.rniah_score),
("PLAD", composite.plad_score),
):
if score is None:
continue
ab = band(score)
if ab in ("FAIL", "DEGRADED"):
bits.append(f"{axis_name} {ab.lower()}")
if bits:
desc = (
f"Composite of {composite.composite:.2f} falls in the "
f"{_BAND_PRETTY.get(b, b)} band. Surfaces below threshold: "
f"{', '.join(bits)}."
)
else:
desc = (
f"Composite of {composite.composite:.2f} falls in the "
f"{_BAND_PRETTY.get(b, b)} band. All measured axes pass."
)
return (
f'<div class="summary-box {cls}">'
f'<div class="icon">{_esc(icon_text)}</div>'
f'<div class="body">'
f'<div class="title">{_esc(title)}</div>'
f'<div class="desc">{_esc(desc)}</div>'
f'</div></div>'
)
def _kv_pair(label: str, value: str) -> str:
return f"<dt>{_esc(label)}</dt><dd>{_esc(value)}</dd>"
def _run_details(model_meta: dict, hw_meta: dict,
reference_label: str, candidate_label: str,
env_meta: Optional[dict] = None) -> str:
# Model card
model_dl = []
for k, label in (
("name", "name"), ("size_gb", "size"), ("format", "format"),
("model_type", "type"), ("hidden_size", "hidden"),
("num_hidden_layers", "layers"),
("num_attention_heads", "heads"),
("num_key_value_heads", "kv heads"),
("head_dim", "head dim"),
("vocab_size", "vocab"),
):
if k not in model_meta:
continue
v = model_meta[k]
if k == "size_gb":
v = f"{v} GB"
model_dl.append(_kv_pair(label, v))
# Hardware card
hw_dl = []
if hw_meta.get("chip"):
hw_dl.append(_kv_pair("chip", hw_meta["chip"]))
if hw_meta.get("platform_pretty") or hw_meta.get("platform"):
hw_dl.append(_kv_pair(
"platform",
hw_meta.get("platform_pretty") or hw_meta.get("platform"),
))
if "ram_gb" in hw_meta:
hw_dl.append(_kv_pair("ram", f"{hw_meta['ram_gb']} GB"))
if hw_meta.get("machine"):
hw_dl.append(_kv_pair("arch", hw_meta["machine"]))
if hw_meta.get("python"):
hw_dl.append(_kv_pair("python", hw_meta["python"]))
if hw_meta.get("nvidia_gpus"):
gpu_str = ", ".join(
f"{g['name']} ({g['memory_mb']/1024:.1f} GB)"
for g in hw_meta["nvidia_gpus"]
)
hw_dl.append(_kv_pair("gpu", gpu_str))
# Environment card
# Reference + candidate split into K/V lines for legibility.
def _split_kv(spec: str) -> str:
parts = [p.strip() for p in spec.split(",")]
return "<br>".join(parts) if parts else spec
env_dl = []
em = env_meta or {}
backend_name = em.get("backend")
if backend_name:
# Map internal backend name to a friendlier display label.
backend_display = {
"llamacpp": "llama.cpp",
"mlx": "MLX (Apple Silicon)",
"vllm": "vLLM",
"sglang": "SGLang",
}.get(backend_name, backend_name)
env_dl.append(_kv_pair("backend", backend_display))
# Engine-specific version + commit fields (whichever the active backend
# populated). Surfaced because "which engine?" is the first question
# readers ask when a report lands in their inbox.
for key, label in (
("llama_cpp_commit", "llama.cpp commit"),
("llama_cpp_version", "llama.cpp version"),
("mlx_lm_version", "mlx-lm version"),
("mlx_version", "mlx version"),
("vllm_version", "vLLM version"),
("sglang_url", "SGLang URL"),
("served_model_id", "served model"),
):
if em.get(key):
env_dl.append(_kv_pair(label, em[key]))
env_dl.extend([
f"<dt>reference</dt><dd>{_split_kv(_esc(reference_label))}</dd>",
f"<dt>candidate</dt><dd>{_split_kv(_esc(candidate_label))}</dd>",
])
return (
f'<div class="run-grid">'
f'<div class="run-card">'
f'<div class="card-head"><span class="dot"></span><h3>Model</h3></div>'
f'<dl>{"".join(model_dl)}</dl>'
f'</div>'
f'<div class="run-card">'
f'<div class="card-head"><span class="dot"></span><h3>Hardware</h3></div>'
f'<dl>{"".join(hw_dl)}</dl>'
f'</div>'
f'<div class="run-card">'
f'<div class="card-head"><span class="dot"></span><h3>Environment</h3></div>'
f'<dl>{"".join(env_dl)}</dl>'
f'</div>'
f'</div>'
)
# --------------------------------------------------------------------------
# CSS
# --------------------------------------------------------------------------
_CSS = r"""
:root {
color-scheme: light dark;
/* light-dark() requires Chrome 123+ / Safari 17.5+ / Firefox 120+ (all 2024).
Fallback browsers see the light values. */
--bg: light-dark(#f6f6f7, #0a0a0c);
--paper: light-dark(#ffffff, #131316);
--subtle: light-dark(#fafafa, #1c1c20);
--hover: light-dark(#f4f4f5, #27272a);
--fg: light-dark(#09090b, #fafafa);
--fg-2: light-dark(#27272a, #e4e4e7);
--fg-muted: light-dark(#52525b, #a1a1aa);
--fg-faint: light-dark(#a1a1aa, #71717a);
--border: light-dark(#e4e4e7, #27272a);
--border-strong: light-dark(#d4d4d8, #3f3f46);
--red: light-dark(#b42318, #fca5a5);
--red-bg: light-dark(#fef3f2, #2a1517);
--red-br: light-dark(#fecdca, #5e2424);
--amber: light-dark(#b54708, #fcd34d);
--amber-bg: light-dark(#fffaeb, #2a1f0a);
--amber-br: light-dark(#fedf89, #5c4319);
--green: light-dark(#027a48, #86efac);
--green-bg: light-dark(#ecfdf3, #0e1f13);
--green-br: light-dark(#abefc6, #1f4a2c);
--blue: light-dark(#175cd3, #93c5fd);
--blue-bg: light-dark(#eff8ff, #0e1929);
--blue-br: light-dark(#b2ddff, #1f3a5c);
--gray: light-dark(#52525b, #a1a1aa);
--code-bg: light-dark(#18181b, #0a0a0c);
--code-fg: #e4e4e7;
--code-dim: #71717a;
--code-line: light-dark(#27272a, #27272a);
--sans: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
--mono: "Geist Mono", ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace;
}
/* Manual override — the toggle button sets data-theme on <html>.
Without it, color-scheme stays "light dark" and follows the OS. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }
* { box-sizing: border-box; }
html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
body {
font-family: var(--sans);
font-size: 14px;
line-height: 1.5;
color: var(--fg);
background: var(--bg);
margin: 0;
padding: 32px 20px 80px;
font-feature-settings: "ss01", "cv11";
}
.report {
max-width: 1080px;
margin: 0 auto;
background: var(--paper);
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
}
/* HEADER */
.masthead {
position: relative;
padding: 24px 32px 20px;
border-bottom: 1px solid var(--border);
display: grid;
grid-template-columns: 1fr auto;
gap: 24px;
align-items: start;
}
.masthead .brand { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.masthead .brand-mark {
width: 22px; height: 22px; border-radius: 5px;
background: var(--fg); color: var(--paper);
display: grid; place-items: center;
font-family: var(--mono); font-size: 11px; font-weight: 600; letter-spacing: -0.04em;
}
.masthead .brand-text { font-family: var(--mono); font-size: 12px; font-weight: 500; }
.masthead .brand-text .ver { color: var(--fg-faint); font-weight: 400; margin-left: 6px; }
.masthead h1 {
margin: 0; font-size: 24px; font-weight: 600; letter-spacing: -0.02em;
line-height: 1.2; font-family: var(--mono);
}
.masthead .subtitle { margin-top: 6px; font-size: 13px; color: var(--fg-muted); }
.masthead .meta {
display: grid; gap: 4px; text-align: right;
font-family: var(--mono); font-size: 12px; color: var(--fg-muted);
margin-top: 32px; /* leave room for the absolute-positioned theme toggle */
}
.masthead .meta .row { display: flex; gap: 8px; justify-content: flex-end; align-items: center; }
.masthead .meta .k { color: var(--fg-faint); }
.masthead .meta .v { color: var(--fg-2); font-weight: 500; }
/* THEME TOGGLE — sun/moon button at top-right of the masthead */
.theme-toggle {
position: absolute;
top: 20px; right: 24px;
width: 30px; height: 30px;
border-radius: 7px;
border: 1px solid var(--border);
background: var(--paper);
color: var(--fg-muted);
cursor: pointer;
display: grid; place-items: center;
padding: 0; z-index: 1;
transition: background 100ms ease, border-color 100ms ease, color 100ms ease;
}
.theme-toggle:hover { background: var(--hover); border-color: var(--border-strong); color: var(--fg); }
.theme-toggle:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
.theme-toggle svg { width: 14px; height: 14px; display: block; }
.theme-toggle .icon-sun { display: none; }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) .theme-toggle .icon-moon { display: none; }
:root:not([data-theme="light"]) .theme-toggle .icon-sun { display: block; }
}
:root[data-theme="dark"] .theme-toggle .icon-moon { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-sun { display: block; }
:root[data-theme="light"] .theme-toggle .icon-moon { display: block; }
:root[data-theme="light"] .theme-toggle .icon-sun { display: none; }
/* STATS STRIP */
.stats-strip {
display: grid;
grid-template-columns: 1.4fr 1fr 1fr 1fr 1fr;
border-bottom: 1px solid var(--border);
background: var(--subtle);
}
.stat { padding: 18px 24px 20px; border-right: 1px solid var(--border); display: flex; flex-direction: column; gap: 6px; }
.stat:last-child { border-right: none; }
.stat .label { font-family: var(--mono); font-size: 11px; font-weight: 500; color: var(--fg-muted); letter-spacing: -0.01em; }
.stat .label .axis-letter { color: var(--fg-faint); margin-right: 6px; }
.stat .value-row { display: flex; align-items: baseline; gap: 8px; }
.stat .value {
font-family: var(--mono); font-size: 28px; font-weight: 500;
letter-spacing: -0.03em; color: var(--fg); line-height: 1;
font-feature-settings: "tnum", "ss01";
}
.stat .delta { font-family: var(--mono); font-size: 11px; color: var(--fg-faint); font-weight: 400; }
.stat.composite .value { font-size: 36px; font-weight: 600; }
.stat .badge-row { margin-top: 2px; }
/* BADGES */
.badge {
display: inline-flex; align-items: center; gap: 5px;
padding: 2px 8px 2px 7px;
border-radius: 999px;
font-family: var(--mono); font-size: 11px; font-weight: 500;
line-height: 1.5; letter-spacing: -0.01em;
border: 1px solid; white-space: nowrap;
}
.badge::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
.badge.red { color: var(--red); background: var(--red-bg); border-color: var(--red-br); }
.badge.amber { color: var(--amber); background: var(--amber-bg); border-color: var(--amber-br); }
.badge.green { color: var(--green); background: var(--green-bg); border-color: var(--green-br); }
.badge.blue { color: var(--blue); background: var(--blue-bg); border-color: var(--blue-br); }
.badge.gray { color: var(--fg-muted); background: var(--subtle); border-color: var(--border); }
/* SECTION */
.section { padding: 28px 32px; border-bottom: 1px solid var(--border); }
.section:last-of-type { border-bottom: none; }
.section-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; gap: 16px; }
.section-head h2 { margin: 0; font-size: 13px; font-weight: 600; letter-spacing: -0.01em; }
.section-head .hint { font-family: var(--mono); font-size: 11px; color: var(--fg-faint); letter-spacing: -0.01em; }
/* SUMMARY BOX */
.summary-box {
border-radius: 8px; padding: 14px 16px;
display: flex; gap: 12px; align-items: flex-start;
border: 1px solid;
}
.summary-box.red { background: var(--red-bg); border-color: var(--red-br); }
.summary-box.red .icon { background: var(--red); }
.summary-box.red .title { color: var(--red); }
.summary-box.amber { background: var(--amber-bg); border-color: var(--amber-br); }
.summary-box.amber .icon { background: var(--amber); }
.summary-box.amber .title { color: var(--amber); }
.summary-box.green { background: var(--green-bg); border-color: var(--green-br); }
.summary-box.green .icon { background: var(--green); }
.summary-box.green .title { color: var(--green); }
.summary-box .icon {
width: 18px; height: 18px; border-radius: 50%;
color: #fff; display: grid; place-items: center;
font-family: var(--mono); font-size: 11px; font-weight: 600;
flex-shrink: 0; margin-top: 1px;
}
.summary-box .body { flex: 1; }
.summary-box .title { font-weight: 600; font-size: 13.5px; margin-bottom: 4px; letter-spacing: -0.01em; }
.summary-box .desc { color: var(--fg-2); font-size: 13px; }
/* FINDINGS */
.findings { display: flex; flex-direction: column; margin-top: 16px; }
.finding {
display: grid; grid-template-columns: auto 1fr; gap: 12px;
padding: 12px 0; border-bottom: 1px solid var(--border); align-items: flex-start;
}
.finding:last-child { border-bottom: none; }
.finding:first-child { border-top: 1px solid var(--border); }
.finding .num {
font-family: var(--mono); font-size: 11px; font-weight: 500;
color: var(--fg-faint); background: var(--subtle);
padding: 2px 6px; border-radius: 4px; border: 1px solid var(--border);
flex-shrink: 0; margin-top: 1px;
}
.finding p { margin: 0; font-size: 13.5px; color: var(--fg-2); line-height: 1.5; }
.finding p strong { font-weight: 600; color: var(--fg); }
/* AXES TABLE */
.axes-table { width: 100%; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.axes-table .head-row {
display: grid;
grid-template-columns: 96px minmax(0, 1.4fr) 90px 110px minmax(0, 1.2fr);
background: var(--subtle); border-bottom: 1px solid var(--border);
padding: 10px 16px; gap: 16px; align-items: center;
font-family: var(--mono); font-size: 11px; color: var(--fg-muted);
font-weight: 500; letter-spacing: -0.01em;
}
.axis-row {
display: grid;
grid-template-columns: 96px minmax(0, 1.4fr) 90px 110px minmax(0, 1.2fr);
padding: 14px 16px; gap: 16px; align-items: center;
border-bottom: 1px solid var(--border);
}
.axis-row:last-of-type { border-bottom: none; }
.axis-row .key {
font-family: var(--mono); font-size: 12px; color: var(--fg-muted);
font-weight: 500; display: flex; align-items: center; gap: 6px;
}
.axis-row .key .letter {
display: inline-grid; place-items: center;
width: 18px; height: 18px; border-radius: 4px;
background: var(--fg); color: #fff;
font-size: 10px; letter-spacing: 0; font-weight: 500;
}
.axis-row .name { font-size: 13.5px; font-weight: 500; color: var(--fg); letter-spacing: -0.01em; }
.axis-row .name .desc {
display: block; font-size: 12px; color: var(--fg-muted);
font-weight: 400; margin-top: 2px; line-height: 1.4;
}
.axis-row .score {
font-family: var(--mono); font-size: 16px; font-weight: 500;
text-align: right; font-feature-settings: "tnum"; letter-spacing: -0.02em;
}
.axis-row .meter { position: relative; height: 6px; background: var(--hover); border-radius: 3px; overflow: hidden; }
.axis-row .meter .fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 3px; }
.axis-detail-row { padding: 0 16px 18px 16px; background: var(--subtle); border-bottom: 1px solid var(--border); }
.axis-detail-row:last-of-type { border-bottom: none; }
.detail-inner { padding: 16px 0 4px; border-top: 1px dashed var(--border); }
.detail-label { font-family: var(--mono); font-size: 11px; font-weight: 500; color: var(--fg-muted); margin-bottom: 10px; letter-spacing: -0.01em; }
.detail-label .legend { color: var(--fg-faint); margin-left: 8px; font-weight: 400; }
/* MATRIX */
.matrix {
width: 100%; border-collapse: separate; border-spacing: 0;
font-family: var(--mono); font-size: 12px;
border: 1px solid var(--border); border-radius: 6px;
overflow: hidden; background: var(--paper);
}
.matrix th, .matrix td {
padding: 10px 14px; text-align: center;
border-right: 1px solid var(--border); border-bottom: 1px solid var(--border);
font-feature-settings: "tnum";
}
.matrix th:last-child, .matrix td:last-child { border-right: none; }
.matrix tr:last-child th, .matrix tr:last-child td { border-bottom: none; }
.matrix thead th { background: var(--subtle); font-weight: 500; font-size: 11px; color: var(--fg-muted); }
.matrix tbody th { background: var(--subtle); font-weight: 500; color: var(--fg-2); font-size: 11px; }
.matrix .cell-pass { color: var(--green); background: var(--green-bg); font-weight: 500; }
.matrix .cell-fail { color: var(--red); background: var(--red-bg); font-weight: 500; }
.matrix .cell-na { color: var(--fg-faint); background: var(--paper); font-style: italic; }
/* PLAD TABLE */
.plad-table {
width: 100%; border-collapse: collapse;
background: var(--paper); border: 1px solid var(--border);
border-radius: 6px; overflow: hidden;
}
.plad-table th, .plad-table td { padding: 10px 14px; text-align: left; border-bottom: 1px solid var(--border); vertical-align: middle; }
.plad-table tr:last-child th, .plad-table tr:last-child td { border-bottom: none; }
.plad-table thead th { background: var(--subtle); font-family: var(--mono); font-size: 11px; font-weight: 500; color: var(--fg-muted); }
.plad-table .label { font-family: var(--mono); font-size: 12.5px; color: var(--fg); width: 18%; font-weight: 500; }
.plad-table .score-cell { font-family: var(--mono); font-size: 14px; color: var(--fg); width: 14%; font-weight: 500; font-feature-settings: "tnum"; }
.plad-table .band-cell { width: 16%; }
.plad-table .meter-cell { width: 36%; }
.plad-table .note-cell { width: 16%; font-size: 12px; color: var(--fg-faint); font-style: italic; }
.plad-table .skipped { color: var(--fg-faint); font-style: italic; font-family: var(--mono); font-size: 12.5px; }
.mini-meter { position: relative; height: 6px; background: var(--hover); border-radius: 3px; }
.mini-meter .fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 3px; }
/* RUN DETAILS */
.run-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.run-card { border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; background: var(--paper); }
.run-card .card-head { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; padding-bottom: 10px; border-bottom: 1px solid var(--border); }
.run-card .card-head .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--fg-muted); }
.run-card h3 { margin: 0; font-size: 12px; font-weight: 600; letter-spacing: -0.01em; }
.run-card dl { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 8px 14px; }
.run-card dt { font-family: var(--mono); font-size: 11.5px; color: var(--fg-muted); align-self: baseline; }
.run-card dd { margin: 0; font-family: var(--mono); font-size: 12px; word-break: break-word; font-feature-settings: "tnum"; text-align: right; }
/* CODE */
.code-block {
background: var(--code-bg); color: var(--code-fg);
font-family: var(--mono); font-size: 12.5px; line-height: 1.65;
padding: 14px 16px; border-radius: 8px; border: 1px solid var(--code-line);
display: flex; align-items: flex-start; gap: 10px;
}
.code-block .prompt { color: var(--code-dim); user-select: none; flex-shrink: 0; }
.code-block code { color: var(--code-fg); white-space: pre-wrap; word-break: break-word; flex: 1; min-width: 0; }
.code-block code .flag { color: #93c5fd; }
.code-block code .arg { color: #fcd34d; }
.code-block code .placeholder { color: var(--code-dim); font-style: italic; }
details.json-toggle { margin-top: 16px; }
details.json-toggle summary {
cursor: pointer; font-family: var(--mono); font-size: 12px; color: var(--fg-muted);
list-style: none; display: inline-flex; align-items: center; gap: 6px;
padding: 6px 10px; border: 1px solid var(--border); border-radius: 6px;
background: var(--paper); user-select: none; font-weight: 500;
transition: background 100ms ease, border-color 100ms ease;
}
details.json-toggle summary:hover { background: var(--hover); border-color: var(--border-strong); }
details.json-toggle summary::-webkit-details-marker { display: none; }
details.json-toggle summary::before {
content: "›"; color: var(--fg-faint); font-size: 14px; line-height: 1;
transition: transform 120ms ease; display: inline-block;
}
details.json-toggle[open] summary::before { transform: rotate(90deg); }
details.json-toggle pre {
margin: 12px 0 0; background: var(--code-bg); color: var(--code-fg);
padding: 16px 18px; border-radius: 8px; overflow: auto;
font-family: var(--mono); font-size: 11.5px; line-height: 1.65;
max-height: 480px; border: 1px solid var(--code-line);
}
/* FOOTER */
.report-foot { padding: 0; background: var(--subtle); border-top: 1px solid var(--border); }
.foot-info {
padding: 14px 32px; border-bottom: 1px solid var(--border);
font-size: 12.5px; color: var(--fg-muted); line-height: 1.5;
display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.foot-info .what { color: var(--fg-2); }
.foot-info .docs-link {
display: inline-flex; align-items: center; gap: 6px;
font-family: var(--mono); font-size: 12px; color: var(--fg);
text-decoration: none; padding: 5px 10px; border: 1px solid var(--border);
border-radius: 6px; background: var(--paper); font-weight: 500;
transition: background 100ms ease, border-color 100ms ease;
white-space: nowrap; flex-shrink: 0;
}
.foot-info .docs-link:hover { background: var(--hover); border-color: var(--border-strong); }
.foot-info .docs-link .arrow { color: var(--fg-faint); font-size: 11px; }
.foot-bar { padding: 12px 32px; display: flex; justify-content: space-between; align-items: center; font-family: var(--mono); font-size: 11px; color: var(--fg-muted); }
.foot-bar .right { display: flex; gap: 14px; }
.foot-bar .right span { color: var(--fg-faint); }
/* RESPONSIVE */
@media (max-width: 900px) {
.stats-strip { grid-template-columns: repeat(2, 1fr); }
.stat { border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.stat:nth-child(2n) { border-right: none; }
.stat.composite { grid-column: 1 / -1; }
.axes-table .head-row { display: none; }
.axis-row { grid-template-columns: auto 1fr; gap: 10px 14px; padding: 14px; }
.axis-row .key { grid-row: 1; }
.axis-row .name { grid-row: 1; grid-column: 2; }
.axis-row .score { grid-row: 2; grid-column: 1 / 2; text-align: left; }
.axis-row .badge-cell { grid-row: 2; grid-column: 2; }
.axis-row .meter { grid-row: 3; grid-column: 1 / -1; }
.run-grid { grid-template-columns: 1fr; }
.masthead { grid-template-columns: 1fr; }
.masthead .meta { text-align: left; }
.masthead .meta .row { justify-content: flex-start; }
}
@media (max-width: 560px) {
body { padding: 12px; }
.section { padding: 20px 18px; }
.masthead { padding: 18px; }
.stats-strip { grid-template-columns: 1fr; }
.stat { border-right: none; }
.foot-info { flex-direction: column; align-items: flex-start; padding: 14px 18px; }
.foot-bar { padding: 12px 18px; }
}
"""
# --------------------------------------------------------------------------
# Main entry: html_report
# --------------------------------------------------------------------------
def html_report(
*,
model: str,
reference_label: str,
candidate_label: str,
composite: CompositeScore,
gtm: GTMResult,
kld: KLDResult,
rniah: Optional[RNIAHResult] = None,
plad: Optional[PLADResult] = None,
raw_json: Optional[dict] = None,
) -> str:
"""Render the report as a self-contained HTML page (string)."""
from . import __version__
model_meta = _model_metadata(Path(model))
hw_meta = _hardware_metadata()
repro = _repro_command(
raw_json=raw_json, model=model,
reference_label=reference_label, candidate_label=candidate_label,
has_rniah=(rniah is not None and composite.rniah_score is not None),
has_plad=(plad is not None and composite.plad_score is not None),
)
diag = interpret_pattern(
gtm_score=composite.gtm_score,
kld_score=composite.kld_score,
rniah_score=composite.rniah_score,
plad_score=composite.plad_score,
)
axis_a_key = "trajectory" if isinstance(gtm, TrajectoryResult) else "gtm"
rid = _report_id()
when_pretty = _dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Stats strip
stats = [_stat_block(axis_a_key, composite.gtm_score, is_composite=False)] # placeholder, replaced below
stats = [
_stat_block("composite", composite.composite, is_composite=True).replace(
'class="label"><span class="axis-letter"></span>composite',
'class="label">Composite score',
),
]
# Actual composite block (overrides the placeholder)
rniah_low_conf = (rniah is not None and _rniah_low_confidence(rniah))
stats = []
stats.append(_stat_block("composite", composite.composite, is_composite=True))
stats.append(_stat_block(axis_a_key, composite.gtm_score))
stats.append(_stat_block("kld", composite.kld_score))
if composite.rniah_score is not None:
stats.append(_stat_block(
"rniah", composite.rniah_score,
low_confidence=rniah_low_conf,
))
if composite.plad_score is not None:
stats.append(_stat_block("plad", composite.plad_score))
# Axes table rows
axis_rows = [_axis_row(axis_a_key, composite.gtm_score),
_axis_row("kld", composite.kld_score)]
if composite.rniah_score is not None and rniah is not None:
axis_rows.append(_axis_row(
"rniah", composite.rniah_score, low_confidence=rniah_low_conf,
))
axis_rows.append(_rniah_matrix_detail(rniah))
if composite.plad_score is not None and plad is not None:
axis_rows.append(_axis_row("plad", composite.plad_score))
axis_rows.append(_plad_table_detail(plad))
n_axes = sum(1 for s in (
composite.gtm_score, composite.kld_score,
composite.rniah_score, composite.plad_score,
) if s is not None)
raw = _json.dumps(raw_json or {}, indent=2, default=str)
return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>REFRACT report — {_esc(model_meta.get("name", model))}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>{_CSS}</style>
</head>
<body>
<div class="report">
<header class="masthead">
<button class="theme-toggle" type="button" aria-label="Toggle theme" title="Toggle light / dark">
<svg class="icon-moon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
<svg class="icon-sun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
</button>
<div>
<div class="brand">
<span class="brand-mark">R</span>
<span class="brand-text">refract <span class="ver">v{_esc(__version__)}</span></span>
</div>
<h1>{_esc(model_meta.get("name", model))}</h1>
<div class="subtitle">Quantization audit · {_esc(candidate_label)} against fp16 reference</div>
</div>
<div class="meta">
<div class="row"><span class="k">report</span><span class="v">{_esc(rid)}</span></div>
<div class="row"><span class="k">generated</span><span class="v">{_esc(when_pretty)}</span></div>
<div class="row"><span class="k">scoring</span><span class="v">0–100, higher is better</span></div>
</div>
</header>
<div class="stats-strip">
{''.join(stats)}
</div>
<section class="section">
<div class="section-head">
<h2>Diagnosis</h2>
<span class="hint">{len(diag)} finding{'s' if len(diag) != 1 else ''}</span>
</div>
{_summary_box(composite)}
{_findings(diag)}
</section>
<section class="section">
<div class="section-head">
<h2>Per-axis breakdown</h2>
<span class="hint">{n_axes} axes</span>
</div>
<div class="axes-table">
<div class="head-row">
<div>axis</div>
<div>metric</div>
<div style="text-align: right;">score</div>
<div>band</div>
<div>distribution</div>
</div>
{''.join(axis_rows)}
</div>
</section>
<section class="section">
<div class="section-head">
<h2>Run details</h2>
<span class="hint">model · hardware · environment</span>
</div>
{_run_details(model_meta, hw_meta, reference_label, candidate_label,
env_meta=(raw_json or {}).get("environment"))}
</section>
<section class="section">
<div class="section-head">
<h2>Reproduce</h2>
<span class="hint">command</span>
</div>
<div class="code-block">
<span class="prompt">$</span>
<code>{_highlight_repro(repro)}</code>
</div>
<details class="json-toggle">
<summary>Raw JSON · machine-readable</summary>
<pre>{_esc(raw)}</pre>
</details>
</section>
<footer class="report-foot">
<div class="foot-info">
<div class="what">
<strong style="color: var(--fg); font-weight: 600;">What is this?</strong>
REFRACT — <em>REFerence-anchored Robust Acid-test for Compressed Transformers</em> — is a quantization audit framework that scores how faithfully a quantized KV-cache config preserves the model's own fp16 behaviour. The repository contains documentation, the motivation paper, and guidance on interpreting these scores.
</div>
<a class="docs-link" href="https://github.com/TheTom/turboquant_plus/tree/main/refract" target="_blank" rel="noopener">
github.com/TheTom/turboquant_plus
<span class="arrow">↗</span>
</a>
</div>
<div class="foot-bar">
<span>refract · v{_esc(__version__)}</span>
<div class="right">
<span>schema refract.report.v0.3.1</span>
<span>·</span>
<span>{_esc(rid)}</span>
</div>
</div>
</footer>
</div>
<script>
(function() {{
var btn = document.querySelector('.theme-toggle');
if (!btn) return;
btn.addEventListener('click', function() {{
var html = document.documentElement;
var current = html.getAttribute('data-theme');
if (!current) {{
// No override yet — read OS preference and flip to opposite.
current = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}}
html.setAttribute('data-theme', current === 'dark' ? 'light' : 'dark');
}});
}})();
</script>
</body>
</html>
"""