feat(ai): issue verified application preview decisions
This commit is contained in:
@@ -7,6 +7,7 @@ from app.models.agent_asset import (
|
||||
from app.models.agent_conversation import AgentConversation, AgentConversationMessage
|
||||
from app.models.agent_feedback import AgentOperationFeedback
|
||||
from app.models.agent_run import AgentRun, AgentToolCall, AgentTraceEvent, SemanticParseLog
|
||||
from app.models.ai_application_preview import AIApplicationPreviewDecision
|
||||
from app.models.ai_learning import AIDecision, AIDecisionFeedback, WorkflowOutcome
|
||||
from app.models.approval import ApprovalRecord
|
||||
from app.models.audit_log import AuditLog
|
||||
@@ -49,6 +50,7 @@ __all__ = [
|
||||
"AgentRun",
|
||||
"AgentToolCall",
|
||||
"AgentTraceEvent",
|
||||
"AIApplicationPreviewDecision",
|
||||
"ApprovalRecord",
|
||||
"AuditLog",
|
||||
"AuthSession",
|
||||
|
||||
105
server/src/app/models/ai_application_preview.py
Normal file
105
server/src/app/models/ai_application_preview.py
Normal file
@@ -0,0 +1,105 @@
|
||||
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(),
|
||||
)
|
||||
@@ -44,6 +44,20 @@ class AIDecision(Base):
|
||||
"idempotency_key",
|
||||
name="uq_ai_decisions_tenant_idempotency",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"preview_decision_id",
|
||||
name="uq_ai_decisions_tenant_preview_decision",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id", "preview_decision_id"],
|
||||
[
|
||||
"ai_application_preview_decisions.tenant_id",
|
||||
"ai_application_preview_decisions.id",
|
||||
],
|
||||
ondelete="RESTRICT",
|
||||
name="fk_ai_decisions_tenant_preview_decision",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id", "expense_case_id"],
|
||||
["expense_cases.tenant_id", "expense_cases.id"],
|
||||
@@ -90,6 +104,7 @@ class AIDecision(Base):
|
||||
expense_case_id: Mapped[str] = mapped_column(String(36), nullable=False)
|
||||
business_event_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
|
||||
expense_claim_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
||||
preview_decision_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
|
||||
agent_run_id: Mapped[str | None] = mapped_column(String(120), nullable=True)
|
||||
correlation_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
subject_type: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
|
||||
Reference in New Issue
Block a user