術前休薬日数をまとめたオープンデータ kyuyaku-data のシリーズ記事です。データそのものの紹介は、開発者向け と 医療者向け の記事に書きました。
手術の前には、服用中の薬の一部を一時的に中止します。これを休薬といいます。kyuyaku-data は、どの薬を手術の何日前から休薬するかを、薬ごとに整理したデータです。根拠にしているのは、国内外の診療ガイドライン (学会が出す診療の指針) と添付文書 (医療者向けの薬の公式文書) です。157 剤について、資料ごとの日数と、その日数が資料のどのページのどの表に書いてあるか (記載箇所)、該当部分の引用を持っています。
医療のデータなので、値の誤りは許されません。ところが、参照しているガイドラインは 30 本を超え、それぞれ数十〜数百ページあります。休薬日数の多くは表に書かれ、同じ薬でも資料や版ごとに値が違います。実際に、PDF から読み取った値には誤りが数多く見つかりました。
そこで、読み取りは誤りうるという前提で、次の段階を重ねています。
- 同じ PDF を、2 通りの指示文で独立に読み取る
- 2 回の結果が食い違った箇所だけ、原文の該当ページで確かめ直す
- 引用と数値が原文にあるかを、プログラムで機械的に照合する
- 人が原文で確認し、検査にすべて合格したものだけを公開する
PDF の読み取りと照合には AI (大規模言語モデル) を使っていますが、公開してよいかを決める検査には AI を使っていません。この記事では、実際に起きた誤りと、それを防ぐための工夫を、使ったモデルやライブラリとあわせて書きます。
読み取りで実際に起きた誤り
AI による読み取りを含め、PDF からの機械的な抽出で実際に見つかった誤りです。いずれも修正済みです。
| 誤りの種類 | 実例 |
|---|---|
| 表番号のずれ | 日本循環器学会のガイドライン (2020 年) の「表 27」にある値を、「表 6」の値として記録していた (55 か所) |
| 別の薬の数値の混入 | 米国区域麻酔学会 (ASRA) のガイドラインで、抗凝固薬エドキサバンの値として、同じ表にある別の抗凝固薬ベトリキサバンの「120 時間」を記録していた (正しくは 70 時間) |
| 期間の取り違え | 同じ ASRA のガイドラインで、血栓溶解薬の値として 10 日を記録していた (正しくは 48 時間) |
| 適用範囲外の当てはめ | 乾癬の治療薬についての日本皮膚科学会のガイダンス (2022 年) で、欧州の資料が TNF 阻害薬という種類の薬に限って示す「半減期の 5 倍」という休薬期間を、別の種類の注射薬 4 剤にも当てはめていた |
| 出典の取り違え | 添付文書に書かれていない日数を、「添付文書に書かれた日数」として記録していた |
| ページのずれ | 高血圧治療ガイドライン 2019 の記載箇所で、PDF ファイル上のページ番号 (p.196) と、紙面に印刷されたページ番号 (p.174) を取り違えていた (29 剤) |
| 引用の 1 語の違い | 欧州心臓病学会 (ESC) のガイドライン (2022 年) の引用で、原文の "lower risk" が "lower rate" になっていた |
| 文字の混線 | 2 段組の添付文書 PDF で、左右の段の文字が混ざった引用を記録していた (2026 年 7 月に 66 剤を是正) |
どれも、一見するともっともらしい誤りです。表の 1 行のずれや 1 語の違いは、人が読み流しても気づきません。値が正しくても、記載箇所がずれていれば、利用者が原典で確かめられなくなります。
PDF の読み方の工夫
関係するページを絞り、元のページ番号で扱う
12 ページを超える PDF では、読み取りの前に、手術前後の休薬に関係するページを探します。まず、pdfminer.six でページごとにテキストを取り出します。それを、モデルに一度に渡せる量に収めるため 400 KB ずつに分けて gpt-oss-120b に渡し、関係するページを広めに選ばせます。分け目で文脈が切れて見落とさないよう、2 つ目以降のまとまりには、直前のまとまりにある最後の 2 ページを先頭に重ねて入れています。選んだページに前後 1 ページを足し、pypdf で元の PDF から切り出してから読み取ります。関係するページを特定できなければ、推測で進めずに止めます。
長いガイドラインは、同じく gpt-oss-120b (reasoning effort は low) に章の構成を検出させ、章ごとに読み取ってから統合します。
ここで一度、ページがずれる不具合が起きました。後で説明する「食い違いに関わるページだけを切り出す」段階で、絞り込み後の PDF を基準にページを数えてしまい、元の PDF のページ番号とずれたのです。今は、切り出しには必ず絞り込み前の元の PDF を使い、記録するページはすべて元の PDF のページ番号に対応付けています。
テキストではなく、PDF のページそのものを読ませる
PDF の表は、テキストとして取り出すと行と列の区切りが崩れやすく、1 行ずれただけで別の薬の日数になります。そこで読み取りには、取り出したテキストではなく、表や図を含むページを PDF のまま使っています。
モデルは、複数の AI モデルを同じ API で呼べる OpenRouter を経由して使っています。読み取りでは、切り出した PDF を base64 にして、OpenRouter の file-parser プラグインで Gemini 3.8 Flash に渡します。出力の上限は 16,384 トークンです。
PDF の渡し方は、結果を大きく左右します。file-parser プラグインには、PDF の扱い方 (PDF エンジン) が 3 つあります。
-
native: PDF をそのままモデルに渡す。PDF を直接読めるモデルでだけ使える -
cloudflare-ai: 先に PDF をテキストに変換してから渡す -
mistral-ocr: OCR (画像から文字を読み取る処理) で変換してから渡す
表を正しく読ませるには、PDF を直接読めるモデルに native で渡すのが前提です。後で書くように、テキストへの変換を挟むエンジンでは、モデルに本文が届かないことがありました。
表の行と列、推奨文に付いた項目番号 (例: 推奨 13.2.1) は、PDF を画像にして目でも確かめています。
2 段組の PDF は、段組みを認識できるライブラリで読む
添付文書の多くは 2 段組です。当初は pdfminer.six と pypdf でテキストを取り出していました。この方法では、左右の段が行単位で混ざりました。さらに、CID フォント (文字を文字コードではなくフォント内の番号で持つ形式) の文字が (cid:NNNN) という表記のまま残り、判読できない引用になっていました。
PyMuPDF の get_text() で取り出し直すと、段の順序と CID フォントの文字が正しくなりました。見た目が同じで文字コードの違う字 (「⿇」と「麻」など) も、ふつうの字にそろいました。今は、混線の跡 ((cid: の文字列、日本語の文字どうしの間に入った空白、節番号の断片など) が紛れ込んでいないかを、変更のたびにプログラムで検査しています。
2 回独立に読み取り、食い違いを原文で確かめる
同じガイドラインを、異なる指示文 (プロンプト) で 2 回読み取ります。2 回目には 1 回目の結果を見せません。読み取る項目は、薬ごとの休薬日数、用量や腎機能による条件、記載箇所、原文の引用です。休薬日数は、出血のリスクが高い手術と低い手術に分けて読み取ります。片方の読み方だけで生じた思い込みは、もう片方との食い違いとして表に出ます。
2 つの結果の突き合わせは、2 つの手順に分けています。
- 2 回分の読み取り結果 (YAML) だけを比べて、食い違いの一覧を作る。この手順では PDF を読まないので、安価な GLM 5.3 Flash で済ませている
- 食い違いに関わるページを、元の PDF から前後 1 ページ付きで切り出し、Gemini 3.8 Flash にそのページの原文を読み直させて、どちらが正しいかを判定させる
判定の経緯は記録として残します。最終結果は jsonschema で決められた形式 (スキーマ) に合うかを確かめ、合わなければ採用しません。
2 回目の読み取りには、開発元の違うモデルも試しました。132 ページのガイドラインで比べたところ、gpt-oss-120b はコンテキストの上限を超え、章ごとに分けると出力が日本語と英語に分かれて、統合したときに同じ薬が重複しました。GLM 5.3 Flash は表の数値や脚注の条件まで正しく取り出し、費用は 1 回 $0.012 でした。なお、ガイドラインの改訂を検知したあとに毎月自動で行う読み取りでは、2 回とも Gemini 3.8 Flash を使い、指示文だけを変えています。
この段階までの結果は、あくまで「未確認」です。公開データには、次の検査に合格し、人が確認したものだけが入ります。
引用と数値を、原文と機械的に照合する
データを変更するたびに次の検査を実行し、すべてに合格しなければ、公開データにも、Web アプリと API を配信しているサーバーにも反映しません。これらの検査は自作の Python スクリプトで、AI を使っていません。
引用が原文にあるか
記録した引用が、一次資料 (ガイドラインや添付文書の原文) から取り出した本文に、文字どおり存在するかを確かめます。本文は pdfplumber で取り出しています。CID フォントのせいで日本語がマラヤーラム文字などに化ける PDF は、PyMuPDF で取り出し直しています。比べる前に、両方を Unicode の NFKC で正規化し、記号の表記をそろえ、空白を除き、小文字にします。原文にない引用があれば、検査は失敗します。
数値が原文のその薬の近くにあるか
記録した休薬日数などの数値から、原文をさかのぼって確かめます。数値が、その薬の引用の中、または原文でその薬の名前から 200 字以内に現れれば一致とします。単純な文字列検索では誤一致が多いので、次の点を作り込みました。
- 探している数字が、別の数の一部に一致しないようにする。「7」を探すときに「17」に、「4」を探すときに「4.7」や「24」に一致させない
- 日・週・月・時間を換算して比べる。範囲で書かれた値は、両端を確かめる
- 表の見出しの
Day NやWeek Nは照合に使わない。日程表の見出しに必ず一致してしまうため
照合の範囲を調べたところ、300 字を超える距離での一致はすべて誤一致でした。そこで範囲を 200 字にしています。
さらに、自動で一致したかどうかにかかわらず、ガイドラインから読み取ったすべての数値 (703 件) に、原文を読んで確認した記録を付けることを必須にしました。記録のない値が出てきたら、検査は失敗します。記録の内訳は次のとおりです。
- 原文にその値が書かれているもの: 531 件
- 半減期 (薬の血中濃度が半分になるまでの時間) の倍数などから計算した値: 113 件
- 薬の種類ごとの推奨を、個々の薬に当てはめた値: 59 件
そのガイドラインに書かれていない、ほかの資料から持ってきた値は、記録があっても失敗にします。
意図しない値の変化がないか
全薬剤・全ガイドラインの値をスナップショットとして記録し、変更のたびに突き合わせます。ある箇所の修正が、関係のない薬の値を変えてしまうのを防ぐためです。意図した変更は、スナップショットを明示的に更新して承認します。承認しなければ、コミットも反映もできません。
そのほかの検査
- 別名の衝突: 同じ成分の飲み薬と注射薬のように名前の似た薬に、同じガイドラインの別の値が割り当てられていないか
- 2 段組の抽出崩れ: 引用に文字の混線がないか
- スキーマ: 読み取り結果の形式が正しいか
- 公開データ: 内部用の項目が混ざっていないか、公開データが作者の管理する元のデータ (正本) と一致しているか
これらの検査は、コミット前のフック (pre-commit) と GitHub Actions の両方で実行します。公開リポジトリへの同期とサーバーへの配置は、検査のジョブに合格したときだけ実行されます。
別のモデルに「怪しい箇所」を挙げさせる
703 件の確認を終えたあと、追加の誤り探しとして、データと原文を見比べて「怪しい箇所の候補」を挙げさせる監査をしました。候補を挙げるのは Gemini 3.8 Flash に任せました。PDF エンジンは native、出力の上限は 16,384 トークンです。大きなガイドラインは、記録されたページの前後 1 ページだけを切り出して渡しました (最大 12 ページ)。データを直すかどうかは、候補ごとに Claude Opus 5.5 が原文と照らして判定し、作者が確認しました。
| 項目 | 数 |
|---|---|
| 照合した組み合わせ (資料 × 薬) | 461 (成功 460) |
| 費用 | 約 $4 |
| 判定した候補 | 328 (挙がった候補 312 と、照合から漏れていた 16) |
| 本物の誤り | 195 |
| 誤りではないが、どの資料の値を採るかという方針を見直す指摘 | 93 |
| 誤りではなかった | 28 |
| そのほか | 12 |
本物の誤り 195 件の多くは記載箇所や引用の誤りで、休薬日数そのものに関わる誤りは約 15 件でした。前に挙げた「ページのずれ」と「引用の 1 語の違い」は、この監査で見つかったものです。
最初の指示文では、引用を一定の文字数で切っていることを誤りと指摘するなど、誤検出が多くありました。そこで指示文を直してから全件を実行しています。候補を挙げるモデルには GLM 5.3 Flash も試しました。GLM は PDF を直接読めないため、cloudflare-ai エンジンでテキストに変換して渡しました。すると、一部の薬で本文がモデルに届かなかったので、使っていません。
最後は人が原文で確かめる
読み取った値は「未確認」として扱い、作者が原文で確認するまで公開データに入れません。出典を「確認済み (verified)」にできるのは、原文と照合して記載箇所と引用を記録できたものだけです。要約や検索結果、モデルが覚えている知識は根拠にしません。
決まった手順で確実にできることは、AI を使わずにプログラムで行っています。商品名とジェネリック医薬品名の収集 (KEGG 医薬品データベースから毎月取得) や、ガイドライン PDF と添付文書の改訂の検知 (内容のハッシュ値と本文の比較) がそうです。ガイドラインの改訂を検知すると、新しい版の読み取りまでを自動で行い、結果を作者に届けます。休薬日数を自動で書き換えることはしません。
使ったモデルとツールの一覧
ここまでの各段階で使ったモデルとライブラリをまとめます。モデルはすべて OpenRouter 経由で呼んでいます。
| 段階 | モデル・ライブラリ | 入力 |
|---|---|---|
| 関係するページの絞り込み | gpt-oss-120b | pdfminer.six でページごとに取り出したテキスト |
| 章の検出 (長いガイドライン) | gpt-oss-120b (reasoning effort は low) | 同上 |
| ページの切り出し | pypdf | 元の PDF |
| 読み取り (2 回) | Gemini 3.8 Flash | PDF そのもの (file-parser プラグイン、PDF エンジンは native) |
| 食い違いの一覧 | GLM 5.3 Flash | 2 回分の読み取り結果 (YAML) |
| 原文での判定 | Gemini 3.8 Flash | 食い違いに関わるページを切り出した PDF |
| 形式の検査 | jsonschema | 読み取り結果 (YAML) |
| 照合用の本文の取り出し | pdfplumber (文字化けするものは PyMuPDF) | ガイドラインの PDF |
| 添付文書の本文の取り出し | PyMuPDF | 添付文書の PDF |
| 引用と数値の照合 | 自作の Python スクリプト | 照合用の本文 |
| 追加の監査 | Gemini 3.8 Flash (PDF エンジンは native) |
記録したページの前後を切り出した PDF |
| 原文の確認、監査の候補の判定 | Claude Opus 5.5 (Claude Code) と作者 | PDF の本文と、PDF を画像にした表 |
公開していない部分
公開リポジトリにあるのは、データ、API、検索用の Web アプリ、公開データを整形・配信するコードです。PDF の読み取り・照合のプログラムと指示文、監視の設定は公開していません。AI サービスの呼び出しに課金を伴う契約情報 (API キーなど) と、その運用の設定を含むためです。一次資料の PDF も、各学会・製薬企業の著作物なので公開していません。
おわりに
データを作ってみると、手間の大半は、読み取った値をどう疑い、誤りをどの段階で見つけるかに使っていました。2 回の独立した読み取りと原文での確認で誤りの多くを表に出し、機械的な照合を公開の関門にし、最後は人が原文で確かめます。この重ね方は、医療以外の分野で文書からデータを作る場合にも使えるはずです。
データの中身と使い方は、開発者向けの紹介記事 をご覧ください。誤りに気づいた場合は、根拠となる資料とページを添えて Issue でお知らせください。