【医療】電子カルテのサマリ生成をClaude Codeで実装した事例|HIPAA配慮の安全設計【2026】

中小クリニック向けに電子カルテのSOAPサマリ生成をClaude Codeで実装したパターンを解説。個人情報保護法・HIPAA配慮のローカルLLMフォールバック2層構成、Pythonサンプルコード3本付き。

【医療】電子カルテのサマリ生成をClaude Codeで実装した事例|HIPAA配慮の安全設計【2026】

結論:中小クリニックにおける電子カルテのSOAPサマリ生成は、Claude Code + ローカルLLMの2層構成により、個人情報保護法・HIPAA配慮を維持しながら実装できる。

  • 要点1:PHI(保護対象保健情報)はローカルネットワーク内にとどめ、外部LLM送信前に匿名化処理を行う設計が基本
  • 要点2:Anthropic Claude API はオプトアウト設定(BAA締結相当)環境でのみ使用し、クラウド/ローカルの切り替えをフラグで制御する
  • 要点3:Pythonサンプル3本(匿名化・サマリ生成・レビューUI)をそのまま参照できる構成で解説する

対象読者:クリニック・病院のIT担当者、医療系SaaS開発者、Claude Code の医療応用を検討するエンジニア

今日やること:ローカル環境でサンプルコードを動かし、匿名化パイプラインの動作を確認する

※本記事は想定モデルケース(実装パターン解説)です。特定の実在クリニックや医療機関の事例ではありません。数値はパターン参考値(試算)です。医療現場への実際の導入にあたっては、必ず個人情報保護法・医療情報システムの安全管理に関するガイドライン(厚生労働省)および所属組織のコンプライアンス担当・専門家にご確認ください。

1. なぜ今、電子カルテのAIサマリ生成が注目されるのか

「外来終わって19時、カルテが20件残ってる」——中小クリニックのIT担当者から、こういう相談が増えています。電子カルテの入力・要約作業は医師・看護師の時間を大量に消費するにもかかわらず、患者ケアそのものには直接つながらない間接業務です。

厚生労働省「医療情報システムの安全管理に関するガイドライン 第6.0版」(2023年5月公表)では、クラウドサービス利用時の安全管理対策として「送信するデータの匿名化・仮名化」「アクセスログの保管」「外部送信ポリシーの明文化」が求められています(参照: 厚生労働省 医療情報ガイドライン第6.0版、2023年5月)。

一方、Anthropic は2024年以降、エンタープライズ・API利用において 「モデルトレーニングへのデータ不使用(オプトアウト)」を標準提供しており、米国 HIPAA 対応の Business Associate Agreement(BAA)締結が可能なエンタープライズプランも提供しています(参照: Anthropic Privacy Policy)。日本のクリニック向けには匿名化を前提とした設計がより現実的です。

本記事では、「匿名化ファースト + ローカルLLMフォールバック」の2層構成を軸に、Claude Code を使って電子カルテのSOAPサマリ自動生成を実装するパターンを解説します。

2. 実装前に整理すべきリスクと設計原則

2-1. 医療情報の3類型と送信ルール

実装に入る前に、扱うデータの種類を明確にします(※想定モデルケースの分類例):

データ種別 具体例 外部LLM送信
識別情報(PHI) 氏名・生年月日・住所・保険証番号 送信禁止
臨床情報(匿名化後) 主訴・所見・検査値(患者番号に置換済み) 条件付きOK
構造テンプレート SOAPフォーマット・入力スキーマ OK

AI は補助ツールであり、最終診断判断者ではありません。生成されたサマリは必ず医師・看護師が確認・編集する運用設計にしてください。

2-2. 2層アーキテクチャの概要

