0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

医療の「診療報酬改定」をエンジニアが要件定義してみた──腎代替療法診療体制充実加算を"仕様"として実装する

0
Posted at

医療の「診療報酬改定」をエンジニアが要件定義してみた──腎代替療法診療体制充実加算を"仕様"として実装する

対象読者:医療機関の情シス/医療DXに関わるエンジニア/制度対応を仕組み化したい事務長・臨床工学技士
キーワード:要件定義・ドメインモデル・バリデーション・Python・医療DX

TL;DR

  • 2026年度診療報酬改定で新設された「腎代替療法診療体制充実加算」は、エンジニアの目で見るとバリデーションルールの集合体です。
  • この記事では、制度を**「仕様書」として要件定義し、施設が算定要件を満たしているかを判定するPythonのチェッカーを実装**します(コードは全文掲載・そのまま動きます)。
  • 制度の肝は「経過措置のある要件/ない要件が混在」「移植実績は"新規"だけカウント(更新は除外)」という2点。これをEnumと日付ロジックで表現します。
  • 患者情報は一切扱わず、施設の体制メタデータだけで判定する設計にします(医療AI・DXの鉄則)。

なぜエンジニアが診療報酬を読むのか

医療現場のシステム開発をしていると、必ずぶつかる壁があります。「このシステム、診療報酬の算定要件を満たせてる?」という問いです。

診療報酬というのは、2年に1度改定される巨大なルールの集合体です。点数(=価格)が、施設の体制・実績・記録の有無といった大量の条件分岐で決まる。これはもう、複雑なビジネスロジックそのものです。しかもルールは疑義解釈という名の"パッチ"で後から挙動が変わる。

今回題材にするのは、2026年度改定で新設された腎代替療法診療体制充実加算(J038 人工腎臓 注15、20点)。透析クリニックの経営に直結する加算ですが、要件が「経過措置あり/なし」で入り組んでいて、現場が混乱しています。

これを要件定義 → ドメインモデル化 → バリデーション実装の流れで、エンジニアの道具箱で料理してみます。制度の背景そのものは既存記事(概要編・実務編)に譲り、ここでは**"どう仕様に落とすか"**に集中します。

⚠️ 先にお断り:本記事は2026年7月時点の告示・通知・疑義解釈に基づく筆者の個人的な整理です。実際の届出・算定は必ず管轄の地方厚生局・院内の医事責任者にご確認ください。コードは要件理解のための教材であり、算定可否を保証するものではありません。


ステップ1:制度を"仕様書"として読む

まず、加算の要件を自然言語のまま棚卸しします。要件は大きく4つの柱です。

要件(ざっくり) 経過措置
災害対策 ハザードマップでリスク把握→災害マニュアル作成 なし
災害対策 災害時情報伝達訓練に年1回以上参加 令和9年5月31日まで
説明+実績 HD・PD・移植の3療法を説明 なし
説明+実績 イ:PD管理24回/年 or ウ:移植手続き前年2人以上 令和10年5月31日まで
シャント連携 自院不可時の連携体制を書面整備 なし
緩和ケア 提供体制(望ましい) ―(努力目標)

ここでエンジニア的に一番おいしいポイントが2つあります。

ポイントA:経過措置=「有効期限付きのフラグ」

要件には「今すぐ必須」と「特定の日付まで猶予」が混在しています。これはif 現在日付 <= 経過措置期限: return Trueという時限バリデーションとして実装できます。運用上、この日付を過ぎたら判定ロジックの挙動が変わる。つまり未来に自動で厳しくなる仕様です。

ポイントB:移植実績は"新規"だけ(更新は除外)

2026年6月26日の疑義解釈(その9)問8で、移植ルートの数え方が確定しました。

腎移植希望登録を更新しただけの患者は「腎移植に向けた手続きを行った患者」に含まれない。カウントできるのは「新規登録」「先行的移植」「移植離脱」だけ。

これはまさに区分値(enum)のフィルタリング問題です。患者イベントを型で分類し、「更新」を弾く。エンジニアなら一瞬で「あ、filterだ」と分かる構造です。


