# 电能质量 13 张表批量补数设计说明

## 1. 背景

`tools/add-data` 当前只有模块骨架和历史 SQL 文件 [DATA_FLICKER.sql](D:/Work/SourceCode/CN_Tool/tools/add-data/DATA_FLICKER.sql)，尚未提供真实业务接口。

本次需求是在 `add-data` 模块中补齐一套“按时间区间批量生成电能质量数据并写入数据库”的能力，满足以下业务目标：

- 支持按时间区间补数，例如一天、一月。
- 支持时间戳步长选择：`1`、`3`、`5`、`10` 分钟。
- 支持单监测点和多监测点，前端可选择监测点 ID，默认单监测点。
- 支持批量写入，优先保证大区间补数时的效率。
- 同一主键数据已存在时跳过，不覆盖、不删除。
- 同步提供前端页面交互方案与参数展示规则。

## 2. 范围确认

根据当前 [DATA_FLICKER.sql](D:/Work/SourceCode/CN_Tool/tools/add-data/DATA_FLICKER.sql) 内容，本次补数范围按 13 张表处理，而不是 14 张表：

1. `data_flicker`
2. `data_fluc`
3. `data_harmphasic_i`
4. `data_harmphasic_v`
5. `data_harmpower_p`
6. `data_harmpower_q`
7. `data_harmpower_s`
8. `data_harmrate_i`
9. `data_harmrate_v`
10. `data_i`
11. `data_inharm_i`
12. `data_plt`
13. `data_v`

## 3. 已确认需求

本次设计基于以下已确认结论：

- 写入方式是“按时间戳步长生成整段数据”，不是定时常驻写库。
- 重复补数时采用“跳过已存在数据”策略。
- 前端方案需要和后端接口一起设计。
- 分表时间轴规则已确认：
  - `data_flicker` 固定每 `10` 分钟写一组
  - `data_fluc` 固定每 `10` 分钟写一组
  - `data_plt` 固定每 `2` 小时写一组
  - 其余 `10` 张表按前端选择的 `1`、`3`、`5`、`10` 分钟步长写入
  - 所有时间点都按自然时间槽对齐，采用整点整分方式生成，不按传入开始时间顺延
- 展示字段需要覆盖：
  - `电能质量参数名称`
  - `相别`
  - `是否合格`
  - `最大值`
  - `最小值`
  - `平均值`
  - `95%概率大值`
  - `数据展示（保留小数）`
- 数值展示默认保留 `2` 位小数。
- 相别参照用户提供图片中的“相别”列。

## 4. 关键约束

### 4.1 主键约束

13 张表均以 `LINEID + TIMEID + PHASIC_TYPE` 作为主键，重复写入时必须避免主键冲突导致整批失败。

### 4.2 相别字段长度约束

`PHASIC_TYPE` 字段长度为 `varchar(2)`。因此前端展示中的相别值和数据库实际落库值不能简单等同：

- 页面展示可使用 `T`、`ABC`、`AB/BC/CA`
- 落库时必须拆分为真实可存储的 `PHASIC_TYPE`

### 4.3 数据量约束

当时间区间为“一月”、步长为“1 分钟”、监测点为“多选”时，补数规模会非常大，不能采用单条插入或一次性全量加载入内存的方式。

### 4.4 现有仓库约束

- 当前仓库是后端工程，没有现成前端代码，本次只输出前端交互方案和接口契约。
- 默认不执行 `mvn` 编译、打包、测试。
- 修改范围应尽量收敛在 `tools/add-data` 模块内。

## 5. 方案选择

本次比较三种实现路径：

### 方案 A：同步接口直接写库

特点：

- 控制器接收参数后直接在请求线程中完成全部写库。
- 实现最简单。

问题：

- 大区间、多监测点时容易超时。
- 任务执行中断后难以定位进度。
- 前端体验差，无法明确看到当前执行状态。

结论：

不采用。

### 方案 B：异步任务 + JDBC 批量写入

