"""analyze_events 테이블 ORM — POST /documents/{id}/analyze 호출 관측 (Phase E.2) 목적: 분석 failure mode 분류 (timeout / parse / llm / missing_summary) + source 별 사용 패턴 (document_server / synology_chat / ui_search / ui_detail / eval). 단계 3 snapshot DB 설계 입력이 됨. """ from datetime import datetime from typing import Any from sqlalchemy import ARRAY, BigInteger, Boolean, DateTime, Float, ForeignKey, Integer, Text from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column from core.database import Base class AnalyzeEvent(Base): __tablename__ = "analyze_events" id: Mapped[int] = mapped_column(BigInteger, primary_key=True) doc_id: Mapped[int] = mapped_column( BigInteger, ForeignKey("documents.id", ondelete="CASCADE"), nullable=False ) user_id: Mapped[int | None] = mapped_column( BigInteger, ForeignKey("users.id", ondelete="SET NULL") ) mode: Mapped[str] = mapped_column(Text, default="quick", nullable=False) # quick / full / summary_triage / summary_deep / retrieval_select / synthesis text_limit: Mapped[int | None] = mapped_column(Integer) truncated: Mapped[bool] = mapped_column(Boolean, default=False) layers_returned: Mapped[list[Any] | None] = mapped_column(JSONB, default=list) cached: Mapped[bool] = mapped_column(Boolean, default=False) latency_ms: Mapped[int | None] = mapped_column(Integer) model_name: Mapped[str | None] = mapped_column(Text) prompt_version: Mapped[str | None] = mapped_column(Text) # None (success) | "timeout" | "llm" | "parse" | "missing_summary" | "no_text" error_code: Mapped[str | None] = mapped_column(Text) # document_server / synology_chat / ui_search / ui_detail / eval / unknown source: Mapped[str] = mapped_column(Text, default="document_server", nullable=False) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.now, nullable=False ) # PR-A (migration 153) — routing shadow observability subject_domain: Mapped[str | None] = mapped_column(Text) risk_flags: Mapped[list[str] | None] = mapped_column(ARRAY(Text)) high_impact_task: Mapped[bool | None] = mapped_column(Boolean) escalated_to_26b: Mapped[bool | None] = mapped_column(Boolean) escalation_reasons: Mapped[list[str] | None] = mapped_column(ARRAY(Text)) confidence: Mapped[float | None] = mapped_column(Float) policy_violation: Mapped[bool | None] = mapped_column(Boolean) policy_violation_ids: Mapped[list[str] | None] = mapped_column(ARRAY(Text)) shadow_would_route_to: Mapped[str | None] = mapped_column(Text) policy_version: Mapped[str | None] = mapped_column(Text) # PR-B (migration 159) — 실제 호출 tier 와 R2 backlog guard 이벤트 tier: Mapped[str | None] = mapped_column(Text) # 'triage' | 'primary' | 'fallback' suppressed_reason: Mapped[str | None] = mapped_column(Text) # 'backlog_guard(ratio=0.42,pending=7)'