89 lines
2.7 KiB
Python
89 lines
2.7 KiB
Python
from datetime import datetime
|
|
from enum import StrEnum
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
|
|
|
|
class HistoryElementType(StrEnum):
|
|
PIPE = "pipe"
|
|
JUNCTION = "junction"
|
|
|
|
|
|
class HistoryMode(StrEnum):
|
|
OBSERVED = "observed"
|
|
REALTIME_COMPARISON = "realtime_comparison"
|
|
ANALYSIS_COMPARISON = "analysis_comparison"
|
|
|
|
|
|
class HistoryMetric(StrEnum):
|
|
FLOW = "flow"
|
|
PRESSURE = "pressure"
|
|
|
|
|
|
class HistorySource(StrEnum):
|
|
SCADA_RAW = "scada_raw"
|
|
SCADA_CLEANED = "scada_cleaned"
|
|
REALTIME_SIMULATION = "realtime_simulation"
|
|
ANALYSIS_SIMULATION = "analysis_simulation"
|
|
|
|
|
|
class ElementHistoryTarget(BaseModel):
|
|
element_id: str = Field(min_length=1, max_length=128)
|
|
element_type: HistoryElementType
|
|
device_ids: list[str] | None = Field(default=None, max_length=100)
|
|
|
|
@field_validator("element_id")
|
|
@classmethod
|
|
def normalize_element_id(cls, value: str) -> str:
|
|
return value.strip()
|
|
|
|
@field_validator("device_ids")
|
|
@classmethod
|
|
def normalize_device_ids(cls, value: list[str] | None) -> list[str] | None:
|
|
if value is None:
|
|
return None
|
|
normalized = list(dict.fromkeys(item.strip() for item in value if item.strip()))
|
|
if not normalized:
|
|
raise ValueError("device_ids must contain at least one device")
|
|
return normalized
|
|
|
|
|
|
class ElementHistoryQuery(BaseModel):
|
|
start_time: datetime
|
|
end_time: datetime
|
|
mode: HistoryMode = HistoryMode.OBSERVED
|
|
run_id: UUID | None = None
|
|
elements: list[ElementHistoryTarget] = Field(min_length=1, max_length=200)
|
|
|
|
@model_validator(mode="after")
|
|
def validate_query(self) -> "ElementHistoryQuery":
|
|
if self.start_time >= self.end_time:
|
|
raise ValueError("start_time must be earlier than end_time")
|
|
if self.mode == HistoryMode.ANALYSIS_COMPARISON and self.run_id is None:
|
|
raise ValueError("run_id is required for analysis_comparison")
|
|
if self.mode != HistoryMode.ANALYSIS_COMPARISON and self.run_id is not None:
|
|
raise ValueError("run_id is only valid for analysis_comparison")
|
|
return self
|
|
|
|
|
|
class HistoryPoint(BaseModel):
|
|
time: datetime
|
|
value: float | None
|
|
|
|
|
|
class ElementHistorySeries(BaseModel):
|
|
element_id: str
|
|
element_type: HistoryElementType
|
|
device_id: str | None = None
|
|
metric: HistoryMetric
|
|
source: HistorySource
|
|
source_unit: str = Field(description="Unit used by the returned point values")
|
|
display_unit: str = Field(description="Recommended UI display unit")
|
|
unit_inferred: bool = False
|
|
points: list[HistoryPoint]
|
|
|
|
|
|
class ElementHistoryResponse(BaseModel):
|
|
series: list[ElementHistorySeries]
|