本記事で紹介する構成は以下の通りです(※想定モデルケース):

  1. Layer 1 — ローカル匿名化レイヤー(必須):カルテデータのPHIを除去・仮名化し、院内ネットワーク内でトークン化する
  2. Layer 2-A — Claude API(クラウドLLM):匿名化済みデータのみを受け取り、SOAP形式サマリを生成する。Anthropic のオプトアウト設定が有効な場合のみ使用
  3. Layer 2-B — ローカルLLM(Ollama + Llama / Qwen):クラウド接続不可時・高感度ケースのフォールバック。院内サーバーで完結

フラグ USE_CLOUD_LLM の値でLayer 2のルーティングを切り替えます。

3. 実装ステップ(手順)

  1. 環境準備:Python 3.11以上、anthropic SDK、spaCy(日本語モデル)、Ollama をインストールする
  2. PHI匿名化パイプラインの構築:正規表現 + spaCy でPHIエンティティを検出し、PATIENT_XXXX 形式の仮IDに置換する(サンプルコード1参照)
  3. Claude API接続の設定ANTHROPIC_API_KEY を環境変数から読み込み、USE_CLOUD_LLM フラグと組み合わせてルーティングロジックを実装する(サンプルコード2参照)
  4. ローカルLLMフォールバックの設定:Ollama を院内サーバーにインストールし、llama3.2 または qwen2.5:14b モデルを起動する。HTTP API(ポート11434)でPythonから呼び出す
  5. SOAPサマリ生成関数の実装:匿名化テキストをプロンプトに埋め込み、S(主観)O(客観)A(評価)P(計画)の4ブロックで出力させる
  6. レビューUIの構築:Streamlit を使い、生成サマリを医師が編集・承認するシンプルなWebUIを実装する(サンプルコード3参照)
  7. アクセスログの保存:誰が・いつ・どの患者番号のサマリを生成したかを院内ログDBに記録する(個人情報保護法の安全管理措置として推奨)
  8. 動作テスト(ダミーデータで):実患者データを使わず、架空の症例テキストで匿名化 → サマリ生成 → レビューUIの一連の流れを確認する
  9. セキュリティレビュー:院内のコンプライアンス担当者・法務または外部の医療情報セキュリティ専門家によるレビューを受ける
  10. 段階的展開:パイロット診療科(例:内科1診)で1〜2週間のトライアル → フィードバック収集 → 全体展開の順で進める

4. Pythonサンプルコード①:PHI匿名化パイプライン

以下は、日本語カルテテキストからPHIを除去するサンプルコードです。実際の運用では、所属組織のコンプライアンス担当者の確認を得た上で使用してください。

"""
PHI Anonymization Pipeline for Japanese Medical Records
※ 本サンプルはパターン解説用です。実運用前に専門家レビューが必要です。
"""
import re
import hashlib
import unicodedata
from dataclasses import dataclass, field
from typing import Optional

# ---------- 設定 ----------
PHI_SALT = "your-clinic-specific-salt-change-this"  # 本番では環境変数から読み込む

# 日本語氏名の典型パターン(簡易版 / 実運用はspaCy等を併用)
NAME_PATTERNS = [
    r"患者[::]s*([^s、。,]+)",
    r"氏名[::]s*([^s、。,]+)",
    r"お名前[::]s*([^s、。,]+)",
]

DATE_PATTERNS = [
    r"d{4}年d{1,2}月d{1,2}日",
    r"d{1,2}/d{1,2}",
    r"令和d+年d{1,2}月d{1,2}日",
]

ID_PATTERNS = [
    r"d{10}",      # 保険証番号の類
    r"d{3}-d{4}-d{4}",  # 電話番号パターン
    r"〒d{3}-d{4}",       # 郵便番号
]


@dataclass
class AnonymizationResult:
    original_length: int
    anonymized_text: str
    replaced_count: int
    patient_token: str
    phi_types_detected: list = field(default_factory=list)


def generate_patient_token(raw_id: str) -> str:
    """患者識別情報をハッシュトークンに変換"""
    combined = f"{PHI_SALT}:{raw_id}"
    return "PATIENT_" + hashlib.sha256(combined.encode()).hexdigest()[:8].upper()


