"""The printed Fundamentals and forecast table (the app's "Print / PDF"): the report HTML inside the page's own stylesheet, every
opened, print media, Letter with 0.4in margins, printed by Chrome and read back with pdfplumber. A long provenance header once pushed both value columns off the printed page (SPGI b3779fcd20362bdf). Skipped where Playwright, Chrome or pdfplumber is unavailable (the Space does not run the test suite).""" import os import pathlib import sys import tempfile import pytest sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) pytest.importorskip("playwright") pdfplumber = pytest.importorskip("pdfplumber") CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" if not os.path.exists(CHROME): pytest.skip("Chrome is not installed", allow_module_level=True) import catalog as C # noqa: E402 import reconcile as RC # noqa: E402 import report_model as RM # noqa: E402 from test_spgi_lineage import good, patched # noqa: E402,F401 (the SPGI-shaped fixture) CSS = (pathlib.Path(__file__).resolve().parents[2] / "static" / "app.css").read_text() MARGIN_PT = 0.4 * 72 ROWS = (("Revenue", "REVENUE_TTM", "FORECAST_REVENUE"), ("Operating margin", "OPERATING_MARGIN_TTM", "FORECAST_OPERATING_MARGIN"), ("Net margin", "NET_MARGIN_TTM", "FORECAST_NET_MARGIN"), ("EPS (adjusted basis)", "EPS_ADJUSTED_TTM", "FORECAST_EPS")) def print_pdf(report_html, out, extra_css=""): from playwright.sync_api import sync_playwright page = (f"" f"
{report_html}
") with sync_playwright() as p: b = p.chromium.launch(executable_path=CHROME) pg = b.new_page() pg.set_content(page, wait_until="load") pg.evaluate("document.querySelectorAll('#report details').forEach(d => d.open = true)") # the app's beforeprint pg.emulate_media(media="print") pg.pdf(path=out, format="Letter", print_background=True, margin={k: "0.4in" for k in ("top", "bottom", "left", "right")}) b.close() def check_fundamentals(pdf_path, heads, values, footnotes): """Problems with the printed table (empty = the 12 checks pass). heads: the two column headings; values: {row label: (historical, forecast)}; footnotes: texts that must be printed.""" bad = [] with pdfplumber.open(pdf_path) as pdf: page = next((p for p in pdf.pages if "Fundamentals and forecast" in (p.extract_text() or "")), None) if page is None: return ["no page carries the Fundamentals and forecast table"] text = " ".join((page.extract_text() or "").split()) words = page.extract_words(keep_blank_chars=False, use_text_flow=True) top0 = next(w["top"] for w in words if w["text"] == "Fundamentals") for h in heads: # 9: both headings readable, not clipped if " ".join(h.upper().split()) not in text.upper(): bad.append(f"column heading '{h}' is not fully printed") table_words = [] for label, (hist, fcst) in values.items(): # 1–8: every row shows both values first = label.split()[0] lw = next((w for w in words if w["text"] == first and w["top"] > top0), None) if lw is None: bad.append(f"row '{label}' is not printed") continue line = [w for w in words if abs(w["top"] - lw["top"]) < 4 and w["x0"] >= lw["x0"]] table_words += line got = [w["text"] for w in line] for want, col in ((hist, "historical"), (fcst, "forecast")): if want not in got: bad.append(f"{label}: {col} value '{want}' is not visible (row prints {got})") if hist in got and fcst in got and got.index(hist) > got.index(fcst): bad.append(f"{label}: values out of column order") for w in table_words: # 12: inside the printable area if w["x0"] < MARGIN_PT - 2 or w["x1"] > page.width - MARGIN_PT + 2: bad.append(f"'{w['text']}' is outside the page margins ({w['x0']:.0f}–{w['x1']:.0f} of {page.width:.0f})") for i, a in enumerate(table_words): # 10: no cell text overlaps another for b in table_words[i + 1:]: if a["x0"] < b["x1"] - 0.5 and b["x0"] < a["x1"] - 0.5 and a["top"] < b["bottom"] - 0.5 and b["top"] < a["bottom"] - 0.5: bad.append(f"'{a['text']}' overlaps '{b['text']}'") for f in footnotes: # 11: provenance footnotes printed if " ".join(f.split()) not in text: bad.append(f"footnote not printed: '{f[:60]}…'") return bad def _spgi_report(st): cat = C.build_catalog(st, include_deferred=True) return RM.build_user_report(st, cat, {"summary": [], "thesis": [], "risks": [], "seats": {}}, []) def _expected(rep): i = next(i for i, b in enumerate(rep.blocks) if b.css == "rp-fund") tbl = rep.blocks[i] vals = {} for row in tbl.content["rows"]: label = "".join(f.text for f in row[0]) vals[label] = ("".join(f.text for f in row[1]), "".join(f.text for f in row[2])) feet = ["".join(f.text for f in b.content) for b in rep.blocks[i + 1:i + 3]] return tbl.content["head"][1:], vals, feet def _spgi_state_changes(st): reports = [{"fiscalDateEnding": e} for e in ("2026-06-30", "2026-03-31", "2025-12-31", "2025-09-30")] q = RC.ttm_quality({"status": "RECONCILED", "overrides": [], "unverified_quarters": ["2026-06-30"]}, reports) return [("historical", "reconciliation", dict(st.historical.get("reconciliation"), ttm_quality=q)), ("forecast", "consensus_anchor", {"eps": {"model_weight": 0.0, "reason": "RESTATEMENT"}, "revenue": {"model_weight": 0.0, "reason": "RESTATEMENT"}})] def test_printed_fundamentals_table_shows_every_value_and_footnote(good): st = good.state with patched(st, *_spgi_state_changes(st)): rep = _spgi_report(st) heads, vals, feet = _expected(rep) assert len(vals) == 4 and all(h and f for h, f in vals.values()) assert "through 2026-03-31" in feet[0] and "set from sell-side consensus" in feet[1] with tempfile.TemporaryDirectory() as d: out = os.path.join(d, "report.pdf") print_pdf(RM.render_html(rep), out) assert check_fundamentals(out, heads, vals, feet) == [] def test_the_check_catches_the_old_clipped_table(good): """Negative control: the old long provenance header with the old print behaviour (no wrapping, a scrolling container) must fail the check, as the published b3779fcd20362bdf PDF did.""" st = good.state with patched(st, *_spgi_state_changes(st)): rep = _spgi_report(st) heads, vals, feet = _expected(rep) tbl = next(b for b in rep.blocks if b.css == "rp-fund") tbl.content["head"][1] = RM.ttm_header(RM.historical_quality(st), True) old_css = "@media print{.rp-scroll{overflow-x:auto!important}.rp-t{width:auto!important;min-width:100%!important}" \ ".rp-t th{white-space:nowrap!important}}" with tempfile.TemporaryDirectory() as d: out = os.path.join(d, "old.pdf") print_pdf(RM.render_html(rep), out, extra_css=old_css) assert check_fundamentals(out, [tbl.content["head"][1], heads[1]], vals, feet)