特点：

- 请求只负责创建任务，后台异步执行。
- 任务状态可轮询。
- 批量写入时可按表、按批次提交。

优势：

- 更适合月级、多监测点补数。
- 可以精细控制批次大小。
- 当前数据源连接串已开启 `rewriteBatchedStatements=true`，适合批处理。

结论：

采用该方案。

### 方案 C：只生成 SQL 文件，人工执行

特点：

- 后端只生成 SQL 脚本，不直接写库。

问题：

- 需要人工执行，流程割裂。
- 不符合“前端选择后直接补数”的目标。

结论：

不采用。

## 6. 总体设计

### 6.1 设计目标

在 `tools/add-data` 中新增一套“批量补数任务”能力，支持：

- 前端提交补数参数
- 后端预估本次写入规模
- 后端异步生成数据并按表批量写入
- 前端轮询任务状态
- 前端获取参数展示规则

### 6.2 模块边界

本次能力只落在 `tools/add-data`，不向 `system`、`user`、`detection` 扩散业务逻辑。

如需监测点列表，首版不假设存在基础监测点表，也不新增点位管理能力，直接由前端传入选中的 `lineIds`。

### 6.3 接口拆分

建议把“任务管理”和“参数模板展示”分成两类接口：

- 补数任务接口：负责预估、创建、状态查询
- 模板展示接口：负责返回图片对应的参数展示规则

## 7. 数据模型与相别规则

### 7.1 展示相别与落库相别分离

前端展示相别来自用户图片中的“相别”列，但落库时需要展开成真正的 `PHASIC_TYPE` 值。

建议在模板中同时维护：

- `phaseDisplay`：前端展示值
- `phaseCodes`：落库使用的真实相别集合

### 7.2 相别展开规则

建议默认规则如下：

- `T` -> `["T"]`
- `ABC` -> `["A", "B", "C"]`
- `AB/BC/CA` -> `["AB", "BC", "CA"]`

这套规则同时用于：

- 决定每个参数需要展开多少条写库数据
- 决定前端参数表如何显示相别列

### 7.3 参数模板职责

参数模板不直接等于数据库表结构，而是承担以下职责：

- 描述前端展示行
- 绑定该参数对应的数据表
- 描述展示相别和落库相别
- 约定是否展示“是否合格”
- 约定最大值、最小值、平均值、95%概率大值的展示逻辑
- 约定保留小数位数，默认 `2`

## 8. 后端接口设计

### 8.1 预估接口

接口：

- `POST /addData/task/preview`

作用：

- 根据 `lineIds`、时间区间、时间戳步长，返回本次补数的预估规模

入参：

- `lineIds`：监测点 ID 列表
- `startTime`：开始时间
- `endTime`：结束时间
- `intervalMinutes`：`1`、`3`、`5`、`10`

返回内容：

- 时间点数量
- 监测点数量
- 每张表预估写入条数
- 总预估条数

说明：

- 预估逻辑必须按分表时间轴分别计算。
- `intervalMinutes` 只影响 `data_harmphasic_i`、`data_harmphasic_v`、`data_harmpower_p`、`data_harmpower_q`、`data_harmpower_s`、`data_harmrate_i`、`data_harmrate_v`、`data_i`、`data_inharm_i`、`data_v` 这 `10` 张表。
- `data_flicker`、`data_fluc` 固定按 `10` 分钟时间槽计算。
- `data_plt` 固定按 `2` 小时时间槽计算。

### 8.2 创建任务接口

接口：

- `POST /addData/task/create`

作用：

- 创建一个批量补数任务，并立即进入后台执行

入参：

- `lineIds`
- `startTime`
- `endTime`
- `intervalMinutes`

返回内容：

- `taskId`
- 初始状态

说明：

- 前端默认提交单个 `lineId`
- 多监测点时前端直接提交多个 `lineIds`
- 不单独拆分“单点接口”和“多点接口”
- `intervalMinutes` 仅作为 `10` 张基础实时类表的生成步长
- 固定频率表仍按各自时间轴生成，不跟随 `intervalMinutes`

