"""Pydantic request/response models.""" from __future__ import annotations from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field class _Base(BaseModel): model_config = ConfigDict(from_attributes=True) # ── Auth ───────────────────────────────────────────────────────────────────── class LoginRequest(BaseModel): username: str password: str class TokenResponse(BaseModel): access_token: str token_type: str = "bearer" # ── Source Profiles ────────────────────────────────────────────────────────── class SourceProfileCreate(BaseModel): source_code: str source_name: str source_type: Literal["manual", "python_crawler", "api", "csv", "external_system"] description: str | None = None api_endpoint: str | None = None auth_method: str | None = None update_frequency: Literal["daily", "weekly", "monthly", "on_demand"] | None = None authority_level: int = Field(default=3, ge=1, le=5) enabled: bool = True metadata_jsonb: dict = Field(default_factory=dict) class SourceProfileUpdate(BaseModel): source_name: str | None = None description: str | None = None authority_level: int | None = Field(default=None, ge=1, le=5) enabled: bool | None = None update_frequency: str | None = None # ── Question Traces ────────────────────────────────────────────────────────── class QuestionTraceCreate(BaseModel): question_text: str source: Literal["real", "simulated"] = "real" origin: str | None = None user_session: str | None = None asked_at: datetime | None = None class QuestionTraceBatch(BaseModel): questions: list[QuestionTraceCreate] class SimulationQuestion(BaseModel): question_text: str scenario_tags: list[str] = Field(default_factory=list) enabled: bool = True class SimulationQuestionUpdate(BaseModel): question_text: str | None = None scenario_tags: list[str] | None = None enabled: bool | None = None # ── Field Decisions ────────────────────────────────────────────────────────── class FieldDecisionUpdate(BaseModel): field_decisions: dict[str, str] note: str | None = None # ── Acquisition Tasks ──────────────────────────────────────────────────────── class AcquisitionTaskCreate(BaseModel): title: str description: str | None = None scenario_tags: list[str] = Field(default_factory=list) target_entity_types: list[str] = Field(default_factory=list) target_fields: list[str] = Field(default_factory=list) suggested_collection_method: str | None = None priority: int = Field(default=3, ge=1, le=5) due_at: datetime | None = None class TaskFromGap(BaseModel): trace_id: int title: str | None = None priority: int = Field(default=3, ge=1, le=5) class TaskAssign(BaseModel): assignee: str class TaskComplete(BaseModel): result_summary: str | None = None # ── Inventory ──────────────────────────────────────────────────────────────── class IssueResolve(BaseModel): resolution_note: str | None = None # ── Vocabulary ─────────────────────────────────────────────────────────────── class VocabularyTermCreate(BaseModel): entity_type: str canonical_name: str aliases: list[str] = Field(default_factory=list) forbidden_aliases: list[str] = Field(default_factory=list) notes: str | None = None class VocabularyTermUpdate(BaseModel): canonical_name: str | None = None aliases: list[str] | None = None forbidden_aliases: list[str] | None = None notes: str | None = None # ── Conflicts ──────────────────────────────────────────────────────────────── class ConflictResolve(BaseModel): resolution: str chosen_value: Any | None = None note: str | None = None # ── Aligner ────────────────────────────────────────────────────────────────── class AlignSuggestRequest(BaseModel): candidate_ids: list[int] class MergeEntities(BaseModel): note: str | None = None # ── Rollback ───────────────────────────────────────────────────────────────── class RollbackRequest(BaseModel): reason: str | None = None # ── RBAC & Accounts (P1) ───────────────────────────────────────────────────── class RoleCreate(BaseModel): role_key: str = Field(pattern=r"^[a-z][a-z0-9_]{1,30}$") label: str description: str | None = None sort_order: int | None = None class RoleUpdate(BaseModel): label: str | None = None description: str | None = None sort_order: int | None = None class CapabilityCreate(BaseModel): cap_key: str = Field(pattern=r"^[a-z][a-z0-9_]{1,40}$") label: str sort_order: int | None = None class RoleCapCell(BaseModel): role_key: str cap_key: str value: str class UserCreate(BaseModel): username: str = Field(min_length=3, max_length=120) password: str = Field(min_length=4, max_length=72) full_name: str | None = None phone: str | None = None status: str | None = None roles: list[str] = [] class UserUpdate(BaseModel): full_name: str | None = None phone: str | None = None status: str | None = None password: str | None = Field(default=None, min_length=4, max_length=72) roles: list[str] | None = None class AreaUpdate(BaseModel): name: str | None = None note: str | None = None responsible_user_id: int | None = None class UserAreasSet(BaseModel): area_ids: list[str] = [] # existing area ids picked from the tree custom_areas: list[str] = [] # free-text area names typed by admin