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>
123 lines
5.5 KiB
Markdown
123 lines
5.5 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
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`.
|