Download engine/tests/test_render_pdf.py from s-ttp/crucible: direct link, hf CLI and curl.
- Browser
- Download file 7.9 kB
-
https://huggingface.co/spaces/s-ttp/crucible/resolve/main/engine/tests/test_render_pdf.py
- Command line
-
hf download hf://spaces/s-ttp/crucible/engine/tests/test_render_pdf.py
-
curl -L -o test_render_pdf.py https://huggingface.co/spaces/s-ttp/crucible/resolve/main/engine/tests/test_render_pdf.py
7.9 kB
| """The printed Fundamentals and forecast table (the app's "Print / PDF"): the report HTML inside the page's own | |
| stylesheet, every <details> 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"<!doctype html><html><head><meta charset='utf-8'><style>{CSS}{extra_css}</style></head><body class='has-analysis'>" | |
| f"<main><section id='analysis' class='analysis'><div id='report'>{report_html}</div></section></main></body></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) | |