ステップ2:ドメインモデルを設計する

要件を型に落とします。まず、移植ルートで発生しうる「患者イベントの種類」をEnumで定義。ここが仕様の心臓部です。

from enum import Enum

class TransplantEvent(Enum):
    """腎移植ルートで実績カウント対象になりうるイベント種別。

    疑義解釈(その9)問8:カウント対象は NEW_REGISTRATION / PREEMPTIVE / WITHDRAWAL のみ。
    RENEWAL(登録更新)は明確に対象外。
    """
    NEW_REGISTRATION = "new_registration"   # 新規登録 → カウント対象
    PREEMPTIVE       = "preemptive"          # 先行的腎移植 → カウント対象
    WITHDRAWAL       = "withdrawal"          # 移植実施・透析離脱 → カウント対象
    RENEWAL          = "renewal"             # 登録更新 → カウント対象外(ここが今回の肝)

# カウント対象の集合を定数化しておく(仕様が変わってもここだけ直せばよい)
COUNTABLE_EVENTS = {
    TransplantEvent.NEW_REGISTRATION,
    TransplantEvent.PREEMPTIVE,
    TransplantEvent.WITHDRAWAL,
}

RENEWALをわざわざ列挙しているのがポイントです。「対象外の値も明示的に型に含める」ことで、"更新をうっかりカウントする"というバグを設計段階で殺せます

次に、施設の体制を表すデータクラスを定義します。患者個人のデータは持たせません。持つのは「体制が整っているか」の真偽値と、集計済みの件数だけです。

from dataclasses import dataclass, field
from datetime import date

@dataclass
class TransplantRecord:
    """移植ルートの1イベント(患者は特定しない集計単位)。"""
    event: TransplantEvent
    fiscal_year: int  # 実績が属する年度(例:2025)

@dataclass
class FacilityState:
    """加算判定に必要な施設の体制メタデータ。

    ※患者氏名・診療内容などの個人情報は一切含めない。
      持つのは『体制が整っているか』の真偽と集計値のみ。
    """
    # --- 柱1:災害対策 ---
    has_hazard_based_manual: bool          # ハザードマップに基づく災害マニュアル
    disaster_drill_dates: list[date] = field(default_factory=list)  # 訓練参加日

    # --- 柱2:説明+実績 ---
    explains_three_modalities: bool = False  # HD/PD/移植の3療法説明
    c102_count_last_year: int = 0            # PDルート:C102算定回数(過去1年)
    transplant_records: list[TransplantRecord] = field(default_factory=list)  # 移植ルート

    # --- 柱3:シャント連携 ---
    has_shunt_referral_agreement: bool = False  # 連携体制の書面

    # 判定基準日(デフォルトは今日)
    as_of: date = field(default_factory=date.today)

ステップ3:バリデーションを実装する

いよいよ判定ロジック本体です。要件ごとにメソッドを分け、なぜOK/NGなのかの理由も返すようにします(現場が「何が足りないか」を一目で分かるようにするため)。

# --- 経過措置の期限を定数として一元管理 ---
GRACE_DRILL   = date(2027, 5, 31)   # 訓練参加の経過措置
GRACE_RECORD  = date(2028, 5, 31)   # 実績要件の経過措置
PD_MIN_COUNT  = 24                  # イ:PD管理の最低回数/年
TX_MIN_COUNT  = 2                   # ウ:移植手続きの最低人数/前年

def count_transplant_achievements(records: list[TransplantRecord], fiscal_year: int) -> int:
    """指定年度の『カウント対象イベント』のみを数える。更新(RENEWAL)は自動的に除外。"""
    return sum(
        1 for r in records
        if r.event in COUNTABLE_EVENTS and r.fiscal_year == fiscal_year
    )

