Apple revenue: read the export, anchor the inputs, and explain the posterior
Start with the introduction for terminology. This notebook loads real ADC model data and follows one fiscal period through both methods. Each figure comes from the loaded data and the installed library.
Question: How do an existing model forecast and consensus become an explainable estimate for Apple’s revenue?
Route: Inspect the export → choose the reading moment → decompose the anchored estimate → construct the Bayesian posterior → inspect refusals.
Scope: A worked calculation, not a performance evaluation or a trading backtest.
Run just install in the postforecast checkout and select .venv/bin/python as the kernel. Run the notebook from docs/, alongside pf.py. The export files stay in the source repository; no vendor client or credential is needed to read an already downloaded export.
Alternate location: Set POSTFORECAST_EXPORT to a directory containing universe.parquet and signals/. The buy-side study’s BSC_SIGNALS layout has the same structure; point this variable at that directory to use it.
Pinned selection: The directory, entity, target period, and as-of date are explicit below. Changing the export requires reviewing model provenance and the anchoring coefficient choice, not merely rerunning the cells.
Missing data: Execution stops with the missing path; synthetic data is never substituted. Existing rendered outputs remain readable without local exports.
import hashlibimport osfrom pathlib import Pathimport matplotlib.pyplot as pltimport numpy as npimport pandas as pdimport tutorial_support as examplesfrom IPython.display import display as showimport postforecast as pfexamples.set_style()export_root = Path( os.environ.get("POSTFORECAST_EXPORT","~/repos/adc-models/data/customers-371/line-items/190/2026-09-20", )).expanduser()ticker ="AAPL"period ="2026-09-26"as_of = pd.Timestamp("2026-09-22", tz="UTC")parameters = pf.CORE_2025_01universe = pd.read_parquet(export_root /"universe.parquet")company = universe.loc[universe.ticker.eq(ticker) & universe.country.eq("US")].squeeze()entity = company.entity_idpd.DataFrame( {"setting": ["company","entity","period end","as of (exclusive UTC)","export directory","coefficient version", ],"value": [ company.company_name, entity, period, as_of.strftime("%Y-%m-%d"), export_root.name, parameters.version, ], })
setting
value
0
company
Apple, Inc.
1
entity
graph:entity::company::F_000C7F-E
2
period end
2026-09-26
3
as of (exclusive UTC)
2026-09-22
4
export directory
2026-09-20
5
coefficient version
core-2025-01
2. Load observations, retaining their provenance
Live model:model_prediction supplies the current forecast.
Historical model:model_pit_backtest supplies reconstructed historical forecasts. Keeping it separate prevents silently splicing different source roles.
Consensus: The analyst reference level for the same entity and period.
Actual: The outcome observations available in this export, including revisions.
Memory: Parquet filters select one entity before loading its rows into pandas.
Dates: These files encode availability as dates. Parsing as UTC midnight preserves that date convention; it does not establish intraday delivery timing.
The hashes identify the exact files read. The directory’s date is a label, not a substitute for checking the timestamps inside those files.
ImportantA historical cutoff is not a historical model snapshot
The ADC repository’s docs/export-vintage-analysis.md documents that later exports can contain changed historical forecasts after model replacement. model_pit_backtest reconstructs history using the selected model and stored input versions. Its known_at column alone does not prove that model selection, tuning, or vendor data existed as shown on the historical date.
The calculations below respect the timestamps supplied. They demonstrate the methodology on this export, without claiming a tradable historical record.
3. Form a tidy estimate panel
One row means one source’s value for one subject at one availability time. The source’s fiscal period-end date becomes the opaque period label, avoiding an incorrect assumption about calendar quarters. Monetary values retain their export units throughout calculation; charts scale them to billions.
Extra provenance columns such as model_id remain attached. The panel validator rejects duplicate observation keys instead of averaging conflicting records.
The first chart follows one target period through availability time. It is not a chart of revenue across quarters. A source can revise the same period many times; only its latest observation strictly before the cutoff is read.
The 30-day marker is the earlier consensus reading used for the revision. A line ending before the cutoff means the latest available observation is older than the reading moment; the library does not invent an update.
Revision contribution:\(c\) times the consensus revision.
Frozen coefficients:CORE_2025_01 is the shipped Core example, trained through 2024-12-31. Applying it to this later export demonstrates the arithmetic; it does not validate those coefficients for the export’s changed model vintage.
Missing earlier consensus: The method explicitly drops the revision term when no revision can be measured. Other missing required inputs can cause refusal.
The contribution chart measures percentage points of expected surprise, not revenue dollars. The contributions must sum to the published surprise.
Figure 2: Three contributions sum to expected surprise.
6. Build the independent Bayesian posterior
The Bayesian route learns uncertainty from the company’s realised history. Historical model and consensus readings are selected strictly before each period’s first actual timestamp present in the export. Forward moments use only actuals available before the outer as_of moment.
First observed actual: This API retains the first actual in the export for each period. If earlier original reports are absent, that is a provenance gap; later data cannot repair it by relabelling a timestamp.
Prior growth: Mean and sample variance of sequential realised growth.
Model uncertainty: Mean squared historical model growth error.
Consensus uncertainty: Mean squared historical consensus growth error.
Separate histories:backtest_source="model_history" learns from reconstructed history, while forecast_source="model" supplies the current reading.
Basic configuration: Bias correction and analyst-dispersion weighting stay off so every weight is explained by the historical errors. The independent reference covers those extensions.
For precisions \(\tau_i\) and corresponding growth readings \(g_i\):
The same previous actual \(A_{\mathrm{prev}}\) converts every reading to growth: \(g_M=M/A_{\mathrm{prev}}-1\) and \(g_C=C/A_{\mathrm{prev}}-1\). The posterior level is \(A_{\mathrm{prev}}(1+\mu_{\mathrm{post}})\).
settings = pf.BayesianPredictionSettings( version="adc-walkthrough-independent-v1", method="independent", series="predictions", values=("prediction","prediction-lower","prediction-upper","prior-mean","model-growth","consensus-growth","posterior-growth","prior-precision","model-precision","consensus-precision","prior-weight","model-weight","consensus-weight", ),)posteriors = pf.bayesian_kpi_predictions( panel, as_of, forecast_source="model", backtest_source="model_history", settings=settings,)posterior = posteriors.loc[posteriors.period.eq(period)].iloc[0]ingredients = pd.DataFrame( {"Ingredient": ["Prior", "Model", "Consensus"],"Growth": [ posterior["prior-mean"], posterior["model-growth"], posterior["consensus-growth"], ],"Precision": [ posterior[f"{name}-precision"] for name in ["prior", "model", "consensus"] ],"Weight": [ posterior[f"{name}-weight"] for name in ["prior", "model", "consensus"] ], })ingredients["Weighted growth"] = ingredients.Growth * ingredients.Weightshow(ingredients)show( posteriors.loc[ posteriors.period.eq(period), ["period", "prediction", "prediction-lower", "prediction-upper", "eligible"], ])
Figure 3: Estimated precision determines each weight.
7. Compare the answers in revenue units
Anchored estimate: The frozen rule expresses expected surprise relative to consensus. Its intercept and revision term can place it outside the gap between the model and consensus.
Bayesian posterior: The independent precision-weighted combination operates in growth space and includes the historical prior as a third ingredient.
Interval: The displayed band uses the reference’s heavy-tailed Student-t settings. A broad interval exposes uncertainty; it is not evidence of measured 90% coverage on this company.
No winner inferred: This single example cannot establish which method is more accurate. That requires a separate, provenance-aware evaluation.
Farther-out periods may have consensus but no live model. The output retains those subjects and their reasons. A diagnostic or a consensus value alone is not permission to treat a refused prediction as published.
Removing the live forecast demonstrates an anchored refusal on the same subject. The original calculation remains unchanged.
These executable checks verify the arithmetic and that filtering out observations at or after as_of leaves the Bayesian result unchanged. They cannot establish upstream historical availability; that requires the model and vendor provenance.
The audit payload records method settings and file hashes. Store it alongside the input snapshot and the exact installed postforecast revision in the consuming application.
Checks passed: attribution, weights, posterior, cutoff, and refusal.
Try the methodology yourself
Move the reading moment: Choose an earlier as_of within the live-model coverage and rerun. Observe which timestamps are selected and whether the freshness rules still permit publication.
Change the target: Use another fiscal period visible in the output. Keep all historical input periods so the posterior retains its training history.
Raise the history threshold: Increase minimum_observations and inspect eligibility, missing weights, and intervals. The independent reference has a documented forward two-way update when model precision is unavailable; read that rule before interpreting such a result.
Inspect a different model: Change the ticker and select a matching fiscal period. Confirm coverage and units before comparing the result.
A production caller additionally decides which export vintage, source freshness policy, parameter calibration, and interval validation are appropriate for its use. The Bayesian API does not inherit the anchored method’s age limits automatically.