Files
TJWaterServerBinary/app/domain/schemas/sensor_placement.py
T
jiang 3fbb17bb30 fix(sensor-placement): enforce project write boundaries
Bind every scheme request to ProjectContext, keep viewer access read-only, reject concurrent optimization jobs without blocking worker threads, and cap export/update payload sizes. Run optimization and workbook work off the event loop.
2026-07-30 16:21:38 +08:00

89 lines
2.5 KiB
Python

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,
max_length=200,
)
sensor_location: list[str] = Field(..., min_length=1, max_length=200)
@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, max_length=200)
adjustment_status: dict[str, AdjustmentStatus] = Field(
default_factory=dict,
max_length=200,
)
@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