def check_disaster(state: FacilityState) -> tuple[bool, str]:
    """柱1:災害対策。マニュアルは常時必須、訓練は経過措置あり。"""
    if not state.has_hazard_based_manual:
        return False, "NG: ハザードマップに基づく災害マニュアルが未整備(経過措置なし)"

    # 直近1年に訓練参加があるか
    one_year_ago = date(state.as_of.year - 1, state.as_of.month, state.as_of.day)
    drilled = any(d >= one_year_ago for d in state.disaster_drill_dates)

    if drilled:
        return True, "OK: マニュアル整備済み+直近1年に訓練参加あり"
    if state.as_of <= GRACE_DRILL:
        return True, f"OK(経過措置): 訓練は{GRACE_DRILL}まで猶予。ただし早めの参加を推奨"
    return False, f"NG: {GRACE_DRILL}を過ぎ、直近1年の訓練参加が確認できない"

def check_achievement(state: FacilityState) -> tuple[bool, str]:
    """柱2の実績要件:イ(PD) or ウ(移植)。経過措置あり。"""
    # イ:PDルート
    if state.c102_count_last_year >= PD_MIN_COUNT:
        return True, f"OK(イ/PD): C102 {state.c102_count_last_year}回 ≥ {PD_MIN_COUNT}"

    # ウ:移植ルート(前年度で数える。更新は除外済み)
    last_fy = state.as_of.year - 1
    tx = count_transplant_achievements(state.transplant_records, last_fy)
    if tx >= TX_MIN_COUNT:
        return True, f"OK(ウ/移植): {last_fy}年度の新規実績 {tx}件 ≥ {TX_MIN_COUNT}"

    # どちらも未達 → 経過措置で救えるか
    if state.as_of <= GRACE_RECORD:
        return True, (f"OK(経過措置): 実績は{GRACE_RECORD}まで猶予。"
                      f"現状 PD={state.c102_count_last_year}回/移植新規={tx}件。"
                      f"恒久対応の体制づくりを2027年前半までに開始推奨")
    return False, (f"NG: {GRACE_RECORD}を過ぎ、PD({state.c102_count_last_year}回)・"
                   f"移植新規({tx}件)ともに基準未達")

def check_explanation(state: FacilityState) -> tuple[bool, str]:
    if state.explains_three_modalities:
        return True, "OK: HD/PD/移植の3療法説明あり"
    return False, "NG: 3療法の説明体制が未整備(経過措置なし)"

def check_shunt(state: FacilityState) -> tuple[bool, str]:
    if state.has_shunt_referral_agreement:
        return True, "OK: シャント連携の書面あり"
    return False, "NG: シャント連携の書面が未整備(経過措置なし)"

def evaluate_facility(state: FacilityState) -> dict:
    """全要件を評価し、算定可否と各要件の理由を返す。"""
    checks = {
        "災害対策":   check_disaster(state),
        "3療法説明":  check_explanation(state),
        "実績要件":   check_achievement(state),
        "シャント連携": check_shunt(state),
    }
    all_ok = all(ok for ok, _ in checks.values())
    return {
        "算定可否": "算定可" if all_ok else "要件未充足",
        "詳細": {k: {"判定": "" if ok else "×", "理由": msg}
                 for k, (ok, msg) in checks.items()},
    }

ステップ4:動かしてみる

3つのシナリオで試します。

from pprint import pprint

# シナリオ1:更新患者を"実績"と誤認していた施設(今回いちばん多い誤解)
facility_1 = FacilityState(
    has_hazard_based_manual=True,
    disaster_drill_dates=[date(2025, 9, 1)],
    explains_three_modalities=True,
    c102_count_last_year=0,
    transplant_records=[
        TransplantRecord(TransplantEvent.RENEWAL, 2025),  # 更新 → 数えない
        TransplantRecord(TransplantEvent.RENEWAL, 2025),  # 更新 → 数えない
    ],
    has_shunt_referral_agreement=True,
    as_of=date(2026, 7, 1),
)

# シナリオ2:PDルートで恒久対応できている施設
facility_2 = FacilityState(
    has_hazard_based_manual=True,
    disaster_drill_dates=[date(2028, 6, 1)],  # 判定基準日の直近1年内に訓練参加
    explains_three_modalities=True,
    c102_count_last_year=26,  # ≥24 → イでクリア
    has_shunt_referral_agreement=True,
    as_of=date(2028, 7, 1),   # 経過措置明けでもOK
)

