import type { NotifyRule, NotifySchedule } from '@/data/notify'; import type { Severity } from '@/core/types'; // Движок правил уведомлений: сопоставляет событие с правилами, объясняет ПОЧЕМУ, // и собирает объединённый набор каналов. Чистые функции — тестируемо и предсказуемо. export interface NEvent { section: string; severity: Severity; objectId?: string; objectName?: string; text?: string; } export interface RuleMatch { rule: NotifyRule; reasons: string[]; } export interface RuleMiss { rule: NotifyRule; why: string; } // Активно ли расписание правила в момент now export function scheduleActive(schedule: NotifySchedule, now: Date): boolean { const h = now.getHours(); const day = now.getDay(); // 0=вс … 6=сб const workday = day >= 1 && day <= 5; if (schedule === 'always') return true; if (schedule === 'work') return workday && h >= 9 && h < 18; if (schedule === 'night') return !workday || h < 9 || h >= 18; return true; } export function evaluate(ev: NEvent, rules: NotifyRule[], now: Date = new Date()): { matched: RuleMatch[]; missed: RuleMiss[]; channels: string[]; } { const matched: RuleMatch[] = []; const missed: RuleMiss[] = []; for (const r of rules) { if (!r.enabled) { missed.push({ rule: r, why: 'правило выключено' }); continue; } const secOk = r.sections.length === 0 || r.sections.includes(ev.section); const sevOk = r.severities.length === 0 || r.severities.includes(ev.severity); const schedOk = scheduleActive(r.schedule, now); if (!secOk) { missed.push({ rule: r, why: `раздел «${ev.section}» не в списке правила` }); continue; } if (!sevOk) { missed.push({ rule: r, why: `важность «${ev.severity}» не в списке правила` }); continue; } if (!schedOk) { missed.push({ rule: r, why: 'вне расписания правила' }); continue; } const reasons: string[] = [ r.sections.length ? `раздел ∈ {${r.sections.join(', ')}}` : 'любой раздел', r.severities.length ? `важность ∈ {${r.severities.join(', ')}}` : 'любая важность', 'расписание активно', ]; matched.push({ rule: r, reasons }); } // Объединённый набор каналов (по приоритету правил) const order = matched.slice().sort((a, b) => b.rule.priority - a.rule.priority); const channels: string[] = []; for (const m of order) for (const c of m.rule.channels) if (!channels.includes(c)) channels.push(c); return { matched, missed, channels }; }