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:
LincPay
2026-08-07 18:20:00 +05:30
co-authored by Claude Opus 5
parent 22870eb480
commit b79b92fe8d
19 changed files with 1756 additions and 14 deletions
+42
View File
@@ -217,6 +217,7 @@ believes.
| Feature store + derivations | ✅ Phase C — see §6.2 |
| Risk engine + rule engine + admin API | ✅ Phase D — see §6.3 |
| Console wire-up: rules screen, market panel, freshness | ✅ Phase E — see §6.4 |
| Core banking / LMS connector seam | ✅ Phase F — see §6.5 |
| LightGBM quantile heads + SHAP | ○ R1 milestone |
Phases B, C and D each terminate in a table you can inspect, so each is testable
@@ -339,6 +340,47 @@ a worse idea than the round trip through an API client.
---
### 6.5 What Phase F delivered
Package `com.lincfusion.udyamlinc.connector`, plus `/officer/integration`.
**The problem it answers.** 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.
There is no single integration to build ahead of that room. What can be built
ahead of it is the shape of the seam, and that is what this is.
| Piece | Notes |
|---|---|
| `CoreBankingConnector` | Fetches and returns rows. Never writes — an adapter that could write would be an adapter that could bypass the no-PII rule. |
| `FileDropConnector` | **The one that is real.** Reads the newest matching file from a drop directory a bank delivers to. Leaves the file where it found it, so a failed run is repeatable. |
| `SimulatedCoreConnector` × 4 | Finacle, FLEXCUBE, BaNCS and an in-house REST LMS, each emitting the same portfolio in its own column dialect. Rows derived from real enterprises and loan records, drift seeded from the account ref so an extract is reproducible. |
| `FieldMapper` | `{canonical_column: vendor_column}`. Validated before it is stored, not at the next nightly run. |
| `ConnectorSyncService` | Fetch → map → **`LoanBookEntryService.stage`** → run record. |
| `ConnectorProfile`, `ConnectorSyncRun` | Per-bank configuration and per-attempt audit. |
| `features/officer/IntegrationPage.tsx` | Connections, field mapping, sync history. Simulated adapters badged everywhere they appear. |
**Three decisions worth keeping.** The mapped rows go through the validator the
manual upload already had rather than a second one written for the automated
path — the obvious shape would be a parallel validator, and it would eventually
enforce the no-PII rule differently. Unmapped vendor columns are dropped because
the map is keyed by canonical name, which makes the promise structural rather
than remembered. And `simulated` is stamped per run rather than read from the
profile, so switching a profile to a live adapter cannot retroactively make its
old runs look real.
**One real defect this surfaced.** `FORBIDDEN_COLUMNS` was matched by exact
normalised name, so `CUST_NAME` — what Finacle actually calls it — was not
caught, and a directly uploaded bank file would have kept the borrower's name in
`manual_entry_line.raw_row`. Matching now also splits separators and camelCase.
That fix belongs to the manual upload path as much as to the connector.
**Still not built:** SFTP polling with credentials (the drop is assumed mounted or
synced by something the bank already runs), scheduled execution (`scheduleCron` is
recorded and read by nothing), and incremental watermarks.
---
## 7. Explicitly not being built yet
Kafka, Neo4j, Elasticsearch, Keycloak, MinIO, and separate deployables. All appear