def anonymize_medical_text(
    text: str,
    patient_raw_id: str,
    extra_phi: Optional[list] = None,
) -> AnonymizationResult:
    """
    カルテテキストからPHIを除去して匿名化テキストを返す。

    Args:
        text: 元のカルテテキスト
        patient_raw_id: 患者の院内ID(ハッシュ化に使用)
        extra_phi: 追加で除去したい文字列リスト(住所など)

    Returns:
        AnonymizationResult: 匿名化結果
    """
    result_text = text
    replaced_count = 0
    phi_types = []
    patient_token = generate_patient_token(patient_raw_id)

    # 患者IDそのものを置換
    if patient_raw_id in result_text:
        result_text = result_text.replace(patient_raw_id, patient_token)
        replaced_count += 1
        phi_types.append("patient_id")

    # 氏名パターン
    for pattern in NAME_PATTERNS:
        matches = re.findall(pattern, result_text)
        for match in matches:
            result_text = result_text.replace(match, "[氏名削除]")
            replaced_count += 1
        if matches:
            phi_types.append("name")

    # 日付パターン(年月日はサマリ生成に必要なため「[日付]」として残す)
    for pattern in DATE_PATTERNS:
        matches = re.findall(pattern, result_text)
        for match in matches:
            result_text = result_text.replace(match, "[日付]")
            replaced_count += len(matches)
        if matches:
            phi_types.append("date")

    # ID・電話・郵便番号パターン
    for pattern in ID_PATTERNS:
        matches = re.findall(pattern, result_text)
        for match in matches:
            result_text = result_text.replace(match, "[ID削除]")
            replaced_count += len(matches)
        if matches:
            phi_types.append("id")

    # 追加PHI
    if extra_phi:
        for phi in extra_phi:
            if phi in result_text:
                result_text = result_text.replace(phi, "[削除]")
                replaced_count += 1
                phi_types.append("extra")

    return AnonymizationResult(
        original_length=len(text),
        anonymized_text=result_text,
        replaced_count=replaced_count,
        patient_token=patient_token,
        phi_types_detected=list(set(phi_types)),
    )


# ---------- 使用例 ----------
if __name__ == "__main__":
    # ダミーデータでテスト
    sample_text = """
    患者:田中 太郎(架空)
    2026年5月20日 来院
    主訴:咳嗽・発熱
    所見:体温38.2℃、SpO2 97%
    """
    result = anonymize_medical_text(
        text=sample_text,
        patient_raw_id="P00123456",
        extra_phi=["田中 太郎(架空)"],
    )
    print(f"PHI検出種別: {result.phi_types_detected}")
    print(f"置換件数: {result.replaced_count}")
    print(f"匿名化後テキスト:n{result.anonymized_text}")

注意事項:上記は簡易パターンマッチングです。実際の医療現場では spaCy の日本語固有表現モデルや、医療情報専用の匿名化ライブラリ(例: presidio-analyzer の日本語対応版)との組み合わせを検討してください。専門家のレビューなしに本番投入しないことを強くお勧めします。

5. Pythonサンプルコード②:2層LLMルーティング + SOAPサマリ生成

匿名化済みテキストを受け取り、クラウドLLM(Claude)またはローカルLLM(Ollama)でSOAPサマリを生成するロジックです。

"""
2-Layer LLM Router for SOAP Summary Generation
匿名化済みテキストのみを受け取ること(PHI未除去のテキストを渡さないこと)
"""
import os
import json
import logging
import anthropic
import requests
from dataclasses import dataclass

logger = logging.getLogger(__name__)

# ---------- 設定(環境変数から読み込む) ----------
ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
USE_CLOUD_LLM = os.environ.get("USE_CLOUD_LLM", "false").lower() == "true"
OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434")
OLLAMA_MODEL = os.environ.get("OLLAMA_MODEL", "llama3.2")
CLAUDE_MODEL = "claude-3-5-sonnet-20241022"  # 2026年6月時点。最新モデルは公式ドキュメント確認

