Files
TJWaterServerBinary/resources/db_v2/WNDB_STRUCTURE.md
T
jiang 5966d039de refactor(backend)!: separate algorithm and data layers
Reorganize algorithm packages by business responsibility, move orchestration into services, and keep database access behind pooled repositories.

Harden analysis API validation, remove unsafe legacy simulation endpoints, and add regression and architecture boundary coverage.

BREAKING CHANGE: legacy algorithm module paths and obsolete simulation endpoints are removed.
2026-09-04 17:30:55 +08:00

203 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WNDB 文件结构说明
本文记录 2026-08-25 完成的 WNDB 目录与命令结构重构。检查对象是当前代码,不以旧 SQL 脚本或历史目录为依据。
## 结论
当前结构适合继续维护。WNDB 已按连接基础设施、管网模型、GIS、INP 和命令执行分组,原来的编号文件名、根目录聚合门面和星号导入已经移除。WDA、SCADA 资产查询和测压点选址也已离开底层模型目录。
本次调整了代码文件、导入关系、命令分派、项目生命周期接口和 WNDB 内部命令对象。无状态服务不再发布“打开、关闭、是否打开项目”三个旧 HTTP 操作,数据库连接在请求中按需从池借用。历史撤销日志已从数据库中移除,内部接口不再保留无效的兼容字段。真实库回归时发现五张明细表错误地把局部顺序号设成全局主键,已在 `tjwater_v2` 中改为父对象 ID 与 `sequence_no` 的复合主键。
`tjwater_v2` 是 v2 业务库和时序库的正式物理库名。元数据库中的逻辑项目代码仍为 `tjwater_next`,由项目路由指向 `tjwater_v2`;两者不必同名。版本模板固定为 `tjwater_v2_template`,不按项目代码动态派生。目前该模板已从实际 v2 结构创建、清空项目数据、刷新空物化视图并封存,压缩后约 19 MB。
## 当前目录
```text
app/native/wndb/
├── __init__.py
├── core/
│ ├── connection.py
│ ├── database.py
│ ├── model_replace.py
│ └── projects.py
├── model/
│ ├── elements.py
│ ├── junctions.py
│ ├── reservoirs.py
│ ├── tanks.py
│ ├── pipes.py
│ ├── pumps.py
│ ├── valves.py
│ ├── patterns.py
│ ├── curves.py
│ ├── options.py
│ ├── options_v2.py
│ ├── options_v3.py
│ └── 其他 EPANET 模型模块
├── gis/
│ ├── coordinates.py
│ ├── vertices.py
│ ├── labels.py
│ ├── backdrop.py
│ ├── regions.py
│ ├── network_views.py
│ └── region_geometry.py
├── inp/
│ ├── sections.py
│ ├── importer.py
│ └── exporter.py
└── commands/
├── api.py
├── cascade.py
└── executor.py
```
目录内共有 49 个 Python 文件。`app/native/wndb/__init__.py` 只保留包说明,不再统一导出所有函数。调用方需要从具体职责模块导入,依赖来源可以直接从文件头确认。
## 各目录的职责
### core:连接、事务和数据库基础能力
`connection.py` 管理项目连接池、管理连接池和项目事务上下文。连接池按路由后的 DSN 复用,并限制缓存规模。
`database.py` 提供 `ChangeSet``DatabaseCommand`、参数化查询和物化视图刷新。`DatabaseCommand` 只保存待执行的 SQL 和执行成功后返回给调用方的变更列表,不再生成或保存撤销 SQL。模型直接修改时按需刷新视图;批量命令在外层事务提交后只刷新一次。物化视图保留模型坐标 `x``y`,同时将供 GeoServer 使用的 `geom` 转换为 `EPSG:3857`,WNDB 查询不会把发布坐标误当成模型坐标。
`projects.py` 只负责项目数据库的创建、复制、删除和异常安全的临时库上下文,不再保存“项目已打开”状态,也不混入模型查询。`postgres`、模板库、旧 WNDB 模板库 `project` 和元数据库均属于保护对象;模板复制源只能精确匹配 `WNDB_TEMPLATE_DB_NAME`,不能重新引入每项目 `_template`。批量清理不再扫描并删除服务器上的未知数据库,调用方必须显式提供每一个目标库名。数据库级 advisory lock 与 `datallowconn` 共同串行化多 worker 下的复制和删除。普通项目复制若复制源仍有其他会话会直接失败,不再主动终止正常请求。
`model_replace.py` 在源库可重复读快照中读取 `network``gis` 基表,并按外键拓扑顺序复制到目标业务库。替换在单一事务内完成,不再删除并重建整个业务库;普通模型修改和整体替换共用同一项目级事务锁。INP 替换时,`analysis.results` 保留历史记录,只有新模型中不存在的元素引用会置空,`asset.scada_devices` 保留仍能匹配新节点或管段的设备;临时分析库则从当前项目复制模型和有效 SCADA 映射。
### model:管网模型和仿真配置
`model` 按业务实体命名,不再使用 `s2_junctions.py` 这类 INP 章节编号。节点、连接、模式、曲线、需求、规则和仿真设置都能从文件名直接定位。
`options_v2.py``options_v3.py` 分别负责 EPANET V2、V3 的 `[OPTIONS]` 章节导入导出;数据库中的 `engine_version = 'legacy'` 仍表示 V2 配置,仅作为现有存储标识保留。
每个实体模块保留三类紧密相关的函数:读取实体、生成并执行实体变更、转换该实体对应的一行或一段 INP 内容。完整文件的读取顺序、事务和项目生命周期由 `inp` 目录负责。因此,实体级编解码仍靠近实体定义,跨章节编排已经集中。
`elements.py` 保存节点、连接、模式、曲线和区域的通用类型判断及拓扑查询。它不再承担业务算法。
### gis:空间数据和区域几何
`coordinates.py``vertices.py``labels.py``backdrop.py` 对应原始 GIS 数据。`regions.py` 负责区域持久化,`region_geometry.py` 负责边界、凸包、膨胀和区域内元素查询。`network_views.py` 是 GIS 查询视图的只读适配层,负责统一节点、链路、拓扑和需求投影的批量读取。坐标读取不会补写默认几何;缺少坐标时仅向调用方返回 `(0, 0)`,真实坐标的初始化仍由新增、导入或修复命令负责。
管网实体修改会调用坐标 SQL 辅助函数,区域几何也会读取管网拓扑。这里存在明确的模型与 GIS 协作,但没有模块导入环。现阶段继续拆出抽象接口只会增加层级,没有实际收益。
### GIS 统一查询视图
`tjwater_v2` 以现有 GIS 物化视图为基础增加了两个不保存重复数据的普通视图:
| 视图 | 来源 | 用途 |
| --- | --- | --- |
| `gis.network_nodes` | `gis.junctions``gis.reservoirs``gis.tanks` | 统一返回 `id`、模型坐标 `x/y``node_type` |
| `gis.network_links` | `gis.pipes``gis.pumps``gis.valves` | 统一返回 `id`、起止节点和 `link_type` |
普通视图始终读取底层物化视图当前内容,不需要单独刷新。管网修改提交后仍由 `gis.refresh_all_materialized_views` 刷新节点、管道、泵、阀门等物化视图,两个统一视图会随之得到最新结果。视图和全部字段均已在数据库中写入说明。
旧服务先读取节点或链路 ID,再逐个查询坐标、类型和端点,会对完整管网产生数万次往返。当前节点坐标、主干节点、全部链路、主干管道、区域边界链路、区域拓扑和用水量汇总均改为视图批量查询。没有调用方的 `get_nodes_in_extent``get_links_in_extent`,以及仅服务于旧聚合过程的四个模型辅助函数已删除;单元素详情接口仍保留原始模型查询。
### inp:文件级导入导出
`sections.py` 只保存 INP 章节名称和输出顺序。旧文件中混放的 `s1_title``s2_junction` 等命令类型常量已经移除。
`importer.py` 负责文件分段、导入顺序、项目事务、版本转换和导入后的物化视图刷新。INP 更新先从 `tjwater_v2_template` 创建唯一暂存库并完成解析,再在当前业务库中事务替换模型表。模型提交后即使暂存库清理失败也仍会刷新物化视图;清理失败会记录日志,不再遮蔽主操作。ChangeSet 导入使用每请求唯一临时文件并在 `finally` 删除,避免同项目并发导入互相覆盖。`exporter.py` 负责按 EPANET 版本组织各章节并写出文件或 `ChangeSet`
### commands:批量修改和级联关系
`api.py` 为级联删除和选项同步补齐命令元数据。`cascade.py` 将删除节点、连接、模式和曲线的请求展开为完整的关联修改。`executor.py` 在一个项目事务中执行展开后的命令。
元素命令分派已经由类型到处理函数的显式注册表实现。新增元素时,只需把受支持的新增、修改或删除处理函数登记到对应注册表。没有处理函数的命令保持空操作,行为与改造前一致。
### 命令执行与事务
```mermaid
flowchart LR
REQUEST[ChangeSet 请求]
EXPAND[展开级联修改]
BUILD[实体模块生成 DatabaseCommand]
EXECUTE[执行 SQL]
RESULT[返回 ChangeSet]
COMMIT[提交项目事务]
REFRESH[刷新物化视图]
REQUEST --> EXPAND --> BUILD --> EXECUTE --> RESULT
EXECUTE --> COMMIT --> REFRESH
```
实体模块根据请求生成 `DatabaseCommand`,其中 `sql` 是要执行的语句,`changes` 是成功后的变更结果。批量命令先在同一个项目事务中展开级联关系,再依次执行 SQL;任何写入步骤失败都会回滚整个事务。事务提交后统一刷新物化视图,避免一次批量修改触发多次刷新。直接调用单个实体修改时,如果当前没有外层项目事务,则由 `execute_command` 完成提交并按影响范围刷新视图。
物化视图采用并发刷新,因此刷新位于模型事务提交之后。若刷新失败,模型修改已经持久化,代码会抛出 `MaterializedViewRefreshAfterCommitError`。HTTP 层返回专用 Problem Details、`503``X-TJWater-Changes-Committed: true`,明确提示不能盲目重放原始写入。
旧实现中的 `DbChangeSet` 同时保存执行和撤销两套 SQL、两套变更结果,但数据库已经不再提供 operation 或 snapshot 撤销日志,这些字段没有消费者。当前代码已经删除 `undo_sql``undo_cs` 及各实体模块中的撤销 SQL 构造,也删除了只为撤销结果读取旧记录的查询和辅助方法。局部修改仍会读取一次当前记录,用于补齐请求中未提供的字段;这类读取属于更新语义,不是撤销机制。
## WNDB 之外的业务代码
以下代码不再放在 `app/native/wndb`
| 职责 | 当前路径 | 原因 |
| --- | --- | --- |
| 用水量分配计算 | `app/algorithms/demand_allocation/` | 只接收普通拓扑数据;数据库装载位于 `app/services/demand_allocation.py` |
| SCADA 资产查询 | `app/infra/db/postgresql/scada.py` | 对应 `asset.scada_devices` 的 PostgreSQL 仓储 |
| 测压点选址结果 | `app/infra/db/postgresql/sensor_placement.py` | 对应 `analysis.runs``analysis.results` 的仓储 |
| 网络 API 门面 | `app/services/tjnetwork.py` | 暂时组合 WNDB 与 EPANET 供 HTTP 接口调用;算法层不再依赖该门面 |
`tjnetwork.py` 从约 995 行缩减到约 285 行。它不再通过 WNDB 根包获得全部函数,只显式导入当前接口使用的能力。过时的 `scripts/test_tjnetwork.py` 依赖已移除的 operation、snapshot、DMA 和旧 SCADA API,已经一并删除。
### HTTP 执行边界
WNDB 当前使用同步 `psycopg` 连接池。`network/``components/` 和同步 EPANET 仿真接口统一声明为同步处理函数;公开 REST 路由的异步适配器把这些函数送入线程池,并把项目路由上下文传入工作线程,不会在事件循环线程上阻塞数据库或求解器。异步业务库和时序库访问使用带借用计数和代际切换的项目池:活跃旧池不会被 LRU 淘汰或强制关闭,配置变化后新请求立即使用新池,旧池在已有借用归还后关闭。元数据库保持独立 SQLAlchemy 异步池。
临时分析库先由固定 `tjwater_v2_template` 提供结构,再从当前项目的一致性快照复制 `network/gis` 模型与 SCADA 映射并刷新物化视图。V3→V2 格式转换不需要项目模型,单独使用空模板临时库。旧 `online_Analysis.py`、restore 和 open/close 项目脚本已经删除,不再保留每项目模板与 operation 恢复入口。
## 依赖方向
```mermaid
flowchart TD
API[HTTP 接口]
SERVICE[应用服务]
ALGORITHM[业务算法]
REPOSITORY[PostgreSQL 业务仓储]
COMMANDS[wndb.commands]
INP[wndb.inp]
MODEL[wndb.model]
GIS[wndb.gis]
CORE[wndb.core]
DB[(项目业务数据库)]
API --> SERVICE
API --> REPOSITORY
SERVICE --> ALGORITHM
SERVICE --> COMMANDS
SERVICE --> INP
SERVICE --> MODEL
SERVICE --> GIS
SERVICE --> REPOSITORY
COMMANDS --> MODEL
COMMANDS --> GIS
COMMANDS --> CORE
INP --> MODEL
INP --> GIS
INP --> CORE
MODEL --> CORE
GIS --> CORE
MODEL --> GIS
GIS --> MODEL
CORE --> DB
REPOSITORY --> DB
```
WNDB 根包不再作为依赖汇聚点。上层若只需要管道查询,应直接依赖 `model.pipes`;需要区域几何时依赖 `gis.region_geometry`;需要项目连接时依赖 `core.connection`
`app/algorithms` 已按业务能力改为 `burst_detection``burst_localization``scada_cleaning``pipe_health_prediction``valve_isolation``dma_leakage_estimation``pressure_sensor_placement``demand_allocation`。自动化架构测试禁止算法层反向依赖 service、infra、native 或 API,也禁止 infra 依赖 service/algorithm。少量只做仓储转发的 HTTP 接口仍直接依赖 repository,后续按业务聚合需要迁入应用服务,不为机械包装而增加空壳层。
## 仍需留意的文件规模
`importer.py``region_geometry.py` 行数较多,但函数仍围绕单一职责。只有在继续增加 INP 格式或区域算法时,才需要分别拆出版本转换器或边界算法模块。目前没有必要为了控制文件行数继续分层。
## 验证结果
- 本地 conda 环境单元、鉴权和 API 测试:313 项通过,2 项按条件跳过。
- 一次性实库从 `tjwater_v2_template` 创建后,通过 INP 暂存解析和事务替换得到 11 个节点、13 条连接、11 条坐标及 9 条 junction 物化视图记录,验证后已完整删除。
- `tjwater_v2` 统一视图覆盖 87,907 个节点和 91,054 条链路,与六个来源物化视图的合计数量一致。实测完整节点读取约 0.17 秒、完整链路读取约 0.10 秒、完整拓扑两次批量查询约 1.12 秒;耗时仅作为当前环境基线,不作为固定性能承诺。
- `tjwater_v2` 真实数据库测试:11 项通过,覆盖业务库和时序库并发借用、失效连接自动重建、临时库模型/SCADA/视图完整克隆与清理、嵌套事务回滚、分析运行生命周期、恶意标识符转义、明细表复合主键、统一 GIS 查询视图,以及 WNDB pattern 增删改、级联解除需求关联和整体回滚。
- Python 编译、未使用导入扫描、撤销字段残留扫描和 `git diff --check` 均通过。