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:
2026-04-01 16:59:10 +05:30
parent 7b59543d36
commit b8556cf440
20 changed files with 959 additions and 3 deletions

View File

@@ -0,0 +1,30 @@
// src/rules-engine/facts/lead-facts.provider.ts
import type { FactProvider, FactValue } from '../types/fact.types';
export class LeadFactsProvider implements FactProvider {
name = 'lead';
async resolveFacts(lead: any): Promise<Record<string, FactValue>> {
const createdAt = lead.createdAt ? new Date(lead.createdAt).getTime() : Date.now();
const lastContacted = lead.lastContacted ? new Date(lead.lastContacted).getTime() : null;
return {
'lead.source': lead.leadSource ?? lead.source ?? null,
'lead.status': lead.leadStatus ?? lead.status ?? null,
'lead.priority': lead.priority ?? 'NORMAL',
'lead.campaignId': lead.campaignId ?? null,
'lead.campaignName': lead.campaignName ?? null,
'lead.interestedService': lead.interestedService ?? null,
'lead.contactAttempts': lead.contactAttempts ?? 0,
'lead.ageMinutes': Math.round((Date.now() - createdAt) / 60000),
'lead.ageDays': Math.round((Date.now() - createdAt) / 86400000),
'lead.lastContactedMinutes': lastContacted ? Math.round((Date.now() - lastContacted) / 60000) : null,
'lead.hasPatient': !!lead.patientId,
'lead.isDuplicate': lead.isDuplicate ?? false,
'lead.isSpam': lead.isSpam ?? false,
'lead.spamScore': lead.spamScore ?? 0,
'lead.leadScore': lead.leadScore ?? 0,
};
}
}