- Deterministic scoring.py (quant 60 / qual 40 / flags -15, profitability heaviest) - Per-company JSON ledger with forecast-target chaining deck N-1 -> N - Single-shot sandbox agent with guided-JSON fallback ladder (no tool loop) - Portfolio dashboard with sparklines, KPI hit rates, BDEF category bars - 48 unit tests green; endpoints smoke-tested; npm check+build green Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
266 lines
10 KiB
Python
266 lines
10 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 render_deck_report(record: dict, extraction: dict, adjudication_md: str | 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.append(f"## Composite: **{_fmt(record.get('composite'))} / 100**")
|
||
lines.append("")
|
||
q = record.get("quant", {})
|
||
lines.append(f"Quant {_fmt(q.get('score'))} · Qual {_fmt(record.get('qual', {}).get('score'))}"
|
||
f" · Penalties −{_fmt(record.get('penalties', {}).get('total'))}"
|
||
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"
|