Files
lingniu-vehicle-ingest/vehicle-data-platform/apps/web/src/v2/domain/alert.ts
2026-07-18 17:27:08 +08:00

59 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { AlertEvent, AlertRule, AlertSeverity, AlertStatus } from '../../api/types';
import { formatZhNumber } from './formatters';
const alertTimeFormatter = new Intl.DateTimeFormat('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
timeZone: 'Asia/Shanghai'
});
export const severityLabels: Record<AlertSeverity, string> = { critical: '紧急', major: '重要', minor: '一般' };
export const statusLabels: Record<AlertStatus, string> = { unprocessed: '未处理', processing: '处理中', recovered: '已恢复', closed: '已关闭', ignored: '已忽略' };
export const actionLabels: Record<string, string> = { trigger: '触发', acknowledge: '已确认', close: '已关闭', ignore: '已忽略', recover: '已恢复', processing: '处理中', recovered: '已恢复', closed: '已关闭', ignored: '已忽略' };
export const metricLabels: Record<string, string> = { speed_kmh: '速度', soc_percent: 'SOC', alarm_active: '协议告警位', freshness_sec: '离线时长', data_delay_sec: '数据延迟' };
export const operatorLabels: Record<string, string> = { gt: '>', gte: '≥', lt: '<', lte: '≤', eq: '=', neq: '≠', between: '区间内', outside: '区间外', changed: '状态变化' };
export function formatAlertTime(value: string) {
if (!value) return '—';
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value.replace('T', ' ').slice(0, 19);
return alertTimeFormatter.format(date).replace(/\//g, '-');
}
export function alertValue(event: Pick<AlertEvent, 'triggerValue' | 'unit'>) {
return `${formatZhNumber(Number(event.triggerValue.toFixed(2)), 2)} ${event.unit}`.trim();
}
export function thresholdText(event: Pick<AlertEvent, 'operator' | 'threshold' | 'thresholdHigh' | 'unit' | 'durationSec'>) {
const duration = event.durationSec > 0 ? `,持续 ${event.durationSec}` : '';
if (event.operator === 'between' || event.operator === 'outside') return `${operatorLabels[event.operator]} ${event.threshold}${event.thresholdHigh} ${event.unit}${duration}`.trim();
if (event.operator === 'changed') return `状态发生变化${duration}`;
return `${operatorLabels[event.operator] ?? event.operator} ${formatZhNumber(Number(event.threshold.toFixed(2)), 2)} ${event.unit}${duration}`.trim();
}
export function alertDeltaText(event: Pick<AlertEvent, 'operator' | 'triggerValue' | 'threshold' | 'unit'>) {
if (!Number.isFinite(event.triggerValue) || !Number.isFinite(event.threshold)) return '规则已命中';
const delta = event.operator === 'gt' || event.operator === 'gte'
? event.triggerValue - event.threshold
: event.operator === 'lt' || event.operator === 'lte'
? event.threshold - event.triggerValue
: Number.NaN;
if (!Number.isFinite(delta)) return '规则已命中';
return `+${formatZhNumber(Number(Math.max(0, delta).toFixed(2)), 2)} ${event.unit}`.trim();
}
export function ruleCondition(rule: AlertRule, labels: Record<string, string> = metricLabels) {
const threshold = rule.operator === 'changed' ? '状态变化' : rule.operator === 'between' || rule.operator === 'outside' ? `${operatorLabels[rule.operator]} ${rule.threshold}${rule.thresholdHigh}` : rule.valueType === 'boolean' ? (rule.booleanThreshold ? '是' : '否') : `${operatorLabels[rule.operator] ?? rule.operator} ${rule.threshold}`;
return `${labels[rule.metric] ?? rule.metric} ${threshold}${rule.durationSec ? ` · ${rule.durationSec}` : ''}`;
}
export function canAct(status: AlertStatus, action: 'acknowledge' | 'close' | 'ignore') {
if (action === 'acknowledge') return status === 'unprocessed';
if (action === 'close') return status === 'unprocessed' || status === 'processing' || status === 'recovered';
return status === 'unprocessed' || status === 'processing';
}