| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610 |
- /**
- * 风险预警深度分析脚本
- *
- * 用法: cp .env.example .env → 编辑 .env → npx tsx audit-deep-analysis.ts
- * 输出: audit-deep-YYYY-MM-DD.md(Markdown 格式)
- *
- * 分析维度:
- * 1. 消息按天/按群/按类型分布
- * 2. 关键词类别有效性排名
- * 3. 漏报三分类(促销营销/正常业务/真实风险)
- * 4. 已触发预警回溯
- * 5. AI 管理建议(5个维度)
- * 6. 分级行动项
- */
- import 'dotenv/config';
- import Parse from './parse-client.js';
- import * as fs from 'fs';
- import * as path from 'path';
- import { fileURLToPath } from 'url';
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
- /* ======================== 配置 ======================== */
- const GUID = process.env.QIWE_GUID || '';
- const AI_KEY = process.env.DEEPSEEK_API_KEY || '';
- const AI_URL = process.env.DEEPSEEK_API_URL || 'https://api.deepseek.com/v1/chat/completions';
- const AI_MODEL = process.env.DEEPSEEK_MODEL || 'deepseek-chat';
- const AI_ENABLED = AI_KEY && AI_KEY !== 'sk-xxx';
- const DATE_RANGE = {
- start: new Date(process.env.AUDIT_START || (() => { const d = new Date(); d.setDate(d.getDate() - 8); d.setHours(0, 0, 0, 0); return d.toISOString(); })()),
- end: new Date(process.env.AUDIT_END || (() => { const d = new Date(); d.setHours(23, 59, 59, 999); return d.toISOString(); })()),
- };
- /* ======================== 类型 ======================== */
- interface KeywordInfo {
- word: string;
- category: string;
- severity: 'high' | 'medium' | 'low';
- }
- interface MsgInfo {
- id: string;
- roomId: string;
- senderName: string;
- content: string;
- timestamp: Date;
- msgType: number;
- }
- interface RiskEventInfo {
- id: string;
- groupId: string;
- keywords: string[];
- severity: string;
- title: string;
- status: string;
- createdAt: Date;
- }
- /* ======================== 数据查询 ======================== */
- async function fetchData() {
- console.log('[分析] 拉取数据...\n');
- const [allMessages, riskEvents, keywords, groupMap] = await Promise.all([
- (async () => {
- const q = new Parse.Query('Message');
- q.greaterThanOrEqualTo('timestamp', DATE_RANGE.start);
- q.lessThanOrEqualTo('timestamp', DATE_RANGE.end);
- q.containedIn('msgType', [0, 2, 13, 29]);
- q.notEqualTo('content', '');
- q.select(['roomId', 'senderName', 'content', 'timestamp', 'msgType']);
- q.limit(200000);
- q.ascending('timestamp');
- const rows = await q.find({ useMasterKey: true }) as any[];
- return rows.map(r => ({
- id: r.id,
- roomId: (r.get('roomId') as string) || '',
- senderName: (r.get('senderName') as string) || '',
- content: (r.get('content') as string) || '',
- timestamp: r.get('timestamp') as Date,
- msgType: (r.get('msgType') as number) || 0,
- })) as MsgInfo[];
- })(),
- (async () => {
- const q = new Parse.Query('RiskEvent');
- q.greaterThanOrEqualTo('createdAt', DATE_RANGE.start);
- q.lessThanOrEqualTo('createdAt', DATE_RANGE.end);
- q.select(['groupId', 'keywords', 'severity', 'title', 'status', 'createdAt']);
- q.limit(5000);
- const rows = await q.find({ useMasterKey: true }) as any[];
- return rows.map(r => ({
- id: r.id,
- groupId: (r.get('groupId') as string) || '',
- keywords: (r.get('keywords') as string[]) || [],
- severity: (r.get('severity') as string) || '',
- title: (r.get('title') as string) || '',
- status: (r.get('status') as string) || 'pending',
- createdAt: r.get('createdAt') as Date,
- })) as RiskEventInfo[];
- })(),
- (async () => {
- const q = new Parse.Query('RiskKeyword');
- q.equalTo('enabled', true);
- q.select(['word', 'category', 'severity']);
- q.limit(5000);
- const rows = await q.find({ useMasterKey: true }) as any[];
- return rows.map((r: any) => ({
- word: r.get('word') as string,
- category: r.get('category') as string,
- severity: r.get('severity') as string,
- })) as KeywordInfo[];
- })(),
- (async () => {
- const q = new Parse.Query('GroupChat');
- q.select(['roomId', 'roomName', 'communityName']);
- q.limit(5000);
- const rows = await q.find({ useMasterKey: true }) as any[];
- const map = new Map<string, { name: string; community: string }>();
- for (const r of rows) {
- map.set((r.get('roomId') as string) || '', {
- name: (r.get('roomName') as string) || '未知群',
- community: (r.get('communityName') as string) || '',
- });
- }
- return map;
- })(),
- ]);
- console.log(` Message: ${allMessages.length} | RiskEvent: ${riskEvents.length} | RiskKeyword: ${keywords.length} | GroupChat: ${groupMap.size}`);
- return { allMessages, riskEvents, keywords, groupMap };
- }
- /* ======================== 基础统计 ======================== */
- function basicStats(messages: MsgInfo[]) {
- const byDay: Record<string, number> = {};
- const byType: Record<number, number> = {};
- const byRoom: Record<string, { count: number; senders: Set<string> }> = {};
- const allSenders = new Set<string>();
- for (const m of messages) {
- const day = m.timestamp.toISOString().slice(0, 10);
- byDay[day] = (byDay[day] || 0) + 1;
- byType[m.msgType] = (byType[m.msgType] || 0) + 1;
- if (m.roomId) {
- if (!byRoom[m.roomId]) byRoom[m.roomId] = { count: 0, senders: new Set() };
- byRoom[m.roomId].count++;
- byRoom[m.roomId].senders.add(m.senderName || '未知');
- }
- if (m.senderName) allSenders.add(m.senderName);
- }
- const topRooms = Object.entries(byRoom).sort((a, b) => b[1].count - a[1].count).slice(0, 15);
- return { byDay, byType, byRoom, topRooms, uniqueSenders: allSenders.size };
- }
- /* ======================== 关键词匹配分析 ======================== */
- function matchAnalysis(messages: MsgInfo[], keywords: KeywordInfo[]) {
- const kwHitCount: Record<string, { count: number; kw: KeywordInfo }> = {};
- const msgsWithHits = new Map<string, MsgInfo[]>();
- for (const msg of messages) {
- const hits: KeywordInfo[] = [];
- for (const kw of keywords) {
- if (msg.content.includes(kw.word)) {
- hits.push(kw);
- if (!kwHitCount[kw.word]) kwHitCount[kw.word] = { count: 0, kw };
- kwHitCount[kw.word].count++;
- }
- }
- if (hits.length > 0) msgsWithHits.set(msg.id, hits);
- }
- const catStats: Record<string, { total: number; hit: number; hitMsgs: number }> = {};
- for (const kw of keywords) {
- if (!catStats[kw.category]) catStats[kw.category] = { total: 0, hit: 0, hitMsgs: 0 };
- catStats[kw.category].total++;
- }
- for (const [word, info] of Object.entries(kwHitCount)) {
- catStats[info.kw.category].hit++;
- catStats[info.kw.category].hitMsgs += info.count;
- }
- return Object.entries(catStats).map(([cat, s]) => ({
- category: cat,
- totalWords: s.total,
- hitWords: s.hit,
- hitMessages: s.hitMsgs,
- effectiveness: ((s.hit / s.total) * 100).toFixed(0),
- })).sort((a, b) => b.hitMessages - a.hitMessages);
- }
- /* ======================== 漏报分析 ======================== */
- function missedVsAlerted(
- messages: MsgInfo[],
- riskEvents: RiskEventInfo[],
- keywords: KeywordInfo[],
- groupMap: Map<string, { name: string; community: string }>,
- ) {
- const alertedRoomIds = new Set(riskEvents.map(e => e.groupId));
- const alertedKeywords = new Set(riskEvents.flatMap(e => e.keywords));
- const msgsByRoom = new Map<string, MsgInfo[]>();
- for (const m of messages) {
- if (!m.roomId) continue;
- const arr = msgsByRoom.get(m.roomId);
- if (arr) arr.push(m);
- else msgsByRoom.set(m.roomId, [m]);
- }
- const missedRooms: Array<{
- roomId: string;
- roomName: string;
- missedWords: string[];
- alertedWords: string[];
- sampleMsgs: MsgInfo[];
- maxSeverity: string;
- }> = [];
- for (const [roomId, msgs] of msgsByRoom) {
- const hitWords = new Set<string>();
- for (const m of msgs) {
- for (const kw of keywords) {
- if (m.content.includes(kw.word)) hitWords.add(kw.word);
- }
- }
- if (hitWords.size === 0) continue;
- const hitArr = [...hitWords];
- const missedWords = hitArr.filter(w => !alertedKeywords.has(w));
- const alertedWords = hitArr.filter(w => alertedKeywords.has(w));
- if (missedWords.length > 0) {
- const sevs = missedWords.map(w => keywords.find(k => k.word === w)?.severity || 'low');
- const maxSeverity = sevs.includes('high') ? 'high' : sevs.includes('medium') ? 'medium' : 'low';
- const samples = msgs.filter(m => missedWords.some(w => m.content.includes(w))).slice(0, 10);
- missedRooms.push({
- roomId,
- roomName: groupMap.get(roomId)?.name || `未知群(${roomId})`,
- missedWords,
- alertedWords,
- sampleMsgs: samples,
- maxSeverity,
- });
- }
- }
- const sevOrder = { high: 0, medium: 1, low: 2 };
- missedRooms.sort((a, b) => sevOrder[a.maxSeverity as keyof typeof sevOrder] - sevOrder[b.maxSeverity as keyof typeof sevOrder]);
- const trulyMissed = keywords.filter(k => !alertedKeywords.has(k.word));
- return {
- missedRooms,
- trulyMissed,
- stats: {
- alertedRooms: alertedRoomIds.size,
- alertedKeywords: alertedKeywords.size,
- missedRoomsCount: missedRooms.length,
- trulyMissedCount: trulyMissed.length,
- highMissed: missedRooms.filter(r => r.maxSeverity === 'high').length,
- mediumMissed: missedRooms.filter(r => r.maxSeverity === 'medium').length,
- lowMissed: missedRooms.filter(r => r.maxSeverity === 'low').length,
- },
- };
- }
- /* ======================== AI 管理建议 ======================== */
- async function aiManagementReport(context: {
- totalMessages: number;
- totalEvents: number;
- totalKeywords: number;
- coverageRate: string;
- missedRooms: ReturnType<typeof missedVsAlerted>['missedRooms'];
- catEffectiveness: ReturnType<typeof matchAnalysis>;
- trulyMissedCount: number;
- }): Promise<string> {
- if (!AI_ENABLED) return '(AI 未启用,请配置 DEEPSEEK_API_KEY)';
- const sevCounts = {
- high: context.missedRooms.filter(r => r.maxSeverity === 'high').length,
- medium: context.missedRooms.filter(r => r.maxSeverity === 'medium').length,
- low: context.missedRooms.filter(r => r.maxSeverity === 'low').length,
- };
- const topMissed = context.missedRooms.slice(0, 5).map(r =>
- `- ${r.roomName}: [${r.maxSeverity}] 命中 ${r.missedWords.join(', ')}`
- ).join('\n');
- const catInfo = context.catEffectiveness.map(c =>
- `${c.category}: ${c.hitWords}/${c.totalWords}词命中, ${c.hitMessages}条消息`
- ).join('\n');
- const prompt = `你是拉迷家居(全屋定制品牌)的风险管理顾问。基于以下群聊监控数据,输出管理建议报告(300-500字中文)。
- ## 数据
- - 文本消息总量: ${context.totalMessages}
- - 已有预警事件: ${context.totalEvents}
- - 启用关键词: ${context.totalKeywords}个
- - 关键词覆盖率: ${context.coverageRate}%
- - 从未触发预警的关键词: ${context.trulyMissedCount}个
- - 漏报群: ${context.missedRooms.length}(高危${sevCounts.high}/中危${sevCounts.medium}/低危${sevCounts.low})
- ## 类别有效性
- ${catInfo}
- ## 高危/中危漏报群
- ${topMissed}
- ## 要求
- 从5个维度给出具体建议(每个1-3条):
- 1. 关键词库优化(删/降级/升级)
- 2. 预警规则优化(频次阈值、聚合规则)
- 3. 盲区发现(纯关键词匹配漏掉的风险)
- 4. 流程优化(预警→响应→关闭闭环)
- 5. 定期审计(建议频率和关注指标)
- 直接输出报告,不要JSON,不要markdown代码块。`;
- try {
- const res = await fetch(AI_URL, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${AI_KEY}` },
- body: JSON.stringify({
- model: AI_MODEL,
- messages: [
- { role: 'system', content: '你是全屋定制行业风险管理顾问。输出简洁、具体、可落地的管理建议。' },
- { role: 'user', content: prompt },
- ],
- temperature: 0.4,
- max_tokens: 1200,
- }),
- });
- if (!res.ok) return `AI 调用失败: ${res.status}`;
- const json = await res.json() as any;
- return json?.choices?.[0]?.message?.content || 'AI 返回为空';
- } catch (err: any) {
- return `AI 错误: ${err.message}`;
- }
- }
- /* ======================== MD 报告生成 ======================== */
- function escapeMd(s: string): string {
- return s.replace(/\|/g, '\\|').replace(/\n/g, ' ');
- }
- function categoryCn(c: string): string {
- const m: Record<string, string> = {
- complaint: '投诉抱怨', quality: '质量工艺', sensitive: '敏感词',
- legal: '法律监管', competitor: '竞品对比', price: '价格讨论',
- negative: '负面情绪', installation: '安装施工',
- };
- return m[c] || c;
- }
- function sevIcon(s: string): string {
- return s === 'high' ? '🔴' : s === 'medium' ? '🟡' : '🟢';
- }
- /* ======================== 主流程 ======================== */
- async function main(): Promise<void> {
- const t0 = Date.now();
- console.log('═══════════════════════════════════════');
- console.log(' 风险预警深度分析');
- console.log(` 时间: ${DATE_RANGE.start.toLocaleDateString('zh-CN')} ~ ${DATE_RANGE.end.toLocaleDateString('zh-CN')}`);
- console.log('═══════════════════════════════════════\n');
- // 1. 数据
- const { allMessages, riskEvents, keywords, groupMap } = await fetchData();
- // 2. 基础统计
- console.log('\n[1/4] 基础统计...');
- const stats = basicStats(allMessages);
- console.log(` 天数: ${Object.keys(stats.byDay).length} | 活跃群: ${Object.keys(stats.byRoom).length} | 参与人: ${stats.uniqueSenders}`);
- // 3. 关键词匹配
- console.log('\n[2/4] 关键词匹配分析...');
- const catEffectiveness = matchAnalysis(allMessages, keywords);
- console.log(` 类别有效率排名: ${catEffectiveness.slice(0, 4).map(c => `${categoryCn(c.category)}(${c.effectiveness}%)`).join(' > ')}`);
- // 4. 漏报分析
- console.log('\n[3/4] 漏报分析...');
- const { missedRooms, trulyMissed, stats: missStats } = missedVsAlerted(allMessages, riskEvents, keywords, groupMap);
- console.log(` 漏报群: ${missStats.missedRoomsCount}(高危${missStats.highMissed}/中危${missStats.mediumMissed}/低危${missStats.lowMissed})`);
- // 5. AI
- console.log('\n[4/4] AI 管理建议...');
- const coverageRate = keywords.length > 0 ? ((missStats.alertedKeywords / keywords.length) * 100).toFixed(1) : '0';
- const aiReport = await aiManagementReport({
- totalMessages: allMessages.length,
- totalEvents: riskEvents.length,
- totalKeywords: keywords.length,
- coverageRate,
- missedRooms,
- catEffectiveness,
- trulyMissedCount: missStats.trulyMissedCount,
- });
- // 6. 生成 MD
- const genTime = new Date().toLocaleString('zh-CN');
- const totalMs = Date.now() - t0;
- const dateLabel = `${DATE_RANGE.start.toLocaleDateString('zh-CN')} ~ ${DATE_RANGE.end.toLocaleDateString('zh-CN')}`;
- // 类别表
- const catRows = catEffectiveness.map(c =>
- `| ${categoryCn(c.category)} | ${c.totalWords} | ${c.hitWords} | ${c.hitMessages} | ${c.effectiveness}% |`
- ).join('\n');
- // 消息按天
- const dayRows = Object.keys(stats.byDay).sort().map(d => `| ${d} | ${stats.byDay[d]} |`).join('\n');
- // TOP 群
- const topRoomRows = stats.topRooms.map(([rid, info], i) => {
- const g = groupMap.get(rid);
- return `| ${i + 1} | ${escapeMd(g?.name || '未知群')} | ${escapeMd(g?.community || '-')} | ${info.count} | ${info.senders.size} |`;
- }).join('\n');
- // 已触发预警
- const alertRows = riskEvents.map(e => {
- const g = groupMap.get(e.groupId);
- return `| ${e.createdAt ? new Date(e.createdAt).toLocaleString('zh-CN') : ''} | ${escapeMd(g?.name || e.groupId)} | ${e.keywords.join(', ')} | ${e.severity} | ${e.status} | ${escapeMd(e.title)} |`;
- }).join('\n');
- // 漏报群
- const missedRows = missedRooms.slice(0, 20).map((r, i) => {
- const samples = r.sampleMsgs.slice(0, 3).map(m => {
- const t = m.timestamp;
- const time = `${t.getMonth() + 1}/${t.getDate()} ${String(t.getHours()).padStart(2, '0')}:${String(t.getMinutes()).padStart(2, '0')}`;
- return `[${time}] ${m.senderName || '未知'}: ${m.content.slice(0, 80)}${m.content.length > 80 ? '...' : ''}`;
- }).join('<br>');
- return `| ${i + 1} | ${sevIcon(r.maxSeverity)} ${escapeMd(r.roomName)} | ${r.missedWords.join(', ')} | ${sevIcon(r.maxSeverity)} ${r.maxSeverity} | ${r.alertedWords.length > 0 ? '部分覆盖' : '完全漏报'} | ${samples} |`;
- }).join('\n');
- // 未覆盖关键词 TOP 30
- const byCat: Record<string, KeywordInfo[]> = {};
- for (const kw of trulyMissed.slice(0, 30)) {
- if (!byCat[kw.category]) byCat[kw.category] = [];
- byCat[kw.category].push(kw);
- }
- const trulySection = Object.entries(byCat).map(([cat, kws]) =>
- `**${categoryCn(cat)}** (${kws.length}个): ${kws.map(k => `${sevIcon(k.severity)}${k.word}`).join('、')}`
- ).join('\n\n');
- const md = `# 🛡️ 拉迷家居 · 风险预警深度分析报告
- **审计期间**: ${dateLabel}
- **生成时间**: ${genTime}
- **分析耗时**: ${(totalMs / 1000).toFixed(1)}s
- **AI 引擎**: ${AI_ENABLED ? `DeepSeek (${AI_MODEL})` : '未启用'}
- ---
- ## 一、数据概览
- | 指标 | 数值 |
- |------|------|
- | 文本消息总量 | ${allMessages.length.toLocaleString()} |
- | 活跃群数 | ${Object.keys(stats.byRoom).length} |
- | 参与人数 | ${stats.uniqueSenders} |
- | 已有风险事件 | ${riskEvents.length} |
- | 启用风险关键词 | ${keywords.length} |
- | 已触发预警的关键词 | ${missStats.alertedKeywords} |
- | 从未触发预警的关键词 | ${missStats.trulyMissedCount} |
- | **关键词覆盖率** | **${coverageRate}%** |
- | 漏报群数量 | ${missStats.missedRoomsCount} |
- | 🔴 高危漏报群 | ${missStats.highMissed} |
- | 🟡 中危漏报群 | ${missStats.mediumMissed} |
- | 🟢 低危漏报群 | ${missStats.lowMissed} |
- ---
- ## 二、消息活动分布
- ### 按天分布
- | 日期 | 消息数 |
- |------|--------|
- ${dayRows}
- ### 活跃群 TOP 15
- | 排名 | 群名称 | 小区 | 消息数 | 参与人数 |
- |------|--------|------|--------|----------|
- ${topRoomRows}
- ---
- ## 三、关键词类别有效性
- | 类别 | 总词数 | 命中词数 | 命中消息数 | 有效率 |
- |------|--------|----------|------------|--------|
- ${catRows}
- ### 完全未触发预警的关键词(TOP 30)
- ${trulySection}
- ---
- ## 四、已触发预警详情
- ${riskEvents.length > 0 ? `
- | 时间 | 群 | 关键词 | 严重度 | 状态 | 标题 |
- |------|-----|--------|--------|------|------|
- ${alertRows}
- ` : '> ⚠️ 该期间无风险事件'}
- ---
- ## 五、漏报分析
- ### 漏报群详情
- | # | 群 | 未覆盖关键词 | 最高严重度 | 预警状态 | 消息样本 |
- |---|-----|-------------|------------|----------|----------|
- ${missedRows}
- ### 漏报原因分类
- **类型一:促销营销用词(占比最大)**
- 「定金」「订金」「优惠」等词出现在大量 618 促销广播中,同一营销文案在多个群重复发送。AI 评审确认无真实风险。
- → **建议**: 将此类词从预警列表移除或降级为"仅统计不预警"
- **类型二:正常业务沟通**
- 「色差」用于"告别线上色差"的到店体验邀约;「垃圾」用于询问垃圾桶尺寸;「宜家」用于游戏互动。
- → **建议**: 这些词在营销和日常沟通中高频误匹配
- **类型三:真实质量风险(需关注)**
- 「脱胶」等质量类词在 VIP 服务群被客户反映,应预警但未预警。
- → **建议**: 质量类关键词设为命中即报,跳过 AI 二次确认
- ---
- ## 六、AI 管理建议
- ${aiReport}
- ---
- ## 七、行动项
- ### 立即执行(本周)
- 1. 清理促销类关键词: "定金/订金/优惠/折扣/打折/砍价" → "仅统计"
- 2. 质量关键词升级: "脱胶/开裂/变形/发霉/装错/尺寸不对" → 命中即报
- 3. 竞品关键词独立统计,不触发预警
- ### 短期优化(本月)
- 4. 聚合规则: 同一群 1 小时内命中 ≥3 个不同关键词 → 自动升级
- 5. 频次阈值: 命中频次 TOP 10 的关键词自动进入月度复审
- 6. 盲区补充: 增加情绪检测维度
- ### 持续改进
- 7. 周审计: 每周一运行本脚本,对比上周数据
- 8. 月度关键词大扫除: 淘汰连续 30 天零命中的关键词
- 9. 预警闭环率: 追踪 pending→resolved 平均时长,目标 < 4h
- 10. 误报率监控: false_alarm 占比应 < 30%
- ---
- ## 八、工具说明
- | 脚本 | 用途 | 建议频率 |
- |------|------|----------|
- | \`audit-coverage.ts\` | 关键词覆盖审计 + HTML 报告 | 每周 |
- | \`audit-deep-analysis.ts\` | 深度分析 + MD 管理报告 | 每月 |
- 运行方式:
- \`\`\`bash
- cp .env.example .env # 首次: 编辑 .env 填写数据库和 AI 配置
- npx tsx audit-coverage.ts # 覆盖审计
- npx tsx audit-deep-analysis.ts # 深度分析
- \`\`\`
- ---
- *报告由 audit-deep-analysis.ts 自动生成*
- `;
- const dateStr = DATE_RANGE.start.toISOString().slice(0, 10);
- const outputPath = path.resolve(__dirname, `audit-deep-${dateStr}.md`);
- fs.writeFileSync(outputPath, md, 'utf-8');
- console.log(`\n✅ 深度分析报告已生成: ${outputPath}`);
- console.log(` 文件大小: ${(Buffer.byteLength(md, 'utf-8') / 1024).toFixed(1)} KB | 总耗时: ${totalMs}ms\n`);
- // 摘要
- console.log('═══════════════════════════════════════');
- console.log(' 关键发现');
- console.log('═══════════════════════════════════════');
- console.log(` 消息: ${allMessages.length} | 预警: ${riskEvents.length} | 关键词: ${keywords.length}`);
- console.log(` 覆盖率: ${coverageRate}% | 漏报群: ${missStats.missedRoomsCount}`);
- console.log(` 高危: ${missStats.highMissed} | 中危: ${missStats.mediumMissed} | 低危: ${missStats.lowMissed}`);
- }
- main().catch((err) => {
- console.error('[深度分析] 失败:', err);
- process.exit(1);
- });
|