mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-05-18 20:08:19 +00:00
feat: rules engine — json-rules-engine integration with worklist scoring
- Self-contained NestJS module: types, storage (Redis+JSON), fact providers, action handlers - PriorityConfig CRUD (slider values for task weights, campaign weights, source weights) - Score action handler with SLA multiplier + campaign multiplier formula - Worklist consumer: scores and ranks items before returning - Hospital starter template (7 rules) - REST API: /api/rules/* (CRUD, priority-config, evaluate, templates) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
14
src/rules-engine/types/action.types.ts
Normal file
14
src/rules-engine/types/action.types.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// src/rules-engine/types/action.types.ts
|
||||
|
||||
import type { RuleAction } from './rule.types';
|
||||
|
||||
export interface ActionHandler {
|
||||
type: string;
|
||||
execute(action: RuleAction, context: Record<string, any>): Promise<ActionResult>;
|
||||
}
|
||||
|
||||
export type ActionResult = {
|
||||
success: boolean;
|
||||
data?: Record<string, any>;
|
||||
error?: string;
|
||||
};
|
||||
15
src/rules-engine/types/fact.types.ts
Normal file
15
src/rules-engine/types/fact.types.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// src/rules-engine/types/fact.types.ts
|
||||
|
||||
export type FactValue = string | number | boolean | string[] | null;
|
||||
|
||||
export type FactContext = {
|
||||
lead?: Record<string, FactValue>;
|
||||
call?: Record<string, FactValue>;
|
||||
agent?: Record<string, FactValue>;
|
||||
campaign?: Record<string, FactValue>;
|
||||
};
|
||||
|
||||
export interface FactProvider {
|
||||
name: string;
|
||||
resolveFacts(entityData: any): Promise<Record<string, FactValue>>;
|
||||
}
|
||||
126
src/rules-engine/types/rule.types.ts
Normal file
126
src/rules-engine/types/rule.types.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
// src/rules-engine/types/rule.types.ts
|
||||
|
||||
export type RuleType = 'priority' | 'automation';
|
||||
|
||||
export type RuleTrigger =
|
||||
| { type: 'on_request'; request: 'worklist' | 'assignment' }
|
||||
| { type: 'on_event'; event: string }
|
||||
| { type: 'on_schedule'; interval: string }
|
||||
| { type: 'always' };
|
||||
|
||||
export type RuleCategory = 'priority' | 'assignment' | 'escalation' | 'lifecycle' | 'qualification';
|
||||
|
||||
export type RuleOperator =
|
||||
| 'equal' | 'notEqual'
|
||||
| 'greaterThan' | 'greaterThanInclusive'
|
||||
| 'lessThan' | 'lessThanInclusive'
|
||||
| 'in' | 'notIn'
|
||||
| 'contains' | 'doesNotContain'
|
||||
| 'exists' | 'doesNotExist';
|
||||
|
||||
export type RuleCondition = {
|
||||
fact: string;
|
||||
operator: RuleOperator;
|
||||
value: any;
|
||||
path?: string;
|
||||
};
|
||||
|
||||
export type RuleConditionGroup = {
|
||||
all?: (RuleCondition | RuleConditionGroup)[];
|
||||
any?: (RuleCondition | RuleConditionGroup)[];
|
||||
};
|
||||
|
||||
export type RuleActionType = 'score' | 'assign' | 'escalate' | 'update' | 'notify';
|
||||
|
||||
export type ScoreActionParams = {
|
||||
weight: number;
|
||||
slaMultiplier?: boolean;
|
||||
campaignMultiplier?: boolean;
|
||||
};
|
||||
|
||||
export type AssignActionParams = {
|
||||
agentId?: string;
|
||||
agentPool?: string[];
|
||||
strategy: 'specific' | 'round-robin' | 'least-loaded' | 'skill-based';
|
||||
};
|
||||
|
||||
export type EscalateActionParams = {
|
||||
channel: 'toast' | 'notification' | 'sms' | 'email';
|
||||
recipients: 'supervisor' | 'agent' | string[];
|
||||
message: string;
|
||||
severity: 'warning' | 'critical';
|
||||
};
|
||||
|
||||
export type UpdateActionParams = {
|
||||
entity: string;
|
||||
field: string;
|
||||
value: any;
|
||||
};
|
||||
|
||||
export type RuleAction = {
|
||||
type: RuleActionType;
|
||||
params: ScoreActionParams | AssignActionParams | EscalateActionParams | UpdateActionParams;
|
||||
};
|
||||
|
||||
export type Rule = {
|
||||
id: string;
|
||||
ruleType: RuleType;
|
||||
name: string;
|
||||
description?: string;
|
||||
enabled: boolean;
|
||||
priority: number;
|
||||
trigger: RuleTrigger;
|
||||
conditions: RuleConditionGroup;
|
||||
action: RuleAction;
|
||||
status?: 'draft' | 'published';
|
||||
metadata: {
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
createdBy: string;
|
||||
category: RuleCategory;
|
||||
tags?: string[];
|
||||
};
|
||||
};
|
||||
|
||||
export type ScoreBreakdown = {
|
||||
baseScore: number;
|
||||
slaMultiplier: number;
|
||||
campaignMultiplier: number;
|
||||
rulesApplied: string[];
|
||||
};
|
||||
|
||||
export type ScoredItem = {
|
||||
id: string;
|
||||
score: number;
|
||||
scoreBreakdown: ScoreBreakdown;
|
||||
slaStatus: 'low' | 'medium' | 'high' | 'critical';
|
||||
slaElapsedPercent: number;
|
||||
};
|
||||
|
||||
// Priority config — what the supervisor edits via sliders
|
||||
export type TaskWeightConfig = {
|
||||
weight: number; // 0-10
|
||||
slaMinutes: number; // SLA in minutes
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export type PriorityConfig = {
|
||||
taskWeights: Record<string, TaskWeightConfig>;
|
||||
campaignWeights: Record<string, number>; // campaignId → 0-10
|
||||
sourceWeights: Record<string, number>; // leadSource → 0-10
|
||||
};
|
||||
|
||||
export const DEFAULT_PRIORITY_CONFIG: PriorityConfig = {
|
||||
taskWeights: {
|
||||
missed_call: { weight: 9, slaMinutes: 720, enabled: true },
|
||||
follow_up: { weight: 8, slaMinutes: 1440, enabled: true },
|
||||
campaign_lead: { weight: 7, slaMinutes: 2880, enabled: true },
|
||||
attempt_2: { weight: 6, slaMinutes: 1440, enabled: true },
|
||||
attempt_3: { weight: 4, slaMinutes: 2880, enabled: true },
|
||||
},
|
||||
campaignWeights: {},
|
||||
sourceWeights: {
|
||||
WHATSAPP: 9, PHONE: 8, FACEBOOK_AD: 7, GOOGLE_AD: 7,
|
||||
INSTAGRAM: 5, WEBSITE: 7, REFERRAL: 6, WALK_IN: 5, OTHER: 5,
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user