diff --git a/scripts/mofin_health.py b/scripts/mofin_health.py
index d75b4189..21b943e3 100644
--- a/scripts/mofin_health.py
+++ b/scripts/mofin_health.py
@@ -157,6 +157,92 @@ def match_cron(cron_jobs, name_keywords):
deduped.append(j)
return deduped
+
+# ── 功能树描述 ──
+NODE_DESC = {
+ "数据采集": "从腾讯/东财/小果采集原始行情、新闻、资金流数据",
+ "策略分析": "策略评估、新鲜度检查、重评和成长分析",
+ "推荐推送": "生成简报、推荐并推送到XMPP",
+ "风险监控": "宏观风险信号、跨市场背离检测",
+ "自检/审计": "系统健康检查、监控采集、审计",
+ "执行/修复": "自愈系统、门禁跟进、清理修复",
+ "持仓复查": "持仓基本面复查和策略复盘",
+ "信号消费": "消费小果情感分析和宏观风险信号",
+ "系统服务": "系统维护(如DB真空整理)",
+ "持仓监控": "特定持仓(300308/芯碁微装)盘中监控",
+ "市场快照": "每10分钟采集全市场板块和指数快照",
+ "宏观新闻": "采集宏观新闻和财经资讯",
+ "价格监控": "每2分钟刷新持仓/自选实时价格→写入live_prices",
+ "小果扫描": "小果独立扫描潜在机会",
+ "资金流采集": "盘中采集板块资金流向",
+ "宏观上下文刷新": "刷新大盘指数/市场情绪",
+ "策略重评": "价格偏离买入区或策略过期时自动重评",
+ "持仓自选新鲜度检查": "检查策略是否过期或价格严重偏离",
+ "自选买入区提醒": "自选进入买入区时推送提醒",
+ "策略评估": "每日/每周策略效果评估",
+ "分支自成长": "策略分支探索和剪枝",
+ "元自成长": "系统元层级自我进化",
+ "MoFin盘前中监控": "上午盘中实时监控+推送",
+ "MoFin午后监控": "下午盘中实时监控+推送",
+ "cron报告推XMPP": "cron报告通过XMPP推送到手机",
+ "开盘简报": "每日开盘前市场简报",
+ "收盘简报": "每日收盘后市场简报",
+ "市场精选推荐": "每日全市场潜力股精推",
+ "宏观风险扫描": "从新闻中检测系统性风险",
+ "宏观风险信号消费": "消费宏观风险信号并生成建议",
+ "跨市场背离检测": "检测A股/港股/美股指数背离",
+ "系统全局审计": "7维度系统全面审计",
+ "全局cron健康监控": "监控所有cron的运行状态",
+ "重评管道审计": "审计策略重评管道完整性",
+ "健康监控数据采集": "采集健康数据供Dashboard展示",
+ "自愈执行器": "每10分钟自动处理TODO列表",
+ "策略质量门禁": "新策略必须通过9维验证才能写入",
+ "自选自动清理": "开盘前清理过期自选数据",
+ "建议对账": "每周对账校验建议准确性",
+ "持仓基本面复查": "每周持仓基本面深度复查",
+ "策略复盘": "每日策略执行复盘",
+ "小果情感分析": "收盘后对持仓/自选做新闻情感分析",
+ "宏观风险信号消费-盘中": "盘中消费宏观风险信号",
+ "小果市场筛选": "全市场扫描值得关注的板块和个股",
+ "芯碁微装": "芯碁微装午后价格监控",
+ "300308": "300308午后紧盯+入场信号监控",
+ "硬编码扫描": "扫描脚本中的硬编码参数",
+ "系统体检": "开盘前系统全面体检",
+ "盘中自检": "盘中高频自检",
+ "记忆守卫": "每日记忆清理和优化",
+ "数据治理": "每周数据清理和归档",
+ "自选股自动重评": "周末自动重评自选股策略",
+ "多周期缓存": "刷新MA5/MA20/MA60等技术指标缓存",
+ "数据同步": "同步数据到Dashboard",
+ "盘前热点扫描": "盘前扫描市场热点",
+ "宏观新闻采集": "采集宏观新闻",
+ "宏观新闻采集-周末": "周末宏观新闻采集",
+ "state.db真空整理": "DB真空整理维护",
+ "分支剪枝-每日": "修剪已失效的策略分支",
+ "自选股自动重评-周末": "周末批量重评自选股策略",
+ "系统健康检查-开盘前": "开盘前检查所有核心组件是否正常",
+ "多周期缓存刷新-开盘前": "开盘前刷新技术指标缓存",
+ "MoFin 系统常规体检-开盘前": "开盘前8:00全面系统体检",
+ "开盘前钉对钉验证": "开盘前15项验证(脚本同步/DB完整性/资产公式)",
+ "cron-推XMPP中继": "将cron输出通过XMPP中继推送",
+ "小果信号消费-盘中": "盘中消费小果扫描信号",
+ "硬编码扫描-每日": "扫描脚本中的硬编码参数",
+ "盘中自检-高频": "每15分钟盘中自检",
+ "数据治理-每周": "每周数据治理",
+ "记忆守卫-每日": "每日记忆优化",
+ "300308入场信号紧盯": "300308入场信号(13:00-14:00)",
+ "300308午后紧盯": "300308午后监控(13:00-15:00含止损)",
+ "多周期缓存刷新-盘中": "盘中刷新技术指标缓存",
+ "知识萃取-盘后": "盘后从分析报告中萃取可复用知识",
+ "区间维护": "每30分钟维护买入区",
+ "知微洞察生成": "生成每日市场洞察(15:35)",
+ "小果市场筛选-全市场": "小果筛选全市场关注板块",
+ "数据同步-dashboard": "同步数据到Dashboard",
+ "state.db真空整理-每周": "每周DB真空整理",
+ "未分类": "未被规则匹配的cron自动归入此",
+}
+
+
def build_feature_tree(cron_jobs, db_stats):
# 硬编码分类规则:标签→匹配关键词
rules = {
@@ -228,6 +314,10 @@ def build_feature_tree(cron_jobs, db_stats):
def attach_pipes(node, parent_cat=None):
nonlocal matched_names
label = node.get("label", "")
+ # 附加描述(自动带脚本名的节点去掉括号内容匹配)
+ desc_key = label.split(" (")[0] if " (" in label else label
+ if desc_key in NODE_DESC:
+ node["desc"] = NODE_DESC[desc_key]
keywords = rules.get(label)
pipes = []
if keywords:
@@ -251,8 +341,11 @@ def build_feature_tree(cron_jobs, db_stats):
attach_pipes(c, parent_cat or label)
def make_cron_node(j):
+ name = j.get("name", "?")
+ desc_key = name.split(" (")[0] if " (" in name else name
return {
- "label": f"{j.get('name','?')} ({j.get('script','LLM')})",
+ "label": f"{name} ({j.get('script','LLM')})",
+ "desc": NODE_DESC.get(desc_key, ""),
"status": j.get("last_status", "unknown"),
"pipes": [{
"name": j.get("name", ""),
diff --git a/static/mofin_health.html b/static/mofin_health.html
index 4b00f466..912d88b6 100644
--- a/static/mofin_health.html
+++ b/static/mofin_health.html
@@ -68,7 +68,7 @@ function switchTab(idx) {
function renderFeatureTree(tree) {
let grp = (function(){let i=0;return function(){return 'ftg-'+(++i);}})();
- let html = '
| 功能模块 | 来源 | 类型 | 调度 | 状态 | 最后运行 |
';
+ let html = '| 功能模块 | 说明 | 来源 | 类型 | 调度 | 状态 | 最后运行 |
';
function walk(node, depth, groupId) {
const indent = 'padding-left:'+(depth*20+8)+'px';
@@ -80,8 +80,10 @@ function renderFeatureTree(tree) {
if (isCat) {
// 分类节点——生成一个可折叠组
const g = grp();
+ const desc = node.desc || '';
html += ``;
html += `| ▼ ${node.label} | `;
+ html += `${desc} | `;
html += `- | - | - | ${cls} | - |
`;
// 子节点都带上这个组的data-sub
node.children.forEach(c => walk(c, depth+1, g));
@@ -89,18 +91,19 @@ function renderFeatureTree(tree) {
}
if (pipes.length > 0) {
- pipes.forEach((p, i) => {
- const indent2 = i===0 ? indent : 'padding-left:'+(depth*20+32)+'px';
- const tagCls = p.status==='ok'?'ok':p.status==='error'?'error':'warn';
- const pBadge = p.profile==='position-analyst' ? '📋' : '📦';
- // 子节点显式带data-group
- const groupAttr = groupId ? `data-group="${groupId}"` : '';
- if (i===0) {
- html += `| ${node.label} | `;
- } else {
- html += `
| └ ${p.name||p.script||'LLM'} | `;
- }
- html += `${pBadge} ${p.profile||'?'} | `;
+ if (pipes.length > 0) {
+ pipes.forEach((p, i) => {
+ const indent2 = i===0 ? indent : 'padding-left:'+(depth*20+32)+'px';
+ const tagCls = p.status==='ok'?'ok':p.status==='error'?'error':'warn';
+ const pBadge = p.profile==='position-analyst' ? '📋' : '📦';
+ const descText = i===0 ? (node.desc || '') : '';
+ const groupAttr = groupId ? `data-group="${groupId}"` : '';
+ if (i===0) {
+ html += `
| ${node.label} | `;
+ } else {
+ html += `
| └ ${p.name||p.script||'LLM'} | `;
+ }
+ html += `${descText} | `;
html += `${p.type||'LLM'} | `;
html += `${p.schedule||'-'} | `;
html += `${p.status} | `;