Files

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)