読まない道具 — 診療フローで判断を外に置く方法

6 分
目標2026-04に医師が診療フローの道具を作った話を載せた。あの記事は、道具が何をしないかを言葉でだけ書いた。今回はコードに引いた — このサーバーには判断を出す道具がなく、判定のように聞こえる語を出そうとすると例外が出る。

先に二つ明かす。

第一に、2026年4月にこの雑誌は、ある医師が診療フローの道具を作った話を載せた。この現場は特定の医院ではない。 実際にそう回っている形をそのまま構成し、どこかは明かさない。以下はその形どおりに自分で作ったものであり、手順と数値は作り物だ。

第二に、この記事は医療助言ではなく、ここで作ったものも医療機器ではない。 このシリーズは臨床検証をしていないし、する位置にもいない。扱うのはソフトウェアの境界ひとつ — 判断を道具の外に置くことがコードでどう見えるか。

言葉で引く線は消える

元の記事にもその線はあった。「診断は人に残し、診療の流れだけを画面に整理する。」

正しい言葉だ。問題はそれが文としてだけあったことだ。

文としてある線は消える。特にこういう消え方をする — 数か月後に誰かが「要約が一行あると便利なんだけど」とフィールドをひとつ足す。そのフィールドに おおむね正常範囲 のような文字列が入る。誰もその瞬間に気づかない。 道具が判定を始めた瞬間だ。

だから今回はコードに引いた。二度。

第一の線 — そんな道具がない

tools offered: flow.state, flow.done, readings.list

三つだけだ。手順を見せ、手順ひとつを完了に印を付け、計測値をそのまま返す。判定・分類・解釈をする道具がない。

検証が道具の名前から見る。

WORDS = ["diagnos", "assess", "judg", "risk", "abnormal", "recommend"]
for n in s.tool_names():
    assert not any(w in n.lower() for w in WORDS), n

これは先行する編で確認した原理の適用だ。道具一覧がすなわち表面であり、一覧にない能力は人が押してもモデルが選んでも届かない。

第二の線 — 判定のように聞こえれば例外が出る

一覧だけでは足りない。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

道具が実際にやること

線を二度引いたので、残ったものを見よう。

手順を順番どおりに見せる。 医師が書いた順番そのままだ。

routine: intake* -> vitals* -> review -> exam -> plan -> note

* が終わったものだ。画面の一番上に出るのは次にやることだが、これも助言ではない。

// "Next" is position in a list the clinician wrote. It is not advice.
'next': _guard(next.label),

一覧における位置にすぎない。 この区別は些細に見えるが、「次はこれをしてください」と「あなたが書いた順序で次の枠はこれです」は責任の位置が違う。

今日の手順 — 各ステップに、チェックする前に何が要るかが付く。実レンダーのキャプチャ

一段階を完了に印を付けると位置だけが移る。

一段階を終えたあと — NEXT IN THE LIST が次へ進む。「次」は臨床医が書いた一覧上の位置であって助言ではない

計測値はそのまま渡すが、基準を横に付ける。

readings: Blood pressure 148/92mmHg (clinic uses <130/80)
        | Heart rate 78bpm (clinic uses 60-100)
        | Temperature 36.8C (clinic uses 36.1-37.2)

148/92の横に <130/80 がある。ところが「高い」とは言わない。 その判断はこの道具のものではない。

基準を付けること自体が必要な理由がある。数字だけがあると、読む人が判定を自分で埋める。 そしてその判定はどこにも記録されない。基準が横にあれば、少なくとも何と比べたのかが画面に残る。

検証もそれを見る。

readings = s.call("readings.list")["readings"]
assert len(readings) == 3 and all(r["usual"] for r in readings)

計測値 — 数値ごとに通常範囲が並び、判定はどこにもない

このサンプルの範囲

医師はいない。 そして上の手順と基準値は私が作ったものだ。clinic uses <130/80 のような値はこのサンプルの数字であって、どの診療指針でもない。

この記事が検証したのはソフトウェアの性質ひとつまでだ。「この道具は判断を出さない」ということ。それが良い診療の道具だという意味ではない。 臨床的に有用か、安全か、規制要件を満たすかはすべてこのシリーズの外であり、その判断に必要な検証をしていない。

医療機器規制も扱っていない。 どの時点からこの種のソフトウェアが規制対象になるかは管轄ごとに異なり、この記事はその線を検討していない。

ガードは語の一覧だ。 迂回しようとすれば迂回される。これは不注意を止める装置であって悪意を止める装置ではない。

やらないことをコードに書く

元の記事の一文は正しかった。診断は人に残す。

作ってみて一行足す。その一文はコードのどこかに、実行される形で存在しなければならない。 文書にだけあれば六か月後には誰も覚えておらず、覚えていない原則は原則ではない。

このサーバーでその形は三つだった。

  • ない道具 — 一覧が表面だ
  • 投げるガード — 判定のように聞こえれば例外が出る
  • 失敗する検証 — 上の二つが崩れれば通過しない

三つとも大した技術ではない。ただ三つとも文ではなくコードだ。その差が六か月後に残る。

練習課題

設備の例から答えをひとつ選び、それが引用している記録を指してください。該当する記録がなければツールは何をすべきですか。

関連記事The Tool That Refuses to Read — Keeping Judgment Outside a Clinic Flow