注塑车间 42 台设备,每台都接了采集网关。报警推送上线的头一周,值班群一晚上进了两百多条消息。第二天早上我问夜班班长老陈,昨晚 7 号机怎么样了,他愣了一下,说不知道有这回事。报警确实发出去了,也确实有人划到过那条消息,但没有人认领,两屏之后就冲没了。
这件事之后我把链路拆开看了一遍。发消息这一步早就做完了,技术上没有难度;真正缺的是消息发出去之后的那一段:谁负责看、看了要不要接、接了什么时候算完。这三件事没有系统承接,群就只是一个更吵的通知栏。
后来我们把报警改成了带按钮的卡片,认领动作回写到工单表,超时没人接就往上推一级。跑了三个月,夜间报警从发出到有人接的平均时间,从四十七分钟降到七分钟。下面把这套东西拆开写,重点不在机器人怎么发消息,而在状态怎么落地。
一、报警为什么在群里沉下去
1.1 一晚上两百条消息的群
那个群里有三十七个人:三个班组长、八个机修、两个计划,还有几个管理和供应商的人。消息来源不止报警,还有交接班留言、领料确认、临时调机通知。报警混在里头,和"3 号机模温到了"这种日常话术长得一样,没有视觉优先级。
我翻过一晚的聊天记录,两百一十三条消息里有二十九条是报警,其中十一条是同一个传感器反复抖动发出来的。那台设备当天在修,网关没断电,阈值一直踩着线,于是十分钟发一次。人一旦认定某类消息是噪音,就会连着真消息一起忽略,这是群的固有毛病,不是人的问题。
1.2 报警没有主
报警的对象是设备,可设备不会看消息。真正该处理的人,取决于这台设备在哪个班、归哪个机修、当时在不在厂里。这些信息采集系统里没有,排班表里有,但排班表是纸的,贴在车间办公室门后。
于是报警只能 @ 所有人,@ 所有人等于没有 @。我后来统计过一条规律:一条报警如果三十分钟内没人回"收到",它被处理的概率会掉一半以上。不是人懒,是三十分钟后现场已经进了下一个动作,回头处理需要重建上下文,成本比当时高得多。
1.3 三个断点
拆完发现断点有三个。发出这一端,报警没有分级,抖动和停机走同一条通道。中间这一段,消息没有归属,谁都能看,谁都不算责任人。后面那一段,处理完了没有回写,同样的问题下个月再来一遍,没人知道它来过。
三个断点里,中间那个是根。前端的降噪和后端的复盘都能慢慢补,可如果没有"这条报警归谁"这个字段,前后两端做得再好,也只是让噪音更精致一点。
二、把卡片做成一张工单
2.1 卡片上只放四个字段
我们试过一版信息很全的卡片:设备编号、报警码、阈值、当前值、持续时长、班次、责任人、历史同类次数,一共十一行。发出去之后,机修的反馈是"看着累"。手机上一条消息占满一屏,划过去就当看过了。
第二版砍到四个字段:哪台设备、什么现象、发生时间、现在归谁。设备编号用车间里的叫法,比如"7 号机"而不是"INJ-07";现象用一句话,比如"模温连续 6 分钟高于上限"。剩下的细节塞进卡片底部一个按钮里,点进去才看。信息密度降下来,反而有人愿意读了。
2.2 消息卡片的结构
卡片走群机器人的消息接口,主体是一段 JSON。下面这份是现在用的结构,字段做了简化,真实环境里还带签名字段和重试标记:
json
{
"msgtype": "interactive",
"card": {
"header": {
"template": "orange",
"title": { "tag": "plain_text", "content": "设备报警 · 7号机" }
},
"elements": [
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": "**现象**:模温连续 6 分钟高于上限\n**发生**:03:12\n**归属**:夜班机修组"
}
},
{
"tag": "action",
"actions": [
{
"tag": "button",
"text": { "tag": "plain_text", "content": "我来处理" },
"type": "primary",
"value": { "act": "claim", "alarm_id": "A20260314007", "ver": 3 }
},
{
"tag": "button",
"text": { "tag": "plain_text", "content": "不是我的班" },
"type": "default",
"value": { "act": "reject", "alarm_id": "A20260314007", "ver": 3 }
}
]
}
]
}
}
这段配置里有两个东西是后来才想明白的。一个是 ver 字段,卡片的版本号,认领时带上它,服务端就能判断这条卡片是不是过期了,避免两个人同时点、把同一条报警认给两个班。另一个是那个"不是我的班"按钮,看着像给推责任开了口子,实际上是整套流程里很关键的一个设计------它把"这条不该给我"变成了一个可统计的动作。
2.3 按钮回传必须走服务端
群机器人的按钮回调会带一个用户标识,这个标识不能直接当工号用。我们维护了一张人员映射表,把群里的标识和排班系统里的工号对上,对不上的请求一律拒掉,记一条日志。
认领的写入口只有服务端一个:收到回调,先校验版本号,再用一条带条件的更新落库,条件是"当前责任人为空且版本号一致"。更新影响行数为零,就说明有人抢先了,回一条"已被某某认领"的提示。这个细节很土,但它是两个人同时点击不出错的全部原因。
三、认领之后的状态机
3.1 四个状态
报警在库里只有四个状态:待认领、处理中、已关闭、已挂起。待认领超过十五分钟没人接,升级到班组长;超过四十五分钟,升级到设备主管。处理中超过两小时没有进展记录,提醒一次;超过四小时,自动挂起并生成一条复盘任务。
状态少是有意的。我们试过七个状态的版本,加上了"已派工""待备件""待外协",结果现场记不住,机修在群里问"这个该点哪个"。四个状态加一个自由文本的进展记录,现场反而用得顺。
3.2 升级不能靠喊
升级动作做成定时任务,每五分钟扫一次待认领的表。这里有个坑:升级消息如果还是 @ 所有人,等于把噪音从一个群搬到另一个群。我们的做法是升级时直接 @ 具体的人,用映射表查到班组长在群里的标识,只 @ 一个。
另一个坑是夜里。凌晨两点的升级消息,如果对象是白班主管,那就是纯骚扰。所以升级链上每一级都配了生效时段,夜里只升到值班负责人,白天的链路到早上八点再补一遍汇总。
3.3 关闭要有证据
"已关闭"这个动作我们要求带两样东西:一句处理说明,一张现场照片。照片不是为了追责,是为了让复盘的时候能看懂。有一台设备连续三周报同一条报警,看照片才发现是传感器探头结了料垢,擦一下就好的事,因为没人写说明,硬是当成电路故障查了三轮。
照片存在文件服务里,工单表只存路径和上传人。这条数据在季度复盘时特别值钱:同类报警按设备、按报警码聚一下,前五个高频项基本就是技改清单。
四、跑了三个月的变化
4.1 几个能看的数
夜间报警从发出到有人认领的平均时间,从四十七分钟降到七分钟,中位数是四分钟。待认领超过十五分钟的比例,从三成降到百分之六。重复报警,也就是同一设备同一报警码二十四小时内再发的数量,下降了四成,因为关闭时必须写说明,写的人会顺手把根因填上。
还有一个不太正经的指标:群里"收到"这两个字出现的次数掉了八成。这不是坏事,认领按钮替掉了口头确认,人不用再打字表态。班组长的手机也安静了,他说以前晚上听见震动就醒,现在只有真事才响。
4.2 没解决的部分
误报还是多。采集网关的阈值按设备型号统一配,同一型号不同模具的工况差别很大,注定有一批报警是假的。我们的处理办法是给每条报警加一个"误报"标记,标记三次以上的组合自动进降噪候选,由工艺的人复核后再调阈值。这条路走得很慢,但没有更好的办法。
另外,外协维修队不在群里的那部分,闭环是断的。他们有自己的一套报修渠道,我们这边的报警关不掉,只能挂起。这块后来单独做了一条对账流程,不在这一篇的范围里。
常见问题
问:报警卡片用什么形式发比较合适,纯文本还是卡片?
答:要看有没有回写动作。纯文本适合通报,人看完就完了;只要需要人点一下表示"我接了",就该用卡片,因为按钮回调带了用户标识和自定义参数,能直接落库。纯文本让用户回复关键词也行,但要处理错别字、多回复、回复到别的消息上这些情况,维护成本反而更高。
问:两个人同时点认领,怎么保证不会认给两个人?
答:靠数据库的条件更新,不靠应用层判断。更新语句带上"责任人为空"和"版本号一致"两个条件,影响行数为零就代表没抢到,返回提示即可。应用层先查后写一定有并发窗口,尤其在群里多人同时点的时候。
问:升级链怎么配才不烦人?
答:每一级都配生效时段和升级间隔,夜里只留一个值班负责人。升级消息里必须 @ 具体的人,不要 @ 所有人。另外留一个"我今天不看这个群"的开关,让人能主动退出链路,比让人静音整个群要好------静音之后连该看的也看不到了。
问:报警关闭要求传照片,现场抵触怎么办?
答:把要求降到一张,并且允许拍糊。真正卡人的不是拍照,是填表。我们把处理说明做成三个选项加一句话,点两下就能提交,抵触很快就没了。如果一开始就要求写两百字分析,这条规则活不过两周。