diff --git a/deploy/profile-scripts/market_insight.py b/deploy/profile-scripts/market_insight.py index 2bb438c3..fcb7d8ae 100644 --- a/deploy/profile-scripts/market_insight.py +++ b/deploy/profile-scripts/market_insight.py @@ -139,8 +139,8 @@ def generate(): insights.append("风险板块: " + " | ".join(loser_insights[:3])) # ── 洞察4:资金流向异动 ── - big_inflow = [s for s in sectors if s.get("net_inflow", 0) > 50] - big_outflow = [s for s in sectors if s.get("net_inflow", 0) < -50] + big_inflow = [s for s in sectors if (s.get("net_inflow") or 0) > 50] + big_outflow = [s for s in sectors if (s.get("net_inflow") or 0) < -50] if big_inflow: top = max(big_inflow, key=lambda s: s["net_inflow"]) insights.append( diff --git a/deploy/profile-scripts/mofin_health.py b/deploy/profile-scripts/mofin_health.py index bc9198f8..18dba3a2 100644 --- a/deploy/profile-scripts/mofin_health.py +++ b/deploy/profile-scripts/mofin_health.py @@ -868,22 +868,95 @@ def build_report(): }) # JSON文件 + # 已迁移到DB的旧JSON文件:不再报"无读取方"假警报,真实健康信号看DB表新鲜度 + MIGRATED_TO_DB = { + "multi_tf_cache.json": "mtf_cache", + "macro_context.json": "macro_context_log", + "market.json": "market_snapshots", + "live_prices.json": "live_prices", + "price_history.json": "price_events", + "macro_risk_state.json": "macro_context_log", + } json_entities = [] for jf in sorted(WEB_DATA.glob("*.json")): if jf.name == "stocks": continue if jf.stem.startswith("temp_"): continue readers = flows["json_read"].get(jf.name, []) size = jf.stat().st_size / 1024 + migrated = MIGRATED_TO_DB.get(jf.name) + desc = JSON_DESC.get(jf.name, "") + if migrated: + desc = (desc + " " if desc else "") + f"(已迁移到DB表 {migrated},此为遗留文件)" json_entities.append({ "name": jf.name, - "desc": JSON_DESC.get(jf.name, ""), + "desc": desc, "size_kb": round(size, 1), "readers": readers[:10], "writers": [], # 难以精确追踪 "last_modified": datetime.fromtimestamp(jf.stat().st_mtime).strftime("%m-%d %H:%M"), - "warn": len(readers) == 0 and jf.name not in ("portfolio.json", "market.json"), + "warn": (len(readers) == 0 and jf.name not in ("portfolio.json", "market.json") + and not migrated), + "migrated_to_db": migrated or None, }) + # ── DB表新鲜度:真实数据管道健康信号(替代对遗留JSON文件的mtime检查)── + # 注意:活跃数据在 /home/hmo/MoFin/data/mofin.db(live_prices/mtf_cache 今日有写入), + # 不用 get_conn()(它指向 web-dashboard 的库,那边部分表是旧的) + db_freshness = [] + FRESHNESS_TABLES = [ + ("mtf_cache", "updated_at", "多周期均线缓存"), + ("macro_context_log", "created_at", "宏观上下文"), + ("market_snapshots", "created_at", "市场快照"), + ("live_prices", "updated_at", "实时价格"), + ("price_events", "created_at", "价格事件"), + ] + try: + _fc = sqlite3.connect("/home/hmo/MoFin/data/mofin.db", timeout=10) + for tname, tcol, label in FRESHNESS_TABLES: + try: + row = _fc.execute( + f"SELECT MAX({tcol}) FROM {tname}").fetchone() + if row and row[0]: + last_dt = datetime.fromisoformat(str(row[0]).replace("Z", "")) + age_h = (now - last_dt).total_seconds() / 3600 + db_freshness.append({ + "table": tname, "label": label, + "last_record": last_dt.strftime("%m-%d %H:%M"), + "age_hours": round(age_h, 1), + "warn": age_h > 24, + }) + else: + db_freshness.append({"table": tname, "label": label, + "last_record": None, "age_hours": -1, "warn": True}) + except Exception: + pass # 表不存在或列名不同,跳过 + _fc.close() + except Exception: + pass + + # price_events 特殊处理:活跃存储是 price_events.json(price_monitor 实时写入), + # DB 表是旧遗留。读 JSON 最后一条事件的时间。 + try: + _pe_path = Path("/home/hmo/web-dashboard/data/price_events.json") + if _pe_path.exists(): + _pe = json.loads(_pe_path.read_text(encoding="utf-8")) + _items = _pe if isinstance(_pe, list) else _pe.get("events", []) + if _items: + _last = _items[-1] + _ts = _last.get("timestamp") or _last.get("created_at") or "" + _dt = datetime.fromisoformat(str(_ts).replace("Z", "")) + _age = (now - _dt).total_seconds() / 3600 + # 替换 db_freshness 里 price_events 那条(DB 旧数据) + db_freshness = [f for f in db_freshness if f["table"] != "price_events"] + db_freshness.append({ + "table": "price_events.json", "label": "价格事件", + "last_record": _dt.strftime("%m-%d %H:%M"), + "age_hours": round(_age, 1), + "warn": _age > 24, + }) + except Exception: + pass + # ── Tab 3: 流程/cron映射 ── pipelines = [] for j in sorted(cron_jobs, key=lambda x: x.get("name","")): @@ -912,6 +985,7 @@ def build_report(): "entities": entities, "json_files": json_entities, "pipelines": pipelines, + "db_freshness": db_freshness, } out_path = WEB_DATA / "mofin_health.json" with open(out_path, "w") as f: diff --git a/deploy/profile-scripts/premarket_full_review.py b/deploy/profile-scripts/premarket_full_review.py index 1cebcd1c..abf133df 100644 --- a/deploy/profile-scripts/premarket_full_review.py +++ b/deploy/profile-scripts/premarket_full_review.py @@ -19,6 +19,23 @@ from strategy_lifecycle import regenerate_all result = regenerate_all(stdout=True) print(f"\n重评完成: {result.get('ok',0)}/{result.get('total',0)}成功") +# Step 1.5: 持仓 12 维 LLM 深度分析——后台分离执行(12-40分钟,不能阻塞 cron 的 120s 超时) +print("\n" + "=" * 50) +print("🧠 持仓12维LLM分析(后台分离启动)") +print("=" * 50) +import subprocess as _sp +analysis_result = {"mode": "detached"} +try: + _log = open("/tmp/holdings_12d_daily.log", "a") + _sp.Popen( + ["python3", "/home/hmo/.hermes/profiles/position-analyst/scripts/batch_reassess.py", + "--type", "holding", "--today"], + stdout=_log, stderr=_log, start_new_session=True) + print(" ✅ 12维分析已后台启动,日志: /tmp/holdings_12d_daily.log(结果落DB,不阻塞盘前流程)") +except Exception as e: + print(f" ⚠️ 12维分析启动失败: {e}") + analysis_result = {"mode": "detached", "error": str(e)[:100]} + # Step 2: 自选退出 print("\n" + "=" * 50) print("🔍 自选退出检查") diff --git a/deploy/profile-scripts/price_monitor.py b/deploy/profile-scripts/price_monitor.py index 5c1a3641..5d23c525 100644 --- a/deploy/profile-scripts/price_monitor.py +++ b/deploy/profile-scripts/price_monitor.py @@ -581,7 +581,7 @@ def run_once(round_label=""): # === 第三步:买入区偏离检测 + 自动重评 === reassesed_codes = [] # 先做急跌检测(仅持仓,自选股不推送暴跌告警) - holdings_codes = {d["code"] for d in active if d.get("shares", 0) > 0} + holdings_codes = {d["code"] for d in active if (d.get("shares") or 0) > 0} for d in active: code = d["code"] # 非持仓跳过 diff --git a/deploy/profile-scripts/promote_candidates.py b/deploy/profile-scripts/promote_candidates.py index 83718d80..6eebf8a7 100644 --- a/deploy/profile-scripts/promote_candidates.py +++ b/deploy/profile-scripts/promote_candidates.py @@ -10,7 +10,8 @@ from datetime import datetime DB_PATH = Path("/home/hmo/MoFin/data/mofin.db") def main(): - conn = sqlite3.connect(str(DB_PATH)) + conn = sqlite3.connect(str(DB_PATH), timeout=30) + conn.execute("PRAGMA busy_timeout=30000") conn.row_factory = sqlite3.Row # 读未提拔候选(按评分降序) diff --git a/deploy/profile-scripts/sync_profile_scripts.sh b/deploy/profile-scripts/sync_profile_scripts.sh old mode 100755 new mode 100644 diff --git a/scripts/check_default_pool.py b/scripts/check_default_pool.py new file mode 100644 index 00000000..354f7ccd --- /dev/null +++ b/scripts/check_default_pool.py @@ -0,0 +1,9 @@ +import json +d = json.load(open('/home/hmo/.hermes/auth.json')) +pool = d.get('credential_pool', {}) +for name, entries in pool.items(): + if 'custom' in name or 'ocg' in name.lower(): + for e in entries: + print(f"{name}: label={e.get('label')} priority={e.get('priority')} " + f"last_status={e.get('last_status')} err_code={e.get('last_error_code')} " + f"req_count={e.get('request_count')}") \ No newline at end of file diff --git a/scripts/check_llm_jobs.py b/scripts/check_llm_jobs.py new file mode 100644 index 00000000..8be4514d --- /dev/null +++ b/scripts/check_llm_jobs.py @@ -0,0 +1,11 @@ +import json +d = json.load(open('/home/hmo/.hermes/cron/jobs.json')) +jobs = d if isinstance(d, list) else d.get('jobs', []) +for j in jobs: + if j.get('name') in ('wiki-self-growth', 'evolution-pulse', '大脑任务执行', '知识研究-日常', '梦境循环-知识库归并'): + print(j['name']) + print(' provider:', j.get('provider'), '| model:', j.get('model'), '| base_url:', j.get('base_url')) + print(' no_agent:', j.get('no_agent'), '| script:', j.get('script')) + print(' timeout:', j.get('timeout'), '| max_turns:', j.get('max_turns')) + print(' prompt head:', str(j.get('prompt'))[:120]) + print() \ No newline at end of file diff --git a/scripts/check_price_events.py b/scripts/check_price_events.py new file mode 100644 index 00000000..46486058 --- /dev/null +++ b/scripts/check_price_events.py @@ -0,0 +1,19 @@ +import sqlite3, json +# price_events in web-dashboard db +c = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db') +try: + r = c.execute("SELECT MAX(created_at), COUNT(*) FROM price_events").fetchone() + print('web-dashboard mofin.db price_events:', r) +except Exception as e: + print('web-dashboard db err:', e) +c.close() + +# price_events.json tail +try: + d = json.load(open('/home/hmo/web-dashboard/data/price_events.json')) + print('price_events.json type:', type(d).__name__, 'len:', len(d) if hasattr(d, '__len__') else '?') + items = d if isinstance(d, list) else d.get('events', []) + if items: + print('last event:', json.dumps(items[-1], ensure_ascii=False)[:250]) +except Exception as e: + print('json err:', e) \ No newline at end of file diff --git a/scripts/check_table_schemas.py b/scripts/check_table_schemas.py new file mode 100644 index 00000000..2a2483a2 --- /dev/null +++ b/scripts/check_table_schemas.py @@ -0,0 +1,9 @@ +import sqlite3 +conn = sqlite3.connect('/home/hmo/MoFin/data/mofin.db') +for t in ('mtf_cache', 'macro_context_log', 'live_prices', 'price_events', 'market_snapshots'): + try: + cols = [c[1] for c in conn.execute(f"PRAGMA table_info({t})")] + cnt = conn.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0] + print(f"{t}: rows={cnt} cols={cols}") + except Exception as e: + print(f"{t}: ERROR {e}") \ No newline at end of file diff --git a/scripts/full_error_inventory.py b/scripts/full_error_inventory.py new file mode 100644 index 00000000..4e9b25c8 --- /dev/null +++ b/scripts/full_error_inventory.py @@ -0,0 +1,29 @@ +import json, os +from datetime import datetime, timezone + +now = datetime.now(timezone.utc) +out = [] +for jf, prof in [('/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json', 'pa'), + ('/home/hmo/.hermes/cron/jobs.json', 'default')]: + d = json.load(open(jf)) + jobs = d if isinstance(d, list) else d.get('jobs', []) + for j in jobs: + st = j.get('last_status') + en = j.get('enabled', True) + if st == 'error' or not en: + lr = str(j.get('last_run_at') or '?')[:19] + err = str(j.get('last_error') or '')[:300].replace('\n', ' | ') + out.append({ + 'profile': prof, 'name': j.get('name'), 'script': j.get('script'), + 'no_agent': j.get('no_agent'), 'status': st, 'enabled': en, + 'last_run': lr, 'schedule': j.get('schedule_display') or str(j.get('schedule')), + 'error': err, + }) + +print(f"TOTAL problem jobs: {len(out)}\n") +for o in out: + flag = 'DISABLED' if not o['enabled'] else 'ERROR' + print(f"[{flag}] ({o['profile']}) {o['name']}") + print(f" script={o['script']} no_agent={o['no_agent']} sched={o['schedule']} last={o['last_run']}") + print(f" err: {o['error'][:250]}") + print() \ No newline at end of file diff --git a/scripts/get_today_errors.py b/scripts/get_today_errors.py new file mode 100644 index 00000000..a784360a --- /dev/null +++ b/scripts/get_today_errors.py @@ -0,0 +1,20 @@ +import json + +d = json.load(open('/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json')) +jobs = d if isinstance(d, list) else d.get('jobs', []) +targets = ['价格监控-高频', '知微洞察生成', '候选股自动提拔-每30分', '健康监控数据采集-每15分', '盘前全量重评-自选退出'] +for j in jobs: + if j.get('name') in targets: + print('='*70) + print(j['name'], '| last:', str(j.get('last_run_at'))[:19]) + print(str(j.get('last_error'))[:1200]) + print() + +d2 = json.load(open('/home/hmo/.hermes/cron/jobs.json')) +jobs2 = d2 if isinstance(d2, list) else d2.get('jobs', []) +for j in jobs2: + if j.get('name') in ('市场数据采集', '记忆守卫-每日', 'wiki-self-growth', 'evolution-pulse', '大脑任务执行'): + print('='*70) + print('[default]', j['name'], '| last:', str(j.get('last_run_at'))[:19]) + print(str(j.get('last_error'))[:800]) + print() \ No newline at end of file diff --git a/scripts/inv5.py b/scripts/inv5.py new file mode 100644 index 00000000..125c6793 --- /dev/null +++ b/scripts/inv5.py @@ -0,0 +1,60 @@ +import json, os, subprocess +from datetime import datetime + +now = datetime.now() + +print("=== 1. DATA FILES ===") +for f in ['multi_tf_cache.json', 'macro_context.json']: + p = f'/home/hmo/MoFin/data/{f}' + if os.path.exists(p): + mtime = datetime.fromtimestamp(os.path.getmtime(p)) + h = (now - mtime).total_seconds() / 3600 + print(f'{f}: {mtime} ({h:.0f}h ago)') + else: + print(f'{f}: MISSING') + +pm = '/home/hmo/web-dashboard/data/market.json' +print(f'market.json: {"EXISTS" if os.path.exists(pm) else "MISSING"}') + +print() +print("=== 2. CRON MISSING SCRIPTS in profile scripts dir ===") +d = json.load(open('/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json')) +jobs = d if isinstance(d, list) else d.get('jobs', []) +sdir = '/home/hmo/.hermes/profiles/position-analyst/scripts' +exist = set(os.listdir(sdir)) +for j in jobs: + s = j.get('script', '') + if s and s not in exist and j.get('no_agent'): + f = subprocess.run(['find', '/home/hmo/MoFin', '-name', s, '-not', '-path', '*/venv/*'], + capture_output=True, text=True, timeout=15) + locs = [l for l in f.stdout.splitlines() if l.strip()] + print(f"MISSING: {s} (job: {j.get('name')}) -> {locs[:2] if locs else 'NOT FOUND'}") + +print() +print("=== 3. suggestions table ===") +r = subprocess.run(['grep', '-rn', 'suggestions', '/home/hmo/MoFin/deploy/profile-scripts/', '--include=*.py'], + capture_output=True, text=True, timeout=10) +for l in r.stdout.splitlines(): + if 'suggestions' in l.lower(): + print(l.strip()) +# Check if any cron references it +r2 = subprocess.run(['python3', '-c', ''' +import json +d = json.load(open("/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json")) +jobs = d if isinstance(d, list) else d.get("jobs", []) +for j in jobs: + if "suggest" in str(j): + print(j.get("name",""), j.get("script",""), j.get("prompt","")[:100]) +'''], capture_output=True, text=True, timeout=5) +print(r2.stdout) + +print() +print("=== 4. price_monitor newspaper ===") +r = subprocess.run(['grep', '-n', 'newspaper', '/home/hmo/MoFin/deploy/profile-scripts/price_monitor.py'], capture_output=True, text=True, timeout=5) +print(r.stdout[-500:] if len(r.stdout) > 500 else r.stdout) + +print() +print("=== 5. Which crons generate multi_tf_cache / macro_context ===") +for j in jobs: + if j.get('name') and ('多周期' in j.get('name','') or '宏观' in j.get('name','') or 'multi' in j.get('name','').lower() or 'market' in j.get('name','').lower()): + print(f"{j.get('name')}: script={j.get('script','?')} status={j.get('last_status','?')} last_run={j.get('last_run_at','?')} err={str(j.get('last_error',''))[:80]}") \ No newline at end of file diff --git a/scripts/investigate_5_issues.py b/scripts/investigate_5_issues.py new file mode 100644 index 00000000..83f3887f --- /dev/null +++ b/scripts/investigate_5_issues.py @@ -0,0 +1,47 @@ +import json, os + +# 1. Check data pipeline stagnation +print("=== DATA FILE AGES ===") +for f in ['multi_tf_cache.json', 'macro_context.json', 'market.json']: + p = f'/home/hmo/MoFin/data/{f}' + if os.path.exists(p): + sec = (os.path.getmtime(p)) + from datetime import datetime + print(f' {f}: {datetime.fromtimestamp(sec)} ({int((os.path.getmtime(p))/3600)}h ago)') + else: + print(f' {f}: MISSING') + +# 2. Cron scripts missing from profile scripts dir +print() +print("=== MISSING CRON SCRIPTS ===") +d = json.load(open('/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json')) +jobs = d if isinstance(d, list) else d.get('jobs', []) +scripts_dir = '/home/hmo/.hermes/profiles/position-analyst/scripts' +existing = set(os.listdir(scripts_dir)) +missing = [] +for j in jobs: + s = j.get('script', '') + if s and s not in existing and j.get('no_agent'): + missing.append((j.get('name'), s)) + print(f' MISSING: {s} (job: {j.get(\"name\",\"?\")})') +if not missing: + print(' (none missing)') + +# 3. Find source locations for missing scripts +print() +print("=== FIND MISSING SCRIPTS IN MoFin ===") +for name, script in missing: + import subprocess + r = subprocess.run(['find', '/home/hmo/MoFin', '-name', script, '-not', '-path', '*/venv/*'], + capture_output=True, text=True, timeout=15) + found = [l for l in r.stdout.splitlines() if l.strip()] + print(f' {script}: {found if found else "NOT FOUND"}') + +# 4. market.json generator +print() +print("=== market.json GENERATOR ===") +r = subprocess.run(['grep', '-rn', "'market.json'", '/home/hmo/MoFin/deploy/profile-scripts/', '/home/hmo/MoFin/scripts/'], + capture_output=True, text=True, timeout=10) +for l in r.stdout.splitlines(): + if 'market.json' in l: + print(f' {l}') \ No newline at end of file diff --git a/scripts/kanban_create.py b/scripts/kanban_create.py new file mode 100644 index 00000000..28498540 --- /dev/null +++ b/scripts/kanban_create.py @@ -0,0 +1,39 @@ +import sqlite3, uuid, time + +db = sqlite3.connect('/home/hmo/.hermes/kanban.db') +print("=== statuses ===") +for r in db.execute("SELECT status, COUNT(*) FROM tasks GROUP BY status"): + print(r) +print() +# a pending example if any +r = db.execute("SELECT id, title, status, assignee FROM tasks WHERE status NOT IN ('done','cancelled') LIMIT 5").fetchall() +for x in r: + print(x) + +# create the card for zhiwei +tid = 't_' + uuid.uuid4().hex[:8] +now = int(time.time()) +title = "12维分析管道已系统化 — 每日自动刷新,请知悉并验证" +body = """笑笑(Sisyphus)完成系统级修复,知微不用再做任何手动补评。 + +背景:老爸发现盘前全量重评只更新了技术参数,12维LLM深度分析(full_analysis)为空/陈旧。 + +已落地的系统改动(已全部部署到246并提交git): +1. premarket_full_review.py 新增 Step 1.5:每交易日08:10自动跑 batch_reassess.py --type holding --today,14只持仓12维分析每日强制刷新 +2. batch_reassess.py 升级:--type holding|watchlist|all 全覆盖;分析超20h视为过期自动重评;现金/总资产改为从 portfolio_summary 实时读取(不再硬编码);港股前缀修复(00700等5位代码) +3. 新增每日12:30 cron「自选12维分析补全-每日午间」(watchlist_12d_backfill.py),109只缺分析的自选股每日补全 +4. 验证:300308 已生成1920字12维分析(信号=观望)并写入DB;当前正在后台跑14只持仓的全量补评(/tmp/holdings_12d_backfill.log) + +需要知微做的: +- 验证今天开盘简报/盯盘里能正常引用最新12维分析 +- 观察今日12:30自选补全任务是否正常触发 +- 有问题在kanban回复或XMPP找笑笑""" +db.execute( + "INSERT INTO tasks (id, title, body, assignee, status, priority, created_by, created_at) VALUES (?,?,?,?,?,?,?,?)", + (tid, title, body, 'zhiwei', 'pending', 1, 'xxm', now)) +db.commit() +print() +print('created:', tid) +for r in db.execute("SELECT id, title, status, assignee, created_by FROM tasks WHERE id=?", (tid,)): + print(r) +db.close() \ No newline at end of file diff --git a/scripts/kanban_fix_status.py b/scripts/kanban_fix_status.py new file mode 100644 index 00000000..7c30a8d8 --- /dev/null +++ b/scripts/kanban_fix_status.py @@ -0,0 +1,7 @@ +import sqlite3 +db = sqlite3.connect('/home/hmo/.hermes/kanban.db') +db.execute("UPDATE tasks SET status='ready' WHERE id='t_2ff55641'") +db.commit() +for r in db.execute("SELECT id, status, assignee FROM tasks WHERE id='t_2ff55641'"): + print(r) +db.close() \ No newline at end of file diff --git a/scripts/kanban_inspect.py b/scripts/kanban_inspect.py new file mode 100644 index 00000000..a93c0783 --- /dev/null +++ b/scripts/kanban_inspect.py @@ -0,0 +1,13 @@ +import sqlite3, uuid +from datetime import datetime + +db_path = '/home/hmo/.hermes/kanban.db' +db = sqlite3.connect(db_path) +# inspect schema +schema = db.execute("SELECT sql FROM sqlite_master WHERE name='tasks'").fetchone() +print(schema[0] if schema else 'no tasks table') +print() +# recent rows to see id format and fields +for r in db.execute("SELECT id, title, status, assignee, created_by, created_at FROM tasks ORDER BY created_at DESC LIMIT 5"): + print(r) +db.close() \ No newline at end of file diff --git a/scripts/notify_user.py b/scripts/notify_user.py new file mode 100644 index 00000000..5736100f --- /dev/null +++ b/scripts/notify_user.py @@ -0,0 +1,24 @@ +import json, urllib.request + +msg = """[笑笑] 12维分析管道已系统化完成 ✅ + +你早上指出的缺口(盘前重评只有技术参数、12维LLM分析为空/陈旧)已从系统层面修复,不是手动缝补: + +1️⃣ 盘前管道改造:每交易日 08:10 自动跑持仓12维分析(batch_reassess --type holding --today,每日强制刷新14只) +2️⃣ batch_reassess 升级:持仓/自选全覆盖、分析超20h自动重评、现金/总资产改从 portfolio_summary 实时读(之前硬编码的是几周前的旧值)、港股前缀修复 +3️⃣ 自选补全:新增每日12:30 cron「自选12维分析补全」,109只缺分析的自选股每日自动补 +4️⃣ 验证:300308 已生成1920字12维分析(观望)写入DB;此刻后台正在跑14只持仓全量补评 + +代码已提交并同步到246 repo(merge 4dcfee81)。 +已通过 kanban 通知知微(t_2ff55641),让她验证今日开盘简报和12:30自选任务。 + +另外凌晨的自愈巡检还修了:promote候选崩溃、DB锁、符号链接阻断、candidate_filter等问题,今天开盘应该全部正常。""" + +payload = json.dumps({"to": "hmo@yoin.fun", "body": msg, "type": "chat"}).encode() +req = urllib.request.Request("http://127.0.0.1:5805/", data=payload, + headers={"Content-Type": "application/json"}) +try: + resp = urllib.request.urlopen(req, timeout=10) + print("XMPP sent:", resp.read().decode()[:100]) +except Exception as e: + print("XMPP fail:", e) \ No newline at end of file diff --git a/scripts/test_freshness_query.py b/scripts/test_freshness_query.py new file mode 100644 index 00000000..f7207eff --- /dev/null +++ b/scripts/test_freshness_query.py @@ -0,0 +1,16 @@ +import sqlite3 +from datetime import datetime +conn = sqlite3.connect('/home/hmo/MoFin/data/mofin.db') +for t, c in [('mtf_cache','updated_at'),('macro_context_log','created_at'), + ('live_prices','updated_at'),('price_events','created_at'),('market_snapshots','created_at')]: + try: + row = conn.execute(f"SELECT MAX({c}) FROM {t}").fetchone() + print(f"{t}.{c} MAX = {row[0]!r}") + if row and row[0]: + try: + dt = datetime.fromisoformat(str(row[0]).replace('Z','')) + print(f" parsed OK: {dt}") + except Exception as pe: + print(f" PARSE FAIL: {pe}") + except Exception as e: + print(f"{t}: QUERY ERROR {e}") \ No newline at end of file diff --git a/scripts/verify_health_json.py b/scripts/verify_health_json.py new file mode 100644 index 00000000..04df3b8e --- /dev/null +++ b/scripts/verify_health_json.py @@ -0,0 +1,11 @@ +import json +d = json.load(open('/home/hmo/web-dashboard/static/mofin_health.json')) +print('generated:', d['generated_at']) +print('db_freshness:') +for f in d.get('db_freshness', []): + print(f" {f['label']}({f['table']}): last={f['last_record']} age={f['age_hours']}h warn={f['warn']}") +print() +warned = [j['name'] for j in d['json_files'] if j.get('warn')] +print(f'json_files warned: {len(warned)}') +migrated = [j['name'] for j in d['json_files'] if j.get('migrated_to_db')] +print(f'migrated (no longer warned): {migrated}') \ No newline at end of file