Topic

Instalment Allocation Using Balance Deltas Instead of Transactions

• 2 min read
📖 Detailed standalone
Article preview

This article explains how to allocate instalments when systems only receive periodic balance snapshots rather than transaction data. Instead of assuming payment events, it calculates the change between balance snapshots and applies that inferred movement safely across instalments. By anchoring calculations to a trusted baseline and evolving state incrementally, the model preserves determinism, auditability, and consistent account behaviour even when the underlying events are not visible.

balance snapshot processing instalment allocation deterministic system design arrears management baseline state modeling financial systems integration

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.

  1. Allocation is incremental, not full recomputation.
  2. The waterfall is strictly sequence-based.
  3. Negative delta is recorded, not inferred as reversal.
  4. Status evaluation uses balanceProcessedDate, not processing time.
  5. Overpayment is preserved explicitly instead of being silently truncated.
Allocation Lifecycle
Allocation Lifecycle

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.

Illustrative Allocation Model
Illustrative Allocation Model

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.