The console was wearing an approximation of the bank. Everything here was read off HDFC Bank's own production assets instead. The mark was inside out. It had been drawn as a blue square with a red square inset; HDFC's is the reverse — a red ground, a white channel cross splitting it into four corner blocks, and a small blue square at the centre. Every coordinate now comes verbatim from the SVG the bank serves in its own site header, including the "HDFC BANK" wordmark outlines, so the lockup is the bank's rather than a redrawing of it. The favicon in index.html carried the same inversion and is fixed with the same numbers. Open Sans replaces the Segoe UI stack. hdfcbank.com preloads OpenSans Regular / SemiBold / Bold / ExtraBold and sets its whole site in it. It is self-hosted through @fontsource-variable/open-sans rather than pulled from a CDN, for the reason the API URL is read at runtime: this build gets promoted between environments and dropped behind bank proxies, and a font that sometimes fails to arrive is a product that sometimes looks like someone else's. The header stops being a navy band. HDFC puts the full lockup on a light ground on every one of its own digital surfaces, and a navy header made the real logo unusable — the lockup's own blue bar would have dissolved into it. The blue now does its work as the rule under the header, the active queue and the primary button, which is how the bank uses it. The lockup's 3px white keyline is the bank's own, so it sits correctly on the panel in dark mode too and no reversed variant is needed. Palette, grounded rather than invented: the page canvas is #F0F6FB, the tint hdfcbank.com uses behind content; the good/ tokens sit on the hue of the bank's #00B947 instead of a generic emerald, darkened to #00713D because #00B947 is 2.6:1 on white and unreadable as a label. The two brand hexes were already right and are unchanged. Also closes three AA failures the palette claimed it did not have — --color-faint in light, and --color-faint / --color-brand in dark, each below 4.5:1 on --color-panel3. White on --color-signal (4.31:1) is left alone knowingly: darkening HDFC red for the Decline button would have made it a different red from the logo beside it.
157 lines
7.7 KiB
Markdown
157 lines
7.7 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.
|
|
|
|
## Brand
|
|
|
|
Everything below was read off HDFC Bank's own production assets rather than
|
|
recalled or approximated. Check the source before changing any of it.
|
|
|
|
| What | Value | Where it came from |
|
|
|---|---|---|
|
|
| Logo | `src/components/HdfcLogo.tsx` | Coordinates lifted verbatim from `s7ap1.scene7.com/is/content/hdfcbankPWS/hdfc-bank-logo` — the SVG the bank serves in its own site header |
|
|
| Blue | `#004C8F` | The lockup's bar fill |
|
|
| Red | `#ED232A` | The mark's ground |
|
|
| Typeface | Open Sans | hdfcbank.com preloads OpenSans Regular / SemiBold / Bold / ExtraBold and sets its whole site in it. Self-hosted through `@fontsource-variable/open-sans`, never a CDN |
|
|
| Page tint | `#F0F6FB` | The tint behind content on hdfcbank.com |
|
|
| Success green | hue of `#00B947` | The bank's green, darkened to `#00713D` — `#00B947` itself is 2.6:1 on white and unreadable as a label |
|
|
| Tagline | "We understand your world" | Shown once, on the sign-in card |
|
|
|
|
**The mark is a RED square with a BLUE centre**, split by a white channel
|
|
cross — not a blue square with a red inset. It was drawn inside out here
|
|
originally, in the component *and* in the favicon in `index.html`. Those two
|
|
carry the same coordinates; change one and you must change the other.
|
|
|
|
The lockup carries the bank's own 3px white keyline, which is what lets it sit
|
|
on a coloured ground — so there is no reversed variant, and none is needed. It
|
|
is also why the header is a light surface: HDFC puts the full lockup on a light
|
|
ground on every one of its own digital surfaces, and the navy header this app
|
|
used to have would have dissolved the lockup's own blue bar into itself. The
|
|
bank's blue does its work as the rule under the header, the active queue and
|
|
the primary button.
|
|
|
|
## 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. `HdfcLogo.tsx`
|
|
is the single exception, and it earns it: a logo's colours are not design
|
|
decisions, so the mark stays `#ED232A` and `#004C8F` in every theme.
|
|
|
|
**Red is never decoration** — it marks the logo 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.
|
|
|
|
Every text colour clears WCAG AA on each surface it actually lands on, in both
|
|
themes. There is one knowing exception: white on `--color-signal` is 4.31:1 —
|
|
the Decline button. Darkening HDFC red to clear AA would have made the one red
|
|
button on the screen a different red from the logo beside it.
|
|
|
|
## 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`.
|