fix(pipelines): clear today's real cron errors + kill monitoring false alarms
Real errors fixed (all verified by manual run): - price_monitor.py: shares None -> TypeError at L584 (now completes 3m7s, full 39-stock reassess + zone triggers + Dad push) - market_insight.py: net_inflow None -> TypeError at L142 (now 0.3s, 5 insights) - promote_candidates.py: add busy_timeout=30s (DB lock under concurrent writes) - premarket_full_review.py: 12-dim analysis now detached background launch (was doomed by cron 120s script timeout no matter what) Systemic: - HERMES_CRON_SCRIPT_TIMEOUT=600 drop-in for both gateway services (fixes mofin_health SIGTERM, market_watch timeout, memory_guardian timeout) - sync_profile_scripts.sh: re-hardlink deploy->profile scripts after every deploy (scp replaces files = new inode = broken hardlink = cron silently runs stale code; this caused promote to keep failing after my first fix) Monitoring false-alarm fixes (the '花瓶' problem): - mofin_health.py: legacy JSONs that migrated to DB (multi_tf_cache/ macro_context/market/live_prices/price_history/macro_risk_state) no longer warn 'no readers'; marked as migrated - NEW db_freshness section: real pipeline health from DB tables (mtf_cache 0.4h / macro_context_log 2h / market_snapshots 2h / live_prices 0.4h / price_events.json 0.4h — ALL HEALTHY) - price_events freshness reads live JSON store (DB table is legacy) - market.json placeholder created (13+ scripts have fallback paths) Investigation notes: wiki-self-growth 03:04 key1 429 predates full key6 activation on default gateway; current 8642 verified on key6 and working. Weekend 'Blocked' jobs verified fixed (vacuum_state_db passes).
This commit is contained in:
@@ -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')}")
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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}")
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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]}")
|
||||
@@ -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}')
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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}")
|
||||
@@ -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}')
|
||||
Reference in New Issue
Block a user