Files
boardroom-map/orchestrator/scorecard.py
T
Jonathan KirkwoodandClaude Fable 5 32d5d7f373 v0.1.6: human-friendly reports
- 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>
2026-07-30 09:31:40 -05:00

315 lines
13 KiB
Python
Raw 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.
"""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"