diff --git a/aucma-base/src/main/resources/board4_end_to_end_full_implementation_guide.md b/aucma-base/src/main/resources/board4_end_to_end_full_implementation_guide.md
new file mode 100644
index 0000000..0e70464
--- /dev/null
+++ b/aucma-base/src/main/resources/board4_end_to_end_full_implementation_guide.md
@@ -0,0 +1,1164 @@
+# Board4 产量统计端到端完整实现说明
+
+## 1. 文档目的
+
+本文档是本次 `Board4` 产量统计改造的**完整端到端说明文档**,目标是把以下内容一次性讲清楚:
+
+1. 为什么要改
+2. 改造后的总体架构
+3. 数据库对象分别做什么
+4. 自动采集数据从入库到 `Board4` 展示的完整链路
+5. OLD 设备 PDA 数据从入库到 `Board4` 展示的完整链路
+6. 夜间 `RT -> DAY` 搬迁的完整链路
+7. `board1 / board4 / trace / SPC / 参数监控` 这些查询是怎么走的
+8. 本次所有代码文件、SQL 文件、文件位置、关键代码与关键 SQL
+9. 数据库部署顺序
+10. 常见问题与排查方法
+
+本文档位置:
+
+```text
+zs_aucma-mes-back/aucma-base/src/main/resources/board4_end_to_end_full_implementation_guide.md
+```
+
+---
+
+## 2. 改造背景
+
+### 2.1 原始问题
+
+`Board4` 原始产量统计存在以下问题:
+
+1. 日产量、月产量原本依赖“取最新值”或“按天取最后值”之类逻辑
+2. 这种逻辑无法正确处理:
+ - 计数器回退
+ - 误操作清零
+ - `生产计数-当前日期生产总数` 跨天不清零
+3. 自动采集即将切换到月分表,原来的单表 SQL 无法直接兼容
+4. OLD 设备仍然由 PDA 人工录入,不能走同一条自动采集链路
+
+### 2.2 这次改造要解决的核心目标
+
+1. `Board4.dayTotal` 改为读取实时累计表 `RT_DAILY_PROD_STATE`
+2. `Board4.monthTotal` 改为读取 `DEVICE_DAILY_PRODUCTION + RT_DAILY_PROD_STATE`
+3. `Board4.deviceProductionList` 改为读取 `RT_DAILY_PROD_STATE`
+4. 自动采集设备写月分表 `BASE_DEVICE_PARAM_VAL_YYYYMM`
+5. OLD 设备继续写 `BASE_DEVICE_PARAM_VAL`
+6. OLD 设备写入成功后,Java 同步更新 `RT_DAILY_PROD_STATE`
+7. 自动采集设备写入成功后,由月分表触发器更新 `RT_DAILY_PROD_STATE`
+8. 夜间将 `RT_DAILY_PROD_STATE` 搬迁到 `DEVICE_DAILY_PRODUCTION`
+9. 让 `board1 / board4 / trace / SPC / latest 参数监控` 都兼容“双源查询”
+
+---
+
+### 2.3 当前理解与代码现状
+
+本次改造并不是从零开始设计,而是在既有代码和既有业务约束上逐步演进。
+
+改造前的主要代码现状如下:
+
+1. `Board4` 的日累计、月累计、设备产量列表位于:
+ `zs_aucma-mes-back/aucma-report/src/main/resources/mapper/report/Board4Mapper.xml`
+2. 原 SQL 仍采用“取最新值”或“按天取最后值”的口径,不具备完整的增量求和能力
+3. `BaseDeviceParamVal` 原本把 `recordId` 当成 `Long` 使用,默认绑定旧单表序列
+4. `BaseDeviceParamValMapper.xml` 中大量查询默认指向旧单表 `BASE_DEVICE_PARAM_VAL`
+5. `DmsMobileController` 是 OLD 设备 PDA 参数写入和三色灯写入的重要入口
+6. `board1/index.vue` 与 `board4/index.vue` 都依赖设备状态统计接口,但原来没有统一双源口径
+7. `val/index.vue`、`trace/index.vue`、SPC 查询都和 `BaseDeviceParamValMapper.xml` 的查询能力直接相关
+
+这意味着:
+
+1. 不能只改 `Board4Mapper.xml`
+2. 必须同时改造数据落库路径、状态统计路径、追溯路径、SPC 路径
+3. 必须保证“OLD 单表 + 自动采集月分表 + RT 实时表 + DAY 日汇总表”四层协同一致
+
+### 2.4 业务事实与已确认定义
+
+业务上已经确认的事实与定义如下:
+
+1. 产量相关参数有两个:
+ - `机台状态-实际产出数量`
+ - `生产计数-当前日期生产总数`
+2. 已知异常包括:
+ - `生产计数-当前日期生产总数` 可能跨天不清零
+ - `机台状态-实际产出数量` 可能被误操作清零
+ - 采样频率不能完全覆盖硬件瞬时变化,可能丢失峰值
+ - 不同设备发生异常的频率与时间不一致
+3. 当前 5 台 OLD 设备无法自动采集,只能由 PDA 手工录入
+4. OLD 设备识别规则固定为:
+ `device_code` 以 `OLD-` 开头
+5. 设备去掉 `OLD-` 前缀后,视为进入自动采集链路
+6. `Board4.yearTotal` 固定使用 `BASE_ORDERINFO`,统计周期固定为“上一自然年”
+7. `Board4.selectDeviceProductionAnalysis` 固定展示“今日增量”
+8. `BASE_DEVICE_PARAM_VAL` 固定作为 OLD 设备人工录入单表
+9. `BASE_DEVICE_PARAM_VAL_YYYYMM` 固定作为自动采集月分表
+10. 两类表长期并存
+11. OLD 设备转自动采集后:
+ - 旧表历史保留
+ - 统计查询不再回看旧表历史
+12. C# 采集程序直连数据库,Java 不承接其入库
+13. `board1`、`val/index.vue`、`trace/index.vue`、SPC、设备状态接口都要适配双源
+14. 实时表命名固定为 `RT_DAILY_PROD_STATE`
+15. 日汇总表命名固定为 `DEVICE_DAILY_PRODUCTION`
+16. 有效设备口径固定为 `BASE_DEVICELEDGER.IS_FLAG = 1`
+
+### 2.5 样本数据与实际情况
+
+在 `val.sql` 和相关分析文档中,可以归纳出以下“实际运行情况”:
+
+1. 数据采集存在明显时间序列特征,设备会以近似固定频率写入参数值
+2. 两个产量参数并非语义完全一致:
+ - `机台状态-实际产出数量` 更接近累积计数器
+ - `生产计数-当前日期生产总数` 更接近理论上的“日计数器”
+3. 在样本区间中,按增量求和计算时,两种参数在局部样本上得到的总量可能相同
+4. 但从长期稳定性看,`机台状态-实际产出数量` 更适合做主口径
+5. 自动采集数据是大批量、持续写入的;PDA/OLD 数据是稀疏、人工驱动的
+6. 因采集频率限制,任何方案都无法完全恢复“采样间隔内瞬时增长又瞬时清零”的峰值
+
+因此,本方案追求的是:
+
+1. 在现有信息条件下给出稳健、可解释、可持续维护的产量统计
+2. 尽量避免高估
+3. 接受极端丢峰导致的少量低估
+
+### 2.6 改造前 SQL 逻辑的问题
+
+改造前以“最新值 / 当天最后值 / 本月每天最后值”为主的统计方式,存在以下问题:
+
+1. 对 `生产计数-当前日期生产总数`:
+ - 跨天不清零会直接导致日累计虚高
+2. 对 `机台状态-实际产出数量`:
+ - 中途清零会导致之前已产出部分被覆盖掉
+3. 如果一天内多次清零,只取最终值会丢失中间已产出量
+4. 按月分表后,旧 SQL 直接查 `BASE_DEVICE_PARAM_VAL`,无法兼容 `BASE_DEVICE_PARAM_VAL_YYYYMM`
+5. 原 `Board4` 高刷大屏如果每次都扫明细做窗口计算,性能与维护成本都不可接受
+6. 原 `board1` 和 `board4` 状态统计没有统一到双源三色灯口径,存在页面口径漂移风险
+
+### 2.7 详细需求清单
+
+本次从业务到技术,实际要满足的需求可以拆成 6 组:
+
+#### 2.7.1 Board4 统计需求
+
+1. `yearTotal` 继续来自 `BASE_ORDERINFO`
+2. `monthTotal` 输出当月累计产量
+3. `dayTotal` 输出当日累计产量
+4. `selectDeviceProductionAnalysis` 输出“今日增量”
+5. `deviceProductionList` 的合计要与 `dayTotal` 口径一致
+
+#### 2.7.2 双源数据需求
+
+1. 自动采集数据进入 `BASE_DEVICE_PARAM_VAL_YYYYMM`
+2. OLD 人工录入数据进入 `BASE_DEVICE_PARAM_VAL`
+3. 查询层必须同时兼容:
+ - OLD 单表
+ - 自动采集月分表
+4. `board1` 的状态、开机时间、设备列表必须兼容双源
+
+#### 2.7.3 主键与记录标识需求
+
+1. 自动采集月分表 `record_id` 是字符串业务唯一标识
+2. 自动采集月分表 `record_id` 不承担数值主键语义
+3. OLD 单表继续保留数值序列能力
+4. Java 领域模型需要统一主键语义,避免双字段歧义
+
+#### 2.7.4 跨月查询需求
+
+1. `trace` 允许自定义时间范围,可能跨 2~N 个月
+2. SPC 允许自定义时间范围,也可能跨月
+3. 月初、跨月边界必须能正确拿到边界前值
+
+#### 2.7.5 自动化需求
+
+1. 当前月自动采集写入时,能够自动维护 RT
+2. 夜间自动完成 RT -> DAY 搬迁
+3. 后续月份自动创建下月分表与下月触发器
+4. 尽量避免每个月手工运维
+
+#### 2.7.6 可维护性需求
+
+1. 方案要兼容 Oracle 11 / Oracle 19
+2. 要避免动态表名注入风险
+3. 要尽量复用 SQL 片段,减少重复维护
+4. 要让排障时能清晰区分:
+ - 明细层问题
+ - 触发器/Java RT 问题
+ - 夜间汇总问题
+ - 查询层问题
+
+### 2.8 需求分析与技术细节
+
+#### 2.8.1 为什么主口径选 `机台状态-实际产出数量`
+
+原因:
+
+1. 它更接近累积计数器语义
+2. 对“跨天不清零”问题天然免疫
+3. 只要处理回退/清零,就能稳定用 delta-sum 计算
+4. 比 `生产计数-当前日期生产总数` 少一层“理论应清零但现实未清零”的业务解释负担
+
+#### 2.8.2 为什么算法选 delta-sum
+
+delta-sum 是监控系统处理 counter 类时间序列的通用做法。
+
+规则:
+
+1. `prev_val IS NULL`:记 0
+2. `val >= prev_val`:记差值
+3. `val < prev_val`:视为重置,记 `val`
+
+优点:
+
+1. 能处理正常递增
+2. 能处理异常清零
+3. 能处理一天内多次回退
+4. 逻辑简单,可在 Java、触发器、存储过程三处统一实现
+
+#### 2.8.3 边界记录问题
+
+无论是日累计还是月累计,delta-sum 都依赖前一条记录。
+
+这带来一个客观问题:
+
+1. 时间窗口内第一条记录没有前值时,默认 delta = 0
+2. 如果窗口开始前到第一条记录之间设备已有产量,这部分会丢失
+
+处理方式分两类:
+
+1. 精确计算场景:
+ - 在明细层引入边界前一条记录
+ - 用 `LAG()` 做跨边界求差
+2. 实时累计场景:
+ - RT 表只从当天首条开始做实时累计
+ - 历史日的最终权威值由夜间搬迁/汇总沉淀
+
+这也是为什么当前最终方案选择:
+
+1. Board4 实时查询读 RT/DAY
+2. 不让高刷页面直接扫明细做全量窗口计算
+
+#### 2.8.4 按月分表的跨月查询问题
+
+手动月分表会带来两个直接后果:
+
+1. 表名不能用 `#{}` 绑定,只能用 `${}` 拼接
+2. 跨月查询必须先在 Java 层计算涉及的月份后缀列表
+
+这就是 `DeviceParamTableRouter.resolveReadTableSuffixes(...)` 存在的原因。
+
+关键点:
+
+1. 后缀由服务端计算
+2. 后缀必须强校验为 6 位数字
+3. 不能接受前端直接传表名
+
+#### 2.8.5 Oracle 11 / 19 兼容性
+
+本方案默认兼容:
+
+1. 测试环境 Oracle 11
+2. 生产环境 Oracle 19
+
+因此设计上遵循:
+
+1. MyBatis XML 和触发器尽量使用 Oracle 11 能运行的语法
+2. 动态 SQL 与夜间包不要依赖过新的语法糖
+3. `DBMS_SCHEDULER` 用标准方式创建 job
+
+#### 2.8.6 `recordId` 为什么统一改成 `String`
+
+原因:
+
+1. 自动采集月分表的 `record_id` 本身就是字符串业务标识
+2. 如果 OLD 单表继续在 Java 里保留 `Long recordId`,会出现双语义主键
+3. 为了统一领域模型,Java 侧改成 `String recordId`
+4. OLD 单表仍可在数据库层用数值序列,Java 侧通过 `TO_CHAR` 接收
+
+#### 2.8.7 为什么 Board4 读 RT/DAY 而不是读明细
+
+原因:
+
+1. `board4` 是高刷大屏
+2. 频繁刷明细 + 窗口函数对数据库压力大
+3. 读 RT/DAY 的复杂度更低
+4. 把计算压力前移到写入时和夜间批处理,更符合这个场景
+
+#### 2.8.8 为什么 Board4 状态统计要复用 board1
+
+原因:
+
+1. `board4/index.vue` 的 `runningData` 实际来自 `aucma-base` 的状态接口
+2. `board1/index.vue` 也依赖同一组状态接口
+3. 如果两边口径不统一,会出现:
+ - `board1` 显示运行
+ - `board4` 显示为空/停机
+4. 所以状态统计必须统一复用双源三色灯查询
+
+#### 2.8.9 为什么切换后的 OLD 历史不再回看
+
+这是业务定义,不是技术限制。
+
+确定规则后,系统才有清晰的一致性边界:
+
+1. OLD 阶段查旧表
+2. 自动采集阶段查月分表
+3. 切换后不做跨源拼接历史补算
+
+好处:
+
+1. 实现简单
+2. 口径清晰
+3. 性能与维护压力更低
+
+代价:
+
+1. 某些跨切换周期报表不连续
+
+业务已经接受这个代价,所以方案按此执行。
+
+### 2.9 方案的边界与局限
+
+即使当前方案已经是可运行的最优实现,仍要明确几个边界:
+
+1. 触发器只对新插入数据生效,不会自动回补历史数据
+2. 如果当日数据是在触发器建立前写入的,RT 当天会出现缺口
+3. 夜间 job 没跑之前,DAY 表不会有历史沉淀
+4. 当前“后台普通 CRUD 按 recordId 精准反查月分表”的能力并未作为本轮主闭环重点
+5. 自动建分表包依赖数据库具备对应 DDL 权限与表空间配额
+
+---
+
+## 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 设备参数历史追溯原始明细
+
+特点:
+
+1. 单表
+2. 继续使用序列主键
+3. Java 侧 `recordId` 统一按 `String` 接收
+
+#### 2. 自动采集层 `BASE_DEVICE_PARAM_VAL_YYYYMM`
+
+职责:
+
+1. 自动采集设备的原始采集明细
+2. 按月物理分表
+
+特点:
+
+1. 表名规则为:
+
+```text
+BASE_DEVICE_PARAM_VAL_202603
+BASE_DEVICE_PARAM_VAL_202604
+...
+```
+
+2. `record_id` 由自动采集侧传入字符串业务唯一标识
+3. 每月一个物理表
+
+#### 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. 计数器回退时,当前值代表重置后的新增量
+
+---
+
+## 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` 的未汇总部分提供来源
+
+关键表结构:
+
+```sql
+CREATE TABLE RT_DAILY_PROD_STATE (
+ PROD_DATE DATE NOT NULL,
+ DEVICE_CODE VARCHAR2(64) NOT NULL,
+ PARAM_NAME VARCHAR2(128) NOT NULL,
+ LAST_PARAM_VAL NUMBER(18,4) DEFAULT 0 NOT NULL,
+ CURRENT_TOTAL NUMBER(18,4) DEFAULT 0 NOT NULL,
+ RESET_COUNT NUMBER(10) DEFAULT 0 NOT NULL,
+ DIRTY_FLAG NUMBER(1) DEFAULT 0 NOT NULL,
+ LAST_COLLECT_TIME DATE,
+ UPDATE_TIME DATE DEFAULT SYSDATE NOT NULL
+);
+```
+
+主键:
+
+```sql
+PRIMARY KEY (PROD_DATE, DEVICE_CODE, PARAM_NAME)
+```
+
+说明:
+
+1. 一天内每设备每参数只保留一行状态
+2. `LAST_PARAM_VAL` 是最近一次采集值
+3. `CURRENT_TOTAL` 是当天累计值
+4. `RESET_COUNT` 记录当日回退次数
+
+---
+
+## 5.2 `DEVICE_DAILY_PRODUCTION`
+
+文件位置:
+
+```text
+zs_aucma-mes-back/aucma-base/src/main/resources/device_daily_production.sql
+```
+
+作用:
+
+1. 保存设备的日产量权威结果
+2. 让 `Board4.monthTotal` 不需要全月扫明细
+
+关键表结构:
+
+```sql
+CREATE TABLE DEVICE_DAILY_PRODUCTION (
+ PROD_DATE DATE NOT NULL,
+ DEVICE_CODE VARCHAR2(64) NOT NULL,
+ PARAM_NAME VARCHAR2(128) NOT NULL,
+ DAILY_PROD NUMBER(18,4) DEFAULT 0 NOT NULL,
+ RESET_COUNT NUMBER(10) DEFAULT 0 NOT NULL,
+ CALC_TIME DATE DEFAULT SYSDATE NOT NULL
+);
+```
+
+---
+
+## 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
+
+核心过程:
+
+```sql
+PROCEDURE FLUSH_RT_TO_DAY(
+ P_PROD_DATE IN DATE DEFAULT TRUNC(SYSDATE) - 1,
+ P_PARAM_NAME IN VARCHAR2 DEFAULT '机台状态-实际产出数量'
+)
+```
+
+核心逻辑:
+
+```sql
+MERGE INTO DEVICE_DAILY_PRODUCTION D
+USING (
+ SELECT PROD_DATE, DEVICE_CODE, PARAM_NAME, CURRENT_TOTAL, RESET_COUNT
+ FROM RT_DAILY_PROD_STATE
+ WHERE PROD_DATE = V_TARGET_DATE
+ AND PARAM_NAME = P_PARAM_NAME
+) S
+...
+
+DELETE FROM RT_DAILY_PROD_STATE
+ WHERE PROD_DATE = V_TARGET_DATE
+ AND PARAM_NAME = P_PARAM_NAME;
+```
+
+---
+
+## 5.4 `TRG_BDPV_202603_RT`
+
+文件位置:
+
+```text
+zs_aucma-mes-back/aucma-base/src/main/resources/base_device_param_val_202603_trigger.sql
+```
+
+作用:
+
+1. 自动采集分表新增数据后,自动更新 `RT_DAILY_PROD_STATE`
+
+当前版本针对:
+
+```text
+BASE_DEVICE_PARAM_VAL_202603
+```
+
+核心逻辑:
+
+```sql
+IF :NEW.PARAM_NAME <> '机台状态-实际产出数量' THEN
+ RETURN;
+END IF;
+
+IF :NEW.DEVICE_CODE LIKE 'OLD-%' THEN
+ RETURN;
+END IF;
+
+V_NEW_VAL := TO_NUMBER(:NEW.PARAM_VALUE);
+V_PROD_DATE := TRUNC(:NEW.COLLECT_TIME);
+```
+
+然后:
+
+1. 先查 RT 是否已有当日行
+2. 有则更新
+3. 无则插入基准行
+
+注意:
+
+1. 触发器只处理**新插入**的数据
+2. 对触发器创建前已存在的历史数据**不会自动回补**
+
+---
+
+## 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`
+
+关键 SQL:
+
+```sql
+DBMS_SCHEDULER.CREATE_JOB(
+ JOB_NAME => 'JOB_FLUSH_RT_TO_DAY',
+ JOB_TYPE => 'PLSQL_BLOCK',
+ JOB_ACTION => 'BEGIN PKG_DEVICE_PROD_CALC.FLUSH_RT_TO_DAY(TRUNC(SYSDATE) - 1, ''机台状态-实际产出数量''); END;',
+ REPEAT_INTERVAL => 'FREQ=DAILY;BYHOUR=0;BYMINUTE=10;BYSECOND=0',
+ ENABLED => FALSE
+);
+```
+
+---
+
+## 5.6 `PKG_DEVICE_PARAM_PARTITION`
+
+文件位置:
+
+```text
+zs_aucma-mes-back/aucma-base/src/main/resources/device_param_partition_auto_pkg.sql
+```
+
+作用:
+
+1. 自动创建下月分表
+2. 自动创建下月索引
+3. 自动创建下月触发器
+
+核心过程:
+
+```sql
+PROCEDURE ENSURE_MONTH_TABLE_AND_TRIGGER(
+ P_TARGET_SUFFIX IN VARCHAR2 DEFAULT TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 1), 'YYYYMM'),
+ P_SOURCE_SUFFIX IN VARCHAR2 DEFAULT TO_CHAR(TRUNC(SYSDATE, 'MM'), 'YYYYMM')
+)
+```
+
+核心逻辑:
+
+1. 校验后缀必须是 6 位数字
+2. 用当前月表克隆下月表
+3. 为下月表创建索引
+4. 为下月表创建 RT 触发器
+
+---
+
+## 5.7 `JOB_ENSURE_DEVICE_PARAM_PARTITION`
+
+文件位置:
+
+```text
+zs_aucma-mes-back/aucma-base/src/main/resources/device_param_partition_auto_scheduler_job.sql
+```
+
+作用:
+
+1. 每月月底自动执行 `PKG_DEVICE_PARAM_PARTITION.ENSURE_MONTH_TABLE_AND_TRIGGER`
+
+关键 SQL:
+
+```sql
+DBMS_SCHEDULER.CREATE_JOB(
+ JOB_NAME => 'JOB_ENSURE_DEVICE_PARAM_PARTITION',
+ JOB_TYPE => 'PLSQL_BLOCK',
+ JOB_ACTION => 'BEGIN PKG_DEVICE_PARAM_PARTITION.ENSURE_MONTH_TABLE_AND_TRIGGER; END;',
+ REPEAT_INTERVAL => 'FREQ=MONTHLY;BYMONTHDAY=28,29,30,31;BYHOUR=23;BYMINUTE=55;BYSECOND=0',
+ ENABLED => TRUE
+);
+```
+
+说明:
+
+1. 28/29/30/31 都尝试跑
+2. 包本身幂等,所以重复跑不会重复建
+
+---
+
+## 6. 自动采集设备完整链路
+
+完整链路如下:
+
+```text
+自动采集
+ -> BASE_DEVICE_PARAM_VAL_202603
+ -> TRG_BDPV_202603_RT
+ -> RT_DAILY_PROD_STATE
+ -> JOB_FLUSH_RT_TO_DAY
+ -> DEVICE_DAILY_PRODUCTION
+ -> Board4Mapper.xml
+ -> Board4ServiceImpl.java
+ -> Board4Controller.java
+ -> board4/index.vue
+```
+
+### 6.1 自动采集入月分表
+
+月分表规则由:
+
+文件:
+
+```text
+zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/support/DeviceParamTableRouter.java
+```
+
+关键代码:
+
+```java
+private static final DateTimeFormatter YM_FORMATTER = DateTimeFormatter.ofPattern("yyyyMM");
+private static final Pattern SUFFIX_PATTERN = Pattern.compile("^\\d{6}$");
+```
+
+写入表名计算:
+
+```java
+String tableName = deviceParamTableRouter.resolveWriteTable(
+ baseDeviceParamVal.getDeviceCode(), baseDeviceParamVal.getCollectTime());
+```
+
+如果是自动采集设备,结果会是:
+
+```text
+BASE_DEVICE_PARAM_VAL_202603
+```
+
+### 6.2 触发器更新 RT
+
+自动采集数据写入后:
+
+1. `TRG_BDPV_202603_RT` 自动触发
+2. 只处理:
+
+```text
+PARAM_NAME = '机台状态-实际产出数量'
+```
+
+3. 通过 delta-sum 更新:
+
+```text
+RT_DAILY_PROD_STATE
+```
+
+### 6.3 Board4 读实时表
+
+文件:
+
+```text
+zs_aucma-mes-back/aucma-report/src/main/resources/mapper/report/Board4Mapper.xml
+```
+
+#### `dayTotal`
+
+```xml
+
+```
+
+#### `deviceProductionList`
+
+```xml
+
+```
+
+#### `monthTotal`
+
+```xml
+
+```
+
+---
+
+## 7. OLD 设备完整链路
+
+完整链路如下:
+
+```text
+PDA
+ -> DmsMobileController
+ -> BaseDeviceParamValServiceImpl
+ -> BASE_DEVICE_PARAM_VAL
+ -> syncRtStateIfNeeded()
+ -> RtDailyProdStateServiceImpl
+ -> RT_DAILY_PROD_STATE
+ -> Board4Mapper.xml
+ -> board4/index.vue
+```
+
+### 7.1 OLD 判断统一入口
+
+文件:
+
+```text
+zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/support/DeviceParamTableRouter.java
+```
+
+关键代码:
+
+```java
+public boolean isOldDevice(String deviceCode) {
+ return deviceCode != null && deviceCode.startsWith("OLD-");
+}
+```
+
+### 7.2 DMS 入口
+
+文件:
+
+```text
+zs_aucma-mes-back/aucma-dms/src/main/java/com/aucma/dms/controller/DmsMobileController.java
+```
+
+当 OLD 设备走 PDA 更新当日产量时,最终进入:
+
+```java
+baseDeviceParamValService.upsertTodayParamValue(baseDeviceParamVal);
+```
+
+### 7.3 Java 同步更新 RT
+
+文件:
+
+```text
+zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/impl/BaseDeviceParamValServiceImpl.java
+```
+
+关键代码:
+
+```java
+if ("BASE_DEVICE_PARAM_VAL".equals(tableName)) {
+ int rows = baseDeviceParamValMapper.insertBaseDeviceParamVal(baseDeviceParamVal);
+ syncRtStateIfNeeded(baseDeviceParamVal, rows);
+ return rows;
+}
+```
+
+同步 RT 的核心:
+
+```java
+if (!deviceParamTableRouter.isOldDevice(entity.getDeviceCode())) {
+ return;
+}
+if (!DAILY_OUTPUT_PARAM_NAME.equals(entity.getParamName())) {
+ return;
+}
+rtDailyProdStateService.incrementProduction(
+ entity.getDeviceCode(), entity.getParamName(), newVal, entity.getCollectTime());
+```
+
+### 7.4 RT 累加逻辑
+
+文件:
+
+```text
+zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/impl/RtDailyProdStateServiceImpl.java
+```
+
+关键逻辑:
+
+```java
+if (state == null) {
+ insertState.setLastParamVal(newVal);
+ insertState.setCurrentTotal(BigDecimal.ZERO);
+ ...
+}
+
+if (newVal.compareTo(lastParamVal) > 0) {
+ delta = newVal.subtract(lastParamVal);
+} else if (newVal.compareTo(lastParamVal) < 0) {
+ delta = newVal;
+ resetCount += 1;
+}
+```
+
+---
+
+## 8. 夜间搬迁完整链路
+
+```text
+RT_DAILY_PROD_STATE(昨日)
+ -> PKG_DEVICE_PROD_CALC.FLUSH_RT_TO_DAY
+ -> DEVICE_DAILY_PRODUCTION
+ -> 删除昨日 RT
+```
+
+作用:
+
+1. 把历史天数据沉淀到 DAY
+2. 保持 RT 只关注最近实时状态
+3. 让 `monthTotal` 可以高效统计
+
+---
+
+## 9. board1 / board4 状态统计完整链路
+
+这里和产量不是同一条链路,但和 `board4` 一样重要。
+
+### 9.1 状态查询统一来源
+
+`board1/index.vue` 和 `board4/index.vue` 的设备状态统计统一复用:
+
+文件:
+
+```text
+zs_aucma-mes-back/aucma-base/src/main/resources/mapper/base/BaseDeviceParamValMapper.xml
+```
+
+对应接口:
+
+```text
+/baseDeviceParamVal/val/statistics
+/baseDeviceParamVal/val/deviceStatus
+/baseDeviceParamVal/val/deviceStartTime
+```
+
+### 9.2 双源状态统计 SQL
+
+关键片段:
+
+```xml
+WITH merged_param AS (
+ SELECT ...
+ FROM BASE_DEVICE_PARAM_VAL
+ WHERE device_code LIKE 'OLD-%'
+ UNION ALL
+ SELECT ...
+ FROM BASE_DEVICE_PARAM_VAL_${suffix}
+ WHERE device_code NOT LIKE 'OLD-%'
+)
+```
+
+这表示:
+
+1. OLD 状态数据取旧单表
+2. 自动采集状态数据取月分表
+3. board1 / board4 状态口径一致
+
+---
+
+## 10. trace / SPC / latest 参数监控完整链路
+
+## 10.1 latest 参数监控
+
+前端:
+
+```text
+zs_aucma-mes-ui/src/views/baseDeviceParamVal/val/index.vue
+```
+
+调用:
+
+```text
+/baseDeviceParamVal/val/latest
+```
+
+服务端:
+
+```text
+BaseDeviceParamValServiceImpl.selectLatestBaseDeviceParamValList(...)
+```
+
+流程:
+
+1. 默认给最近 24 小时时间范围
+2. 通过 `resolveReadTableSuffixes(beginTime, endTime)` 计算跨月表后缀
+3. `BaseDeviceParamValMapper.xml` 通过 `dualSourceFrom` 合并 OLD + 月分表
+
+## 10.2 trace 参数追溯
+
+入口:
+
+```text
+/baseDeviceParamVal/val/trace/list
+```
+
+服务端:
+
+```java
+Map params = buildStringRangeParams(deviceCode, paramCode, startTime, endTime);
+List list = baseDeviceParamValMapper.selectTraceList(params);
+```
+
+SQL:
+
+```xml
+
+ SELECT ...
+ FROM BASE_DEVICE_PARAM_VAL_${suffix}
+
+```
+
+## 10.3 SPC
+
+入口:
+
+```text
+/baseDeviceParamVal/val/spcData
+```
+
+服务端:
+
+```java
+List values = baseDeviceParamValMapper.selectParamHistoryValues(params);
+```
+
+流程:
+
+1. 先双源查询参数名称
+2. 再双源查询参数历史值
+3. 最后在 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`
+6. `zs_aucma-mes-back/aucma-base/src/main/resources/device_param_partition_auto_pkg.sql`
+7. `zs_aucma-mes-back/aucma-base/src/main/resources/device_param_partition_auto_scheduler_job.sql`
+
+---
+
+## 12. 脚本执行顺序
+
+按数据库首次部署/重建顺序,必须执行:
+
+1. `rt_daily_prod_state.sql`
+2. `device_daily_production.sql`
+3. `device_prod_calc_pkg.sql`
+4. 确保当前月分表 `BASE_DEVICE_PARAM_VAL_202603` 存在
+5. `base_device_param_val_202603_trigger.sql`
+6. `device_prod_calc_scheduler_job.sql`
+7. `device_param_partition_auto_pkg.sql`
+8. `device_param_partition_auto_scheduler_job.sql`
+
+说明:
+
+1. 当前月分表本身必须存在,自动建分表包不会替你补“当前月”
+2. 自动建分表包只负责“用当前月去准备下月”
+
+---
+
+## 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. 当前月触发器已经 `ENABLED`
+2. 自动采集今天也已经写了主参数
+
+如果这些数据是在触发器创建前插入的,那么:
+
+1. 历史数据不会自动补到 `RT_DAILY_PROD_STATE`
+2. 只有后续新增插入才会触发 RT 累计
+
+所以会出现:
+
+1. `board4` 先为空
+2. 等下一批采集数据来了后开始有值
+3. 但今天的数据会偏小,因为缺了触发器创建前的那一段
+
+---
+
+## 14. 当前最终结论
+
+本次改造从数据库存储过程到程序查询的完整链路已经打通:
+
+1. 自动采集设备:
+ - 写 `BASE_DEVICE_PARAM_VAL_YYYYMM`
+ - 由分表触发器更新 `RT_DAILY_PROD_STATE`
+2. OLD 设备:
+ - 写 `BASE_DEVICE_PARAM_VAL`
+ - 由 Java 服务同步更新 `RT_DAILY_PROD_STATE`
+3. 夜间:
+ - `JOB_FLUSH_RT_TO_DAY`
+ - 调用 `PKG_DEVICE_PROD_CALC`
+ - 将 RT 搬迁到 DAY
+4. 月底:
+ - `JOB_ENSURE_DEVICE_PARAM_PARTITION`
+ - 调用 `PKG_DEVICE_PARAM_PARTITION`
+ - 自动创建下月分表与下月触发器
+5. 程序查询层:
+ - `Board4` 查 RT / DAY
+ - `board1` 状态查询走双源三色灯
+ - `trace / SPC / latest` 走双源跨月分表查询
+
+换句话说,当前系统已经不是“单表直接统计”,而是:
+
+```text
+原始明细层
+ -> 实时累计层
+ -> 日汇总层
+ -> 程序查询层
+ -> 前端展示层
+```
+
+这就是本次从数据库存储过程到程序查询的完整闭环。最自律帅气聪明的臧辰浩