# [DEF:backend.src.models.report:Module] # @TIER: CRITICAL # @SEMANTICS: reports, models, pydantic, normalization, pagination # @PURPOSE: Canonical report schemas for unified task reporting across heterogeneous task types. # @LAYER: Domain # @RELATION: DEPENDS_ON -> backend.src.core.task_manager.models # @INVARIANT: Canonical report fields are always present for every report item. # [SECTION: IMPORTS] from datetime import datetime from enum import Enum from typing import Any, Dict, List, Optional from pydantic import BaseModel, Field, field_validator, model_validator # [/SECTION] # [DEF:TaskType:Class] # @TIER: CRITICAL # @INVARIANT: Must contain valid generic task type mappings. # @SEMANTICS: enum, type, task # @PURPOSE: Supported normalized task report types. class TaskType(str, Enum): LLM_VERIFICATION = "llm_verification" BACKUP = "backup" MIGRATION = "migration" DOCUMENTATION = "documentation" UNKNOWN = "unknown" # [/DEF:TaskType:Class] # [DEF:ReportStatus:Class] # @TIER: CRITICAL # @INVARIANT: TaskStatus enum mapping logic holds. # @SEMANTICS: enum, status, task # @PURPOSE: Supported normalized report status values. class ReportStatus(str, Enum): SUCCESS = "success" FAILED = "failed" IN_PROGRESS = "in_progress" PARTIAL = "partial" # [/DEF:ReportStatus:Class] # [DEF:ErrorContext:Class] # @TIER: CRITICAL # @INVARIANT: The properties accurately describe error state. # @SEMANTICS: error, context, payload # @PURPOSE: Error and recovery context for failed/partial reports. class ErrorContext(BaseModel): code: Optional[str] = None message: str next_actions: List[str] = Field(default_factory=list) # [/DEF:ErrorContext:Class] # [DEF:TaskReport:Class] # @TIER: CRITICAL # @INVARIANT: Must represent canonical task record attributes. # @SEMANTICS: report, model, summary # @PURPOSE: Canonical normalized report envelope for one task execution. class TaskReport(BaseModel): report_id: str task_id: str task_type: TaskType status: ReportStatus started_at: Optional[datetime] = None updated_at: datetime summary: str details: Optional[Dict[str, Any]] = None error_context: Optional[ErrorContext] = None source_ref: Optional[Dict[str, Any]] = None @field_validator("report_id", "task_id", "summary") @classmethod def _non_empty_str(cls, value: str) -> str: if not isinstance(value, str) or not value.strip(): raise ValueError("Value must be a non-empty string") return value.strip() # [/DEF:TaskReport:Class] # [DEF:ReportQuery:Class] # @TIER: CRITICAL # @INVARIANT: Time and pagination queries are mutually consistent. # @SEMANTICS: query, filter, search # @PURPOSE: Query object for server-side report filtering, sorting, and pagination. class ReportQuery(BaseModel): page: int = Field(default=1, ge=1) page_size: int = Field(default=20, ge=1, le=100) task_types: List[TaskType] = Field(default_factory=list) statuses: List[ReportStatus] = Field(default_factory=list) time_from: Optional[datetime] = None time_to: Optional[datetime] = None search: Optional[str] = Field(default=None, max_length=200) sort_by: str = Field(default="updated_at") sort_order: str = Field(default="desc") @field_validator("sort_by") @classmethod def _validate_sort_by(cls, value: str) -> str: allowed = {"updated_at", "status", "task_type"} if value not in allowed: raise ValueError(f"sort_by must be one of: {', '.join(sorted(allowed))}") return value @field_validator("sort_order") @classmethod def _validate_sort_order(cls, value: str) -> str: if value not in {"asc", "desc"}: raise ValueError("sort_order must be 'asc' or 'desc'") return value @model_validator(mode="after") def _validate_time_range(self): if self.time_from and self.time_to and self.time_from > self.time_to: raise ValueError("time_from must be less than or equal to time_to") return self # [/DEF:ReportQuery:Class] # [DEF:ReportCollection:Class] # @TIER: CRITICAL # @INVARIANT: Represents paginated data correctly. # @SEMANTICS: collection, pagination # @PURPOSE: Paginated collection of normalized task reports. class ReportCollection(BaseModel): items: List[TaskReport] total: int = Field(ge=0) page: int = Field(ge=1) page_size: int = Field(ge=1) has_next: bool applied_filters: ReportQuery # [/DEF:ReportCollection:Class] # [DEF:ReportDetailView:Class] # @TIER: CRITICAL # @INVARIANT: Incorporates a report and logs correctly. # @SEMANTICS: view, detail, logs # @PURPOSE: Detailed report representation including diagnostics and recovery actions. class ReportDetailView(BaseModel): report: TaskReport timeline: List[Dict[str, Any]] = Field(default_factory=list) diagnostics: Optional[Dict[str, Any]] = None next_actions: List[str] = Field(default_factory=list) # [/DEF:ReportDetailView:Class] # [/DEF:backend.src.models.report:Module]