210 lines
6.4 KiB
Python
210 lines
6.4 KiB
Python
"""Typed read-only projections consumed by the supply workbench."""
|
|
|
|
from typing import Annotated, Literal
|
|
|
|
from pydantic import AwareDatetime, BeforeValidator, JsonValue, model_validator
|
|
|
|
from app.domain.schemas.shared import (
|
|
NonEmptyStr,
|
|
NonNegativeInt,
|
|
PositiveInt,
|
|
StrictBool,
|
|
StrictModel,
|
|
)
|
|
|
|
|
|
def _workbench_datetime_wire_value(value: object):
|
|
from datetime import datetime
|
|
|
|
if not isinstance(value, (datetime, str)):
|
|
raise ValueError("datetime input must be an RFC3339 string or datetime instance")
|
|
return value
|
|
|
|
|
|
WorkbenchDateTime = Annotated[
|
|
AwareDatetime, BeforeValidator(_workbench_datetime_wire_value)
|
|
]
|
|
ProjectionAvailability = Literal["ready", "not_configured", "degraded"]
|
|
|
|
|
|
class WorkbenchRuntimeCondition(StrictModel):
|
|
id: NonEmptyStr
|
|
task_id: Literal["backend-schedule"]
|
|
session_id: NonEmptyStr
|
|
title: NonEmptyStr
|
|
summary: NonEmptyStr
|
|
status: Literal["pending", "running", "completed", "warning", "error"]
|
|
risk_level: Literal["normal", "attention", "critical"]
|
|
scheduled_at: WorkbenchDateTime
|
|
updated_at: WorkbenchDateTime
|
|
detail: NonEmptyStr
|
|
recommendation: NonEmptyStr | None
|
|
evidence: list[NonEmptyStr]
|
|
model_name: Literal["TJWater scheduler"]
|
|
|
|
|
|
class WorkbenchAlertSource(StrictModel):
|
|
task_type: Literal["scheduled_task", "analysis_task", "other_task"]
|
|
task_id: NonEmptyStr
|
|
run_id: NonEmptyStr
|
|
|
|
|
|
class WorkbenchRuntimeAlert(StrictModel):
|
|
id: NonEmptyStr
|
|
condition_id: NonEmptyStr | None
|
|
source: WorkbenchAlertSource
|
|
title: NonEmptyStr
|
|
description: NonEmptyStr
|
|
occurred_at: WorkbenchDateTime
|
|
severity: Literal["warning", "critical"]
|
|
|
|
|
|
class WorkbenchPersistedAlert(StrictModel):
|
|
alert_id: NonEmptyStr
|
|
condition_id: NonEmptyStr | None
|
|
source: WorkbenchAlertSource
|
|
title: NonEmptyStr
|
|
description: NonEmptyStr
|
|
occurred_at: WorkbenchDateTime
|
|
severity: Literal["warning", "critical"]
|
|
status: Literal["active", "acknowledged", "resolved"]
|
|
producer_id: NonEmptyStr | None = None
|
|
updated_at: WorkbenchDateTime | None = None
|
|
|
|
|
|
class WorkbenchRuntimeScenario(StrictModel):
|
|
id: NonEmptyStr
|
|
run_id: NonEmptyStr
|
|
name: NonEmptyStr
|
|
description: NonEmptyStr
|
|
status: Literal["draft", "active", "review"]
|
|
run_type: NonEmptyStr
|
|
run_type_label: NonEmptyStr
|
|
run_status: NonEmptyStr
|
|
created_at: WorkbenchDateTime
|
|
|
|
|
|
class WorkbenchRuntimeAnalysisFact(StrictModel):
|
|
label: NonEmptyStr
|
|
value: NonEmptyStr
|
|
|
|
|
|
class WorkbenchRuntimeAnalysisResult(StrictModel):
|
|
result_type: NonEmptyStr
|
|
created_at: WorkbenchDateTime
|
|
facts: list[WorkbenchRuntimeAnalysisFact]
|
|
|
|
|
|
class WorkbenchRuntimeLatestAnalysis(StrictModel):
|
|
run_id: NonEmptyStr
|
|
name: NonEmptyStr
|
|
run_type: NonEmptyStr
|
|
run_type_label: NonEmptyStr
|
|
status: NonEmptyStr
|
|
status_label: NonEmptyStr
|
|
created_by: NonEmptyStr
|
|
created_at: WorkbenchDateTime
|
|
started_at: WorkbenchDateTime
|
|
results: list[WorkbenchRuntimeAnalysisResult]
|
|
map_payload: dict[str, JsonValue] | None
|
|
|
|
|
|
class WorkbenchAnalysisHistoryItem(StrictModel):
|
|
run_id: NonEmptyStr
|
|
name: NonEmptyStr
|
|
run_type: NonEmptyStr
|
|
run_type_label: NonEmptyStr
|
|
status: NonEmptyStr
|
|
status_label: NonEmptyStr
|
|
created_by: NonEmptyStr
|
|
created_at: WorkbenchDateTime
|
|
started_at: WorkbenchDateTime
|
|
has_results: StrictBool
|
|
|
|
|
|
class WorkbenchAnalysisHistoryPage(StrictModel):
|
|
items: list[WorkbenchAnalysisHistoryItem]
|
|
limit: PositiveInt
|
|
offset: NonNegativeInt
|
|
next_offset: NonNegativeInt | None
|
|
|
|
|
|
class WorkbenchRuntimeWorkOrderStage(StrictModel):
|
|
id: NonEmptyStr
|
|
sequence: PositiveInt
|
|
title: NonEmptyStr
|
|
status: Literal["done", "current", "pending", "blocked", "skipped"]
|
|
owner: NonEmptyStr
|
|
time_label: NonEmptyStr
|
|
description: NonEmptyStr
|
|
|
|
|
|
class WorkbenchRuntimeWorkOrderSubtask(StrictModel):
|
|
id: NonEmptyStr
|
|
title: NonEmptyStr
|
|
summary: NonEmptyStr
|
|
status: Literal["pending", "running", "blocked", "completed", "cancelled"]
|
|
scheduled_at: WorkbenchDateTime
|
|
location: NonEmptyStr
|
|
assignee: NonEmptyStr
|
|
duration_minutes: PositiveInt
|
|
reply_window_minutes: PositiveInt
|
|
stages: list[WorkbenchRuntimeWorkOrderStage]
|
|
reply_requirements: list[NonEmptyStr]
|
|
|
|
@model_validator(mode="after")
|
|
def validate_stages(self):
|
|
if not self.stages:
|
|
raise ValueError("subtask stages must not be empty")
|
|
if [stage.sequence for stage in self.stages] != list(
|
|
range(1, len(self.stages) + 1)
|
|
):
|
|
raise ValueError("subtask stages must use contiguous sequence numbers")
|
|
if len({stage.id for stage in self.stages}) != len(self.stages):
|
|
raise ValueError("subtask stage ids must be unique")
|
|
if sum(stage.status == "current" for stage in self.stages) > 1:
|
|
raise ValueError("subtask stages may contain at most one current stage")
|
|
return self
|
|
|
|
|
|
class WorkbenchRuntimeWorkOrder(StrictModel):
|
|
id: NonEmptyStr
|
|
code: NonEmptyStr
|
|
scheduled_at: WorkbenchDateTime
|
|
title: NonEmptyStr
|
|
summary: NonEmptyStr
|
|
status: Literal["pending", "running", "awaiting_reply", "completed"]
|
|
risk_level: Literal["normal", "attention", "critical"]
|
|
updated_at: WorkbenchDateTime
|
|
detail: NonEmptyStr
|
|
recommendation: NonEmptyStr | None
|
|
duration_minutes: PositiveInt
|
|
source: NonEmptyStr
|
|
dispatcher: NonEmptyStr
|
|
priority: Literal["normal", "urgent"]
|
|
subtasks: list[WorkbenchRuntimeWorkOrderSubtask]
|
|
|
|
@model_validator(mode="after")
|
|
def validate_subtasks(self):
|
|
if not self.subtasks:
|
|
raise ValueError("work order subtasks must not be empty")
|
|
if len({subtask.id for subtask in self.subtasks}) != len(self.subtasks):
|
|
raise ValueError("work order subtask ids must be unique")
|
|
return self
|
|
|
|
|
|
class WorkbenchRuntimeSnapshot(StrictModel):
|
|
availability: ProjectionAvailability
|
|
source: Literal["none", "scheduled_tasks"]
|
|
scenario_availability: ProjectionAvailability
|
|
artifact_availability: ProjectionAvailability
|
|
schedule_availability: ProjectionAvailability
|
|
work_order_availability: ProjectionAvailability
|
|
alert_availability: ProjectionAvailability
|
|
generated_at: WorkbenchDateTime
|
|
conditions: list[WorkbenchRuntimeCondition]
|
|
alerts: list[WorkbenchRuntimeAlert]
|
|
scenarios: list[WorkbenchRuntimeScenario]
|
|
latest_analysis: WorkbenchRuntimeLatestAnalysis | None
|
|
work_orders: list[WorkbenchRuntimeWorkOrder]
|