readthrough / tests /test_note_fidelity.py
Viney's picture
feat: The Brief renders as the published research note
93f9cc0
Raw History Blame Contribute Delete
15.1 kB
# -*- coding: utf-8 -*-
"""The note must render as the published note, not merely in its spirit.
The brief was asked to reproduce a specific published document exactly β€” same
colours, same placements. "Looks close" is not the requirement, so these tests
compare against the document itself rather than against a description of it.
``tests/fixtures/published_note.html`` holds the two things that can be
compared mechanically: the stylesheet, and the three figures. Everything else
about the page is data-dependent, so those assertions are structural β€” the ten
sections in their published order, six tiles in a six-column grid, a section
that keeps its slot when its data is missing.
Two divergences from the published file are deliberate, and each is asserted
here so it cannot drift into an accidental third:
* every selector carries a ``.rt-note`` prefix, because Streamlit's own base
styles would otherwise win on specificity;
* every ``<svg>`` carries ``xmlns``, because Streamlit's sanitiser drops an
SVG without it and the figure renders as an empty box.
"""
from __future__ import annotations
import pathlib
import re
import pytest
FIXTURE = pathlib.Path(__file__).parent / "fixtures" / "published_note.html"
@pytest.fixture(scope="module")
def published() -> str:
return FIXTURE.read_text(encoding="utf-8")
# ── The stylesheet ───────────────────────────────────────────────────────────
def _rules(css: str) -> list[tuple[str, str]]:
"""Flatten CSS to ``(selector, sorted declarations)`` pairs, in order."""
css = re.sub(r"/\*.*?\*/", "", css, flags=re.S)
out = []
for selector, block in re.findall(r"([^{}]+)\{([^{}]*)\}", css):
selector = " ".join(selector.split())
if selector.startswith("@"):
continue
declarations = ";".join(
sorted(d.strip() for d in block.split(";") if d.strip())
)
out.append((selector, declarations))
return out
def _unscope(selector: str) -> str:
"""Strip the ``.rt-note`` prefix so selectors can be compared."""
parts = []
for part in selector.split(","):
part = part.strip()
if part == ".rt-note":
part = "body" # the note is a div here, a document there
elif part.startswith(".rt-note "):
part = part[len(".rt-note "):]
parts.append(part)
return ",".join(parts)
# Rules we add on top of the published stylesheet, each with its reason.
# Anything not listed here is drift, and the test says so. Empty is the goal:
# the stylesheet is currently the published one, rule for rule.
ADDED_RULES: dict[str, str] = {}
def test_stylesheet_declarations_are_the_published_ones(published):
"""Every published rule, in order, with the same declarations and values."""
from dashboard.note_css import NOTE_CSS, TOKENS
original = _rules(published[published.index("<style>") + 7: published.index("</style>")])
# margin:0 belongs to the real <body> there; here the note is a div inside
# a page that already sets it.
original = [
(sel, ";".join(d for d in decl.split(";") if d != "margin:0") if sel == "body" else decl)
for sel, decl in original
]
mine = [(_unscope(sel), decl) for sel, decl in _rules(TOKENS + NOTE_CSS)]
extra = [(sel, decl) for sel, decl in mine if sel in ADDED_RULES]
assert len(extra) == len(ADDED_RULES), (
f"expected the additions {sorted(ADDED_RULES)}, found {[s for s, _ in extra]}"
)
kept = [(sel, decl) for sel, decl in mine if sel not in ADDED_RULES]
assert len(kept) == len(original), (
f"rule count drifted: published {len(original)}, ours {len(kept)}. "
f"A new rule needs an entry in ADDED_RULES with its reason."
)
for (sel_a, decl_a), (sel_b, decl_b) in zip(original, kept):
assert sel_a.replace(" ", "") == sel_b.replace(" ", ""), (
f"selector drifted: published {sel_a!r}, ours {sel_b!r}"
)
assert decl_a == decl_b, f"{sel_a}: published {decl_a!r}, ours {decl_b!r}"
def test_every_selector_is_scoped_to_the_note():
"""An unscoped selector would restyle the whole Streamlit page."""
from dashboard.note_css import NOTE_CSS
for selector, _ in _rules(NOTE_CSS):
for part in selector.split(","):
part = part.strip()
assert part == ".rt-note" or part.startswith(".rt-note "), (
f"{part!r} escapes the note's scope"
)
def test_the_light_tokens_are_the_published_values():
"""The palette the app is pinned to is the note's own light palette."""
from dashboard.note_css import TOKENS, TOKENS_LIGHT
def values(css: str) -> dict[str, str]:
block = css[css.index(":root{") + 6: css.index("}", css.index(":root{"))]
out = {}
for decl in block.split(";"):
if "--" in decl:
name, _, value = decl.partition(":")
out[name.strip()] = value.strip()
return out
published_light = values(TOKENS)
pinned = values(TOKENS_LIGHT)
for name, value in published_light.items():
assert pinned.get(name) == value, f"{name}: pinned {pinned.get(name)!r} != {value!r}"
# ── The figures ──────────────────────────────────────────────────────────────
def _normalise(svg: str) -> str:
"""Compare on rendered output, not on the two encodings we must differ in.
Undone here: the ``xmlns`` the sanitiser requires, and ``&#36;`` for the
dollar sign, which st.markdown would otherwise read as LaTeX. Both render
exactly as the published note does; everything else must match literally.
"""
svg = re.sub(r">\s+<", "><", svg.strip())
return svg.replace(' xmlns="http://www.w3.org/2000/svg"', "").replace("&#36;", "$")
def _fixture_figure(published: str, name: str) -> str:
block = re.search(rf'<figure id="{name}">(.*?)</figure>', published, re.S)
assert block, f"fixture has no figure {name!r}"
return block.group(1)
def _footnote(svg: str) -> str:
from html import unescape
match = re.search(r'<text x="44" y="16" class="ct">([^<]*)</text>', svg)
return unescape(match.group(1)) if match else ""
def test_bar_figure_geometry_is_identical(published):
"""Same bars, ticks, labels and coordinates as the published figure.
The series is recovered from the published SVG's own geometry rather than
from its printed labels: the note plots the unrounded value and prints the
rounded one, so reading the labels back would move every bar by a tenth of
a pixel and make the comparison meaningless.
"""
from dashboard.note_charts import bars_vs_guidance
svg = _fixture_figure(published, "revenue_vs_guidance")
scale = (274.0 - 50.4) / 100.0
bars = re.findall(
r'<rect x="[\d.]+" y="([\d.]+)" width="[\d.]+" height="[\d.]+" '
r'fill="var\((--[a-z0-9-]+)\)"',
svg,
)
labels = re.findall(r'y="290" text-anchor="middle" class="ct">([^<]+)</text>', svg)
printed = re.findall(r'class="cv">([\d,]+)</text>', svg)
deltas = re.findall(r'y="306" text-anchor="middle" class="cb">([^<]+)</text>', svg)
tick_y = re.findall(r'y1="([\d.]+)" y2="[\d.]+" stroke="var\(--warn\)"', svg)
guides = [None] + [(274.0 - float(y)) / scale for y in tick_y]
points = [
{
"label": label,
"value": (274.0 - float(bar[0])) / scale,
"value_text": printed[n],
"guide": guides[n],
"delta": deltas[n - 1] if n else "",
"derived": bar[1] == "--ink-3",
}
for n, (bar, label) in enumerate(zip(bars, labels))
]
assert len(points) == 14
mine = bars_vs_guidance(
points,
label="Chiffre d’affaires trimestriel et point mΓ©dian de la guidance",
footnote=_footnote(svg),
)
assert _normalise(mine) == _normalise(svg)
def test_paired_figure_geometry_is_identical(published):
from dashboard.note_charts import paired_bars
svg = _fixture_figure(published, "net_income_vs_fcf")
scale = (250.0 - 59.7) / 60.0
bars = re.findall(
r'<rect x="[\d.]+" y="([\d.]+)" width="[\d.]+" height="[\d.]+" fill="var\(--[a-z0-9-]+\)"',
svg,
)
printed = re.findall(r'class="cv">([\d,]+)</text>', svg)
labels = re.findall(r'y="266" text-anchor="middle" class="ct">([^<]+)</text>', svg)
ratios = re.findall(r'y="284" text-anchor="middle" class="(cb(?: warn)?)">([^<]+)</text>', svg)
groups = [
{
"label": label,
"left": (250.0 - float(bars[2 * n])) / scale,
"left_text": printed[2 * n],
"right": (250.0 - float(bars[2 * n + 1])) / scale,
"right_text": printed[2 * n + 1],
"ratio": ratios[n][1],
"ratio_warn": "warn" in ratios[n][0],
}
for n, label in enumerate(labels)
]
mine = paired_bars(
groups,
label="RΓ©sultat net contre free cash-flow par trimestre",
footnote=_footnote(svg),
)
assert _normalise(mine) == _normalise(svg)
def test_line_figure_geometry_is_identical(published):
from dashboard.note_charts import line_series
svg = _fixture_figure(published, "days_sales_outstanding")
scale = (226.0 - 28.0) / 40.0
dots = re.findall(r'<circle cx="[\d.]+" cy="([\d.]+)" r="(\d)"', svg)
printed = re.findall(r'class="cv">([\d,]+)</text>', svg)
labels = re.findall(r'y="242" text-anchor="middle" class="ct">([^<]+)</text>', svg)
points = [
{
"label": label,
"value": 30.0 + (226.0 - float(cy)) / scale,
"value_text": text,
"emphasis": radius == "6",
}
for (cy, radius), text, label in zip(dots, printed, labels)
]
mine = line_series(
points,
label="DΓ©lai de recouvrement clients en jours",
footnote=_footnote(svg),
)
assert _normalise(mine) == _normalise(svg)
def test_figures_declare_their_namespace():
"""Without xmlns, Streamlit's sanitiser drops the SVG and the figure empties."""
from dashboard.note_charts import bars_vs_guidance, line_series, paired_bars
bars = bars_vs_guidance([{"label": "Q1", "value": 1.0}], label="x")
pair = paired_bars([{"label": "Q1", "left": 1.0, "right": 0.5}], label="x")
line = line_series([{"label": "Q1", "value": 1.0}], label="x")
for svg in (bars, pair, line):
assert svg.startswith('<svg xmlns="http://www.w3.org/2000/svg"')
def test_figures_never_emit_a_bare_dollar_sign():
"""A pair of raw dollar signs is read as LaTeX by st.markdown."""
from dashboard.note_charts import bars_vs_guidance
svg = bars_vs_guidance(
[{"label": "Q1", "value": 1.0}], label="x", footnote="bn$ per share"
)
assert "$" not in svg
assert "&#36;" in svg
def test_the_stylesheet_contains_no_angle_bracket():
"""A "<" anywhere in the CSS truncates the <style> element that carries it.
This is not hypothetical: a comment in this stylesheet that mentioned the
svg tag by name cut the element off at that point, dropping every rule
after it β€” the note's grid, its sticky rail, and the app chrome CSS
concatenated behind it. The page still rendered, just with no layout.
"""
from dashboard.note_css import FONT_IMPORT, NOTE_CSS, TOKENS, TOKENS_LIGHT
for name, css in (("FONT_IMPORT", FONT_IMPORT), ("TOKENS", TOKENS),
("TOKENS_LIGHT", TOKENS_LIGHT), ("NOTE_CSS", NOTE_CSS)):
assert "<" not in css, f"{name} contains '<', which truncates the style element"
assert "</" not in css, f"{name} contains '</'"
# ── The document's structure ─────────────────────────────────────────────────
PUBLISHED_SECTIONS = (
"resume", "print", "cash", "bilan", "segments",
"direction", "capital", "bullbear", "watch", "annexes",
)
@pytest.fixture
def thin_brief() -> dict:
"""A brief with nothing in it β€” the case that must not lose a section."""
return {
"ticker": "ZZZZ",
"company_name": "Nothing Ingested Inc",
"filing_date": "2026-01-31",
"what_matters_most": "",
"what_changed": [],
"bull_points": [],
"bear_points": [],
"what_to_watch": [],
}
def _sections(html: str) -> list[str]:
return re.findall(r'<section id="([a-z]+)"', html)
def test_sections_are_the_published_ones_in_order(thin_brief):
"""The ten sections, in the published order, from any brief."""
from dashboard.note import build
assert tuple(_sections(build(thin_brief, series=[], market=None, lang="fr"))) == PUBLISHED_SECTIONS
def test_a_section_with_no_data_keeps_its_slot(thin_brief):
"""A section that vanished when empty would move every heading below it."""
from dashboard.note import build
html = build(thin_brief, series=[], market=None, lang="en")
assert len(_sections(html)) == len(PUBLISHED_SECTIONS)
# and it says so rather than standing empty
assert "ZZZZ" in html
def test_the_tile_row_is_always_six_wide(thin_brief):
"""The grid is repeat(6,1fr): five tiles would leave a hole in the rule."""
from dashboard.note import build
for brief, series in ((thin_brief, []),):
html = build(brief, series=series, market=None, lang="fr")
assert html.count('class="tile"') == 6
def test_the_document_never_emits_a_bare_dollar_sign(thin_brief):
"""st.markdown reads a pair of dollar signs as LaTeX and eats the middle."""
from dashboard.note import build
assert "$" not in build(thin_brief, series=[], market=None, lang="en")
def test_the_masthead_and_rail_are_always_present(thin_brief):
from dashboard.note import build
html = build(thin_brief, series=[], market=None, lang="fr")
for marker in ('class="mast"', 'class="head"', 'nav class="toc"',
'class="grid"', "<aside>", 'class="foot"'):
assert marker in html, f"{marker} missing"
def test_currency_follows_the_language(thin_brief):
""""96,2 Md$" in French, "$96.2bn" in English β€” same amount, same tile."""
from dashboard.note import _money, _per_share
with_lang = lambda fn, lang: fn(96_200_000_000, lang=lang) if fn is _money else fn(2.46, lang=lang)
assert _money(96_200_000_000, lang="fr") == "96,2 Md&#36;"
assert _money(96_200_000_000, lang="en") == "&#36;96.2bn"
assert _per_share(2.46, lang="fr") == "2,46 &#36;"
assert _per_share(2.46, lang="en") == "&#36;2.46"
def test_rounding_is_half_up_like_a_printed_table():
"""13.25 prints as 13.3, the way the filing does β€” not 13.2."""
from dashboard.note import _num
assert _num(13.25, 1, lang="en") == "13.3"
assert _num(13.25, 1, lang="fr") == "13,3"