A loan owes €500 on 1 August. The borrower pays €300 on 10 August and €200 on 20 August. At 31 August, is the account current, partially delinquent, cured—or 30 days past due?
The answer depends on the due-item structure, allocation, effective dates, calendar, materiality and grace policy. A standalone dpd field cannot establish any of those semantics.
Start with versioned contractual obligations
type DueItem = {
dueItemId: string;
dueDate: string;
contractualAmountMinor: bigint;
currency: string;
scheduleVersion: string;
effectiveFrom: string;
};A schedule is an ordered set of due items. Restructure, reschedule, payment holiday, maturity change or correction creates new effective-dated schedule state; it must not overwrite the schedule that governed a historical decision.
Each obligation remains individually reconstructible. Total arrears alone cannot reveal which due date remains open, so it cannot fully explain DPD.
| Element | Question |
|---|---|
| Due item identity | Which contractual obligation is this? |
| Due date and amount | What became payable, and when? |
| Schedule version | Which contract state governed the account? |
| Effective interval | Was this schedule valid at decision time? |
| Adjustment lineage | Why did the obligation change? |
A payment event does not identify the obligation it satisfied
type DueAllocation = {
paymentEventId: string;
dueItemId: string;
amountMinor: bigint;
allocationVersion: string;
};One payment can create several allocation rows. An illustrative oldest-due-first rule applies €300 to the 1 August €500 item, leaving €200. That is not a universal hierarchy; the engine injects a governed, versioned policy and preserves the realised result.
The oldest relevant unpaid obligation anchors DPD
The threshold, inclusivity and day-count convention are policy inputs—not universal assumptions. If no relevant overdue item exists, current-state representation normally returns DPD = 0 rather than a negative number.
DPD policy belongs in explicit versioned inputs
Calendar days, business days or contractual business date; define the convention and holiday source.
Version thresholds and tolerance rules. Unpaid-with-grace is not the same as paid.
Many DPD methods operate at date granularity. Do not use a 23h59m timestamp difference to create a day transition. Store machine instants consistently, then derive contractual dates in the correct business timezone.
A payment effective at 23:58 local on 31 August may be stored as 1 September UTC. A naïve DATE(utc_timestamp) creates false delinquency. Preserve source zone and convert before deriving business date.
interface DpdPolicy {
version: string;
materialityMinor: bigint;
daysBetween(
dueDate: LocalDate,
asOfDate: LocalDate
): number;
isGraceEligible(dueItem: DueItemState, asOfDate: LocalDate): boolean;
}Partial, excess and advance payments have explicit state
| Scenario | Due-item effect | DPD consequence |
|---|---|---|
| Partial €300 against €500 | €200 remains on original obligation | DPD can continue from original due date |
| €600 against August €500 and September €500 | August clears; €100 applies to September | Oldest unpaid shifts to September; DPD steps down |
| Excess beyond arrears | Principal, future due or unapplied cash per policy | Never guess from amount alone |
| Advance payment | May prepay, reduce principal or remain unapplied | Future items change only under contract rules |
| Settled, not allocated | Cash exists while due item remains open | Operational hold may be warranted; not cure |
DPD can move 45 → 14 when payment clears the oldest instalment but leaves a more recent one unpaid. This step-down matters to roll rates. Cross-account payments require facility-aware allocation; customer-level payment totals cannot determine facility DPD.
Payment finality is also explicit: authorised, settled, posted and reversible are different states. Some systems may justify provisional operational DPD and confirmed DPD, but the trade-off and consumer contract must be visible.
Cure and reopen emerge from rebuilt obligations
Technical cure can be defined when no relevant overdue due item remains and DPD returns to zero under the approved policy. An explicit ACCOUNT_CURED event may support downstream orchestration, but it must remain explainable from the underlying state.
The payment that created technical cure was undone.
The borrower genuinely cured, then later deteriorated through a new event path.
Late payments create known and restated DPD
Uses schedule and events available at T; reproduces historical collections and model decisions.
Uses later evidence economically effective by T; supports reconciliation and corrected analysis.
A decision made at known DPD = 5 remains historically reproducible even if a backdated payment later makes restated DPD = 0. Do not overwrite the decision, model input or known-state series.
DPD = 5late payment arrivesRESTATED AT T
DPD = 0
Contract changes transform schedule state explicitly
| Change | Required event/state | Control |
|---|---|---|
| Restructure | Old/new schedule versions and effective date | Historical DPD retains old schedule |
| Payment holiday | Explicit schedule transformation | Never force DPD to zero silently |
| Waiver / adjustment | Due-item adjustment event | Do not disguise as payment allocation |
| Write-off | Accounting/lifecycle event | Does not automatically imply DPD = 0 |
| Reallocation | Corrected due-item allocation lineage | Restate DPD from obligations |
Default can use DPD among several criteria, but Default ≠ DPD. The DPD engine derives delinquency state; it is not the full default or accounting-stage engine.
Raw DPD, bucket, facility and customer state are distinct
Store raw DPD separately from a policy-versioned delinquency bucket. Bands such as CURRENT, EARLY, MID and LATE are illustrative; changing thresholds must not rewrite raw historical DPD.
| Object | Meaning |
|---|---|
| Facility/account DPD | Contractual obligation state for one consistency boundary |
| Customer delinquency | A later aggregation such as max, rule-based or exposure-aware state |
| DPD bucket | Versioned decision classification derived from raw DPD |
| Default state | Broader credit-risk outcome with criteria beyond DPD |
| Technical cure | No relevant overdue obligation; not sustainable recovery evidence |
Joint borrower identity does not change facility DPD. Behavioural scoring, roll rates, vintage curves, collections, PTP analysis and ECL consume this state differently, so the engine must expose levels and versions rather than one overloaded field.
The calculation function consumes reconstructed due-item state
type DelinquencyState = {
accountId: string;
asOfDate: string;
arrearsMinor: bigint;
oldestUnpaidDueDate?: string;
daysPastDue: number;
scheduleVersion: string;
allocationVersion: string;
dpdLogicVersion: string;
bucketPolicyVersion: string;
stateMode: "KNOWN" | "RESTATED";
};function calculateDpd(
asOfDate: LocalDate,
dueItems: DueItemState[],
policy: DpdPolicy
): number {
const overdue = dueItems
.filter((x) => x.remainingMinor > policy.materialityMinor)
.filter((x) => !policy.isGraceEligible(x, asOfDate))
.filter((x) => x.dueDate < asOfDate);
if (overdue.length === 0) return 0;
const oldest = overdue
.map((x) => x.dueDate)
.sort(compareDates)[0];
return policy.daysBetween(oldest, asOfDate);
}The example assumes comparable local dates and simple policy hooks. Production code must define inclusive boundaries, missing dates, contractual calendars and validated schedule state. The function recomputes after payment, reversal, adjustment or restructure; it never applies arithmetic such as dpd -= 30.
Golden streams make DPD arithmetic inspectable
| Checkpoint | Remaining 01 Aug | Oldest unpaid | DPD result |
|---|---|---|---|
| 01 Aug · €500 due | €500 | 01 Aug | Policy-defined day zero |
| 10 Aug · €300 paid | €200 | 01 Aug | 9 |
| 15 Aug · €200 paid | €0 | None | 0 · technical cure |
| 20 Aug · €200 reversed | €200 | 01 Aug | 19 · reopened |
Values assume calendar-day difference and no grace; they are fictional test semantics, not a universal convention.
| Checkpoint | 01 Aug due | 01 Sep due | 01 Oct due | Oldest unpaid | DPD |
|---|---|---|---|---|---|
| 14 Sep before payment | €500 | €500 | Future | 01 Aug | 44 |
| 15 Sep payment €700 | €0 | €300 | Future | 01 Sep | 14 |
| 01 Oct new due | €0 | €300 | €500 | 01 Sep | 30 |
| 10 Oct payment €400 | €0 | €0 | €400 | 01 Oct | 9 |
The second stream proves DPD step-down: payments clear older obligations and expose newer unpaid dates. It also proves that payment amount and total arrears alone are insufficient.
Replay and migration tests protect the definition
| Test | Proof |
|---|---|
| Partial / excess payment | Configured allocation; no accidental reset |
| Cure reversal | Arrears, oldest due and DPD rebuild |
| Backdated payment | Known and restated DPD both survive |
| Restructure | Old and new schedule versions reproduce |
| Midnight / timezone | Business-date result remains stable |
| Duplicate payment | Idempotency leaves DPD unchanged |
| Allocation version | Historical replay uses original rules |
| Snapshot parity | Snapshot + tail equals full replay |
Apply invariants only where product semantics permit. Before a system migration, replay representative historical accounts through old and new logic, compare distributions and investigate every material difference. Otherwise infrastructure change can masquerade as credit-risk drift.
Reconcile definitions; do not force-match outputs
| Account | Servicing DPD | Derived DPD | Difference | Reason |
|---|---|---|---|---|
| acc_1042 | 30 | 29 | −1 | Business-date boundary |
| acc_2208 | 0 | 12 | +12 | Payment allocation pending |
| acc_3191 | 45 | 14 | −31 | Schedule version mismatch |
Classify timing, schedule, allocation, reversal, materiality/grace and source defects. Monitor DPD distribution, zero-to-positive transitions, cure, reopen and reconciliation-difference rates without universal thresholds.
Sudden DPD collapse, spikes at one exact value, unusually high cure after batch or repeated next-day reopen can indicate infrastructure rather than borrower behaviour. Nightly-only transitions can also create artificial daily patterns.
The Entimema DPD architecture makes every day traceable
- Define schedule
- Define allocation
- Define effective payments
- Reconstruct remaining due
- Identify oldest unpaid obligation
- Apply calendar policy
- Derive DPD
- Classify delinquency
- Test replay
- Reconcile
A DPD Integrity & Delinquency Reconstruction Agent can explain state
A future controlled agent can reconstruct due items, match payments, identify unexplained arrears, compare servicing and derived DPD, trace payment/reversal transitions, find false cure or delinquency candidates, detect schedule mismatch, compare known/restated DPD and monitor migration drift.
Credit Risk
Stable behavioural, roll-rate, vintage, cure and ECL inputs.
Finance
Decision Automation
Fresh, explainable delinquency for collections and customer treatment.
Continue with Reversals, Chargebacks and Corrections, Late-Arriving Events and Backdated Corrections, Reconstructing Account State, Idempotency in Event Processing, Event Time vs Processing Time vs Posting Time, The Payment Is Not the Balance, Early Warning Systems, Behavioural Credit Scoring, Collections Prioritisation, Cure & Re-Default Analytics and Promise-to-Pay Analytics. Credit data models, point-in-time features and cross-function reconciliation are future research directions, not fabricated routes.