先说明两件事。
第一,2026 年 4 月,本刊登过一篇某位医生做诊疗流程工具的文章。这个现场不是某家特定的诊所。 我们把它实际运转的形状照实构成,也不点名是哪里。以下是按那个形状自己做的,流程与数值都是编的。
第二,这篇不是医疗建议,这里做的东西也不是医疗器械。 这个系列没有做临床验证,也不在能做的位置上。它处理的是软件的一条边界 —把判断放在工具之外,在代码里长什么样。
用文字画的线会被抹掉
原文里也有那条线。「诊断留给人,只把诊疗的流程整理到画面上。」
说得对。问题是它只作为一句话存在。
作为一句话存在的线会被抹掉。而且是以这种方式被抹掉:几个月后有人说「要是有一行摘要就方便了」,加了一个字段。那个字段里放进 大体在正常范围内 这样的字符串。没有人注意到那一刻。 那正是工具开始下诊断的那一刻。
所以这次我们把它画进了代码。画了两次。
第一条线 — 没有那样的工具
tools offered: flow.state, flow.done, readings.list
只有三个。展示流程、把某一步标记为完成、把测量值原样返回。没有做判定、分类、解读的工具。
验证从工具的名字开始看。
for W in diagnos assess triage interpret advise; do
if grep -qi "tools offered:.*$W" captures/run.log; then
echo " a tool is named like it produces a judgement: $W"; exit 1
fi
done
这是先前一篇里确认过的原理的应用。工具清单就是表面,而不在清单上的能力,人去按也好模型去选也好都够不着。
第二条线 — 听起来像判定就抛异常
光有清单不够,因为判定可能混进 flow.state 返回的字符串里。
/// Words that would turn a report into a verdict. Anything this server is
/// about to say is checked against them.
///
/// A list of words is a crude guard and it is meant to be. It cannot stop a
/// determined author, but it does stop the ordinary way this line gets
/// crossed: someone adds a helpful-sounding summary field months later and
/// nobody notices that the tool started diagnosing.
static const forbidden = [
'diagnos', 'likely', 'suggests', 'consistent with', 'probable',
'abnormal', 'normal', 'healthy', 'concerning', 'severe', 'mild',
'recommend', 'should take', 'prescribe',
];
static String _guard(String s) {
final lower = s.toLowerCase();
for (final w in forbidden) {
if (lower.contains(w)) {
throw StateError('clinic_server tried to emit a judgement word: "$w"');
}
}
return s;
}
这个服务器吐出的每一个字符串都要经过这个函数。 请注意 normal 在清单上 — 它是看起来最无害的词,也是最常越线的词。
这是个粗糙的守卫。铁了心的人挡不住。可这条线实际被抹掉的方式不是铁了心,而是疏忽,而疏忽靠这点东西就挡住了。
而且验证会扫描整次运行。
no judgement vocabulary in anything the server returned