Entimema

Building a Reliable DPD Engine

Entimema
Contents

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?

01 AUG€500 DUE
10 AUG€300 PAYMENT
20 AUG€200 PAYMENT
31 AUGDERIVE STATE

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.

DPD(T) = f(Schedule, Payments, Allocation, Adjustments, Calendar, T)
DPD is derived credit state

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.

RemainingDueⱼ(T) = Dueⱼ − AppliedPaymentⱼ(T) − ApprovedAdjustmentⱼ(T)
Due-item state

Each obligation remains individually reconstructible. Total arrears alone cannot reveal which due date remains open, so it cannot fully explain DPD.

Schedule lineage required for point-in-time DPD
ElementQuestion
Due item identityWhich contractual obligation is this?
Due date and amountWhat became payable, and when?
Schedule versionWhich contract state governed the account?
Effective intervalWas this schedule valid at decision time?
Adjustment lineageWhy did the obligation change?

A payment event does not identify the obligation it satisfied

Allocate(Payment, OpenDueItems, Rulesv) → AllocationResult
Versioned allocation
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.

PaymentAmount = AllocatedAmount + UnappliedAmount
Payment conservation

The oldest relevant unpaid obligation anchors DPD

OldestUnpaidDueDate(T) = min { DueDateⱼ : RemainingDueⱼ(T) > threshold }
Oldest unpaid due
DPD(T) = DateDifference(OldestUnpaidDueDate(T), BusinessDate(T))
Conceptual DPD calculation

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.

PAYMENT / REVERSAL / RESTRUCTUREDUE-ITEM STATEARREARSOLDEST UNPAID DUEDPDDELINQUENCY STATE
Payment, reversal and restructure change due-item state; DPD is derived only after remaining obligations and the oldest relevant due date are known.

DPD policy belongs in explicit versioned inputs

CALENDAR

Calendar days, business days or contractual business date; define the convention and holiday source.

MATERIALITY / GRACE

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

Payment scenarios
ScenarioDue-item effectDPD consequence
Partial €300 against €500€200 remains on original obligationDPD can continue from original due date
€600 against August €500 and September €500August clears; €100 applies to SeptemberOldest unpaid shifts to September; DPD steps down
Excess beyond arrearsPrincipal, future due or unapplied cash per policyNever guess from amount alone
Advance paymentMay prepay, reduce principal or remain unappliedFuture items change only under contract rules
Settled, not allocatedCash exists while due item remains openOperational 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.

PAYMENTARREARS CLEAREDCUREPAYMENT REVERSEDARREARS RESTOREDDPD RECOMPUTED
The reversal restores the realised allocation; the engine recalculates due items, oldest unpaid date and DPD rather than restoring a cached value.
CURE REVERSAL

The payment that created technical cure was undone.

RE-DEFAULT

The borrower genuinely cured, then later deteriorated through a new event path.

Late payments create known and restated DPD

DPDknown(T)

Uses schedule and events available at T; reproduces historical collections and model decisions.

DPDrestated(T)

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.

KNOWN AT T
DPD = 5
late payment arrivesRESTATED AT T
DPD = 0
The late payment changes corrected economic delinquency, not the information that production possessed at the decision time.

Contract changes transform schedule state explicitly

Contractual changes and DPD
ChangeRequired event/stateControl
RestructureOld/new schedule versions and effective dateHistorical DPD retains old schedule
Payment holidayExplicit schedule transformationNever force DPD to zero silently
Waiver / adjustmentDue-item adjustment eventDo not disguise as payment allocation
Write-offAccounting/lifecycle eventDoes not automatically imply DPD = 0
ReallocationCorrected due-item allocation lineageRestate 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.

State boundaries
ObjectMeaning
Facility/account DPDContractual obligation state for one consistency boundary
Customer delinquencyA later aggregation such as max, rule-based or exposure-aware state
DPD bucketVersioned decision classification derived from raw DPD
Default stateBroader credit-risk outcome with criteria beyond DPD
Technical cureNo 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

Golden cure and reopen stream
CheckpointRemaining 01 AugOldest unpaidDPD result
01 Aug · €500 due€50001 AugPolicy-defined day zero
10 Aug · €300 paid€20001 Aug9
15 Aug · €200 paid€0None0 · technical cure
20 Aug · €200 reversed€20001 Aug19 · reopened

Values assume calendar-day difference and no grace; they are fictional test semantics, not a universal convention.

Multi-instalment golden case; oldest-due-first illustration
Checkpoint01 Aug due01 Sep due01 Oct dueOldest unpaidDPD
14 Sep before payment€500€500Future01 Aug44
15 Sep payment €700€0€300Future01 Sep14
01 Oct new due€0€300€50001 Sep30
10 Oct payment €400€0€0€40001 Oct9

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

Minimum DPD test architecture
TestProof
Partial / excess paymentConfigured allocation; no accidental reset
Cure reversalArrears, oldest due and DPD rebuild
Backdated paymentKnown and restated DPD both survive
RestructureOld and new schedule versions reproduce
Midnight / timezoneBusiness-date result remains stable
Duplicate paymentIdempotency leaves DPD unchanged
Allocation versionHistorical replay uses original rules
Snapshot paritySnapshot + tail equals full replay
DPD ≥ 0No oldest unpaid due ⇒ DPD = 0DPD > 0 ⇒ relevant arrears > 0Remaining due reconciles to due-item effectsAllocated + unapplied = payment

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

DPD reconciliation evidence
AccountServicing DPDDerived DPDDifferenceReason
acc_10423029−1Business-date boundary
acc_2208012+12Payment allocation pending
acc_31914514−31Schedule 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

CONTRACTUAL SCHEDULEDUE ITEMSCANONICAL PAYMENTS / ADJUSTMENTSALLOCATION ENGINEREMAINING DUEOLDEST UNPAID OBLIGATIONDPD ENGINEDELINQUENCY / CURE STATERISK / COLLECTIONS / ECL
Contractual obligations and canonical events remain separate until versioned allocation constructs remaining due; only then does the DPD engine classify state for consumers.
ENTIMEMA FRAMEWORKEntimema DPD decision framework
  1. Define schedule
  2. Define allocation
  3. Define effective payments
  4. Reconstruct remaining due
  5. Identify oldest unpaid obligation
  6. Apply calendar policy
  7. Derive DPD
  8. Classify delinquency
  9. Test replay
  10. 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.

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.