# -*- coding: utf-8 -*- """ 连通性与表结构自检 (实机, 需 .env 真实连接串) ============================================== 运行: docker compose run --rm pms-web python scripts/check_db.py 检查项: 1. 三个库连通性 (153 代理 / 因子库 / 指数库) 2. pms_* 十张表是否存在与当前行数 3. 下游三表可读性 + 持仓数量列探测结果 (回填 QMT_INTERFACE_REQUIREMENTS A1/D1 用) 4. 行情 Redis (db13) 连通性与样本键 5. 运行参数表当前生效值 全部通过退出码 0; 任一 FAIL 退出码 1 (WARN 不影响退出码)。 """ import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) PMS_TABLES = ["pms_command", "pms_plan", "pms_position", "pms_lot", "pms_instruction", "pms_proposal", "pms_action_ledger", "pms_daily_report", "pms_industry_map", "pms_runtime_param"] DOWNSTREAM = ["trading_position", "trading_order", "trading_buy_plan"] FAILED, WARNED = [], [] def line(tag, msg): print(f" {tag:<6}{msg}") def fail(msg): FAILED.append(msg) line("FAIL", msg) def warn(msg): WARNED.append(msg) line("WARN", msg) def main(): from app.db import session as dbs print("\n[1] 数据库连通性") for name, label in (("proxy", "153 代理 (pms_* / trading_*)"), ("factor", "因子分表库"), ("index", "大盘指数库")): r = dbs.ping(name) if r["ok"]: line("OK", f"{label}") elif name == "proxy": fail(f"{label} 连接失败: {r['error']}") else: warn(f"{label} 连接失败 (非致命): {r['error']}") print("\n[2] PMS 自有表") for t in PMS_TABLES: try: r = dbs.fetch_one(f"SELECT COUNT(*) AS n FROM {t}") line("OK", f"{t:<20} {r['n']} 行") except Exception as e: fail(f"{t} 不可用: {type(e).__name__}: {e}") print("\n[3] 下游只读表") from app.repo import downstream_repo for t in DOWNSTREAM: try: cols = downstream_repo.describe(t) line("OK", f"{t:<20} {len(cols)} 列: " f"{', '.join(str(c.get('Field')) for c in cols[:10])}" f"{' ...' if len(cols) > 10 else ''}") except Exception as e: warn(f"{t} 读取失败: {type(e).__name__}: {e}") try: ds = downstream_repo.fetch_positions() line("OK" if ds["columns"].get("qty") else "WARN", f"持仓列探测: {ds['columns']} (共 {ds['raw_count']} 行)") if ds["raw_count"] and not ds["columns"].get("qty"): warn("未识别出持仓数量列 —— 请按 QMT 需求清单 A1/D1 取得 DDL 后补 QTY_CANDIDATES") except Exception as e: warn(f"持仓快照读取失败: {e}") print("\n[4] 行情 Redis (db13)") try: from app.services import market pos = [] try: from app.repo import pms_repo pos = [p["ts_code"] for p in pms_repo.list_positions(only_open=True)][:3] except Exception: pass probe = pos or ["600000.SH"] got = {c: market.get_price(c) for c in probe} if any(v for v in got.values()): line("OK", f"实时价样本: {got}") else: warn(f"取不到实时价样本 {got} (非交易时段属正常)") except Exception as e: warn(f"Redis 行情不可用: {type(e).__name__}: {e}") print("\n[5] 运行参数 (表值优先于 settings 初值)") try: from app.services import param_store snap = param_store.snapshot() from_table = [p for p in snap["params"] if p["source"] == "table"] line("OK", f"可调参数 {len(snap['params'])} 项, 其中页面已改写 {len(from_table)} 项") for k in ("PMS_TOTAL_SCALE", "PMS_PORTFOLIO_CAP", "PMS_STOCK_CAP", "PMS_AUTONOMY", "PMS_SECTOR_SOURCE"): line("", f"{k:<26} = {param_store.get(k)}") if snap.get("source_error"): warn(f"参数表读取异常: {snap['source_error']}") except Exception as e: fail(f"参数中心不可用: {type(e).__name__}: {e}") print("\n" + "-" * 62) if FAILED: print(f"FAILED: {len(FAILED)} 项致命问题, {len(WARNED)} 项告警") for m in FAILED: print(" - " + m) sys.exit(1) print(f"ALL OK ({len(WARNED)} 项告警)" if WARNED else "ALL OK") if __name__ == "__main__": main()