Files
X-Financial/server/src/app/models/ai_application_preview.py

106 lines
4.3 KiB
Python
Raw Normal View History

from __future__ import annotations
import uuid
from datetime import datetime
from typing import Any
from sqlalchemy import CheckConstraint, DateTime, Index, String, UniqueConstraint, func
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.types import JSON
from app.db.base_class import Base
class AIApplicationPreviewDecision(Base):
"""费用申请落单前,由服务端签发并短期持有的不可变建议快照。"""
__tablename__ = "ai_application_preview_decisions"
__table_args__ = (
UniqueConstraint(
"tenant_id",
"id",
name="uq_ai_application_preview_decisions_tenant_id",
),
UniqueConstraint(
"tenant_id",
"actor_id",
"auth_session_id",
"issue_request_id",
name="uq_ai_application_preview_decisions_issue_request",
),
CheckConstraint(
"status IN ('issued', 'consumed', 'expired', 'revoked')",
name="ck_ai_application_preview_decisions_status",
),
CheckConstraint(
"decision_source IN ('heuristic', 'rule', 'hybrid', 'server_draft')",
name="ck_ai_application_preview_decisions_source",
),
CheckConstraint(
"consumed_action IS NULL OR consumed_action IN ('save_draft', 'submit')",
name="ck_ai_application_preview_decisions_consumed_action",
),
CheckConstraint(
"status != 'consumed' OR ("
"consumed_action IS NOT NULL AND consumed_request_id IS NOT NULL AND "
"consumed_claim_id IS NOT NULL AND consumed_business_event_id IS NOT NULL AND "
"consumed_final_fingerprint IS NOT NULL AND consumed_at IS NOT NULL)",
name="ck_ai_application_preview_decisions_consumed_fields",
),
CheckConstraint(
"expires_at > created_at",
name="ck_ai_application_preview_decisions_expiry",
),
Index(
"ix_ai_application_preview_decisions_actor_status_expiry",
"tenant_id",
"actor_id",
"status",
"expires_at",
),
Index(
"ix_ai_application_preview_decisions_conversation",
"tenant_id",
"conversation_id",
"created_at",
),
)
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
)
tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
actor_id: Mapped[str] = mapped_column(String(120), nullable=False)
auth_session_id: Mapped[str] = mapped_column(String(120), nullable=False)
conversation_id: Mapped[str] = mapped_column(String(120), nullable=False, default="")
decision_type: Mapped[str] = mapped_column(String(80), nullable=False)
decision_source: Mapped[str] = mapped_column(String(20), nullable=False)
field_keys_json: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
field_fingerprints_json: Mapped[dict[str, str]] = mapped_column(
JSON,
nullable=False,
default=dict,
)
snapshot_fingerprint: Mapped[str] = mapped_column(String(80), nullable=False)
fingerprint_key_version: Mapped[str] = mapped_column(String(40), nullable=False)
source_version_json: Mapped[dict[str, Any]] = mapped_column(
JSON,
nullable=False,
default=dict,
)
status: Mapped[str] = mapped_column(String(20), nullable=False, default="issued")
issue_request_id: Mapped[str] = mapped_column(String(120), nullable=False)
consumed_action: Mapped[str | None] = mapped_column(String(30), nullable=True)
consumed_request_id: Mapped[str | None] = mapped_column(String(120), nullable=True)
consumed_claim_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
consumed_business_event_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
consumed_final_fingerprint: Mapped[str | None] = mapped_column(String(80), nullable=True)
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
consumed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
)