#!/usr/bin/env python3 # -*- coding: utf-8 -*- """杜邦 KPI 定义种子应用器 —— 执行 scripts/sql/dupont_kpi_defs_20261003.sql 任务:finance-20261003-dupont-period-basis-design-fullstack.md 动作:① 两账套注册 F_ASSET_TOTAL/F_EQUITY_TOTAL/F_EQUITY_MULTIPLIER(derived) ② F_ASSET_TURNOVER data_source_type: manual → derived 幂等:SQL 自带条件 INSERT(NOT EXISTS)+ 条件 UPDATE,可安全重跑。 用法(**写库**,须先过 approval-gate 放行): venv/bin/python3 scripts/apply_dupont_kpi_defs.py --check # 只读预检(不执行任何语句) venv/bin/python3 scripts/apply_dupont_kpi_defs.py --apply # 执行(需放行) """ from __future__ import annotations import argparse import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from sqlalchemy import text # noqa: E402 from app.database import get_engine # noqa: E402 SQL_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "sql", "dupont_kpi_defs_20261003.sql") TARGETS = ("F_ASSET_TOTAL", "F_EQUITY_TOTAL", "F_EQUITY_MULTIPLIER") TARGET_ENTITIES = (1, 2) def statements(sql_text: str): """按 ASCII ';' 切分(本文件的字符串字面量内不含 ';',已人工核对)""" out = [] for chunk in sql_text.split(";"): stmt = chunk.strip() if stmt and not all(ln.strip().startswith("--") or not ln.strip() for ln in stmt.splitlines()): out.append(stmt) return out def snapshot(conn) -> dict: rows = conn.execute(text("""SELECT entity_id, kpi_code, data_source_type, status FROM kpi_definitions WHERE entity_id IN (1,2) AND kpi_code IN ('F_ASSET_TOTAL','F_EQUITY_TOTAL','F_EQUITY_MULTIPLIER', 'F_ASSET_TURNOVER') ORDER BY entity_id, kpi_code""")).fetchall() return {f"{r[0]}/{r[1]}": (r[2], r[3]) for r in rows} def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--check", action="store_true", help="只读预检(不执行写语句)") ap.add_argument("--apply", action="store_true", help="执行种子 SQL(写库)") args = ap.parse_args() if not (args.check or args.apply): print("须指定 --check 或 --apply", file=sys.stderr) return 2 eng = get_engine() with open(SQL_FILE, encoding="utf-8") as f: stmts = statements(f.read()) print(f"SQL 文件: {SQL_FILE}") print(f"语句数: {len(stmts)}") for i, s in enumerate(stmts, 1): head = " ".join(s.split())[:110] print(f" [{i}] {head}...") with eng.connect() as conn: before = snapshot(conn) print(f"\n写前像({len(before)} 行): {before}") if args.check: print("\n[--check] 只读预检:未执行任何语句(写路径未触发)") return 0 for i, s in enumerate(stmts, 1): r = conn.execute(text(s)) print(f" [{i}] rowcount={r.rowcount}") conn.commit() after = snapshot(conn) print(f"\n写后像({len(after)} 行): {after}") added = {k: v for k, v in after.items() if k not in before} changed = {k: (before[k], v) for k, v in after.items() if k in before and before[k] != v} print(f"新增: {added}") print(f"变更: {changed}") missing = [f"{e}/{c}" for e in TARGET_ENTITIES for c in TARGETS if f"{e}/{c}" not in after] print(f"缺件: {missing or '无'}") return 0 if __name__ == "__main__": sys.exit(main())