feat(scada): add calibrated monitoring and incident recovery

This commit is contained in:
2026-11-07 19:44:58 +08:00
parent 26fa5d1909
commit ff8aba4964
109 changed files with 9165 additions and 629 deletions
+33 -3
View File
@@ -5,8 +5,10 @@ import re
from typing import Annotated, Literal
from uuid import UUID
from pydantic import Field, JsonValue, model_validator
from app.domain.schemas.answer_ui import ReportUiBlock, validate_report_ui_blocks
from pydantic import Field, JsonValue, model_serializer, model_validator
from app.domain.schemas.answer_ui import (
CHART_SAMPLE_TIME_PATTERN, ReportUiBlock, validate_chart_sample_time, validate_report_ui_blocks,
)
from app.domain.schemas.task_question import TaskQuestionRequest
from app.domain.schemas.shared import NonEmptyStr, PositiveInt, StrictBool, StrictModel
@@ -588,6 +590,7 @@ class OperationalTaskSummary(StrictModel):
execution_request_id: UUID
title: NonEmptyStr
summary: NonEmptyStr
handling_context: dict[str, JsonValue] = Field(default_factory=dict)
source: TaskSource
trigger: TaskTrigger
scheduled_for: WorkbenchDateTime | None
@@ -692,15 +695,42 @@ class AgentArtifactsResult(StrictModel):
class TaskSafeChartSeries(StrictModel):
name: Annotated[str, Field(max_length=128)]
data: Annotated[list[Annotated[float, Field(strict=True, allow_inf_nan=False)]], Field(min_length=1, max_length=100)]
type: Literal["line", "bar", "scatter"] | None = None
data: Annotated[list[Annotated[float, Field(strict=True, allow_inf_nan=False)] | None], Field(min_length=1, max_length=100)]
sample_times: Annotated[list[Annotated[str, Field(
strict=True, max_length=64,
pattern=CHART_SAMPLE_TIME_PATTERN,
)] | None], Field(min_length=1, max_length=100)] | None = None
@model_serializer(mode="wrap")
def serialize_optional_fields(self, handler):
return {key: value for key, value in handler(self).items() if value is not None}
@model_validator(mode="after")
def validate_sample_times(self):
if self.sample_times is not None:
if len(self.sample_times) != len(self.data):
raise ValueError("chart sample_times must align with data")
for value, at in zip(self.data, self.sample_times, strict=True):
if at is not None:
if value is None:
raise ValueError("chart sample_times require an observed value")
validate_chart_sample_time(at)
return self
class TaskSafeChart(StrictModel):
title: Annotated[str, Field(max_length=200)]
chart_type: Literal["line", "bar"]
x_axis_name: Annotated[str, Field(max_length=128)] | None = None
y_axis_name: Annotated[str, Field(max_length=128)] | None = None
x_data: Annotated[list[Annotated[str, Field(max_length=128)]], Field(min_length=1, max_length=100)]
series: Annotated[list[TaskSafeChartSeries], Field(min_length=1, max_length=8)]
@model_serializer(mode="wrap")
def serialize_optional_fields(self, handler):
return {key: value for key, value in handler(self).items() if value is not None}
@model_validator(mode="after")
def validate_alignment(self):
if any(len(series.data) != len(self.x_data) for series in self.series):