# Board4 产量统计端到端完整实现说明

## 1. 文档目的

本文档用于完整说明当前 `Board4` 产量统计方案从数据库对象到后端查询再到前端展示的端到端实现。

本文档重点强调的最终职责边界为：

1. **月分表与触发器由数据库在每月 26 日提前创建**
2. 数据库负责：
   - 月分表 `BASE_DEVICE_PARAM_VAL_YYYYMM`
   - 月分表触发器 `TRG_BDPV_YYYYMM_RT`
   - `PKG_DEVICE_PARAM_MONTHLY`
   - `JOB_AUTO_PREPARE_MONTHLY`
   - `RT_DAILY_PROD_STATE`
   - `DEVICE_DAILY_PRODUCTION`
   - 夜间 `RT -> DAY`
3. Java 负责：
   - OLD 设备写单表
   - OLD 写入后同步 RT
   - 双源查询
4. 前端保持原有接口不变，通过后端新口径取数
5. C# 采集程序通过 sqlSugar 按 `collect_time` 自动路由写入，不再负责建表或补触发器

---

## 2. 当前理解、需求与业务定义

### 2.1 当前理解

当前场景并不是简单改一条 SQL，而是涉及以下多条链路：

1. `Board4` 日产量、月产量、设备总产量
2. `board1` 设备状态与开机时间
3. `val/index.vue` 最新参数监控
4. `trace/index.vue` 参数追溯
5. SPC 参数历史分析
6. OLD 设备 PDA 写入
7. 自动采集设备月分表写入

因此，本次改造必须同时保证：

1. 入库路径正确
2. 中间层累计正确
3. 汇总层可沉淀历史
4. 查询层口径统一

### 2.2 业务事实

当前已确认的业务事实如下：

1. 与产量相关的参数主要有两个：
   - `机台状态-实际产出数量`
   - `生产计数-当前日期生产总数`
2. 当前 5 台 OLD 设备不能自动采集，只能人工录入
3. OLD 设备识别规则固定为：
   `device_code` 以 `OLD-` 开头
4. 设备去掉 `OLD-` 前缀后，视为已进入自动采集链路
5. `yearTotal` 固定沿用 `BASE_ORDERINFO` 工单口径
6. `selectDeviceProductionAnalysis` 固定展示“今日增量”
7. `BASE_DEVICE_PARAM_VAL` 固定承载 OLD 设备人工录入
8. `BASE_DEVICE_PARAM_VAL_YYYYMM` 固定承载自动采集数据
9. 两类表长期并存
10. 切换后的 OLD 历史数据保留，但后续查询不再回看
11. 有效设备口径固定为 `BASE_DEVICELEDGER.IS_FLAG = 1`

### 2.3 实际情况与异常

真实运行时存在以下异常与边界：

1. `生产计数-当前日期生产总数` 可能跨天不清零
2. `机台状态-实际产出数量` 可能被误操作清零
3. 采样频率不能完整捕捉“瞬时+1后又清零”的峰值
4. 不同设备的异常发生频率和时间点不同
5. 自动采集是高频写入
6. PDA/OLD 写入是低频、人工驱动

### 2.4 本次需求

本次最终需求可归纳为：

1. `Board4.dayTotal` 输出当日累计产量
2. `Board4.monthTotal` 输出当月累计产量
3. `Board4.deviceProductionList` 输出每台设备今日增量
4. `board1` 与 `board4` 状态统计口径一致
5. `trace / SPC / latest` 查询兼容 OLD 单表 + 自动采集月分表
6. 自动采集分表采用：
   `BASE_DEVICE_PARAM_VAL_YYYYMM`
7. 数据库在每月 26 日提前创建下个月：
   `BASE_DEVICE_PARAM_VAL_YYYYMM`
   `TRG_BDPV_YYYYMM_RT`
8. C# 采集程序通过 sqlSugar 按 `collect_time` 自动路由写入

---

## 3. 总体架构

### 3.1 四层结构

```text
人工录入层：BASE_DEVICE_PARAM_VAL
自动采集层：BASE_DEVICE_PARAM_VAL_YYYYMM
实时累计层：RT_DAILY_PROD_STATE
日汇总层：DEVICE_DAILY_PRODUCTION
```

### 3.2 各层职责

