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
+28 -1
View File
@@ -5,6 +5,8 @@ from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field, model_validator
from app.domain.schemas.project_settings import ScadaMonitoringSettings
class ReplayModel(BaseModel):
model_config = ConfigDict(extra="forbid", allow_inf_nan=False)
@@ -78,7 +80,26 @@ class ScadaReplayCreate(ReplayModel):
class ScadaReplayCommand(ReplayModel):
request_id: UUID
expected_revision: int = Field(ge=1)
action: Literal["step", "stop", "retry"]
action: Literal["step", "monitor", "stop", "retry"]
at_time: datetime | None = None
@model_validator(mode="after")
def monitoring_time(self):
if self.action == "monitor":
if self.at_time is None or self.at_time.tzinfo is None:
raise ValueError("选定时刻监测需要提供带时区的 at_time")
elif self.at_time is not None:
raise ValueError("只有选定时刻监测可以指定 at_time")
return self
class ScadaMonitoringIncident(ReplayModel):
incident_id: UUID
status: Literal["active", "resolved"]
action: Literal["raised", "ongoing", "resolved"]
started_at: datetime
last_observed_at: datetime
recovery_count: int = Field(ge=0, le=2)
class ScadaMonitoringResult(ReplayModel):
@@ -94,7 +115,10 @@ class ScadaMonitoringResult(ReplayModel):
pressure: dict
outlet_flow: dict
burst_detection: dict
weighted: dict | None = None
incident: ScadaMonitoringIncident | None = None
role_gaps: list[str] = Field(default_factory=list)
anomaly_signals: list[dict] = Field(default_factory=list)
class ScadaReplaySnapshot(ReplayModel):
@@ -108,6 +132,8 @@ class ScadaReplaySnapshot(ReplayModel):
step_pending: bool = False
monitoring_count: int = 0
last_monitoring: ScadaMonitoringResult | None = None
monitoring_config: ScadaMonitoringSettings | None = None
monitoring_calibration: dict | None = None
virtual_now: datetime
delivered_until: datetime | None = None
processed_until: datetime | None = None
@@ -129,6 +155,7 @@ class ScadaReplayCapabilities(ReplayModel):
monitoring_interval_seconds: int | None = Field(None, ge=60, le=31_536_000)
burst_monitoring_ready: bool = False
burst_monitoring_reason: str | None = None
monitoring_config: ScadaMonitoringSettings | None = None
current: ScadaReplaySnapshot | None = None