lead-to-policy/src/api/forms.ts
2026-06-23 11:20:31 +05:30

175 lines
5.1 KiB
TypeScript

// Activity metadata + the per-state "next action" map. Field schemas are no
// longer mirrored here — <ActivityForm> fetches them live from the form-screens
// endpoint (see api/schema.ts), so the form layout never drifts from config.
export type FieldType =
| 'text'
| 'email'
| 'phone'
| 'number'
| 'date'
| 'datetime'
| 'longtext'
| 'select'
| 'multiselect'
| 'boolean'
| 'lookup'
| 'ocr'
| 'file';
export interface FieldOption {
value: string;
label: string;
}
export interface OcrMapping {
extraction_key: string;
target_field: string;
}
/** A renderable form field. Built at runtime by api/schema.ts from the live
* form-screens config; consumed by <ActivityForm> and the bespoke screens. */
export interface FieldDef {
id: string;
label: string;
type: FieldType;
required?: boolean;
options?: FieldOption[];
prefix?: string;
placeholder?: string;
/** Companion/record key to seed from (defaults to id minus the _input suffix). */
seedKey?: string;
// lookup
lookupTemplate?: string;
lookupDisplay?: string[]; // columns shown in the label
lookupStorage?: string[]; // columns persisted on submit
// ocr
docLabel?: string; // "PAN" / "Aadhaar"
ocrMappings?: OcrMapping[];
}
/** Activity display copy + uid. Fields come from the schema, not from here. */
export interface ActivityMeta {
uid: string;
name: string;
submitLabel: string;
/** Past-tense confirmation, e.g. "advanced to Qualified". */
done: string;
}
// Activity keys are stable, human-readable handles used by STATE_NEXT.
export const ACTIVITY_META = {
capture: {
uid: 'cc1a73d5-61e1-4416-af30-2b1eeb623a58',
name: 'Capture Lead',
submitLabel: 'Create lead',
done: 'created — added to the pipeline as a New Lead',
},
qualify: {
uid: '6b741e44-37f2-4bb0-90ab-ed139f03b7b3',
name: 'Qualify Lead',
submitLabel: 'Qualify lead',
done: 'advanced to Qualified',
},
assign: {
uid: 'da61aaa1-003b-4601-9f42-f93ff230497f',
name: 'Assign Lead',
submitLabel: 'Assign lead',
done: 'advanced to Assigned',
},
contact: {
uid: 'fea6b3a8-846d-4f6f-8e92-033c4cf4b6e2',
name: 'Log First Contact',
submitLabel: 'Log contact',
done: 'advanced to Contacted',
},
schedule: {
uid: 'c444d32f-ed6a-4e36-b8b7-b82e73a141d4',
name: 'Schedule Meeting',
submitLabel: 'Schedule meeting',
done: 'advanced to Meeting Scheduled',
},
assess: {
uid: 'ba3d3e7f-0d3f-49f2-a156-6cace33c228a',
name: 'Assess Eligibility',
submitLabel: 'Submit assessment',
done: 'advanced to Eligibility Assessed',
},
underwrite: {
uid: 'ed8ec0dc-4c2c-4d9e-b798-757f6a9d4730',
name: 'Submit Underwriting',
submitLabel: 'Submit underwriting',
done: 'advanced to Underwriting',
},
issue: {
uid: '7b8fb734-a780-4c6a-837b-c5e7ca28e489',
name: 'Issue Policy',
submitLabel: 'Issue policy',
done: 'advanced to Policy Issued',
},
onboard: {
uid: 'ae415f4e-a33e-432e-882f-733238de8bcc',
name: 'Onboard Customer',
submitLabel: 'Complete onboarding',
done: 'advanced to Customer 360',
},
disqualify: {
uid: 'ea99fc60-caba-45c1-afc1-7386a2fa9220',
name: 'Disqualify',
submitLabel: 'Disqualify lead',
done: 'moved to Disqualified',
},
} satisfies Record<string, ActivityMeta>;
export type ActivityKey = keyof typeof ACTIVITY_META;
// OCR field configs for Collect Documents (extraction → mapped target inputs).
export const OCR_FIELDS: Record<string, { id: string; docLabel: string; mappings: OcrMapping[] }> = {
pan: {
id: 'pan_ocr_2',
docLabel: 'PAN',
mappings: [{ extraction_key: 'pan_number', target_field: 'pan_number_input' }],
},
aadhaar: {
id: 'aadhar_ocr_2',
docLabel: 'Aadhaar',
mappings: [{ extraction_key: 'aadhaar_number', target_field: 'aadhaar_number_input' }],
},
};
// Per-state next action. `route` → dedicated rich screen; else inline form.
// Routed steps (recommend / documents / underwriting / issue) render their own
// screen, so `activity` is only set when the step renders an inline form.
export interface NextAction {
activity?: ActivityKey;
route?: string;
/** Button label for routed steps. */
label?: string;
}
export const STATE_NEXT: Record<string, NextAction> = {
'New Lead': { activity: 'qualify' },
Qualified: { activity: 'assign', route: '/assign', label: 'Assign lead' },
Assigned: { activity: 'contact' },
Contacted: { activity: 'schedule' },
'Meeting Scheduled': { route: '/recommend', label: 'Recommend product' },
'Product Recommended': { route: '/documents', label: 'Collect documents' },
'Documents Collected': { activity: 'assess' },
'Eligibility Assessed': { route: '/underwriting', label: 'Submit underwriting' },
Underwriting: { route: '/issue', label: 'Issue policy' },
'Policy Issued': { activity: 'onboard' },
};
// States from which Disqualify is a valid (secondary) action.
export const DISQUALIFY_STATES = new Set([
'New Lead',
'Qualified',
'Assigned',
'Contacted',
'Meeting Scheduled',
'Product Recommended',
'Documents Collected',
'Eligibility Assessed',
'Underwriting',
]);