Files
bxh/app/contracts.py

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