137 lines
5.3 KiB
Python
137 lines
5.3 KiB
Python
"""线索 ORM 模型。
|
|
|
|
对应需求 R7(线索+证据链+解释)、R17(闭环状态)、R18(置信度分级)、R19(线索不可删)。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import datetime as dt
|
|
import enum
|
|
import uuid
|
|
|
|
from sqlalchemy import DateTime, Enum, Float, ForeignKey, Index, String, Text
|
|
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from app.db import Base
|
|
|
|
|
|
def _enum_values(enum_cls):
|
|
"""让 SQLAlchemy 使用枚举的 value(小写)写入 PG 原生 enum,而非 name。"""
|
|
return [m.value for m in enum_cls]
|
|
|
|
|
|
def _uuid() -> uuid.UUID:
|
|
return uuid.uuid4()
|
|
|
|
|
|
def _now() -> dt.datetime:
|
|
return dt.datetime.now(dt.UTC)
|
|
|
|
|
|
class ConfidenceTier(str, enum.Enum):
|
|
"""置信度三级分流(R18)。"""
|
|
|
|
HIGH = "high" # 高置信:直接推送处置
|
|
MEDIUM = "medium" # 中置信:人工复核
|
|
LOW = "low" # 低置信:归档备查
|
|
|
|
|
|
class ClueStatus(str, enum.Enum):
|
|
"""线索闭环状态机(R17)。"""
|
|
|
|
NEW = "new" # 新生成
|
|
ASSIGNED = "assigned" # 已分派
|
|
REVIEWING = "reviewing" # 研判中
|
|
CONFIRMED = "confirmed" # 已定性属实
|
|
DISMISSED = "dismissed" # 已定性误报
|
|
RECTIFYING = "rectifying" # 整改中
|
|
TRANSFERRED = "transferred" # 已移交
|
|
CLOSED = "closed" # 已销项闭环
|
|
|
|
|
|
class Clue(Base):
|
|
"""审计线索。线索一经生成不可物理删除(R19),失效通过状态表达。"""
|
|
|
|
__tablename__ = "clue"
|
|
__table_args__ = (
|
|
Index("ix_clue_status", "status"),
|
|
Index("ix_clue_scenario", "scenario_code"),
|
|
Index("ix_clue_assignee", "assignee"),
|
|
)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=_uuid)
|
|
title: Mapped[str] = mapped_column(String(256), nullable=False)
|
|
risk_domain: Mapped[str] = mapped_column(String(32), nullable=False) # 收入/成本/采购/资金/合规
|
|
scenario_code: Mapped[str] = mapped_column(String(32), nullable=False) # 如 R8/R9
|
|
confidence: Mapped[ConfidenceTier] = mapped_column(
|
|
Enum(ConfidenceTier, name="confidence_tier", values_callable=_enum_values),
|
|
nullable=False,
|
|
)
|
|
score: Mapped[float] = mapped_column(Float, default=0.0) # 0-1 风险评分
|
|
status: Mapped[ClueStatus] = mapped_column(
|
|
Enum(ClueStatus, name="clue_status", values_callable=_enum_values),
|
|
default=ClueStatus.NEW,
|
|
nullable=False,
|
|
)
|
|
# 人话解释(判定理由)与证据链
|
|
rationale: Mapped[str] = mapped_column(Text, default="")
|
|
evidence: Mapped[dict] = mapped_column(JSONB, default=dict)
|
|
# 涉及的主体(金额、实体 id 列表等)
|
|
subjects: Mapped[dict] = mapped_column(JSONB, default=dict)
|
|
amount_involved: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
|
|
assignee: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
# 误报/属实反馈(R18 反馈学习)
|
|
feedback: Mapped[str | None] = mapped_column(String(16), nullable=True) # confirmed/false_positive
|
|
|
|
# 可追溯:产生该线索时的模型/规则/数据版本(R19 三重留痕)
|
|
model_version: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
rule_version: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
data_version_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), nullable=True)
|
|
|
|
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=_now)
|
|
updated_at: Mapped[dt.datetime] = mapped_column(
|
|
DateTime(timezone=True), default=_now, onupdate=_now
|
|
)
|
|
|
|
history: Mapped[list[ClueStatusHistory]] = relationship(
|
|
back_populates="clue", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class ClueStatusHistory(Base):
|
|
"""线索状态流转留痕(R17/R19)。"""
|
|
|
|
__tablename__ = "clue_status_history"
|
|
__table_args__ = (Index("ix_csh_clue", "clue_id"),)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=_uuid)
|
|
clue_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("clue.id"), nullable=False
|
|
)
|
|
from_status: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
to_status: Mapped[str] = mapped_column(String(16), nullable=False)
|
|
actor: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
note: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=_now)
|
|
|
|
clue: Mapped[Clue] = relationship(back_populates="history")
|
|
|
|
|
|
class WorkingPaper(Base):
|
|
"""审计底稿(R17):研判完成自动生成,可追溯。"""
|
|
|
|
__tablename__ = "working_paper"
|
|
__table_args__ = (Index("ix_wp_clue", "clue_id"),)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=_uuid)
|
|
clue_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("clue.id"), nullable=False
|
|
)
|
|
content: Mapped[str] = mapped_column(Text, default="")
|
|
conclusion: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
author: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
snapshot: Mapped[dict] = mapped_column(JSONB, default=dict) # 证据/版本快照
|
|
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=_now)
|