#!/usr/bin/env python3 """ macro_signal_consumer.py — 消费宏观风险信号,写入风险状态 no_agent 模式:有HIGH风险→输出风险摘要 | 无→静默 管道位置: macro_risk_scanner (8:30/11:30) → signal_news(source=macro_watch) → 本脚本 ↓ macro_risk_state.json — 供所有监控 cron 读取 ↓ 如果 HIGH → 推送到 Dad """ import sqlite3, json, os, sys, time from pathlib import Path from datetime import datetime BASE = Path("/home/hmo/MoFin") DATA = BASE / "data" DB_PATH = DATA / "mofin.db" STATE_PATH = DATA / "macro_risk_state.json" def db_update(conn, sql, params, max_retries=3): """幂等DB更新,遇到锁自动重试""" for attempt in range(max_retries): try: conn.execute(sql, params) conn.commit() return True except sqlite3.OperationalError as e: if "locked" in str(e).lower(): if attempt < max_retries - 1: time.sleep(1) continue raise print(f"[MACRO-CONSUMER] DB更新失败(持续锁): {sql}", file=sys.stderr) return False def main(): conn = sqlite3.connect(str(DB_PATH), timeout=10) conn.execute("PRAGMA busy_timeout=5000") conn.row_factory = sqlite3.Row # 读取未处理的 macro_watch 信号 rows = conn.execute( "SELECT * FROM signal_news WHERE source='macro_watch' AND (processed=0 OR processed IS NULL)" ).fetchall() if not rows: # 无新信号时状态文件维持 15 分钟过期 try: state = json.loads(STATE_PATH.read_text()) created = datetime.strptime(state.get("created_at", "2000-01-01"), "%Y-%m-%d %H:%M:%S") if (datetime.now() - created).total_seconds() > 900: # 15 min state["level"] = "none" state["expired"] = True STATE_PATH.write_text(json.dumps(state, ensure_ascii=False, indent=2)) except: pass conn.close() return # SILENT # 聚合风险等级(考虑修正覆盖信号) levels = {"宏观-WATCH_HIGH": "high", "宏观-WATCH_MEDIUM": "medium", "宏观-WATCH_INFO": "info"} highest = "info" all_summaries = [] def _effective_level(sentiment, summary): """修正覆盖信号取修正后的级别,不按原始sentiment算""" if "修正覆盖" in (summary or ""): s = (summary or "").lower() # 明确说零风险/正面/利好 → info if any(kw in s for kw in ["零风险", "正面利好", "正面进展", "非风险", "利好"]): return "info" # 明确说MEDIUM → medium if "medium" in s or "中风险" in s: return "medium" # 修正覆盖HIGH → 默认降为medium(不保留原始HIGH) return "medium" return levels.get(sentiment, "info") for r in rows: sentiment = r["overall_sentiment"] lv = _effective_level(sentiment, r["summary"]) if lv == "high": highest = "high" elif lv == "medium" and highest != "high": highest = "medium" all_summaries.append({ "sentiment": sentiment, "summary": r["summary"][:300], "key_articles": r["key_articles"], "created_at": r["created_at"], }) # 写入风险状态文件 state = { "level": highest, "signals": all_summaries, "signal_count": len(rows), "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "expired": False, } STATE_PATH.write_text(json.dumps(state, ensure_ascii=False, indent=2)) # 标记为已处理(含重试) for r in rows: db_update(conn, "UPDATE signal_news SET processed=1 WHERE id=?", (r["id"],)) conn.close() # no_agent 输出(有 HIGH 才主动出声) if highest == "high": print(f"[MACRO-RISK] ⚠️ HIGH: {len(rows)}条高风险信号") for s in all_summaries: print(f" {s['summary'][:100]}") elif highest == "medium": if len(os.environ.get("MACRO_VERBOSE", "")) > 0: print(f"[MACRO-RISK] MEDIUM: {len(rows)}条中风险信号") # HIGH 信号会通过 no_agent 推送到 XMPP if __name__ == "__main__": main()