"""Render a verified brief to Markdown / HTML. Claim references are substituted here and nowhere else, so the value a reader sees is the value the verifier checked. A reference with no matching claim renders as an explicit ``[unverified]`` marker rather than silently vanishing. """ from __future__ import annotations import html import re from collections.abc import Mapping from app.models.brief import CLAIM_REF_PATTERN, Brief, NumericClaim UNVERIFIED_MARKER = "[unverified]" def format_claim(claim: NumericClaim) -> str: """Human-facing rendering of one verified figure.""" value = claim.value if claim.unit == "usd": return f"${value:,.2f}" if claim.unit == "percent": return f"{value:+.2f}%" if claim.unit == "ratio": return f"{value:,.2f}" if claim.unit == "score": return f"{value:+.2f}" return f"{value}" def substitute(text: str, claims: Mapping[str, NumericClaim]) -> str: """Replace every ``{{cN}}`` with its verified, formatted value.""" def _replace(match: re.Match[str]) -> str: claim = claims.get(match.group(1)) return format_claim(claim) if claim else UNVERIFIED_MARKER return CLAIM_REF_PATTERN.sub(_replace, text or "") def _cell(claim_id: str | None, claims: Mapping[str, NumericClaim]) -> str: if not claim_id: return "—" claim = claims.get(claim_id) return format_claim(claim) if claim else UNVERIFIED_MARKER def render_markdown(brief: Brief) -> str: """The brief as Markdown — used for the email body and the archive.""" claims = brief.claims_by_id() out: list[str] = [] out.append(f"# AlphaBrief — {brief.generated_for}") out.append("") out.append(f"**{substitute(brief.headline, claims)}**") out.append("") if brief.partial: out.append("> ⚠️ **Partial coverage** — at least one ticker failed to return data.") out.append("") out.append(substitute(brief.executive_summary, claims)) out.append("") if brief.snapshot: out.append("## Snapshot") out.append("") out.append( "| Ticker | Company | Close | 1D | 30D | Vol (ann.) | Max DD | P/E | Sentiment |" ) out.append("| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |") for row in brief.snapshot: out.append( "| " + " | ".join( [ row.ticker, row.company or "—", _cell(row.last_close, claims), _cell(row.change_1d, claims), _cell(row.return_30d, claims), _cell(row.volatility, claims), _cell(row.max_drawdown, claims), _cell(row.pe_ratio, claims), _cell(row.sentiment, claims), ] ) + " |" ) out.append("") if brief.key_moves: out.append("## Key moves") out.append("") for move in brief.key_moves: out.append(f"- **{move.ticker}** — {substitute(move.narrative, claims)}") out.append("") if brief.news_and_sentiment: out.append("## News & sentiment") out.append("") for entry in brief.news_and_sentiment: out.append(f"- **{entry.ticker}** — {substitute(entry.summary, claims)}") if entry.top_headline: source = f" _({entry.headline_source})_" if entry.headline_source else "" out.append(f" - Top headline: “{entry.top_headline}”{source}") out.append("") if brief.risk_flags: out.append("## Risk flags") out.append("") for flag in brief.risk_flags: out.append( f"- **{flag.ticker}** · _{flag.category}_ — {substitute(flag.assessment, claims)}" ) out.append(f" - Evidence: “{flag.evidence}”") out.append("") if brief.watch_items: out.append("## Watch items") out.append("") for item in brief.watch_items: prefix = f"**{item.ticker}** — " if item.ticker else "" out.append(f"- {prefix}{substitute(item.item, claims)}") out.append("") if brief.data_gaps: out.append("## Data gaps") out.append("") for gap in brief.data_gaps: out.append(f"- {gap}") out.append("") out.append("---") out.append( "_Every figure above was computed by the MCP tool layer and independently " "recomputed by a deterministic verifier before this brief was released. " "Delivery required human approval._" ) return "\n".join(out) def render_html(brief: Brief) -> str: """A self-contained dark-theme HTML email body.""" claims = brief.claims_by_id() esc = html.escape rows = "".join( "
⚠️ Partial coverage — at least one ticker ' "failed to return data.
" if brief.partial else "" ) return f"""ALPHABRIEF
{esc(brief.generated_for)}
{partial_banner}{esc(substitute(brief.executive_summary, claims))}
| Ticker | Company | Close | 1D | 30D | Vol | Max DD | P/E | Sent. |
|---|
Every figure above was computed by the MCP tool layer and independently recomputed by a deterministic verifier before release. Delivery required human approval.