### 8.3 任务状态接口

接口：

- `GET /addData/task/status/{taskId}`

作用：

- 供前端轮询当前任务执行状态

返回内容：

- 任务状态：`WAITING`、`RUNNING`、`SUCCESS`、`FAILED`
- 当前执行表名
- 当前批次信息
- 已写入条数
- 已跳过条数
- 失败条数
- 失败原因
- 开始时间、结束时间

### 8.4 参数模板接口

接口：

- `GET /addData/template/list`

作用：

- 返回前端页面参数规则表所需的静态配置

返回内容：

- 电能质量参数名称
- 相别展示值
- 是否展示“是否合格”
- 最大值规则
- 最小值规则
- 平均值规则
- 95%概率大值规则
- 保留小数位数

## 9. 后端分层设计

### 9.1 Controller 层

建议新增两个控制器：

- `AddDataTaskController`
  - 补数任务相关接口
- `AddDataTemplateController`
  - 参数模板查询接口

### 9.2 Service 层

建议拆分为：

- `AddDataTaskService`
  - 创建任务
  - 状态查询
  - 预估规模
- `AddDataTemplateService`
  - 返回前端参数模板

### 9.3 Component 层

建议新增以下组件：

- `AddDataTaskExecutor`
  - 负责异步执行任务
- `AddDataBatchWriter`
  - 负责按表批量写入
- `AddDataValueGenerator`
  - 负责根据模板生成每条记录的值
- `AddDataTaskStatusHolder`
  - 首版用内存保存任务状态
- `AddDataTemplateRegistry`
  - 持有参数模板与相别映射

### 9.4 数据访问层

本次不建议为 13 张表分别创建超长 MyBatis XML 批量插入语句。

原因如下：

- 宽表字段极多，XML 可维护性差。
- 13 张表结构差异大，重复代码很多。
- 本次更偏工具型批处理写数，不适合以业务实体方式逐表建模。

建议采用：

- `JdbcTemplate`
- `PreparedStatement`
- `batchUpdate`

在写入 SQL 上统一使用：

- `INSERT IGNORE`

以满足“跳过已存在数据”的业务要求。

## 10. 批量写入策略

### 10.1 总体原则

批量写入必须满足以下原则：

- 不先查存在再插入
- 不整段数据一次性全部放入内存
- 按表、按批次逐步提交
- 主键冲突自动跳过

### 10.2 跳过已存在数据

采用 `INSERT IGNORE` 而不是以下策略：

- 不采用“先查后写”，避免额外查询放大数据库压力
- 不采用“覆盖更新”，与已确认需求不符
- 不采用“先删后写”，避免误删

### 10.3 流式生成

建议按如下顺序流式生成：

1. 先遍历时间点
2. 再遍历监测点
3. 再按模板展开相别
4. 最后路由到对应表的批缓存

达到批次阈值后立即落库并清空当前缓存。

### 10.4 分批阈值

建议根据表宽度分层设置批次大小：

- 窄表：`500 ~ 1000` 行/批
- 宽表：`100 ~ 200` 行/批

其中以下表建议视为宽表：

- `data_harmphasic_i`
- `data_harmphasic_v`
- `data_harmpower_p`
- `data_harmpower_q`
- `data_harmpower_s`
- `data_harmrate_i`
- `data_harmrate_v`
- `data_i`
- `data_inharm_i`
- `data_v`

### 10.5 任务统计

任务执行过程中需累计以下统计值：

- `insertedCount`
- `skippedCount`
- `failedCount`

其中：

- `insertedCount` 表示本次真正插入成功的行数
- `skippedCount` 表示因主键重复被忽略的行数
- `failedCount` 表示非主键冲突导致的失败数量

## 11. 数据生成策略

### 11.1 首版原则

首版不引入复杂公式配置，不让前端传计算表达式。数据生成规则由后端内置。

