153 lines
5.9 KiB
Python
153 lines
5.9 KiB
Python
# -*- 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_* / 因子分表)"),
|
|
("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']}")
|
|
rf = dbs.ping("factor")
|
|
line("OK" if rf["ok"] else "INFO",
|
|
"因子库直连 (SOURCE_DB_EXT_DSN) —— PMS 当前**不使用**: 因子分表经 "
|
|
"ShardingSphere 动态分片, 必须走 153 代理读 (沿用 bionic 结论), "
|
|
f"故此项连不通不影响任何功能。当前: {'通' if rf['ok'] else rf['error'].splitlines()[0]}")
|
|
|
|
print("\n[2] PMS 自有表")
|
|
missing_tables = []
|
|
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:
|
|
msg = str(e)
|
|
if "does not exist" in msg or "doesn't exist" in msg:
|
|
missing_tables.append(t)
|
|
fail(f"{t} 不存在")
|
|
else:
|
|
fail(f"{t} 不可用: {type(e).__name__}: {e}")
|
|
if missing_tables:
|
|
line("", "→ 建表: docker compose run --rm pms-web python scripts/init_db.py --yes")
|
|
|
|
print("\n[3] 下游只读表 (完整列定义, 可直接回填 QMT 需求清单 D1)")
|
|
from app.repo import downstream_repo
|
|
for t in DOWNSTREAM:
|
|
try:
|
|
cols = downstream_repo.describe(t)
|
|
line("OK", f"{t} —— {len(cols)} 列")
|
|
for c in cols:
|
|
line("", f" {str(c.get('Field')):<26} {str(c.get('Type')):<18} "
|
|
f"{'NULL' if str(c.get('Null')).upper() == 'YES' else 'NOT NULL':<9}"
|
|
f"{str(c.get('Comment') or '')[:40]}")
|
|
except Exception as e:
|
|
warn(f"{t} 读取失败: {type(e).__name__}: {e}")
|
|
try:
|
|
ds = downstream_repo.fetch_positions()
|
|
if ds["raw_count"] and not ds["columns"].get("qty"):
|
|
warn(f"未识别出持仓数量列 {ds['columns']} —— 把真实列名补进 "
|
|
f"downstream_repo.QTY_CANDIDATES")
|
|
else:
|
|
line("OK", f"持仓列映射: {ds['columns']} (共 {ds['raw_count']} 行)")
|
|
sample = ds["rows"][:3]
|
|
for s in sample:
|
|
line("", f" {s['ts_code']:<12} 持仓 {s['qty']} 可用 {s['avail_qty']} "
|
|
f"成本 {s['cost']} 现价 {s['price']}")
|
|
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
|
|
if not pos:
|
|
try:
|
|
pos = [r["ts_code"] for r in downstream_repo.fetch_positions()["rows"][:3]]
|
|
except Exception:
|
|
pos = []
|
|
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} (非交易时段属正常; 若报 unknown command HELLO, "
|
|
f"说明 redis-py 版本过新走了 RESP3, 重新 build 镜像即可 —— "
|
|
f"requirements 已锁 redis<6)")
|
|
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()
|