149 lines
4.9 KiB
Python
149 lines
4.9 KiB
Python
# [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] |