mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-05-18 20:08:19 +00:00
- 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>
124 lines
4.0 KiB
TypeScript
124 lines
4.0 KiB
TypeScript
// 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 };
|
|
}
|
|
}
|