LincPayandClaude Opus 5 b79b92fe8d 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>
2026-08-07 18:20:00 +05:30

UdyamLinc

Predictive cash-flow intelligence and early warning for rural microenterprises. Team LincFusion · NABARD Hackathon

A dairy owner in Sehore does not default suddenly. Feed prices rise for six weeks, the milk rate holds flat, the margin thins, and the instalment that was comfortable in June is not comfortable in October. By the time a missed EMI appears on a bank's system, the cause is three months old and the cheap interventions have expired.

UdyamLinc watches the causes rather than the symptom: mandi prices, rainfall and heat against the thirty-year normal, the enterprise's own ledger, and the loan book — turned into features, scored, and surfaced as an alert an officer can act on while it is still early.


The institutional model

NABARD does not lend to enterprises. It refinances and supervises the banks that do — regional rural banks, cooperative banks and small finance banks. A platform that collapses those into one console implicitly casts the apex bank as the lender, which is wrong about the sector and wrong about who may see a borrower.

So there are three tiers, and they are not ranks of one permission:

Tier Who Sees Cannot
NABARD apex aggregates across every partner bank read any borrower row
Bank RRB / cooperative / SFB its own book in full see another bank's book
Enterprise borrower itself see anything else

The restriction on NABARD is the point, not a limitation. Because the apex tier can never reach a borrower, portfolio-wide early warning can be granted to it at all — a bank can tell its regulator, truthfully, that this platform does not expose its customers.

Cross-tenant requests return 404, identical to a fabricated id. A distinguishable 403 would confirm the id is real and on somebody's book.


Running it

Prerequisites — Java 17, Maven, Node 20+, PostgreSQL with a database named UdyamLinc_DEMO.

# Backend — port 8082, context path /api/v1/udyam
cd UdyamLinc-Backend
export DATAGOV_API_KEY=your_data_gov_in_key     # optional; without it Agmarknet records SKIPPED
mvn spring-boot:run

# UI — port 5182
cd UdyamLinc-UI
npm install
npm run dev

Open http://localhost:5182. The schema and demo portfolio are created on first boot.

Demo credentials

Password for every console login: Udyam@123

Username Role Lands on
nabard.admin NABARD administrator /nabard
nabard.officer NABARD officer /nabard
mpgb.admin MP Gramin Bank — head office (44 borrowers) /officer
mpgb.officer MP Gramin — Sehore branch (12 borrowers) /officer
dccb.admin Sehore DCCB — head office (39 borrowers) /officer
dccb.officer Sehore DCCB — branch /officer
supervisor supervisor (MP Gramin) /officer

Enterprise PWA: any seeded enterprise id — e.g. MSME-4417 — with PIN 1234.

To see tenancy working, sign in as mpgb.admin, note a borrower id, sign out, sign in as dccb.admin and request that id directly. It returns "no such enterprise".

The demo path

POST /admin/ingestion/run        # calls data.gov.in and Open-Meteo
POST /admin/features/compute     # 14 derivations per enterprise
POST /admin/scoring/run          # score, evaluate trip-wires, raise alerts
GET  /admin/scoring/explain/MSME-4417

Then open /officer/feeds (provenance), /officer/rules (why each trip-wire did or did not fire) and /nabard (the supervisory view).


What is real, and what is not

Being straight about this is more useful than a demo that implies more than it has.

Real, on live data

  • Agmarknet mandi prices via data.gov.in — 16,000 rows, four districts, verified against the live API
  • Rainfall and heat from Open-Meteo, against a thirty-year normal
  • 14 feature derivations, point-in-time correct on both valid_for_date and observed_at
  • The rule engine, the staleness gate, and the score
  • Manual entry, the loan-book upload and the tenancy model

Real, but computed without a model

  • The P10/P50/P90 forecast is deterministic, not machine-learned — median monthly ledger position, margin drift, and a band that widens with the square root of the horizon. It sits behind a ForecastStrategy seam, so the LightGBM quantile heads replace it as a configuration change.

Demo data

  • The loan book and ledger are seeded. A real CBS extract is the deepest gap in the system: DPD history is the training label, and without it no model can be trained.
  • udyam.forecast.trust-seeded defaults to true, which lets dscr_p10 fall back to the seeder's forecast rows so the demo portfolio looks populated. Set UDYAM_TRUST_SEEDED_FORECASTS=false for any run whose alerts are meant to be believed — trip-wires firing drops from 34 to 5, and those 5 are real.

Not built

  • Account Aggregator consent, so upi_inflow_change is null and TW-02 can never fire
  • A three-year price backfill, so price_z_score is null and TW-05 cannot fire
  • NECC egg, dairy-federation milk and fodder feeds — no usable API exists, which is why manual entry does

Design decisions worth knowing

A feature that cannot be computed is stored as null with a reason, never skipped. NO_SIGNAL, NO_LEDGER, NO_CBS, NO_CONSENT, NO_FORECAST, INSUFFICIENT_HISTORY. "We did not know" and "we never looked" are different facts.

Trip-wires refuse to fire on stale data. A rule firing on a three-week-old price is worse than no rule. Each feature carries the age of the data that actually built it — not the district's newest row, which would let a month-old milk rate inherit yesterday's maize freshness.

A fired trip-wire escalates the band but never rewrites the score. They answer different questions — how stressed is this enterprise, and has a specific agreed line been crossed — and an audit needs both.

Missing inputs widen the forecast band; they never lower its centre. A pessimistic P50 built on ignorance would manufacture breaches.

Nothing is written until someone has seen what will be. Uploads stage a preview reporting what will land, what was rejected and why, and what will overwrite an existing value.

No PII by schema, not by policy. enterprise holds no name, phone, Aadhaar or account number, and a loan-book upload whose headers carry one is refused whole.


Layout

UdyamLinc-Backend/     Spring Boot 4 · Java 17 · Postgres
  apex/                NABARD aggregates — no endpoint returns a borrower
  institution/         Institution, Branch — the tenancy key
  security/            JWT, TenantContext
  ingestion/           Agmarknet, Open-Meteo, scheduler
  features/            Feature store and the 14 derivations
  rules/  risk/        SpEL trip-wires, scoring and forecast strategies
  manualentry/         Upload, preview, commit
  ARCHITECTURE.md      The build contract — read this first
  API-SPEC.md          Endpoint reference

UdyamLinc-UI/          Vite · React 19 · TypeScript
  features/nabard/     Apex portal
  features/officer/    Bank console
  features/enterprise/ Enterprise PWA, offline, Hindi-first

ARCHITECTURE.md is the build contract and supersedes any other plan.


Attribution

  • Mandi prices — data.gov.in Open Government Data platform, Ministry of Agriculture and Farmers Welfare
  • Rainfall and temperature — Open-Meteo reanalysis archive
  • State boundaries — DataMeet India community (datameet/maps), CC BY 4.0. Simplified for display and not an authoritative depiction of any boundary; the source predates the 2019 reorganisation, so Ladakh is drawn within Jammu and Kashmir.

The data.gov.in API key is read from the environment and is never committed. It is redacted before any URL reaches the audit log.

S
Description
No description provided
Readme
498 KiB
Languages
Java 100%