crucible / engine /report_model.py
s-ttp's picture
Withheld-run fixes: recorded margin floor; re-blend bounded by the recorded cap; precise break-even revenue and sign-aware growth clause; pre-revenue message; no retry invitation for deterministic failures
7e513ed verified
Raw History Blame Contribute Delete
117 kB
"""UserReport, the canonical renderer, the final claim audit, and the InternalQARecord.
One structured report is built once, from the frozen AnalysisState and the ingested prose. HTML
and markdown are two serialisations of the same fragment list, so they cannot say different
things. Every number is a claim fragment that resolves to one state path; the audit re-resolves
each fragment against the frozen state and rejects any material number that is not a claim.
The audited fragments ARE the delivered document: the web page, the download and the saved report
all use these bytes. Internal QA (validator ids, lineage, rejected sentences, engine logs) lives in
a separate record and never enters the user report.
"""
from __future__ import annotations
from collections.abc import Mapping
import html as _html
import re
from dataclasses import dataclass, field
import catalog as C
from prose import BENIGN, DENIAL, IDENTIFIER, MATERIAL_NUMBER, Fragment
# ═══════════════════════════════════════════════════════════════════════════════════════
# structure
# ═══════════════════════════════════════════════════════════════════════════════════════
@dataclass
class Block:
kind: str # heading | para | bullets | points | table | tiles | callout | kv
section: str
content: object = field(default_factory=list) # para: [Fragment]; bullets: [[Fragment]]; table: {"head": [...], "rows": [[[Fragment]]]}
title: str | None = None
css: str | None = None
def units(self):
"""Fragment sequences that read as one phrase: a paragraph, one bullet, one table row.
Label adjacency is judged inside a unit, never across table cells or bullets."""
if self.kind in ("para", "callout", "heading"):
return [list(self.content)]
if self.kind in ("bullets", "points"):
return [list(item) for item in self.content]
if self.kind in ("table", "kv", "tiles"):
return [[f for cell in row for f in cell] for row in self.content.get("rows", [])]
return []
def fragments(self):
if self.kind in ("para", "callout", "heading"):
return list(self.content)
if self.kind in ("bullets", "points"):
return [f for item in self.content for f in item]
if self.kind in ("table", "kv", "tiles"):
out = [Fragment(h) for h in self.content.get("head", [])]
for row in self.content.get("rows", []):
for cell in row:
out.extend(cell)
return out
return []
@dataclass
class UserReport:
ticker: str
company: str
as_of: str
version: str
blocks: list
claims: dict # claim_id → {value, rendered, path, label}
limitations: list
arguments: dict = field(default_factory=dict) # role → validated crucible.args/2 set (engine/arguments.py)
def fragments(self):
return [f for b in self.blocks for f in b.fragments()]
class _R:
"""Fragment helpers bound to a frozen state and its catalog. The renderer never computes a
number: it formats a state value or it writes words."""
def __init__(self, state, cat):
self.s, self.cat, self.used = state, cat, {}
self.seen_evidence = set() # evidence printed in full once per report; later mentions are compact
self.authored_seen = set() # an authored text is printed once per report
def c(self, cid, cap=False):
cl = self.cat.get(cid)
if cl is None:
return Fragment("unavailable")
self.used[cid] = cl
txt = cl.rendered
if cap and cl.stype == C.REL and txt:
txt = txt[0].upper() + txt[1:] # a relation clause that opens a sentence
return Fragment(txt, cid, cl.value)
def p(self, path, stype, label):
cl = C.path_claim(self.s, path, stype, label)
if cl.rendered is None:
return Fragment("—")
self.used[cl.claim_id] = cl
return Fragment(cl.rendered, cl.claim_id, cl.value)
@staticmethod
def t(text):
return Fragment(text)
def _para(section, *frags, css=None, title=None):
return Block("para", section, [f if isinstance(f, Fragment) else Fragment(str(f)) for f in frags], title=title, css=css)
# ── HistoricalMetricQuality and forecast lineage: every label below derives from typed state ──
def historical_quality(s):
"""HistoricalMetricQuality of the trailing figures (reconcile.ttm_quality). A state built before it existed is read
conservatively: any unchecked quarter makes the series at most partially reconciled."""
rec = s.historical.get("reconciliation") or {}
q = rec.get("ttm_quality")
if isinstance(q, Mapping) and q.get("status"):
return q
st, pe = rec.get("status"), str(rec.get("period_end") or "")
import datetime as _dt
try: # only the quarters inside the trailing year count
lo = (_dt.date.fromisoformat(pe[:10]) - _dt.timedelta(days=360)).isoformat()
except ValueError:
lo = ""
unv = [e for e in rec.get("unverified_quarters") or [] if str(e) > lo]
status = ("SEC_RECONCILED" if st in ("RECONCILED", "SEC_ONLY") and not unv else
"PARTIALLY_RECONCILED" if st == "RECONCILED" else "PROVIDER_ONLY" if st and st != "NO_STATEMENTS" else "UNVERIFIED")
return {"status": status, "verified_through": None, "unverified_periods": unv, "source_mix": [], "quarters": []}
def ttm_header(q, separated=False):
"""The trailing column's label from its quality state: only SEC_RECONCILED reads as reconciled without qualification."""
st, unv = q.get("status"), list(q.get("unverified_periods") or [])
ends = " and ".join(unv)
if st == "SEC_RECONCILED":
parts = ["reconciled to SEC filings"]
elif st == "PARTIALLY_RECONCILED":
parts = [f"reconciled to SEC filings through {q['verified_through']}" if q.get("verified_through")
else "partly reconciled to SEC filings",
f"quarter{'s' if len(unv) > 1 else ''} ended {ends} from the data provider, unverified"]
elif st == "PROVIDER_ONLY":
parts = ["the data provider's figures, not checked against SEC filings"]
else:
parts = ["unverified"]
if separated:
parts.append("before the separation")
return "Trailing twelve months (" + "; ".join(parts) + ")"
def anchored_forecasts(s):
"""The forecast keys set from sell-side consensus (model weight 0), and those shrunk toward it (0 < weight < 1)."""
anc = s.forecast.get("consensus_anchor") or {}
full, part = [], []
for k in ("revenue", "eps"):
a = anc.get(k) if isinstance(anc, Mapping) else None
if isinstance(a, Mapping) and a.get("model_weight") is not None:
(full if float(a["model_weight"]) <= 0.0 else part if float(a["model_weight"]) < 1.0 else []).append(k)
return full, part
def forecast_header(s, separated=False):
full, part = anchored_forecasts(s)
name = {"revenue": "revenue", "eps": "EPS"}
notes = (["continuing company"] if separated else []) + \
([" and ".join(name[k] for k in full) + " set from sell-side consensus"] if full else []) + \
([" and ".join(name[k] for k in part) + " shrunk toward sell-side consensus"] if part else [])
return "Engine forecast, next 12 months" + (" (" + "; ".join(notes) + ")" if notes else "")
# compact render fields: a table header carries a short label and a marker; the full provenance sentence is the
# footnote printed directly under the table (a long header pushed the value columns off a printed page)
HIST_MARK, FCST_MARK = "\u2020", "\u2021" # † ‡ (an asterisk would start a Markdown list item)
def historical_quality_render(q, separated=False):
"""{full_description, table_header, footnote} for the trailing column, all from HistoricalMetricQuality."""
st, unv = q.get("status"), list(q.get("unverified_periods") or [])
ends = " and ".join(unv)
what = "Trailing twelve months" + (", before the separation" if separated else "")
one = len(unv) == 1
unchecked = (f"the quarter{'' if one else 's'} ended {ends} could not be verified against SEC filings and "
f"use{'s' if one else ''} the data provider's figures")
if st == "SEC_RECONCILED":
body = "reconciled to SEC filings"
elif st == "PARTIALLY_RECONCILED":
body = (f"reconciled to SEC filings through {q['verified_through']}" if q.get("verified_through")
else "partly reconciled to SEC filings") + f"; {unchecked}"
elif st == "PROVIDER_ONLY":
body = "the data provider's figures, not checked against SEC filings"
else:
body = "not verified against SEC filings"
return {"full_description": ttm_header(q, separated),
"table_header": ("TTM, pre-separation" if separated else "TTM") + HIST_MARK,
"footnote": f"{HIST_MARK} {what}: {body}."}
def forecast_lineage_render(s, separated=False):
"""{full_description, table_header, footnote} for the forecast column, from the typed consensus anchor."""
full, part = anchored_forecasts(s)
name = {"revenue": "revenue", "eps": "EPS"}
anc = s.forecast.get("consensus_anchor") or {}
why = {"RESTATEMENT": "the reported statements still include a separated business",
"MISFIRE": "the model's own estimate was set aside"}
reasons = [why.get(str((anc.get(k) or {}).get("reason"))) for k in full]
reasons = [x for x in dict.fromkeys(reasons) if x]
parts = ["Engine forecast" + (" for the continuing company" if separated else "") + "."]
if full:
subj = " and ".join(name[k] for k in full)
parts.append(f"{subj[:1].upper() + subj[1:]} {'are' if len(full) > 1 else 'is'} set from sell-side consensus in "
f"this run" + (f" ({'; '.join(reasons)})" if reasons else "") + ", not independent engine estimates.")
if part:
subj = " and ".join(name[k] for k in part)
parts.append(f"{subj[:1].upper() + subj[1:]} {'are' if len(part) > 1 else 'is'} shrunk toward sell-side consensus.")
if not full and not part:
parts.append("A model estimate, not guidance or consensus.")
return {"full_description": forecast_header(s, separated),
"table_header": ("Next 12 months, continuing company" if separated else "Next 12 months") + FCST_MARK,
"footnote": f"{FCST_MARK} " + " ".join(parts)}
_ANCHOR_WHY = {"RESTATEMENT": "the reported statements still include a separated business, so it is set from sell-side "
"consensus for the continuing company",
"MISFIRE": "the model's own estimate was set aside and replaced by sell-side consensus"}
_DIMENSION_WORDS = {"DEFINITION": "earnings definition", "ACCOUNTING_BASIS": "accounting basis", "PERIOD": "period",
"PERIMETER": "business perimeter", "CURRENCY": "currency", "SECURITY_BASIS": "share basis"}
def consensus_absence_sentence(s, rid, noun):
"""Why the engine's figure is not compared with consensus, from relations.absence (typed), or None."""
import relations as _RL
ab = _RL.absence(s, rid)
if ab is None:
return None
if ab["kind"] == "ANCHORED":
why = _ANCHOR_WHY.get(str(ab["anchor"].get("reason")), "it is set from sell-side consensus")
return (f"It is not compared with the engine's forecast: the engine's {noun} forecast is not an independent "
f"estimate ({why}).")
if ab["kind"] == "INCOMPATIBLE":
dims = [_DIMENSION_WORDS.get(d, "basis") for d in ab["dimensions"]]
return ("It is not compared with the engine's forecast: the two are not on the same "
+ " or ".join(dict.fromkeys(dims)) + ".")
return "It is not compared with the engine's forecast."
def build_user_report(s, cat, prose, limitations):
"""``prose`` holds ingested fragment lists: summary, thesis[], risks[], seats{role: {...}}, pm{...}."""
r = _R(s, cat)
B = []
eng = s.valuation.get("engine") or {}
terms = eng.get("terms") or {}
iv_word = s.valuation.get("intrinsic_label") or terms.get("intrinsic") or "intrinsic value"
per = s.historical.get("perimeter") or {}
sep = per.get("event")
pol_methods = set((s.issuer.get("policy") or {}).get("pricing_methods") or ())
# ── verdict ──
B.append(Block("heading", "verdict", [r.t(f"{s.identity.get('company_name')} ({s.identity.ticker})")]))
B.append(_para("verdict", r.c("RATING"), r.t(" · "), r.c("HEADLINE_RETURN"),
r.t(" model-implied 12-month price return · share price "), r.c("PRICE"), css="verdict"))
# the few figures a reader needs first; each tile is one phrase for the label checks
tiles = []
if "INTRINSIC_VALUE" in cat:
tiles.append([[r.t(iv_word[:1].upper() + iv_word[1:])], [r.c("INTRINSIC_VALUE")], [r.t("price "), r.c("PRICE_VS_INTRINSIC")]])
if "MARKET_COMPOSITE" in cat:
tiles.append([[r.t("Market pricing")], [r.c("MARKET_COMPOSITE")], [r.t("price "), r.c("PRICE_VS_COMPOSITE")]])
if "RANGE_LOW" in cat and "RANGE_HIGH" in cat:
tiles.append([[r.t("Analyst scenarios")], [r.c("RANGE_LOW"), r.t(" to "), r.c("RANGE_HIGH")],
[r.t("probability-weighted "), r.c("PM_RETURN")]])
if "MOMENTUM" in cat:
tiles.append([[r.t("Momentum")], [r.c("MOMENTUM")], []])
if tiles:
B.append(Block("tiles", "verdict", {"rows": tiles}))
args = prose.get("arguments") or {}
s_args = args.get("summary") or {}
if s_args.get("thesis") or (not prose.get("summary") and (args.get("pm") or {}).get("thesis")):
B.append(Block("heading", "summary", [r.t("In brief")]))
_bt = s_args.get("thesis") or args["pm"]["thesis"]
r.authored_seen.add(_bt)
B.append(Block("para", "summary", [_authored(_bt)]))
elif prose.get("summary"):
B.append(Block("heading", "summary", [r.t("In brief")]))
B.append(Block("para", "summary", prose["summary"]))
else:
# no authored summary survived ingestion: state the two value relations directly (template sentences built from
# the typed RelationshipClaims; nothing authored, nothing rewritten)
brief = [cid for cid in ("REL_PRICE_INTRINSIC__FULL_SENTENCE", "REL_PRICE_MARKET_COMPOSITE__FULL_SENTENCE") if cid in cat]
if brief:
B.append(Block("heading", "summary", [r.t("In brief")]))
frs = []
for i, cid in enumerate(brief):
frs += ([r.t(" ")] if i else []) + [r.c(cid)]
B.append(Block("para", "summary", frs))
_why = _admit(r, s_args.get("items_thesis"))
_wrong = _admit(r, s_args.get("items_risks"))
if _why:
B.append(Block("bullets", "summary", [_arg_item(r, s, cat, args, p) for p in _why], title="Why"))
elif prose.get("thesis"):
B.append(Block("bullets", "summary", prose["thesis"], title="Why"))
if _wrong:
B.append(Block("bullets", "summary", [_arg_item(r, s, cat, args, p) for p in _wrong],
title="What would make this wrong"))
elif prose.get("risks"):
B.append(Block("bullets", "summary", prose["risks"], title="What would make this wrong"))
# ── debate: every seat in full, the portfolio manager last ──
pm = (prose.get("seats") or {}).get("pm") or {}
seats = prose.get("seats") or {}
order = (("neutral", "Neutral analyst", "SCENARIO_NEUTRAL_RETURN", "SCENARIO_NEUTRAL_PROB"),
("bear", "Bear", "SCENARIO_BEAR_RETURN", "SCENARIO_BEAR_PROB"),
("bull", "Bull", "SCENARIO_BULL_RETURN", "SCENARIO_BULL_PROB"))
B.append(Block("heading", "debate", [r.t("The analyst debate")] +
([r.t(" · "), r.c("RANGE_LOW"), r.t(" to "), r.c("RANGE_HIGH")] if ("RANGE_LOW" in cat and "RANGE_HIGH" in cat) else [])))
def _notes(entry, title=None, opponent=None):
"""One seat's reasoning and key points; a title labels the round (opening case, rebuttal). A rebuttal point's
topic is, by the prompt's contract, the OPPOSING claim: it is labelled as the opponent's, never shown as if the
author asserted it."""
if entry.get("reasoning"):
B.append(Block("para", "debate", entry["reasoning"], title=title))
if entry.get("key_points"):
pts = entry["key_points"]
if opponent:
pts = [[Fragment(f"The {opponent}'s claim — ")] + list(kp) for kp in pts]
# headlines only: each point shows its topic, the full point opens on a tap
B.append(Block("points", "debate", pts, title=None if entry.get("reasoning") else title))
def _has(e):
return bool((e or {}).get("reasoning") or (e or {}).get("key_points"))
if any(k in args for k in ("neutral", "bear", "bull", "bear_open", "bull_open", "pm")):
_structured_debate(B, r, s, cat, args, seats, order, _notes)
order = () # rendered above (legacy seats included, in round order)
for role, name, ret_id, p_id in order:
st = seats.get(role) or {}
B.append(_para("debate", r.t(f"{name} — "), r.c(ret_id),
r.t(f" · {(s.scenarios.get(role) or {}).get('label') or ''} · probability "), r.c(p_id), css="seat-head"))
op = seats.get(f"{role}_open") or {}
if not (_has(op) or _has(st)):
# nothing this analyst wrote met the report's evidence standards: say so instead of an empty section
B.append(_para("debate", r.t(_empty_seat_note(s, role))))
elif op:
_notes(op, "Opening case")
_notes(st, "Rebuttal", opponent={"bear": "bull", "bull": "bear"}.get(role))
else:
_notes(st)
if order or not args: # otherwise the structured debate rendered the PM block
B.append(_para("debate", r.t("Portfolio manager — probability-weighted "), r.c("PM_RETURN"),
r.t(f" · {s.scenarios.get('pm_label') or ''}"), css="seat-head"))
if (order or not args) and all(c in cat for c in ("SCENARIO_BEAR_PROB", "SCENARIO_NEUTRAL_PROB", "SCENARIO_BULL_PROB",
"PM_RETURN_PHRASE")):
# the PM's decision, always stated from its typed claims (then any authored PM prose that survived ingestion)
B.append(_para("debate", r.t("The portfolio manager weights the bear case at "), r.c("SCENARIO_BEAR_PROB"),
r.t(", the neutral case at "), r.c("SCENARIO_NEUTRAL_PROB"), r.t(" and the bull case at "),
r.c("SCENARIO_BULL_PROB"), r.t(", for "), r.c("PM_RETURN_PHRASE"), r.t(".")))
if (order or not args) and (pm.get("reasoning") or pm.get("key_points")):
_notes(pm)
trg = s.scenarios.get("triggers") or []
if trg:
items = []
for i, t in enumerate(trg[:3]):
# the quantity is named by the claim the threshold is measured against (its reader label and today's value),
# never by the author's free "metric" string; a trigger with no resolvable source claim stays in QA
src = t.get("source_claim_id") or t.get("source_guidance_id")
subj = trigger_subject(s, cat, src)
if not subj:
continue
dest = "the bull case" if t.get("scenario_destination") == "BULL" else "the bear case"
verb = "rises to" if t.get("side") == "above" else "falls to"
thr = r.p(f"scenarios.triggers.{i}.threshold", t.get("threshold_kind") or C.TEXT, "trigger threshold (hypothetical)")
side = f" or {'above' if t.get('side') == 'above' else 'below'}"
cl = cat.get(src)
if getattr(cl, "stype", None) == C.GUIDANCE:
# measured against a guided range: the realised metric moves, the guidance is the reference
items.append([r.t(f"If {subj} {verb} "), thr, r.t(f"{side} (company guidance: "), r.c(src),
r.t(f"), the view moves toward {dest}.")])
elif set(getattr(C.claim_def(s, src), "prohibited", ()) or ()) & {"now", "current", "today"}:
items.append([r.t(f"If {subj} {verb} "), thr, r.t(f"{side}, the view moves toward {dest}.")])
else:
items.append([r.t(f"If {subj}, now "), r.c(src), r.t(f", {verb} "), thr,
r.t(f"{side}, the view moves toward {dest}.")])
if items:
B.append(Block("bullets", "debate", items, title="What would change the view"))
# ── valuation ──
B.append(Block("heading", "valuation", [r.t("Intrinsic value")] + ([r.t(" · "), r.c("INTRINSIC_VALUE")] if "INTRINSIC_VALUE" in cat else [])))
lc = s.valuation.get("lifecycle") or {}
if "LIFECYCLE_STAGE" in cat:
lp = [r.t("Life-cycle stage: "), r.c("LIFECYCLE_STAGE")]
# the two growth rates are shown together only when they describe comparable businesses (compatible())
_cmp = C.comparison(s, "LIFECYCLE_TRAILING_GROWTH", "LIFECYCLE_FORWARD_GROWTH")
if "LIFECYCLE_TRAILING_GROWTH" in cat and (_cmp is None or _cmp[0] or "LIFECYCLE_FORWARD_GROWTH" not in cat):
lp += [r.t(", with trailing revenue growth of "), r.c("LIFECYCLE_TRAILING_GROWTH")]
if "LIFECYCLE_FORWARD_GROWTH" in cat:
# named by the claim's own semantic label (its source and period): "forward revenue growth" alone read as the
# same quantity as the engine's next-twelve-month forecast growth, a different metric
import arguments as _A
_fg = _A._READER["LIFECYCLE_FORWARD_GROWTH"][0].strip()
lp += [r.t(f" and {_fg} of " if (lp[-1:] and len(lp) > 2) else f", with {_fg} of "),
r.c("LIFECYCLE_FORWARD_GROWTH")]
lp += [r.t(".")]
B.append(_para("valuation", *lp))
if "INTRINSIC_VALUE" in cat:
# the complete relations, rendered from their RelationshipClaims (subject, direction, size, object)
if "REL_PRICE_INTRINSIC" in cat:
vp = [r.c("REL_PRICE_INTRINSIC", cap=True), r.t(".")]
else:
vp = [r.t(f"The {iv_word} is "), r.c("INTRINSIC_VALUE"), r.t(".")]
if "REL_PRICE_MECHANICAL" in cat and not s.valuation.get("same_model"):
vp += [r.t(" "), r.c("REL_PRICE_MECHANICAL", cap=True), r.t(".")]
B.append(_para("valuation", *vp))
stories = prose.get("stories") or []
adopted = next((x for x in stories if x.get("adopted")), None)
if adopted and adopted.get("story"):
risk = ([r.t(" Key risk: ")] + list(adopted["risk"]) + [r.t(".")]) if adopted.get("risk") else []
B.append(Block("para", "valuation", list(adopted["story"]) + risk, title="The story behind the value"))
# what today's price requires, as typed relations: each break-even figure against the story figure it measures,
# the growth bound to its own pair (start → break-even revenue), and the computed expectations polarity
implied = [cid for cid in ("REL_BREAK_EVEN_REVENUE_STORY", "REL_BREAK_EVEN_REVENUE_GROWTH", "REL_BREAK_EVEN_MARGIN_STORY",
"REL_EXPECTATIONS_STORY") if cid in cat]
if implied:
B.append(Block("bullets", "valuation", [[r.c(cid, cap=True), r.t(".")] for cid in implied], title="What the price implies"))
dmi = s.valuation.get("damodaran_inputs") or {}
if dmi.get("available") and "DMD_ERP" in cat:
rows = []
if "DMD_INDUSTRY" in cat:
rows.append([[r.t("Industry (his classification)")], [r.c("DMD_INDUSTRY")]])
if "DMD_BETA" in cat:
basis = "business-mix" if dmi.get("business_mix") else "industry"
rows.append([[r.t("Beta, unlevered → levered" + (" (business mix)" if basis == "business-mix" else ""))],
[r.c("DMD_UNLEVERED_BETA"), r.t(" → "), r.c("DMD_BETA")]])
erp_cell = [r.c("DMD_ERP"), r.t(" (implied "), r.c("DMD_IMPLIED_ERP"), r.t(", "), r.c("DMD_ERP_MONTH")]
if (dmi.get("crp") or 0) > 0:
erp_cell += [r.t(", plus country risk premium "), r.c("DMD_CRP"), r.t(" for "), r.c("DMD_COUNTRY")]
rows.append([[r.t("Equity risk premium")], erp_cell + [r.t(")")]])
def _applicability(ind, own, own_words):
"""The industry figure beside the company's own when they differ by more than 2x either way: a benchmark
that does not describe the business is labelled as such, never presented as its norm. ``own`` is a claim id
or a (state path, stype, label) triple."""
iv = getattr(cat.get(ind), "value", None)
if isinstance(own, tuple):
ov = s.value(own[0])
own_frag = lambda: r.p(*own)
else:
ov = getattr(cat.get(own), "value", None)
own_frag = lambda: r.c(own)
try:
ratio = float(ov) / float(iv) if iv and ov else None
except (TypeError, ValueError):
ratio = None
if ratio is None or 0.5 <= ratio <= 2.0:
return [r.c(ind)]
return [r.c(ind), r.t(f" (not representative of this company: {own_words} "), own_frag(), r.t(")")]
if "DMD_S2C" in cat:
rows.append([[r.t("Industry sales-to-capital")],
_applicability("DMD_S2C", ("valuation.story.assumptions.sales_to_capital", C.RATIO, "sales-to-capital"),
"the story uses")])
if "DMD_MARGIN" in cat:
rows.append([[r.t("Industry operating margin")],
_applicability("DMD_MARGIN", "STORY_START_MARGIN", "the continuing company's starting margin is")])
if "WACC_BENCHMARK" in cat:
rows.append([[r.t("Mature-market cost-of-capital benchmark")], [r.c("WACC_BENCHMARK")]])
B.append(Block("table", "valuation", {"head": ["Input (Damodaran's data)", "Value"], "rows": rows},
title="Market and industry inputs"))
mix = dmi.get("business_mix") or []
if mix:
base = "valuation.damodaran_inputs.business_mix."
by_value = all(g.get("estimated_value") for g in mix)
head = ["Filed segment", "Share of revenue"] + (["Industry EV/Sales", "Weight (estimated value)"] if by_value else []) + \
["Industry (his)", "Unlevered beta"]
B.append(Block("table", "valuation", {"head": head,
"rows": [[[r.t(str(g.get("name")))],
[r.p(f"{base}{i}." + ("revenue_weight" if by_value else "weight"), C.PCT, "segment share of revenue")]]
+ ([[r.p(f"{base}{i}.industry_ev_sales", C.MULTIPLE, "segment industry EV/Sales")],
[r.p(f"{base}{i}.weight", C.PCT, "segment value weight")]] if by_value else [])
+ [[r.t(str(g.get("industry")))], [r.p(f"{base}{i}.unlevered_beta", C.RATIO, "segment industry unlevered beta")]]
for i, g in enumerate(mix)]},
title="Business mix"))
stry = s.valuation.get("story") or {}
if stry.get("ran"):
A = "valuation.story.assumptions."
rows = [[[r.t("Market size at maturity")], [r.c("STORY_TAM")]],
[[r.t("Peak market share")], [r.c("STORY_SHARE")]],
[[r.t("Revenue at maturity")], [r.c("STORY_REVENUE_MATURITY")]],
[[r.t("Years to maturity")], [r.c("STORY_YEARS")]],
] + ([[[r.t(str((s.valuation.get("story_base") or {}).get("revenue_label") or "Starting revenue").capitalize())],
[r.c("STORY_START_REVENUE")]]] if "STORY_START_REVENUE" in cat else []) + [
] + ([[[r.t(str((s.valuation.get("story_base") or {}).get("label") or "Starting operating margin").capitalize())],
[r.c("STORY_START_MARGIN")]]] if "STORY_START_MARGIN" in cat else []) + [
[[r.t("Operating margin at maturity (story assumption)")], [r.c("STORY_MARGIN")]],
[[r.t("Sales to capital")], [r.p(A + "sales_to_capital", C.RATIO, "sales-to-capital")]],
] + ([[[r.t("Initial cost of capital")], [r.c("STORY_WACC")]]] if "STORY_WACC" in cat else [])
# the discount-rate path shown is the vector the valuation used (valuation.discount_rate_path)
_dp = s.valuation.get("discount_rate_path") or {}
if "STORY_WACC_STABLE" in cat and "STORY_WACC_HOLD" in cat:
if abs((_dp.get("terminal_wacc") or 0) - (_dp.get("initial_wacc") or 0)) < 1e-9:
rows.append([[r.t("Cost of capital path")], [r.t("the initial rate in every year")]])
else:
rows.append([[r.t("Cost of capital path")], [r.c("STORY_WACC_HOLD"), r.t(" years at the initial rate, then a straight line over "),
r.p("valuation.discount_rate_path.transition_years", C.COUNT, "transition years"),
r.t(" years to the terminal rate")]])
rows.append([[r.t("Terminal cost of capital used")], [r.c("STORY_WACC_STABLE")]])
_yr = _dp.get("yearly_rates") or []
if _yr:
cell = []
for i in range(len(_yr)):
cell += ([r.t(" · ")] if i else []) + [r.p(f"valuation.discount_rate_path.yearly_rates.{i}", C.PCT, f"discount rate year {i + 1}")]
rows.append([[r.t("Discount rate by year")], cell])
rows += [[[r.t("Terminal growth")], [r.c("STORY_TERMINAL_GROWTH")]]]
if _dp.get("terminal_spread") is not None:
rows.append([[r.t("Terminal cost of capital less terminal growth")],
[r.p("valuation.discount_rate_path.terminal_spread", C.PP, "terminal spread")]])
rows += [[[r.t("Failure probability")], [r.c("STORY_FAILURE")]]]
B.append(Block("table", "valuation", {"head": ["Story assumption (adopted storyteller)", "Value"], "rows": rows},
title="Story assumptions"))
segs = stry.get("segments") or []
if segs:
SB = "valuation.story.segments."
_a0 = stry.get("assumptions") or {}
_capped = (_a0.get("margin_proposed") is not None and _a0.get("margin_target") is not None
and abs(float(_a0["margin_proposed"]) - float(_a0["margin_target"])) > 0.0005)
B.append(Block("table", "valuation", {"head": ["Business (adopted story)", "Revenue at maturity (USD bn)",
"Operating margin at maturity" + (" (storyteller's proposal)" if _capped else ""),
"Sales to capital"],
"rows": [[[r.t(str(g.get("name")))], [r.p(f"{SB}{i}.rev_usd_b", C.RATIO, "segment revenue at maturity (USD bn)")],
([r.p(f"{SB}{i}.margin", C.PCT, "segment maturity margin")] if g.get("margin") is not None else [r.t("n/a")]),
([r.p(f"{SB}{i}.sales_to_capital", C.RATIO, "segment sales-to-capital")] if g.get("sales_to_capital") else [r.t("story-level")])]
for i, g in enumerate(segs)]},
title="Segment build"))
_a = stry.get("assumptions") or {}
if _a.get("margin_proposed") is not None and _a.get("margin_target") is not None \
and abs(float(_a["margin_proposed"]) - float(_a["margin_target"])) > 0.0005:
_raised = _a.get("margin_floor") is not None and float(_a["margin_proposed"]) < float(_a["margin_target"])
B.append(_para("valuation", r.t("The segments' revenue-weighted operating margin at maturity ("),
r.p("valuation.story.assumptions.margin_proposed", C.PCT, "segment-weighted maturity margin"),
r.t(") is raised to " if _raised else ") is capped at "),
r.p("valuation.story.assumptions.margin_target", C.PCT, "maturity operating margin used"),
r.t(", the maturity operating margin the valuation uses"
+ (": a currently profitable company is not valued below the lower of today's margin and "
"the best sustained margin in its sector." if _raised else "."))))
srows = []
for i, x in enumerate(stry.get("stories") or []):
srows.append([[r.t(f"Storyteller {i + 1}" + (" (adopted)" if x.get("adopted") else "")
+ (f", {x['n_segments']} businesses" if x.get("n_segments") else ""))],
[r.p(f"valuation.story.stories.{i}.value", C.CURRENCY, "storyteller value")],
[r.p(f"valuation.story.stories.{i}.derived.margin_target", C.PCT, "storyteller maturity margin")],
[r.p(f"valuation.story.stories.{i}.derived.years", C.COUNT, "storyteller years")]])
if srows:
B.append(Block("table", "valuation", {"head": ["Storyteller", "Value", "Maturity margin", "Years"], "rows": srows},
title="The storytellers"))
items = [[r.t(f"Storyteller {i + 1}: ")] + x["story"] for i, x in enumerate(stories) if x.get("story")]
if len(items) > 1:
B.append(Block("bullets", "valuation", items, title="Their stories"))
div = s.valuation.get("storyteller_diversity") or {}
if div.get("status") == "DUPLICATE_STORY":
B.append(_para("valuation", r.t("Two storytellers used effectively identical assumptions, so the range reflects fewer "
"independent views than the number of storytellers.")))
sens = s.valuation.get("sensitivity") or []
if sens:
def _case(i, x):
if x.get("driver") and x.get("shift") is not None:
kind = C.PP if x.get("shift_kind") == "PP" else C.PCT_SIGNED
return [r.t(f"{x['driver']} "), r.p(f"valuation.sensitivity.{i}.shift", kind, "sensitivity shift")]
return [r.t(str(x.get("case")))]
B.append(Block("table", "valuation", {"head": ["Change", "Value", "Change vs base"],
"rows": [[_case(i, x),
[r.p(f"valuation.sensitivity.{i}.value", C.CURRENCY, "sensitivity value")],
[r.p(f"valuation.sensitivity.{i}.vs_base", C.PCT_SIGNED, "change vs base")]]
for i, x in enumerate(sens)]}, title="How sensitive is the value?"))
# ── pricing ──
B.append(Block("heading", "pricing", [r.t("Market pricing")] + ([r.t(" · "), r.c("MARKET_COMPOSITE")] if "MARKET_COMPOSITE" in cat else [])))
if "REL_PRICE_MARKET_COMPOSITE" in cat:
B.append(_para("pricing", r.c("REL_PRICE_MARKET_COMPOSITE", cap=True), r.t(".")))
meth = s.pricing.get("methods") or []
if meth:
B.append(Block("table", "pricing", {"head": ["Method", "Implied price", "Multiple", "Share of composite"],
"rows": [[[r.t(m.get("label") or m.get("name"))],
[r.p(f"pricing.methods.{i}.price", C.CURRENCY, "method price")],
([r.p(f"pricing.methods.{i}.target_multiple", C.MULTIPLE, "target multiple"),
r.t(_method_basis(m))]
if m.get("target_multiple") else [r.t("—")]),
[r.p(f"pricing.methods.{i}.share", C.PCT, "method share") if m.get("in_composite") else r.t("excluded")]]
for i, m in enumerate(meth)]}))
# raw → trim → cap → final, so the published composite can be reproduced from the table (shown when a step moved it)
steps = [[[r.t("Weighted average of the methods")], [r.c("MARKET_COMPOSITE_RAW")]]]
_moved = (s.value("pricing.raw_composite") is not None and s.value("pricing.market_composite") is not None
and abs(s.value("pricing.raw_composite") - s.value("pricing.market_composite")) > 0.005)
if s.value("pricing.post_trim_composite") is not None and s.value("pricing.raw_composite") is not None \
and abs(s.value("pricing.post_trim_composite") - s.value("pricing.raw_composite")) > 1e-6:
steps.append([[r.t("After dropping outlier methods")], [r.c("MARKET_COMPOSITE_POST_TRIM")]])
if (s.pricing.get("cap") or {}).get("applied"):
steps.append([[r.t("Bounded to "), r.p("pricing.cap.lower", C.MULTIPLE, "cap lower"), r.t("–"),
r.p("pricing.cap.upper", C.MULTIPLE, "cap upper"), r.t(" the price")], [r.c("MARKET_COMPOSITE")]])
else:
steps.append([[r.t("Market composite")], [r.c("MARKET_COMPOSITE")]])
if _moved:
B.append(Block("table", "pricing", {"head": ["Step", "Value"], "rows": steps}, title="How the composite is built"))
if not pol_methods or "P/E" in pol_methods: # an issuer whose policy excludes earnings multiples gets none
pe = [r.t("Forward P/E on the engine's EPS forecast: "), r.c("PE_FORWARD")] # whose EPS, never implied
if "PE_TRAILING" in cat:
pe += [r.t("; trailing P/E "), r.c("PE_TRAILING"), r.t(" on adjusted EPS and "), r.c("PE_TRAILING_GAAP"), r.t(" on reported EPS")]
elif sep:
pe += [r.t("; the company's own trailing multiples are not shown because its trailing earnings include the "
"separated business")]
if "PEG" in cat:
pe += [r.t("; forward PEG "), r.c("PEG")]
B.append(_para("pricing", *pe, r.t(".")))
if "ANALYST_TARGET" in cat:
at = [r.t("Mean sell-side price target "), r.c("ANALYST_TARGET"), r.t(" ("), r.c("TARGET_VS_PRICE"), r.t(" from the price)")]
if "ANALYST_TARGET_ADJUSTED" in cat and s.pricing.get("analyst_target_in_composite"):
at += [r.t("; it enters the composite at "), r.c("ANALYST_TARGET_ADJUSTED")]
B.append(_para("pricing", *at, r.t(".")))
peers = (s.pricing.get("peers") or {}).get("peers") or []
if peers:
prow = []
for i, pe in enumerate(peers[:8]):
base = f"pricing.peers.peers.{i}."
prow.append([[r.t(str(pe.get("ticker")))], [r.p(base + "pe", C.MULTIPLE, "peer P/E")],
[r.p(base + "ev_ebitda", C.MULTIPLE, "peer EV/EBITDA")], [r.p(base + "op_margin", C.PCT, "peer operating margin")],
[r.p(base + "rev_growth", C.PCT_SIGNED, "peer revenue growth")]])
sj = "pricing.peers.subject."
if (s.pricing.get("peers") or {}).get("subject"):
prow.insert(0, [[r.t(f"{s.identity.ticker} (subject)")], [r.p(sj + "pe", C.MULTIPLE, "subject P/E (peer panel)")],
[r.p(sj + "ev_ebitda", C.MULTIPLE, "subject EV/EBITDA")], [r.p(sj + "op_margin", C.PCT, "subject operating margin (peer panel)")],
[r.p(sj + "rev_growth", C.PCT_SIGNED, "subject revenue growth (peer panel)")]])
B.append(Block("table", "pricing", {"head": ["Company", "P/E (trailing)", "EV/EBITDA", "Operating margin", "Revenue growth"],
"rows": prow}, title="Relative to peers (Sharadar panel, trailing)"))
notes = []
if (s.pricing.get("peers") or {}).get("subject_withheld"):
notes.append(f"{s.identity.ticker}'s own row is not shown: {(s.pricing.get('peers') or {}).get('subject_withheld')}.")
subj = (s.pricing.get("peers") or {}).get("subject") or {}
wh = ([s.identity.ticker] if subj.get("multiples_withheld") else []) + \
[str(pe.get("ticker")) for pe in peers[:8] if pe.get("multiples_withheld")]
if wh:
notes.append("Valuation multiples are not shown for " + ", ".join(wh) + ": the panel reports them in a different currency.")
if notes:
B.append(_para("pricing", r.t(" ".join(notes))))
# ── fundamentals and forecast ──
B.append(Block("heading", "forecast", [r.t("Fundamentals and forecast")]))
rows = [[[r.t("Revenue")], [r.c("REVENUE_TTM")], [r.c("FORECAST_REVENUE")]],
[[r.t("Operating margin")], [r.c("OPERATING_MARGIN_TTM")], [r.c("FORECAST_OPERATING_MARGIN")]],
[[r.t("Net margin")], [r.c("NET_MARGIN_TTM")], [r.c("FORECAST_NET_MARGIN")]],
[[r.t("EPS (adjusted basis)")], [r.c("EPS_ADJUSTED_TTM")], [r.c("FORECAST_EPS")]]]
_hq, _fl = historical_quality_render(historical_quality(s), bool(sep)), forecast_lineage_render(s, bool(sep))
B.append(Block("table", "forecast", {"head": ["", _hq["table_header"], _fl["table_header"]], "rows": rows}, css="rp-fund"))
B.append(_para("forecast", r.t(_hq["footnote"]), css="rp-foot"))
B.append(_para("forecast", r.t(_fl["footnote"]), css="rp-foot"))
if sep:
B.append(_para("forecast", r.t(f"The trailing figures include a business separated from the company on {sep.get('effective')}. "
"The forecast and sell-side estimates describe the continuing company, so the two columns "
"describe different businesses: no growth rate is computed between them.")))
fc = (s.forecast.get("coherence") or {}).get("status")
words = {"CONSISTENT": "The revenue, earnings and operating-income models agree.",
"DIVERGENT": "The independent operating-income and net-income models diverge from the reconciled forecast; confidence is reduced.",
"CONFLICTING": "The independent operating-income and net-income models conflict with the reconciled forecast; treat the margin path as uncertain.",
"NOT_VERIFIABLE": "The forecast components could not be cross-checked against each other."}.get(fc)
if not sep and not s.issuer.get("is_financial") and fc in ("DIVERGENT", "CONFLICTING", "NOT_VERIFIABLE"):
B.append(_para("forecast", r.t(words))) # a caveat only; agreement needs no sentence
if "CONSENSUS_EPS_FY1" in cat and "REL_FORECAST_EPS_CONSENSUS" in cat:
B.append(_para("forecast", r.t("Sell-side consensus EPS is "), r.c("CONSENSUS_EPS_FY1"), r.t(" for the current fiscal year ("),
r.c("CONSENSUS_ANALYSTS"), r.t(" contributors). "), r.c("REL_FORECAST_EPS_CONSENSUS", cap=True), r.t(".")))
elif "CONSENSUS_EPS_FY1" in cat:
B.append(_para("forecast", r.t("Sell-side consensus EPS is "), r.c("CONSENSUS_EPS_FY1"), r.t(" for the current fiscal year ("),
r.c("CONSENSUS_ANALYSTS"), r.t(" contributors). "
+ consensus_absence_sentence(s, "REL_FORECAST_EPS_CONSENSUS", "EPS"))))
if "REL_ENGINE_REVENUE_CONSENSUS" in cat:
B.append(_para("forecast", r.c("REL_ENGINE_REVENUE_CONSENSUS", cap=True), r.t(".")))
# company guidance against consensus: only as its typed relation (same fiscal period and horizon, compatible basis)
if "REL_GUIDANCE_EPS_CONSENSUS" in cat:
B.append(_para("forecast", r.c("REL_GUIDANCE_EPS_CONSENSUS", cap=True), r.t(".")))
# company guidance (its own release): company-level rows of the headline concepts, each value a verified claim
g = s.guidance.get("company") or {}
if g.get("status") == "AVAILABLE":
# EVERY verified company-level row: a guidance value an author cites must also be in the table
rows_g = guidance_table_rows(s, cat)
if rows_g:
src = g.get("source") or {}
# one row per guided figure: a GAAP and a non-GAAP row with the same label, period and value are one fact
# printed once with both bases (the table repeated four such pairs)
merged = {}
for i, m in rows_g:
_cl = cat.get(f"GUIDANCE_{i}")
key = (str(m.get("label")), str(m.get("period_label")), str(_cl.rendered) if _cl is not None else f"#{i}")
merged.setdefault(key, (i, m, []))[2].append(
{"GAAP": "GAAP", "ADJUSTED": "non-GAAP"}.get(m.get("accounting_basis"), "—"))
rows_out = []
for (_lab, _per, _v), (i, m, bases) in merged.items():
bs = [b for b in dict.fromkeys(bases) if b != "—"]
rows_out.append([[r.t(str(m.get("label")))], [r.t(str(m.get("period_label")))], [r.c(f"GUIDANCE_{i}")],
[r.t(" and ".join(bs) if bs else "—")]])
B.append(Block("table", "forecast", {"head": ["Company guidance", "Period", "Guided", "Basis"], "rows": rows_out},
title=f"Company guidance ({src.get('form')} filed {src.get('filed')})"))
ins = s.insiders.get("canon") or {}
if ins.get("characterization"):
B.append(_para("forecast", r.t("Insider transactions (SEC Form 4): "), r.c("INSIDER_CHARACTERIZATION"), r.t(".")))
if s.ownership.get("status") == "CURRENT" and "OWN_13F_TRACKED" in cat:
prev = [r.t(", against "), r.c("OWN_13F_HOLDERS_PREV"), r.t(" the quarter before")] if "OWN_13F_HOLDERS_PREV" in cat else []
B.append(_para("forecast", r.t("Institutional ownership (SEC Form 13F, quarter ended "), r.c("OWN_13F_AS_OF"), r.t("): "),
r.c("OWN_13F_HOLDERS"), r.t(" institutions reported a position"), *prev, r.t(". "), r.c("OWN_13F_TRACKED"), r.t(" tracked long-term investors hold the stock, including "),
r.c("OWN_13F_NAMES"), r.t("; last quarter "), r.c("OWN_13F_NEW"), r.t(" opened a position, "), r.c("OWN_13F_ADDS"),
r.t(" added, "), r.c("OWN_13F_TRIMS"), r.t(" trimmed and "), r.c("OWN_13F_EXITS"),
r.t(" exited.")))
if (s.leadership.get("roles") or {}).get("CEO"):
B.append(_para("forecast", r.t("Chief executive: "), r.c("CEO"), r.t(".")))
# ── blend ──
B.append(Block("heading", "blend", [r.t("What drives the headline")]))
src = s.blend.get("sources") or []
if src:
B.append(Block("table", "blend", {"head": ["Source", "View", "Share of the blend"],
"rows": [[[r.t(str(x.get("label") or x.get("name")))],
[r.p(f"blend.sources.{i}.return", C.PCT_SIGNED, "source view")],
[r.p(f"blend.sources.{i}.share", C.PCT, "source share")]] for i, x in enumerate(src)]}))
for i, a in enumerate(s.blend.get("adjustments") or ()):
B.append(_para("blend", r.t(f"Adjustment applied to the weighted sum — {a.get('name')}: ×"),
r.p(f"blend.adjustments.{i}.factor", C.RATIO, "blend adjustment factor"), r.t(".")))
exc = [str(x.get("name")) for x in (s.blend.get("excluded_sources") or ())
if x.get("name") and x.get("kind") != "PRICE_MODEL"] # the ML lines below cover the price model
if exc:
B.append(_para("blend", r.t("Carrying no weight in this analysis: " + ", ".join(exc) + ".")))
B += _ml_paragraphs(s, r, cat)
# ── limitations and sources (plain language only) ──
B.append(Block("heading", "limits", [r.t("Sources and limitations")]))
if limitations:
B.append(Block("bullets", "limits", [[r.t(x)] for x in limitations]))
B.append(_para("limits", r.t(f"Statements through {s.historical.get('statements_as_of')} · price as of {s.market.get('price_as_of')} · "
"not investment advice.")))
B.append(_para("limits", r.t("Analysis version "), Fragment(s.version, "VERSION", s.version), r.t("."), css="version"))
claims = {cid: cl.to_dict() for cid, cl in r.used.items()}
# claims that arrived inside ingested prose are registered too: every rendered claim is listed
for b in B:
for f in b.fragments():
if f.claim_id and f.claim_id not in claims and f.claim_id in cat:
claims[f.claim_id] = cat[f.claim_id].to_dict()
claims["VERSION"] = {"claim_id": "VERSION", "path": "version", "stype": "TEXT", "label": "analysis version",
"value": s.version, "rendered": s.version}
return UserReport(s.identity.ticker, s.identity.get("company_name"), s.identity.get("as_of"), s.version, B, claims, limitations,
arguments=dict(prose.get("arguments") or {}))
# blend source labels come from the typed BlendSource kind (state_builder.BLEND_KIND_LABEL), never from a legacy name
def _ml_paragraphs(s, r, cat):
"""Machine learning, in plain words. Two models are checked on every run; each counts only as far
as its out-of-sample record allows. A model that is not admitted is described without its figures."""
pm = (s.quant.get("price_model") or {}).get("reliability") or {}
pr = (s.quant.get("panel_rank") or {}).get("reliability") or {}
out = []
if pr.get("persona_visibility") and "ML_RANK_SHARE" in cat:
out.append(_para("blend", r.t("Machine-learning rank: above "), r.c("ML_RANK_SHARE"), r.t(" of the "), r.c("ML_RANK_UNIVERSE"),
r.t(" largest US companies (decile "), r.c("ML_RANK_DECILE"), r.t("); walk-forward rank correlation "),
r.c("ML_RANK_IC"), r.t(". Weak evidence only.")))
elif pr.get("state") == "UNRELIABLE":
why = ("failed in the most recent test period" if pr.get("failed_recently") else "out-of-sample record too weak")
out.append(_para("blend", r.t(f"Machine-learning rank: not used ({why}).")))
if pm.get("state") in ("LIMITED", "VALIDATED"):
out.append(_para("blend", r.t("Machine-learning price model: " + ("used with a capped share (limited out-of-sample record)."
if pm.get("state") == "LIMITED" else "used (validated out of sample)."))))
elif pm.get("state") in ("UNRELIABLE", "UNAVAILABLE"):
out.append(_para("blend", r.t("Machine-learning price model: not used ("
+ ("direction calls no better than chance)." if pm.get("state") == "UNRELIABLE"
else "not enough history to test)."))))
return out
# ═══════════════════════════════════════════════════════════════════════════════════════
# serialisation: both formats walk the same blocks
# ═══════════════════════════════════════════════════════════════════════════════════════
def _frag_md(f):
return f.text
def _frag_html(f):
t = _html.escape(f.text)
return f'<span class="cl" data-claim="{_html.escape(f.claim_id)}">{t}</span>' if f.claim_id else t
def render_markdown(rep: UserReport):
L = []
for b in rep.blocks:
if b.kind == "heading":
L.append(("# " if b.section == "verdict" else "## ") + "".join(_frag_md(f) for f in b.content))
elif b.kind in ("para", "callout"):
txt = "".join(_frag_md(f) for f in b.content)
if b.title:
L.append(f"*{b.title}*")
L.append(f"**{txt}**" if b.css in ("verdict", "seat-head") else txt)
elif b.kind in ("bullets", "points"): # the download keeps every point in full
if b.title:
L.append(f"**{b.title}**")
L.extend("- " + "".join(_frag_md(f) for f in item) for item in b.content)
elif b.kind == "tiles":
for row in b.content.get("rows", []):
lab, val, note = ("".join(_frag_md(f) for f in cell) for cell in (row + [[], [], []])[:3])
L.append(f"- **{lab}:** {val}" + (f" ({note})" if note else ""))
elif b.kind == "table":
if b.title:
L.append(f"**{b.title}**")
head = b.content.get("head", [])
L.append("| " + " | ".join(head) + " |")
L.append("|" + "---|" * len(head))
for row in b.content.get("rows", []):
L.append("| " + " | ".join("".join(_frag_md(f) for f in cell) for cell in row) + " |")
L.append("")
return "\n".join(L).strip() + "\n"
# ── structured arguments (engine/arguments.py): view first, every figure rendered from its typed claim ──────────
HEAD_END = "head_end" # sentinel fragment origin: headline before it, evidence after it
_SEAT_NAMES = {"neutral": "neutral analyst", "bear": "bear", "bull": "bull"}
def _authored(text):
return Fragment(str(text), origin="authored")
QUOTE = "quote" # an authored text quoted by another author (a rebuttal quotes its target)
def trigger_subject(s, cat, cid):
"""The reader's name for the quantity a trigger watches: its source claim's label (a guidance row by its own label,
period and basis). None when the claim is not in the catalog."""
cl = cat.get(cid) if cid else None
if cl is None or cl.rendered is None:
return None
if getattr(cl, "stype", None) == C.GUIDANCE:
# the realised metric named by the guidance row's own label — the same words the guidance table prints, so a
# scope qualifier ("excluding OSTTRA") is never lost (the concept alone labelled the ex-OSTTRA range as the plain
# adjusted margin change, run be9ce3de408e3216) — with its period and basis, each written once
import guidance as _G
v = cl.value if isinstance(cl.value, dict) else {}
rows = ((s.guidance.get("company") or {}).get("metrics")) or []
try:
label = str((rows[int(str(cid).split("_")[1]) - 1] or {}).get("label") or "").strip()
except (ValueError, IndexError):
label = ""
label = label or _G.concept_label(v.get("concept"))
basis = C.guidance_basis_label(cat, cid)
basis = None if (basis and basis.lower() in label.lower()) else basis
qual = ", ".join(x for x in (v.get("period_label"), basis) if x)
return label + (f" ({qual})" if qual else "")
label = re.sub(r"\s*\((?:complete relation|[^)]*form:[^)]*)\)", "", str(cl.label or "")).strip()
import arguments as _A
note = _A._perimeter_note(s) if cid in _A._TRAILING else None # defensive: typed triggers refuse these sources
return (("the " + label) + (f" ({note})" if note else "")) if label else None
def _rels(s):
return {rid: {"lhs": x.get("lhs_claim_id"), "rhs": x.get("rhs_claim_id"), "magnitude_claim": x.get("presentation_claim_id"),
"type": x.get("relation_type")}
for rid, x in (((s.get("relations.state") or {}).get("items")) or {}).items()}
def _arg_item(r, s, cat, args, p, head=None):
"""One structured point / item: [head…, view, HEAD_END, " — ", evidence…]. Evidence the report has already printed
in full is printed compactly (the report's evidence registry)."""
import arguments as A
r.authored_seen.add(p["view"])
frs = list(head or []) + [_authored(p["view"]), Fragment("", origin=HEAD_END)]
ev = A.render_evidence(r, p.get("evidence") or [], s, cat, _rels(s), seen=r.seen_evidence)
return frs + ([Fragment(" — ")] + ev if ev else [])
def _fresh(r, text):
"""True the first time an authored text is printed in this report (a text is never printed twice)."""
return bool(text) and text not in r.authored_seen
def _admit(r, objs):
"""The objects whose text has not been printed yet, each registered as it is admitted (two identical views in
one list print once)."""
out = []
for p in objs or []:
if _fresh(r, p.get("view")):
r.authored_seen.add(p["view"])
out.append(p)
return out
def _stance_text(role, stance, label):
who = _SEAT_NAMES.get(role, role)
if stance == "REVISED_LABEL":
return f"The {who} changes its rating after the exchange, to {label}." if label else f"The {who} changes its rating after the exchange."
if stance == "REVISED_RETURN":
return f"The {who} revises its return after the exchange; its rating stays {label}." if label else \
f"The {who} revises its return after the exchange."
return f"The {who} holds its case."
def _empty_seat_note(s, role):
"""A seat with no publishable reasoning: whose the return and the probability are, and what the portfolio manager
knew when it set the probability (scenarios.pm_evidence_state, recorded before the PM round)."""
st = (s.scenarios.get("pm_evidence_state") or {}).get(role) or {}
told = ", who was told this seat had no accepted supporting arguments" if st.get("accepted") == 0 else ""
return ("This analyst's written reasoning is not shown: none of it met the report's evidence standards. The scenario "
f"return is the analyst's own; its probability was assigned by the portfolio manager{told}.")
def _structured_debate(B, r, s, cat, args, seats, order, _notes):
"""The debate in round order: opening cases, rebuttals (each answer quotes the point it answers), the decision."""
import arguments as A
def opening(role):
o = args.get(f"{role}_open")
if o is None and args.get(role) is not None and "answers" not in args[role]:
o = args[role]
return o
for role, name, ret_id, p_id in order:
if role not in seats and opening(role) is None:
continue
B.append(_para("debate", r.t(f"{name} — "), r.c(ret_id),
r.t(f" · {(s.scenarios.get(role) or {}).get('label') or ''} · probability "), r.c(p_id), css="seat-head"))
o = opening(role)
if o is not None:
th = o.get("thesis") if _fresh(r, o.get("thesis")) else None
if th:
r.authored_seen.add(th)
pts = _admit(r, o.get("points"))
if th:
B.append(Block("para", "debate", [_authored(th)], title="Opening case" if role != "neutral" else None))
if pts:
B.append(Block("points", "debate", [_arg_item(r, s, cat, args, p) for p in pts],
title=None if th else ("Opening case" if role != "neutral" else None)))
if not o.get("thesis") and not o.get("points"):
B.append(_para("debate", r.t(_empty_seat_note(s, role))))
elif not th and not pts:
B.append(_para("debate", r.t("This analyst's validated points are printed above.")))
else: # a seat that ignored the schema: its legacy prose
leg = seats.get(f"{role}_open") or seats.get(role) or {}
if leg.get("reasoning") or leg.get("key_points"):
_notes(leg)
else:
B.append(_para("debate", r.t(_empty_seat_note(s, role))))
replies = [(role, opp) for role, opp in (("bear", "bull"), ("bull", "bear"))
if (args.get(role) or {}).get("answers") is not None and f"{role}_open" in args]
for role, opp in replies:
a = args[role]
B.append(_para("debate", r.t(f"The {role} replies to the {opp}"), css="seat-head"))
B.append(_para("debate", r.t(_stance_text(role, a.get("stance"), (s.scenarios.get(role) or {}).get("label")))))
if _fresh(r, a.get("thesis")):
r.authored_seen.add(a["thesis"])
B.append(Block("para", "debate", [_authored(a["thesis"])]))
items = []
for x in a.get("answers") or []:
if not _fresh(r, x.get("view")):
continue
head = [Fragment(f"On the {opp}'s point “"), Fragment(str(x["to_view"]), origin=QUOTE),
Fragment(f"” — {A.VERDICT_CHIP.get(x['verdict'], x['verdict'])}: ")]
items.append(_arg_item(r, s, cat, args, x, head=head))
if items:
B.append(Block("points", "debate", items))
pmx = args.get("pm")
B.append(_para("debate", r.t("Portfolio manager — probability-weighted "), r.c("PM_RETURN"),
r.t(f" · {s.scenarios.get('pm_label') or ''}"), css="seat-head"))
# the decision is always stated from its typed claims, whatever authored text survived
if all(c in cat for c in ("SCENARIO_BEAR_PROB", "SCENARIO_NEUTRAL_PROB", "SCENARIO_BULL_PROB", "PM_RETURN_PHRASE")):
B.append(_para("debate", r.t("The portfolio manager weights the bear case at "), r.c("SCENARIO_BEAR_PROB"),
r.t(", the neutral case at "), r.c("SCENARIO_NEUTRAL_PROB"), r.t(" and the bull case at "),
r.c("SCENARIO_BULL_PROB"), r.t(", for "), r.c("PM_RETURN_PHRASE"), r.t(".")))
if pmx and (pmx.get("thesis") or pmx.get("points")):
if _fresh(r, pmx.get("thesis")):
r.authored_seen.add(pmx["thesis"])
B.append(Block("para", "debate", [_authored(pmx["thesis"])]))
pts = _admit(r, pmx.get("points"))
if pts:
B.append(Block("points", "debate", [_arg_item(r, s, cat, args, p) for p in pts], title="Deciding factors"))
elif (seats.get("pm") or {}).get("reasoning") or (seats.get("pm") or {}).get("key_points"):
_notes(seats["pm"])
def guidance_table_rows(s, cat):
"""[(n, row)] of the company-level guidance rows the report prints (segment-named rows are left out). A guidance
value an author cites must be one of these."""
g = s.guidance.get("company") or {}
if g.get("status") != "AVAILABLE":
return []
segn = {str(x).lower() for sg in ((s.segments.get("canonical") or {}).get("segments") or [])
for x in [sg.get("name")] + [p.get("name") for p in sg.get("product_lines") or []] if x}
return [(i, m) for i, m in enumerate(g.get("metrics") or [], 1)
if f"GUIDANCE_{i}" in cat and not any(n and n in str(m.get("label", "")).lower() for n in segn)]
def _split_topic(frags):
"""("Topic", rest) for a key point written as "Topic: point" (the analysts' format), else (None, frags)."""
if frags and not frags[0].claim_id:
t = frags[0].text
k = t.find(": ")
if 0 < k <= 80 and (t[k + 2:].strip() or len(frags) > 1):
return t[:k], ([Fragment(t[k + 2:])] if t[k + 2:] else []) + list(frags[1:])
return None, frags
OPEN_SECTIONS = ("verdict", "summary") # shown in full; every other section is a collapsed, expandable panel
HEADLINE_SECTIONS = ("valuation",) # inside these, every titled part shows its title and opens on a tap
def render_html(rep: UserReport):
H = [f'<article class="crucible-report" data-version="{_html.escape(rep.version)}">']
cur, fold = None, False
for b in rep.blocks:
if b.section != cur:
if cur is not None:
H.append("</details>" if fold else "</section>")
fold = b.section not in OPEN_SECTIONS and b.kind == "heading"
H.append(f'<details class="rp-sec rp-more rp-{b.section}">' if fold else f'<section class="rp-sec rp-{b.section}">')
cur = b.section
if b.kind == "heading":
tag = "h1" if b.section == "verdict" else "h2"
inner = f"<{tag}>" + "".join(_frag_html(f) for f in b.content) + f"</{tag}>"
H.append(f"<summary>{inner}</summary>" if (fold and tag == "h2") else inner)
elif b.kind == "points":
if b.title:
H.append(f"<h3>{_html.escape(b.title)}</h3>")
items = []
for item in b.content:
k = next((i for i, f in enumerate(item) if f.origin == HEAD_END), None)
if k is not None: # structured: the view is the headline, the evidence opens on a tap
hh = "".join(_frag_html(f) for f in item[:k])
rest = [f for f in item[k + 1:]]
if rest and not rest[0].claim_id and rest[0].text.strip() == "—":
rest = rest[1:]
body = "".join(_frag_html(f) for f in rest)
items.append(f"<li><details><summary>{hh}</summary><p>{body}</p></details></li>" if body.strip()
else f"<li>{hh}</li>")
continue
topic, rest = _split_topic(item)
if topic:
items.append(f'<li><details><summary>{_html.escape(topic)}</summary><p>'
+ "".join(_frag_html(f) for f in rest) + "</p></details></li>")
else:
items.append("<li>" + "".join(_frag_html(f) for f in item) + "</li>")
H.append('<ul class="rp-points">' + "".join(items) + "</ul>")
elif b.kind == "tiles":
cells = []
for row in b.content.get("rows", []):
lab, val, note = ("".join(_frag_html(f) for f in cell) for cell in (row + [[], [], []])[:3])
cells.append(f'<div class="rp-tile"><div class="rp-tile-l">{lab}</div><div class="rp-tile-v">{val}</div>'
f'<div class="rp-tile-n">{note}</div></div>')
H.append('<div class="rp-tiles">' + "".join(cells) + "</div>")
elif b.kind in ("para", "callout", "bullets", "table") and b.title and b.section in HEADLINE_SECTIONS:
H.append(f'<details class="rp-sub"><summary>{_html.escape(b.title)}</summary>')
H.append(_block_body(b))
H.append("</details>")
elif b.kind in ("para", "callout"):
if b.title:
H.append(f'<h4 class="rp-round">{_html.escape(b.title)}</h4>')
H.append(_block_body(b))
elif b.kind == "bullets":
if b.title:
H.append(f"<h3>{_html.escape(b.title)}</h3>")
H.append(_block_body(b))
elif b.kind == "table":
if b.title:
H.append(f"<h3>{_html.escape(b.title)}</h3>")
H.append(_block_body(b))
if cur is not None:
H.append("</details>" if fold else "</section>")
H.append("</article>")
return "\n".join(H)
def _block_body(b):
"""A paragraph, list or table without its title."""
if b.kind in ("para", "callout"):
return f'<p class="{b.css or ""}">' + "".join(_frag_html(f) for f in b.content) + "</p>"
if b.kind == "bullets":
return "<ul>" + "".join("<li>" + "".join(_frag_html(f) for f in item) + "</li>" for item in b.content) + "</ul>"
if b.kind == "table":
heads = b.content.get("head", [])
head = "".join(f"<th>{_html.escape(h)}</th>" for h in heads)
# each cell carries its column name, so a phone can show a row as a labelled card
rows = "".join("<tr>" + "".join(f'<td data-label="{_html.escape(heads[j] if j < len(heads) else "")}">'
+ "".join(_frag_html(f) for f in cell) + "</td>" for j, cell in enumerate(row)) + "</tr>"
for row in b.content.get("rows", []))
kv = " rp-kv" if len(b.content.get("head", [])) == 2 else "" # label/value tables stack on phones
css = f" {b.css}" if b.css else ""
return f'<div class="rp-scroll"><table class="rp-t{kv}{css}"><thead><tr>{head}</tr></thead><tbody>{rows}</tbody></table></div>'
return ""
def _method_basis(m):
"""What a composite multiple is applied to, and the basis it was set on: a target multiple set on a trailing basis
and applied to next-twelve-month figures is not the company's trailing multiple."""
raw = str(m.get("raw_input_basis") or "").lower()
period = ("next-12-month " if ("ntm" in raw or "next" in raw) else "forecast " if "forecast" in raw
else "trailing " if ("ttm" in raw or "trailing" in raw) else "")
noun = ("adjusted EPS" if "adjusted eps" in raw else "EPS" if "eps" in raw else "free cash flow per share" if "fcf" in raw
else "revenue per share" if "revenue" in raw else "ebitda" if "ebitda" in raw else None)
set_on = {"TRAILING_ADJUSTED": "trailing", "TRAILING": "trailing", "TRAILING_GAAP": "trailing",
"FORWARD_ADJUSTED": "forward"}.get(m.get("multiple_basis"))
if noun is None:
return _BASIS_WORDS.get(m.get("multiple_basis"), "")
# whose figure: the engine's reconciled forecast (the P/E leg applies the multiple to the engine's EPS, 7.8% above
# consensus in the SPGI runs, which a reader could not tell from "next-12-month adjusted EPS")
whose = "the engine's " if ("reconciledforecaststate" in raw or raw.startswith("forecast")) else ""
return f" on {whose}{period}{noun}" + (f" (multiple set on a {set_on} basis)" if set_on and not period.startswith(set_on) else "")
_BASIS_WORDS = {"TRAILING_ADJUSTED": " trailing, on adjusted EPS", "FORWARD_ADJUSTED": " forward, on adjusted EPS",
"TRAILING": " trailing", "TRAILING_GAAP": " trailing, on reported EPS"}
_SPAN = re.compile(r'<span class="cl" data-claim="([^"]+)">(.*?)</span>')
# internal QA vocabulary. A check id (A14, S3, F6 …) counts only next to QA words: "A14" is a TSMC process node and
# "S3" an AWS service, so a bare id in persona prose is ordinary text
_QA_ID = r"[ADFXRS]\d{1,2}"
QA_VOCAB = re.compile(r"\bSEMANTIC_[A-Z_]+|\bFINAL_RENDER\w*|publication[- ](?:blocked|ready)|\bStage [AB]\b|"
r"\bvalidator\b|(?-i:\bPASS\b|\bFAIL\b)|NOT_APPLICABLE|"
rf"\b(?:check|checks|gate|audit|rule|validation)\s+{_QA_ID}\b|"
rf"\b{_QA_ID}\s+(?:check|gate|audit|passed|failed|warning|blocked|blocking)\b", re.I)
# ═══════════════════════════════════════════════════════════════════════════════════════
# REPORT_VALIDATED: the audit runs on the delivered fragments and bytes
# ═══════════════════════════════════════════════════════════════════════════════════════
_IDENT = IDENTIFIER
def audit(rep: UserReport, state, md: str, html_doc: str, quant_hidden_values=()):
from validation import FAIL, NA_, PASS, CheckResult, ValidationResult, _run # noqa: F401
frags = rep.fragments()
def numbers():
bad = [f.text for f in frags if not f.claim_id and MATERIAL_NUMBER.search(BENIGN.sub(" ", f.text))]
return (FAIL, f"{len(bad)} material number(s) outside a claim: {bad[:3]}") if bad else \
(PASS, f"every material number is one of {len(rep.claims)} registered claims")
def values():
bad = []
for f in frags:
if not f.claim_id or f.claim_id == "VERSION":
continue
cd = C.claim_def(state, f.claim_id)
if cd is not None:
want = C.claim_for(state, cd).rendered
elif f.claim_id.startswith("P:"):
want = C.path_claim(state, f.claim_id[2:], rep.claims[f.claim_id]["stype"], "").rendered
else:
bad.append(f"{f.claim_id}: unknown claim")
continue
if want != f.text and not (cd is not None and (cd.stype == C.REL or cd.clause) and want and f.text == want[0].upper() + want[1:]):
bad.append(f"{f.claim_id}: shows '{f.text}', state gives '{want}'")
vf = [f for f in frags if f.claim_id == "VERSION"]
if not vf or any(f.text != state.version for f in vf):
bad.append("the document does not carry the frozen state's version")
return (FAIL, "; ".join(bad[:4])) if bad else (PASS, "every claim fragment equals its value in the frozen state")
def identity():
from prose import label_violations
bad = []
for unit in (u for b in rep.blocks for u in b.units()):
bad += [f"{cid} labelled '{w}'" for cid, w in
label_violations(unit, lambda c: getattr(C.claim_def(state, c), "prohibited", ()))]
return (FAIL, "; ".join(bad[:4])) if bad else (PASS, "no claim carries a label its type forbids")
def terminology():
terms = (state.valuation.get("engine") or {}).get("forbidden_renderer_terms") or []
bad = set()
for unit in (u for b in rep.blocks for u in b.units()):
for sent in re.split(r"(?<=[.!?])\s+", "".join(f.text for f in unit).lower()):
for t in terms:
# a sentence that states the method does not apply is not using it
if re.search(r"\b" + re.escape(t.lower()) + r"(?![a-z])", sent) and not DENIAL.search(sent):
bad.add(t)
return (FAIL, f"forbidden valuation terms present: {sorted(bad)}") if bad else (PASS, "valuation vocabulary is the selected engine's")
def no_qa():
text = "".join(f.text for f in frags if not f.claim_id)
m = QA_VOCAB.search(text)
return (FAIL, f"internal QA vocabulary in the user report: '{m.group(0)}'") if m else (PASS, "no internal QA vocabulary")
def quant_hidden():
leaks = []
pm_vis = (((state.quant.get("price_model") or {}).get("reliability") or {}).get("persona_visibility"))
pr_vis = (((state.quant.get("panel_rank") or {}).get("reliability") or {}).get("persona_visibility"))
for f in frags:
if f.claim_id and f.claim_id.startswith("P:quant.") and not (pm_vis if "price_model" in f.claim_id else pr_vis):
leaks.append(f.claim_id)
for i, src in enumerate(state.blend.get("sources") or ()):
if src.get("kind") == "PRICE_MODEL" and not pm_vis:
leaks.append(f"blend source {src.get('name')}")
prose_text = "".join(f.text for f in frags if not f.claim_id)
leaks += [v for v in quant_hidden_values if v and v in prose_text]
return (FAIL, f"an unreliable model's figures appear: {leaks}") if leaks else (PASS, "unreliable models contribute no user-visible figure")
def same_bytes():
spans = _SPAN.findall(html_doc)
want = [(f.claim_id, _html.escape(f.text)) for f in frags if f.claim_id]
if spans != want:
return FAIL, f"HTML claim spans ({len(spans)}) differ from the report fragments ({len(want)})"
if render_markdown(rep) != md:
return FAIL, "markdown is not the serialisation of the audited fragments"
return PASS, f"HTML and markdown carry the same {len(want)} claim fragments"
def required():
need = ["RATING", "HEADLINE_RETURN", "PRICE", "PM_RETURN", "VERSION"]
need += [cid for cid, path in (("MARKET_COMPOSITE", "pricing.market_composite"), ("FORECAST_EPS", "forecast.eps"))
if state.value(path) is not None]
if state.value("valuation.intrinsic_value") is not None:
need.append("INTRINSIC_VALUE")
if state.value("consensus.eps_fy1") is not None:
need.append("CONSENSUS_EPS_FY1")
have = {f.claim_id for f in frags if f.claim_id}
miss = [x for x in need if x not in have]
return (FAIL, f"required claims not rendered: {miss}") if miss else (PASS, f"{len(need)} required claims rendered")
# ── S9–S12: the semantic permissions hold in the delivered text, not only at ingestion ──
from prose import guidance_violation, motive_violation, qualifier_violation, SENT
def _sentences():
for b in rep.blocks:
for unit in b.units():
if any(f.origin in ("authored", HEAD_END, QUOTE) for f in unit):
continue # structured arguments: audited by STRUCTURED_ARGUMENTS with the typed validators
cur = []
for f in unit:
cur.append(f)
if not f.claim_id and re.search(r"[.!?](\s|$)", f.text):
yield cur
cur = []
if cur:
yield cur
def guidance_evidence():
bad = ["".join(f.text for f in u)[:120] for u in _sentences() if guidance_violation(u)]
g = (state.guidance.get("company") or {}).get("status")
return (FAIL, f"guidance described without a guidance claim: {bad[:2]}") if bad else \
(PASS, f"company guidance {g}; every mention cites a verified guidance claim")
def _motive(kind):
bad = []
for u in _sentences():
mv = motive_violation("".join(f.text for f in u))
if mv and mv[0] == kind:
bad.append("".join(f.text for f in u)[:120])
return (FAIL, f"motive attributed to {kind} filings: {bad[:2]}") if bad else (PASS, f"no motive inferred from {kind} filings")
def _pairs_ok():
from prose import implicit_comparison_violation, ordering_violation
_cat = C.build_catalog(state, include_deferred=False)
cp, ds = C.implicit_counterparts(state, _cat), C.claim_descriptors(state, _cat)
bad = []
for u in _sentences():
tok = "".join(f"[[{f.claim_id}]]" if (f.claim_id and not f.claim_id.startswith("P:")) else f.text for f in u)
cv = implicit_comparison_violation(tok, cp, ds) or ordering_violation(tok, lambda c: C.claim_range(state, c))
if cv:
bad.append(f"{cv[0]} vs {cv[1]}: {'; '.join(cv[2])[:80]}")
return (FAIL, f"incompatible comparisons in the report: {bad[:2]}") if bad else (PASS, "no stated ordering contradicts the values")
# ── structural relation / evidence / epistemic checks on what the authors wrote (origin = llm) ──
import evidence as _EV
import guidance as _GD
import prose as _P
import relations as _RL
_rs = state.relations.get("state") or {}
_pairs = _RL.pair_index(_rs)
_R = _P.Rules(catalog=C.build_catalog(state, include_deferred=True), pair_fn=lambda a, b: C.pair_status(state, a, b, _pairs),
magnitude_bindings=_RL.magnitude_bindings(_rs), claim_model=_EV.claim_model,
evidence_targets=lambda c: _EV.evidence_targets(state, c),
guidance_terms={c: (_GD.epistemic_state(state.guidance.get("company"), c), n) for c, n in _GD.CONCEPT_TERMS.items()},
expectations_polarity=((_rs.get("items") or {}).get("REL_EXPECTATIONS_STORY") or {}).get("relation_type"))
import pipeline as _PL
for _k, _v in _PL.REL_RULES(state, _R.catalog).items():
setattr(_R, _k, _v)
def _authored():
for u in _sentences():
if any(getattr(f, "origin", "system") == "llm" for f in u):
yield u, "".join(f"[[{f.claim_id}]]" if (f.claim_id and not f.claim_id.startswith("P:")) else f.text for f in u)
def _rel_kind(kind):
def run():
bad = []
for u, tok in _authored():
v = _P.relation_violation(tok, _R)
if v and v[0] == kind:
bad.append(v[1][:110])
if kind == "RELATION_IDENTITY":
pres = set(C.RELATION_PRESENTATION)
bad += [f"[[{f.claim_id}]] in authored prose (a relation's size without its subject)"
for u, _t in _authored() for f in u if f.claim_id in pres and f.origin == "llm"]
bad += [p for k, p in _RL.check(state) if k == "REL1"]
if kind == "RELATION_MAGNITUDE":
bad += [p for k, p in _RL.check(state) if k == "REL2"]
return (FAIL, "; ".join(bad[:2])) if bad else (PASS, f"{len(_rs.get('items') or {})} typed relations hold in the text")
return run
def _evidence_scope():
bad = [(_P.evidence_scope_violation(tok, _R) or "")[:110] for u, tok in _authored() if _P.evidence_scope_violation(tok, _R)]
return (FAIL, "; ".join(bad[:2])) if bad else (PASS, "model evidence describes only its own model")
def _evidence_visibility():
bad = [f.claim_id for u, _t in _authored() for f in u
if f.claim_id and f.origin == "llm" and _EV.claim_model(f.claim_id)
and not _EV.model_visible(state, _EV.claim_model(f.claim_id))]
return (FAIL, f"hidden models' figures in authored prose: {sorted(set(bad))}") if bad else (PASS, "no hidden model's figure in authored prose")
def _epistemic():
bad = [tok[:110] for u, tok in _authored() if _P.guidance_epistemic_violation(tok, _R.guidance_terms)]
return (FAIL, f"guidance presence/absence asserted beyond its epistemic state: {bad[:2]}") if bad else \
(PASS, "no guidance presence or absence asserted beyond its epistemic state")
def _polarity():
bad = [tok[:110] for u, tok in _authored() if _P.polarity_violation(tok, _R.expectations_polarity)]
return (FAIL, f"expectation polarity not from the structured relation: {bad[:2]}") if bad else \
(PASS, "expectation polarity only as REL_EXPECTATIONS_STORY states it")
def _returns():
bad = [f.claim_id for u, _t in _authored() for f in u if f.origin == "llm" and f.claim_id in C.PHRASE_OF]
for f in frags:
cd = C.claim_def(state, f.claim_id) if f.claim_id and not f.claim_id.startswith("P:") else None
if cd is not None and cd.stype == C.PHRASE:
rc = C.return_claim(state.value(cd.path), cd.phrase)
if rc is None or f.text not in (rc["rendered"], rc["rendered"][:1].upper() + rc["rendered"][1:]):
bad.append(f"{f.claim_id}: phrase does not carry the sign of its value")
return (FAIL, f"signed figures without their own direction: {bad[:3]}") if bad else (PASS, "signed quantities carry their own direction")
def _doubled():
bad = [tok[:110] for u, tok in _authored() if _P.qualifier_doubled(tok, _R.catalog) or _P.operand_doubled(tok, _R.catalog)]
return (FAIL, f"a claim's own qualifier written twice: {bad[:2]}") if bad else (PASS, "no qualifier written twice")
def _clause_grammar():
bad = [tok[:110] for u, tok in _authored()
if _P.clause_grammar_violation(tok, _R.full_clause) or _P.mode_subject_violation(tok, _R.rel_subjects)
or _P.relation_slot_violation(tok, _R.rel_subjects, _R.catalog)]
return (FAIL, f"relationship clauses used as noun phrases or attached to another subject: {bad[:2]}") if bad else \
(PASS, "every relationship token sits in a clause position, at most two per sentence")
def _trends():
bad = [tok[:110] for u, tok in _authored() if _P.trend_violation(tok, _R)]
return (FAIL, f"trend conclusions without an approved trend relationship: {bad[:2]}") if bad else \
(PASS, f"trend conclusions only from approved trend relationships ({len(_R.trends_allowed)} available)")
def _interpretation():
bad = [tok[:110] for u, tok in _authored() if _P.interpretation_violation(tok, _R.interpretation)]
perm = {f: d.get("permission") for f, d in (_R.interpretation or {}).items()}
return (FAIL, f"descriptive-only evidence interpreted: {bad[:2]}") if bad else (PASS, f"evidence interpretation within permission {perm}")
def _guidance_binding():
bad = [tok[:110] for u, tok in _authored() if _P.guidance_binding_violation(tok, _R.guidance_names)]
return (FAIL, f"guidance values attached to another subject: {bad[:2]}") if bad else \
(PASS, "every authored guidance value is named by its own concept")
def _final_claim_only():
"""Cover and summary: every material number is a claim fragment whose text and value are the frozen state's."""
bad = []
for b in rep.blocks:
if b.section not in ("verdict", "summary"):
continue
for f in b.fragments():
if not f.claim_id:
if MATERIAL_NUMBER.search(BENIGN.sub(" ", f.text)):
bad.append(f"authored number '{MATERIAL_NUMBER.search(BENIGN.sub(' ', f.text)).group(0).strip()}'")
continue
cd = C.claim_def(state, f.claim_id) if not f.claim_id.startswith("P:") else None
if f.claim_id.startswith("P:") or f.claim_id == "VERSION":
continue
if cd is None:
bad.append(f"{f.claim_id}: not in the final claim registry")
continue
cl = C.claim_for(state, cd)
want = cl.rendered or ""
if f.text != want and not ((cd.stype == C.REL or cd.clause) and want and f.text == want[0].upper() + want[1:]):
bad.append(f"{f.claim_id}: shows '{f.text}', final registry has '{want}'")
elif f.value is not None and cl.value is not None and not isinstance(cl.value, dict) and f.value != cl.value:
bad.append(f"{f.claim_id}: value {f.value} ≠ final {cl.value}")
return (FAIL, "; ".join(bad[:3])) if bad else (PASS, "cover and summary numbers are final claims only")
def _ownership_phrase():
bad = [f.claim_id for u, _t in _authored() for f in u if f.origin == "llm" and f.claim_id == "OWN_13F_HOLDERS_CHANGE"]
d = C.BY_ID.get("OWN_13F_HOLDERS_CHANGE_PHRASE")
for f in frags:
if f.claim_id == "OWN_13F_HOLDERS_CHANGE_PHRASE":
rc = C.return_claim(state.value(d.path), d.phrase)
if rc is None or f.text not in (rc["rendered"], rc["rendered"][:1].upper() + rc["rendered"][1:]):
bad.append("the ownership-change phrase does not own its direction, magnitude and period")
return (FAIL, "; ".join(map(str, bad[:3]))) if bad else (PASS, "ownership change appears only as its complete phrase")
def _relation_logic():
bad = [tok[:110] for u, tok in _authored() if _P.relation_logic_violation(tok, _R.alternative_cases, _R.case_variables)]
return (FAIL, f"alternative conditional cases stated jointly: {bad[:2]}") if bad else \
(PASS, "alternative break-even cases are never stated as a joint requirement")
def _valuation_relations():
bad = [tok[:110] for u, tok in _authored() if _P.valuation_relation_violation(tok, _R.valuation_relations)
or _P.universal_valuation_violation(tok, _R.valuation_relations)]
return (FAIL, f"valuation direction words without their relation: {bad[:2]}") if bad else \
(PASS, "valuation direction words cite and agree with their relation")
def _segment_perimeter():
bad = [tok[:110] for u, tok in _authored() if _P.segment_perimeter_violation(tok, _R.segment_perimeter)]
return (FAIL, f"segment counts outside their perimeter: {bad[:2]}") if bad else \
(PASS, "segment counts cited with their perimeter")
def _evidence_domain():
bad = [tok[:110] for u, tok in _authored() if _P.evidence_domain_violation(tok, _R)]
return (FAIL, f"evidence used outside its domain: {bad[:2]}") if bad else (PASS, "evidence establishes only its own domain")
def _lineage():
"""Every material number in the document is a claim of the FINAL frozen state (text and value), a guidance value
is an AVAILABLE row of the final guidance state, and every guidance value an author cites is in the table."""
bad = []
rows = (state.guidance.get("company") or {}).get("metrics") or []
for f in frags:
if not f.claim_id:
mm = MATERIAL_NUMBER.search(BENIGN.sub(" ", f.text))
if mm:
bad.append(f"'{mm.group(0).strip()}' has no claim")
continue
if f.claim_id.startswith("P:") or f.claim_id == "VERSION":
continue
cd = C.claim_def(state, f.claim_id)
if cd is None:
bad.append(f"{f.claim_id}: not in the final state")
continue
if f.claim_id.startswith("GUIDANCE_"):
try:
row = rows[int(f.claim_id.split("_")[1]) - 1]
except (ValueError, IndexError):
bad.append(f"{f.claim_id}: no such final guidance row")
continue
if (state.guidance.get("company") or {}).get("status") != "AVAILABLE" or not row.get("verified"):
bad.append(f"{f.claim_id}: not an available, verified row")
table = {f.claim_id for b in rep.blocks if b.kind == "table" and b.section == "forecast"
for f in b.fragments() if f.claim_id and f.claim_id.startswith("GUIDANCE_")}
cited = {f.claim_id for u, _t in _authored() for f in u if f.claim_id and f.claim_id.startswith("GUIDANCE_")}
bad += [f"{c}: cited in prose but absent from the guidance table" for c in sorted(cited - table)]
return (FAIL, "; ".join(bad[:3])) if bad else (PASS, "every material number resolves to a final claim")
def _typed_rule(fn, what, ok):
def run():
bad = [tok[:110] for u, tok in _authored() if fn(tok)]
return (FAIL, f"{what}: {bad[:2]}") if bad else (PASS, ok)
return run
# ── claim lineage and historical data quality (typed; the relations are re-derived from state) ──
_lin = {}
def _lineage_hits():
"""Every authored object (own evidence) and thesis (named counterparts) through arguments.lineage_reasons, and
every template sentence by co-citation: (code, where, text)."""
if "v" in _lin:
return _lin["v"]
import arguments as _A
hits = []
cat0 = C.build_catalog(state, include_deferred=False)
ctx = _A.context(state, cat0, _PL.rules_for(state, cat0), _PL.INTERNAL_TERMS, QA_VOCAB)
for role, a in (rep.arguments or {}).items():
if not a:
continue
for key in ("points", "items_thesis", "items_risks", "answers"):
for p in a.get(key) or []:
ev = list(p.get("evidence") or []) + list(p.get("to_evidence") or [])
hits += [(x.split(":")[0], f"{role}.{key}", p.get("view") or "") for x in _A.lineage_reasons(ctx, p.get("view"), ev)]
if a.get("thesis"):
sc = _A._scope(_A._rest_objects(role, a))
hits += [(x.split(":")[0], f"{role}.thesis", a["thesis"])
for x in _A.lineage_reasons(ctx, a["thesis"], list(sc), cocite=False)]
for u in _sentences():
ids = [f.claim_id for f in u if f.claim_id and not f.claim_id.startswith("P:")]
txt = "".join(f.text for f in u)
hits += [(x.split(":")[0], "template", txt) for x in _A.lineage_reasons(ctx, txt, ids, named=False)]
_lin["v"] = hits
return hits
def _guidance_consensus():
bad = []
it = (_rs.get("items") or {}).get("REL_GUIDANCE_EPS_CONSENSUS")
if it:
bad += [f"REL_GUIDANCE_EPS_CONSENSUS: {w}" for w in _RL.verify_guidance_relation(state, it)]
bad += [f"{w}: {x[:90]}" for c, w, x in _lineage_hits() if c == "GUIDANCE_CONSENSUS_COMPATIBILITY"]
return (FAIL, f"guidance compared with consensus without a compatible typed relation: {bad[:2]}") if bad else \
(PASS, "guidance meets consensus only through a relation on one fiscal period, horizon and basis"
+ ("" if it else " (none is compatible: stated separately)"))
def _forecast_relation():
bad = []
for rid in ("REL_FORECAST_EPS_CONSENSUS", "REL_ENGINE_REVENUE_CONSENSUS"):
it = (_rs.get("items") or {}).get(rid)
if it:
bad += [f"{rid}: {w}" for w in _RL.verify_forecast_relation(state, rid, it)]
bad += [f"{w}: {x[:90]}" for c, w, x in _lineage_hits() if c == "FORECAST_RELATION_REQUIRED"]
return (FAIL, f"engine forecasts compared with consensus without their relation: {bad[:2]}") if bad else \
(PASS, "engine-vs-consensus comparisons only through independent, same-horizon relations")
def _historical_quality():
"""The trailing column's label and the TTM metrics' quality derive from HistoricalMetricQuality; a series with an
unchecked quarter is never labelled simply reconciled."""
q = historical_quality(state)
bad = []
stored = (state.historical.get("reconciliation") or {}).get("ttm_quality")
if stored is not None and dict(q) != dict(stored):
bad.append("a quality state exists but the labels were derived without it")
qs = q.get("quarters") or []
if qs:
chk = [x.get("source") not in ("PROVIDER_ONLY", "UNAVAILABLE") for x in qs]
want = "SEC_RECONCILED" if all(chk) else "PARTIALLY_RECONCILED" if any(chk) else "PROVIDER_ONLY"
if q.get("status") != want:
bad.append(f"quality status {q.get('status')} but its quarters give {want}")
unv = [x["period"] for x, ok in zip(qs, chk) if not ok]
if list(q.get("unverified_periods") or []) != unv:
bad.append(f"unverified periods {q.get('unverified_periods')} ≠ {unv}")
if q.get("status") == "SEC_RECONCILED" and q.get("unverified_periods"):
bad.append("SEC_RECONCILED with unverified periods")
fund = [i for i, b in enumerate(rep.blocks) if b.kind == "table" and b.css == "rp-fund"]
if not fund:
bad.append("the fundamentals table is missing")
for i in fund:
h = rep.blocks[i].content.get("head") or ["", "", ""]
want = [historical_quality_render(q, sep_) for sep_ in (False, True)]
if h[1] not in {w["table_header"] for w in want}:
bad.append(f"trailing column labelled '{h[1]}'")
foot = ""
for nxt in rep.blocks[i + 1:]: # the footnote paragraphs directly under the table
if nxt.css != "rp-foot":
break
foot += " ".join(f.text for f in nxt.content) + " "
if not any(w["footnote"] in foot for w in want):
bad.append("the trailing column's quality footnote is not printed under the table")
if FCST_MARK in str(h[2]) and FCST_MARK + " " not in foot:
bad.append("the forecast column's lineage footnote is not printed under the table")
mq = {"SEC_RECONCILED": "RECONCILED", "PARTIALLY_RECONCILED": "PARTIALLY_RECONCILED"}.get(q.get("status"), "UNVERIFIED")
for mid in ("revenue_ttm", "operating_income_ttm", "net_income_ttm"):
m = state.historical.get(mid)
if m is not None and m.value is not None and m.quality_status != mq:
bad.append(f"{mid} carries {m.quality_status}, its quarters give {mq}")
return (FAIL, "; ".join(bad[:3])) if bad else \
(PASS, f"{q.get('status')} (verified through {q.get('verified_through')}; unverified {q.get('unverified_periods') or 'none'})")
def _segment_note():
a = ((state.valuation.get("story") or {}).get("assumptions")) or {}
if a.get("margin_proposed") is None or a.get("margin_target") is None or \
abs(float(a["margin_proposed"]) - float(a["margin_target"])) <= 0.0005 or not (state.valuation.get("story") or {}).get("segments"):
return PASS, "segment margins are the margin the valuation uses"
shown = {f.claim_id for f in frags if f.claim_id}
ok = {"P:valuation.story.assumptions.margin_proposed", "P:valuation.story.assumptions.margin_target"} <= shown
return (PASS, "the segments' weighted margin and the margin used (capped or floored) are both shown") if ok else \
(FAIL, "segment margins shown without the cap or floor to the margin the valuation uses")
def _structured():
"""Every authored fragment belongs to a validated structured object, and every object still validates with
the pipeline's own rule inputs (the same functions ingestion used)."""
sets = rep.arguments or {}
texts = set()
for a in sets.values():
if not a:
continue
if a.get("thesis"):
texts.add(a["thesis"])
for key in ("points", "items_thesis", "items_risks", "answers"):
for p in a.get(key) or []:
texts.add(p["view"])
if p.get("to_view"):
texts.add(p["to_view"])
quotes = {p["to_view"] for a in sets.values() if a for p in a.get("answers") or [] if p.get("to_view")}
bad = [f.text[:80] for f in frags if f.origin == "authored" and f.text not in texts]
bad += [f"quote: {f.text[:70]}" for f in frags if f.origin == QUOTE and f.text not in quotes]
if sets:
import arguments as _A
import pipeline as _PLs
try:
cat0 = C.build_catalog(state, include_deferred=False)
ctx = _A.context(state, cat0, _PLs.rules_for(state, cat0), _PLs.INTERNAL_TERMS, QA_VOCAB)
for role, a in sets.items():
if not a or role == "summary":
continue
_a, drops = _A.revalidate(ctx, a, own={"value": None, "label": None, "return_claim": None})
bad += [f"{role}: {d['reason'][:90]}" for d in drops if not str(d["reason"]).startswith(
("REVALIDATION: VIEW_RATING", "REVALIDATION: VIEW_RETURN_DIRECTION"))]
except Exception as e: # noqa: BLE001
bad.append(f"revalidation raised {type(e).__name__}: {str(e)[:80]}")
return (FAIL, f"structured arguments that do not validate: {bad[:3]}") if bad else \
(PASS, f"{sum(1 for f in frags if f.origin == 'authored')} authored fragments, all from validated objects")
def _identifiers():
"""No internal identifier (snake_case or UPPER_SNAKE) reaches the reader, in template or authored text: the
PM's triggers printed "If revenue_growth_next_fy falls to 5.0%" in run 8d12481bffa6e2e6."""
bad = []
for f in frags:
if f.claim_id == "VERSION":
continue
m = _IDENT.search(f.text or "")
if m:
bad.append(m.group(0))
return (FAIL, f"internal identifiers in the document: {sorted(set(bad))[:4]}") if bad else \
(PASS, "no internal identifiers")
def _authored_once():
from collections import Counter
cnt = Counter(f.text for f in frags if f.origin == "authored")
dup = [t[:70] for t, n in cnt.items() if n > 1]
return (FAIL, f"authored text printed more than once: {dup[:2]}") if dup else \
(PASS, "every authored text is printed once")
def _public_language():
bad = [tok[:110] for u, tok in _authored() if _P.public_language_violation(tok, _PL.INTERNAL_TERMS)]
return (FAIL, f"internal QA language in the document: {bad[:2]}") if bad else (PASS, "public language only")
def _conclusion(kind):
def run():
bad = []
for u, tok in _authored():
v = _P.evidence_conclusion_violation(tok, _R.evidence_uses)
if v and v[0] == kind:
bad.append(tok[:110])
return (FAIL, f"evidence used beyond its allowed conclusion types: {bad[:2]}") if bad else \
(PASS, "evidence supports only its allowed conclusion types")
return run
def _blend_labels():
from state_builder import BLEND_KIND_LABEL
rows = [b for b in rep.blocks if b.kind == "table" and b.section == "blend"]
shown = ["".join(f.text for f in row[0]) for b in rows for row in b.content.get("rows", [])]
bad = []
for x, lab in zip(state.blend.get("sources") or [], shown):
want = BLEND_KIND_LABEL.get(x.get("kind")) if x.get("kind") != "OTHER" else f"Other source ({x.get('name')})"
if lab != want:
bad.append(f"'{lab}' shown for a {x.get('kind')} source (want '{want}')")
return (FAIL, "; ".join(bad[:2])) if bad else (PASS, "blend labels derive from typed source kinds")
def _directional():
from prose import directional_violation
restricted = tuple(x for x, blocked in (
("13F", not (state.ownership.get("permissions") or {}).get("use_as_return_signal", False)),
("insider", not (((state.insiders.get("canon") or {}).get("materiality") or {}).get("material")))) if blocked)
bad = []
for u in _sentences():
tok = "".join(f"[[{f.claim_id}]]" if f.claim_id else f.text for f in u)
if directional_violation(tok, restricted):
bad.append(tok[:100])
return (FAIL, f"restricted evidence used directionally: {bad[:2]}") if bad else (PASS, "no restricted evidence carries a direction")
def _relative():
from prose import relative_violation
allowed = tuple(k for k, v in (state.pricing.get("relative") or {}).items() if v.get("comparison_allowed"))
bad = [("".join(f.text for f in u))[:100] for u in _sentences() if relative_violation("".join(f.text for f in u), allowed)]
return (FAIL, f"relative valuation without a comparable peer basis: {bad[:2]}") if bad else (PASS, "relative valuation only on comparable bases")
def stable_wacc():
bad = []
for u in _sentences():
bad += [cid for cid, _q in qualifier_violation(u, lambda c: getattr(C.claim_def(state, c), "required_qualifiers", ()))]
return (FAIL, f"stable-period rate written without its qualifier: {bad[:3]}") if bad else \
(PASS, "every stable-period rate carries its stable / mature / terminal qualifier")
checks = [(numbers, "R1", "every material number is a registered claim", True, "presentation"),
(values, "R2", "every rendered claim equals the frozen state", True, "identity"),
(identity, "R3", "claims keep their semantic identity", True, "identity"),
(terminology, "R4", "valuation vocabulary from the selected engine", True, "policy"),
(no_qa, "R5", "no internal QA content in the user report", True, "presentation"),
(quant_hidden, "R6", "unreliable models invisible to users", True, "policy"),
(same_bytes, "R7", "HTML and markdown are the audited fragments", True, "identity"),
(required, "R8", "required claims present", True, "presentation"),
(guidance_evidence, "S9", "guidance only with canonical guidance evidence", True, "policy"),
(lambda: _motive("13F"), "S10", "no motive inferred from 13F ownership", True, "policy"),
(lambda: _motive("insider"), "S11", "no motive inferred from insider filings", True, "policy"),
(stable_wacc, "S12", "stable-period cost of capital never presented as current", True, "identity"),
(_pairs_ok, "S16", "prose comparisons are between compatible typed claims", True, "identity"),
(_directional, "S24", "evidence without return-signal permission carries no direction", True, "policy"),
(_relative, "S26", "relative valuation requires the same basis and perimeter", True, "policy"),
(_rel_kind("RELATION_IDENTITY"), "RELATION_IDENTITY", "relations keep their subject and object", True, "identity"),
(_rel_kind("RELATION_MAGNITUDE"), "RELATION_MAGNITUDE", "magnitudes stay bound to the pair they measure", True, "identity"),
(_rel_kind("RELATION_REQUIRED_FOR_PAIR"), "RELATION_REQUIRED_FOR_PAIR",
"related quantities share a sentence only through an approved relation", True, "identity"),
(_evidence_scope, "MODEL_EVIDENCE_SCOPE", "model evidence evaluates only its allowed target models", True, "policy"),
(_evidence_visibility, "MODEL_EVIDENCE_VISIBILITY", "hidden models contribute no authored figure", True, "policy"),
(_epistemic, "GUIDANCE_EPISTEMIC_STATE", "no absence asserted from rejected or unfound guidance", True, "policy"),
(_polarity, "EXPECTATIONS_POLARITY", "expectation polarity only from the structured relation", True, "identity"),
(_returns, "RETURN_DIRECTION_RENDER", "signed quantities carry their own direction", True, "presentation"),
(_blend_labels, "BLEND_SOURCE_KIND", "blend labels derive from typed source kinds", True, "presentation"),
(_doubled, "CLAIM_QUALIFIER_DOUBLED", "no claim's own direction or bound is written twice", True, "presentation"),
(_clause_grammar, "RELATION_CLAUSE_GRAMMAR", "relationship clauses keep clause positions", True, "presentation"),
(_trends, "TREND_RELATION_REQUIRED", "trend conclusions need an approved trend relationship", True, "identity"),
(_interpretation, "EVIDENCE_INTERPRETATION", "descriptive-only evidence carries no interpretation", True, "policy"),
(_guidance_binding, "GUIDANCE_LABEL_BINDING", "guidance values are named by their own concept", True, "identity"),
(_final_claim_only, "FINAL_CLAIM_ONLY", "cover and summary numbers resolve to final claims", True, "identity"),
(_ownership_phrase, "OWNERSHIP_CHANGE_PHRASE", "ownership change only as its complete phrase", True, "presentation"),
(_relation_logic, "RELATION_LOGIC_PRESERVATION", "paraphrase preserves each relation's logical structure", True, "identity"),
(_valuation_relations, "VALUATION_RELATION_LANGUAGE", "valuation direction language follows its relation", True, "identity"),
(_segment_perimeter, "SEGMENT_PERIMETER_LANGUAGE", "segment counts carry their perimeter", True, "identity"),
(_evidence_domain, "EVIDENCE_DOMAIN_SCOPE", "evidence establishes only its own domain", True, "policy"),
(_lineage, "FINAL_MATERIAL_CLAIM_LINEAGE", "every material number resolves to a final claim", True, "identity"),
(_public_language, "PUBLIC_LANGUAGE_ONLY", "no internal QA language in the document", True, "presentation"),
(_conclusion("EVIDENCE_CONCLUSION_SCOPE"), "EVIDENCE_CONCLUSION_SCOPE", "evidence supports only allowed conclusions",
True, "policy"),
(_conclusion("NEWS_EVIDENCE_SCOPE"), "NEWS_EVIDENCE_SCOPE", "news supports only its classified uses", True, "policy"),
(_typed_rule(lambda t: _P.performance_violation(t, _R.stock_returns, _R.company_words), "stock performance without its return claim",
"stock performance stated only with its signed return claim"),
"STOCK_PERFORMANCE_CLAIM", "stock performance only from the signed return claim", True, "identity"),
(_typed_rule(lambda t: _P.assessment_violation(t, _R.assessments, _R.assumption_claims),
"assumptions judged beyond their typed assessment",
"assumptions called conservative or aggressive only as assessed"),
"ASSUMPTION_ASSESSMENT", "assumption judgements follow their typed assessment", True, "identity"),
(_typed_rule(lambda t: _P.quote_violation(t, _R.transcript_turns, _R.company_words),
"call quotations attributed to the wrong speaker", "call quotations keep their speaker"),
"QUOTE_ATTRIBUTION", "call quotations keep their speaker's role", True, "identity"),
(_typed_rule(lambda t: _P.statement_domain_violation(t, _R.statements), "figures placed on the wrong statement",
"figures stay on their own financial statement"),
"STATEMENT_DOMAIN", "figures stay on their own financial statement", True, "identity"),
(_typed_rule(lambda t: _P.phrase_slot_violation(t, _R.catalog), "predicate or list claims out of their slot",
"predicate and list claims sit in their render slots"),
"CLAIM_RENDER_SLOT", "predicate and list claims keep their render slot", True, "presentation"),
(_typed_rule(lambda t: _P.family_comparison_violation(t), "comparisons across metric families",
"no comparison across metric families without a typed relation"),
"METRIC_FAMILY_RELATION", "comparisons across metric families need a typed relation", True, "identity"),
(_typed_rule(lambda t: _P.proximity_violation(t, _R.valuation_magnitudes), "closeness to value outside the band",
"closeness to value only within the band of a cited anchor"),
"VALUE_PROXIMITY", "'close to fair value' only within the closeness band of a cited anchor", True, "identity"),
(_typed_rule(lambda t: _P.named_counterpart_violation(t, _R) or _P.claim_label_violation(t, _R.catalog),
"a figure compared with an uncited quantity, or not named by its concept",
"figures are named by their concept and compared only with cited, compatible claims"),
"CLAIM_LABEL_BINDING", "figures are named by their concept; named comparisons cite their claim", True, "identity"),
(_typed_rule(lambda t: _P.magnitude_violation(t, _R.catalog) or _P.spelled_count_violation(t),
"verbal magnitudes or spelled counts without a claim", "verbal magnitudes fit their claims"),
"VERBAL_MAGNITUDE", "verbal magnitudes and counts fit a claim of their metric", True, "identity"),
(_typed_rule(lambda t: _P.paraphrase_attribution_violation(t, _R.transcript_turns, _R.company_words),
"an analyst's words attributed to management", "paraphrased call content keeps its speaker"),
"PARAPHRASE_ATTRIBUTION", "paraphrased call content keeps its speaker's role", True, "identity"),
(_structured, "STRUCTURED_ARGUMENTS", "authored judgement comes only from validated structured objects",
True, "identity"),
(_identifiers, "NO_INTERNAL_IDENTIFIERS", "no internal identifier reaches the reader", True, "presentation"),
(_authored_once, "AUTHORED_ONCE", "no authored text is printed twice", True, "presentation"),
(_segment_note, "SEGMENT_MARGIN_DISCLOSURE", "a capped segment margin is disclosed with the margin used", True,
"presentation"),
(_guidance_consensus, "GUIDANCE_CONSENSUS_COMPATIBILITY",
"guidance against consensus only on one fiscal period, horizon and basis", True, "identity"),
(_forecast_relation, "FORECAST_RELATION_REQUIRED",
"engine forecasts against consensus only through their typed relation", True, "identity"),
(_historical_quality, "HISTORICAL_QUALITY_LABEL", "trailing-figure labels derive from their quality state",
True, "presentation")]
return ValidationResult("REPORT_VALIDATED", _run(checks))
# ═══════════════════════════════════════════════════════════════════════════════════════
# records
# ═══════════════════════════════════════════════════════════════════════════════════════
USER_SAFE_MESSAGES = {
# data and forecast/valuation checks depend only on the company's figures: a retry before the data changes fails the
# same way, so the message must not invite one
"INITIAL": ("Some source data for this company could not be reconciled, so a complete analysis could not be "
"published. This usually lasts until the company's next filing or data update: running the analysis again "
"now will most likely give the same result."),
"DATA_VALIDATED": ("The forecast or valuation for this company did not pass its consistency checks, so the analysis "
"was not published. This comes from how the model handles this company's figures, so running it again "
"will usually give the same result; the case is recorded for review."),
# these two depend on the analyst models' answers, which differ from run to run: a retry can succeed
"FORECAST_VALIDATED": ("The analyst panel's conclusions could not be combined into a consistent result, so the analysis "
"was not published. Please try again later."),
"ANALYSIS_FINALIZED": ("The finished report did not pass its final accuracy check, so it was not published. "
"Please try again later."),
}
# a withheld run whose cause is known and specific says so (pipeline.Run.withheld_reason)
WITHHELD_REASON_MESSAGES = {
"PRE_REVENUE": ("This company has little or no revenue yet: its operating losses are larger than its revenue. The model "
"values a business from an established revenue base, so pre-revenue and early-revenue companies are not "
"covered. Running the analysis again will give the same result."),
}
USER_SAFE_MESSAGE = USER_SAFE_MESSAGES["INITIAL"]
def user_payload(rep: UserReport, md: str, html_doc: str, state):
"""The ONLY object the public API, the web page and the download use."""
return {"schema": "crucible.user_report/1", "status": "PUBLISHED", "ticker": rep.ticker, "company": rep.company,
"as_of": rep.as_of, "version": rep.version,
"rating": state.value("recommendation.rating"),
"headline": {"value": state.value("blend.headline_return"), "rendered": (rep.claims.get("HEADLINE_RETURN") or {}).get("rendered")},
"price": {"value": state.value("market.price"), "rendered": (rep.claims.get("PRICE") or {}).get("rendered")},
"limitations": rep.limitations, "html": html_doc, "markdown": md,
"claims": {k: {"rendered": v.get("rendered"), "value": v.get("value"), "label": v.get("label")} for k, v in rep.claims.items()}}
def withheld_payload(ticker, company=None, stage_reached="INITIAL", reason=None):
"""The stage reached is the last one that PASSED; the message describes the step after it. A known, specific cause
(``reason``) replaces the stage's general message."""
out = {"schema": "crucible.user_report/1", "status": "WITHHELD", "ticker": ticker, "company": company,
"message": WITHHELD_REASON_MESSAGES.get(reason) or USER_SAFE_MESSAGES.get(stage_reached, USER_SAFE_MESSAGE)}
if reason in WITHHELD_REASON_MESSAGES:
out["reason"] = reason
return out
def qa_record(*, ticker, run_id, stage_reached, validations, state_snapshot, rejections, reconciliation, notes=None,
report_audit=None, timings=None, arguments=None):
"""Internal QA only: never served publicly, never rendered into the user report."""
return {"schema": "crucible.internal_qa/1", "ticker": ticker, "run_id": run_id, "stage_reached": stage_reached,
"arguments": arguments,
"validations": [v.to_dict() for v in validations if v is not None],
"report_audit": report_audit.to_dict() if report_audit is not None else None,
"rejected_sentences": [r.to_dict() for r in rejections], "reconciliation": reconciliation,
"state": state_snapshot, "notes": notes or [], "timings": timings or {}}