Worked Example
A concrete example makes the model easier to reason about.
Assume an arrangement with four instalments.
| Sequence | Due Date | Scheduled | Paid | Unpaid |
|---|---|---|---|---|
| 1 | 2026-01-05 | £100 | £0 | £100 |
| 2 | 2026-02-05 | £100 | £0 | £100 |
| 3 | 2026-03-05 | £100 | £0 | £100 |
| 4 | 2026-04-05 | £100 | £0 | £100 |
Arrangement-level state at creation:
{
"arrangementCreatedDate": "2026-01-01",
"balanceAtArrangementCreation": 400,
"lastApportionedBalance": 400,
"unappliedNegativeDelta": 0,
"overpaidAmount": 0
}
At this point, total arrears is £400.
Snapshot 1: Balance Decreases A new trusted snapshot arrives:
{
"balanceProcessedDate": "2026-02-10",
"currentBalance": 250
}
Baseline:
Baseline = 400
Delta:
delta = baseline - currentBalance
delta = 400 - 250
delta = 150
Because delta > 0, £150 is eligible for allocation.
Allocation waterfall:
1.Instalment 1 receives £100
- paid = £100
- unpaid = £0
- remaining = £50
2.Instalment 2 receives £50
- paid = £50
- unpaid = £50
- remaining = £0
Updated instalment state:
| Sequence | Due Date | Scheduled | Paid | Unpaid | isMissed | nextUnpaid |
|---|---|---|---|---|---|---|
| 1 | 2026-01-05 | £100 | £100 | £0 | false | false |
| 2 | 2026-02-05 | £100 | £50 | £50 | true | true |
| 3 | 2026-03-05 | £100 | £0 | £100 | false | false |
| 4 | 2026-04-05 | £100 | £0 | £100 | false | false |
Why is instalment 2 missed?
Because:
dueDate <= balanceProcessedDate
unpaidAmount > tolerance
Persisted arrangement-level state becomes:
{
"baseline": 400,
"delta": 150,
"appliedDelta": 150,
"overpaidAmount": 0,
"unappliedNegativeDelta": 0,
"balanceProcessedDate": "2026-02-10",
"lastApportionedBalance": 250
}
Snapshot 2: Balance Decreases Again
Another trusted snapshot arrives:
{
"balanceProcessedDate": "2026-03-07",
"currentBalance": 120
}
New baseline:
baseline = lastApportionedBalance = 250
Delta:
delta = 250 - 120 = 130
Allocation waterfall:
1.Instalment 2 receives £50
- paid = £100
- unpaid = £0
- remaining = £80
2.Instalment 3 receives £80
- paid = £80
- unpaid = £20
- remaining = £0
Updated instalment state:
| Sequence | Due Date | Scheduled | Paid | Unpaid | isMissed | nextUnpaid |
|---|---|---|---|---|---|---|
| 1 | 2026-01-05 | £100 | £100 | £0 | false | false |
| 2 | 2026-02-05 | £100 | £100 | £0 | false | false |
| 3 | 2026-03-05 | £100 | £80 | £20 | true | true |
| 4 | 2026-04-05 | £100 | £0 | £100 | false | false |
Persisted arrangement-level state:
{
"baseline": 250,
"delta": 130,
"appliedDelta": 130,
"overpaidAmount": 0,
"unappliedNegativeDelta": 0,
"balanceProcessedDate": "2026-03-07",
"lastApportionedBalance": 120
}
Snapshot 3: Balance Increases
A later snapshot arrives:
{
"balanceProcessedDate": "2026-03-08",
"currentBalance": 140
}
base line:
baseline = 120
Delta:
delta = 120 - 140 = -20
Because delta < 0, the model does not reverse instalment state.
Instead:
unappliedNegativeDelta += 20
Updated arrangement-level state:
{
"baseline": 120,
"delta": -20,
"appliedDelta": 0,
"overpaidAmount": 0,
"unappliedNegativeDelta": 20,
"balanceProcessedDate": "2026-03-08",
"lastApportionedBalance": 140
}
Instalment state remains unchanged. This is deliberate.
The signal does not tell us whether the increase came from:
- a fee
- a reversal
- a correction
- some other adjustment So the model records the increase explicitly instead of inventing a reversal.
Snapshot 4: Overpayment Case
Assume a later trusted snapshot:
{
"balanceProcessedDate": "2026-04-10",
"currentBalance": 0
}
Current baseline:
baseline = 140
Delta:
delta = 140 - 0 = 140
Current unpaid across instalments:
- Instalment 3 unpaid = £20
- Instalment 4 unpaid = £100
Total unpaid = £120
Allocation waterfall:
1.Instalment 3 receives £20
- unpaid becomes £0
- remaining = £120
2.Instalment 4 receives £100
- unpaid becomes £0
- remaining = £20 All instalments are now fully paid.
Remaining £20 becomes:
overpaidAmount += 20
Final arrangement-level state:
{
"overpaidAmount": 20
}
This shows an important property of the model:
Excess positive delta is not discarded. It is captured explicitly as state.
Worked Example: What This Demonstrates
This example shows five important properties of the model.
- Allocation is incremental, not full recomputation.
- The waterfall is strictly sequence-based.
- Negative delta is recorded, not inferred as reversal.
- Status evaluation uses balanceProcessedDate, not processing time.
- Overpayment is preserved explicitly instead of being silently truncated.
State Diagram: Meaning of Each Stage
SnapshotReceived
A host snapshot arrives with:
- arrears balance
- balance processed date At this point, nothing is trusted yet.
ValidateSnapshot
The snapshot is accepted only if it is structurally safe to process. Examples of rejection conditions:
- it predates arrangement creation
- it is older than the last trusted processed snapshot This protects the state model from back-propagation and out-of-order mutation.
DetermineBaseline
The system chooses the reference point against which balance movement will be measured.
[-] first run: use balanceAtArrangementCreation [-] later runs: use lastApportionedBalance
This step is critical because all later logic depends on baseline integrity.
ComputeDelta
The system calculates:
delta = baseline - currentBalance
This is the only allocatable input.
The model does not reconstruct historical transactions or replay the schedule from scratch.
PositiveDelta
If * delta > 0 *, arrears has reduced.
That reduction is treated as value eligible for instalment allocation.
The model proceeds into the contractual waterfall.
AllocateWaterfall
The system allocates delta sequentially across instalments in ascending sequence number.
This is where the model converges positive balance movement into contractual payment state.
NegativeDelta
If ** delta < 0 **, arrears increased.
The model refuses to guess whether this increase reflects:
- fees
- reversals
- adjustments
So it does not mutate instalments.
RecordNegativeDelta
The increase is captured explicitly as:
unappliedNegativeDelta
This preserves visibility without silently undoing prior state.
ZeroDelta
If delta = 0, no balance movement occurred relative to baseline.
The allocation state remains unchanged.
This is what makes duplicate snapshot replay safe.
RecalculateStatuses
After delta handling, the system recalculates:
- missed instalments
- next unpaid instalment
These are derived from the same temporal reference:
balanceProcessedDate
This keeps payment state and contractual status aligned.
PersistState
All changes must be persisted atomically.
That includes:
- updated arrangement metadata
- updated instalment state
- baseline progression
- audit metadata
If this step is not atomic, future delta calculations become unsafe.
Converged
At this point, the system has safely incorporated the latest trusted snapshot into a deterministic state.
This is the new trusted point for the next run.