import uuid from datetime import datetime, timedelta from typing import Dict, List, Optional, Any from pydantic import BaseModel, Field, validator from sqlalchemy import String, Text, func from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship from sqlalchemy.sql import delete class Base(DeclarativeBase): pass class Metadata(BaseModel): authority_id: str = Field(..., description="Authority ID for routing") reference_number: str = Field(..., description="Aktenzeichen/Kassenzeichen") doc_type: str = Field(..., enum=["NOTICE", "COURT_ORDER", "GENERAL_INQUIRY"]) issued_at: datetime @validator('doc_type') def validate_doc_type(cls, v): if v not in ["NOTICE", "COURT_ORDER", "GENERAL_INQUIRY"]: raise ValueError("Invalid document type") return v class DocumentEnvelope(BaseModel): id: Optional[str] = Field(default_factory=lambda: str(uuid.uuid4()), read_only=True) metadata: Metadata encrypted_payload: str = Field(..., description="Base64 E2E Encrypted PDF") status: str = Field(default="RECEIVED", enum=["RECEIVED", "ROUTED", "ASSIGNED", "CLOSED"], read_only=True) retention_date: Optional[datetime] = None class ThreadType(BaseModel): type: str = Field(..., enum=["TEXT_CHAT", "CALLBACK_REQUEST", "APPOINTMENT"]) initial_message: Optional[str] = None class InteractionThread(BaseModel): id: Optional[str] = Field(default_factory=lambda: str(uuid.uuid4()), read_only=True) document_id: str type: str = Field(..., enum=["TEXT_CHAT", "CALLBACK_REQUEST", "APPOINTMENT"]) status: str = Field(default="OPEN", enum=["OPEN", "PENDING_OFFICIAL", "PENDING_CITIZEN", "RESOLVED"]) assigned_official_id: Optional[str] = None class Message(BaseModel): id: Optional[str] = Field(default_factory=lambda: str(uuid.uuid4()), read_only=True) thread_id: str sender_role: str = Field(..., enum=["CITIZEN", "OFFICIAL"]) content: str = Field(..., description="Encrypted message content") timestamp: datetime = Field(default_factory=datetime.now) class ExportRequest(BaseModel): case_id: str target_system: str = "eAkte-Standard-V2" include_attachments: bool = True class ExportJob(BaseModel): job_id: str status: str = "QUEUED" # DB Models class Document(Base): __tablename__ = "documents" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) reference_number: Mapped[str] = mapped_column(String(50), index=True) authority_id: Mapped[str] = mapped_column(String(50), index=True) doc_type: Mapped[str] = mapped_column(String(20)) status: Mapped[str] = mapped_column(String(20), default="RECEIVED") storage_path: Mapped[str] = mapped_column(String(255)) # S3 key issued_at: Mapped[datetime] retention_date: Mapped[Optional[datetime]] threads: relationship("Thread", back_populates="document") class Thread(Base): __tablename__ = "threads" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) document_id: Mapped[str] = mapped_column(String(36), index=True) type: Mapped[str] = mapped_column(String(20)) status: Mapped[str] = mapped_column(String(20), default="OPEN") assigned_official_id: Mapped[Optional[str]] = mapped_column(String(100)) document: relationship("Document", back_populates="threads") messages: relationship("Message", back_populates="thread") class Message(Base): __tablename__ = "messages" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) thread_id: Mapped[str] = mapped_column(String(36), index=True) sender_role: Mapped[str] = mapped_column(String(20)) content: Mapped[str] = mapped_column(Text) # Encrypted timestamp: Mapped[datetime] = mapped_column(default=func.now()) thread: relationship("Thread", back_populates="messages")