Commit Graph

19 Commits

Author SHA1 Message Date
b9c76482a2 config: the v2 state model
The workflow dropped four stages on 7 September and renamed two. This
table is the console's mirror of it and was still describing the old
shape, which breaks quietly rather than loudly.

WHAT CHANGED IN THE WORKFLOW

Risk Captured, Proposal Accepted, KYC Verified and Underwriting Cleared
are gone. A lead entered and left each of them inside a minute, so they
were pipeline steps wearing a stage's clothes. Meanwhile the one genuine
stall -- a partner agent finding three documents -- had no stage at all
and happened inside "Contacted", which meant the queue that most needed
watching was the only one that did not exist. zk-state-docs is that
queue, and it is where the work those four stages used to represent now
happens: upload, capture, advise, quote, all without the lead moving.

Quote Presented absorbs the other three the same way -- acceptance,
identity and the underwriting screen -- and leaves in one move for
Payment Pending, Referred or Declined, decided by a single gate that
reads both answers. The two-gate chain it replaces could not express
"identity refers but the risk is fine": the first gate routed to
Referred before appetite had been asked, so an underwriter always
received half a file.

Qualified became Awaiting Contact and Quoted became Quote Presented.
"Qualified" meant pre-call here and post-call on the whiteboard -- the
same word on opposite sides of the dial.

WHY THE RENAME IS THE DANGEROUS HALF

STAGES.name is compared against current_state_name in three places: the
queue filter, the sidebar tally, and the action lookup on the lead
detail page. A workflow rename that is not mirrored here does not error
anywhere -- the record view returns zero rows, correctly, for a name no
lead is in. Both renamed queues would simply have read empty. The header
comment now says so.

Worse, zk-state-docs was absent from STATE_ACTIVITIES entirely, so a
lead sitting there rendered no buttons at all. Document upload is the
human step the entire rest of the flow hangs off.

ALSO

Parked / Nurture has been in the workflow since 4 September and was
never added here, so its Resume Outreach button did not exist. Until a
scheduler lands, that button is the only thing that wakes a parked lead,
so it gets a nav group of its own rather than being filed under
"running by itself", which it is not.

DROPPABLE follows the new state list. Mark Lost now also reaches
Referred, which the workflow had never allowed and now does: a lead
whose cover lapses while an underwriter holds the file previously had
nowhere to go.

doc_vehicle_photos becomes conditional. The comment said no break_in
flag existed to test -- one does now, derived at filing on all three
doors, so the slot honours its own "break-in only" caveat instead of
being shown to every motor renewal.

Verified by diffing STAGES.name and ACTIONS against tbl_wf_states and
tbl_wf_state_allowed_activities on dev; both match exactly.
2026-09-07 12:22:42 +05:30
Likith K R
66ea08e317 Updated to new frontend specifications 2026-09-04 13:26:20 +05:30
Likith K R
ae548a3a10 UI Enhancements in recordsview and detailsview 2026-09-02 15:33:26 +05:30
8b58b2243e console: Request Premium, and surface Log Payment Nudge
Issue Payment Link is now Request Premium — the platform has no payment
integration and the fabricated link field is off the form. The action's
'by' was 'Payment webhook', which was aspirational; collection happens on
the insurer's own rails and ops records the reference.

Log Payment Nudge added to Payment Pending. Engage performs it; without an
entry here the activity is invisible to the console rather than broken.
2026-08-26 13:48:08 +05:30
e32916cad3 Read the extract response correctly, and fail visibly
/ocr-extract answers { extracted: {...}, raw: "..." }. The client checked
`fields` first and `extracted` second, which was right by luck — but an
extraction that returned nothing, or threw, did so silently: the button
went back to idle and the operator had no way to tell a read from a
no-read.

Now an empty or failed extraction says so under the field, and either
way the UPLOAD survives. The file is already stored and referenced; a
failed read only means the fields are not pre-filled, which someone can
recover by typing. Losing the upload because the read failed would not
be recoverable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 15:23:35 +05:30
43259b378c fix(upload): always send workflow_uuid
/upload requires it and it is fixed for this app, but the client only
appended it when ctx happened to carry it — and FileField passes only
activity, field and instance. Every upload came back 400 with
"Missing required params: workflow_uuid".

Taken from config now, so it cannot be forgotten by a caller.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 13:46:51 +05:30
6df594f0b4 Render file and OCR fields as uploads, not text boxes
Collect Documents shipped with every document field rendering as a plain
text input — ActivityForm had no case for `file` or `ocr`, so they fell
through to the default. There was a form asking you to type an RC book.

Uploads happen ON PICK rather than on submit, because /ocr-extract takes
a REFERENCE to a stored file: the document crosses the wire once, it
survives a reload (a draft cannot carry a File), and re-extracting costs
no second upload.

For an ocr field the extracted values are shown inline — "read from the
document" — and filled into the fields they map to. Only into fields that
are still blank: a value a person typed is never overwritten by one a
model read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 13:37:51 +05:30
b90c94686e Offer Collect Documents where the workflow allows it
The activity was added to the workflow on both Contacted and Proposal
Accepted, but not to the console's action map — so the platform allowed
it, the console never offered it, and nothing errored anywhere. There was
simply no way to upload a document.

Notes the hazard on the map itself: it is hand-maintained against
tbl_wf_state_allowed_activities, and anything missing from it is
invisible rather than broken.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 13:26:48 +05:30
1fb8d0dc98 Put dates where the work is decided
The timeline carried timestamps; the two screens people actually act on
carried none. A queue with no dates cannot tell you which lead is going
stale, and on a renewal book the countdown to expiry is the number that
decides whether anyone should act today — it was buried in a field group.

