This commit is contained in:
qsc
2026-07-12 02:42:44 +08:00
parent 711205c059
commit dd588b5daa
+123
View File
@@ -0,0 +1,123 @@
# 污水厂智能控制平台 — 一致性决策基线
> 本文件是 `spec-数据管理.md`、`spec-历史数据.md` 和 `code-standards.md` 的跨文档最高优先级契约。出现歧义时,先按本文件执行,再修订对应文档。
## 1. 文档优先级与变更规则
1. 本文件中的“已采纳”决策优先于其他文档中的旧表述。
2. 任何 API、数据库或运行架构变更,必须同步修改三份关联文档和自动化契约测试。
3. 文档示例必须是合法 JSON、SQL、Go 或 TypeScript,不得在 JSON 代码块中写注释。
4. API 冻结后,破坏性变更必须升级 API 版本或提供兼容迁移期。
## 2. 已采纳决策
### ADR-001|运行模型与模块边界
- 生产环境使用单进程嵌入式模型:`cmd/server/main.go` 同时启动 Web API 和采集引擎。
- 模块间通过公开的 Service/领域接口通信,不通过本应用自身的 HTTP,也不得直接访问其他模块的 Repository、ORM 模型或数据库表。
- Handler 只负责参数绑定和响应转换;运行时状态、元数据聚合和权限判断均由 Service 完成。
### ADR-002|协议驱动与设备连接
- 协议注册表保存无状态 `ProtocolDriverFactory`,不保存有连接状态的驱动单例。
- 每个设备由工厂创建独立 `ProtocolConnection`;采集和写入共享该设备连接。
- 同一设备连接默认串行执行协议读写,除非驱动明确声明并通过并发测试证明支持并发。
- 采集调度采用有界 worker pool;“每点位一个任务”不等于“每点位一个 goroutine”。
### ADR-003UUID 与 TDengine 子表
- API、PostgreSQL、TDengine 普通字段和 TAG 中统一使用带连字符的 36 位标准 UUID。
- TDengine 子表名唯一格式为 `p_<uuid32>`,正则为 `^p_[0-9a-f]{32}$`
- 请求不得传入表名;表名只能由服务端对已验证 UUID 规范化后派生。
### ADR-004|动态 SQL 标识符
- 用户值必须参数化。
- 动态表名属于受控标识符例外:必须由服务端派生、通过固定正则白名单,并使用驱动提供的标识符引用能力;禁止直接拼接任何用户字符串。
### ADR-005|历史点位生命周期
- 历史模块默认展示活跃点位和仍有历史数据的归档点位。
- 活跃点位:点位和设备均未删除、均启用,且 `store_history=true`
- 归档点位:点位或设备已禁用/逻辑删除,或当前 `store_history=false`,但 TDengine 中仍存在历史数据。
- 归档点位只读、可查询、不可继续采集。
### ADR-006|历史元数据与配置变更
- `collection_points` 增加 `unit``valid_min``valid_max``history_started_at`
- 首次准备写入历史数据前设置 `history_started_at`;一旦非空,禁止修改 `device_id``data_type`。需要改变时必须新建点位。
- 点位名称、设备名称、分组和单位可以修改;历史 API 使用 PostgreSQL 当前元数据展示。
- TDengine 中的 `point_name``device_name``data_type` 是写入时快照,不是当前配置的权威来源。
### ADR-007|质量和空值模型
- API 质量枚举:`good | bad | none`
- TDengine 只存储 `good=0``bad=1``none` 表示查询无匹配记录,不落库。
- `value` 允许为 `null`:读取失败、断线或解析失败时写入 `quality=bad``value=null`
- 读取成功但超出配置有效范围时写入 `quality=bad` 且保留数值。
- TDengine 和 API 增加可空 `quality_reason`,使用稳定代码,例如 `timeout``disconnected``parse_error``out_of_range`
- 表格无匹配记录统一返回 `{value:null, quality:"none", quality_reason:null, matched_ts:null}`,不得返回整个元素为 `null`
### ADR-008|历史查询边界与降采样
- 所有历史查询要求 `end_time > start_time`,最大跨度 31 天。
- 曲线查询最多 20 个点位;`max_samples` 默认 2000,范围 100~10000。
- 原始记录超过上限时,服务端执行自适应 min-max 降采样,保留首尾、极值、质量变化和 gap 边界。
- 响应返回 `sampled``raw_count``sample_count`
### ADR-009|表格最近邻匹配
- 匹配窗口为 `interval_minutes / 2` 的精确时长,不进行整分钟向上取整。
- 原始查询范围扩展为 `[start_time-window, end_time+window]`
- 目标时间为 `start_time + n*interval` 且不晚于 `end_time`;不强制追加未对齐的 `end_time`
- 距离相同时选择较早的原始时间戳,避免使用未来值优先。
- 每个目标时间独立匹配;同一原始记录允许匹配相邻目标时间,并通过 `matched_ts` 明示。
### ADR-010API 响应和文件流
- JSON API 使用 `{code,message,data}` 包装。
- CSV、其他文件、SSE 和 WebSocket 是明确例外:成功响应直接返回对应流;失败响应仍返回统一 JSON 错误。
- 同步设备写入只有写入和回读均成功时返回 HTTP 200、`code=0`
### ADR-011HTTP 状态与业务错误码
- HTTP 状态表达错误类别;业务 `code` 表达模块内具体原因,不从数字前缀推断 HTTP 类别。
- 模块范围:通用/鉴权 `10001~10999`、设备 `40001~40999`、采集点 `41001~41999`、写入点 `42001~42999`、历史 `43001~43999`、采集引擎 `50001~50999`、写入引擎 `51001~51999`
### ADR-012CSV 分类和文件名
- 配置导入导出 CSV 使用固定 `snake_case` 标题,可再次导入。
- 用户报表 CSV 允许本地化和动态列名;重复点位名称追加 UUID 前 8 位。
- 文件名由服务端对已解析参数重新格式化,只允许 ASCII 字母、数字、下划线、短横线和点。
### ADR-013|写入值和回读
- 写入日志数据库可用 TEXT 保存原始值,但 API 必须按 `data_type` 返回 JSON boolean/number。
- `write_points` 增加 `readback_tolerance`BOOL/INT 严格相等,REAL 使用绝对误差 `abs(actual-target) <= readback_tolerance`
- 当前阶段仅支持内部网络中的未认证人工写入,服务端固定记录 `source=manual``operator=null`
### ADR-014|配置生效时限
- Service 在事务提交后发布进程内配置变更事件,正常情况下 1 秒内生效。
- 采集引擎每 5 秒执行一次全量版本校对作为丢事件后的兜底;因此对外 SLA 为 5 秒内最终生效。
### ADR-015|历史图表职责
- 历史 API 只返回原始值和元数据。
- 分段自适应 Y 轴由前端图表组件实现,必须标明非线性/断轴效果;不得把映射后的显示坐标冒充原始值。
### ADR-016|环境配置
- PostgreSQL 和 TDengine 的主机、端口、用户名、密码和数据库名均通过环境变量配置。
- TDengine 数据库名使用 `TDENGINE_DATABASE`,默认值可为 `aquacontrolai`,代码和迁移不得写死生产值。
## 3. 当前文档版本
| 文档 | 版本 |
|---|---|
| `spec-数据管理.md` | v1.2 |
| `spec-历史数据.md` | v1.1 |
| `code-standards.md` | v1.2 |
| 本基线 | v1.0 |
> 最后更新:2026-07-11