209 lines
6.8 KiB
Python
209 lines
6.8 KiB
Python
"""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
|