### 11.2 分表时间轴规则

本次补数不采用“全部表共用同一时间轴”的方式，而是拆成三类时间轴：

1. 固定 `10` 分钟时间轴
2. 固定 `2` 小时时间轴
3. 用户步长时间轴

具体规则如下：

- `data_flicker`
  - 固定每 `10` 分钟一组
  - 按自然时间槽对齐，例如 `10:00`、`10:10`、`10:20`
- `data_fluc`
  - 固定每 `10` 分钟一组
  - 按自然时间槽对齐
- `data_plt`
  - 固定每 `2` 小时一组
  - 按自然时间槽对齐，例如 `00:00`、`02:00`、`04:00`
- 其余 `10` 张表
  - 按前端传入的 `intervalMinutes` 生成
  - 允许值仅为 `1`、`3`、`5`、`10`
  - 同样按自然时间槽对齐

如果前端传入 `startTime=10:07`：

- `10` 分钟表应从 `10:10` 开始
- `2` 小时表应从 `12:00` 或下一个符合槽位的时刻开始
- 用户步长表应从不小于 `startTime` 的下一个对应槽位开始

### 11.3 统一生成方式

建议采用“基础状态生成 + 派生字段回填 + 分表时间轴裁切”的方式：

- 先按 `(lineId, timeId)` 生成基础电气状态
- 再从基础状态派生电压、电流、谐波、功率、闪变
- 最后按各表自己的时间轴决定是否落库

统一原则如下：

- 同一时间点的 13 张表数据必须同源，不允许各表独立随机
- 主值字段按基准值和受控扰动生成
- 最大值字段不小于主值
- 最小值字段不大于主值
- `95%概率大值` 对应 `CP95` 字段
- 宽表中的谐波分量和派生列按统一规则回填
- 同一输入参数应尽量生成可复现的结果，不建议使用完全无约束随机数

### 11.4 逐表生成规律

#### 11.4.1 基础源表

`data_v` 作为电压源表，优先生成：

- 先生成 `FREQ`
- 再生成 `RMS`
- 再派生 `RMSAB`、`RMSBC`、`RMSCA`
- 再派生 `VU_DEV`、`VL_DEV`、`FREQ_DEV`
- 再派生 `V_POS`、`V_NEG`、`V_ZERO`、`V_UNBALANCE`
- 最后生成 `V_1 ~ V_50`
- `V_THD` 由 `V_2 ~ V_50` 与 `V_1` 计算

`data_i` 作为电流源表，与 `data_v` 保持同源：

- 先生成 `RMS`
- 再生成 `I_POS`、`I_NEG`、`I_ZERO`、`I_UNBALANCE`
- 再生成 `I_1 ~ I_50`
- `I_THD` 由 `I_2 ~ I_50` 与 `I_1` 计算

对这两张源表：

- `RMS_MAX`、`RMS_MIN`、`RMS_CP95`
- `FREQ_MAX`、`FREQ_MIN`、`FREQ_CP95`
- `V_THD_MAX`、`V_THD_MIN`、`V_THD_CP95`
- `I_THD_MAX`、`I_THD_MIN`、`I_THD_CP95`

均必须由主值派生，不单独随机。

#### 11.4.2 谐波幅值表

`data_harmphasic_v`：

- 直接复用 `data_v` 中的 `V_1 ~ V_50`
- 各次谐波的 `MAX`、`MIN`、`CP95` 与 `data_v` 同源派生

`data_harmphasic_i`：

- 直接复用 `data_i` 中的 `I_1 ~ I_50`
- 各次谐波的 `MAX`、`MIN`、`CP95` 与 `data_i` 同源派生

#### 11.4.3 谐波占比表

`data_harmrate_v`：

- 从 `data_harmphasic_v` 派生
- 建议按 `V_n / V_1 * 100` 计算 `V_1 ~ V_50` 对应占比值
- `MAX`、`MIN`、`CP95` 也按同样比例换算