SOAP_SYSTEM_PROMPT = """あなたは医療記録の整理を支援するアシスタントです。
提供されるカルテテキストは**匿名化済み**です。
以下のSOAP形式で簡潔にサマリを作成してください。

出力形式(JSONで返すこと):
{
  "S": "主観的情報(患者の訴え)",
  "O": "客観的情報(バイタル、検査値、所見)",
  "A": "評価・印象(考えられる診断・状態)",
  "P": "計画(治療方針、次のステップ)",
  "confidence": "high/medium/low(情報の充足度)",
  "missing_info": ["不足している情報のリスト"]
}

重要な注意事項:
- 最終診断はAIが行うものではありません。必ず担当医師が確認・修正してください。
- 不明瞭な情報は「不明」と記載し、推測での補完を行わないこと。
- 薬剤名・用量は元テキストに記載された内容のみ転記する。
"""


@dataclass
class SOAPSummary:
    S: str
    O: str
    A: str
    P: str
    confidence: str
    missing_info: list
    llm_used: str  # "claude" or "ollama"
    model_name: str


class LLMRouter:
    """2層LLMルーター: ClaudeAPI → Ollamaフォールバック"""

    def __init__(self):
        self.claude_client = None
        if USE_CLOUD_LLM and ANTHROPIC_API_KEY:
            self.claude_client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
        elif USE_CLOUD_LLM and not ANTHROPIC_API_KEY:
            logger.warning("USE_CLOUD_LLM=true だが ANTHROPIC_API_KEY が未設定。Ollama にフォールバック。")

    def _call_claude(self, anonymized_text: str) -> dict:
        """Claude API を呼び出してSOAPサマリを生成"""
        if not self.claude_client:
            raise ValueError("Claude client not initialized")

        message = self.claude_client.messages.create(
            model=CLAUDE_MODEL,
            max_tokens=1024,
            system=SOAP_SYSTEM_PROMPT,
            messages=[
                {
                    "role": "user",
                    "content": f"以下のカルテテキスト(匿名化済み)からSOAPサマリを作成してください:nn{anonymized_text}",
                }
            ],
        )
        response_text = message.content[0].text
        return json.loads(response_text)

    def _call_ollama(self, anonymized_text: str) -> dict:
        """Ollamaローカルモデルを呼び出してSOAPサマリを生成"""
        payload = {
            "model": OLLAMA_MODEL,
            "system": SOAP_SYSTEM_PROMPT,
            "prompt": f"以下のカルテテキスト(匿名化済み)からSOAPサマリを作成してください:nn{anonymized_text}",
            "format": "json",
            "stream": False,
        }
        response = requests.post(
            f"{OLLAMA_BASE_URL}/api/generate",
            json=payload,
            timeout=120,
        )
        response.raise_for_status()
        return json.loads(response.json()["response"])

    def generate_soap_summary(self, anonymized_text: str) -> SOAPSummary:
        """
        匿名化済みテキストからSOAPサマリを生成。
        ClaudeAPI → Ollama の順でフォールバック。

        Args:
            anonymized_text: PHI除去済みのカルテテキスト

        Returns:
            SOAPSummary

        Raises:
            RuntimeError: 両方のLLMが失敗した場合
        """
        # PHI未除去チェック(簡易)
        phi_indicators = ["患者:", "氏名:", "〒", "生年月日:"]
        for indicator in phi_indicators:
            if indicator in anonymized_text:
                raise ValueError(
                    f"PHI指標 '{indicator}' が検出されました。匿名化してから渡してください。"
                )

        # Layer 2-A: Claude API(USE_CLOUD_LLM=true の場合のみ)
        if USE_CLOUD_LLM and self.claude_client:
            try:
                result = self._call_claude(anonymized_text)
                return SOAPSummary(
                    llm_used="claude",
                    model_name=CLAUDE_MODEL,
                    **result,
                )
            except Exception as e:
                logger.warning(f"Claude API 失敗: {e}. Ollama にフォールバック。")

        # Layer 2-B: Ollama(フォールバック or デフォルト)
        try:
            result = self._call_ollama(anonymized_text)
            return SOAPSummary(
                llm_used="ollama",
                model_name=OLLAMA_MODEL,
                **result,
            )
        except Exception as e:
            logger.error(f"Ollama も失敗: {e}")
            raise RuntimeError("全LLMバックエンドが失敗しました。手動でサマリを作成してください。") from e


