Files
egorin/src/core/notifyEngine.ts
T
GermanandClaude Opus 4.8 b1c52ab748 Железный Егорин — форк Орбиты: тёплый стиль (amber), логин с odyssey WebGL-молнией справа, энергомониторинг+панель
Ребренд Мониторинг Орбита -> Железный Егорин; accent green->amber #F59E0B; LoginScreen правая половина = Lightning (WebGL, hue 35 тёплый) под стеклянной формой; левая панель — тёплый градиент.
Доступ агентам: репо публичный, push german-токеном.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 23:57:16 +03:00

79 lines
2.8 KiB
TypeScript

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 };
}