Files
group_fqcd_jr/app/model/fund.py
T

397 lines
24 KiB
Python
Raw Normal View History

"""场内基金模拟交易域(fin_*)只读 ORM 映射。
设计约定(强约束):
1. 本模块只做「列映射」:不提供任何写辅助方法(无 insert/update/delete/save/merge),
底座、Agent 与业务组员都不得借本模块下单、改持仓或改交易数据(项目硬性业务红线)。
2. 列名、类型、可空性严格对齐 MySQL 实际结构(information_schema.COLUMNS 逐列核对),
不允许任何 DDL / Alembic 迁移改动库结构;回归校验由 tools/audit_constraints.py 负责。
3. 不声明 ForeignKey:跨表关系由 app/repository/fund_query_repository.py 的
客户数据范围解析器表达,避免 ORM 引入 flush 期级联写语义。
4. 未映射 MySQL 生成列(GENERATION_EXPRESSION 非空)——本域当前没有生成列。
5. 只读查询统一走 app/repository/fund_query_repository.py,Agent(Service 层)
不得直接访问本模块的 ORM 实体。
"""
from datetime import date, datetime
from decimal import Decimal
from typing import Any
from sqlalchemy import JSON, Date, Integer, Numeric, String, Text
from sqlalchemy.dialects.mysql import BIGINT, DATETIME, TINYINT
from sqlalchemy.orm import Mapped, mapped_column
from app.model.base import Base
__all__ = [
"FundCapitalFlow",
"FundCashLedger",
"FundCustomerProfile",
"FundFeeRule",
"FundHolding",
"FundMarketPrice",
"FundNavHistory",
"FundProduct",
"FundRiskAlert",
"FundRiskAssessment",
"FundRiskNotification",
"FundSimAccount",
"FundSimOrder",
"FundTransaction",
]
class FundProduct(Base):
"""场内基金产品主数据(fin_product)。"""
__tablename__ = "fin_product"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
product_code: Mapped[str] = mapped_column(String(32), nullable=False)
product_name: Mapped[str] = mapped_column(String(128), nullable=False)
exchange_code: Mapped[str] = mapped_column(String(16), nullable=False)
product_category: Mapped[str] = mapped_column(String(32), nullable=False)
risk_level: Mapped[str] = mapped_column(String(8), nullable=False)
fund_manager: Mapped[str | None] = mapped_column(String(64), nullable=True)
currency: Mapped[str] = mapped_column(String(8), nullable=False)
lot_size: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False)
price_tick: Mapped[Decimal] = mapped_column(Numeric(18, 6), nullable=False)
current_nav: Mapped[Decimal | None] = mapped_column(Numeric(18, 6), nullable=True)
current_nav_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
min_amount: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
open_start_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
open_end_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
open_period_start: Mapped[date | None] = mapped_column(Date, nullable=True)
open_period_end: Mapped[date | None] = mapped_column(Date, nullable=True)
transaction_fee_rate: Mapped[Decimal | None] = mapped_column(Numeric(10, 6), nullable=True)
single_investor_max_holding_ratio: Mapped[Decimal] = mapped_column(
Numeric(7, 4), nullable=False
)
management_fee_rate: Mapped[Decimal | None] = mapped_column(Numeric(7, 4), nullable=True)
custodian_fee_rate: Mapped[Decimal | None] = mapped_column(Numeric(7, 4), nullable=True)
risk_disclosure_required: Mapped[int] = mapped_column(TINYINT(display_width=1), nullable=False)
second_confirmation_required: Mapped[int] = mapped_column(
TINYINT(display_width=1), nullable=False
)
recording_required: Mapped[int] = mapped_column(TINYINT(display_width=1), nullable=False)
status: Mapped[str] = mapped_column(String(16), nullable=False)
created_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
updated_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
class FundMarketPrice(Base):
"""场内日行情(fin_market_price),行情为公共数据,无客户归属。"""
__tablename__ = "fin_market_price"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
product_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
trade_date: Mapped[date] = mapped_column(Date, nullable=False)
open_price: Mapped[Decimal] = mapped_column(Numeric(18, 6), nullable=False)
high_price: Mapped[Decimal] = mapped_column(Numeric(18, 6), nullable=False)
low_price: Mapped[Decimal] = mapped_column(Numeric(18, 6), nullable=False)
close_price: Mapped[Decimal] = mapped_column(Numeric(18, 6), nullable=False)
#: 当日涨跌幅(%),来自行情源。**可空**:源未提供、或该行由不提供涨跌幅的降级路径
#: (如 `eastmoney_nav_fallback`)写入时为 NULL —— 调用方必须显示"暂无"而不是当成 0。
change_pct: Mapped[Decimal | None] = mapped_column(Numeric(10, 4), nullable=True)
volume: Mapped[Decimal | None] = mapped_column(Numeric(24, 4), nullable=True)
turnover_amount: Mapped[Decimal | None] = mapped_column(Numeric(24, 2), nullable=True)
total_fund_shares: Mapped[Decimal] = mapped_column(Numeric(24, 4), nullable=False)
source: Mapped[str] = mapped_column(String(32), nullable=False)
source_updated_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
created_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
class FundNavHistory(Base):
"""基金净值历史(fin_nav_history),公共数据。"""
__tablename__ = "fin_nav_history"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
product_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
nav_date: Mapped[date] = mapped_column(Date, nullable=False)
nav: Mapped[Decimal] = mapped_column(Numeric(12, 6), nullable=False)
created_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
class FundSimAccount(Base):
"""模拟资金账户(fin_sim_account)。"""
__tablename__ = "fin_sim_account"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
account_no: Mapped[str] = mapped_column(String(32), nullable=False)
customer_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
currency: Mapped[str] = mapped_column(String(8), nullable=False)
cash_balance: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
available_cash: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
frozen_cash: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
initial_balance: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
status: Mapped[str] = mapped_column(String(16), nullable=False)
version: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
created_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
updated_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
class FundCashLedger(Base):
"""资金流水(fin_cash_ledger)。无 customer_id,客户归属经 account_id 间接解析。"""
__tablename__ = "fin_cash_ledger"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
ledger_no: Mapped[str] = mapped_column(String(64), nullable=False)
account_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
transaction_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
entry_type: Mapped[str] = mapped_column(String(24), nullable=False)
amount: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
balance_after: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
available_cash_after: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
frozen_cash_after: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
idempotency_key: Mapped[str] = mapped_column(String(128), nullable=False)
occurred_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
created_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
class FundCapitalFlow(Base):
"""资金划转/出入金流水(fin_capital_flow)。"""
__tablename__ = "fin_capital_flow"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
flow_no: Mapped[str] = mapped_column(String(64), nullable=False)
customer_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
account_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
transaction_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
flow_type: Mapped[str] = mapped_column(String(16), nullable=False)
amount: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
balance_after: Mapped[Decimal | None] = mapped_column(Numeric(18, 2), nullable=True)
status: Mapped[str] = mapped_column(String(16), nullable=False)
settled_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
occurred_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
payer_name: Mapped[str | None] = mapped_column(String(64), nullable=True)
source_type: Mapped[str] = mapped_column(String(16), nullable=False)
related_work_order_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
match_status: Mapped[str] = mapped_column(String(16), nullable=False)
created_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
updated_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
class FundFeeRule(Base):
"""费率规则(fin_fee_rule),配置型公共数据。"""
__tablename__ = "fin_fee_rule"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
rule_code: Mapped[str] = mapped_column(String(64), nullable=False)
product_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
exchange_code: Mapped[str | None] = mapped_column(String(16), nullable=True)
order_side: Mapped[str] = mapped_column(String(8), nullable=False)
customer_tier: Mapped[str | None] = mapped_column(String(16), nullable=True)
min_trade_amount: Mapped[Decimal | None] = mapped_column(Numeric(18, 2), nullable=True)
max_trade_amount: Mapped[Decimal | None] = mapped_column(Numeric(18, 2), nullable=True)
fee_rate: Mapped[Decimal] = mapped_column(Numeric(10, 6), nullable=False)
minimum_fee: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
fixed_fee: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
priority: Mapped[int] = mapped_column(Integer, nullable=False)
effective_from: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
effective_until: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
status: Mapped[str] = mapped_column(String(16), nullable=False)
created_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
updated_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
class FundSimOrder(Base):
"""模拟委托单(fin_sim_order)。只读查询,禁止在此写入委托。"""
__tablename__ = "fin_sim_order"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
order_no: Mapped[str] = mapped_column(String(64), nullable=False)
customer_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
account_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
product_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
order_side: Mapped[str] = mapped_column(String(8), nullable=False)
price_type: Mapped[str] = mapped_column(String(8), nullable=False)
quantity: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False)
limit_price: Mapped[Decimal | None] = mapped_column(Numeric(18, 6), nullable=True)
quote_price: Mapped[Decimal] = mapped_column(Numeric(18, 6), nullable=False)
quote_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
quote_source: Mapped[str] = mapped_column(String(32), nullable=False)
channel: Mapped[str | None] = mapped_column(String(32), nullable=True)
advisor_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
filled_quantity: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False)
average_executed_price: Mapped[Decimal | None] = mapped_column(Numeric(18, 6), nullable=True)
status: Mapped[str] = mapped_column(String(24), nullable=False)
risk_rule_hits: Mapped[Any | None] = mapped_column(JSON, nullable=True)
risk_disclosure_ack_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
second_confirmation_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
recording_reference: Mapped[str | None] = mapped_column(String(64), nullable=True)
ops_handler_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
ops_handled_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
compliance_handler_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
compliance_handled_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
reject_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
submitted_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
cancelled_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
created_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
updated_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
class FundTransaction(Base):
"""成交记录(fin_transaction)。只读查询,禁止在此写入成交。"""
__tablename__ = "fin_transaction"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
transaction_no: Mapped[str] = mapped_column(String(64), nullable=False)
order_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
work_order_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
customer_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
account_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
product_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
order_side: Mapped[str] = mapped_column(String(8), nullable=False)
transaction_type: Mapped[str] = mapped_column(String(16), nullable=False)
executed_price: Mapped[Decimal] = mapped_column(Numeric(18, 6), nullable=False)
nav: Mapped[Decimal] = mapped_column(Numeric(12, 6), nullable=False)
executed_quantity: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False)
shares: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False)
gross_amount: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
amount: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
fee_rule_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
fee_rate_snapshot: Mapped[Decimal] = mapped_column(Numeric(10, 6), nullable=False)
fee_amount: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
fee: Mapped[Decimal | None] = mapped_column(Numeric(18, 2), nullable=True)
net_amount: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
quote_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
quote_source: Mapped[str] = mapped_column(String(32), nullable=False)
executed_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
confirmed_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
confirmed_by: Mapped[int | None] = mapped_column(Integer, nullable=True)
auto_confirmed: Mapped[int] = mapped_column(TINYINT(display_width=1), nullable=False)
created_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
class FundHolding(Base):
"""持仓(fin_holding)。只读查询,禁止在此修改持仓。"""
__tablename__ = "fin_holding"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
customer_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
trade_account: Mapped[str] = mapped_column(String(32), nullable=False)
product_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
total_quantity: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False)
shares: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False)
available_quantity: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False)
frozen_quantity: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False)
average_cost: Mapped[Decimal] = mapped_column(Numeric(18, 6), nullable=False)
cost_amount: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
market_value: Mapped[Decimal | None] = mapped_column(Numeric(18, 2), nullable=True)
current_value: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
profit_loss: Mapped[Decimal | None] = mapped_column(Numeric(18, 2), nullable=True)
profit_loss_ratio: Mapped[Decimal | None] = mapped_column(Numeric(10, 4), nullable=True)
status: Mapped[str] = mapped_column(String(16), nullable=False)
first_acquired_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
version: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
updated_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
class FundCustomerProfile(Base):
"""客户画像(fin_customer_profile),主键为 customer_id。"""
__tablename__ = "fin_customer_profile"
customer_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
trade_account: Mapped[str] = mapped_column(String(32), nullable=False)
real_name: Mapped[str] = mapped_column(String(64), nullable=False)
birth_date: Mapped[date | None] = mapped_column(Date, nullable=True)
occupation: Mapped[str | None] = mapped_column(String(64), nullable=True)
mobile_masked: Mapped[str | None] = mapped_column(String(32), nullable=True)
investor_type: Mapped[str] = mapped_column(String(8), nullable=False)
investment_horizon: Mapped[str | None] = mapped_column(String(32), nullable=True)
preferred_asset_class: Mapped[Any | None] = mapped_column(JSON, nullable=True)
trading_frequency: Mapped[str | None] = mapped_column(String(32), nullable=True)
last_active_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
total_asset: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False)
behavior_score: Mapped[int] = mapped_column(Integer, nullable=False)
risk_tags: Mapped[Any | None] = mapped_column(JSON, nullable=True)
opened_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
updated_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
class FundRiskAssessment(Base):
"""风险测评记录(fin_risk_assessment)。"""
__tablename__ = "fin_risk_assessment"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
customer_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
questionnaire_version: Mapped[str] = mapped_column(String(32), nullable=False)
answers: Mapped[Any] = mapped_column(JSON, nullable=False)
total_score: Mapped[int] = mapped_column(Integer, nullable=False)
investor_type: Mapped[str] = mapped_column(String(8), nullable=False)
assessed_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
valid_until: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
created_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
class FundRiskAlert(Base):
"""风险预警事件(fin_risk_alert)。"""
__tablename__ = "fin_risk_alert"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
alert_no: Mapped[str] = mapped_column(String(64), nullable=False)
customer_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
related_transaction_id: Mapped[int | None] = mapped_column(
BIGINT(unsigned=True), nullable=True
)
related_order_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
related_work_order_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
primary_risk_work_order_id: Mapped[int | None] = mapped_column(
BIGINT(unsigned=True), nullable=True
)
alert_type: Mapped[str] = mapped_column(String(64), nullable=False)
alert_level: Mapped[str] = mapped_column(String(8), nullable=False)
trigger_rule_codes: Mapped[Any] = mapped_column(JSON, nullable=False)
evidence_summary: Mapped[str] = mapped_column(Text, nullable=False)
evidence_snapshot: Mapped[Any] = mapped_column(JSON, nullable=False)
priority_score: Mapped[int] = mapped_column(Integer, nullable=False)
event_status: Mapped[str] = mapped_column(String(16), nullable=False)
status: Mapped[str] = mapped_column(String(16), nullable=False)
ack_status: Mapped[str] = mapped_column(String(16), nullable=False)
ack_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
handler_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
due_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
is_escalated: Mapped[int] = mapped_column(TINYINT(display_width=1), nullable=False)
escalated_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
escalation_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
handle_result: Mapped[str | None] = mapped_column(Text, nullable=True)
closed_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
close_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
ai_analysis: Mapped[Any | None] = mapped_column(JSON, nullable=True)
manual_remark: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
updated_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
class FundRiskNotification(Base):
"""风险通知(fin_risk_notification)。无 customer_id,客户归属经 alert_id 间接解析。"""
__tablename__ = "fin_risk_notification"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
notification_no: Mapped[str] = mapped_column(String(64), nullable=False)
alert_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
channel: Mapped[str] = mapped_column(String(16), nullable=False)
receiver_user_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
receiver_email: Mapped[str | None] = mapped_column(String(128), nullable=True)
title: Mapped[str] = mapped_column(String(128), nullable=False)
content: Mapped[str] = mapped_column(Text, nullable=False)
send_status: Mapped[str] = mapped_column(String(16), nullable=False)
sent_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
read_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
acknowledged_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
fail_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)