Commit Graph

72 Commits

Author SHA1 Message Date
aca5d1a043 console: role-scoped access, portfolio analytics, enterprise register
Four things, and the first is the one that mattered.

ROLE-BASED ACCESS. The console showed every action to everyone on
purpose — "the refusal is the demo" — and the workflow refused
server-side. That is a defensible engineering position and a poor
product: an underwriter saw a row of six buttons, five of which 403.

src/api/permissions.js now mirrors tbl_wf_activity_permissions, and the
session already carries user.roles, so nothing new had to be fetched.
Action buttons, sidebar queues and the entry doors are all filtered.
It remains presentation only — the platform still refuses, and a role
added there but missing here hides a button that would have worked,
which is the failure mode to watch.

Each role's app now looks like their job. Ops sees the whole board and
still cannot clear a referral. An underwriter sees one queue and two
buttons. Agents see the three queues they work in. Reporting is NOT
gated: the overview counts the whole portfolio for everyone, because an
agent tracking leads they filed is reasonable and acting on them is not.

One deliberate divergence from the workflow, commented where it lives:
the two scheduled activities carry no roles at all, because that is the
only configuration under which the scheduler can perform them. Open to
the SYSTEM is not open to everyone signing in, so the UI narrows them —
otherwise an underwriter is offered "Send Document Reminder".

ANALYTICS. The overview was three lists. It is now a measured page:
open leads, pipeline value, written premium, conversion, commission —
then renewal exposure in three buckets, the queues needing a person with
the age of the oldest item in each, and stage distribution as bars.

Checked against the live book rather than assumed, which found a real
bug before it shipped: Policy Issued is written business AND not yet
terminal, so its premium was counted in pipeline and production both.
₹42,912 double counted on 37 leads. Pipeline now excludes anything
already on risk.

The lead page gained the same treatment: renewal countdown, lead age,
time in current stage, premium, commission, AI confidence. Age and
dwell are computed — neither is on the record, and they are the two
figures that answer "is this moving?", which no field could.

TERMINOLOGY. The previous pass over-corrected: fixing builder jargon
("the workflow decides, server-side") produced chat ("What you can do",
"The agents have it", "Nothing here right now"). Neither is how an
operations console reads. Now: Actions · Record · Audit trail · Action
required · Customer response · Automated · Scheduled · Document
collection · Underwriting referral · Premium confirmation.

LAYOUT. Prose subtitles cut to one line or removed. Measures render as
tabular figures in a bordered strip instead of label/value pairs. The
overview splits into work on the left and distribution on the right.
Stage bars replace a wall of equal-weight cards.

Pre-existing lint errors unchanged at 11; none in the new files.
2026-09-07 16:31:04 +05:30
4c210c7e09 console: a page to monitor from, and words an operator would use
Three problems, and the first one is the reason the other two were hard
to see.

THERE WAS NO DASHBOARD. "/" redirected to the underwriting queue — one
stage, usually empty, and belonging to somebody else. Every screen in
the app answered "show me this queue"; none answered "how is the book
doing, and is anything waiting on me?", which is the only question a
person has before they have picked a queue.

So: a Today page. Three blocks, in the order somebody cares about them.
What is waiting on a person, as three cards that colour only when they
have something in them. Renewals running out, worst first, capped at 30
days because a renewal six months away is not a thing to look at today.
Then every stage with its count, zeroes included — a stage quietly
receiving nothing is only visible if its zero is on screen.

It is one list call, counted in the browser. That is honest at this size
and it is the same call the queues already make. If the book outgrows it
the answer is a counts endpoint, not a bigger page.

THE WORDS WERE WRITTEN FOR WHOEVER BUILT THE WORKFLOW. "What happens
next" was followed by "these are the only activities this state allows
— but the workflow decides, server-side, whether you may". A failed list
said "this queue has no record view yet". Panels were called "Flow
details". A closed lead read "nothing runs from here". None of that is
wrong; all of it is addressed to the wrong reader.

Now: "What you can do", "Lead details", "History", "This queue could not
be loaded". Queue names say what they want — "Documents needed",
"Underwriter to review", "Payment to confirm". The sidebar group called
"Not yet" says "Not due yet", and "Running by itself" says "The agents
have it", which is the actual claim being made.

THE QUEUE HAD NINE COLUMNS AND LED WITH THE WRONG ONE. Two were internal
vocabulary: attribution status renders "clear" or "contested" and means
nothing to an operator, and the channel is background rather than
something anyone scans a queue for. Both fold into a subtitle under the
customer. That leaves six columns and puts the renewal countdown — the
number that decides whether to act today — second instead of fifth.

Also: the timeline printed a raw slug when a step came back without a
name. It is the screen this product is demonstrated on, so a
"zk-act-doc-reminder" in the middle of an otherwise readable story is
expensive. It now reads as English.
2026-09-07 16:13:13 +05:30
eea044b9f9 config: the two scheduled activities
The workflow now schedules work for itself. Two activities exist that
nobody navigates to — the scheduler performs them — and both are offered
here as well, because a person needs to be able to do by hand what the
timer does on its own.

Retry Call, in Awaiting Contact. Booked automatically four hours after an
unanswered call, clamped into 09:00-21:00 IST, three attempts and then
the lead closes. It records the attempt; whether it also DIALS is gated
on the lead's `auto_retry` field, which ships unset, so nothing rings a
phone until somebody arms it deliberately.

Send Document Reminder, in Document Pending. Fires at 24h, 72h and 120h
from Engage's request. On the third unanswered one the lead leaves the
queue — Parked if the renewal is still weeks off, Lost if the cover is
about to lapse. That is the bound: a chase with no ceiling fills a book
with leads nobody will ever work, all of them looking active.

Both are labelled "Scheduled — or you" rather than given an AI or a role,
because neither is true. The performer on the timeline will read System.
2026-09-07 14:12:42 +05:30
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