guanghulab/scripts/neural/analyze-digest.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

187 lines
6.5 KiB
JavaScript
Raw Permalink 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.

// scripts/neural/analyze-digest.js
// 🧬 天眼日报分析引擎
// 输入:最新日报 + 历史日报最近7天+ 分析规则
// 输出:工单列表(如果有需要处理的问题)
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const DIGEST_DIR = 'data/neural-reports/daily-digest';
const RULES_PATH = 'skyeye/neural-analysis-rules.json';
const WORK_ORDER_DIR = 'data/neural-reports/work-orders';
const TERMINAL_STATUSES = ['CLOSED', 'ESCALATED', 'VERIFIED'];
function loadJSON(p) {
try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (e) { return null; }
}
function getRecentDigests(days) {
days = days || 7;
if (!fs.existsSync(DIGEST_DIR)) return [];
return fs.readdirSync(DIGEST_DIR)
.filter(function(f) { return f.endsWith('.json') && f !== '.gitkeep'; })
.sort().reverse().slice(0, days)
.map(function(f) { return loadJSON(path.join(DIGEST_DIR, f)); })
.filter(Boolean);
}
function evaluateCondition(digest, condition) {
var field = condition.field;
var operator = condition.operator;
var value = condition.value;
var fieldValue = field.split('.').reduce(function(obj, key) {
if (key === '*') return obj;
return obj ? obj[key] : undefined;
}, digest);
switch (operator) {
case 'equals': return fieldValue === value;
case 'not_empty': return Array.isArray(fieldValue) ? fieldValue.length > 0 : !!fieldValue;
case 'greater_than': return (fieldValue || 0) > value;
case 'greater_than_percent': {
var ofField = condition.of.split('.').reduce(function(o, k) { return o ? o[k] : undefined; }, digest);
return ofField > 0 && (fieldValue / ofField * 100) > value;
}
case 'contains': return JSON.stringify(fieldValue || '').indexOf(value) !== -1;
default: return false;
}
}
function detectTrends(digests, trendRules) {
var alerts = [];
if (digests.length < 3) return alerts;
for (var i = 0; i < trendRules.length; i++) {
var rule = trendRules[i];
if (rule.name === 'failure_rate_rising') {
var rates = digests.slice(0, 3).map(function(d) { return d.failure_rate_percent; });
if (rates[0] > rates[1] && rates[1] > rates[2]) {
alerts.push({
type: 'trend_alert', rule: rule.name, severity: rule.severity,
description: rule.description, data: { rates_last_3_days: rates }
});
}
}
if (rule.name === 'workflow_going_silent') {
var latestMap = digests[0] ? digests[0].brain_summary : null;
if (latestMap) {
var brains = Object.entries(latestMap);
for (var b = 0; b < brains.length; b++) {
var brain = brains[b][0];
var summary = brains[b][1];
var workflows = summary.workflows || [];
for (var w = 0; w < workflows.length; w++) {
var wf = workflows[w];
var allSilent = digests.slice(0, 3).every(function(d) {
var bSummary = d.brain_summary ? d.brain_summary[brain] : null;
var bwf = bSummary ? (bSummary.workflows || []).find(function(x) { return x.id === wf.id; }) : null;
return !bwf || bwf.runs === 0;
});
if (allSilent && wf.runs === 0) {
alerts.push({
type: 'silence_alert', rule: rule.name, severity: rule.severity,
description: wf.name + ' 连续 3 天无运行记录',
data: { workflow_id: wf.id, brain: brain }
});
}
}
}
}
}
}
return alerts;
}
function generateWorkOrders(digest, rules, trendAlerts) {
var workOrders = [];
var now = new Date().toISOString();
var severityEntries = Object.entries(rules.severity_rules);
for (var s = 0; s < severityEntries.length; s++) {
var severity = severityEntries[s][0];
var rule = severityEntries[s][1];
for (var c = 0; c < rule.conditions.length; c++) {
var condition = rule.conditions[c];
if (evaluateCondition(digest, condition)) {
workOrders.push({
id: 'WO-' + Date.now() + '-' + crypto.randomBytes(2).toString('hex'),
created: now,
source: 'neural-analysis-engine',
source_digest: digest.digest_id,
severity: severity.split('_')[0].toUpperCase(),
title: '[' + severity.split('_')[0].toUpperCase() + '] ' + condition.field + ' 触发 ' + rule.description,
description: '日报 ' + digest.digest_id + ' 中 ' + condition.field + ' 触发了 ' + rule.description + ' 规则',
action: rule.action,
notify: rule.notify,
timeout_hours: rule.timeout_hours,
status: 'pending'
});
break;
}
}
}
for (var t = 0; t < trendAlerts.length; t++) {
var alert = trendAlerts[t];
workOrders.push({
id: 'WO-TREND-' + Date.now() + '-' + crypto.randomBytes(2).toString('hex'),
created: now,
source: 'neural-trend-detection',
source_digest: digest.digest_id,
severity: alert.severity,
title: '[趋势] ' + alert.description,
description: JSON.stringify(alert.data),
action: 'investigate_trend',
status: 'pending'
});
}
return workOrders;
}
function main() {
console.log('\n━━━ 🧬 天眼日报分析引擎启动 ━━━\n');
var rules = loadJSON(RULES_PATH);
if (!rules) {
console.log('❌ 分析规则文件不存在,跳过');
return;
}
var digests = getRecentDigests(7);
if (digests.length === 0) {
console.log('⚠️ 无日报数据,跳过');
return;
}
var latest = digests[0];
console.log('📊 分析日报: ' + latest.digest_id);
console.log(' 整体健康: ' + latest.overall_health);
console.log(' 历史对比: ' + digests.length + ' 天数据\n');
var trendAlerts = detectTrends(digests, (rules.trend_detection || {}).rules || []);
console.log('📈 趋势告警: ' + trendAlerts.length + ' 个');
var workOrders = generateWorkOrders(latest, rules, trendAlerts);
console.log('📋 生成工单: ' + workOrders.length + ' 个\n');
if (workOrders.length > 0) {
fs.mkdirSync(WORK_ORDER_DIR, { recursive: true });
var date = latest.digest_id.replace('NEURAL-DIGEST-', '');
fs.writeFileSync(
path.join(WORK_ORDER_DIR, 'work-orders-' + date + '.json'),
JSON.stringify({ date: date, work_orders: workOrders }, null, 2)
);
console.log('💾 工单已保存到 ' + WORK_ORDER_DIR + '/');
}
for (var i = 0; i < workOrders.length; i++) {
console.log(' ' + workOrders[i].severity + ' | ' + workOrders[i].title);
}
console.log('\n━━━ 分析完成 ━━━\n');
}
main();