# ---------- 使用例 ----------
if __name__ == "__main__":
    # 匿名化済みテキスト(前段の anonymize_medical_text() の出力を想定)
    anonymized = """
    患者トークン: PATIENT_A1B2C3D4
    [日付] 来院
    主訴:咳嗽・発熱3日間
    所見:体温38.2℃、SpO2 97%、咽頭発赤あり
    既往歴:高血圧(ACE阻害薬服用中)
    """

    router = LLMRouter()
    summary = router.generate_soap_summary(anonymized)
    print(f"使用LLM: {summary.llm_used} ({summary.model_name})")
    print(f"S: {summary.S}")
    print(f"O: {summary.O}")
    print(f"A: {summary.A}")
    print(f"P: {summary.P}")
    print(f"信頼度: {summary.confidence}")
    print(f"不足情報: {summary.missing_info}")

6. Pythonサンプルコード③:StreamlitによるレビューUI

生成されたSOAPサマリを医師・看護師が確認・編集するシンプルなWebUIです。承認前のサマリはカルテシステムに書き戻しません。

"""
SOAP Summary Review UI (Streamlit)
実行: streamlit run soap_review_ui.py
※ このUIは院内ネットワーク内でのみ公開すること
"""
import streamlit as st
import json
import logging
from datetime import datetime
from anonymizer import anonymize_medical_text
from llm_router import LLMRouter

logger = logging.getLogger(__name__)

st.set_page_config(page_title="カルテサマリ確認", layout="wide")
st.title("電子カルテ SOAPサマリ 確認・編集")
st.caption("⚠️ AIが生成したサマリは必ず担当医師が確認・編集した上で確定してください。")

# ---------- 入力フォーム ----------
with st.form("input_form"):
    st.subheader("1. カルテ情報入力")
    patient_id = st.text_input(
        "患者院内ID(匿名化に使用、外部送信されません)",
        placeholder="例: P00123456",
    )
    raw_text = st.text_area(
        "カルテテキスト",
        height=200,
        placeholder="外来記録・SOAP下書き・申し送りメモ等を貼り付けてください",
    )
    extra_phi_str = st.text_input(
        "追加で除去するPHI(カンマ区切り、任意)",
        placeholder="例: 東京都文京区○○, 090-xxxx-xxxx",
    )
    submitted = st.form_submit_button("匿名化 → サマリ生成")

