feat(connector): core banking / LMS seam, one real adapter and four dialects
The loan book is the training label and the one input no public feed
supplies. Integrating with a lender's core is not a technical unknown —
it is an onboarding process per bank, with a schema agreed in a room.
What can be built before that room is the seam, and this is it.
CoreBankingConnector fetches and returns rows; it never writes. Mapped
rows go to LoanBookEntryService.stage — the same validator an uploaded
spreadsheet goes through, landing in the same manual_entry_batch with
the same preview. A parallel validator for the automated path would be
the obvious shape and would eventually enforce the no-PII rule
differently.
FileDropConnector is real: it reads the newest matching file from a
directory a bank delivers to, and leaves it there so a failed run is
repeatable. Finacle, FLEXCUBE, BaNCS and an in-house REST LMS are
simulated, each emitting the same portfolio in its own column dialect —
DPD_DAYS, OVERDUE_DAYS, DAYS_OVERDUE, daysPastDue — which is what makes
the case for a mapping layer rather than asserting it.
The mapping is {canonical: vendor}, keyed by canonical name, so an
unmapped vendor column has no route through and is dropped at the
boundary. CUST_NAME and MOBILE_NO stay out structurally rather than by
anyone remembering. Auto-commit is off on every profile: a sync stages
and stops, keeping the rule that nothing is written until someone has
seen what will be. simulated is stamped per run, not read from the
profile, so switching a profile live cannot make old runs look real.
Fixes a real defect this surfaced: FORBIDDEN_COLUMNS matched on the
exact normalised name, so CUST_NAME — what Finacle actually calls it —
was missed, and a directly uploaded bank file would have kept the
borrower's name in manual_entry_line.raw_row. Matching now splits
separators and camelCase, catching borrowerMobile too, while
loan_account_ref still splits clean.
Verified against a running instance: Finacle 44/44 accepted with 4
columns dropped, the file drop 2 accepted and 1 UNKNOWN_ENTERPRISE
rejected from a real CSV, and both CUST_NAME and borrowerMobile refused
at mapping save.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,17 @@ public class ManualEntryBatch {
|
||||
@Column(length = 260)
|
||||
private String filename;
|
||||
|
||||
/**
|
||||
* {@code UPLOAD}, or the connector code the rows arrived through.
|
||||
*
|
||||
* <p>Kept on the batch rather than inferred from the filename, because once a
|
||||
* CBS connector runs nightly, "who put this number here" stops being answerable
|
||||
* by "someone uploaded a file" — and that question is the reason this table
|
||||
* exists.
|
||||
*/
|
||||
@Column(length = 40)
|
||||
private String source;
|
||||
|
||||
/** The username from the JWT. Never a display name — this is an audit field. */
|
||||
@Column(nullable = false, length = 64)
|
||||
private String uploadedBy;
|
||||
@@ -68,5 +79,6 @@ public class ManualEntryBatch {
|
||||
void onCreate() {
|
||||
if (uploadedAt == null) uploadedAt = LocalDateTime.now();
|
||||
if (status == null) status = "PREVIEW";
|
||||
if (source == null) source = "UPLOAD";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user