feat(projects): automate project infrastructure provisioning
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
import fcntl
|
||||
@@ -16,7 +17,7 @@ import wntr
|
||||
from app.algorithms.pressure_sensor_placement import kmeans_placement
|
||||
from app.algorithms.pressure_sensor_placement import sensitivity_placement
|
||||
from app.infra.db.postgresql import sensor_placement as sensor_placement_repository
|
||||
from app.native.wndb.inp.exporter import dump_inp
|
||||
from app.services.project_inp import temporary_project_inp
|
||||
|
||||
|
||||
class SensorPlacementNotFoundError(LookupError):
|
||||
@@ -31,7 +32,7 @@ class SensorPlacementConflictError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def _sensor_inp_path(project_code: str) -> Path:
|
||||
def _sensor_lock_path(project_code: str) -> Path:
|
||||
if (
|
||||
not project_code
|
||||
or project_code in {".", ".."}
|
||||
@@ -40,14 +41,13 @@ def _sensor_inp_path(project_code: str) -> Path:
|
||||
or "\x00" in project_code
|
||||
):
|
||||
raise SensorPlacementValidationError("管网名称不是有效的项目标识")
|
||||
return Path("db_inp") / f"{project_code}.db.inp"
|
||||
return Path("db_inp") / f"{project_code}.sensor.lock"
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _sensor_inp_lock(project_code: str):
|
||||
inp_path = _sensor_inp_path(project_code)
|
||||
inp_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
lock_path = inp_path.with_suffix(".sensor.lock")
|
||||
def _sensor_run_lock(project_code: str) -> Iterator[None]:
|
||||
lock_path = _sensor_lock_path(project_code)
|
||||
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with lock_path.open("w", encoding="utf-8") as lock_file:
|
||||
try:
|
||||
fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
@@ -56,11 +56,23 @@ def _sensor_inp_lock(project_code: str):
|
||||
"当前项目已有监测点优化任务正在运行,请稍后重试"
|
||||
) from exc
|
||||
try:
|
||||
yield inp_path
|
||||
yield
|
||||
finally:
|
||||
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _sensor_network_model(
|
||||
project_code: str,
|
||||
) -> Iterator[wntr.network.WaterNetworkModel]:
|
||||
with _sensor_run_lock(project_code):
|
||||
with temporary_project_inp(
|
||||
project_code,
|
||||
purpose="sensor-placement",
|
||||
) as inp_path:
|
||||
yield wntr.network.WaterNetworkModel(str(inp_path))
|
||||
|
||||
|
||||
def _create_validated_placement(
|
||||
project_code: str,
|
||||
*,
|
||||
@@ -88,10 +100,7 @@ def optimize_sensor_placement_by_sensitivity(
|
||||
) -> dict[str, Any]:
|
||||
"""Run sensitivity placement and persist the validated result."""
|
||||
|
||||
with _sensor_inp_lock(project_code):
|
||||
inp_path = _sensor_inp_path(project_code)
|
||||
dump_inp(project_code, str(inp_path), "2")
|
||||
network_model = wntr.network.WaterNetworkModel(str(inp_path))
|
||||
with _sensor_network_model(project_code) as network_model:
|
||||
sensor_locations = sensitivity_placement.optimize_sensor_placement(
|
||||
network_model,
|
||||
sensor_num=sensor_count,
|
||||
@@ -115,9 +124,7 @@ def optimize_sensor_placement_by_kmeans(
|
||||
) -> dict[str, Any]:
|
||||
"""Export the model, run K-means placement, and persist the result."""
|
||||
|
||||
with _sensor_inp_lock(project_code) as inp_path:
|
||||
dump_inp(project_code, str(inp_path), "2")
|
||||
network_model = wntr.network.WaterNetworkModel(str(inp_path))
|
||||
with _sensor_network_model(project_code) as network_model:
|
||||
sensor_locations = kmeans_placement.optimize_sensor_placement(
|
||||
network_model,
|
||||
sensor_count=sensor_count,
|
||||
|
||||
Reference in New Issue
Block a user