mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
feat: rules engine — priority config UI + worklist scoring
- Rules engine spec v2 (priority vs automation rules distinction) - Priority Rules settings page with weight sliders, SLA config, campaign/source weights - Collapsible config sections with dynamic headers - Live worklist preview panel with client-side scoring - AI assistant panel (collapsible) with rules-engine-specific system prompt - Worklist panel: score display with SLA status dots, sort by score - Scoring library (scoring.ts) for client-side preview computation - Sidebar: Rules Engine nav item under Configuration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,11 @@ type WorklistRow = {
|
||||
source: string | null;
|
||||
lastDisposition: string | null;
|
||||
missedCallId: string | null;
|
||||
// Rules engine scoring (from sidecar)
|
||||
score?: number;
|
||||
scoreBreakdown?: { baseScore: number; slaMultiplier: number; campaignMultiplier: number; rulesApplied: string[] };
|
||||
slaStatus?: 'low' | 'medium' | 'high' | 'critical';
|
||||
slaElapsedPercent?: number;
|
||||
};
|
||||
|
||||
const priorityConfig: Record<string, { color: 'error' | 'warning' | 'brand' | 'gray'; label: string; sort: number }> = {
|
||||
@@ -228,7 +233,9 @@ const buildRows = (missedCalls: MissedCall[], followUps: WorklistFollowUp[], lea
|
||||
// Remove rows without a phone number — agent can't act on them
|
||||
const actionableRows = rows.filter(r => r.phoneRaw);
|
||||
|
||||
// Sort by rules engine score if available, otherwise by priority + createdAt
|
||||
actionableRows.sort((a, b) => {
|
||||
if (a.score != null && b.score != null) return b.score - a.score;
|
||||
const pa = priorityConfig[a.priority]?.sort ?? 2;
|
||||
const pb = priorityConfig[b.priority]?.sort ?? 2;
|
||||
if (pa !== pb) return pa - pb;
|
||||
@@ -280,6 +287,7 @@ export const WorklistPanel = ({ missedCalls, followUps, leads, loading, onSelect
|
||||
rows = [...rows].sort((a, b) => {
|
||||
switch (sortDescriptor.column) {
|
||||
case 'priority': {
|
||||
if (a.score != null && b.score != null) return (a.score - b.score) * dir;
|
||||
const pa = priorityConfig[a.priority]?.sort ?? 2;
|
||||
const pb = priorityConfig[b.priority]?.sort ?? 2;
|
||||
return (pa - pb) * dir;
|
||||
@@ -404,7 +412,7 @@ export const WorklistPanel = ({ missedCalls, followUps, leads, loading, onSelect
|
||||
<div className="flex flex-1 flex-col min-h-0 overflow-hidden px-2 pt-3">
|
||||
<Table size="sm" sortDescriptor={sortDescriptor} onSortChange={setSortDescriptor}>
|
||||
<Table.Header>
|
||||
<Table.Head id="priority" label="PRIORITY" className="w-20" isRowHeader allowsSorting />
|
||||
<Table.Head id="priority" label="SCORE" className="w-20" isRowHeader allowsSorting />
|
||||
<Table.Head id="name" label="PATIENT" allowsSorting />
|
||||
<Table.Head label="PHONE" />
|
||||
<Table.Head label={tab === 'missed' ? 'BRANCH' : 'SOURCE'} className="w-28" />
|
||||
@@ -433,9 +441,22 @@ export const WorklistPanel = ({ missedCalls, followUps, leads, loading, onSelect
|
||||
}}
|
||||
>
|
||||
<Table.Cell>
|
||||
<Badge size="sm" color={priority.color} type="pill-color">
|
||||
{priority.label}
|
||||
</Badge>
|
||||
{row.score != null ? (
|
||||
<div className="flex items-center gap-2" title={row.scoreBreakdown ? `${row.scoreBreakdown.rulesApplied.join(', ')}\nSLA: ×${row.scoreBreakdown.slaMultiplier}\nCampaign: ×${row.scoreBreakdown.campaignMultiplier}` : undefined}>
|
||||
<span className={cx(
|
||||
'size-2.5 rounded-full shrink-0',
|
||||
row.slaStatus === 'low' && 'bg-success-solid',
|
||||
row.slaStatus === 'medium' && 'bg-warning-solid',
|
||||
row.slaStatus === 'high' && 'bg-error-solid',
|
||||
row.slaStatus === 'critical' && 'bg-error-solid animate-pulse',
|
||||
)} />
|
||||
<span className="text-xs font-bold tabular-nums text-primary">{row.score.toFixed(1)}</span>
|
||||
</div>
|
||||
) : (
|
||||
<Badge size="sm" color={priority.color} type="pill-color">
|
||||
{priority.label}
|
||||
</Badge>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user