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>
A judge who cannot run the code reads the README, and until now the
reasoning behind this system lived only in Java comments and commit
messages — including the one thing a NABARD panel will check first, which
is why the apex tier cannot reach a borrower.
README covers the institutional model and why NABARD's restriction is the
point rather than a limitation, how to run both projects, the seven console
logins with what each sees, the demo path, and the design decisions worth
defending.
It also states plainly what is real and what is not: live Agmarknet and
Open-Meteo data and a working rule engine, a forecast that is deterministic
rather than machine-learned, and a seeded loan book. It says that
trust-seeded defaults to true and that the honest setting turns 34
trip-wires into 5. A demo that implies more than it has is worth less than
one that is straight about the gap.
API-SPEC documents every endpoint across the four surfaces, the response
envelope, the tenancy rule, small-cell suppression, effective dating and
the rejection reasons — plus the one defect found while writing it:
SecurityConfig whitelists /health but nothing is mapped there, so it
returns 500. The working probe is /auth/health.
Every credential and figure quoted was verified against the running
service: seven logins, the enterprise PIN, and the 44 / 39 / 59 / 12
portfolio counts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>