# シナリオ3:経過措置明け・新規実績も不足
facility_3 = FacilityState(
    has_hazard_based_manual=True,
    disaster_drill_dates=[date(2028, 9, 1)],
    explains_three_modalities=True,
    c102_count_last_year=10,
    transplant_records=[TransplantRecord(TransplantEvent.NEW_REGISTRATION, 2027)],  # 新規1件のみ
    has_shunt_referral_agreement=True,
    as_of=date(2028, 7, 1),
)

for i, f in enumerate([facility_1, facility_2, facility_3], 1):
    print(f"\n===== シナリオ{i} =====")
    pprint(evaluate_facility(f), sort_dicts=False)

実行結果

===== シナリオ1 =====
{'算定可否': '算定可',
 '詳細': {'災害対策': {'判定': '○', '理由': 'OK: マニュアル整備済み+直近1年に訓練参加あり'},
        '3療法説明': {'判定': '○', '理由': 'OK: HD/PD/移植の3療法説明あり'},
        '実績要件': {'判定': '○',
                 '理由': 'OK(経過措置): 実績は2028-05-31まで猶予。現状 PD=0回/移植新規=0件。'
                       '恒久対応の体制づくりを2027年前半までに開始推奨'},
        'シャント連携': {'判定': '○', '理由': 'OK: シャント連携の書面あり'}}}

注目してほしいのはシナリオ1です。更新2件を「実績あり」と誤認していた施設ですが、移植新規は0件と正しく判定されています(RENEWALが除外されるため)。今は経過措置で「算定可」ですが、理由欄が「このままだと2028年に詰む」と警告している。これがコード化の価値です。人間の目視だと「更新2件あるから大丈夫」と流してしまう。

===== シナリオ3 =====
{'算定可否': '要件未充足',
 '詳細': {...
        '実績要件': {'判定': '×',
                 '理由': 'NG: 2028-05-31を過ぎ、PD(10回)・移植新規(1件)ともに基準未達'},
        ...}}

シナリオ3は経過措置明け・実績不足で、明確に要件未充足未来の日付を入れるとロジックが自動で厳しくなる——時限バリデーションが効いているのが分かります。


ステップ5:応用──「訓練参加の証拠」もスキーマ化する

実務編で触れた「災害時情報伝達訓練の参加証明書は個別発行されない」問題。これも記録スキーマを先に決めておくことで、後の適時調査に耐えられます。JSON Schema的に定義するとこうなります。

@dataclass
class DrillRecord:
    """災害時情報伝達訓練の参加記録(自施設で保全する)。"""
    date: date                 # 実施日
    organizer: str             # 主催(日本透析医会/都道府県透析医会 等)
    operator: str              # 入力担当者(氏名でなく役割でも可:例「透析室主任」)
    screenshot_path: str       # 送信完了画面のスクショ保存先
    findings: str = ""         # 訓練で発覚した不備
    manual_updated: bool = False  # その不備をマニュアルに反映したか

# 例:2026年9月1日の訓練
drill = DrillRecord(
    date=date(2026, 9, 1),
    organizer="日本透析医会",
    operator="透析室主任",
    screenshot_path="/secure/drills/2026-09-01.png",
    findings="連絡網の電話番号が2件古かった",
    manual_updated=True,
)

manual_updatedフラグが地味に重要です。「訓練で見つけた不備をマニュアルに反映した履歴」は、"マニュアルが運用されている"ことの証拠になる。PDCAが回っている証跡をデータで残せます。


エビデンス:この「仕組み化」は現場の何を変えるのか

制度対応のコード化は事務効率の話に見えますが、根っこは患者アウトカムにつながります。ここは医学論文の裏づけがあります。

According to PubMed、米国で透析導入した患者約3万人を分析した研究では、導入前の治療選択教育プログラムに参加した患者は、腹膜透析(在宅透析)を選ぶ割合が約5倍(調整OR 5.13)、開始90日の死亡リスクが約4割低下(調整HR 0.61)していました(Lacson E, et al. Am J Kidney Dis. 2011;58(2):235-242. DOI)。※参加者に前向きな患者が多い偏りは著者も限界として明記。

