guanghulab/scripts/skyeye/scan-soldier-health.js

157 lines
4.5 KiB
JavaScript
Raw Normal View History

// scripts/skyeye/scan-soldier-health.js
// 天眼·扫描模块D15 · 小兵健康全局视图
//
// 扫描内容:
// ① 读取铸渊将军仪表盘 commander-dashboard.json
// ② 小兵故障率 > 20% → P0 工单
// ③ 小兵连续7天同一错误未修复 → P1 工单
// ④ 全局小兵健康统计
//
// 输出JSON → stdout
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
const DASHBOARD_PATH = path.join(ROOT, 'data/bulletin-board/dashboard.json');
const SKYEYE_REPORTS_DIR = path.join(ROOT, 'data/skyeye-reports');
const FAILURE_RATE_THRESHOLD = 20;
const CONSECUTIVE_FAILURE_THRESHOLD_DAYS = 7;
// ━━━ 安全读取 JSON ━━━
function readJSON(filePath) {
try {
if (!fs.existsSync(filePath)) return null;
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (e) {
return null;
}
}
// ━━━ 获取最近N天的天眼报告 ━━━
function getRecentReports(days) {
const reports = [];
try {
if (!fs.existsSync(SKYEYE_REPORTS_DIR)) return reports;
const files = fs.readdirSync(SKYEYE_REPORTS_DIR)
.filter(f => f.startsWith('skyeye-') && f.endsWith('.json'))
.sort()
.reverse()
.slice(0, days);
for (const file of files) {
const report = readJSON(path.join(SKYEYE_REPORTS_DIR, file));
if (report) reports.push(report);
}
} catch (e) {
// ignore
}
return reports;
}
// ━━━ D15 主扫描 ━━━
function scanSoldierHealth() {
const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
const now = new Date();
const bjTime = new Date(now.getTime() + BEIJING_OFFSET_MS).toISOString()
.replace('T', ' ').slice(0, 19) + '+08:00';
const result = {
dimension: 'D15',
name: '小兵健康全局视图',
scan_time: bjTime,
status: '✅',
dashboard_exists: false,
total_soldiers: 0,
healthy: 0,
failed: 0,
needs_optimization: 0,
failure_rate: 0,
issues: [],
recurring_errors: [],
recommendations: []
};
// 读取将军仪表盘
const dashboard = readJSON(DASHBOARD_PATH);
if (!dashboard) {
result.dashboard_exists = false;
result.status = '⚠️';
result.issues.push({
type: 'dashboard_missing',
message: '将军仪表盘不存在 · 需先运行 commander-dashboard.js',
action: 'P1工单 · 生成仪表盘'
});
} else {
result.dashboard_exists = true;
const gv = dashboard.global_view || {};
result.total_soldiers = gv.total_soldiers || 0;
result.healthy = gv.healthy || 0;
result.failed = gv.failed || 0;
result.needs_optimization = gv.needs_optimization || 0;
// 计算故障率
if (result.total_soldiers > 0) {
result.failure_rate = Math.round((result.failed / result.total_soldiers) * 100);
}
// 故障率 > 20% → P0
if (result.failure_rate > FAILURE_RATE_THRESHOLD) {
result.status = '❌';
result.issues.push({
type: 'high_failure_rate',
message: `小兵故障率 ${result.failure_rate}% > ${FAILURE_RATE_THRESHOLD}%`,
action: 'P0工单 · 全局小兵修复'
});
}
}
// 检查连续7天同一错误
const recentReports = getRecentReports(CONSECUTIVE_FAILURE_THRESHOLD_DAYS);
if (recentReports.length >= 2) {
// 收集各报告中的失败 workflow
const errorMap = {};
for (const report of recentReports) {
if (report.workflow_health && report.workflow_health.details) {
for (const wf of report.workflow_health.details) {
if (wf.status === 'failed') {
const key = wf.name || wf.file;
errorMap[key] = (errorMap[key] || 0) + 1;
}
}
}
}
// 连续7天同一错误
for (const [name, count] of Object.entries(errorMap)) {
if (count >= CONSECUTIVE_FAILURE_THRESHOLD_DAYS) {
result.recurring_errors.push({
soldier: name,
consecutive_failures: count,
action: 'P1工单 · 建议铸渊介入'
});
if (result.status === '✅') result.status = '⚠️';
}
}
}
// 生成建议
if (result.failed > 0) {
result.recommendations.push(`修复 ${result.failed} 个故障小兵`);
}
if (result.recurring_errors.length > 0) {
result.recommendations.push(`${result.recurring_errors.length} 个小兵存在持续性故障,需铸渊介入`);
}
if (result.total_soldiers === 0) {
result.recommendations.push('仪表盘无小兵数据 · 需先生成仪表盘');
}
console.log(JSON.stringify(result, null, 2));
return result;
}
scanSoldierHealth();