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:
123
src/rules-engine/rules-engine.controller.ts
Normal file
123
src/rules-engine/rules-engine.controller.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
// src/rules-engine/rules-engine.controller.ts
|
||||
|
||||
import { Controller, Get, Post, Put, Delete, Patch, Param, Body, HttpException, Logger } from '@nestjs/common';
|
||||
import { RulesStorageService } from './rules-storage.service';
|
||||
import { RulesEngineService } from './rules-engine.service';
|
||||
import type { Rule, PriorityConfig } from './types/rule.types';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
@Controller('api/rules')
|
||||
export class RulesEngineController {
|
||||
private readonly logger = new Logger(RulesEngineController.name);
|
||||
|
||||
constructor(
|
||||
private readonly storage: RulesStorageService,
|
||||
private readonly engine: RulesEngineService,
|
||||
) {}
|
||||
|
||||
// --- Priority Config (slider UI) ---
|
||||
|
||||
@Get('priority-config')
|
||||
async getPriorityConfig() {
|
||||
return this.storage.getPriorityConfig();
|
||||
}
|
||||
|
||||
@Put('priority-config')
|
||||
async updatePriorityConfig(@Body() body: PriorityConfig) {
|
||||
return this.storage.updatePriorityConfig(body);
|
||||
}
|
||||
|
||||
// --- Rule CRUD ---
|
||||
|
||||
@Get()
|
||||
async listRules() {
|
||||
return this.storage.getAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async getRule(@Param('id') id: string) {
|
||||
const rule = await this.storage.getById(id);
|
||||
if (!rule) throw new HttpException('Rule not found', 404);
|
||||
return rule;
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createRule(@Body() body: any) {
|
||||
if (!body.name || !body.trigger || !body.conditions || !body.action) {
|
||||
throw new HttpException('name, trigger, conditions, and action are required', 400);
|
||||
}
|
||||
return this.storage.create({
|
||||
...body,
|
||||
ruleType: body.ruleType ?? 'priority',
|
||||
enabled: body.enabled ?? true,
|
||||
priority: body.priority ?? 99,
|
||||
});
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
async updateRule(@Param('id') id: string, @Body() body: Partial<Rule>) {
|
||||
const updated = await this.storage.update(id, body);
|
||||
if (!updated) throw new HttpException('Rule not found', 404);
|
||||
return updated;
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async deleteRule(@Param('id') id: string) {
|
||||
const deleted = await this.storage.delete(id);
|
||||
if (!deleted) throw new HttpException('Rule not found', 404);
|
||||
return { status: 'ok' };
|
||||
}
|
||||
|
||||
@Patch(':id/toggle')
|
||||
async toggleRule(@Param('id') id: string) {
|
||||
const toggled = await this.storage.toggle(id);
|
||||
if (!toggled) throw new HttpException('Rule not found', 404);
|
||||
return toggled;
|
||||
}
|
||||
|
||||
@Post('reorder')
|
||||
async reorderRules(@Body() body: { ids: string[] }) {
|
||||
if (!body.ids?.length) throw new HttpException('ids array required', 400);
|
||||
return this.storage.reorder(body.ids);
|
||||
}
|
||||
|
||||
// --- Evaluation ---
|
||||
|
||||
@Post('evaluate')
|
||||
async evaluate(@Body() body: { trigger: string; triggerValue: string; facts: Record<string, any> }) {
|
||||
return this.engine.evaluate(body.trigger, body.triggerValue, body.facts);
|
||||
}
|
||||
|
||||
// --- Templates ---
|
||||
|
||||
@Get('templates/list')
|
||||
async listTemplates() {
|
||||
return [{ id: 'hospital-starter', name: 'Hospital Starter Pack', description: 'Default rules for a hospital call center', ruleCount: 7 }];
|
||||
}
|
||||
|
||||
@Post('templates/:id/apply')
|
||||
async applyTemplate(@Param('id') id: string) {
|
||||
if (id !== 'hospital-starter') throw new HttpException('Template not found', 404);
|
||||
|
||||
let template: any;
|
||||
try {
|
||||
template = JSON.parse(readFileSync(join(__dirname, 'templates', 'hospital-starter.json'), 'utf8'));
|
||||
} catch {
|
||||
throw new HttpException('Failed to load template', 500);
|
||||
}
|
||||
|
||||
// Apply priority config
|
||||
await this.storage.updatePriorityConfig(template.priorityConfig);
|
||||
|
||||
// Create rules
|
||||
const created: Rule[] = [];
|
||||
for (const rule of template.rules) {
|
||||
const newRule = await this.storage.create(rule);
|
||||
created.push(newRule);
|
||||
}
|
||||
|
||||
this.logger.log(`Applied hospital-starter template: ${created.length} rules + priority config`);
|
||||
return { status: 'ok', rulesCreated: created.length, rules: created };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user