Step Cash Flows¶
The
track_incrementsAPI surface exists, but per-step series are not emitted —RollforwardBuilder(..., track_increments=True)andRollforwardCollector.increment_for(label)are stable, but callingincrement_for(...)when the model runs fails with aStructFieldNotFoundError. This page describes the API shape and the supported way to attribute a single step today: difference two runs.
The Problem: Where Did the Money Go?¶
IFRS 17 requires analysis of change — breaking down the movement in the contractual service margin, risk adjustment, and best-estimate liability into its component drivers. Profit testing requires understanding which charges generate margin and which are breakeven. Model validation requires reconciling the account value at each step.
Without increment tracking, the only way to isolate the impact of a single step is to run the model twice — once with the step enabled and once without — and difference the results. For a chain of five steps, that's six model runs to get a complete decomposition.
The API Shape¶
Set track_increments=True on the builder, label every step, and request each cash flow by label from the collector. Per-step emission is not implemented, so increment_for(...) raises StructFieldNotFoundError rather than returning a series:
# docs-skip
b = af.projection.rollforward(
states={"av": af["av_init"]},
track_increments=True,
)
b["av"].add(af["premium"], label="Premium")
b["av"].deduct_nar(
af["coi_rate"],
death_benefit=af["sum_assured"],
label="COI",
)
b["av"].charge(af["admin_rate"], label="Admin")
b["av"].grow(af["interest_rate"], label="Interest")
b["av"].floor(value=0.0)
compiled = compile_rollforward(b)
collector = RollforwardCollector(compiled)
af.av = collector.expr_for("av")
af.premium_added = collector.increment_for("Premium") # not emitted — raises
af.coi_charged = collector.increment_for("COI") # not emitted — raises
af.admin_charged = collector.increment_for("Admin") # not emitted — raises
af.interest_credited = collector.increment_for("Interest") # not emitted — raises
Each cash flow is defined as the dollar change the corresponding step causes at each period:
- Premium =
+premium[t]— added to AV - COI =
−coi_rate[t] × max(0, SA − AV)— deducted from AV - Admin =
−admin_rate[t] × AV— proportional fee reduces AV - Interest =
+interest_rate[t] × AV— growth increases AV
Positive values mean the step increased the account; negative values mean it decreased it.
The Reconciliation Invariant¶
Per-step cash flows are defined so they sum exactly to the total change in account value over each period:
# For every policy and every period t:
# av[t] − av[t−1] == Premium[t] + COI[t] + Admin[t] + Interest[t]
This is not an approximation — it is exact by construction. Each cash flow is the before-and-after balance difference for its step, so the telescoping sum equals the total change. That makes it suitable for regulatory reporting where reconciliation to the penny matters.
.floor(value=0.0) can break this invariant in periods where the account would otherwise have gone negative — the clamp absorbs the difference. To attribute that absorbed amount, reconstruct the pre-floor balance from the per-step cash flows and difference.
Labels Are Required¶
When track_increments=True is set, every step that takes a label must have one. This is enforced at compile time:
# docs-skip
b = af.projection.rollforward(states={"av": ...}, track_increments=True)
b["av"].add(af["premium"]) # ❌ no label — compile fails
ValueError: track_increments=True requires every label-bearing Op to have label=...; Add has label=None
Labels are the addressing mechanism for cash flows; silently auto-generating them would make audit trails hard to reconcile across model versions. .floor(value=...) does not carry a label — it's a clamp, not a labelled cash flow.
What Per-Step Cash Flows Are For¶
Per-step cash flows support:
- IFRS 17 analysis of change — split LRC/LIC movement into step-level drivers
- Profit-test attribution — allocate margin to charges and crediting
- Model validation — reconcile against an external reference model at each step
- Sensitivity analysis — read the COI cash flow directly without re-running with COI rates zeroed
To isolate a single step today, use the two-runs-and-difference approach: run the chain with and without the step and subtract.