Files
TJWaterServerBinary/app/domain/schemas/timeseries_history.py
T
jiang 682c26fddd
Generic Container CI/CD / test-build-publish (push) Successful in 2m10s
Server CI/CD v2 / build-test-publish-and-deploy (push) Successful in 2m10s
feat(timeseries): unify element history queries
2026-09-14 12:30:41 +08:00

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]