175 lines
6.0 KiB
Python
175 lines
6.0 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import uuid
|
||
|
|
from datetime import datetime
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from sqlalchemy import (
|
||
|
|
CheckConstraint,
|
||
|
|
DateTime,
|
||
|
|
ForeignKeyConstraint,
|
||
|
|
Index,
|
||
|
|
Integer,
|
||
|
|
String,
|
||
|
|
Text,
|
||
|
|
UniqueConstraint,
|
||
|
|
func,
|
||
|
|
)
|
||
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||
|
|
from sqlalchemy.types import JSON
|
||
|
|
|
||
|
|
from app.db.base_class import Base
|
||
|
|
|
||
|
|
|
||
|
|
def _new_id() -> str:
|
||
|
|
return str(uuid.uuid4())
|
||
|
|
|
||
|
|
|
||
|
|
class RiskDisposition(Base):
|
||
|
|
"""风险观察的当前处置投影;裁决结论和处置生命周期彼此独立。"""
|
||
|
|
|
||
|
|
__tablename__ = "risk_dispositions"
|
||
|
|
__table_args__ = (
|
||
|
|
UniqueConstraint(
|
||
|
|
"tenant_id",
|
||
|
|
"id",
|
||
|
|
name="uq_risk_dispositions_tenant_id",
|
||
|
|
),
|
||
|
|
UniqueConstraint(
|
||
|
|
"tenant_id",
|
||
|
|
"observation_id",
|
||
|
|
name="uq_risk_dispositions_tenant_observation",
|
||
|
|
),
|
||
|
|
CheckConstraint(
|
||
|
|
"adjudication IN ('unreviewed', 'confirmed', 'false_positive')",
|
||
|
|
name="ck_risk_dispositions_adjudication",
|
||
|
|
),
|
||
|
|
CheckConstraint(
|
||
|
|
"lifecycle_status IN ('open', 'supplement_requested', "
|
||
|
|
"'remediation_in_progress', 'waiver_requested', 'resolved')",
|
||
|
|
name="ck_risk_dispositions_lifecycle",
|
||
|
|
),
|
||
|
|
CheckConstraint("version >= 0", name="ck_risk_dispositions_version"),
|
||
|
|
ForeignKeyConstraint(
|
||
|
|
["tenant_id", "observation_id"],
|
||
|
|
["risk_observations.tenant_id", "risk_observations.id"],
|
||
|
|
ondelete="RESTRICT",
|
||
|
|
name="fk_risk_dispositions_tenant_observation",
|
||
|
|
),
|
||
|
|
Index(
|
||
|
|
"ix_risk_dispositions_tenant_lifecycle_due",
|
||
|
|
"tenant_id",
|
||
|
|
"lifecycle_status",
|
||
|
|
"due_at",
|
||
|
|
),
|
||
|
|
Index("ix_risk_dispositions_assignee", "tenant_id", "assignee"),
|
||
|
|
)
|
||
|
|
|
||
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
|
||
|
|
tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||
|
|
observation_id: Mapped[str] = mapped_column(String(36), nullable=False)
|
||
|
|
adjudication: Mapped[str] = mapped_column(
|
||
|
|
String(24),
|
||
|
|
nullable=False,
|
||
|
|
default="unreviewed",
|
||
|
|
server_default="unreviewed",
|
||
|
|
)
|
||
|
|
lifecycle_status: Mapped[str] = mapped_column(
|
||
|
|
String(32),
|
||
|
|
nullable=False,
|
||
|
|
default="open",
|
||
|
|
server_default="open",
|
||
|
|
)
|
||
|
|
version: Mapped[int] = mapped_column(
|
||
|
|
Integer,
|
||
|
|
nullable=False,
|
||
|
|
default=0,
|
||
|
|
server_default="0",
|
||
|
|
)
|
||
|
|
assignee: Mapped[str | None] = mapped_column(String(120), nullable=True)
|
||
|
|
due_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||
|
|
resolution: Mapped[str | None] = mapped_column(Text(), nullable=True)
|
||
|
|
created_at: Mapped[datetime] = mapped_column(
|
||
|
|
DateTime(timezone=True),
|
||
|
|
nullable=False,
|
||
|
|
server_default=func.now(),
|
||
|
|
)
|
||
|
|
updated_at: Mapped[datetime] = mapped_column(
|
||
|
|
DateTime(timezone=True),
|
||
|
|
nullable=False,
|
||
|
|
server_default=func.now(),
|
||
|
|
onupdate=func.now(),
|
||
|
|
)
|
||
|
|
|
||
|
|
observation = relationship("RiskObservation", back_populates="disposition")
|
||
|
|
events = relationship(
|
||
|
|
"RiskDispositionEvent",
|
||
|
|
back_populates="disposition",
|
||
|
|
order_by="asc(RiskDispositionEvent.version)",
|
||
|
|
lazy="selectin",
|
||
|
|
passive_deletes=True,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class RiskDispositionEvent(Base):
|
||
|
|
"""只追加的风险处置事实;每个版本只对应一个类型化动作。"""
|
||
|
|
|
||
|
|
__tablename__ = "risk_disposition_events"
|
||
|
|
__table_args__ = (
|
||
|
|
UniqueConstraint(
|
||
|
|
"tenant_id",
|
||
|
|
"request_id",
|
||
|
|
name="uq_risk_disposition_events_tenant_request",
|
||
|
|
),
|
||
|
|
UniqueConstraint(
|
||
|
|
"disposition_id",
|
||
|
|
"version",
|
||
|
|
name="uq_risk_disposition_events_version",
|
||
|
|
),
|
||
|
|
CheckConstraint(
|
||
|
|
"action IN ('confirm', 'false_positive', 'request_supplement', "
|
||
|
|
"'start_remediation', 'resolve', 'request_waiver')",
|
||
|
|
name="ck_risk_disposition_events_action",
|
||
|
|
),
|
||
|
|
CheckConstraint("version > 0", name="ck_risk_disposition_events_version"),
|
||
|
|
ForeignKeyConstraint(
|
||
|
|
["tenant_id", "disposition_id"],
|
||
|
|
["risk_dispositions.tenant_id", "risk_dispositions.id"],
|
||
|
|
ondelete="RESTRICT",
|
||
|
|
name="fk_risk_disposition_events_tenant_disposition",
|
||
|
|
),
|
||
|
|
ForeignKeyConstraint(
|
||
|
|
["tenant_id", "observation_id"],
|
||
|
|
["risk_observations.tenant_id", "risk_observations.id"],
|
||
|
|
ondelete="RESTRICT",
|
||
|
|
name="fk_risk_disposition_events_tenant_observation",
|
||
|
|
),
|
||
|
|
Index(
|
||
|
|
"ix_risk_disposition_events_tenant_observation_time",
|
||
|
|
"tenant_id",
|
||
|
|
"observation_id",
|
||
|
|
"created_at",
|
||
|
|
),
|
||
|
|
)
|
||
|
|
|
||
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
|
||
|
|
tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||
|
|
disposition_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||
|
|
observation_id: Mapped[str] = mapped_column(String(36), nullable=False)
|
||
|
|
version: Mapped[int] = mapped_column(Integer, nullable=False)
|
||
|
|
action: Mapped[str] = mapped_column(String(32), nullable=False)
|
||
|
|
actor_id: Mapped[str] = mapped_column(String(120), nullable=False)
|
||
|
|
actor_name: Mapped[str] = mapped_column(String(120), nullable=False)
|
||
|
|
request_id: Mapped[str] = mapped_column(String(120), nullable=False)
|
||
|
|
payload_fingerprint: Mapped[str] = mapped_column(String(64), nullable=False)
|
||
|
|
comment: Mapped[str | None] = mapped_column(Text(), nullable=True)
|
||
|
|
before_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
|
||
|
|
after_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
|
||
|
|
created_at: Mapped[datetime] = mapped_column(
|
||
|
|
DateTime(timezone=True),
|
||
|
|
nullable=False,
|
||
|
|
server_default=func.now(),
|
||
|
|
)
|
||
|
|
|
||
|
|
disposition = relationship("RiskDisposition", back_populates="events")
|