Files
group_fqcd_jr/app/model/risk.py
T
张胜宇 e239eb778b docs: 品牌全量口径统一为「南方基金」+ 作废文档清理
1) 客服 Agent 四份交付文档 + 构建脚手架:品牌由包装占位 XX科技 / 旧名 南方财富
   统一为南方基金(热线 400-889-8899 / 官网 nffund.com),系统名改为「智能服务系统」;
   同步追加 §0.4 修订记录行,工程记录行保留原占位字面以支撑硬编码扫描验收。
2) 开发文档:清理 28 份已作废/残留文档(14 份移出归档 + 14 份仓库副本),
   新增《文档规整方案与开发前待决事项-2026-09-17》。
3) 客服agent 四份交付文档首次纳入本分支。
2026-09-17 15:15:22 +08:00

116 lines
5.8 KiB
Python

"""风控查询所需的补充只读 ORM 映射。
第二版 `app/model/fund.py` 已覆盖多数 `fin_*` 表;本模块只补充风控查询需要、
但当前尚未映射的三张已有表,不负责建表、迁移或写入。
安全边界:
1. 只声明列映射,不提供写入方法。
2. `sys_user.password_hash` 和 `email` 不进入风控只读模型。
3. 不声明 ForeignKey,避免 ORM 写入时产生级联语义。
"""
from datetime import datetime
from decimal import Decimal
from typing import Any
from sqlalchemy import JSON, 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__ = ["RiskLoginRecord", "RiskUser", "RiskWorkOrder"]
class RiskUser(Base):
"""统一身份表中的风控只读字段(sys_user)。"""
__tablename__ = "sys_user"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
user_no: Mapped[str] = mapped_column(String(32), nullable=False)
username: Mapped[str] = mapped_column(String(64), nullable=False)
user_type: Mapped[str] = mapped_column(String(16), nullable=False)
employee_role: Mapped[str | None] = mapped_column(String(32), nullable=True)
customer_tier: Mapped[str | None] = mapped_column(String(16), nullable=True)
investor_type: Mapped[str | None] = mapped_column(String(8), nullable=True)
investor_type_assessed_at: Mapped[datetime | None] = mapped_column(
DATETIME(fsp=0), nullable=True
)
is_professional_investor: Mapped[int] = mapped_column(
TINYINT(display_width=1), nullable=False
)
professional_investor_status: Mapped[str] = mapped_column(String(16), nullable=False)
professional_investor_certified_at: Mapped[datetime | None] = mapped_column(
DATETIME(fsp=0), nullable=True
)
fund_account_status: Mapped[str] = mapped_column(String(16), nullable=False)
fund_account_opened_at: 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 RiskLoginRecord(Base):
"""登录记录只读映射(sys_login_record)。"""
__tablename__ = "sys_login_record"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
user_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
login_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
login_result: Mapped[str] = mapped_column(String(16), nullable=False)
ip_region: Mapped[str | None] = mapped_column(String(64), nullable=True)
device_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
is_common_device: Mapped[int] = mapped_column(TINYINT(display_width=1), nullable=False)
failure_reason: Mapped[str | None] = mapped_column(String(128), nullable=True)
created_at: Mapped[datetime] = mapped_column(DATETIME(fsp=0), nullable=False)
class RiskWorkOrder(Base):
"""风险处置及交易申请兼容工单只读映射(biz_work_order)。"""
__tablename__ = "biz_work_order"
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True)
work_order_no: Mapped[str] = mapped_column(String(64), nullable=False)
customer_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), nullable=False)
order_type: Mapped[str | None] = mapped_column(String(16), nullable=True)
product_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
amount: Mapped[Decimal | None] = mapped_column(Numeric(18, 2), nullable=True)
channel: Mapped[str | None] = mapped_column(String(32), nullable=True)
advisor_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
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)
alert_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
work_order_type: Mapped[str | None] = mapped_column(String(32), nullable=True)
submitter_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
handler_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), nullable=True)
priority: Mapped[str | None] = mapped_column(String(8), nullable=True)
status: Mapped[str] = mapped_column(String(24), nullable=False)
request_detail: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
handle_result: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
submitted_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
accepted_at: Mapped[datetime | None] = mapped_column(DATETIME(fsp=0), nullable=True)
completed_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)