医師は1日に20〜30人の患者を診ます。
各診察の後、15〜20分をEHR(電子カルテ)システムにメモをタイプすることに費やします — 患者を治療するのではなく、フォームを埋めるだけです。週末には、純粋なドキュメント作業のためのオーバーヘッドが8時間以上になります。
これは業務フローの問題ではありません。危機です。医師の燃え尽き(バーンアウト)は現実であり、その大きな要因が書類作成です。Prolificsでは、医療クライアントのインフラを近代化する取り組みを支援していますが、この問題は何度も — 毎回のように — 持ち上がってきました。
そこで、それを解決するためのパイプラインを作りました。医師と患者の会話を聞き取り、構造化された診療記録をEHRへ自動生成する、アシスタント型のアンビエントAIです。
以下が、私たちがどのように実現したか — アーキテクチャ、ツール、トレードオフ、そして結果です。
実際の問題(「入力しすぎ」だけではない)
コードに触れる前に、何が本質的に壊れているのかをはっきりさせましょう。
EpicやCernerのようなEHRシステムは強力ですが、臨床家にとって使いにくいのです。
それらは使いやすさのためではなく、コンプライアンスのために作られています。医師は手作業で:
•適切なSOAP記録テンプレートを選ぶ
•症状、既往、評価、計画を入力する
•ICD-10コードを添付する
•次の患者へ進む前にサインする
一方で患者は、その間ずっとスクリーンを見つめられている状態です。
「人を助けるために医学部に行ったのであって、データ入力係になりたかったわけじゃない。」
— 私たちのクライアントの医療システムにいる医師
この言葉が私たちの心に残りました。これが、1文で表す問題設定です。
アーキテクチャ:正しく用意すべき4つのレイヤー
本番環境で動くアンビエント・ドキュメンテーションシステムは、単に「音声認識+GPT」ではありません。明確に分かれた4つの技術レイヤーからなるパイプラインです。
[マイク入力]
↓
[ASR — 自動音声認識]
↓
[話者ダイアライゼーション — 誰が話したか]
↓
[臨床NLP + 固有表現認識]
↓
[LLM要約 → SOAPノート]
↓
[FHIR API → EHR(Epic / Cerner)]
それぞれを順に見ていきます。
レイヤー1:ASR — 自動音声認識
私たちは3つの選択肢を評価しました。
ツール 精度 HIPAA BAA レイテンシ
Whisper(OpenAI) 非常に高い いいえ(自己ホストのみ) 中
Azure Speech 高い はい(設定あり) 低
AWS Transcribe Medical 高い はい(ネイティブ) 低
私たちは本番運用では、AWS Transcribe Medicalを選びました。臨床用の語彙に合わせて設計されているためです。「メトホルミン」「駆出率」「CABG」のような用語を、語彙のカスタムチューニングなしで扱えます。
import boto3
transcribe = boto3.client('transcribe', region_name='us-east-1')
def start_transcription(audio_s3_uri: str, job_name: str):
transcribe.start_medical_transcription_job(
MedicalTranscriptionJobName=job_name,
Media={'MediaFileUri': audio_s3_uri},
MediaFormat='mp4',
LanguageCode='en-US',
Specialty='PRIMARYCARE',
Type='DICTATION',
OutputBucketName='your-hipaa-bucket'
)
リアルタイム用途(院内の部屋)では、WebSocketを介してAWS Transcribe MedicalのストリーミングAPIを使い、音声を直接ストリーミングしました — テストではレイテンシは300ms未満でした。
レイヤー2:話者ダイアライゼーション
生のトランスクリプトは、「誰が何を話したか」を分からないままだと役に立ちません。医師と患者は話し方が違い、両者の言葉を1つの塊として混ぜてしまうと、その後のNLPが壊れます。
話者セグメンテーションには、pyannote.audio(オープンソース、自己ホスト可能)を使いました。
from pyannote.audio import Pipeline
pipeline = Pipeline.from_pretrained("pyannote/speaker-diarization")
diarization = pipeline("encounter_audio.wav")
for turn, _, speaker in diarization.itertracks(yield_label=True):
print(f"{speaker}: [{turn.start:.1f}s → {turn.end:.1f}s]")
実運用では、最初の10秒の音声に基づいてSpeaker_00を「Clinician」、Speaker_01を「Patient」とラベル付けしました(医師は会話の最初に必ず切り出します)。
レイヤー3:臨床NLP + 固有表現認識
ここが、一般的なパイプラインが破綻する地点です。汎用のLLMは用量を捏造(ハルシネーション)したり、症状の帰属を誤ったりし、「否定」を見落とします(「胸痛はありません」が「胸痛です」になってしまう — 危険な誤りです)。
そこで、scispaCy(臨床NLPライブラリ)を事前処理のフィルタとして動かし、次を抽出しました。
•症状(UMLSのエンティティ・リンキング)
•薬剤+用量
•診断(ICD-10の候補マッピング)
•否定(臨床的な正確さにとって重要)
import scispacy
import spacy
nlp = spacy.load("en_core_sci_lg")
doc = nlp("Patient denies chest pain. Currently on 500mg metformin twice daily.")
for ent in doc.ents:
print(ent.text, ent.label_)
出力:
chest pain — DISEASE
metformin — CHEMICAL
500mg — DOSAGE
この構造化された出力をLLMのプロンプトに渡しました — 生のトランスクリプトではありません。これにより、ハルシネーションが大幅に減りました。
レイヤー4:LLM要約 → SOAPノート
構造化されたNERの出力と、話者分離されたトランスクリプトが揃うと、Claude(Anthropic API経由)にプロンプトを投げ、臨床ノートを生成します。
system_prompt = """
You are a clinical documentation assistant.
Generate a structured SOAP note from the encounter transcript.
Use only explicitly stated clinical facts.
Never infer diagnoses not mentioned.
Flag any low-confidence fields with [REVIEW NEEDED].
Output in JSON matching HL7 FHIR DocumentReference schema.
"""
user_prompt = f"""
Clinician: {clinician_turns}
Patient: {patient_turns}
Extracted Entities: {ner_output}
Generate SOAP note.
"""
[REVIEW NEEDED]のフラグは、臨床ステークホルダーからの譲れない要件でした。医師はサインする前に出力を信頼する必要があります — 沈黙した誤りより、信頼度のシグナルの方がよいのです。
レイヤー5:FHIR API → EHR統合
生成されたノートは、SMART on FHIR APIを介してEpicへDocumentReferenceリソースとして投入します。
import requests
fhir_note = {
"resourceType": "DocumentReference",
"status": "current",
"type": {
"coding": [{
"system": "http://loinc.org",
"code": "11506-3",
"display": "Progress note"
}]
},
"content": [{
"attachment": {
"contentType": "text/plain",
"data": base64.b64encode(soap_note.encode()).decode()
}
}]
}
response = requests.post(
f"{FHIR_BASE_URL}/DocumentReference",
json=fhir_note,
headers={"Authorization": f"Bearer {access_token}"}
)
ハマりどころが1つあります。EpicのサンドボックスFHIRサーバーはLOINCコードの正確性に厳しいです。コードが間違っていると、サイレントな422エラーになります。必ず最初にLOINCデータベースと照合して検証してください。
HIPAA:飛ばせないレイヤー
すべてのコンポーネントは、Business Associate Agreement(BAA)を必要とします。
•AWS Transcribe Medical — ネイティブBAA
•S3バケット — 保存時(AES-256)および転送時(TLS 1.2+)に暗号化すること
•LLM API呼び出し — プライベートエンドポイントを使うか、自己ホストのモデルを使う
•音声の保管期間 — 方針を最初に決める:30日間のQAか、それとも医療記録のように7年以上か?
私たちは標準で、72時間後に生の音声を削除し、識別不能化した構造化ノートのみを保持するようにしました。
実際の成果
このパイプラインを、中規模のプライマリケアグループ(医師12名、週あたり約250件の診療)に導入しました。
導入から60日後:
•1回の診療あたりのドキュメント時間:約18分 → 約4分に減少
•医師の燃え尽きスコア(検証済みの尺度):13ポイント分低下(公表された研究と整合)
•当日中のノート完了率:71% → 96%に上昇
•請求コードの取り込み精度:改善 — NERレイヤーが、これまで見逃されていたHCCコードを捉えました
医師たちは、単に速くなっただけではありません。患者と向き合っている時間が増えたと、私たちに話してくれました。
開発者向けの要点
返却形式: {"translated": "翻訳されたHTML"}1.生のトランスクリプトをそのままLLMの入力として使わないでください。まず臨床NLPで前処理を行わないと、患者に害を及ぼし得る幻覚が生じます。
2.話者ダイアライゼーションは必須です。これがなければ、帰属(アトリビューション)の誤りによって臨床記録が損なわれます。
3.[REVIEW NEEDED](要レビュー)フラグがプロジェクトを救いました。臨床家はブラックボックスの出力を信頼しません。透明性を組み込んでください。
4.FHIRは統合レイヤーであって、単なる後付けではありません。SMART on FHIRを早い段階で学びましょう。EHRのサンドボックス環境はつらくて、遅いです。
5.HIPAA準拠はチェックボックスではなく、アーキテクチャです。コードを書く前にデータフローを設計してください。
次に何をするか
現在、以下を実験しています:
•リアルタイムのアンビエントモード — 診察中にノートを生成し、後から生成するのではなく
•専門領域別モデル — 腫瘍内科と循環器内科では、ノートの構造がまったく異なります
•エージェント型の事前承認(prior auth) — 構造化されたノート出力を使って、保険の事前承認リクエストを自動で下書き作成(現在、最も大きい管理業務の時間の浪費がここです)
Prolificsでは、何十年もエンタープライズ向けのヘルスケア統合に取り組んできました。LLM + FHIR APIの組み合わせは、臨床ワークフローのツールにおける本当に最も刺激的な変化であり、私たちはまさにその入口に立ったばかりです。
話しましょう
あなたはこれに似たものを何か作りましたか?EHR統合でいちばん大変だったのは何でしたか――FHIR API、HIPAA準拠レイヤー、あるいは臨床家の納得(導入の合意)を取り付けることですか?
経験をコメントに書いてください。特に、リアルタイムのアンビエント・スクリビングに取り組んだ方の話をぜひ聞きたいです。ストリーミングのレイテンシの課題は手強くて、他の人たちも私たちと同じ壁にぶつかっているのではないかと思います。



