ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

当数据没有单一真相源:用“置信度降级链 + 纯函数决策入口“收拾脏数据

当数据没有单一真相源:用“置信度降级链 + 纯函数决策入口“收拾脏数据 ## 背景你以为的字段其实根本不存在做企业数据治理时最常被 PM 问到的一句话是这条工单属于哪个部门听起来像查一个字段但真实世界里它根本不是字段。工单系统里只有一串互相打架的**间接信号**工单号前缀、处理人的工号、处理人姓名、业务系统名、表单里填的部门、以及某个 depName 备注。每一个都**可能准、也可能错**而且没有任何一个是权威真相源。很多脚本的做法是写一大坨 if/elif 层层嵌套最后再加一句 else: 未匹配。问题不在于它能跑而在于1. **谁覆盖谁说不清**——改一行逻辑上周对的这周就错了。2. **出错了没法复盘**——你不知道某条记录到底是被哪条规则命中的。3. **没法单测**——函数里塞满了全局状态和副作用。本文给一套我在真实周报流水线里验证过的模式**置信度降级链 单一纯函数决策入口**。它把猜测数据变成了一件可审计、可回归、可测试的事。## 核心思想把每一个信号源按可信度从高到低排成一条链| 优先级 | 信号源 | 可信度 | 说明 || --- | --- | --- | --- || 0 | 工单号常量前缀 | 高 | 如 OPS- 开头一定是运维部 || 1 | 处理人工号 | 高 | 工号能精确反查到部门 || 2 | 处理人姓名 | 高 | 姓名可能在多部门重名需谨慎 || 3 | 业务系统关键词 | 中 | ERP堡垒机映射到部门长词优先 || 4 | 表单部门字段 | 中 | 用户手填可能错 || 5 | depName 子串 | 低 | 备注里蹭到的字兜底用 || - | 全部失败 | — | 标记为 unmatched绝不瞎猜 |关键纪律**高优先级的命中直接返回低优先级只做兜底每一步都记录命中的策略名**。## 代码实现pythonfrom dataclasses import dataclassfrom typing import Dict, List, Optional, Tupledataclassclass DeptResult:dept: Optional[str]ref: str # 命中了什么值工号/关键词/前缀strategy: str # 用哪条策略命中的confidence: str # high / lowHIGH_CONF {prefix, user_id, name}LOW_CONF {keyword, form_dept, depname, unmatched}# 业务系统关键词 → 部门长词必须排在短词前匹配KEYWORD_MAP {供应链: 供应链事业部,供应链: 数字化供应链事业部,堡垒机: 智能数据,ERP: ERP,}SIX_DEPTS [智能数据, 供应链事业部,创新事业部, 云达]# 0. 常量前缀规则最高优先级覆盖一切PREFIX_RULES: List[Tuple[str, str]] [(ops-, 智能数据中心)]def resolve_dept(row: dict,user_index: Dict[str, str],name_index: Dict[str, str]) - DeptResult:六层降级的【单一决策入口】纯函数无副作用可单测。biz_no str(row.get(biz_no, )).lower()# Strategy 0: 常量前缀for pfx, d in PREFIX_RULES:if biz_no.startswith(pfx):return DeptResult(d, pfx, prefix, high)# Strategy 1: 处理人工号uid str(row.get(handler_id, )).strip()if uid in user_index and user_index[uid] in SIX_DEPTS:return DeptResult(user_index[uid], uid, user_id, high)# Strategy 2: 处理人姓名name str(row.get(handler_name, )).strip()if name in name_index and name_index[name] in SIX_DEPTS:return DeptResult(name_index[name], name, name, high)# Strategy 3: 业务系统关键词长词优先避免短词吞长词text .join(str(row.get(k, )) for k in(sys_name, title, service_name))for kw in sorted(KEYWORD_MAP, keylen, reverseTrue):if len(kw) 2 and kw in text:d KEYWORD_MAP[kw]if d in SIX_DEPTS:return DeptResult(d, kw, keyword, low)# Strategy 4/5: 表单字段 / depName 子串略思想相同# ...return DeptResult(None, , unmatched, low)注意两个工程细节- **sorted(KEYWORD_MAP, keylen, reverseTrue)**关键词匹配必须长词优先。否则供应链会先吞掉数字化供应链把本该归到供应链事业部的记录错归到别处。这是所有关键词路由场景的通用坑。- **返回值带 strategy 和 confidence**这一步让每一次判定都可解释。后面接一个回归检测见本系列第 2 篇就能在低置信度猜测悄悄变多时报警。## 进阶把覆盖逻辑也收口成纯函数真实项目里往往先有一个初算结果再用更精确的信号去覆盖它比如用运维处理人反查的部门覆盖关键词猜的部门。这时要警惕**别在代码四个角落各写一份覆盖 if**。收口成一个带开关的纯函数pythondef resolve_department(row, *, baseNone, base_strategyunmatched,ops_aliasNone, enable_ops_aliasTrue):dept, ref, strategy base or (None, , unmatched)if dept is None:r resolve_dept(row, USER_IDX, NAME_IDX)dept, ref, strategy r.dept, r.ref, r.strategy# 阶段2~4按固定优先级叠加覆盖级后者覆盖前者if enable_ops_alias and ops_alias in USER_IDX:dept USER_IDX[ops_alias]; strategy user_by_cacheconf high if strategy in HIGH_CONF else lowreturn DeptResult(dept, ref, strategy, conf)base 入参让先初算、再精炼的语义被精确复刻且**调用方可以按需开关每一级**不同流水线阶段可用信号不同。## 小结- 没有真相源时**不要猜要分级猜**并全程记录用了哪一级。- 决策逻辑必须是**纯函数 单一入口**覆盖逻辑也收口别散落。- 给每个结果打**置信度标签**为后面的回归闸铺路。
返回列表