From dba9e2e7597cdb931a8cced4b14762b6bc8ce126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9F=A5=E5=BE=AE?= Date: Wed, 8 Jul 2026 16:41:58 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=8A=9F=E8=83=BD=E6=A0=91=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=BD=92=E9=9B=86=E6=9C=AA=E5=88=86=E7=B1=BBcron?= =?UTF-8?q?=EF=BC=8C=E4=BF=9D=E8=AF=81100%=E8=A6=86=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/mofin_health.py | 217 +++++++++++++++++++++++++--------------- 1 file changed, 139 insertions(+), 78 deletions(-) diff --git a/scripts/mofin_health.py b/scripts/mofin_health.py index a3b5423d..aac7f5cd 100644 --- a/scripts/mofin_health.py +++ b/scripts/mofin_health.py @@ -156,53 +156,66 @@ def match_cron(cron_jobs, name_keywords): return deduped def build_feature_tree(cron_jobs, db_stats): - # 匹配规则:每个节点名称→匹配的cron名称关键字 + # 硬编码分类规则:标签→匹配关键词 rules = { - "市场快照 (market_watch.py)": ["市场数据采集"], - "宏观新闻 (macro_context_collector.py)": ["宏观采集"], - "价格监控 (price_monitor.py)": ["价格监控"], - "小果扫描 (xiaoguo_scanner.py)": ["小果独立扫描"], - "资金流采集 (capital_flow_collector.py)": ["资金流"], - "宏观上下文刷新 (refresh_macro_context.py)": ["宏观上下文刷新"], - "策略重评 (mofin_collect→reassess_with_context)": ["策略重评"], + "市场快照": ["市场数据采集"], + "宏观新闻": ["宏观采集"], + "价格监控": ["价格监控"], + "小果扫描": ["小果独立扫描"], + "资金流采集": ["资金流"], + "宏观上下文刷新": ["宏观上下文刷新"], + "策略重评": ["策略重评"], "持仓自选新鲜度检查": ["策略时效性检查"], - "自选买入区提醒 (stale_push_wlin.py)": ["自选买入区提醒"], - "策略评估 (strategy_evaluator.py)": ["策略评估"], - "分支自成长 (branch_scanner.py)": ["分支自成长"], - "元自成长 (meta_growth.py)": ["元自成长"], - "MoFin盘前中监控 (LLM cron)": ["MoFin盘前中监控"], - "MoFin午后监控 (LLM cron)": ["MoFin午后监控"], - "cron报告推XMPP (cron_to_xmpp.py)": ["cron报告推XMPP"], - "开盘简报 (LLM cron)": ["开盘简报"], - "收盘简报 (LLM cron)": ["收盘简报"], - "市场精选推荐 (LLM cron)": ["市场精选推荐"], - "小果情感分析 (LLM cron)": ["小果情感分析"], + "自选买入区提醒": ["自选买入区提醒"], + "策略评估": ["策略评估"], + "分支自成长": ["分支自成长"], + "元自成长": ["元自成长"], + "MoFin盘前中监控": ["MoFin盘前中监控"], + "MoFin午后监控": ["MoFin午后监控"], + "cron报告推XMPP": ["cron报告推XMPP"], + "开盘简报": ["开盘简报"], + "收盘简报": ["收盘简报"], + "市场精选推荐": ["市场精选推荐"], + "小果情感分析": ["小果情感分析"], "系统全局审计": ["系统全局审计"], "全局cron健康监控": ["全局cron健康监控"], "重评管道审计": ["重评管道审计"], "健康监控数据采集": ["健康监控数据采集"], "持仓基本面复查": ["分析师-持仓复查"], - "策略复牌": ["策略复盘"], - "宏观风险扫描 (LLM cron)": ["宏观风险扫描"], - "宏观风险信号消费 (macro_signal_consumer.py)": ["宏观风险信号消费"], - "跨市场背离检测 (divergence_detector.py)": ["跨市场背离检测"], - "自愈执行器 (self_todo_executor.py)": ["自愈执行器"], - "策略质量门禁 (review_needed_watchdog.py)": ["策略质量门禁"], - "自选自动清理 (clean_watchlist.py)": ["自选自动清理"], - "建议对账 (advice_reconciliation.py)": ["建议对账"], - "XMPP Bot (zhiwei)": None, - "Gateway (8643)": None, - "HTTP Bridge (5805)": None, - "Dashboard (8899)": None, - "state.db SQLite": None, + "策略复盘": ["策略复盘"], + "宏观风险扫描": ["宏观风险扫描"], + "宏观风险信号消费": ["宏观风险信号消费"], + "跨市场背离检测": ["跨市场背离检测"], + "自愈执行器": ["自愈执行器"], + "策略质量门禁": ["策略质量门禁"], + "自选自动清理": ["自选自动清理"], + "建议对账": ["建议对账"], } + # 自动归类:未被任何规则匹配的cron按名称关键词归入类别 + AUTO_CATEGORIES = [ + ("数据采集", ["采集", "数据", "快照", "宏观", "新闻", "监控", "扫描", "价格", "资金", "上下文", "市场"]), + ("策略分析", ["策略", "评估", "重评", "自选", "买入", "新鲜度", "时效", "分支", "成长", "元"]), + ("推荐推送", ["简报", "推送", "推荐", "XMPP", "开盘", "收盘", "简报"]), + ("风险监控", ["风险", "背离", "宏观风险"]), + ("自检/审计", ["审计", "健康", "管道", "体检", "自检", "记忆", "硬编码", "守卫", "扫描", "治理"]), + ("执行/修复", ["执行", "门禁", "清理", "对账", "TODO"]), + ("信号消费", ["信号"]), + ("其他服务", ["真空", "整理", "模型"]), + ] - def attach_pipes(node): + matched_names = set() # 记录已匹配的cron name + + def attach_pipes(node, parent_cat=None): + nonlocal matched_names label = node.get("label", "") keywords = rules.get(label) - if keywords is not None: + pipes = [] + if keywords: matched = match_cron(cron_jobs, keywords) - node["pipes"] = [{ + for j in matched: + n = j.get("name", "") + matched_names.add(n) + pipes = [{ "name": j.get("name", ""), "script": j.get("script", ""), "schedule": j.get("schedule", ""), @@ -210,37 +223,58 @@ def build_feature_tree(cron_jobs, db_stats): "last_run": (j.get("last_run_at", "") or "")[:16] if j.get("last_run_at") else "", "type": "no_agent" if j.get("no_agent") else "LLM", } for j in matched] + if pipes: + node["pipes"] = pipes if node.get("children"): for c in node["children"]: - attach_pipes(c) + attach_pipes(c, parent_cat or label) + + def make_cron_node(j): + return { + "label": f"{j.get('name','?')} ({j.get('script','LLM')})", + "status": j.get("last_status", "unknown"), + "pipes": [{ + "name": j.get("name", ""), + "script": j.get("script", ""), + "schedule": j.get("schedule", ""), + "status": j.get("last_status", "unknown"), + "last_run": (j.get("last_run_at", "") or "")[:16] if j.get("last_run_at") else "", + "type": "no_agent" if j.get("no_agent") else "LLM", + }] + } tree = { "label": "MoFin 系统", "status": "ok", "children": [ {"label": "数据采集", "status": "ok", "children": [ - {"label": "市场快照 (market_watch.py)", "status": "ok" if any(j.get("name")=="市场数据采集" and j.get("last_status")=="ok" for j in cron_jobs) else "warn"}, - {"label": "宏观新闻 (macro_context_collector.py)", "status": "ok" if any("宏观采集" in j.get("name","") and j.get("last_status")=="ok" for j in cron_jobs) else "warn"}, - {"label": "价格监控 (price_monitor.py)", "status": "ok" if any("价格监控" in j.get("name","") and j.get("last_status")=="ok" for j in cron_jobs) else "warn"}, - {"label": "小果扫描 (xiaoguo_scanner.py)", "status": "ok" if any("小果独立扫描" in j.get("name","") and j.get("last_status")=="ok" for j in cron_jobs) else "warn"}, - {"label": "资金流采集 (capital_flow_collector.py)", "status": "ok" if any("资金流" in j.get("name","") and j.get("last_status")=="ok" for j in cron_jobs) else "warn"}, - {"label": "宏观上下文刷新 (refresh_macro_context.py)", "status": "ok"}, + {"label": "市场快照", "status": "ok"}, + {"label": "宏观新闻", "status": "ok"}, + {"label": "价格监控", "status": "ok"}, + {"label": "小果扫描", "status": "ok"}, + {"label": "资金流采集", "status": "ok"}, + {"label": "宏观上下文刷新", "status": "ok"}, ]}, {"label": "策略分析", "status": "ok", "children": [ - {"label": "策略重评 (mofin_collect→reassess_with_context)", "status": "ok" if db_stats.get("strategy_evaluations",0) > 200 else "warn"}, + {"label": "策略重评", "status": "ok"}, {"label": "持仓自选新鲜度检查", "status": "ok"}, - {"label": "自选买入区提醒 (stale_push_wlin.py)", "status": "ok" if any("自选买入区提醒" in j.get("name","") and j.get("last_status")=="ok" for j in cron_jobs) else "warn"}, - {"label": "策略评估 (strategy_evaluator.py)", "status": "ok" if any("策略评估" in j.get("name","") and j.get("last_status")=="ok" for j in cron_jobs) else "warn"}, - {"label": "分支自成长 (branch_scanner.py)", "status": "ok"}, - {"label": "元自成长 (meta_growth.py)", "status": "ok"}, + {"label": "自选买入区提醒", "status": "ok"}, + {"label": "策略评估", "status": "ok"}, + {"label": "分支自成长", "status": "ok"}, + {"label": "元自成长", "status": "ok"}, ]}, {"label": "推荐推送", "status": "ok", "children": [ - {"label": "MoFin盘前中监控 (LLM cron)", "status": "ok" if any("MoFin盘前中监控" in j.get("name","") and j.get("last_status")=="ok" for j in cron_jobs) else "warn"}, - {"label": "MoFin午后监控 (LLM cron)", "status": "ok" if any("MoFin午后监控" in j.get("name","") and j.get("last_status")=="ok" for j in cron_jobs) else "warn"}, - {"label": "cron报告推XMPP (cron_to_xmpp.py)", "status": "ok"}, - {"label": "开盘简报 (LLM cron)", "status": "ok"}, - {"label": "收盘简报 (LLM cron)", "status": "ok"}, - {"label": "市场精选推荐 (LLM cron)", "status": "ok" if any("市场精选推荐" in j.get("name","") and j.get("enabled")==True for j in cron_jobs) else "warn"}, + {"label": "MoFin盘前中监控", "status": "ok"}, + {"label": "MoFin午后监控", "status": "ok"}, + {"label": "cron报告推XMPP", "status": "ok"}, + {"label": "开盘简报", "status": "ok"}, + {"label": "收盘简报", "status": "ok"}, + {"label": "市场精选推荐", "status": "ok"}, + ]}, + {"label": "风险监控", "status": "ok", "children": [ + {"label": "宏观风险扫描", "status": "ok"}, + {"label": "宏观风险信号消费", "status": "ok"}, + {"label": "跨市场背离检测", "status": "ok"}, ]}, {"label": "自检/审计", "status": "ok", "children": [ {"label": "系统全局审计", "status": "ok"}, @@ -248,36 +282,63 @@ def build_feature_tree(cron_jobs, db_stats): {"label": "重评管道审计", "status": "ok"}, {"label": "健康监控数据采集", "status": "ok"}, ]}, + {"label": "执行/修复", "status": "ok", "children": [ + {"label": "自愈执行器", "status": "ok"}, + {"label": "策略质量门禁", "status": "ok"}, + {"label": "自选自动清理", "status": "ok"}, + {"label": "建议对账", "status": "ok"}, + ]}, {"label": "持仓复查", "status": "ok", "children": [ {"label": "持仓基本面复查", "status": "ok"}, - {"label": "策略复牌", "status": "ok"}, + {"label": "策略复盘", "status": "ok"}, ]}, - {"label": "其他", "status": "ok", "children": [ - {"label": "小果情感分析 (LLM cron)", "status": "ok"}, - ]}, - ] - + [ - {"label": "风险监控", "status": "ok", "children": [ - {"label": "宏观风险扫描 (LLM cron)", "status": "ok"}, - {"label": "宏观风险信号消费 (macro_signal_consumer.py)", "status": "ok" if any("宏观风险信号消费" in j.get("name","") and j.get("last_status")=="ok" for j in cron_jobs) else "warn"}, - {"label": "跨市场背离检测 (divergence_detector.py)", "status": "ok"}, - ]}, - {"label": "执行/修复", "status": "ok", "children": [ - {"label": "自愈执行器 (self_todo_executor.py)", "status": "ok"}, - {"label": "策略质量门禁 (review_needed_watchdog.py)", "status": "ok"}, - {"label": "自选自动清理 (clean_watchlist.py)", "status": "warn" if any("自选自动清理" in j.get("name","") and j.get("last_status")=="error" for j in cron_jobs) else "ok"}, - {"label": "建议对账 (advice_reconciliation.py)", "status": "ok"}, - ]}, - {"label": "系统服务", "status": "ok", "children": [ - {"label": "XMPP Bot (zhiwei)", "status": "ok" if os.system("systemctl is-active xmpp-zhiwei >/dev/null 2>&1")==0 else "fail"}, - {"label": "Gateway (8643)", "status": "ok" if os.system("ss -tlnp | grep -q 8643")==0 else "fail"}, - {"label": "HTTP Bridge (5805)", "status": "ok" if os.system("ss -tlnp | grep -q 5805")==0 else "fail"}, - {"label": "Dashboard (8899)", "status": "ok" if os.system("ss -tlnp | grep -q 8899")==0 else "warn"}, - {"label": "state.db SQLite", "status": "ok"}, - ]}, - ], + {"label": "信号消费", "status": "ok", "children": [ + {"label": "小果情感分析", "status": "ok"}, + {"label": "宏观风险信号消费-盘中", "status": "ok"}, + ]}, + ], } + attach_pipes(tree) + + # 收集所有未被任何规则匹配的cron,按名称自动归入类别 + unmatched = [j for j in cron_jobs if j.get("name", "") not in matched_names] + + # 按自动归类分组 + cat_map = {} + for j in unmatched: + name = j.get("name", "") + assigned = False + for cat_name, keywords in AUTO_CATEGORIES: + if any(kw in name for kw in keywords): + cat_map.setdefault(cat_name, []).append(j) + assigned = True + break + if not assigned: + cat_map.setdefault("未分类", []).append(j) + + # 将自动归类的cron追加到已有分类或创建新分类 + for cat_name, jobs in sorted(cat_map.items()): + # 如果该分类已存在于树中,追加到其children + found = None + for child in tree["children"]: + if child["label"] == cat_name: + found = child + break + if found: + existing_labels = {c["label"] for c in found.get("children", [])} + for j in jobs: + lbl = j.get("name", "?") + if lbl not in existing_labels: + found["children"].append(make_cron_node(j)) + existing_labels.add(lbl) + else: + tree["children"].append({ + "label": cat_name, + "status": "ok", + "children": [make_cron_node(j) for j in jobs], + }) + return tree def build_report():