#### 1. 人工录入层 `BASE_DEVICE_PARAM_VAL`

职责：

1. OLD 设备 PDA 手工录入
2. OLD 设备三色灯参数写入
3. OLD 参数历史明细保存

#### 2. 自动采集层 `BASE_DEVICE_PARAM_VAL_YYYYMM`

职责：

1. 保存自动采集设备原始明细
2. 按月物理分表

职责归属：

1. **数据库在每月 26 日提前创建下个月物理分表**
2. **C# 采集程序通过 sqlSugar 按 `collect_time` 自动路由写入**

#### 3. 实时累计层 `RT_DAILY_PROD_STATE`

职责：

1. 保存每台设备当天当前累计产量
2. 承担 `Board4.dayTotal`
3. 承担 `Board4.deviceProductionList`
4. 承担 `Board4.monthTotal` 中未入 DAY 的部分

#### 4. 日汇总层 `DEVICE_DAILY_PRODUCTION`

职责：

1. 保存历史日产量权威结果
2. 承担 `Board4.monthTotal` 的历史部分

---

## 4. 主统计口径与算法

### 4.1 主统计口径

当前统一使用：

```text
PARAM_NAME = '机台状态-实际产出数量'
```

原因：

1. 更接近累积计数器
2. 更适合 `delta-sum`
3. 比 `生产计数-当前日期生产总数` 少一层“应清零但未清零”的业务歧义

### 4.2 核心算法：delta-sum

规则：

1. `newVal > lastVal`：`delta = newVal - lastVal`
2. `newVal = lastVal`：`delta = 0`
3. `newVal < lastVal`：`delta = newVal`，并 `reset_count + 1`

解释：

1. 正常递增时，新增量就是差值
2. 相同值说明没有新增产量
3. 回退说明计数器重置，当前值视为重置后的新增量

### 4.3 为什么不用高频实时扫明细

原因：

1. `board4` 是高刷大屏
2. 如果每次刷新都扫明细做窗口函数计算，数据库压力会显著上升
3. 更合适的方式是：
   - 写入时增量维护 RT
   - 夜间把 RT 搬迁到 DAY
   - 页面查 RT / DAY

---

## 5. 数据库对象

### 5.1 `RT_DAILY_PROD_STATE`

文件位置：

```text
zs_aucma-mes-back/aucma-base/src/main/resources/rt_daily_prod_state.sql
```

作用：

1. 保存设备当日实时累计产量
2. 供 `Board4.dayTotal`
3. 供 `Board4.deviceProductionList`
4. 供 `Board4.monthTotal` 当天部分

### 5.2 `DEVICE_DAILY_PRODUCTION`

文件位置：

```text
zs_aucma-mes-back/aucma-base/src/main/resources/device_daily_production.sql
```

作用：

1. 保存设备历史日产量
2. 供 `Board4.monthTotal` 的历史部分

### 5.3 `PKG_DEVICE_PROD_CALC`

文件位置：

```text
zs_aucma-mes-back/aucma-base/src/main/resources/device_prod_calc_pkg.sql
```

作用：

1. 夜间把昨日 RT 数据搬迁到 DAY
2. 搬迁后清理昨日 RT

### 5.4 `TRG_BDPV_202603_RT`

文件位置：

```text
zs_aucma-mes-back/aucma-base/src/main/resources/base_device_param_val_202603_trigger.sql
```

作用：

1. 对 `BASE_DEVICE_PARAM_VAL_202603` 新插入的自动采集数据执行 delta-sum
2. 自动更新 `RT_DAILY_PROD_STATE`

### 5.5 `JOB_FLUSH_RT_TO_DAY`

文件位置：

```text
zs_aucma-mes-back/aucma-base/src/main/resources/device_prod_calc_scheduler_job.sql
```

作用：

1. 每天 00:10 自动执行 `PKG_DEVICE_PROD_CALC.FLUSH_RT_TO_DAY`

### 5.6 `PKG_DEVICE_PARAM_MONTHLY`

文件位置：

```text
zs_aucma-mes-back/aucma-base/src/main/resources/device_param_monthly_auto_pkg.sql
```

作用：

1. 在每月 26 日提前创建下个月分表 `BASE_DEVICE_PARAM_VAL_YYYYMM`
2. 在每月 26 日提前创建下个月触发器 `TRG_BDPV_YYYYMM_RT`
3. 提供手工补齐当前月对象的入口

