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.
Alternate location: Set POSTFORECAST_EXPORT to a directory containing universe.parquet and signals/.
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 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","ticker","period end","as of (exclusive UTC)","export directory","coefficient version", ],"value": [ company.company_name, ticker, period, as_of.strftime("%Y-%m-%d"), export_root.name, parameters.version, ], })
setting
value
0
company
Apple, Inc.
1
ticker
AAPL
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 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
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 input 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.
# Readers know the company by its ticker, not by the vendor entity identifier.panel = raw.rename(columns={"period_end_date": "period"}).assign( entity=ticker, target="revenue", period_end=pd.to_datetime(raw.period_end_date, utc=True), known_at=pd.to_datetime(raw.known_at, utc=True),)panel = pf.validate_estimates(panel)coverage = panel.groupby("source", as_index=False).agg( observations=("value", "size"), periods=("period", "nunique"), first_available=("known_at", "min"), last_available=("known_at", "max"),)show(examples.format_dates(coverage))show( examples.format_dates( panel.loc[ panel.period.eq(period), ["entity", "period", "target", "source", "known_at", "value"], ].tail(8) ))
source
observations
periods
first_available
last_available
0
actual
17104
75
2008-01-22
2026-09-13
1
consensus
10014
66
2012-10-02
2026-09-16
2
model
22
2
2026-09-10
2026-09-20
3
model_history
339
15
2023-06-05
2026-09-07
entity
period
target
source
known_at
value
25957
AAPL
2026-09-26
revenue
model_history
2026-07-20
111,894,639,969
25958
AAPL
2026-09-26
revenue
model_history
2026-07-27
109,422,551,905
25959
AAPL
2026-09-26
revenue
model_history
2026-08-03
116,314,703,478
25960
AAPL
2026-09-26
revenue
model_history
2026-08-10
115,241,576,831
25961
AAPL
2026-09-26
revenue
model_history
2026-08-17
114,044,294,161
25962
AAPL
2026-09-26
revenue
model_history
2026-08-24
114,073,213,976
25963
AAPL
2026-09-26
revenue
model_history
2026-08-31
114,566,340,305
25964
AAPL
2026-09-26
revenue
model_history
2026-09-07
114,245,600,391
4. Freeze the reading moment
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.
Model contribution: the gap weight \(b_\Delta\) times the current gap \(\Delta\).
Revision contribution: the revision weight \(b_r\) times the consensus revision \(r\).
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 \(\hat s\).
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 notebook selects the first actual in the export before preparation. The posterior API otherwise uses the latest known actual. If earlier original reports are absent, that is a provenance gap; later data cannot repair it by relabelling a timestamp.
Prior growth: Mean \(\mu_0\) and sample variance \(\sigma_0^2\) of sequential realised growth \(g_i\).
Model uncertainty: Mean squared historical model growth error, \(\sigma_M^2\).
Consensus uncertainty: Mean squared historical consensus growth error, \(\sigma_C^2\).
Separate histories: The notebook uses reconstructed forecasts for earlier periods and the live model for the target period, then explicitly names that selected series model for posterior preparation. Original source labels remain recorded in the input panel.
Basic configuration: Bias correction and analyst-dispersion weighting stay off so every weight is explained by the historical errors. The posterior reference covers those extensions.
The same previous actual \(y_{t-1}\) converts every reading to growth: \(x_M=m/y_{t-1}-1\) and \(x_C=c/y_{t-1}-1\). With precisions \(\tau_j=1/\sigma_j^2\), the weights \(w_j\) and the posterior growth \(\hat\theta\) are:
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 plain preset’s fitted Student-t shape. Its research training cutoff is unknown; see preset provenance. 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 rows loaded per source. 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 posterior still combines prior and consensus 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.