Backtest & Learn (Operate)
Overview
The model should get measurably better at this business over time. This skill
scores past forecasts against the company's actuals, surfaces what keeps missing,
and proposes improvements a human ratifies. The objective metric is reconciliation
error against the user's own books (pyfpa.score_forecast) - the FP&A analog of a
validation loss.
Core principle: self-experimenting, but never self-promoting. The AI may run
and discard bounded challengers autonomously; a human approves replacement of
the champion. Everything learned lives as plain files in .fpa/.
Memory (.fpa/)
forecasts/<period>.snapshot.yaml - each forecast's assumptions + predictions, and (after close) its score.
scorecard.md - the running track record (rendered, never hand-edited).
experiments/<slug>.experiment.yaml - each tested model change, its evidence,
changed files, checks, before/after metrics, and decision.
learnings.md - every accepted change: what, the evidence, the backtest delta, the date.
Workflow
- Snapshot every forecast. When you produce a forecast, persist it:
snapshot_forecast(cfg, forecast_df, label=<period>, created=<today>) →
save_snapshot(..., ".fpa/forecasts/<period>.snapshot.yaml").
- Score at close. When a period closes (actuals via fpa-configure-actuals),
load that period's snapshot,
score_forecast(snap.predicted, actuals), write the
score back into the snapshot, and re-render scorecard.md with render_scorecard.
- Attribute each material per-line miss to a driver (volume / price / cost ratio
/ working-capital timing). Run the fpa-cfo-judgment one-time-item screen first -
never blame the model for a one-off.
- Monitor applied corrections: if a
type: parametric correction's target line keeps missing, flag it as possibly stale (applied → superseded) for the human - never auto-revert.
- Create an experiment before changing the model. State the financial
hypothesis, CFO question, evidence, fit periods, holdout periods, and files
expected to change. Save it with
pyfpa.save_experiment.
- Propose, tagged by type:
- Parametric (an assumption change): re-score it with
holdout_backtest on the
company's history. Surface it only if it lowers holdout fitness (not in-sample),
ranked by the delta. Clamp the proposed move with magnitude_cap (±25%/cycle).
- Structural (a methodology/skill change, e.g. a revenue-recognition lag): surface
only when
persistent_miss is true for the line (same-signed across K≥2 closes)
and it survived the one-time screen. Hand it to fpa-learn-business to generate the
skill on approval - propose, don't auto-write.
- Evaluate. Record before/after metrics and explicit checks in the experiment.
A model change that breaks reconciliation or another accounting invariant is
failed even if one headline metric improves.
- Ratify + log. Present proposals; the human accepts/rejects. On accept, add
an
ExperimentDecision, set status: accepted, save with explicit
overwrite=True, update the company model, and append to learnings.md.
Rejected and reverted experiments remain in memory.
- Run
fpa-research-loop when the miss warrants multiple autonomous
challenger epochs instead of one manually proposed change.
Bootstrap (day one)
If the company already has ~12+ months of actuals, you don't have to wait: run
holdout_backtest immediately (fit on the earlier months, hold out the recent ones)
to report current accuracy and the first round of parametric proposals.
Guardrails (always)
- Never score on data the model was fit on (
holdout_backtest enforces this).
- No proposal off a single period - require a persistent, same-signed miss.
- A parametric change must improve holdout fitness, not in-sample fit.
- Cap how far any assumption moves per cycle (
magnitude_cap).
- Human ratifies champion promotion; autonomous failed epochs are logged and reversible.
- Missing or zero actuals are insufficient evidence, not perfect forecast performance.
Next
Scored + learned → fpa-board-briefing (report the forecast and how it's tracking).
1---2name: fpa-backtest-learn3description: Use when you want the model to learn from how its past forecasts actually turned out - scoring forecasts against the company's real actuals, backtesting assumptions on history, and proposing ratified improvements. Runs at/after monthly close.4---56# Backtest & Learn (Operate)78## Overview910The model should get measurably better at this business over time. This skill11scores past forecasts against the company's actuals, surfaces what keeps missing,12and proposes improvements a human ratifies. The objective metric is reconciliation13error against the user's own books (`pyfpa.score_forecast`) - the FP&A analog of a14validation loss.1516**Core principle:** self-experimenting, but never self-promoting. The AI may run17and discard bounded challengers autonomously; a human approves replacement of18the champion. Everything learned lives as plain files in `.fpa/`.1920## Memory (`.fpa/`)2122- `forecasts/<period>.snapshot.yaml` - each forecast's assumptions + predictions, and (after close) its score.23- `scorecard.md` - the running track record (rendered, never hand-edited).24- `experiments/<slug>.experiment.yaml` - each tested model change, its evidence,25 changed files, checks, before/after metrics, and decision.26- `learnings.md` - every accepted change: what, the evidence, the backtest delta, the date.2728## Workflow29301. **Snapshot every forecast.** When you produce a forecast, persist it:31 `snapshot_forecast(cfg, forecast_df, label=<period>, created=<today>)` →32 `save_snapshot(..., ".fpa/forecasts/<period>.snapshot.yaml")`.332. **Score at close.** When a period closes (actuals via **fpa-configure-actuals**),34 load that period's snapshot, `score_forecast(snap.predicted, actuals)`, write the35 score back into the snapshot, and re-render `scorecard.md` with `render_scorecard`.363. **Attribute** each material per-line miss to a driver (volume / price / cost ratio37 / working-capital timing). **Run the fpa-cfo-judgment one-time-item screen first** -38 never blame the model for a one-off.39 - **Monitor applied corrections:** if a `type: parametric` correction's target line keeps missing, flag it as possibly stale (`applied → superseded`) for the human - never auto-revert.404. **Create an experiment** before changing the model. State the financial41 hypothesis, CFO question, evidence, fit periods, holdout periods, and files42 expected to change. Save it with `pyfpa.save_experiment`.435. **Propose**, tagged by type:44 - **Parametric** (an assumption change): re-score it with `holdout_backtest` on the45 company's history. Surface it **only if it lowers holdout fitness** (not in-sample),46 ranked by the delta. Clamp the proposed move with `magnitude_cap` (±25%/cycle).47 - **Structural** (a methodology/skill change, e.g. a revenue-recognition lag): surface48 **only** when `persistent_miss` is true for the line (same-signed across K≥2 closes)49 and it survived the one-time screen. Hand it to **fpa-learn-business** to generate the50 skill *on approval* - propose, don't auto-write.516. **Evaluate.** Record before/after metrics and explicit checks in the experiment.52 A model change that breaks reconciliation or another accounting invariant is53 failed even if one headline metric improves.547. **Ratify + log.** Present proposals; the human accepts/rejects. On accept, add55 an `ExperimentDecision`, set `status: accepted`, save with explicit56 `overwrite=True`, update the company model, and append to `learnings.md`.57 Rejected and reverted experiments remain in memory.588. **Run `fpa-research-loop`** when the miss warrants multiple autonomous59 challenger epochs instead of one manually proposed change.6061## Bootstrap (day one)6263If the company already has ~12+ months of actuals, you don't have to wait: run64`holdout_backtest` immediately (fit on the earlier months, hold out the recent ones)65to report current accuracy and the first round of parametric proposals.6667## Guardrails (always)6869- Never score on data the model was fit on (`holdout_backtest` enforces this).70- No proposal off a single period - require a persistent, same-signed miss.71- A parametric change must improve **holdout** fitness, not in-sample fit.72- Cap how far any assumption moves per cycle (`magnitude_cap`).73- Human ratifies champion promotion; autonomous failed epochs are logged and reversible.74- Missing or zero actuals are insufficient evidence, not perfect forecast performance.7576## Next7778Scored + learned → **fpa-board-briefing** (report the forecast and how it's tracking).