HDFC-Loan-Desk/README.md
Yashas 2e8cc580ac feat: HDFC Loan Desk operator console
Custom frontend for the HDFC loan-origination demo (dev, org 83 / app 524,
workflow hdfc_wf_loan). MSME and personal loan files: agents assemble the
evidence, rules compute capacity, credit decides.

Stack and platform contract follow the Flight-Disruption-Management console,
which runs against this same cluster:

  * API client carries its predecessor's hard-won notes — instance_id goes
    over the wire as a NUMBER (a quoted id fails the int64 decode), org_id as
    a STRING, record views answer under `data` OR `records`, and audit rows
    need the TRIGGER_ prefix filtered or every activity appears three times.
  * VITE_ZINO_API_URL is read at RUNTIME from the config.js the server writes
    at placement, never compiled in, so one artifact is promoted between
    environments unchanged. Missing config fails loudly.
  * base: './' plus a router basename from <base href>, so one build serves
    any mount path.
  * Forms are read from the LIVE activity schema — no field definitions in
    this repo. Add a field in Studio, redeploy, it appears.

Written for this app:

  * EvidencePanel, the centrepiece. Three independent income sources side by
    side with the widest pair marked; every computed ratio shown against the
    threshold it was tested on; and a visible line between what a rule
    computed and what a model wrote (rules are never violet).
  * Queues are the one record view filtered server-side on
    current_state_name — the sidebar is the pipeline. Income variance is
    surfaced in the list, not only on the file.
  * Application 360 with the evidence panel above the offer and the sanction,
    so the screen reads in the order the decision was made.
  * HDFC palette where red is never decoration: the logo block and declines
    only. The referred queue's amber is the only amber in the pipeline.

Known gaps, documented in README rather than hidden: OCR uploads render as a
visible pending row instead of a control that pretends to work, and Credit
Assessment is still performed by a human pending the agent wiring.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 15:42:55 +05:30

5.5 KiB

HDFC Bank — Loan Desk

Custom operator console for the HDFC loan-origination demo. MSME and personal loan files: agents assemble the evidence, rules compute capacity, credit decides.

The workflow behind it is seeded from sm2/custom-apps/hdfc-loan-desk/ (org 83, app 524, workflow hdfc_wf_loan). Design doc: sm2/app-designs/loan-origination/design.html.

Run it

npm install
npm run dev          # http://localhost:5174
npm run build        # tsc -b && vite build  → dist/
npm run typecheck

.env carries the API host for local dev only:

VITE_ZINO_API_URL=https://dev.getzino.in

Logins (all password HdfcDemo@2026, org 83):

Email Role Can do
priya.rm@hdfc.example RM, Business Banking Capture, Upload Documents, Make Offer, Decline
rahul.credit@hdfc.example Credit Manager (maker) Credit Assessment, Approve / Decline Referred
anita.approver@hdfc.example Credit Approver (checker) Sanction — and nothing else
vikram.ops@hdfc.example Disbursal Ops Release Disbursal — and nothing else

Sign in as Rahul to land in the Credit Queue, which is where the demo happens.

How it talks to the platform

Everything goes through src/api/client.ts. Three routes matter:

  • POST /usr/login — auth. org_id must be a string; a number returns cannot unmarshal number into Go struct field LoginRequest.org_id.
  • POST /app/524/view/recordview — every queue is this one record view (hdfc-rv-applications) filtered server-side on current_state_name.
  • POST /app/524/view/form-screens then POST /app/524/activity — forms are read from the live activity schema and submitted straight back.

Forms are not defined in this repo

ActivityForm renders whatever /view/form-screens returns — labels, types, select options, which fields are mandatory. Add a field to an activity in Studio, redeploy, and it appears here with no frontend change. That is deliberate: the workflow is the source of truth, and a hardcoded form would quietly diverge from it.

The runtime-config contract

VITE_ZINO_API_URL is read at runtime from a config.js the server writes when it places the build — never compiled in. One artifact is promoted between environments unchanged, so a build-time URL would point every environment at whichever backend happened to build it. requireConfigValue throws if it is missing, so a production build with no config.js fails loudly rather than calling the wrong backend.

vite.config.ts uses base: './' and the router takes its basename from the <base href> the server writes, so one build serves any mount path. Do not reintroduce a build-time base.

What the screens are for

Queues (src/screens/PipelineScreen.tsx) — the sidebar is the pipeline, in the order a file moves. The variance figure is surfaced in the list, not just on the file, so a credit manager scanning the queue can see which referrals are corroboration questions before opening any of them.

The file (src/screens/ApplicationScreen.tsx) — evidence panel first, above the offer and the sanction, because whoever reads the screen top to bottom should read the decision in the order it was made.

The evidence panel (src/components/EvidencePanel.tsx) is the point of the whole app. It is built around three claims:

  1. Three independent income sources, side by side, with the widest pair marked. A single figure labelled "variance 44.3%" is a number nobody can check; three figures with the spread drawn is an argument a person can accept or reject on sight.
  2. Every ratio shown against the threshold it was tested on. "1.34" means nothing; "1.34 against a floor of 1.25" is a finding.
  3. A visible line between what a rule computed and what a model wrote. Rules are never violet; the agent's narrative always is. Nobody should have to be told which is which.

Theming

One attribute — data-theme on <html> — swaps the whole console. Every colour is a CSS custom property redefined under :root[data-theme='dark'] in src/styles.css. There is no second stylesheet and no dark: prefix anywhere.

Never put a literal colour in a component. A hex or a raw Tailwind shade is invisible to the toggle and will be wrong in one of the two themes.

The palette is the bank's two colours, and red is never decoration — it marks the logo block and a decline, nothing else. So when red appears in a queue it means something. The referred queue's amber is the only amber in the pipeline, for the same reason: it should read as "needs you" without reading as an error. A referred file is not a bad file.

Known gaps

  • OCR document uploads are not wired. The seven ocr fields on Upload Documents render as a visible pending row rather than a control that pretends to work; the extracted figures are entered directly. The evidence factory that generates the documents is the next piece of work.
  • No AI employees yet. Credit Assessment is currently performed by a human (the Credit Manager holds the role alongside the agent). Wiring the agent changes who submits the activity, not the workflow — the state machine, the gate and this console are unchanged by it.
  • The advisory offer only renders once the advisor has run; the ai_suggested_* columns are deliberately separate from offered_* so a suggestion can never be mistaken for a commitment.
  • Four-eyes is role separation, not same-user detection: it holds because no user is granted both credit_manager and credit_approver.