外观
一份盈亏报告的脱敏:改哪些、不改哪些、怎么验
任务:一份含真实科室、真实费用、真实时间的医院盈亏分析报告,要拿出去分享(讲课、投稿、同行交流)。怎么改,才既不泄露,又不让结论失真。 市面上讲脱敏的多数停在原则。这个案例给的是一份能跑的配方,以及一条几乎所有人都会漏的坑。
本案例只公开脱敏后的做法,不公开任何原始内容
本案例展示的是方法:替换规则的结构、脱敏的步骤、验证的方式。
不公开:真实科室名与代号的对应关系、缩放系数施加于哪些具体数值、任何未脱敏版本的片段或截图。
原因很直接——公开映射关系等于没有脱敏。知道"科室A = 神经内科"之后,脱敏版和原版就没有区别了。
1. 脱敏的目标不是"看不出来"
先摆正一件事。脱敏要同时满足两个条件:
- 不能反推出个人与机构
- 结论仍然成立
只满足第一条很容易——把所有数字换成随机数就行,但报告也就没用了。
难的是第二条。 而多数脱敏事故恰恰出在这里:为了"更安全",把数据改得结论都变了,或者反过来,为了"保结论"留下了能反推的东西。
2. 五步配方
针对一份 HTML 报告(这是最常见的分享形态,图表和数据都在一个文件里):
| 步 | 做什么 | 为什么这么做 |
|---|---|---|
| 1 | 科室名 → 稳定代号 | 建一张固定映射表(约 40 条),同一科室在全文永远是同一个代号 |
| 2 | 财务数值 × 固定系数 | 同一个系数乘所有金额 |
| 3 | 具体日期 → 模糊区间 | 精确到月的时间能配合其他信息定位机构 |
| 4 | 政策细节泛化 | 引用本地文件的具体条款会暴露地区(约 40 条替换规则) |
| 5 | 图表内嵌数据同步替换 | 见下一节——这一步最容易漏 |
为什么用"同一个固定系数"而不是加噪声
这是这份配方里最重要的一个设计。
text
所有金额 × 同一个系数好处是保序:谁高谁低不变,占比不变,趋势不变,同比环比不变。所有基于比较的结论原封不动。
如果给每个数加随机噪声,绝对值确实更"安全"了,但排名会乱、占比会飘、小额项目可能被噪声淹没——结论跟着变,报告就废了。
代价是:绝对金额不再真实,所以脱敏版不能用来讨论"我们院一年多少钱"。这个代价是应该付的——绝对金额本来就是最敏感的部分。
系数本身不能公开
知道系数就能反推原值。所以:代码里可以有,分享出去的报告里不能有,本案例也不写。
同理,映射表(哪个代号对应哪个科室)也不能随报告一起给。
3. 最容易漏的一步:图表里的数据
这是本案例最值得记住的地方。
一份带交互图表的 HTML 报告,数据存在两个地方:
text
① 正文与表格 → 肉眼可见,搜索能搜到
② <script> 里的 JSON → 图表库(Plotly、DataTables 等)用来渲染的数据很多人只处理了 ①。 因为在浏览器里看,页面上已经全是"科室A""科室B"了,看起来干净了。
但打开源码搜一下,图表的 JSON 数据结构里,真实科室名一个不少——它们只是没被渲染成可见文本,或者渲染在鼠标悬停才出现的提示框里。
text
页面上: 科室A 科室B 科室C ← 已替换
源码里: {"x": ["神经内科", "肿瘤内科", ...]} ← 没替换所以脱敏必须同时处理内嵌 JSON,而且数值缩放也要同步施加于 JSON 里的数组,否则图表和表格会对不上——一个用了缩放后的值,一个用了原值。
4. 验证:不验的脱敏等于没脱敏
改完之后必须跑一次自检。这一步只要几行代码,但它是整个流程唯一的兜底。
python
def verify(filepath, dept_map):
content = open(filepath, encoding='utf-8').read()
ok = True
# 1. 映射表里每一个原始名称,都不应该还出现在文件里
for name in dept_map:
if name in content:
print(f"⚠️ 残留:{name} 出现 {content.count(name)} 次")
ok = False
# 2. 具体日期模式不应残留
for pattern, desc in SENSITIVE_PATTERNS:
hits = set(re.findall(pattern, content))
if hits:
print(f"⚠️ {desc}残留:{hits}")
ok = False
return ok关键在第 1 条:它遍历的是映射表的全部键,而不是抽查几个。手工检查会漏掉出现频率低的科室——而那恰恰是最容易被忽略、也最容易定位到具体机构的。
验证必须在最终产物上跑
不是在中间文件上跑,是在你准备发出去的那个文件上跑。
中间某一步处理对了,但后面某一步又把原始数据带回来了——这种情况比想象中常见,尤其是当流程里有"从原文件重新读取"的步骤时。
5. 脱敏之后还剩什么风险
脱敏做干净了,也不代表可以随便发。还有三类残留风险:
| 风险 | 说明 | 怎么处理 |
|---|---|---|
| 结构可识别 | 科室的数量、组合、专科特色本身就能定位机构。有"放射治疗科 + 肿瘤内科 + 肿瘤外科"的三级医院不多 | 只公开需要的那部分科室,不要全院 |
| 数量级可识别 | 即使缩放了,病例数、床位数、总量级仍可能定位 | 病例数也要一并处理,或只给比例 |
| 组合可识别 | 单项都安全,几项一起就能定位(地区 + 等级 + 专科 + 时间) | 减少同时出现的维度 |
第三类最隐蔽。 脱敏检查通常逐项做,而风险是组合出来的。
6. 一份对外报告的最小检查清单
- 图表内嵌 JSON 已同步处理
- 在最终产物
- 映射表与缩放系数没有
倒数第二条最常被漏:把脱敏脚本连同映射表一起放进分享包里,等于把钥匙和锁一起给了。
7. 这个案例不能推出什么
- 不能推出这套配方适用于所有数据。 它针对的是"聚合后的运营报告"。病例级数据的脱敏要求高得多,本案例的方法不足以处理含患者标识的明细。
- 不能推出脱敏后就可以随意公开。 数据的公开还涉及授权、合规与本院制度,脱敏只解决技术层面。
- 不能推出缩放系数法总是对的。 它保序但不保绝对值。如果你的结论依赖绝对金额(例如"是否超过某个阈值"),这个方法会破坏结论,需要换做法。
- 不能推出验证通过就绝对安全。 验证只能检出你想到要检的模式。第 5 节的三类风险,自动验证都查不出来。
8. 相关篇目
- 交付前的通用自检:A04|交付前自检清单
- 报告本身该长什么样:DRG 运营分析报告样例
- 交付前的通用自检:A04|交付前自检清单