refactor(db)!: adopt project-routed pooled databases
Reorganize WNDB by responsibility and remove legacy scheme endpoints.\n\nRoute analysis and time-series access through project pools, preserve transactional realtime replacement, and refresh GIS materialized views after writes.\n\nAdd database architecture documentation, live pooling coverage, API contract updates, and executable container verification.\n\nBREAKING CHANGE: legacy scheme APIs and flat app.native.wndb module imports are removed.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
from urllib.parse import quote
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path, Query, status
|
||||
from fastapi.responses import StreamingResponse
|
||||
@@ -26,8 +27,9 @@ from app.services.sensor_placement import (
|
||||
build_sensor_placement_workbook,
|
||||
can_edit_sensor_placement,
|
||||
get_sensor_placement_candidate,
|
||||
get_sensor_placement_scheme,
|
||||
update_sensor_placement_scheme,
|
||||
get_sensor_placement_run,
|
||||
list_sensor_placement_runs,
|
||||
update_sensor_placement_run,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
@@ -74,19 +76,19 @@ def _service_http_error(exc: Exception) -> HTTPException:
|
||||
)
|
||||
|
||||
|
||||
def _get_scheme_response(
|
||||
def _get_run_response(
|
||||
network: str,
|
||||
scheme_id: int,
|
||||
run_id: UUID,
|
||||
current_user: Any,
|
||||
project_context: ProjectContext,
|
||||
) -> dict[str, Any]:
|
||||
try:
|
||||
scheme = get_sensor_placement_scheme(network, scheme_id)
|
||||
run = get_sensor_placement_run(network, run_id)
|
||||
return {
|
||||
**scheme,
|
||||
**run,
|
||||
"can_edit": (
|
||||
_can_modify_project(project_context)
|
||||
and can_edit_sensor_placement(current_user, scheme)
|
||||
and can_edit_sensor_placement(current_user, run)
|
||||
),
|
||||
}
|
||||
except (
|
||||
@@ -116,7 +118,7 @@ async def get_sensor_placement_candidate_detail(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sensor-placement-optimization-runs",
|
||||
"/sensor-placement-runs",
|
||||
response_model=SensorPlacementSchemeResponse,
|
||||
summary="创建并返回监测点优化方案",
|
||||
)
|
||||
@@ -136,13 +138,13 @@ async def optimize_sensor_placement_scheme(
|
||||
created = await run_in_threadpool(
|
||||
optimizer,
|
||||
name=network,
|
||||
scheme_name=payload.scheme_name,
|
||||
scheme_name=payload.run_name,
|
||||
sensor_number=payload.sensor_count,
|
||||
min_diameter=payload.min_diameter,
|
||||
username=current_user.username,
|
||||
)
|
||||
scheme = get_sensor_placement_scheme(network, int(created["id"]))
|
||||
return {**scheme, "can_edit": True}
|
||||
run = get_sensor_placement_run(network, created["run_id"])
|
||||
return {**run, "can_edit": True}
|
||||
except (
|
||||
SensorPlacementConflictError,
|
||||
SensorPlacementValidationError,
|
||||
@@ -158,31 +160,45 @@ async def optimize_sensor_placement_scheme(
|
||||
|
||||
|
||||
@router.get(
|
||||
"/sensor-placement-schemes/{scheme_id}",
|
||||
"/sensor-placement-runs",
|
||||
response_model=list[SensorPlacementSchemeResponse],
|
||||
summary="获取监测点优化运行",
|
||||
)
|
||||
async def get_sensor_placement_runs(
|
||||
project_context: ProjectContext = Depends(get_project_context),
|
||||
) -> list[dict[str, Any]]:
|
||||
return await run_in_threadpool(
|
||||
list_sensor_placement_runs,
|
||||
project_context.project_code,
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/sensor-placement-runs/{run_id}",
|
||||
response_model=SensorPlacementSchemeResponse,
|
||||
summary="获取监测点方案详情",
|
||||
)
|
||||
async def get_sensor_placement_scheme_detail(
|
||||
scheme_id: int,
|
||||
async def get_sensor_placement_run_detail(
|
||||
run_id: UUID,
|
||||
network: str = Query(..., min_length=1),
|
||||
project_context: ProjectContext = Depends(get_project_context),
|
||||
current_user=Depends(get_current_metadata_user),
|
||||
) -> dict[str, Any]:
|
||||
return _get_scheme_response(
|
||||
return _get_run_response(
|
||||
_project_network(network, project_context),
|
||||
scheme_id,
|
||||
run_id,
|
||||
current_user,
|
||||
project_context,
|
||||
)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/sensor-placement-schemes/{scheme_id}",
|
||||
"/sensor-placement-runs/{run_id}",
|
||||
response_model=SensorPlacementSchemeResponse,
|
||||
summary="覆盖保存监测点方案",
|
||||
)
|
||||
async def overwrite_sensor_placement_scheme(
|
||||
scheme_id: int,
|
||||
async def overwrite_sensor_placement_run(
|
||||
run_id: UUID,
|
||||
payload: SensorPlacementUpdateRequest,
|
||||
network: str = Query(..., min_length=1),
|
||||
project_context: ProjectContext = Depends(get_project_context),
|
||||
@@ -190,21 +206,21 @@ async def overwrite_sensor_placement_scheme(
|
||||
) -> dict[str, Any]:
|
||||
network = _project_network(network, project_context)
|
||||
_require_project_write(project_context)
|
||||
scheme = _get_scheme_response(
|
||||
run = _get_run_response(
|
||||
network,
|
||||
scheme_id,
|
||||
run_id,
|
||||
current_user,
|
||||
project_context,
|
||||
)
|
||||
if not scheme["can_edit"]:
|
||||
raise HTTPException(status_code=403, detail="无权修改该监测点方案")
|
||||
if not run["can_edit"]:
|
||||
raise HTTPException(status_code=403, detail="无权修改该监测点优化运行")
|
||||
|
||||
try:
|
||||
updated = update_sensor_placement_scheme(
|
||||
updated = update_sensor_placement_run(
|
||||
network,
|
||||
scheme_id,
|
||||
expected_sensor_location=payload.expected_sensor_location,
|
||||
sensor_location=payload.sensor_location,
|
||||
run_id,
|
||||
expected_sensor_locations=payload.expected_sensor_locations,
|
||||
sensor_locations=payload.sensor_locations,
|
||||
)
|
||||
return {**updated, "can_edit": True}
|
||||
except (
|
||||
@@ -216,26 +232,26 @@ async def overwrite_sensor_placement_scheme(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sensor-placement-schemes/{scheme_id}/exports/excel",
|
||||
"/sensor-placement-runs/{run_id}/exports/excel",
|
||||
summary="导出监测点工程清单",
|
||||
)
|
||||
async def export_sensor_placement_excel(
|
||||
scheme_id: int,
|
||||
run_id: UUID,
|
||||
payload: SensorPlacementExportRequest,
|
||||
network: str = Query(..., min_length=1),
|
||||
project_context: ProjectContext = Depends(get_project_context),
|
||||
current_user=Depends(get_current_metadata_user),
|
||||
) -> StreamingResponse:
|
||||
network = _project_network(network, project_context)
|
||||
scheme = _get_scheme_response(
|
||||
run = _get_run_response(
|
||||
network,
|
||||
scheme_id,
|
||||
run_id,
|
||||
current_user,
|
||||
project_context,
|
||||
)
|
||||
if (
|
||||
payload.sensor_location != scheme["sensor_location"]
|
||||
and not scheme["can_edit"]
|
||||
payload.sensor_locations != run["sensor_locations"]
|
||||
and not run["can_edit"]
|
||||
):
|
||||
raise HTTPException(status_code=403, detail="无权导出该方案的未保存草稿")
|
||||
|
||||
@@ -243,14 +259,14 @@ async def export_sensor_placement_excel(
|
||||
workbook = await run_in_threadpool(
|
||||
build_sensor_placement_workbook,
|
||||
network=network,
|
||||
scheme=scheme,
|
||||
sensor_location=payload.sensor_location,
|
||||
scheme=run,
|
||||
sensor_location=payload.sensor_locations,
|
||||
adjustment_status=payload.adjustment_status,
|
||||
)
|
||||
except SensorPlacementValidationError as exc:
|
||||
raise _service_http_error(exc) from exc
|
||||
|
||||
filename = f"{scheme['scheme_name']}_监测点清单.xlsx"
|
||||
filename = f"{run['name']}_监测点清单.xlsx"
|
||||
encoded_filename = quote(filename)
|
||||
return StreamingResponse(
|
||||
workbook,
|
||||
|
||||
Reference in New Issue
Block a user