さらにRCT(ランダム化比較試験)でも、対話を含む教育で在宅透析の選択意向が**50%→82.1%**に上昇(P=0.015)と報告されています(Manns BJ, et al. Kidney Int. 2005;68(4):1777-1783. DOI)。

災害対策も同様です。According to PubMed、ハリケーン・カトリーナ後の透析患者386人の調査では、透析を3回以上欠席した患者の入院リスクが有意に高く(調整OR 2.16)、その関連要因に「自施設の災害計画を知らなかったこと」が含まれていました(Anderson AH, et al. Kidney Int. 2009;75(11):1202-1208. DOI)。

つまり、explains_three_modalitieshas_hazard_based_manualという一見ただのbool値は、患者の生存や入院に効くフラグなのです。要件チェッカーは、単なる算定漏れ防止ツールではなく、医療の質のダッシュボードにもなり得ます。


医療でAIやコードを使うときの3原則(重要)

最後に、この手のツールを医療機関で運用するときの絶対ルールです。

  1. 患者の個人情報を入力・保持しない。 本記事のFacilityStateが持つのは体制の真偽値と集計値だけです。氏名・診療内容はもちろん、日時・属性の組み合わせで個人が特定され得る情報も入れない。生成AIに要件を要約させる場合も同じ。職員が個人契約のAIに業務データを入れる「シャドーAI」の統制もセットで。
  2. コードやAIの出力を"根拠"にしない。 このチェッカーはあくまで理解と抜け漏れ防止の補助線です。算定の根拠は必ず告示・通知・疑義解釈の原文。定数(GRACE_RECORD等)は原典と突合してから使う。
  3. 最終責任は人間(施設)にある。 適時調査で説明するのは院長と事務長です。「システムが算定可と言った」は理由になりません。

まとめ

  • 診療報酬改定は、エンジニアにとって要件定義・ドメインモデリングの格好の題材
  • 腎代替療法診療体制充実加算の肝は「経過措置=時限フラグ」と「移植実績は新規のみ(更新除外)=enumフィルタ」。この2つを型で表現すると、人間が見落とすバグを設計段階で潰せる。
  • 体制フラグは患者アウトカムに直結する(PubMedの複数研究が裏づけ)。要件チェッカーは"医療の質のダッシュボード"に育てられる。
  • ただし個人情報を入れない・原文と突合する・責任は人間の3原則は絶対。

制度をコードで読むと、「なぜこの要件なのか」まで理解が進みます。医療DXは、業務の自動化である前に、制度という最上位仕様の読解から始まる——というのが、現場に立つエンジニアとしての実感です。


出典・参考資料

一次資料(制度)

査読論文(出典:PubMed)

  • Lacson E, et al. Effects of a nationwide predialysis educational program on modality choice, vascular access, and patient outcomes. Am J Kidney Dis. 2011;58(2):235-242. DOI
  • Manns BJ, et al. The impact of education on chronic kidney disease patients' plans to initiate dialysis with self-care dialysis: a randomized trial. Kidney Int. 2005;68(4):1777-1783. DOI
  • Anderson AH, et al. Missed dialysis sessions and hospitalization in hemodialysis patients after Hurricane Katrina. Kidney Int. 2009;75(11):1202-1208. DOI

著者プロフィール

臨床工学技士 × AIエンジニア / 11年間、病院の医療機器の現場に立ち続けてきました。いまはAIエンジニアとしても活動しながら、酪農学園大学の研究生として論文博士の取得を目指しています。研究テーマの主軸は遺伝子医療の未来。そのうえで、医療現場と地続きにある病院のIT・サイバーセキュリティ・医療AI導入についても、現場で起きている課題と一次情報を突き合わせながら調べ続けています。

質問・誤りの指摘・「うちの施設ではこうしている」という事例の共有、いつでも歓迎します。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?