记录一次报表数据口径校准:WORKER_NUM 不是 ×10(2026-09-14)

不要急着改前端,先拿 SQL 把 572 万行逐行比对钉死——老表的 WORKER_NUM 就是真实人数,上一版却按 ×10 把它除成了 0。

2026-09-14 这天挺满。上午刚把 Hyperliquid testnet 的 drift 3341.32% 告警挂出去,下午就被叫去修一个 MES 报表:工位利用率、出勤时间、有效工时,显示得全不对。表面看是前端渲染有问题,实际一查,口径从根上就反了。

一、问题现象:所有指标都长得不对劲

接下活的时候,用户给的是 100.64.0.2:3306 上 m3_ai_data 库的 work_load_0521 表,里面有示例数据。前端报表展出来的工位利用率、出勤时间、有效工时,没一个看着合理。当时我没急着动逻辑,先让脚本把表的概况拉出来。

这一拉就发现问题不小:

  • AREA 一共 14 个,前端只认 5 个;
  • MAIN_AREA 分布是 TST1 约 450 万行、TST2 约 77 万行、TST3/TST4 各约 22 万行;
  • WORKER_NUM 的值是 '0'~'7',看着像真实人数,但我们上一版却把它当成“人数×10”来除;
  • WORKLOAD 看起来就是人数乘 10,像是人·秒。

当时最刺眼的是:WORKER_NUM 的离散值不是 10 的倍数。 这直接推翻了我之前的口径判断。

二、先别改代码,用 SQL 把口径钉死

我没继续猜,直接写了几条 SQL 把 work_load_0521 全表逐行比对。这张表示例数据一共 5,728,901 行,我跑完得到的结果很硬:

结论 SQL 实测
workload = worker_num × 10 吻合率 **100.0000%**,分产线 TST1/TST2/TST3/TST4 各自也都是 100%
worker_num = 真实人数 SELECT DISTINCT WORKER_NUM → [0,1,2,3,4,5,6,7],不是 10 的倍数
0 人时 workload 必为 0 5,154,083 / 5,154,083 行成立
采样节奏 SCBX 相邻时间差 [(10.0, 8226次), (11.0, 375次)],跨度÷(条数-1)=10.04s

单看 SCBX 单日(2026-07-24)也能对上:总条数 8602,有人条数 2171,Σworker_num=3819,Σworkload=38190,两者比值正好 10.0000。

到这里基本可以下结论:worker_num 是真实人数,workload = worker_num × 10(人·秒)。 用户后来问“worker_num 是人数,workload 是人数乘以 10 秒的工时,是不是这么算的?”——我的回答是:对,但必须按这张老表自身口径来,不能套用 Doris 那张“人数×10”的表。

三、找到 4 个真 bug:一个反向除零,三个硬编码

口径钉死后,之前“显示都不对”的原因就全浮出来了。一共四个:

  1. 上一版 WORKER_SCALE=10 写死,把真实人数反向除成 0。 老表 WORKER_NUM 本来就是人数,再除 10,一到报表就变成 0。
  2. 工位清单前端硬编码 5 个。 实测 TST1 是 SCBX / SCZZ / DCZZBX / GJQ / ZCZZ,但 TST2 是 GCJC / WLQ / DCZZBX / ZCZZBX / SCZZBX / ZYZS,TST3/TST4 又是 GJQ / UNKNOWN / CG_ZZBJ / CG_ZZBX / CG_ZZTS。三条产线工位集合完全不同,硬编码 5 个必然错位。
  3. 出勤时间与有效工时数值倒挂。 比如出勤 43340(去重秒×10)对上有效 63390 人·秒,单位一混乱,前端就出 “出勤 12.04h < 有效 17.6 人·时” 这种反直觉结果。
  4. 计划出勤硬编码,且文案与配置矛盾。 SCBX 实测峰值才 4 人,但前端文案写“SCBX 2人×16h”,后端却按 8 小时算。

四、v3.0 修复:自动识别、动态工位、自适应采样

我直接重写后端 v3.0。

第一件事是把 WORKER_NUM 口径改成自动识别。 不再写死 ×10 或 ÷10,而是采样最近 800 条记录判定倍数。老表实测自动得到倍数 1(人数),Doris 表自动得到倍数 10。这样同一套代码两套口径都兼容。

第二件事是去掉工位硬编码,改成按 MAIN_AREA 查全量工位。 前端不再只认 5 个,而是动态拉取。TST2 拿到 6 个工位后,矩阵形状立刻变成 6×24 / 6×48,图形不再错位。

第三件事是按工位自适应采样间隔。 本来全线按 10 秒一条算,但实测 GCJC 工位记录数 17146,是其他工位的 2 倍,说明它采样是 5 秒一条。固定 10 秒会把 GCJC 的“作业时长”算错一倍,改成按工位自身时间间隔来算之后,这个坑也平了。

第四件事是把 API 地址改成自动识别。 本地调试 localhost 没问题,但服务器或者其他电脑访问时,localhost 会指向他们自己,接口全 404。改成动态判断后,端到端才算真正打通。

v3.0 上完后,我用同样 572 万行示例数据回测:动态工位、人数系数、自适应采样全部生效,TST2 动态 6 工位、矩阵形状 6×24/6×48,指标不再倒挂。

五、遗留:有些账还没完全算清

虽然报表口径已经校准到能看,但还有几件事没做完:

  • GCJC 的 5 秒采样还没有在全表范围验证覆盖率。 目前只是从记录数推断是 5 秒,还没单独拉出来跟设备日志对时间戳。
  • 计划出勤的文案和后端 8 小时口径还没有统一。 前端还写着“16 小时”,得找业务确认到底按哪个,别改完又出现新的倒挂。
  • 老表 work_load_0521 与 Doris 表的倍数差异,还没落到正式配置。 现在是代码里自动识别,但我更想把它查出来写进字典,避免下次又有人手动改错。

下一步:把前两个遗留问题清掉,给这套报表补一个口径自检脚本,每天跑一次 WORKER_NUM=人数、workload=人数×10 的全表吻合率,不再让这种反向除零的事悄悄发生。