stem-bio-ai / stem_ai /render_html.py
Flamehaven's picture
release: v1.8.2 ICH M15 citation alignment
7728d35 verified
Raw
History Blame
30.2 kB
"""Interactive HTML report renderer for STEM BIO-AI.
Sections: Executive Summary | Decision Path | Code Integrity | AIRI Risk Triggers | Evidence Detail
Self-contained: inline CSS + SVG + JS, zero external dependencies.
"""
from __future__ import annotations
from typing import Any
from .render_html_components import (
_C,
_STAGE_TIPS,
xt,
tier_color,
tip_icon,
svg_gauge,
svg_donut,
svg_hbar,
integrity_card,
domain_card,
airi_row,
evidence_row,
)
from .render_html_styles import build_css, JS
def _compress_evidence_for_html(evidence_ledger: list[dict[str, Any]], limit: int = 200) -> list[dict[str, Any]]:
compact: list[dict[str, Any]] = []
grouped: dict[tuple[str, str, str], dict[str, Any]] = {}
grouped_order: list[tuple[str, str, str]] = []
for ev in evidence_ledger[:limit]:
severity = str(ev.get("severity", ev.get("status", "INFO"))).upper()
detector = str(ev.get("detector", ev.get("check", "")))
file_path = str(ev.get("file", ev.get("path", "")))
line = int(ev.get("line", 0) or 0)
message = str(ev.get("message") or ev.get("detail") or ev.get("explanation") or "").strip()
if severity in {"INFO", "WARN", "PASS"} and detector and file_path and file_path != "." and message:
key = (severity, detector, file_path, message)
if key not in grouped:
grouped[key] = {
"severity": severity.lower(),
"status": severity.lower(),
"detector": detector,
"file": file_path,
"line": line,
"snippet": ev.get("snippet", ""),
"message": message,
"aggregate_count": 1,
"aggregate_lines": [line] if line else [],
}
grouped_order.append(key)
else:
grouped[key]["aggregate_count"] += 1
if line and line not in grouped[key]["aggregate_lines"]:
grouped[key]["aggregate_lines"].append(line)
continue
compact.append(ev)
for key in grouped_order:
group = grouped[key]
count = int(group.get("aggregate_count", 1))
lines = sorted(group.get("aggregate_lines", []))
if count > 1:
base = str(group.get("message") or "Repeated informational evidence detected.").strip()
if lines:
line_preview = ", ".join(str(n) for n in lines[:6])
if len(lines) > 6:
line_preview += ", ..."
group["message"] = f"{base} Aggregated {count} matches from one file (lines: {line_preview})."
else:
group["message"] = f"{base} Aggregated {count} matches from one file."
group["snippet"] = ""
compact.append(group)
return compact
def _calibration_effect_note(calibration: dict[str, Any]) -> str | None:
if calibration.get("profile_read_mode") != "mirror_only":
return None
return (
"Mirror-only policy surface: selected profile metadata is shown in this report, "
"but authoritative scan scoring still follows deterministic runtime constants. "
"Preview-only posture changes, including Stage 4 replication emphasis, do not "
"change the formal score until a future read-through phase."
)
def _nav(ver: str) -> str:
links = [
("#s1", "1. Summary"),
("#s2", "2. Decision Path"),
("#s3", "3. Code Integrity"),
("#s4", "4. AIRI Risk Triggers"),
("#s5", "5. Evidence"),
]
items = "".join(f'<a href="{h}" class="nav-link">{l}</a>' for h, l in links)
return (
f'<nav class="nav" aria-label="Report sections">'
f'<div class="nav-links">{items}</div>'
f'<div class="nav-brand"><strong>STEM-BIO-AI</strong><span>STEM BIO-AI Local CLI Scan &nbsp;|&nbsp; {ver}</span></div>'
f'</nav>'
)
def _hero_accent(tc: str) -> str:
if tc == _C["red"]:
return "#D65D6D"
if tc == _C["amber"]:
return "#D28F22"
if tc == _C["green"]:
return "#2A8A56"
return "#3B86BE"
def _hero(score: dict, final: int, tier: str, tc: str, target: str, date: str) -> str:
gauge = svg_gauge(final, _hero_accent(tc))
use_scope = xt(str(score.get("use_scope", "")))
remote = xt(str(score.get("_target_remote", "")))
title = (
f'<a href="{remote}" class="hero-link" target="_blank" rel="noopener noreferrer">{target}</a>'
if remote
else target
)
return (
f'<header class="hero">'
f'<div class="hero-left"><div class="hero-gauge-card">{gauge}</div></div>'
f'<div class="hero-right">'
f'<div class="eyebrow">STEM BIO-AI Local Audit &nbsp;|&nbsp; {date}</div>'
f'<h1>{title}</h1>'
f'<div class="hero-meta">'
f'<span class="tier">{xt(tier)}</span>'
f'<span class="hero-chip">Deterministic local scan</span>'
f'<span class="hero-chip">No LLM / no network / no runtime execution</span>'
f'</div>'
f'<p class="lede">{use_scope}</p>'
f'</div></header>'
)
def _iter_rubric_rows(rubric: dict[str, Any]) -> list[dict[str, Any]]:
rows: list[dict[str, Any]] = []
for key, info in rubric.items():
if key.endswith("raw_total") or key == "baseline":
continue
if not isinstance(info, dict):
continue
score = info.get("score")
max_score = info.get("max")
if score is None:
continue
rows.append(
{
"key": key,
"score": int(score),
"max": int(max_score) if isinstance(max_score, (int, float)) else None,
"evidence": xt(str(info.get("evidence", ""))),
"detector_id": xt(str(info.get("detector_id", ""))) if info.get("detector_id") else "",
"decision_basis": xt(str(info.get("decision_basis", ""))) if info.get("decision_basis") else "",
"tier_impact": xt(str(info.get("tier_impact", ""))) if info.get("tier_impact") else "",
}
)
return rows
def _select_focus_rows(
rubric: dict[str, Any],
*,
negatives_first: bool = True,
limit: int = 4,
) -> list[dict[str, Any]]:
rows = [r for r in _iter_rubric_rows(rubric) if r["score"] != 0]
if negatives_first:
negatives = sorted([r for r in rows if r["score"] < 0], key=lambda r: abs(r["score"]), reverse=True)
positives = sorted([r for r in rows if r["score"] > 0], key=lambda r: abs(r["score"]), reverse=True)
return (negatives + positives)[:limit]
return sorted(rows, key=lambda r: abs(r["score"]), reverse=True)[:limit]
def _rubric_focus_list(rows: list[dict[str, Any]]) -> str:
if not rows:
return '<li class="focus-line muted">No scored rubric movement surfaced.</li>'
items = ""
for row in rows:
sc = row["score"]
sign = "+" if sc > 0 else ""
detector = (
f'<span class="focus-detector">{row["detector_id"]}</span>'
if row["detector_id"] else ""
)
detail = row["decision_basis"] or row["evidence"]
if row.get("tier_impact"):
detail = f'{detail} | tier impact: {row["tier_impact"]}'
items += (
f'<li class="focus-line">'
f'<div class="focus-top"><span class="focus-key">{xt(row["key"])}</span>'
f'<span class="focus-score">{sign}{sc}</span></div>'
f'<div class="focus-detail">{detector}{detail}</div>'
f'</li>'
)
return items
def _code_integrity_summary(integrity: dict[str, Any]) -> tuple[list[tuple[str, dict[str, Any]]], list[tuple[str, dict[str, Any]]]]:
warnings: list[tuple[str, dict[str, Any]]] = []
clear: list[tuple[str, dict[str, Any]]] = []
for key, info in integrity.items():
if not isinstance(info, dict):
continue
if str(info.get("status", "PASS")).upper() == "PASS":
clear.append((key, info))
else:
warnings.append((key, info))
return warnings, clear
def _section1(result: dict[str, Any], final: int, tc: str,
s1: int, s2: int, s3: int, s4: int, t0: bool,
score_cap: int | None = None) -> str:
score = result["score"]
calibration = result.get("calibration_profile", {})
cls = result.get("classification", {})
freshness = result.get("audit_freshness", {})
policy_name = xt(str(calibration.get("profile_name", "unknown")))
policy_status = xt(str(calibration.get("profile_status", "unknown")))
policy_mode = xt(str(calibration.get("profile_read_mode", "unknown")))
calibration_note = _calibration_effect_note(calibration)
ca_severity = cls.get("ca_severity", "none")
stats = "".join(
f'<div class="metric-card">'
f'<div class="metric-value" style="color:{c}">{v}</div>'
f'<div class="metric-label">{l}</div>'
f'</div>'
for v, l, c in [
(final, "Final Score", tc),
(s1, "S1 Intent", _C["teal"]),
(s2, "S2 Repo", _C["purple"]),
(s3, "S3 Code/Bio", _C["slate"]),
(s4, "S4 Replication", _C["green"]),
]
)
risks = result.get("notable_risks", [])[:5]
positives = result.get("notable_positive_evidence", [])[:4]
stage2_focus = _select_focus_rows(result.get("stage_2r_rubric", {}), negatives_first=True, limit=3)
stage3_focus = _select_focus_rows(result.get("stage_3_rubric", {}), negatives_first=False, limit=3)
if t0:
alert = (
f'<div class="alert-t0">'
f'<strong>Tier Lock [T0-FLOOR]:</strong> Direct clinical framing without an explicit boundary declaration. '
f'Score ceiling at 39. Resolving this condition is required before any tier advancement.'
f'</div>'
)
elif score_cap is not None:
alert = (
f'<div class="alert-t0" style="background:#fff3cd;border-color:#ffc107;color:#856404;">'
f'<strong>Tier Lock [CA-CAP]:</strong> Clinical-adjacent surface detected without explicit non-clinical boundary. '
f'Score ceiling at {score_cap} (T2 maximum). '
f'Adding a non-diagnostic disclaimer resolves this lock.'
f'</div>'
)
else:
alert = ""
risk_list = "".join(f'<li>{xt(str(r))}</li>' for r in risks) or "<li>No notable risks surfaced.</li>"
positive_list = "".join(f'<li>{xt(str(p))}</li>' for p in positives) or "<li>No notable positive evidence surfaced.</li>"
return (
f'<section id="s1">'
f'<h2 class="s-title">Executive Summary</h2>'
f'{alert}'
f'<div class="metric-grid">{stats}</div>'
f'<div class="memo-grid">'
f'<article class="memo-card memo-primary">'
f'<div class="eyebrow">TL;DR</div>'
f'<h3>Decision memo</h3>'
f'<p class="memo-text">This repository lands at <strong>{xt(str(score.get("formal_tier", "")))}</strong> '
f'with a final score of <strong>{final}/100</strong>. The result is driven more by '
f'boundary, workflow-support, and governance weaknesses than by classic code-pattern failures.</p>'
f'<div class="pill-row"><span class="pill">Policy: {policy_name}</span>'
f'<span class="pill">Status: {policy_status}</span>'
f'<span class="pill">Mode: {policy_mode}</span></div>'
f'</article>'
f'<article class="memo-card">'
f'<div class="eyebrow">Primary Risks</div>'
f'<h3>What pushed the review down</h3>'
f'<ul class="memo-list">{risk_list}</ul>'
f'</article>'
f'<article class="memo-card">'
f'<div class="eyebrow">Positive Evidence</div>'
f'<h3>What still supports reviewability</h3>'
f'<ul class="memo-list">{positive_list}</ul>'
f'</article>'
f'<article class="memo-card">'
f'<div class="eyebrow">Freshness</div>'
f'<h3>When to re-check</h3>'
f'<p class="memo-text">Review after <strong>{xt(str(freshness.get("review_after_days", "n/a")))}</strong> days. '
f'Expires on <strong>{xt(str(freshness.get("expires_on", "unknown")))}</strong>.</p>'
f'<p class="memo-note">Change-triggered re-audit now: '
f'{xt(str(freshness.get("change_triggered_reaudit_recommended_now", False)))}</p>'
f'</article>'
f'</div>'
f'<div class="memo-grid" style="margin-top:18px">'
f'<article class="memo-card">'
f'<div class="eyebrow">Stage 2R Focus</div>'
f'<h3>Repo-local contradictions</h3>'
f'<ul class="focus-list">{_rubric_focus_list(stage2_focus)}</ul>'
f'</article>'
f'<article class="memo-card">'
f'<div class="eyebrow">Stage 3 Focus</div>'
f'<h3>Accountability surfaces</h3>'
f'<ul class="focus-list">{_rubric_focus_list(stage3_focus)}</ul>'
f'</article>'
f'<article class="memo-card">'
f'<div class="eyebrow">Policy Boundary</div>'
f'<h3>How to read this artifact</h3>'
f'<p class="memo-text">{xt(calibration_note) if calibration_note else "Authoritative scoring and surfaced policy metadata are aligned in this release line."}</p>'
f'<p class="memo-note"><strong>Classification applied:</strong> '
f'ca_severity={xt(ca_severity)} | '
f'score_cap={xt(str(score_cap)) if score_cap is not None else "none"} | '
f't0_floor={"active" if t0 else "clear"}</p>'
f'</article>'
f'</div>'
f'</section>'
)
def _stage_card(title: str, value: int, color: str, tip: str, focus_rows: list[dict[str, Any]], summary: str) -> str:
bar_color = _C["red"] if value < 40 else (_C["amber"] if value < 65 else color)
return (
f'<article class="stage-card">'
f'<div class="stage-card-top">'
f'<div class="stage-name">{title} {tip_icon(tip)}</div>'
f'<div class="stage-value" style="color:{bar_color}">{value}</div>'
f'</div>'
f'{svg_hbar(value, 100, bar_color)}'
f'<p class="stage-summary">{xt(summary)}</p>'
f'<ul class="focus-list">{_rubric_focus_list(focus_rows)}</ul>'
f'</article>'
)
def _config_pattern_card() -> str:
return (
f'<div class="config-pattern">'
f'<div class="config-copy">'
f'<div class="eyebrow">Configured, Not Rewritten</div>'
f'<h3 class="subhead">Changing review posture does not require touching the score core</h3>'
f'<p class="memo-text">Use <code>stem policy simulate</code> with a governed profile file when you want to preview a different review posture. '
f'The authoritative score path stays deterministic; the profile is surfaced as metadata and preview-only interpretation.</p>'
f'<p class="memo-note">If you only need the default posture, you do not need a profile file at all.</p>'
f'</div>'
f'<div class="config-grid">'
f'<article class="config-card">'
f'<div class="config-label">profile.json</div>'
f'<pre class="config-code">{{\n "profile_name": "strict_clinical_adjacency",\n "profile_read_mode": "mirror_only"\n}}</pre>'
f'</article>'
f'<article class="config-card">'
f'<div class="config-label">command</div>'
f'<pre class="config-code">stem policy simulate /path/to/repo --profile-file profile.json</pre>'
f'</article>'
f'<article class="config-card">'
f'<div class="config-label">artifact note</div>'
f'<pre class="config-code">Calibration Effect: mirror-only\nPolicy metadata surfaced\nFormal score unchanged</pre>'
f'</article>'
f'</div>'
f'</div>'
)
def _section2(result: dict[str, Any], s1: int, s2: int, s3: int, s4: int) -> str:
stage1_focus = _select_focus_rows(result.get("stage_1_rubric", {}), negatives_first=True, limit=4)
stage2_focus = _select_focus_rows(result.get("stage_2r_rubric", {}), negatives_first=True, limit=4)
stage3_focus = _select_focus_rows(result.get("stage_3_rubric", {}), negatives_first=False, limit=4)
stage4_focus = _select_focus_rows(result.get("stage_4_rubric", {}), negatives_first=False, limit=4)
_s3r = result.get("stage_3_rubric", {}).get("stage_3_raw_total", {})
_s3_formula = f" (raw: {_s3r['score']}/{_s3r['max']})" if _s3r.get("score") is not None and _s3r.get("max") else ""
cards = "".join([
_stage_card(
"Stage 1 — README Intent",
s1,
_C["teal"],
_STAGE_TIPS[0],
stage1_focus,
"Claim language, limitation posture, and clinical boundary wording.",
),
_stage_card(
"Stage 2R — Repo Consistency",
s2,
_C["purple"],
_STAGE_TIPS[1],
stage2_focus,
"Internal contradictions between README, workflow claims, and support surfaces.",
),
_stage_card(
"Stage 3 — Code / Bio Responsibility",
s3,
_C["slate"],
_STAGE_TIPS[2],
stage3_focus,
f"Engineering accountability, provenance, and reviewable responsibility surfaces.{_s3_formula}",
),
_stage_card(
"Stage 4 — Replication",
s4,
_C["green"],
_STAGE_TIPS[3],
stage4_focus,
"Reproducibility evidence is reported separately and does not alter the formal tier.",
),
])
return (
f'<section id="s2">'
f'<h2 class="s-title">Decision Path {tip_icon("This section explains where the score came from, with rubric-level movement and detector-linked rationale where available.")}</h2>'
f'<div class="panel decision-panel">'
f'<div class="formula-banner">Final = 0.4 × S1 + 0.2 × S2R + 0.4 × S3 − C1_penalty &nbsp;|&nbsp; Stage 4 remains a separate replication lane.</div>'
f'{_config_pattern_card()}'
f'<div class="stage-deck">{cards}</div>'
f'</div></section>'
)
def _section3(integrity: dict, cc: dict) -> str:
combined: dict[str, Any] = {**integrity}
for k, v in cc.items():
if isinstance(v, dict):
combined[k] = {
"status": v.get("status", "PASS"),
"evidence": [f"count={v.get('count', 0)}"],
}
warn_pairs, pass_pairs = _code_integrity_summary(combined)
warning_cards = "".join(integrity_card(k, v) for k, v in warn_pairs)
pass_cards = "".join(integrity_card(k, v) for k, v in pass_pairs)
warning_fallback = '<p class="memo-text">No WARN/FAIL lanes surfaced.</p>'
hint = tip_icon(
"C1-C6: static code and governance checks. CC1-CC3: Layer 2 AST contract detectors. "
"PASS means no mapped trigger was detected in the current rule scope, not that the whole repository is mature."
)
faq = (
f'<div class="faq-block">'
f'<details class="faq-item"><summary>Why can Code Integrity contain PASS while the overall score is still low?</summary>'
f'<p>Because Code Integrity is a narrow detector family. The formal score is still driven mainly by Stage 1, Stage 2R, and Stage 3 evidence posture.</p></details>'
f'<details class="faq-item"><summary>What changed in the C4 / C5 / C6 split?</summary>'
f'<p>C4 is now reserved for executable fail-open exception behavior, C5 for unsupported compliance or boundary integrity claims, and C6 for mock-auth or no-auth trust-boundary signals.</p></details>'
f'</div>'
)
return (
f'<section id="s3">'
f'<h2 class="s-title">Code Integrity &amp; Contract {hint}</h2>'
f'<div class="integrity-stack">'
f'<div class="panel">'
f'<div class="eyebrow">Warnings First</div>'
f'<h3 class="subhead">Mapped risk lanes that fired</h3>'
f'<div class="card-grid">{warning_cards or warning_fallback}</div>'
f'</div>'
f'<div class="panel">'
f'<div class="eyebrow">Clear Lanes</div>'
f'<h3 class="subhead">What stayed quiet in the current rule scope</h3>'
f'<div class="card-grid">{pass_cards}</div>'
f'{faq}'
f'</div>'
f'</div></section>'
)
def _section4(airi: dict) -> str:
if not airi or "covered_risks" not in airi:
return ""
pct = float(airi.get("coverage_rate", 0))
covered_n = int(airi.get("covered_count", 0))
total_n = int(airi.get("total_risks_in_detector_scope", 0))
gaps = airi.get("known_gaps", [])
all_risks = airi.get("covered_risks", [])
donut = svg_donut(pct, _C["green"], 98)
covered_counts: dict[int, int] = {d: 0 for d in range(1, 8)}
gap_counts: dict[int, int] = {d: 0 for d in range(1, 8)}
for r in all_risks:
try:
d = int(str(r.get("subdomain_id", "0")).split(".")[0])
if d in covered_counts:
covered_counts[d] += 1
except (ValueError, IndexError):
continue
for g in gaps:
try:
d = int(str(g.get("subdomain_id", "0")).split(".")[0])
if d in gap_counts:
gap_counts[d] += 1
except (ValueError, IndexError):
continue
domain_boxes = domain_card(0, covered_n, len(gaps))
domain_boxes += "".join(domain_card(d, covered_counts[d], gap_counts[d]) for d in range(1, 8))
c_rows = "".join(airi_row(r, "covered") for r in all_risks[:24])
g_rows = "".join(airi_row(g, "gap") for g in gaps)
toggle = (
f'<div class="toggle-row">'
f'<div class="toggle-group">'
f'<button class="toggle-btn" data-view="covered" onclick="airiToggle(\'covered\')">Covered ({covered_n})</button>'
f'<button class="toggle-btn" data-view="gaps" onclick="airiToggle(\'gaps\')">Gaps ({len(gaps)})</button>'
f'</div>'
f'<span class="muted-note">Click a domain card to filter. Counts are shown as covered / gaps.</span>'
f'</div>'
)
table = (
f'<div style="overflow-x:auto"><table class="airi-table">'
f'<thead><tr><th>ID</th><th>Risk</th><th>Domain</th><th>Covered by / Note</th></tr></thead>'
f'<tbody>{c_rows}{g_rows}</tbody></table></div>'
)
src = xt(airi.get("airi_version", ""))
bundle_scope = xt(airi.get("airi_bundle_scope", ""))
snapshot = xt(airi.get("airi_upstream_snapshot_date", ""))
license_name = xt(airi.get("airi_upstream_license", ""))
attribution = xt(airi.get("airi_attribution_note", ""))
hint = tip_icon(
"Coverage counts only risks reached through local detector mappings. "
"Coverage is not a safety verdict, and unmapped review concerns remain outside the numerator."
)
faq = (
f'<div class="faq-block" style="margin-top:18px">'
f'<details class="faq-item"><summary>What does 7 / 32 mean?</summary>'
f'<p>It means seven AIRI risk IDs are currently reached by active local detector mappings, out of thirty-two AIRI risk IDs in the current detector scope.</p></details>'
f'<details class="faq-item"><summary>What does “why mapped” mean?</summary>'
f'<p>Each covered AIRI row carries a bounded explanation built from the triggered detector, the local mapping justification, and the trigger reason surfaced by the scan.</p></details>'
f'<details class="faq-item"><summary>What does AIRI not prove here?</summary>'
f'<p>AIRI does not independently verify harm, causality, clinical failure, or legal noncompliance. It is a risk-vocabulary layer around local findings.</p></details>'
f'</div>'
)
mapping_pattern = (
f'<div class="config-pattern" style="margin-top:18px">'
f'<div class="config-copy">'
f'<div class="eyebrow">Mapped, Not Guessed</div>'
f'<h3 class="subhead">AIRI rows light up through active detector mappings</h3>'
f'<p class="memo-text">The report does not infer AIRI coverage from prose alone. '
f'Coverage appears when a local detector fires and a governed mapping exists in the current AIRI runtime bundle.</p>'
f'</div>'
f'<div class="config-grid">'
f'<article class="config-card">'
f'<div class="config-label">trigger</div>'
f'<pre class="config-code">C6_mock_auth_or_fail_open_boundary\nstatus: detected</pre>'
f'</article>'
f'<article class="config-card">'
f'<div class="config-label">mapping</div>'
f'<pre class="config-code">R2R_D5_single_external_service_dependency\n→ 72.04.02 Market Concentration</pre>'
f'</article>'
f'<article class="config-card">'
f'<div class="config-label">report surface</div>'
f'<pre class="config-code">covered_by: detector id\nwhy: bounded mapping reason</pre>'
f'</article>'
f'</div>'
f'</div>'
)
return (
f'<section id="s4">'
f'<h2 class="s-title">MIT AI Risk Repository Coverage {hint}<span class="airi-tag">{src} | airisk.mit.edu</span></h2>'
f'<div class="airi-stack">'
f'<div class="panel">'
f'<div class="eyebrow">Feature Explainer</div>'
f'<h3 class="subhead">What this section is doing</h3>'
f'<p class="memo-text">AIRI is used here as a bounded risk-vocabulary layer around deterministic repository findings. '
f'The report uses the curated runtime bundle, not the full upstream AIRI universe.</p>'
f'<div class="airi-kpi">'
f'<div class="airi-donut">{donut}</div>'
f'<div class="airi-copy">'
f'<div class="metric-inline"><strong>{covered_n} / {total_n}</strong> risks in detector scope</div>'
f'<div class="metric-inline">Bundle scope: <strong>{bundle_scope}</strong></div>'
f'<div class="metric-inline">Snapshot: <strong>{snapshot}</strong> | License: <strong>{license_name}</strong></div>'
f'<p class="memo-note">{attribution}</p>'
f'</div></div>'
f'{faq}'
f'{mapping_pattern}'
f'</div>'
f'<div class="panel">'
f'<div class="eyebrow">Coverage Explorer</div>'
f'<h3 class="subhead">Covered and gap rows</h3>'
f'<div class="domain-grid">{domain_boxes}</div>'
f'{toggle}{table}'
f'</div>'
f'</div></section>'
)
def _section5(evidence_ledger: list) -> str:
if not evidence_ledger:
return (
f'<section id="s5"><h2 class="s-title">Evidence Detail</h2>'
f'<div class="panel" style="color:{_C["dgray"]};font-size:13px">'
f'No evidence entries collected.</div></section>'
)
n = len(evidence_ledger)
display_rows = _compress_evidence_for_html(evidence_ledger, limit=200)
shown = len(display_rows)
chips = " ".join(
f'<span class="filter-chip{" active" if i == 0 else ""}" data-sev="{s}" onclick="filterEv(\'{s}\')">{l}</span>'
for i, (s, l) in enumerate(
[
("all", f"All ({n})"),
("fail", "FAIL"),
("warn", "WARN"),
("pass", "PASS"),
("info", "INFO"),
]
)
)
rows = "".join(evidence_row(ev) for ev in display_rows)
hint = tip_icon(
"Full evidence ledger from all detectors. Filter by severity. Capped at 200 entries in HTML view."
)
compact_note = ""
if shown < min(n, 200):
compact_note = (
f'<div class="muted" style="margin:0 0 10px 0">'
f'Showing {shown} compact rows from the first {min(n, 200)} evidence entries.'
f'</div>'
)
th = f'style="padding:8px 5px;font-size:11px;text-align:left;color:{_C["dgray"]}"'
return (
f'<section id="s5">'
f'<h2 class="s-title">Evidence Detail {hint}</h2>'
f'<div class="panel">'
f'<div class="toggle-row" style="margin-bottom:14px">{chips}</div>'
f'{compact_note}'
f'<div style="overflow-x:auto">'
f'<table style="width:100%;border-collapse:collapse">'
f'<thead><tr style="background:{_C["lgray"]}">'
f'<th style="padding:8px 5px;width:16px"></th>'
f'<th {th}>SEV</th><th {th}>Detector</th><th {th}>Finding</th><th {th}>File</th>'
f'</tr></thead>'
f'<tbody>{rows}</tbody></table></div></div></section>'
)
def render_html(result: dict[str, Any]) -> str:
score = result["score"]
final = int(score["final_score"])
tier = str(score["formal_tier"])
tc = tier_color(tier)
target = xt(str(result["target"]["name"]))
score["_target_remote"] = str(result.get("target", {}).get("remote", "")).removesuffix(".git")
date = xt(str(result.get("generated_at_local", "")))
s1 = int(score.get("stage_1_readme_intent", 0))
s2 = int(score.get("stage_2_repo_local_consistency", 0))
s3 = int(score.get("stage_3_code_bio", 0))
s4 = int(result.get("replication_score", 0))
cls = result.get("classification", {})
t0 = cls.get("t0_hard_floor", False)
score_cap = cls.get("score_cap")
css = build_css(tc)
hero = _hero(score, final, tier, tc, target, date)
sec1 = _section1(result, final, tc, s1, s2, s3, s4, t0, score_cap)
sec2 = _section2(result, s1, s2, s3, s4)
sec3 = _section3(result.get("code_integrity", {}), result.get("code_contract", {}))
sec4 = _section4(result.get("airi_risk_coverage", {}))
sec5 = _section5(result.get("evidence_ledger", []))
ver = xt(result.get("stem_ai_version", ""))
nav = _nav(ver)
return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>STEM BIO-AI &mdash; {target}</title>
<style>{css}</style>
</head>
<body>
{nav}
{hero}
<div class="content">
{sec1}
{sec2}
{sec3}
{sec4}
{sec5}
<div class="footer">
STEM BIO-AI Local CLI Scan &nbsp;|&nbsp; {ver}
&nbsp;|&nbsp; Deterministic surface scan &mdash; no LLM, network, or runtime execution.
</div>
</div>
<script>{JS}</script>
</body>
</html>"""