"""只读探查:第十五条豁免规则(持仓占比)所需的数据是否齐备。 只做 SELECT。结果写 `docs/evidence/exemption-data-probe.json`: python tools/probe_exemption_data.py 要回答的问题: 1. `fin_customer_profile.total_asset` 有没有值、是否为正; 2. `fin_holding` 有没有行、`market_value` / `current_value` 是否可用; 3. 库内是否存在 C3→R4 / C4→R5 的申购交易 —— 即这条豁免规则是否真的会被触发。 """ from __future__ import annotations import asyncio import json from pathlib import Path from typing import Any from sqlalchemy import text from app.infrastructure.db import SessionFactory OUTPUT = Path("docs/evidence/exemption-data-probe.json") QUERIES: dict[str, str] = { "profile_total_asset": """ SELECT COUNT(*) AS rows_count, SUM(total_asset > 0) AS positive_assets, SUM(total_asset = 0) AS zero_assets, MIN(total_asset) AS min_asset, MAX(total_asset) AS max_asset FROM fin_customer_profile """, "profile_investor_type": """ SELECT investor_type, COUNT(*) AS rows_count FROM fin_customer_profile GROUP BY investor_type ORDER BY investor_type """, "holding_shape": """ SELECT COUNT(*) AS rows_count, SUM(market_value IS NULL) AS null_market_value, SUM(current_value > 0) AS positive_current_value, MIN(current_value) AS min_current_value, MAX(current_value) AS max_current_value FROM fin_holding """, "subscription_pairs": """ SELECT c.investor_type AS customer_level, p.risk_level AS product_level, COUNT(*) AS transactions FROM fin_transaction t JOIN fin_customer_profile c ON c.customer_id = t.customer_id JOIN fin_product p ON p.id = t.product_id WHERE t.transaction_type = '申购' GROUP BY c.investor_type, p.risk_level ORDER BY c.investor_type, p.risk_level """, "exemptible_pairs_detail": """ SELECT t.transaction_no, c.investor_type AS customer_level, p.risk_level AS product_level, c.total_asset, ( SELECT h.current_value FROM fin_holding h WHERE h.customer_id = t.customer_id AND h.product_id = t.product_id ORDER BY h.id DESC LIMIT 1 ) AS holding_current_value FROM fin_transaction t JOIN fin_customer_profile c ON c.customer_id = t.customer_id JOIN fin_product p ON p.id = t.product_id WHERE t.transaction_type = '申购' AND ( (c.investor_type = 'C3' AND p.risk_level = 'R4') OR (c.investor_type = 'C4' AND p.risk_level = 'R5') ) ORDER BY t.id """, } async def collect() -> dict[str, Any]: report: dict[str, Any] = {} async with SessionFactory() as session: for name, sql in QUERIES.items(): rows = (await session.execute(text(sql))).mappings().all() report[name] = [dict(row) for row in rows] return report async def main() -> None: report = await collect() OUTPUT.parent.mkdir(parents=True, exist_ok=True) OUTPUT.write_text( json.dumps(report, ensure_ascii=False, indent=2, default=str), encoding="utf-8", ) print(f"wrote {OUTPUT}") if __name__ == "__main__": asyncio.run(main())