diff --git a/BACKEND_NAMING_AUDIT.md b/BACKEND_NAMING_AUDIT.md new file mode 100644 index 0000000..dc5572b --- /dev/null +++ b/BACKEND_NAMING_AUDIT.md @@ -0,0 +1,77 @@ +# Backend Naming Audit + +DOC-003 audit for the internal `TJWaterServerBinary` backend. + +## Scope + +Reviewed FastAPI route decorators under `app/api/v1/endpoints`, router prefixes in `app/api/v1/router.py`, and public request/response schema fields in `app/api` and `app/domain`. + +The backend is mounted only under `/api/v1` from `app/main.py`; the old no-prefix router include remains commented out. + +## Current Good Surface + +These newer routes already follow the naming rule for public HTTP paths: + +- Metadata/admin: `/api/v1/admin/projects`, `/api/v1/admin/users/sync`, `/api/v1/admin/projects/{project_id}/members` +- Audit: `/api/v1/audit/logs`, `/api/v1/audit/logs/count` +- Agent auth: `/api/v1/agent/auth/context` +- Business APIs: `/api/v1/burst-detection/detect`, `/api/v1/burst-location/locate`, `/api/v1/leakage/identify` +- Time-series APIs: `/api/v1/scada/by-ids-time-range`, `/api/v1/scada/by-ids-field-time-range`, `/api/v1/composite/clean-scada` +- Project data APIs: `/api/v1/scada-info`, `/api/v1/scheme-list`, `/api/v1/burst-locate-result` +- Web integrations: `/api/v1/web-search`, `/api/v1/geocode` + +Path template parameters such as `{project_id}`, `{user_id}`, `{device_id}`, `{scheme_name}`, and `{link_id}` intentionally remain `snake_case`. + +## Legacy URL Categories + +### Keep With Compatibility + +These now have `kebab-case` aliases. The frontend has been migrated to the replacement paths; keep the old paths as deprecated compatibility aliases for Agent planning, tests, customer scripts, or external callers: + +| Current URL | Suggested replacement | +| --- | --- | +| `/api/v1/openproject/` | `/api/v1/projects/open` | +| `/api/v1/project_info/` | `/api/v1/project-info` | +| `/api/v1/getallschemes/` | `/api/v1/schemes` | +| `/api/v1/getallsensorplacements/` | `/api/v1/sensor-placement-schemes` | +| `/api/v1/sensorplacementscheme/create` | `/api/v1/sensor-placement-schemes` | +| `/api/v1/burst_analysis/` | `/api/v1/burst-analysis` | +| `/api/v1/valve_isolation_analysis/` | `/api/v1/valve-isolation-analysis` | +| `/api/v1/flushing_analysis/` | `/api/v1/flushing-analysis` | +| `/api/v1/contaminant_simulation/` | `/api/v1/contaminant-simulation` | +| `/api/v1/runsimulationmanuallybydate/` | `/api/v1/simulations/run-by-date` | + +### Broad Legacy Surface + +These route groups expose many command-style concatenated paths. They should not be copied into new work; replace only when a caller migration is planned: + +- Project lifecycle: `listprojects`, `createproject`, `deleteproject`, `isprojectopen`, `closeproject`, `copyproject`, `importinp`, `exportinp`, `readinp`, `dumpinp`, `lockproject`, `unlockproject` +- Network object CRUD: `addjunction`, `getjunctionelevation`, `setpipediameter`, `getvalvesetting`, and similar junction/pipe/pump/tank/reservoir/valve routes +- Region/DMA/VD commands: `calculatedistrictmeteringareaforregion`, `getdistrictmeteringarea`, `generatevirtualdistrict`, and related routes +- SCADA native CRUD: `getscadadevice`, `setscadadevicedata`, `cleanscadaelement`, and related routes +- Snapshot/cache utilities: `takesnapshotforoperation`, `syncwithserver`, `clearrediskey`, `queryredis` +- Advanced simulation endpoints with underscore paths: `pressure_regulation`, `daily_scheduling_analysis`, `network_update`, `pressure_sensor_placement_kmeans` + +### Direct Cleanup Candidates + +These are likely safe only after confirming no caller uses them: + +- `/api/v1/test_dict/`: development/test utility in `misc.py`. +- `/api/v1/takenapshotforcurrentoperation`: typo compatibility path; keep deprecated if any client may still call it. +- `/api/v1/getpumpenergyproperties//` and `/api/v1/setpumpenergyproperties//`: double-slash paths in options endpoints. + +## Field Naming + +Most public JSON, query, and SSE fields are already `snake_case`, including `project_id`, `user_id`, `scheme_name`, `scheme_type`, `start_time`, `end_time`, `device_ids`, `session_id`, and `request_id`. + +Known legacy exception: + +- `BurstAnalysis.burst_ID` in `app/api/v1/endpoints/simulation.py` should become `burst_id` on a new API contract. Preserve `burst_ID` only for the legacy body shape. + +Headers keep standard HTTP casing: + +- `X-Project-Id` + +## Recommendation + +Do not rename existing legacy routes in place. For each active legacy route, keep the new `kebab-case` alias as the documented path, keep the old route marked deprecated, migrate remaining Agent/customer/script callers, then remove only after a documented compatibility window. diff --git a/app/api/v1/endpoints/misc.py b/app/api/v1/endpoints/misc.py index 248032f..5500268 100644 --- a/app/api/v1/endpoints/misc.py +++ b/app/api/v1/endpoints/misc.py @@ -28,7 +28,8 @@ async def fastapi_get_json(): ) -@router.get("/getallsensorplacements/", summary="获取所有传感器位置", description="获取网络中所有传感器的放置位置信息") +@router.get("/sensor-placement-schemes", summary="获取所有传感器位置", description="获取网络中所有传感器的放置位置信息") +@router.get("/getallsensorplacements/", summary="获取所有传感器位置(旧路径)", description="获取网络中所有传感器的放置位置信息", deprecated=True) async def fastapi_get_all_sensor_placements(network: str = Query(..., description="管网名称(或数据库名称)")) -> list[dict[Any, Any]]: """ 获取所有传感器位置 diff --git a/app/api/v1/endpoints/project.py b/app/api/v1/endpoints/project.py index 3b8cc45..c4424ba 100644 --- a/app/api/v1/endpoints/project.py +++ b/app/api/v1/endpoints/project.py @@ -42,7 +42,8 @@ inpDir = "data/" # Assuming data directory exists or is defined somewhere. router = APIRouter() lockedPrjs: Dict[str, str] = {} -@router.get("/project_info/", summary="获取项目信息", description="从数据库获取项目的详细信息,包括地图范围等。", response_model=ProjectMetaResponse) +@router.get("/project-info", summary="获取项目信息", description="从数据库获取项目的详细信息,包括地图范围等。", response_model=ProjectMetaResponse) +@router.get("/project_info/", summary="获取项目信息(旧路径)", description="从数据库获取项目的详细信息,包括地图范围等。", response_model=ProjectMetaResponse, deprecated=True) async def get_project_info_endpoint( network: str = Query(..., description="管网名称(或项目代码)"), metadata_repo: MetadataRepository = Depends(get_metadata_repository), @@ -121,7 +122,8 @@ async def is_project_open_endpoint( """ return is_project_open(network) -@router.post("/openproject/", summary="打开项目", description="将指定项目加载到内存中,并初始化数据库连接池。") +@router.post("/projects/open", summary="打开项目", description="将指定项目加载到内存中,并初始化数据库连接池。") +@router.post("/openproject/", summary="打开项目(旧路径)", description="将指定项目加载到内存中,并初始化数据库连接池。", deprecated=True) async def open_project_endpoint( network: str = Query(..., description="管网名称(或数据库名称)") ): diff --git a/app/api/v1/endpoints/schemes.py b/app/api/v1/endpoints/schemes.py index 3b650e4..a08746e 100644 --- a/app/api/v1/endpoints/schemes.py +++ b/app/api/v1/endpoints/schemes.py @@ -22,7 +22,8 @@ async def fastapi_get_scheme(network: str = Query(..., description="管网名称 """ return get_scheme(network, schema_name) -@router.get("/getallschemes/", summary="获取所有方案", description="获取指定网络的所有方案信息") +@router.get("/schemes", summary="获取所有方案", description="获取指定网络的所有方案信息") +@router.get("/getallschemes/", summary="获取所有方案(旧路径)", description="获取指定网络的所有方案信息", deprecated=True) async def fastapi_get_all_schemes(network: str = Query(..., description="管网名称(或数据库名称)")) -> list[dict[Any, Any]]: """ 获取所有方案列表 diff --git a/app/api/v1/endpoints/simulation.py b/app/api/v1/endpoints/simulation.py index 27be343..c8ad1f4 100644 --- a/app/api/v1/endpoints/simulation.py +++ b/app/api/v1/endpoints/simulation.py @@ -188,7 +188,8 @@ async def dump_output_endpoint(output: str = Query(..., description="模拟输 # Analysis Endpoints -@router.get("/burst_analysis/", summary="爆管分析(高级)", description="高级版本的爆管分析,支持在指定时间点修改泵控制模式和阀门开度,以分析这些改变对爆管影响的作用。支持固定泵和变速泵的独立控制。") +@router.get("/burst-analysis", summary="爆管分析(高级)", description="高级版本的爆管分析,支持在指定时间点修改泵控制模式和阀门开度,以分析这些改变对爆管影响的作用。支持固定泵和变速泵的独立控制。") +@router.get("/burst_analysis/", summary="爆管分析(高级,旧路径)", description="高级版本的爆管分析,支持在指定时间点修改泵控制模式和阀门开度,以分析这些改变对爆管影响的作用。支持固定泵和变速泵的独立控制。", deprecated=True) async def fastapi_burst_analysis( network: str = Query(..., description="管网名称(或数据库名称)"), modify_pattern_start_time: str = Query(..., description="模式修改开始时间(ISO 8601格式)"), @@ -249,7 +250,8 @@ async def fastapi_valve_close_analysis( return result or "success" -@router.get("/valve_isolation_analysis/", summary="阀门隔离分析", description="分析当发生突发事件时,通过关闭指定阀门进行隔离,确定哪些阀门必须关闭、哪些可选关闭,以及隔离的可行性。") +@router.get("/valve-isolation-analysis", summary="阀门隔离分析", description="分析当发生突发事件时,通过关闭指定阀门进行隔离,确定哪些阀门必须关闭、哪些可选关闭,以及隔离的可行性。") +@router.get("/valve_isolation_analysis/", summary="阀门隔离分析(旧路径)", description="分析当发生突发事件时,通过关闭指定阀门进行隔离,确定哪些阀门必须关闭、哪些可选关闭,以及隔离的可行性。", deprecated=True) async def valve_isolation_endpoint( network: str = Query(..., description="管网名称(或数据库名称)"), accident_element: List[str] = Query(..., description="发生事故的管段/节点ID列表"), @@ -289,7 +291,8 @@ async def valve_isolation_endpoint( return result -@router.get("/flushing_analysis/", response_class=PlainTextResponse, summary="冲洗分析(高级)", description="高级版本的冲洗分析,支持同时开启多个阀门进行冲洗,指定排污节点,并设置固定的冲洗流量。返回纯文本格式的分析结果。") +@router.get("/flushing-analysis", response_class=PlainTextResponse, summary="冲洗分析(高级)", description="高级版本的冲洗分析,支持同时开启多个阀门进行冲洗,指定排污节点,并设置固定的冲洗流量。返回纯文本格式的分析结果。") +@router.get("/flushing_analysis/", response_class=PlainTextResponse, summary="冲洗分析(高级,旧路径)", description="高级版本的冲洗分析,支持同时开启多个阀门进行冲洗,指定排污节点,并设置固定的冲洗流量。返回纯文本格式的分析结果。", deprecated=True) async def fastapi_flushing_analysis( network: str = Query(..., description="管网名称(或数据库名称)"), start_time: str = Query(..., description="冲洗开始时间(ISO 8601格式)"), @@ -329,7 +332,8 @@ async def fastapi_flushing_analysis( return result or "success" -@router.get("/contaminant_simulation/", response_class=PlainTextResponse, summary="污染物模拟", description="对管网中的污染物扩散进行模拟,评估污染源对管网的影响范围和浓度分布。支持指定污染源位置、污染浓度和扩散模式。") +@router.get("/contaminant-simulation", response_class=PlainTextResponse, summary="污染物模拟", description="对管网中的污染物扩散进行模拟,评估污染源对管网的影响范围和浓度分布。支持指定污染源位置、污染浓度和扩散模式。") +@router.get("/contaminant_simulation/", response_class=PlainTextResponse, summary="污染物模拟(旧路径)", description="对管网中的污染物扩散进行模拟,评估污染源对管网的影响范围和浓度分布。支持指定污染源位置、污染浓度和扩散模式。", deprecated=True) async def fastapi_contaminant_simulation( network: str = Query(..., description="管网名称(或数据库名称)"), start_time: str = Query(..., description="污染开始时间(ISO 8601格式)"), @@ -715,7 +719,8 @@ async def fastapi_pressure_sensor_placement_kmeans( ) -@router.post("/sensorplacementscheme/create", summary="传感器放置方案创建", description="创建新的传感器放置方案,支持灵敏度分析和KMeans聚类两种方法。根据指定的方法自动计算最优的传感器放置位置。") +@router.post("/sensor-placement-schemes", summary="传感器放置方案创建", description="创建新的传感器放置方案,支持灵敏度分析和KMeans聚类两种方法。根据指定的方法自动计算最优的传感器放置位置。") +@router.post("/sensorplacementscheme/create", summary="传感器放置方案创建(旧路径)", description="创建新的传感器放置方案,支持灵敏度分析和KMeans聚类两种方法。根据指定的方法自动计算最优的传感器放置位置。", deprecated=True) async def fastapi_pressure_sensor_placement( network: str = Query(..., description="管网名称(或数据库名称)"), scheme_name: str = Query(..., description="放置方案名称"), @@ -763,7 +768,8 @@ async def fastapi_pressure_sensor_placement( return "success" -@router.post("/runsimulationmanuallybydate/", summary="手动运行日期指定模拟", description="根据指定的开始时间和持续时间,手动运行水力模拟。开始时间必须是显式带时区的 ISO 8601 / RFC3339 时间。") +@router.post("/simulations/run-by-date", summary="手动运行日期指定模拟", description="根据指定的开始时间和持续时间,手动运行水力模拟。开始时间必须是显式带时区的 ISO 8601 / RFC3339 时间。") +@router.post("/runsimulationmanuallybydate/", summary="手动运行日期指定模拟(旧路径)", description="根据指定的开始时间和持续时间,手动运行水力模拟。开始时间必须是显式带时区的 ISO 8601 / RFC3339 时间。", deprecated=True) async def fastapi_run_simulation_manually_by_date( data: RunSimulationManuallyByDate = Body(..., description="模拟运行参数"), ) -> dict[str, str]: