console: the customer accepts from their own phone

Recording the acceptance was the third human touchpoint, and it was a
person in the office typing what a customer had said somewhere else.
The customer's WhatsApp reply now drives it (81), so this stage has no
step of its own.

"Record the acceptance" drops from a step to a folded recovery lever —
kept, because a customer who says yes on a CALL still needs somebody to
record it, and because a reply Engage judged ambiguous has to be
actionable by a person.

That left Quote Presented with an empty screen at exactly the stage an
operator is most likely to wonder whether something has broken, so the
waiting state now says so. Grey, and no pulse: nothing is happening, and
that is the correct state — unlike the AI strip, where a still dot would
mean something is wrong.

The reply itself surfaces in two places: the audit trail carries it as
"The customer said", and the lead file files it under Proposal beside the
acceptance it produced. The evidence and the decision it justifies are
one line apart, which matters when nobody in the office made it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yashas 2026-09-08 13:26:46 +05:30
parent 5ff74ea22d
commit e5b413ecac
4 changed files with 43 additions and 3 deletions

View File

@ -42,7 +42,7 @@ export const STAGES = [
// 7 September: a lead waited for its three documents inside "Contacted", so
// the queue that most needed watching was the one that did not exist.
{ uid: 'zk-state-docs', name: 'Document Pending', kind: 'needs', need: 'Document collection', by: 'the partner agent' },
{ uid: 'zk-state-quoted', name: 'Quote Presented', kind: 'customer', need: 'Customer decision', by: 'the customer' },
{ uid: 'zk-state-quoted', name: 'Quote Presented', kind: 'customer', need: 'Customer decision', by: 'the customer', doing: 'Waiting for the customer to reply on WhatsApp' },
// The one queue where the machine stops and a person decides.
{ uid: 'zk-state-referred', name: 'Referred to Underwriting', kind: 'needs', need: 'Underwriting referral', by: 'an underwriter' },
{ uid: 'zk-state-payment', name: 'Payment Pending', kind: 'needs', need: 'Premium confirmation', by: 'operations' },
@ -135,7 +135,12 @@ const STATE_ACTIVITIES = {
{ uid: 'zk-act-quote', label: 'Generate Quote', by: 'Rating engine', role: 'force' },
],
'zk-state-quoted': [
{ uid: 'zk-act-accept', label: 'Record the acceptance', by: 'you, on consent', role: 'do' },
// The customer accepts from their own phone: their WhatsApp reply is
// routed to Customer Reply, Engage reads it, and Engage performs this.
// Kept reachable — role 'force', folded away — because a customer who
// says yes on a CALL still needs somebody to record it, and because a
// reply Engage judged ambiguous has to be actionable by a person.
{ uid: 'zk-act-accept', label: 'Record the acceptance by hand', by: 'you, on consent', role: 'force' },
{ uid: 'zk-act-quote', label: 'Re-quote', by: 'Engage AI', role: 'again' },
{ uid: 'zk-act-collect-docs', label: 'Add a document', by: 'you', role: 'again' },
{ uid: 'zk-act-kyc', label: 'Verify KYC', by: 'KYC AI', role: 'force' },

View File

@ -37,6 +37,7 @@ const NARRATIVE = [
['referral_analysis', 'Referral analysis'],
['uw_decision_notes', 'Underwriting decision'],
['documents_notes', 'Documents'],
['customer_reply', 'The customer said'],
['lost_reason', 'Why it was dropped'],
['resume_note', 'Why now'],
]

View File

@ -69,7 +69,7 @@ const GROUPS = [
['Motor risk', ['motor_reg_no','motor_make_model','motor_mfg_year','motor_cc','motor_fuel','motor_idv','motor_ncb_pct','motor_addons','motor_prev_insurer','motor_prev_policy_no','motor_prev_expiry','motor_prev_claim']],
['AI advice', ['ai_recommended_cover','ai_recommended_addons','ai_recommendation_rationale','ai_recommendation_confidence']],
['Quote', ['product_code','quoted_od_premium','quoted_tp_premium','quoted_addon_premium','quoted_section_premiums','quoted_gst','quoted_premium','quoted_breakup','quote_valid_till']],
['Proposal', ['acceptance_ref','accepted_at']],
['Proposal', ['acceptance_ref','accepted_at','customer_reply','customer_reply_from']],
['KYC', ['kyc_mode','kyc_ref','kyc_outcome','kyc_mismatch_notes']],
['Underwriting', ['uw_outcome','uw_survey_required','referral_analysis','uw_referral_reason','uw_decision_notes']],
['Policy', ['payment_link','payment_ref','premium_realised','realised_at','policy_no','policy_issued_at']],
@ -368,6 +368,21 @@ export default function Lead() {
</button>
</div>
</div>
) : stage?.kind === 'customer' && stage.doing ? (
/* Not an agent working, and not a task of yours either the lead
is with someone outside the business. Without this the screen
went blank at exactly the stage where an operator is most likely
to wonder whether something has broken. */
<div className="doing doing--waiting">
<span className="doing__wait" aria-hidden="true" />
<div>
<strong>{stage.doing}</strong>
<span>
Their answer comes back to this lead on its own Engage reads it and
records the acceptance. Nothing is waiting on you.
</span>
</div>
</div>
) : stage?.kind === 'auto' ? (
<div className="doing">
<span className="doing__pulse" aria-hidden="true" />

View File

@ -1225,3 +1225,22 @@
color: var(--zk-muted);
margin-top: 2px;
}
/* ---- waiting on someone outside the business ----
Neither an agent working nor a task of yours. Grey rather than blue, and
no pulse: nothing is happening, and that is the correct state. */
.doing--waiting {
border-color: var(--zk-line);
background: var(--zk-tint);
}
.doing--waiting strong { color: var(--zk-ink); }
.doing__wait {
flex: none;
margin-top: 5px;
width: 10px;
height: 10px;
border-radius: 50%;
border: 2px solid var(--zk-grey);
background: transparent;
}