Queue gains "Renewal due" and "Added", both absolute plus relative: the
absolute answers "when exactly", the relative answers "is this stale".
The countdown is toned — lapsed, urgent, soon, later — on the same
thresholds the workflow itself uses to decide whether to call, so the
screen and the agent are reading the same bands.

Lead header gains the countdown, the current insurer, the registration,
and when the lead arrived and was last touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 12:52:26 +05:30
49c51c24d7 Show what happened on a lead, and who did it
The lead file listed the current field values and nothing about how they
got there. On an app whose whole claim is that agents do the work, that
is the wrong thing to show: you could see the outcome and not whether a
machine or a person produced it.

Adds a timeline from the audit trail — every activity in order, badged by
actor, carrying the reasoning the agent actually wrote (attribution,
eligibility, call notes, recommendation rationale, the premium breakup)
rather than the raw field dump that is already below it.

Colour encodes something real: machine work is blue, people amber, the
platform grey, so a file reads at a glance for how much of it was done by
hand.

Also fixes the audit path — the bare /view/audit is not an API route and
falls through to the SPA, returning HTML with a 200 that parses as a JSON
error rather than an HTTP one. It has to be app-scoped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 12:40:30 +05:30
f61d3a74cd Surface only the agency door
This demo is about the individual agent who sources a lead and gets paid
when it onboards, so the console offers that one entry point and goes
straight to its form rather than showing a chooser with one option.

The direct and bancassurance INIT activities are hidden, not removed —
both still work over the API with their own permissions, and the
bancassurance door is what makes the cross-channel duplicate story
possible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 16:48:05 +05:30
e750eb8287 Render computed fields read-only and seed server prefill
lead_ref is now an id_gen, issued server-side and stripped from the
submission — so the form shows it as auto rather than as an empty box
somebody is expected to type a reference code into, and never sends it.

Also seeds the inputs from the prefill the form response carries. The
door's channel and the signed-in agent are stamped by a prefill pipeline,
so the form should show them already filled rather than ask.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 16:19:15 +05:30
5ea627b064 Make the console operable: add a lead, work a lead
The pipeline could be looked at but not driven. Adds the three pieces
that make the machine walkable end to end from the UI.

- ActivityForm renders whatever /view/form-screens returns — labels,
  types, select options, mandatory flags — and submits it back. No form
  is defined in this repo, so a field added in Studio appears here with
  no code change.
- AddLead presents the three doors. Each is a separate INIT activity with
  its own permissions; the bank one lands further along because the bank
  already did CKYC.
- Lead shows the file grouped in the order it was worked, ending with
  what the sourcing agent earns, plus the activities this state allows
  and who normally performs each.

The "who normally performs this" label is presentational only. Nothing
here enforces anything — the workflow refuses server-side and the form
reports what it said, including a note when a 403 is the platform
declining rather than the console misbehaving.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 16:11:28 +05:30
7a86118169 fix(api): correct the record-view POST body shape
The POST body is not the GET query params with a different verb. The view
is named by rv_template_uid (rv_id is the GET spelling) and paging, sort
and filters all live inside search_query. Sending them at the top level
returns 400 "Missing param: rv_template_uid (body)".

This would have failed the moment the record view was seeded, and the
400 would have read as "the view is missing" rather than "the request
was wrong" — the queue screen renders exactly that notice on any error.

Verified against dev: the old shape returns the missing-param 400, the
new one returns "record view not found", which is the view genuinely not
existing yet.

Also adds rows(), which normalises the response key — workflow views
return `data`, rdbms views have been seen returning `records`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 15:52:19 +05:30
42cfef12bf fix(build): add BASE_HREF placeholder + config.js tag to index.html
The build asserts on <!--BASE_HREF--> and placement fails without it, so
nothing was ever placed and nginx 404'd. The ./config.js tag is the other
half: it carries this environment's VITE_ZINO_API_URL, and without it
requireConfigValue throws and the app renders nothing even once served.

Order matters — the placeholder precedes the favicon href, or that
relative ref resolves against the wrong root once mounted.

Verified by simulating placement locally: filled the placeholder, wrote a
config.js beside the artifact, served it under /zurich-kotak/ and loaded
it. index.html, the JS bundle, config.js and the favicon all 200, the
login renders, and with config.js removed the root stays empty rather
than falling back to some other backend.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 15:47:26 +05:30
49cfdc8c00 Show the signed-in role in the header
RBAC is what this demo argues, so which persona you are signed in as
should never be a guess while presenting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 15:21:00 +05:30
9210681488 Wire the console to the platform and make it deployable
The scaffold had the brand right and nothing behind it: the login was a
600ms setTimeout with a TODO, and three pieces of the frontgen deploy
contract were missing.

- Sign in against POST /usr/login (org_id as a STRING — a number is
  rejected by the gateway) with the session in a provider; surface the
  gateway's own message rather than a generic "invalid credentials",
  because a wrong password and a user without access to this app look
  identical from here and are not.
- Runtime config: the API URL is read from the config.js the server
  writes at placement, never compiled in, and requireConfigValue throws
  so a build with no config.js fails loudly instead of calling whichever
  backend built it.
- base: './' plus a router basename taken from <base href>, so one build
  serves any mount path.
- Move the fonts and logo from public/ into src/assets/ — Vite rewrites
  bundled asset URLs to be relative, while a public/ file referenced as
  "/fonts/..." stays absolute and 404s under the /zurich-kotak/ mount.
- API client for recordview / detailview / form-screens / start /
  activity, and the pipeline shell whose sidebar is the state machine in
  the order a lead moves.

Queue and lead-file data wait on the record and detail views.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 15:20:13 +05:30
Likith K R
f859fbba6d Login page added 2026-08-24 13:04:47 +05:30
Likith K R
eeeb1e2556 Empty vite-react project 2026-08-24 12:27:21 +05:30