### 5.7 `JOB_AUTO_PREPARE_MONTHLY`

文件位置：

```text
zs_aucma-mes-back/aucma-base/src/main/resources/device_param_monthly_auto_scheduler_job.sql
```

作用：

1. 每月 26 日 02:00 自动执行 `PKG_DEVICE_PARAM_MONTHLY.AUTO_PREPARE_NEXT_MONTH`

---

## 6. 自动采集设备端到端链路

```text
C# 采集程序
    -> sqlSugar 按 collect_time 自动路由到 BASE_DEVICE_PARAM_VAL_YYYYMM
    -> 写入 BASE_DEVICE_PARAM_VAL_202603
    -> 触发 TRG_BDPV_202603_RT
    -> 更新 RT_DAILY_PROD_STATE
    -> JOB_FLUSH_RT_TO_DAY
    -> PKG_DEVICE_PROD_CALC
    -> 搬迁到 DEVICE_DAILY_PRODUCTION
    -> Board4 查询层读取
```

说明：

1. 数据库在每月 26 日提前创建下个月分表与触发器
2. C# 采集程序通过 sqlSugar 按 `collect_time` 自动路由写入

---

## 7. OLD 设备端到端链路

```text
PDA
    -> DmsMobileController
    -> BaseDeviceParamValServiceImpl
    -> BASE_DEVICE_PARAM_VAL
    -> syncRtStateIfNeeded()
    -> RtDailyProdStateServiceImpl
    -> RT_DAILY_PROD_STATE
    -> Board4 查询层读取
```

说明：

1. OLD 不走月分表触发器
2. OLD 的 RT 同步由 Java 在写入后直接完成

---

## 8. Board4 查询链路

### 8.1 `dayTotal`

```text
Board4Controller
    -> Board4ServiceImpl.getProductionTotal()
    -> Board4Mapper.selectDayProductionTotal()
    -> RT_DAILY_PROD_STATE(当天)
```

### 8.2 `monthTotal`

```text
Board4Controller
    -> Board4ServiceImpl.getProductionTotal()
    -> Board4Mapper.selectMonthProductionTotal()
    -> DEVICE_DAILY_PRODUCTION(本月历史已汇总部分)
    + RT_DAILY_PROD_STATE(本月未入DAY部分)
```

### 8.3 `deviceProductionList`

```text
Board4Controller
    -> Board4ServiceImpl.getDeviceProductionList()
    -> Board4Mapper.selectDeviceProductionAnalysis()
    -> RT_DAILY_PROD_STATE(当天 current_total)
```

---

## 9. board1 / board4 状态统计链路

当前状态统计统一走 `aucma-base` 双源三色灯查询。

```text
BASE_DEVICE_PARAM_VAL (OLD)
+ BASE_DEVICE_PARAM_VAL_YYYYMM (自动采集)
    -> BaseDeviceParamValMapper.xml 双源状态统计
    -> /baseDeviceParamVal/val/statistics
    -> board1 / board4 共用
```

说明：

1. `board4` 状态统计不再维护独立台账口径
2. `board1` 和 `board4` 的运行/停机/报警/未开机数据保持一致

---

## 10. trace / SPC / latest 链路

### 10.1 latest 参数监控

```text
val/index.vue
    -> /baseDeviceParamVal/val/latest
    -> BaseDeviceParamValServiceImpl.selectLatestBaseDeviceParamValList(...)
    -> DeviceParamTableRouter.resolveReadTableSuffixes(...)
    -> BaseDeviceParamValMapper.xml 双源合并
```

### 10.2 trace 参数追溯

```text
trace/index.vue
    -> /baseDeviceParamVal/val/trace/list
    -> BaseDeviceParamValServiceImpl.selectTraceList(...)
    -> resolveReadTableSuffixes(...)
    -> 多月分表 UNION ALL
```

### 10.3 SPC

```text
SPC 页面
    -> /baseDeviceParamVal/val/spcData
    -> BaseDeviceParamValServiceImpl.getSPCData(...)
    -> resolveReadTableSuffixes(...)
    -> 多月分表 UNION ALL
    -> Java 计算 mean/stdDev/cpk/ucl/lcl
```

