feat(integration): DPD history tab — coverage, generation and backfill

A fourth tab on /officer/integration reading the loan book as a training
set. The verdict banner is the point of the screen: DPD history is the
model's label, and three thousand generated snapshots look identical on
a chart to three thousand real ones, so the split is stated in words at
the top rather than left to be inferred from a percentage.

Four tiles, and "unknown provenance" is one of them — rows written
before stamping existed are shown as their own bucket rather than
counted as real. Generation and purge sit beside a backfill-from-core
control, with the honest note that a backfill from a simulated adapter
produces simulated rows and only a bank's own core counts toward the
real total.

The demo fixture deliberately reports a book that is NOT trainable. A
fixture claiming a healthy training set would defeat the entire purpose
of the panel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
LincPay
2026-08-07 18:58:14 +05:30
co-authored by Claude Opus 5
parent 0a76a53ceb
commit e923daf825
3 changed files with 328 additions and 3 deletions
+55
View File
@@ -552,6 +552,61 @@ export interface ConnectorSyncResponse {
preview?: ManualEntryPreview
}
/**
* The loan book read as a training set.
*
* <p>`verdict` is a sentence, not a number, because "is this trainable" is the
* only question that matters here and it must not be left to whoever is reading
* the percentages to work out.
*/
export interface LoanHistoryCoverage {
totalRecords: number
/** Rows this system generated. Never counts toward a real-data claim. */
syntheticRecords: number
realRecords: number
/** Rows written before provenance was stamped. Never counted as real. */
unknownProvenance: number
enterprisesTotal: number
enterprisesWithAnyLoan: number
enterprisesWithHistory: number
distinctAsOfDates: number
earliest: string | null
latest: string | null
historyCoveragePct: number
trainable: boolean
verdict: string
}
export interface LoanHistoryGenerateResult {
enterprises: number
loans: number
snapshotsWritten: number
monthsRequested: number
from: string | null
to: string | null
note: string
}
/** One month of one loan, as an extract reported it. */
export interface LoanSnapshot {
id: number
accountRef: string
enterpriseId: string
product: string | null
sanctionedAmount: number | null
outstandingAmount: number | null
instalmentAmount: number | null
nextDueDate: string | null
lastPaidDate: string | null
dpd: number | null
bucket: string | null
closed: boolean
asOfDate: string
observedAt: string | null
/** SEEDED | SEEDED_HISTORY | UPLOAD | a connector profile code. */
source: string | null
}
/** The template a mapping targets, and the columns that may never be mapped in. */
export interface CanonicalColumns {
columns: string[]