`data_harmrate_i`：

- 从 `data_harmphasic_i` 派生
- 建议按 `I_n / I_1 * 100` 计算 `I_1 ~ I_50` 对应占比值
- `MAX`、`MIN`、`CP95` 也按同样比例换算

#### 11.4.4 间谐波电流表

`data_inharm_i`：

- 作为电流间谐波表生成
- 各阶值应明显小于对应整数次谐波值
- 建议按“整数次谐波值乘以小比例系数”生成
- `MAX`、`MIN`、`CP95` 继续由主值派生

#### 11.4.5 谐波功率表

`data_harmpower_p`：

- 从电压谐波和电流谐波联合派生
- `P_1 ~ P_50` 建议按电压谐波、电流谐波和相位关系计算
- `P` 为各次分量汇总
- `PF`、`DF` 由功率关系派生

`data_harmpower_q`：

- 与 `data_harmpower_p` 同源
- `Q_1 ~ Q_50` 按无功功率关系派生
- `Q` 为各次分量汇总

`data_harmpower_s`：

- 与 `data_harmpower_p`、`data_harmpower_q` 同源
- `S_1 ~ S_50` 建议由 `P_n` 与 `Q_n` 派生
- `S` 为各次分量汇总

这三张表中的 `MAX`、`MIN`、`CP95` 也必须基于对应主值派生。

#### 11.4.6 闪变与波动表

`data_flicker`：

- 固定 `10` 分钟时间轴
- 生成 `FLUC`、`PST`、`PLT`
- `FLUC` 作为波动主值
- `PST` 从 `FLUC` 派生
- `PLT` 作为长时闪变指标生成，但落库节奏仍为 `10` 分钟

`data_fluc`：

- 固定 `10` 分钟时间轴
- 生成 `FLUC`、`FLUCCF`
- 与 `data_flicker` 同源
- `FLUCCF` 作为 `FLUC` 的修正或归一化结果生成

`data_plt`：

- 固定 `2` 小时时间轴
- 只写 `PLT`
- `PLT` 来源应与 `data_flicker` 中的 `PLT` 同源
- 但只在 `2` 小时槽位落表

### 11.5 前端展示字段映射

由于表结构中没有独立 `AVG` 字段，前端“平均值”首版建议直接取各参数主值字段。

例如：

- 电压类参数取 `RMS`、`V_THD`、`FREQ`
- 电流类参数取 `RMS`、`I_THD`
- 闪变类参数取 `FLUC`、`PST`、`PLT`
- 功率类参数取 `P`、`Q`、`S`

前端展示中的：

- `最大值` -> 对应 `*_MAX`
- `最小值` -> 对应 `*_MIN`
- `95%概率大值` -> 对应 `*_CP95`
- `平均值` -> 对应主值字段

### 11.6 质量标识

`QUALITYFLAG` 首版建议统一使用固定有效值，例如 `1`。

前端“是否合格”不直接读取数据库 `QUALITYFLAG` 的原始含义，而是走模板展示规则，避免把数据生成逻辑和展示文案硬耦合。

## 12. 前端页面交互方案

### 12.1 页面结构

建议页面分为两个区域：

1. 补数任务区
2. 参数规则展示区

### 12.2 补数任务区

表单项建议如下：

- 监测点选择模式：`单点` / `多点`
- 监测点 ID 选择框
- 开始时间
- 结束时间
- 时间戳步长：`1`、`3`、`5`、`10` 分钟
- 预计写入量按钮
- 开始补数按钮

交互说明：

- 默认单监测点
- 多监测点时切换为多选
- 时间戳步长只影响 `10` 张基础实时类表
- `data_flicker`、`data_fluc` 固定按 `10` 分钟槽位写入
- `data_plt` 固定按 `2` 小时槽位写入
- 前端先调用预估接口，展示预计写入规模
- 用户确认后再调用创建任务接口

### 12.3 任务状态展示

前端创建任务后轮询状态接口，建议展示：

