Files
XingHuo/app/repository/core_ro.py
T

159 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Core 模拟库只读访问(jinrong_core · 无 HTTP API)。"""
from __future__ import annotations
from datetime import date, datetime, time, timedelta
from decimal import Decimal
from typing import Any
from sqlalchemy import create_engine, text
from sqlalchemy.engine import Engine
from app.config.settings import settings
class CoreReadOnlyRepository:
"""仅 SELECT jinrong_core;禁止写操作。"""
def __init__(self, engine: Engine | None = None) -> None:
self._engine = engine or self._default_engine()
@staticmethod
def _default_engine() -> Engine:
pwd = settings.mysql_password
auth = f"{settings.mysql_user}:{pwd}" if pwd else settings.mysql_user
url = (
f"mysql+pymysql://{auth}@{settings.mysql_host}:{settings.mysql_port}"
f"/{settings.mysql_core_database}?charset=utf8mb4"
)
return create_engine(url, pool_pre_ping=True)
def get_customer_l0(self, customer_id: str) -> dict[str, Any] | None:
sql = text(
"""
SELECT c.customer_id, c.display_name, c.age, c.occupation, c.open_date,
r.risk_code, r.evaluated_at AS risk_evaluated_at
FROM core_customer c
LEFT JOIN core_customer_risk r ON r.customer_id = c.customer_id
WHERE c.customer_id = :cid AND c.is_active = 1
"""
)
with self._engine.connect() as conn:
row = conn.execute(sql, {"cid": customer_id}).mappings().first()
return dict(row) if row else None
def list_holdings(self, customer_id: str) -> list[dict[str, Any]]:
sql = text(
"""
SELECT h.*, p.product_name, p.min_risk_code, p.product_type
FROM core_holding h
JOIN core_product p ON p.product_id = h.product_id
WHERE h.customer_id = :cid
ORDER BY h.market_value DESC
"""
)
with self._engine.connect() as conn:
return [dict(r) for r in conn.execute(sql, {"cid": customer_id}).mappings()]
def list_trades(
self, customer_id: str, since: date | None = None, limit: int = 50
) -> list[dict[str, Any]]:
sql = text(
"""
SELECT t.*, p.product_name
FROM core_trade t
JOIN core_product p ON p.product_id = t.product_id
WHERE t.customer_id = :cid
AND (:since IS NULL OR t.traded_at >= :since)
ORDER BY t.traded_at DESC
LIMIT :lim
"""
)
with self._engine.connect() as conn:
return [
dict(r)
for r in conn.execute(
sql, {"cid": customer_id, "since": since, "lim": limit}
).mappings()
]
def sum_trades_on_date(self, customer_id: str, day: date) -> Decimal:
"""当日申赎合计金额(RISK-002 累计口径:仅 confirmed 的 subscribe/redeem)。
当日 = 服务器本地时区自然日,以 traded_at 落在 [day 00:00, day+1 00:00) 为准。
"""
day_start = datetime.combine(day, time.min)
day_end = day_start + timedelta(days=1)
sql = text(
"""
SELECT COALESCE(SUM(amount), 0)
FROM core_trade
WHERE customer_id = :cid
AND trade_type IN ('subscribe', 'redeem')
AND trade_status = 'confirmed'
AND traded_at >= :day_start
AND traded_at < :day_end
"""
)
with self._engine.connect() as conn:
total = conn.execute(
sql, {"cid": customer_id, "day_start": day_start, "day_end": day_end}
).scalar_one()
return Decimal(total)
def get_product(self, product_id: str) -> dict[str, Any] | None:
sql = text("SELECT * FROM core_product WHERE product_id = :pid")
with self._engine.connect() as conn:
row = conn.execute(sql, {"pid": product_id}).mappings().first()
return dict(row) if row else None
def get_latest_nav(self, product_id: str) -> dict[str, Any] | None:
sql = text(
"""
SELECT * FROM core_product_nav
WHERE product_id = :pid
ORDER BY nav_date DESC LIMIT 1
"""
)
with self._engine.connect() as conn:
row = conn.execute(sql, {"pid": product_id}).mappings().first()
return dict(row) if row else None
def list_active_customers(self) -> list[dict[str, Any]]:
"""全量在册客户(id + display_name;AML scan_all 全量扫描用,仅 SELECT)。"""
sql = text(
"SELECT customer_id, display_name FROM core_customer WHERE is_active = 1"
)
with self._engine.connect() as conn:
return [dict(r) for r in conn.execute(sql).mappings()]
def list_customers_by_advisor(self, advisor_id: str) -> list[str]:
sql = text(
"""
SELECT customer_id FROM core_customer_advisor
WHERE advisor_id = :aid AND rel_status = 'active'
"""
)
with self._engine.connect() as conn:
return [r[0] for r in conn.execute(sql, {"aid": advisor_id})]
def get_staff(self, staff_id: str) -> dict[str, Any] | None:
"""RBAC 联调:查员工角色种子。"""
sql = text(
"SELECT staff_id, display_name, staff_type, roles FROM core_staff WHERE staff_id = :sid AND is_active = 1"
)
with self._engine.connect() as conn:
row = conn.execute(sql, {"sid": staff_id}).mappings().first()
return dict(row) if row else None
def is_advisor_assigned(self, advisor_id: str, customer_id: str) -> bool:
sql = text(
"""
SELECT 1 FROM core_customer_advisor
WHERE advisor_id = :aid AND customer_id = :cid AND rel_status = 'active'
LIMIT 1
"""
)
with self._engine.connect() as conn:
return conn.execute(sql, {"aid": advisor_id, "cid": customer_id}).first() is not None