from datetime import datetime from typing import Literal from pydantic import BaseModel, Field, field_validator AdjustmentStatus = Literal["current", "original", "added", "replaced"] def _normalize_location_ids(value: list[str]) -> list[str]: normalized = [str(item).strip() for item in value] if any(not item for item in normalized): raise ValueError("sensor locations cannot contain blank node IDs") if len(set(normalized)) != len(normalized): raise ValueError("sensor locations cannot contain duplicate node IDs") return normalized class SensorPlacementOptimizeRequest(BaseModel): network: str = Field( ..., min_length=1, max_length=63, pattern=r"^[^/\\\x00]+$", ) scheme_name: str = Field(..., min_length=1, max_length=32) sensor_type: Literal["pressure"] method: Literal["sensitivity", "kmeans"] sensor_count: int = Field(..., gt=0, le=200) min_diameter: int = Field(default=0, ge=0) @field_validator("network") @classmethod def validate_network(cls, value: str) -> str: normalized = value.strip() if normalized in {".", ".."}: raise ValueError("network must be a project identifier") return normalized class SensorPlacementUpdateRequest(BaseModel): expected_sensor_location: list[str] = Field(..., min_length=1) sensor_location: list[str] = Field(..., min_length=1) @field_validator("expected_sensor_location", "sensor_location") @classmethod def validate_locations(cls, value: list[str]) -> list[str]: return _normalize_location_ids(value) class SensorPlacementExportRequest(BaseModel): sensor_location: list[str] = Field(..., min_length=1) adjustment_status: dict[str, AdjustmentStatus] = Field(default_factory=dict) @field_validator("sensor_location") @classmethod def validate_locations(cls, value: list[str]) -> list[str]: return _normalize_location_ids(value) class SensorPointResponse(BaseModel): node_id: str project_x: float project_y: float map_x: float map_y: float longitude: float latitude: float elevation: float class SensorPlacementSchemeResponse(BaseModel): id: int scheme_name: str sensor_number: int min_diameter: int username: str create_time: datetime sensor_location: list[str] sensor_points: list[SensorPointResponse] can_edit: bool = False