"""Pydantic domain types โ€” the shapes business logic reads. ORM rows live in `store/models.py`; this file is the layer the Orchestrator / tools / API touch so we never leak SQLAlchemy types upward. Spec: docs/Specs.md ยง9. """ from __future__ import annotations from datetime import datetime # noqa: TC003 โ€” Pydantic field type needs runtime resolution from typing import Literal from pydantic import BaseModel, ConfigDict, Field UserRiskTier = Literal["new", "trusted", "neutral", "watched"] Personality = Literal["strict", "balanced", "lenient"] TierOverride = Literal["auto", "fast", "standard", "deep"] StrategyTier = Literal["FAST", "STANDARD", "DEEP"] ToolStatus = Literal["success", "failure", "skipped", "timeout"] InvestigationStatus = Literal["pending", "completed", "failed"] ModAction = Literal["REMOVE", "APPROVE", "ESCALATE", "LOCK"] FeedbackSource = Literal["verdict_card", "reddit_native"] class SubredditProfileRow(BaseModel): model_config = ConfigDict(from_attributes=True, extra="ignore") subreddit_id: str name: str personality: Personality = "balanced" rules: str = "" region: str = "Global" cold_start_count: int = 0 show_cost_in_dashboard: bool = False kill_switch: bool = False tier_override: TierOverride = "auto" class UserMemoryRow(BaseModel): model_config = ConfigDict(from_attributes=True, extra="ignore") subreddit_id: str user_id: str risk_tier: UserRiskTier = "new" prior_violations: int = 0 prior_approvals: int = 0 last_seen_at: datetime | None = None detail: dict[str, object] = Field(default_factory=dict) class ThreadMemoryRow(BaseModel): model_config = ConfigDict(from_attributes=True, extra="ignore") subreddit_id: str post_id: str mod_actions_taken: list[dict[str, object]] = Field(default_factory=list) participants_count: int = 0 last_summary: str = "" last_summary_at: datetime | None = None detail: dict[str, object] = Field(default_factory=dict) class StartInvestigationInput(BaseModel): """What `start_investigation` needs to create a pending row.""" model_config = ConfigDict(extra="forbid") correlation_id: str subreddit_id: str target_kind: Literal["comment", "post"] target_id: str target_body: str = "" target_author_id: str = "" tier: StrategyTier class FinalizeInvestigationInput(BaseModel): """Verdict fields written when the pipeline completes.""" model_config = ConfigDict(extra="forbid") risk_tier: Literal["HIGH", "MEDIUM", "LOW"] recommendation: Literal["REMOVE", "APPROVE", "ESCALATE", "LOCK", "NO_RECOMMENDATION"] calibrated_confidence: float = Field(ge=0.0, le=1.0) rationale: str confidence_breakdown: dict[str, float] model_reasoner: str model_summarizer: str cost_usd: float = Field(ge=0.0) latency_ms: int = Field(ge=0) input_tokens: int = 0 output_tokens: int = 0 validation_flag: bool = False degraded: bool = False cold_start: bool = False class EvidenceRowInput(BaseModel): """One Evidence Accumulator entry to persist.""" model_config = ConfigDict(extra="forbid") evidence_id: str = Field(pattern=r"^ev-\d+$") tool: str summary: str = "" detail: dict[str, object] = Field(default_factory=dict) status: ToolStatus = "success" latency_ms: int = Field(ge=0, default=0) class FeedbackInput(BaseModel): """Mod action alignment โ€” recorded by the verdict-card buttons + onModAction.""" model_config = ConfigDict(extra="forbid") correlation_id: str subreddit_id: str target_id: str mod_action: ModAction raw_action: str = "" moderator_id: str = "" moderator_name: str = "" source: FeedbackSource aligned: bool | None = None