if submitted and patient_id and raw_text:
    extra_phi = [x.strip() for x in extra_phi_str.split(",") if x.strip()] if extra_phi_str else []

    with st.spinner("PHI匿名化中..."):
        anon_result = anonymize_medical_text(
            text=raw_text,
            patient_raw_id=patient_id,
            extra_phi=extra_phi,
        )

    st.subheader("2. 匿名化結果確認")
    col1, col2 = st.columns(2)
    with col1:
        st.metric("置換件数", anon_result.replaced_count)
        st.metric("検出PHI種別", ", ".join(anon_result.phi_types_detected) or "なし")
    with col2:
        st.metric("患者トークン", anon_result.patient_token)

    with st.expander("匿名化後テキスト(外部LLMに送信されるテキスト)を確認"):
        st.text(anon_result.anonymized_text)

    if anon_result.replaced_count == 0:
        st.warning("⚠️ PHIが検出されませんでした。テキストを確認してください。")

    with st.spinner("SOAPサマリ生成中..."):
        try:
            router = LLMRouter()
            summary = router.generate_soap_summary(anon_result.anonymized_text)
        except Exception as e:
            st.error(f"サマリ生成に失敗しました: {e}")
            st.stop()

    st.subheader("3. 生成サマリ(編集して確定してください)")
    st.info(f"使用LLM: {summary.llm_used} ({summary.model_name}) | 信頼度: {summary.confidence}")

    with st.form("review_form"):
        s_text = st.text_area("S(主観的情報)", value=summary.S, height=80)
        o_text = st.text_area("O(客観的情報)", value=summary.O, height=80)
        a_text = st.text_area("A(評価)", value=summary.A, height=80)
        p_text = st.text_area("P(計画)", value=summary.P, height=80)

        if summary.missing_info:
            st.warning(f"不足情報が検出されました: {', '.join(summary.missing_info)}")

        reviewer_name = st.text_input("確認者(医師・看護師)氏名", placeholder="例: 山田 一郎 医師")
        confirmed = st.checkbox("上記サマリを確認・編集し、内容が正確であることを確認しました")
        approve_btn = st.form_submit_button("確定・保存", disabled=not confirmed)

    if approve_btn and confirmed and reviewer_name:
        # 承認ログ保存(本番では院内DBに書き込む)
        log_entry = {
            "timestamp": datetime.now().isoformat(),
            "patient_token": anon_result.patient_token,
            "reviewer": reviewer_name,
            "llm_used": summary.llm_used,
            "soap": {"S": s_text, "O": o_text, "A": a_text, "P": p_text},
        }
        logger.info(f"SOAP承認: {json.dumps(log_entry, ensure_ascii=False)}")
        st.success("✅ サマリが確定されました。カルテシステムへの書き戻しは別途実施してください。")
        st.json(log_entry)

7. セキュリティ設計の詳細

7-1. ネットワーク分離

院内ネットワーク構成の例(※想定モデルケース):

  • カルテデータゾーン(最高機密):電子カルテサーバー、患者DB。インターネット非接続
  • 処理ゾーン(匿名化サーバー):カルテゾーンからデータを受け取り、PHI除去後にサマリゾーンへ転送。双方向通信は禁止
  • サマリゾーン(LLM処理):Ollamaサーバー(ローカル)またはAnthropicへのHTTPS送信。処理済みサマリを処理ゾーン経由でカルテゾーンへ返す
  • レビューUI:院内Wi-Fiのみアクセス可。VPN必須

7-2. アクセスログと監査

個人情報保護法の「安全管理措置」として、以下のログ項目を記録することを推奨します(参照: 医療情報システムの安全管理に関するガイドライン第6.0版):

  1. アクセス日時・ユーザーID
  2. 対象患者トークン(匿名化済み)
  3. 使用したLLM(クラウド/ローカルの別)
  4. 承認医師名と承認タイムスタンプ
  5. 元テキストのハッシュ値(改ざん検知)

7-3. Anthropic API使用時の確認事項

