102 lines
3.4 KiB
Python
102 lines
3.4 KiB
Python
"""只读探查:第十五条豁免规则(持仓占比)所需的数据是否齐备。
|
||||
|
|
|
|||
|
|
只做 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())
|