- 当前状态
- 当前表名
- 已写入数量
- 已跳过数量
- 失败数量
- 失败原因
- 开始时间
- 结束时间

### 12.4 参数规则展示区

参数规则表按用户提供图片组织，至少展示以下列：

- `电能质量参数名称`
- `相别`
- `显示`
- `最大值`
- `最小值`
- `平均值`
- `95%概率大值`
- `是否合格`
- `数据展示（保留小数）`

其中：

- 相别列使用 `phaseDisplay`
- 所有数值默认保留 `2` 位小数
- 该表展示的是模板规则，不是实时数据库统计结果

## 13. 文件结构建议

建议在 `tools/add-data` 下按现有仓库风格扩展：

```text
tools/add-data/src/main/java/com/njcn/gather/tool/adddata/
├── controller
│   ├── AddDataTaskController.java
│   └── AddDataTemplateController.java
├── service
│   ├── AddDataTaskService.java
│   ├── AddDataTemplateService.java
│   └── impl
│       ├── AddDataTaskServiceImpl.java
│       └── AddDataTemplateServiceImpl.java
├── component
│   ├── AddDataTaskExecutor.java
│   ├── AddDataBatchWriter.java
│   ├── AddDataValueGenerator.java
│   ├── AddDataTaskStatusHolder.java
│   └── AddDataTemplateRegistry.java
└── pojo
    ├── param
    └── vo
```

如参数模板需落成资源文件，建议放在：

- `tools/add-data/src/main/resources/template/add-data-parameter-template.json`

## 14. 非目标

本次不包含以下内容：

- 不新增监测点基础信息管理能力
- 不新增监测点字典表
- 不新增数据库迁移框架
- 不在 `system` 或 `user` 模块中扩展通用任务中心
- 不实现真实前端页面代码
- 不做覆盖更新或先删后写逻辑

## 15. 风险与取舍

### 15.1 内存态任务状态的风险

首版如果使用内存保存任务状态：

- 服务重启后任务状态会丢失
- 适合当前工具型场景
- 不适合后续演进为长期审计能力

当前取舍：

- 首版接受该限制
- 如果后续需要审计与追踪，再补任务表

### 15.2 模板与真实表字段映射的风险

用户图片中的参数项比“13 张表名”更偏业务视角，实际落库需要一层参数模板到表结构的映射。

当前取舍：

- 首版把模板规则集中维护在 `AddDataTemplateRegistry`
- 不在前端直接拼接数据库字段名

### 15.3 大批量写入的数据库压力

多监测点、长区间、1 分钟步长时数据库压力会很大。

当前取舍：

- 采用预估接口提前提示规模
- 控制批次大小
- 使用异步执行与 `INSERT IGNORE`

## 16. 验证方式

本次设计和后续实现默认不执行 `mvn`，验证方式以静态检查和链路闭合检查为主。

### 16.1 设计验证

确认以下设计点闭合：

- 13 张表范围明确
- 相别展示与落库规则明确
- 重复数据处理策略明确
- 前后端接口契约明确
- 批量写入策略明确

### 16.2 实现后验证

后续实现时重点检查：

- 入参校验是否覆盖时间区间、步长、监测点 ID 列表
- 预估条数计算是否正确
- SQL 是否统一使用 `INSERT IGNORE`
- 批次是否按宽表和窄表区分
- 重复补数时是否正确累加 `skippedCount`
- 参数模板返回是否与图片字段一致

## 17. 结论

本次采用“异步任务 + JDBC 批量写入 + 参数模板展示接口”的实现方案，在 `tools/add-data` 模块内完成电能质量 13 张表的批量补数能力。

该方案兼顾以下目标：

- 支持单监测点和多监测点
- 支持日级、月级区间
- 支持 1/3/5/10 分钟时间戳步长
- 支持重复补数时跳过已存在数据
- 支持前端按图片规则展示参数和相别
- 在当前仓库约束下把改动范围控制在最小必要集合
