What actually runs when you press refresh?
The interface is React and TypeScript. The calculations run in Python, and FastAPI exposes them over HTTP. A saved result lets the page work when the service is unavailable.
Start with reviewed source data
The figures come from the public Novartis report. Selected tables were transcribed into JSON and checked against the report. Each fact retains its period, unit, accounting basis and source page. This version does not automatically extract or understand an arbitrary PDF.
Validate and calculate in Python
The backend checks the shape and meaning of the input, including expense signs, compatible periods and complete financial lines. Ordinary Python arithmetic produces the changes, margins, shares and reconciliation checks. An LLM does not calculate these numbers.
Expose the calculation through FastAPI
backend/app.pydefines the HTTP routes.GET /api/reviewbuilds the focused quarterly review;GET /api/reportbuilds the wider explorer dataset. These routes call the Python functions on the server and return JSON.Let the interface ask different questions
The React page displays the returned dataset as charts and tables. Period changes, product sorting and source searches happen in the browser. Refreshing makes a new request to the Python service. The page labels whether it is showing that response or a saved calculation.
Try inputs that should fail
The test suite changes signs, periods, units and totals on purpose. A test passes when the calculation rejects the bad input. Saved-output checks also compare a fresh calculation with the version shipped in the site. These tests check this dataset and its rules, not the financial accuracy of every possible document.
The route is real
This excerpt is read directly from the backend source when the site is built. FastAPI registers the URL; build_review() does the work.
@application.get("/api/review")
def review():
report = build_review()
report["model"] = current_model_status()
return reportThe service’s interactive API documentation lists the routes and lets you inspect responses. FastAPI generates this documentation from its route definitions and declared schemas. The financial response itself is checked by the pipeline and tests.
A check that matters
It is not enough for the changes to add up. Both original income statements must also reconcile. Otherwise, the same error in both years could cancel out and leave a convincing movement chart.
income, sales = by_id["operating_income"], by_id["net_sales"]
delta = income["current"] - income["prior"]
residual = delta - sum(c["delta"] for c in contributions)
# Reject an offsetting error that reconciles deltas but breaks both P&L totals.
for period in ("prior", "current"):
if sum(by_id[key][period] for key in CONTRIBUTION_IDS) != income[period]:
raise ValueError(f"{period} income-statement total does not reconcile")
if residual:
raise ValueError("the income movement does not reconcile")This is the actual check used in the focused quarterly review. The wider report applies the same idea to income, cash, balance-sheet totals and the company’s non-IFRS reconciliations.
Why keep a saved result?
The portfolio should still be readable when its Python service is unavailable. The saved JSON is a generated artifact from the same code, with source identifiers and validation results. It is a fallback, not evidence that a request ran successfully. That is why the mode stays visible.
What I delegated, and what I check
I used Codex to help build this example and independent agents to check its figures and claims. The source choices, expense signs, reconciliation rules and displayed results are the parts I need to review. Generating the code does not settle those decisions.
The optional OpenRouter adapter is disabled. Its limited task would be selecting from approved statements. It has not been evaluated with live model calls. The working explorer is a financial-data application, and it does not pretend to be an autonomous analyst.
Follow one number through the system
On page 17, fourth-quarter net sales are 13,336 USD million for 2025 and 13,153 for 2024. In data/novartis-q4-2025.json, the net_sales record keeps both values, their unit and the income-statement source reference.
Python subtracts the prior value from the current value: 13,336 − 13,153 = +183. That becomes the net-sales contribution in the operating-income bridge. FastAPI returns the result, and React draws the bar. The other six contributions bring the total movement to +86 USD million.
One failure check changes an expense to a positive number. Another changes both operating-income totals by the same amount. The first catches a sign error; the second catches an error that a check of the net movement alone would miss.
Inspect or run it yourself
The source package includes setup instructions and pinned dependencies. The public site runs the Python service as a Vercel Function. For local development, the same application can run with Uvicorn. The data comes from the published report.