· compliance· 约 24 分钟精读
Tagworks Appliance Class Hinge Form Threshold Recall — Technical Analysis | KeyRotate Technology
TagWorks 把设备类别存成资产上的一个三成员枚举。它决定表单出现哪个输入框、哪条读数进入判定、召回落在哪一格;而记录本身没有这个字段,两条数据入口又会把它静默改写。本文按源码核对这一格的四个下游与两次丢失。
地下二层的配电间没有窗。检验员把三台设备排在临时工作台上:一台铝壳角磨机,铭牌上印着接地符号;一台塑料外壳的电池充电器,铭牌上是两个同心正方形;第三台是机柜风扇,金属外壳上单独引出一根黄绿线,接到柜体的接地排。
在 TagWorks 里新建这三条资产时,他要动的只有一行:Class。三个选项,Class I (earthed)、Class II (double insulated)、Functional。选完之后表单会长成不同的样子:角磨机多出一个接地电阻输入框,充电器和风扇没有。半年后有人在报告里翻到这三行,看到的也是三种不同的判定过程。
类别是设备在标准里的身份,也是这个应用里唯一一个能同时改变输入位、判定门限与召回周期的字段。本文按源码核对这一格的四个下游,以及它在两条数据入口上被改写的两次。
一、三格里有一格不是标准类别
IEC 61140 把防电击措施分成四类,其中 Class 0 已被淘汰,现场还在用的是三类:Class I 依靠基础绝缘加保护接地,故障电流经保护导体泄放;Class II 依靠双重绝缘或加强绝缘,没有保护导体;Class III 依靠安全特低电压,从电源侧就把电压降下来。AS/NZS 3760 的现场检验对象主要是前两类,检验项目随类别分岔:Class I 要做接地连续性,Class II 不做。
TagWorks 的枚举只有三个成员:
classI / classII / functional
前两个与标准对得上,第三个不是标准类别。在代码里它的显示名是 “Functional earth”,注释写的是 “earth continuity matters functionally, not for safety”——功能性接地,用于信号参考、屏蔽或防静电,不承担保护导体的职责。全工程检索 Class III 与 SELV 没有任何命中,也就是说标准体系里的第三类在这套模型里没有位置,取而代之的是一格现场口径。
这个选择本身可以解释:便携式电动工具与机柜设备里,功能性接地确实常见,而安全特低电压设备通常不进入 PAT 台账。问题出在表达上。三个成员被放在同一个选择器里,标签分别是 “Class I (earthed)”、“Class II (double insulated)”、“Functional”,没有任何一行字说明第三格不是安全等级。操作者看到的是一个三选一的分类问题,实际上前两格回答”怎么防电击”,第三格回答”这根线拿来做什么”。
更直接的结果是:一台名字里带 earth 的设备,在表单里恰恰拿不到 earth 的输入框。因为它与 Class II 走的是同一条分支。
二、门一:输入位由类别决定
检验表单的 Measurements 段只有三个输入位,其中一个的出现条件写死在类别上:
- 接地电阻,仅当
asset.applianceClass == .classI时渲染; - 绝缘电阻,恒有;
- 负载电流,标签写着 “A, evidence only”。
判定入参的组装用的是同一个条件:非 Class I 的资产,接地值一律传 nil,requiresEarth 传 false。即使某条记录里存着一个接地读数,它也不会进入这次判定。
| 类别 | 接地输入位 | 接地进入判定 | 判定必填读数 | 存为 PASS 的前置 |
|---|---|---|---|---|
| Class I | 出现 | 是,门限 100 mΩ | 接地 + 绝缘 | 两条读数都合格,且目视无缺陷 |
| Class II | 不出现 | 否,传 nil | 绝缘 | 绝缘合格,且目视无缺陷 |
| Functional | 不出现 | 否,传 nil | 绝缘 | 绝缘合格,且目视无缺陷 |
Class I 上这条闸门是硬的,不是提示。接地框留空时,判定返回”未测量”,预测结论为空,保存按钮直接不可用;手动把结论改成 Pass 也会被另一条规则挡住——手动合格不能替代缺失或不合格的必填读数。所以”接地没测”在 Class I 上只有两个合法出口:补测,或者存成不合格并写缺陷说明。
Class II 与 Functional 在测量面上完全同形:两个输入位,一条绝缘读数,一个必填项。二者的差别只在下游的召回格子,这一点后面再说。
负载电流是第三个输入位,也是最容易被误读的一个。它只校验”能解析成数字、有限、非负”,不参与任何判定,报告的证据行也不引用它。它可以被记录,不能改变结论。把它放在 Measurements 段里,视觉上与另外两条读数同级,但它实际上是一条备注。
三、门二:一条门限,两种物理测试
判定函数只有两个常量:接地电阻上限 100 mΩ,绝缘电阻下限 1.0 MΩ。求值顺序分三段:先看两条读数有没有越界,任意一条越界即判不合格;再看必填项有没有缺,缺了判”未测量”;最后才判合格。这个顺序是非对称的——判不合格只需一条坏读数,判合格需要全套。方向是对的:现场宁可少放行,不可多放行。
问题不在数值,在归属。100 mΩ 被写成了”Class I 设备”的属性,而新建资产时能填的只有六个字段:名称、标识、站点、类别、环境、风险。没有线长,没有截面积,也没有”这次测的是哪一段”的选择。于是同一个输入框同时承担了两种物理上不同的测试:
- 设备段:从插头接地脚到可触及金属外壳,测的是保护导体与外壳之间的连接,几十毫欧是常见值;
- 软线回路:从插头接地脚经过整条软线到设备端,包含导体本身的电阻,必须按长度算。
应用的手册里其实写着换算率。Thresholds 页签的第一行是 “Flexible cord appliances ≤ 1.0 Ω (1000 mΩ)“,注释是 “0.1 Ω for plug/socket + cord resistance (0.02 Ω/m)“。按这个 0.02 Ω/m,5 米软线的导体电阻正好是 0.1 Ω,把整个门限用完;10 米是 0.2 Ω;25 米是 0.5 Ω。同一本手册的 Calculator 页签里,接地输入框下面写的是 “Limit: ≤ 100 mΩ (0.10 Ω)“。两个页签,两个数,差十倍;中间没有一句话解释它们分别对应哪一段。
把这两个事实叠起来,会得到一个在现场很具体的结论:一条 10 米长、1.0 mm² 的 Class I 延长线,如果如实测出 0.2 Ω,引擎判不合格;而手册的表格接受 1.0 Ω 以内的读数。线越长,偏差越大。判定层没有线长输入,也就没有扣除这一步。
需要说清楚边界:0.1 Ω 这个数本身不错,如果测的是插头到外壳那一段。错的是同一个输入位承担了两种测试,而界面没有让操作者声明自己在测哪一种,也没有为软线留下长度这个变量。这条门限只落在 Class I 上——Class II 与 Functional 根本没有这个输入位,所以永远碰不到它。类别在这里决定的不是”门限多严”,而是”一条多数软线都过不去的门限是否适用”。
四、门三:类别一格,召回翻倍
召回周期由类别、环境、风险三个输入共同决定,间隔表本身已在另文逐格核对,这里只取类别这一维:
| 环境 | 风险 | Class I | Class II | 比值 | Functional |
|---|---|---|---|---|---|
| 办公室 | 低 | 12 | 24 | 2.0 | 12 |
| 办公室 | 高 | 6 | 12 | 2.0 | 6 |
| 长期轻载 | 低 | 12 | 24 | 2.0 | 12 |
| 长期轻载 | 高 | 6 | 12 | 2.0 | 6 |
| 建筑工地 | 低 | 6 | 12 | 2.0 | 12 |
| 建筑工地 | 高 | 3 | 6 | 2.0 | 6 |
| 工业环境 | 低 | 12 | 24 | 2.0 | 12 |
| 工业环境 | 高 | 6 | 12 | 2.0 | 6 |
单位是月。八格全部是二倍关系,没有例外。这不是巧合,而是两套矩阵的构造方式:Class II 的每一格都正好是 Class I 的两倍。换句话说,代码把”类别”当成了间隔的一档,而不是”是否需要接地测试”的开关。两个本来独立的判断被压进同一条乘法关系里。
Functional 走的是第三条分支:只看风险,不看环境。四个环境塌成一个轴,建筑工地与办公室得到同一个数。于是同样在恶劣工地,Class I 的高风险设备 3 个月一回,Functional 的高风险设备 6 个月一回;而 Functional 在表单里连接地框都没有。
这一格的实际重量在于:类别只要被判错一次,召回期直接翻倍,而且翻倍发生在全部八个格子上,不因环境而缓解。
五、记录里没有这一格
翻遍记录模型,找不到类别字段。一条检验记录存的是资产 ID、日期、结论、接地读数、绝缘读数、负载电流、检验员、备注、照片文件名、记录种类、关联记录、放行授权人,外加几项可选的仪器溯源字段。类别不在其中。
判定函数因此需要一个外部参数:每条记录求值时,都由调用方把资产当前的类别传进去。资产详情页取当前资产,仪表盘取当前资产,报告汇总取当前资产,CSV 导出取当前资产。全工程九处类别判定,五处在判定链上(表单三处、记录模型两处),四处在这本手册里。
这带来一个不太好描述的性质:一条记录的结论不是它自己的属性,而是”这条记录 × 当前类别”的即时函数。今天它之所以稳定,靠的不是记录冻结了类别,而是没有修改类别的入口——全工程检索 updateAsset 与任何编辑视图,结果为空。一个不变量靠”没有入口”维持,是脆的:任何一天补上一个编辑页,所有历史结论都会跟着当前值重算。
而删除是唯一能”改类别”的路径。删除函数只有两行,把资产从这个数组里移除,再把该资产的全部记录从另一个数组里移除。没有确认,没有留痕,也没有把记录转成只读档案。于是”分类纠错”与”历史留存”在数据结构上互斥:要么带着错误类别继续用,要么扔掉全部检验历史重建一条。
六、第一次丢失:标签上写了,扫码不读
打印标签时,二维码的载荷是一段竖线分隔的字符串:品牌前缀、标识、名称、类别、检验日期、到期日、结论,共七段。截短后是这样:
TAGWORKS|ID:TG-1|CLASS:classI
类别写在 CLASS: 后面,取值是枚举的原始值。纸面标签上也把类别印在设备名后面。也就是说,一张标签同时用图像和字符串两种方式携带了类别。
扫码回填只取其中一段。规范化函数做的事是:去掉首尾空白;如果不是以 TAGWORKS| 开头就原样返回;否则按竖线切分,跳过第一段,找到第一个以 ID: 开头的字段,取它后面的值。类别、名称、日期、结论全部被丢弃。扫码结果进入新建表单时,只填进标识框,类别保持表单的默认值 classII。
于是一条完整的路径成立了:一台 Class I 设备的旧标签被重新扫进台账,新资产是 Class II。它不再要求接地读数,召回期从 12 个月变成 24 个月。标签上明明写着 CLASS:classI,读标签的那一端不看它。
这不是解析器的缺陷,是载荷设计与读取设计之间没有契约。二维码里装的是一段给人看的描述性字符串,不是一个约定好字段顺序的结构;写入端可以往里加任何一段,读取端只认其中一段,两边都不需要同步。
七、第二次丢失:导出写 classI,导入读回 classII
资产 CSV 有 26 列,第 4 列是类别的原始值,写出来是 classI、classII 或 functional——枚举的 rawValue,没有空格。
导入器把这一格转成小写,然后按短语匹配:
- 含
class ii、class 2、double,或整串等于ii、2,判为 Class II; - 含
class i、class 1、earth,或整串等于i、1,判为 Class I; - 含
functional,判为 Functional; - 其余一律落到默认值 Class II。
classi 这一串里没有空格,也不等于 i,更不含 earth,所以三组条件全部不命中,落进兜底分支。也就是说,这个应用导出的 CSV,再导入回来时,Class I 会变成 Class II。逐条对一遍:
| 输入字符串 | 命中分支 | 解析结果 | 应得结果 |
|---|---|---|---|
classI | 无 | Class II | Class I |
classII | 无 | Class II | Class II(靠兜底碰对) |
Class I (earthed) | class i | Class I | Class I |
Functional earth | earth | Class I | Functional |
Class III | class i | Class I | 无对应成员 |
SELV | 无 | Class II | 无对应成员 |
三个错误方向各不相同。Class I 掉到 Class II,是往宽的方向掉:不要求接地读数,召回翻倍。Functional earth 升到 Class I,是往严的方向掉,但严得没有依据——表单会要求一条接地读数,而这台设备没有保护导体。Class III 与 SELV 在模型里没有位置,一个被当成 Class I,一个被当成 Class II。
最需要留意的是 Functional earth 那一行。表单要求一条接地读数时,操作者只有三种选择:去找一个不存在的测试点、留空导致无法保存、或者填一个数字。任何一个不超过 100 mΩ 的数字都会被判定层接受为合格证据。口径错误在这里变成了证据错误,而这条证据会被写进台账、印进报告。
兜底方向也值得单独说。未知值一律取 Class II,也就是最宽的一档:不要求接地、办公室低风险下 24 个月。解析失败时选择一个方向,等于对未知设备做了最宽松的假设。工程上更稳的做法是拒绝整行并报出行号,让人来决定。
八、报告上的类别与证据行
报告里的逐站点资产表有六列:资产、标识、类别、最近检验、结果、到期。列宽依次是 170、90、70、110、70、80 点,字号 9 点,每格的行高 16 点,绘制方式是直接把字符串画进固定矩形,超出宽度的部分换行,超出高度的部分被裁掉,没有省略号。
类别列只有 70 点。按 9 点字号的平均字宽估算,一行大约放得下 15 个字符;而最长的类别显示名是 “Class II (double insulated)“,27 个字符。第二行只画出上半截就被矩形裁断,也没有省略号。纸面上留下的是半句类别名,读者需要靠上下文猜它是哪一类。
证据行的处理又是另一套。判定层对非 Class I 的资产把接地读数置为 nil,报告层不置:接地读数只要存在,就无条件展开成一段 “Earth 350.0 mΩ”,与绝缘、仪器型号等片段串在同一行,与结果列印在同一个区块里。同一份记录、同一个字段,在判定链上被排除,在排版链上被打印。
在当前界面下,Class II 记录不会产生接地读数,所以页面上不会真的出现”接地 350 mΩ”与 PASS 并排的组合。但代码里没有任何约束阻止这种组合,而约束的缺失本身就是问题:判定用了哪些读数、报告该印哪些读数,本该来自同一份清单,现在来自两段各写各的代码。
九、四条工程线
第一,把类别写进记录。检验记录增加一个”测试时类别”字段,判定只读这个字段;资产上的类别只影响下一次测试。这样历史结论被冻结在记录里,将来无论谁加了编辑入口,旧记录都不会被重算。
第二,入口不猜。导入遇到无法识别的类别字符串时拒绝该行并报出行号与列名,而不是兜底成 Class II。扫码时解析 CLASS: 段;如果标签声明的类别与表单默认值不同,就在新建页显示一行提示,让操作者确认,而不是静默采用默认值。
第三,把门限拆开。接地连续性分成”设备段”与”软线段”两个输入;后者接受线长,按手册自己给出的 0.02 Ω/m 折算,门限回到 1.0 Ω。判定函数接收的是折算后的余量,而不是一个孤立的读数。
第四,一处出字符串。下拉、详情页、CSV、标签、报告共用同一个格式化函数;报告列宽由同一份长度预算约束,长名称要么缩写要么换行,不能靠裁剪收场。
还有一条属于手册:阈值表里的字符串应当由常量生成,Calculator 的选择器要么补上第三类,要么明确写出”本计算器只覆盖两类安全类别”。现在资产模型有三格,计算器只有两格,两处对同一台设备的说法不一致。
十、结语
类别是这套台账里信息量最大的一个字段,也是最容易被当成装饰的一个字段。它决定了表单长什么样、哪条读数算数、多少个月后要再来一次;而它自己既不在记录里,也不在扫码的解析范围里,还在导出的往返中被写成另一个值。
把这一格守住,不需要新的服务、新的权限或新的存储格式。需要的只是三件事:让记录自己记住测试时的类别,让入口在不确定时拒绝而不是猜测,让判定与报告读同一份读数清单。
参考资料
- [1] Standards Australia & Standards New Zealand. AS/NZS 3760 在用电气设备检验与测试. as-nzs-3760
- [2] International Electrotechnical Commission. IEC 61140 防电击保护通则. iec-61140
- [3] Safe Work Australia. 工作场所电气风险管理实务守则. model-code
- [4] WorkSafe Victoria. 电气设备检验与挂牌. worksafe-vic
- [5] Health and Safety Executive. 低风险环境便携电气设备维护 INDG236. indg236
- [6] Apple. NSString 固定矩形绘制. nsstring
- [7] IETF. RFC 4180 逗号分隔值格式. rfc4180
- [8] ISO. ISO/IEC 18004 二维码符号规范. iso-18004