クラウドLLM(Claude API)を使用する場合、以下を必ず確認してください:

  1. オプトアウト設定の有効化:Anthropic コンソールで「Training data opt-out」が有効になっているか確認(console.anthropic.com
  2. データ処理地域の確認:Anthropic の現行利用規約・プライバシーポリシーで日本データの処理地域を確認
  3. BAA締結の要否:米国 HIPAA 規制対象の場合は Business Associate Agreement が必要。日本国内のみの場合でも、個人情報保護法の委託先管理として契約内容を明確にする
  4. 匿名化の確実性:「匿名化済みだからOK」と自己判断せず、情報セキュリティ専門家のレビューを受ける

免責:本記事の情報は2026年6月時点のものです。Anthropic の利用規約・プライバシーポリシーは変更される可能性があります。必ず最新の公式情報をご確認ください。

8. ローカルLLMの選定と性能比較(参考値)

以下は院内サーバーで動作するローカルLLMの参考比較です。※想定モデルケースの参考値であり、実際の性能は環境・タスクによって大きく異なります。

モデル パラメータ数 必要VRAM 日本語品質 推奨用途
Llama 3.2 (3B) 3B 約2GB △(英語中心) 低スペック機でのテスト
Qwen2.5:14b 14B 約9GB ○(日中英対応) 日本語カルテのメイン用途向け
Qwen2.5:72b 72B 約40GB 高精度が必要な場合(高スペックサーバー要)
claude-3-5-sonnet (Cloud) 匿名化済みデータのみ送信、BAA/オプトアウト確認後

9. 段階的導入ロードマップ(想定モデルケース)

Phase 1(1〜2ヶ月):パイロット検証

  • ダミーデータで匿名化パイプライン・サマリ生成の動作確認
  • コンプライアンス担当者・IT部門・医師代表によるレビュー
  • 1診療科(例: 内科1診)での限定トライアル
  • 医師・看護師フィードバック収集 → 匿名化精度・プロンプト改善

Phase 2(3〜4ヶ月):複数診療科展開

  • 2〜3診療科への展開
  • 電子カルテシステムとの連携API開発(受け渡しインターフェース)
  • ログ監査の自動化・定期レビュー体制の確立
  • 異常時のアラート設計(匿名化失敗・LLM接続エラー)

Phase 3(5〜8ヶ月):全院展開・高度化

  • 全診療科への段階展開
  • 病院情報システム(HIS)との統合
  • 専門科ごとのプロンプトカスタマイズ(整形外科・精神科等)
  • 外部セキュリティ監査の実施

10. よくある失敗パターンと回避策

実装パターンを調査する中で確認された、よくある失敗パターンです。

失敗1:匿名化をせずにAPIを呼び出す

最も重大なリスク。「API先で安全管理されている」と思い込み、患者氏名や保険証番号ごとAPIに送信してしまう。
回避策:APIを呼ぶ関数に「PHI指標のチェック」を必ずビルドインする(サンプルコード②の phi_indicators チェックを参照)。CI/CDにも同様のチェックを入れる。

失敗2:生成サマリをそのままカルテに書き戻す

AIが生成したサマリには誤りが含まれる可能性があります。医師確認なしに自動書き戻しをすると、医療安全上のリスクになります。
回避策:サマリは必ず「承認待ち」ステータスで保存し、担当医師の明示的な承認(レビューUI等)後にのみカルテへ反映する。

失敗3:ローカルLLMのモデルを更新せず放置する

モデルの更新により品質が改善されますが、更新検証なしに本番適用すると出力品質が変化します。
回避策:ローカルLLMの更新はステージング環境でテストしてから本番適用する。バージョンをロックし変更管理に含める。

失敗4:院内Wi-Fiの外からレビューUIにアクセス可能にしてしまう

利便性のためにインターネット公開したところ、患者トークン経由で情報が漏洩するリスクが生まれます。
回避策:StreamlitアプリはVPN + 院内Wi-Fiのみに制限する。`–server.headless true –server.allowExternalConnections false` 設定を確認する。

失敗5:アクセスログを取らずに運用する

万一インシデントが発生した際、誰がどのサマリ生成を実行したか追跡できなくなります。
回避策:最初から構造化ログ(JSON形式)を院内DBまたはSIEMに送信する設計にする。ログ保管期間は法令・院内規程に従う。

11. 他の医療用途への応用可能性

本記事で紹介した2層構成は、電子カルテのSOAPサマリ以外にも応用できます(※いずれも想定パターン):

  • 紹介状・退院サマリの下書き生成:入院経過を要約し、紹介先への文書作成工数を削減
  • レセプト入力補助:診療記録から病名コード候補を提示(最終判断は医師)
  • 外来問診票の構造化:自由記述の問診を構造化データに変換し、カルテ入力を効率化
  • 申し送りメモの要約:夜勤・日勤交代時の申し送り内容を箇条書きサマリに変換
  • 治験プロトコル適合チェック:患者属性(匿名化済み)と試験参加条件の照合支援

いずれも、匿名化ファーストの2層構成を基本に、用途に合わせてプロンプトとUIをカスタマイズする形で対応できます。

12. FAQ

Q1: Claude Code は医療情報を学習に使いますか?

A: Anthropic の利用規約では、デフォルト設定でのAPIアクセスにおいてデータをモデルトレーニングに使用しないオプトアウト設定が提供されています。ただし、利用規約は変更される可能性があるため、必ずAnthropic 公式プライバシーポリシーの最新版を確認し、院内コンプライアンス担当者と相談してください。

Q2: 個人情報保護法的に問題ありませんか?

A: 厚生労働省「医療情報システムの安全管理に関するガイドライン第6.0版」では、クラウドサービス利用時の安全管理措置が定められています。本記事の設計(匿名化+ローカルLLMフォールバック)はそのアプローチの一例ですが、個別のクリニック・病院の状況に応じて法務・コンプライアンス専門家に相談することを強くお勧めします。AIが適法性を保証するものではありません。

Q3: ローカルLLMの品質は十分ですか?

A: Qwen2.5:14b以上のモデルでは日本語の医療テキスト処理に一定の性能が期待できますが、Claude 3.5 Sonnet等のクラウドモデルと比較すると品質に差が出る場合があります。本番導入前に、実際のダミーデータを使った品質評価と医師によるサマリ確認テストを十分に行ってください。

Q4: 電子カルテシステムとどう連携しますか?

A: 電子カルテシステムのAPIやCSVエクスポート機能を通じてデータを取得し、処理後のサマリを再度インポートする形が一般的です。HL7 FHIR形式に対応している場合はFHIR APIを活用できます。ただし、電子カルテベンダーとの契約・仕様確認が必要です。

Q5: 中小クリニックでも導入できますか?

A: 本記事の構成はスモールスタートを前提に設計していますが、セキュリティ設計・ネットワーク分離・コンプライアンス対応には相応のコストと専門知識が必要です。まずは院内IT担当者または医療情報システムの専門ベンダーに相談することをお勧めします。Claude Codeはあくまで実装補助ツールであり、システム全体の安全性は設計・運用体制が決定します。

Q6: ローカルLLMの運用コストはどのくらいですか?

A: GPU搭載のオンプレミスサーバーが必要です。Qwen2.5:14b動作には約9GB以上のVRAMが目安です(2026年6月時点の参考値)。初期費用(サーバー購入)のほか、電力・保守コストも考慮してください。クラウドLLMとのコスト比較は用途規模によって異なります。

13. まとめ:今日から始める3つのアクション

  1. 匿名化パイプラインのテスト:サンプルコード①をダミーデータで動かし、PHI検出の精度を自分の環境で確認する
  2. Ollamaの院内サーバーへのインストールollama.aiから入手し、Qwen2.5:14bモデルをダウンロードして動作確認する
  3. コンプライアンス担当者との相談:技術的なプロトタイプが動いた段階で、院内の法務・コンプライアンス担当者または外部専門家にアーキテクチャレビューを依頼する

医療AIの実装は「動けばいい」ではなく、「安全に動き続ける」設計が不可欠です。Claude Code は実装作業を大幅に効率化しますが、最終的な安全性の責任は実装チームと医療機関にあります。段階的に、専門家と連携しながら進めることをお勧めします。

Claude Code の医療・ヘルスケア分野への導入を検討中の方へ

株式会社Uravation では、Claude Code の業界別実装サポートを提供しています。医療情報システムへの適用についても、個別のご相談をお受けしています。

無料相談・お問い合わせはこちら →

著者プロフィール

佐藤傑(さとう・すぐる) 株式会社Uravation代表取締役。
X(@SuguruKun_ai)フォロワー約10万人。100社以上の企業向けAI研修・導入支援を展開。
著書『AIエージェント仕事術』(SBクリエイティブ)。SoftBank IT連載7回執筆。

Next Step

この事例を、自社の業務に置き換える。

対象業務、利用データ、評価基準、社内展開の順番まで整理すると、Claude Code導入の失敗を減らせます。

導入を相談する