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.
132 lines
3.8 KiB
Python
132 lines
3.8 KiB
Python
from contextlib import contextmanager
|
|
import fcntl
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from app.algorithms.sensor import kmeans as kmeans_sensor
|
|
from app.algorithms.sensor import sensitivity
|
|
from app.native.wndb.s42_sensor_placement import create_sensor_placement
|
|
from app.services.sensor_placement import (
|
|
SensorPlacementConflictError,
|
|
SensorPlacementValidationError,
|
|
validate_sensor_placement_nodes,
|
|
)
|
|
from app.services.tjnetwork import dump_inp
|
|
|
|
|
|
def _sensor_inp_path(name: str) -> Path:
|
|
if (
|
|
not name
|
|
or name in {".", ".."}
|
|
or "/" in name
|
|
or "\\" in name
|
|
or "\x00" in name
|
|
):
|
|
raise SensorPlacementValidationError("管网名称不是有效的项目标识")
|
|
return Path("db_inp") / f"{name}.db.inp"
|
|
|
|
|
|
@contextmanager
|
|
def _sensor_inp_lock(name: str):
|
|
inp_path = _sensor_inp_path(name)
|
|
inp_path.parent.mkdir(parents=True, exist_ok=True)
|
|
lock_path = inp_path.with_suffix(".sensor.lock")
|
|
with lock_path.open("w", encoding="utf-8") as lock_file:
|
|
try:
|
|
fcntl.flock(
|
|
lock_file.fileno(),
|
|
fcntl.LOCK_EX | fcntl.LOCK_NB,
|
|
)
|
|
except BlockingIOError as exc:
|
|
raise SensorPlacementConflictError(
|
|
"当前项目已有监测点优化任务正在运行,请稍后重试"
|
|
) from exc
|
|
try:
|
|
yield inp_path
|
|
finally:
|
|
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
|
|
|
|
|
|
def _create_validated_placement(
|
|
name: str,
|
|
*,
|
|
scheme_name: str,
|
|
min_diameter: int,
|
|
username: str,
|
|
sensor_location: list[str],
|
|
) -> dict[str, Any]:
|
|
validate_sensor_placement_nodes(name, sensor_location)
|
|
return create_sensor_placement(
|
|
name,
|
|
scheme_name=scheme_name,
|
|
min_diameter=min_diameter,
|
|
username=username,
|
|
sensor_location=sensor_location,
|
|
)
|
|
|
|
|
|
def pressure_sensor_placement_sensitivity(
|
|
name: str,
|
|
scheme_name: str,
|
|
sensor_number: int,
|
|
min_diameter: int,
|
|
username: str,
|
|
) -> dict[str, Any]:
|
|
"""
|
|
基于改进灵敏度法进行压力监测点优化布置
|
|
:param name: 数据库名称
|
|
:param scheme_name: 监测优化布置方案名称
|
|
:param sensor_number: 传感器数目
|
|
:param min_diameter: 最小管径
|
|
:param username: 用户名
|
|
:return: 新建的监测点方案
|
|
"""
|
|
with _sensor_inp_lock(name):
|
|
sensor_location = sensitivity.get_ID(
|
|
name=name,
|
|
sensor_num=sensor_number,
|
|
min_diameter=min_diameter,
|
|
)
|
|
return _create_validated_placement(
|
|
name,
|
|
scheme_name=scheme_name,
|
|
min_diameter=min_diameter,
|
|
username=username,
|
|
sensor_location=sensor_location,
|
|
)
|
|
|
|
|
|
# 2025/08/21
|
|
# 基于kmeans聚类法进行压力监测点优化布置
|
|
def pressure_sensor_placement_kmeans(
|
|
name: str,
|
|
scheme_name: str,
|
|
sensor_number: int,
|
|
min_diameter: int,
|
|
username: str,
|
|
) -> dict[str, Any]:
|
|
"""
|
|
基于聚类法进行压力监测点优化布置
|
|
:param name: 数据库名称(注意,此处数据库名称也是inp文件名称,inp文件与pg库名要一样)
|
|
:param scheme_name: 监测优化布置方案名称
|
|
:param sensor_number: 传感器数目
|
|
:param min_diameter: 最小管径
|
|
:param username: 用户名
|
|
:return: 新建的监测点方案
|
|
"""
|
|
# dump_inp
|
|
with _sensor_inp_lock(name) as inp_path:
|
|
dump_inp(name, str(inp_path), "2")
|
|
sensor_location = kmeans_sensor.kmeans_sensor_placement(
|
|
name=name,
|
|
sensor_num=sensor_number,
|
|
min_diameter=min_diameter,
|
|
)
|
|
return _create_validated_placement(
|
|
name,
|
|
scheme_name=scheme_name,
|
|
min_diameter=min_diameter,
|
|
username=username,
|
|
sensor_location=sensor_location,
|
|
)
|