- Dashboard renders deck reports and scorecards as formatted pages (self-contained markdown renderer inlined in index.html — headings, styled tables, evidence blockquotes; no CDN, air-gap friendly) - At-a-glance strip on every deck report: composite with delta vs the prior deck, quant/qual/penalty mix, KPI hit/miss chips, BDEF best/weakest category, red-flag count - Generated DECK_REPORT.md now leads with a concise "At a glance" summary table (scorecard.py); jobs.py passes the previous deck record so the delta appears in the file too; dashboard hides the duplicate section since the strip covers it - .gitignore: .venv/ Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
315 lines
13 KiB
Python
315 lines
13 KiB
Python
"""Markdown renderers: per-deck DECK_REPORT and per-company SCORECARD.
|
||
|
||
Pure string builders over the scoring record shape (scoring.score_deck) and
|
||
ledger deck records — no I/O here; jobs.py decides where the files land.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
_CATEGORIES = "ABCDEFGH"
|
||
_CATEGORY_TITLES = {
|
||
"A": "Incentive Alignment & Skin in the Game",
|
||
"B": "Inversion Discipline & Margin of Safety",
|
||
"C": "Circle of Competence & Rational Learning",
|
||
"D": "Capital Allocation Quality",
|
||
"E": "Moat Durability & Competitive Reality",
|
||
"F": "Psychological & Cultural Health",
|
||
"G": "Simplicity, Clarity & Decision Velocity",
|
||
"H": "Board Value-Add & Governance Quality",
|
||
}
|
||
|
||
|
||
def _fmt(x, digits: int = 1) -> str:
|
||
if x is None:
|
||
return "—"
|
||
if isinstance(x, bool):
|
||
return "yes" if x else "no"
|
||
if isinstance(x, float):
|
||
return f"{x:.{digits}f}"
|
||
return str(x)
|
||
|
||
|
||
def _num(x, unit: str = "") -> str:
|
||
if x is None:
|
||
return "—"
|
||
s = f"{x:g}" if isinstance(x, (int, float)) else str(x)
|
||
return f"{s}{unit}" if unit and unit in ("%",) else (f"{s} {unit}".strip() if unit else s)
|
||
|
||
|
||
def _bucket_row(name: str, b: dict) -> str:
|
||
if b.get("na"):
|
||
return f"| {name} | — | — | {b.get('kpi_count', 0)} | NA — weight redistributed |"
|
||
return (f"| {name} | {_fmt(b.get('weight'))} | {_fmt(b.get('score'))} "
|
||
f"| {b.get('kpi_count', 0)} | |")
|
||
|
||
|
||
def _glance_lines(record: dict, prev_record: dict | None) -> list[str]:
|
||
"""Concise 'At a glance' table: the whole deck in five rows."""
|
||
lines = ["## At a glance", "", "| | |", "|---|---|"]
|
||
|
||
comp = f"**{_fmt(record.get('composite'))} / 100**"
|
||
if prev_record is not None and prev_record.get("composite") is not None \
|
||
and record.get("composite") is not None:
|
||
d = round(record["composite"] - prev_record["composite"], 1)
|
||
comp += (f" ({_arrow(d)} {'+' if d > 0 else ''}{_fmt(d)}"
|
||
f" vs {prev_record.get('period') or 'previous deck'})")
|
||
elif prev_record is None:
|
||
comp += " (first graded deck)"
|
||
lines.append(f"| Composite | {comp} |")
|
||
lines.append(f"| Score mix | quant {_fmt(record.get('quant', {}).get('score'))}"
|
||
f" + qual {_fmt(record.get('qual', {}).get('score'))}"
|
||
f" − penalties {_fmt(record.get('penalties', {}).get('total'))} |")
|
||
|
||
kpis = [k for k in (record.get("kpi_results") or [])
|
||
if isinstance(k.get("credit"), (int, float))]
|
||
if kpis:
|
||
missed = [k.get("name") or k.get("canonical_name") or "?"
|
||
for k in kpis if k["credit"] < 0.999]
|
||
hit_txt = f"{len(kpis) - len(missed)} of {len(kpis)} hit"
|
||
if missed:
|
||
hit_txt += f" — missed: {', '.join(missed)}"
|
||
lines.append(f"| KPIs vs target | {hit_txt} |")
|
||
else:
|
||
lines.append("| KPIs vs target | none targeted |")
|
||
|
||
scored = [(cid, c["adjusted"]) for cid in _CATEGORIES
|
||
if (c := record.get("qual", {}).get("categories", {}).get(cid, {}))
|
||
.get("adjusted") is not None]
|
||
if scored:
|
||
best = max(scored, key=lambda x: x[1])
|
||
worst = min(scored, key=lambda x: x[1])
|
||
lines.append(f"| BDEF best / weakest | {best[0]} · {_CATEGORY_TITLES[best[0]]}"
|
||
f" ({_fmt(best[1])}) / {worst[0]} · {_CATEGORY_TITLES[worst[0]]}"
|
||
f" ({_fmt(worst[1])}) |")
|
||
|
||
flags = record.get("penalties", {}).get("flags") or []
|
||
if flags:
|
||
worst_flag = max(flags, key=lambda f: f.get("points") or 0)
|
||
lines.append(f"| Red flags | {len(flags)} "
|
||
f"(−{_fmt(record.get('penalties', {}).get('total'))} pts;"
|
||
f" worst: `{worst_flag.get('code')}`) |")
|
||
else:
|
||
lines.append("| Red flags | none |")
|
||
lines.append("")
|
||
return lines
|
||
|
||
|
||
def render_deck_report(record: dict, extraction: dict, adjudication_md: str | None = None,
|
||
prev_record: dict | None = None) -> str:
|
||
"""One deck's full markdown report."""
|
||
lines: list[str] = []
|
||
company = record.get("company") or "?"
|
||
period = record.get("period") or "unknown period"
|
||
lines.append(f"# Deck report — {company} · {period}")
|
||
lines.append("")
|
||
lines.extend(_glance_lines(record, prev_record))
|
||
q = record.get("quant", {})
|
||
lines.append(f"Graded {record.get('graded_at') or '?'} (job {record.get('job_id') or '?'})")
|
||
lines.append("")
|
||
|
||
# --- quantitative buckets
|
||
lines.append("## Quantitative (max 60)")
|
||
lines.append("")
|
||
lines.append("| Bucket | Weight | Score | KPIs | Note |")
|
||
lines.append("|---|---|---|---|---|")
|
||
lines.append(_bucket_row("Profitability KPIs", q.get("profitability", {})))
|
||
lines.append(_bucket_row("Other KPIs", q.get("other", {})))
|
||
lines.append(_bucket_row("Forecast integrity", q.get("forecast_integrity", {})))
|
||
lines.append("")
|
||
|
||
kpi_results = record.get("kpi_results") or []
|
||
if kpi_results:
|
||
lines.append("### KPI results")
|
||
lines.append("")
|
||
lines.append("| KPI | Actual | Target | Source | Credit |")
|
||
lines.append("|---|---|---|---|---|")
|
||
for r in kpi_results:
|
||
name = r.get("name") or r.get("canonical_name") or "?"
|
||
if r.get("matched_via") == "fuzzy":
|
||
name += " (≈ matched via fuzzy)"
|
||
src = r.get("target_source") or "—"
|
||
credit = "—" if r.get("credit") is None else _fmt(r.get("credit"), 2)
|
||
lines.append(f"| {name} | {_num(r.get('actual'), r.get('unit') or '')} "
|
||
f"| {_num(r.get('target'), r.get('unit') or '')} | {src} | {credit} |")
|
||
lines.append("")
|
||
|
||
fresults = record.get("forecast_results") or []
|
||
if fresults:
|
||
lines.append("### Forecast integrity (prior targets vs this period's actuals)")
|
||
lines.append("")
|
||
lines.append("| KPI | Prior target | Actual | Accuracy |")
|
||
lines.append("|---|---|---|---|")
|
||
for r in fresults:
|
||
lines.append(f"| {r.get('canonical_name')} | {_num(r.get('target'))} "
|
||
f"| {_num(r.get('actual'))} | {_fmt(r.get('accuracy'), 2)} |")
|
||
lines.append("")
|
||
|
||
# --- qualitative
|
||
lines.append("## Qualitative (max 40)")
|
||
lines.append("")
|
||
lines.append("| Category | Median | Evidence quality | Adjusted | Points |")
|
||
lines.append("|---|---|---|---|---|")
|
||
cats = record.get("qual", {}).get("categories", {})
|
||
for cid in _CATEGORIES:
|
||
c = cats.get(cid, {})
|
||
lines.append(f"| {cid}. {_CATEGORY_TITLES[cid]} | {_fmt(c.get('median'))} "
|
||
f"| {_fmt(c.get('evidence_quality'), 2)} | {_fmt(c.get('adjusted'), 2)} "
|
||
f"| {_fmt(c.get('points'), 2)} |")
|
||
lines.append("")
|
||
for cid in _CATEGORIES:
|
||
c = cats.get(cid, {})
|
||
rats = c.get("rationales") or []
|
||
if not rats:
|
||
continue
|
||
best = max(rats, key=lambda r: sum(len(e.get("quote") or "") for e in r.get("evidence") or []))
|
||
lines.append(f"### {cid}. {_CATEGORY_TITLES[cid]}")
|
||
lines.append("")
|
||
lines.append(f"**{best.get('grader')}**: {best.get('rationale')}")
|
||
for ev in best.get("evidence") or []:
|
||
loc = f" — {ev.get('location')}" if ev.get("location") else ""
|
||
lines.append(f"> \"{ev.get('quote')}\"{loc}")
|
||
lines.append("")
|
||
|
||
# --- red flags
|
||
flags = record.get("penalties", {}).get("flags") or []
|
||
lines.append(f"## Red flags (penalty −{_fmt(record.get('penalties', {}).get('total'))})")
|
||
lines.append("")
|
||
if flags:
|
||
lines.append("| Code | Severity | Points | Sources | Description |")
|
||
lines.append("|---|---|---|---|---|")
|
||
for f in flags:
|
||
lines.append(f"| `{f.get('code')}` | {f.get('severity')} | {_fmt(f.get('points'))} "
|
||
f"| {', '.join(f.get('sources') or [])} | {f.get('description')} |")
|
||
else:
|
||
lines.append("None raised.")
|
||
lines.append("")
|
||
|
||
# --- narrative
|
||
narrative = record.get("narrative") or extraction.get("narrative") or {}
|
||
if narrative.get("summary"):
|
||
lines.append("## Narrative")
|
||
lines.append("")
|
||
lines.append(narrative["summary"])
|
||
lines.append("")
|
||
asks = narrative.get("asks") or []
|
||
if asks:
|
||
lines.append("### Asks")
|
||
lines.append("")
|
||
for a in asks:
|
||
lines.append(f"- {a}")
|
||
lines.append("")
|
||
|
||
if adjudication_md:
|
||
lines.append("## Panel adjudication")
|
||
lines.append("")
|
||
lines.append(adjudication_md.strip())
|
||
lines.append("")
|
||
return "\n".join(lines).rstrip() + "\n"
|
||
|
||
|
||
def _arrow(delta: float) -> str:
|
||
if delta > 0:
|
||
return "↑"
|
||
if delta < 0:
|
||
return "↓"
|
||
return "→"
|
||
|
||
|
||
def render_scorecard(company: dict, records: list[dict]) -> str:
|
||
"""Company SCORECARD.md across all live deck records (oldest first)."""
|
||
name = company.get("name") or company.get("slug") or "?"
|
||
lines: list[str] = [f"# Scorecard — {name}", ""]
|
||
if not records:
|
||
lines.append("No graded decks yet.")
|
||
return "\n".join(lines) + "\n"
|
||
|
||
latest = records[-1]
|
||
prev = records[-2] if len(records) > 1 else None
|
||
comp = latest.get("composite") or 0.0
|
||
if prev is not None:
|
||
delta = round(comp - (prev.get("composite") or 0.0), 1)
|
||
lines.append(f"## Latest composite: **{_fmt(comp)}** ({latest.get('period')}) "
|
||
f"{_arrow(delta)} {'+' if delta > 0 else ''}{_fmt(delta)} vs {prev.get('period')}")
|
||
else:
|
||
lines.append(f"## Latest composite: **{_fmt(comp)}** ({latest.get('period')}) — first graded deck")
|
||
lines.append("")
|
||
|
||
# --- composite history
|
||
lines.append("## Composite history")
|
||
lines.append("")
|
||
lines.append("| Period | Composite | Quant | Qual | Penalties |")
|
||
lines.append("|---|---|---|---|---|")
|
||
for r in records:
|
||
lines.append(f"| {r.get('period') or '?'} | {_fmt(r.get('composite'))} "
|
||
f"| {_fmt(r.get('quant', {}).get('score'))} "
|
||
f"| {_fmt(r.get('qual', {}).get('score'))} "
|
||
f"| −{_fmt(r.get('penalties', {}).get('total'))} |")
|
||
lines.append("")
|
||
|
||
# --- categories latest vs previous
|
||
lines.append("## BDEF categories (latest vs previous)")
|
||
lines.append("")
|
||
lines.append("| Category | Latest | Previous | Δ |")
|
||
lines.append("|---|---|---|---|")
|
||
lcats = latest.get("qual", {}).get("categories", {})
|
||
pcats = (prev or {}).get("qual", {}).get("categories", {})
|
||
for cid in _CATEGORIES:
|
||
lp = lcats.get(cid, {}).get("points")
|
||
pp = pcats.get(cid, {}).get("points")
|
||
if lp is not None and pp is not None:
|
||
d = round(lp - pp, 2)
|
||
dcol = f"{_arrow(d)} {'+' if d > 0 else ''}{_fmt(d, 2)}"
|
||
else:
|
||
dcol = "—"
|
||
lines.append(f"| {cid}. {_CATEGORY_TITLES[cid]} | {_fmt(lp, 2)} | {_fmt(pp, 2)} | {dcol} |")
|
||
lines.append("")
|
||
|
||
# --- KPI hit-rate across records
|
||
order: list[str] = []
|
||
per_kpi: dict[str, list] = {}
|
||
for r in records:
|
||
for k in r.get("kpi_results") or []:
|
||
cn = k.get("canonical_name") or k.get("name") or "?"
|
||
if cn not in per_kpi:
|
||
per_kpi[cn] = []
|
||
order.append(cn)
|
||
per_kpi[cn].append(k.get("credit"))
|
||
lines.append("## KPI hit-rate")
|
||
lines.append("")
|
||
lines.append("| KPI | Attempts | Hits | Streak | Last credit |")
|
||
lines.append("|---|---|---|---|---|")
|
||
for cn in order:
|
||
credits = [c for c in per_kpi[cn] if c is not None]
|
||
if not credits:
|
||
continue
|
||
hits = sum(1 for c in credits if c >= 1)
|
||
streak = 0
|
||
for c in reversed(credits):
|
||
if c >= 1:
|
||
streak += 1
|
||
else:
|
||
break
|
||
lines.append(f"| {cn} | {len(credits)} | {hits} | {streak} | {_fmt(credits[-1], 2)} |")
|
||
lines.append("")
|
||
|
||
# --- open flags on the latest deck
|
||
flags = latest.get("penalties", {}).get("flags") or []
|
||
lines.append(f"## Open flags ({latest.get('period')})")
|
||
lines.append("")
|
||
if flags:
|
||
lines.append("| Code | Severity | Points | Sources | Description |")
|
||
lines.append("|---|---|---|---|---|")
|
||
for f in flags:
|
||
lines.append(f"| `{f.get('code')}` | {f.get('severity')} | {_fmt(f.get('points'))} "
|
||
f"| {', '.join(f.get('sources') or [])} | {f.get('description')} |")
|
||
else:
|
||
lines.append("None.")
|
||
lines.append("")
|
||
|
||
# --- deck reports
|
||
lines.append("## Deck reports")
|
||
lines.append("")
|
||
for r in records:
|
||
lines.append(f"- {r.get('period') or '?'} — composite {_fmt(r.get('composite'))} — "
|
||
f"decks/{r.get('deck_id')}.json / DECK_REPORT.md")
|
||
return "\n".join(lines).rstrip() + "\n"
|