"""场外基金运营独立持久化模型。""" from datetime import date, datetime from decimal import Decimal from typing import Any from sqlalchemy import ( JSON, BigInteger, Date, DateTime, Integer, Numeric, String, Text, UniqueConstraint, ) from sqlalchemy.orm import Mapped, mapped_column from app.model.base import Base class OffsiteFundMail(Base): __tablename__ = "offsite_fund_mail" __table_args__ = ( UniqueConstraint("imap_uid", "message_id", name="uk_offsite_mail_source"), ) id: Mapped[int] = mapped_column(BigInteger, primary_key=True) mail_id: Mapped[str] = mapped_column(String(16), unique=True, nullable=False) imap_uid: Mapped[str] = mapped_column(String(64), nullable=False) message_id: Mapped[str] = mapped_column(String(255), nullable=False) received_date: Mapped[date] = mapped_column(Date, nullable=False) sender: Mapped[str] = mapped_column(String(255), nullable=False) return_path: Mapped[str | None] = mapped_column(String(255)) auth_result: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) original_eml_path: Mapped[str] = mapped_column(String(500), nullable=False) status: Mapped[str] = mapped_column(String(32), nullable=False, default="received") retry_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) last_error: Mapped[str | None] = mapped_column(String(500)) next_retry_at: Mapped[datetime | None] = mapped_column(DateTime) last_attempt_at: Mapped[datetime | None] = mapped_column(DateTime) created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) class OffsiteMailCursor(Base): """场外收件箱 UID 游标和处理租约。""" __tablename__ = "offsite_mail_cursor" __table_args__ = ( UniqueConstraint("mailbox", "folder", name="uk_offsite_mail_cursor_scope"), ) id: Mapped[int] = mapped_column( BigInteger().with_variant(Integer, "sqlite"), primary_key=True ) mailbox: Mapped[str] = mapped_column(String(255), nullable=False) folder: Mapped[str] = mapped_column(String(64), nullable=False, default="INBOX") last_uid: Mapped[str] = mapped_column(String(64), nullable=False, default="0") status: Mapped[str] = mapped_column(String(32), nullable=False, default="idle") retry_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) blocked_uid: Mapped[str | None] = mapped_column(String(64)) blocked_message_id: Mapped[str | None] = mapped_column(String(255)) last_error: Mapped[str | None] = mapped_column(String(500)) next_retry_at: Mapped[datetime | None] = mapped_column(DateTime) lease_id: Mapped[str | None] = mapped_column(String(64)) lease_until: Mapped[datetime | None] = mapped_column(DateTime) created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) class OffsiteFundAttachment(Base): __tablename__ = "offsite_fund_attachment" __table_args__ = ( UniqueConstraint("file_hash", "mail_id", name="uk_offsite_attachment_hash"), ) id: Mapped[int] = mapped_column(BigInteger, primary_key=True) attachment_id: Mapped[str] = mapped_column(String(24), unique=True, nullable=False) mail_id: Mapped[str] = mapped_column(String(16), nullable=False) filename: Mapped[str] = mapped_column(String(255), nullable=False) file_hash: Mapped[str] = mapped_column(String(128), nullable=False) media_type: Mapped[str] = mapped_column(String(128), nullable=False) size_bytes: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0) document_type: Mapped[str] = mapped_column(String(24), nullable=False) original_file_path: Mapped[str] = mapped_column(String(500), nullable=False) ocr_text: Mapped[str] = mapped_column(Text, nullable=False) extracted_fields: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) field_confidence: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) page_evidence: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) status: Mapped[str] = mapped_column(String(32), nullable=False, default="recognized") created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) class OffsiteRecognitionAttempt(Base): """场外附件每次 OCR/模型识别尝试的不可覆盖记录。""" __tablename__ = "offsite_recognition_attempt" id: Mapped[int] = mapped_column( BigInteger().with_variant(Integer, "sqlite"), primary_key=True ) task_id: Mapped[str | None] = mapped_column(String(24)) mail_id: Mapped[str | None] = mapped_column(String(16)) attachment_id: Mapped[str | None] = mapped_column(String(24)) imap_uid: Mapped[str | None] = mapped_column(String(64)) message_id: Mapped[str] = mapped_column(String(255), nullable=False) filename: Mapped[str] = mapped_column(String(255), nullable=False) file_hash: Mapped[str] = mapped_column(String(128), nullable=False) original_file_path: Mapped[str] = mapped_column(String(500), nullable=False) attempt_no: Mapped[int] = mapped_column(Integer, nullable=False) source: Mapped[str] = mapped_column(String(16), nullable=False) operator_id: Mapped[str | None] = mapped_column(String(64)) document_type: Mapped[str] = mapped_column(String(24), nullable=False) ocr_status: Mapped[str] = mapped_column(String(16), nullable=False) llm_status: Mapped[str] = mapped_column(String(16), nullable=False) extracted_fields: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) field_confidence: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) missing_fields: Mapped[list[Any]] = mapped_column(JSON, nullable=False) low_confidence_fields: Mapped[list[Any]] = mapped_column(JSON, nullable=False) page_evidence: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) status: Mapped[str] = mapped_column(String(24), nullable=False) error_message: Mapped[str | None] = mapped_column(String(500)) started_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) finished_at: Mapped[datetime | None] = mapped_column(DateTime) created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) class OffsiteFieldCorrection(Base): """场外字段人工修正记录。 OCR 识别值与 NL2SQL 返回值都属于 Agent 产出,人工修正只能新增一条记录, 不覆盖原始值;修正前值、修正后值、变更字段和操作人全部留痕,便于审计对比。 """ __tablename__ = "offsite_field_correction" id: Mapped[int] = mapped_column( BigInteger().with_variant(Integer, "sqlite"), primary_key=True ) target_type: Mapped[str] = mapped_column(String(16), nullable=False) task_id: Mapped[str | None] = mapped_column(String(24)) mail_id: Mapped[str | None] = mapped_column(String(16)) attachment_id: Mapped[str | None] = mapped_column(String(24)) operator_id: Mapped[str] = mapped_column(String(64), nullable=False) original_fields: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) corrected_fields: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) changed_fields: Mapped[list[Any]] = mapped_column(JSON, nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) class OffsiteFundDocument(Base): __tablename__ = "offsite_fund_document" id: Mapped[int] = mapped_column(BigInteger, primary_key=True) task_id: Mapped[str] = mapped_column(String(24), unique=True, nullable=False) mail_id: Mapped[str] = mapped_column(String(16), nullable=False) attachment_id: Mapped[str] = mapped_column(String(24), nullable=False) document_type: Mapped[str] = mapped_column(String(24), nullable=False) fund_code: Mapped[str | None] = mapped_column(String(32)) fund_name: Mapped[str | None] = mapped_column(String(255)) account_identifier: Mapped[str | None] = mapped_column(String(128)) investor_name: Mapped[str | None] = mapped_column(String(128)) application_no: Mapped[str | None] = mapped_column(String(128)) application_date: Mapped[date | None] = mapped_column(Date) raw_application_date: Mapped[str | None] = mapped_column(String(64)) agency: Mapped[str | None] = mapped_column(String(128)) subscription_amount_yuan: Mapped[Decimal | None] = mapped_column(Numeric(20, 4)) redemption_shares: Mapped[Decimal | None] = mapped_column(Numeric(20, 4)) operator_decision: Mapped[str] = mapped_column(String(16), nullable=False, default="未处理") status: Mapped[str] = mapped_column(String(32), nullable=False, default="planned") created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) class OffsiteExecutionPlanTask(Base): __tablename__ = "offsite_execution_plan_task" id: Mapped[int] = mapped_column(BigInteger, primary_key=True) task_id: Mapped[str] = mapped_column(String(24), nullable=False) stage: Mapped[str] = mapped_column(String(16), nullable=False) rule_code: Mapped[str] = mapped_column(String(64), nullable=False) title: Mapped[str] = mapped_column(String(128), nullable=False) depends_on: Mapped[list[Any]] = mapped_column(JSON, nullable=False) input_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) output_json: Mapped[dict[str, Any] | None] = mapped_column(JSON) status: Mapped[str] = mapped_column(String(16), nullable=False, default="待执行") error_message: Mapped[str | None] = mapped_column(String(500)) created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) class OffsiteRuleResult(Base): __tablename__ = "offsite_rule_result" __table_args__ = ( UniqueConstraint("task_id", "rule_code", name="uk_offsite_rule_task"), ) id: Mapped[int] = mapped_column(BigInteger, primary_key=True) task_id: Mapped[str] = mapped_column(String(24), nullable=False) rule_code: Mapped[str] = mapped_column(String(64), nullable=False) rule_name: Mapped[str] = mapped_column(String(128), nullable=False) result: Mapped[str] = mapped_column(String(16), nullable=False) document_value: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) database_value: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) calculation: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) class OffsiteQueryRecord(Base): __tablename__ = "offsite_query_record" id: Mapped[int] = mapped_column(BigInteger, primary_key=True) task_id: Mapped[str] = mapped_column(String(24), nullable=False) rule_code: Mapped[str] = mapped_column(String(64), nullable=False) natural_language_request: Mapped[str] = mapped_column(Text, nullable=False) script_path: Mapped[str] = mapped_column(String(500), nullable=False) result_summary: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) status: Mapped[str] = mapped_column(String(24), nullable=False) error_message: Mapped[str | None] = mapped_column(String(500)) created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) class OffsiteNotification(Base): __tablename__ = "offsite_notification" id: Mapped[int] = mapped_column(BigInteger, primary_key=True) notification_type: Mapped[str] = mapped_column(String(32), nullable=False) business_key: Mapped[str] = mapped_column(String(64), nullable=False) receiver_id: Mapped[str] = mapped_column(String(64), nullable=False) operator_id: Mapped[str] = mapped_column(String(64), nullable=False) agent_draft: Mapped[str] = mapped_column(Text, nullable=False) final_content: Mapped[str] = mapped_column(Text, nullable=False) payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) status: Mapped[str] = mapped_column(String(16), nullable=False, default="待发送") retry_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) provider_message_id: Mapped[str | None] = mapped_column(String(128)) failure_reason: Mapped[str | None] = mapped_column(String(500)) created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) sent_at: Mapped[datetime | None] = mapped_column(DateTime) updated_at: Mapped[datetime | None] = mapped_column(DateTime)