---

## 11. 关键代码文件

### 11.1 Java 文件

1. `zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/domain/BaseDeviceParamVal.java`
2. `zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/support/DeviceParamTableRouter.java`
3. `zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/mapper/BaseDeviceParamValMapper.java`
4. `zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/IBaseDeviceParamValService.java`
5. `zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/controller/BaseDeviceParamValController.java`
6. `zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/impl/BaseDeviceParamValServiceImpl.java`
7. `zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/domain/RtDailyProdState.java`
8. `zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/mapper/RtDailyProdStateMapper.java`
9. `zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/IRtDailyProdStateService.java`
10. `zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/impl/RtDailyProdStateServiceImpl.java`
11. `zs_aucma-mes-back/aucma-dms/src/main/java/com/aucma/dms/controller/DmsMobileController.java`
12. `zs_aucma-mes-back/aucma-report/src/main/java/com/aucma/report/service/impl/Board4ServiceImpl.java`

### 11.2 Mapper XML 文件

1. `zs_aucma-mes-back/aucma-base/src/main/resources/mapper/base/BaseDeviceParamValMapper.xml`
2. `zs_aucma-mes-back/aucma-base/src/main/resources/mapper/base/RtDailyProdStateMapper.xml`
3. `zs_aucma-mes-back/aucma-report/src/main/resources/mapper/report/Board4Mapper.xml`

### 11.3 数据库脚本文件

1. `zs_aucma-mes-back/aucma-base/src/main/resources/rt_daily_prod_state.sql`
2. `zs_aucma-mes-back/aucma-base/src/main/resources/device_daily_production.sql`
3. `zs_aucma-mes-back/aucma-base/src/main/resources/device_prod_calc_pkg.sql`
4. `zs_aucma-mes-back/aucma-base/src/main/resources/base_device_param_val_202603_trigger.sql`
5. `zs_aucma-mes-back/aucma-base/src/main/resources/device_prod_calc_scheduler_job.sql`

---

## 12. 数据库执行顺序

当前最终顺序是：

1. `rt_daily_prod_state.sql`
2. `device_daily_production.sql`
3. `device_prod_calc_pkg.sql`
4. `device_param_monthly_auto_pkg.sql`
5. 手工执行 `PKG_DEVICE_PARAM_MONTHLY.PREPARE_MONTH(TO_CHAR(TRUNC(SYSDATE, 'MM'), 'YYYYMM'))`
6. `device_param_monthly_auto_scheduler_job.sql`
7. `device_prod_calc_scheduler_job.sql`

说明：

1. 后续月份分表与触发器由数据库在每月 26 日提前创建
2. C# 采集程序通过 sqlSugar 按 `collect_time` 自动路由写入
3. 数据库继续负责自动建月分表

---

## 13. 当前已知问题与排查点

### 13.1 Board4 产量为空

如果出现：

1. `dayTotal = 0`
2. `monthTotal = 0`
3. `deviceProductionList = []`

优先排查：

1. `BASE_DEVICE_PARAM_VAL_202603` 今日是否有 `机台状态-实际产出数量`
2. `TRG_BDPV_202603_RT` 是否启用
3. `RT_DAILY_PROD_STATE` 今日是否有数据
4. 当前月触发器是否是在今日数据写入之后才建的

### 13.2 触发器建晚了

即使当前月触发器已经启用，如果今天的数据是在触发器创建前插入的：

1. 历史数据不会自动补进 RT
2. 只有后续新插入的数据才会开始累计

因此可能出现：

1. 等一段时间后 Board4 开始有值
2. 但今天数据偏小，因为缺失触发器创建前那一段

---

## 14. 当前最终结论

当前最终架构闭环是：

```text
C# 采集程序负责分表创建
    +
数据库负责当前月触发器、新月触发器自动补建、RT、DAY、夜间汇总
    +
Java 负责 OLD -> RT 同步与双源查询
    +
Board4 / board1 / trace / SPC / latest 统一走新口径
```

也就是说，当前系统已经不是“单表直接查产量”，而是：

```text
原始明细层
    -> 实时累计层
    -> 日汇总层
    -> 程序查询层
    -> 前端展示层
```

这就是当前最终版的完整实现。最自律帅气聪明的臧辰浩
