2024年8月9日、開業医の先生方を対象に、Text Blazeを活用した電子カルテ入力のオンラインセミナーを開催しました。セミナーでは、日々の診療業務で役立つText Blazeの基本機能から、電子カルテ情報を活用したより高度なスニペットの作成方法まで、幅広い内容を取り扱いました。セミナーの内容を本ブログで公開いたします。
セミナーの概要
- Text Blazeの利点と基本機能
- 動作が非常に早く、Stream Deckなどのマクロキーボードと比較しても圧倒的に効率的です。
- 電子カルテから患者情報を自動取得し、診断書や計画書の作成をスムーズに行えます。
- 高度にカスタマイズ可能で、各クリニックのニーズに合わせたテンプレートを作成できます。
- 動作確認と環境設定
- 各種電子カルテとの連携動作を確認し、実際の業務での利用を見据えた環境設定の方法を紹介しました。
- 最新バージョンのText Blazeを使用し、Chromeへの拡張機能のインストール方法や、印刷用紙設定なども解説しました。
- Text Blazeを用いた具体的なスニペット作成
- 生活習慣病療養計画書のデモを行い、実際にText Blazeでスニペットを作成して動作を確認しました。
- 特に、BMI計算プログラムの作成方法についてスニペット作成の基礎から応用までを実演しました。
下記のコードでは身長、体重からBMI、標準体重を計算しています。Text Blazeでは日本語を変数として利用するには“(バックスラッシュ)で囲む必要があります。
身長: {formtext: name=身長; default=160; cols=5} 体重:{formtext: name=体重; default=60; cols=5}{bmi=`体重`/((`身長`/100)^2)}BMI: {=bmi; format=,.1f}{ideal_bw22=22*((`身長`)/100)^2}{ideal_bw25=25*((`身長`)/100)^2}
標準体重: {=ideal_bw22; format=,.1f}(BMI22)Kg~{=ideal_bw25; format=,.1f}(BMI25)Kg
- 電子カルテ情報の抽出と利用
- 正規表現を活用した情報抽出方法を紹介し、特にデジカルやクリニクスなどの電子カルテシステムから氏名や年齢を抽出する具体的なコード例を示しました。
- 参加者からの質問も多く、非常に実践的なセッションとなりました。
下記のコードでクリニクスで電子カルテ上の属性情報を取得できます。
{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.names-name}さんは{if: bmi>25}肥満です{elseif: bmi<18.5}痩せです{else}正常範囲内の体重です{endif}
{site}コマンドが電子カルテ情報を入力するうえで非常に重要です。デジカルの場合は少し工夫が必要です。
{=extractregex({site: text; page=https://demo.digikar.jp/*; select=ifneeded; selector=.css-e6sfbd > :nth-child(1)}, "\d+ ([\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}ー]+[\s ][\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}ー]+)")}さんは{if: bmi>25}肥満です{elseif: bmi<18.5}痩せです{else}正常範囲内の体重です{endif}
デジカルで属性情報を取得するには下記のコードで該当する部分が抽出可能です。
{site: text; page=https://demo.digikar.jp/*; select=ifneeded; selector=.css-e6sfbd > :nth-child(1)}
このコードで抽出した患者属性情報は”00009 東京 洋子 トウキョウ ヨウコ 0歳3ヶ月8日”の形式になっており氏名のみを取り出すためには{extraregex}で患者情報の取得と正規表現を利用する必要があります。”\d+ ([\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}ー]+[\s ][\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}ー]+)”の部分はIDに引き続いて日本語で名字、半角スペース空けて名前が来るという部分を指定しています。
このコードをさらに工夫すると前回診察時のカルテから初期値として身長体重を取得するという事が可能になります。下記はクリニクス用のコードです。
{if: catch({site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.KlxeCpDfqdtdJcyXPd0h > :nth-child(2) section > div}, "ERR") <> "ERR"}{p_record={site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.KlxeCpDfqdtdJcyXPd0h > :nth-child(2) section > div}}{bmi_measure=testregex({=p_record}, "身長") and testregex({=p_record}, "体重")}{if: bmi_measure}{run: `身長`=extractregex(p_record, "身長\\D{0,8}([0-9]{1,5}(?:\\.[0-9]{1,2})?)", "i")}{run: `体重`=extractregex(p_record, "体重\\D{0,8}([0-9]{1,5}(?:\\.[0-9]{1,2})?)", "i")}{endif}{endif}身長: {formtext: name=身長; cols=5; default=160}cm 体重: {formtext: name=体重; cols=5; default=60}kg{bmi=`体重`/(`身長`/100)^2}
BMI: {=bmi; format=,.1f}{ideal_bw22=22*((`身長`)/100)^2; format=,.1f}{ideal_bw25=25*((`身長`)/100)^2; format=,.1f}
理想体重: {=ideal_bw22; format=,.1f}(BMI22)~{=ideal_bw25; format=,.1f}(BMI25)kg
{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.names-name}さんは{if: bmi>25}肥満です{elseif: bmi<18.5}痩せです{else}正常範囲内の体重です{endif}
理想体重との差は{=`体重`-ideal_bw22; format=,.1f}㎏です。
{catch}コマンドで前回のカルテ記載の有無を判定、カルテ記載がある場合は{testregex}で身長、体重の情報があるかどうかを判定、前回のカルテ記載がある場合は{extraregex}で身長、体重を抽出します({testregex}でデータの有無をチェックしないとエラーがでます)。ここで初期値として前回の値をいれてしまうと変更できなくなるので{run}コマンドで変更可能にしています。{run}コマンドは最近導入された新しいコマンドでこれを用いるとコードがシンプルになります。{run}コマンドを使用できなかったときは初期値を変更可能にするのはdefault値として入れ込まないといけないのですが、そうすると入力フィールドごとに前回カルテ有無の判定、{testregex}、{extraregex}とコードを書かないといけなかったのですが{run}コマンドが導入されたことによりその必要がなくなりコードもシンプルになりました。
Chat GPTでコード解説をしてみます。Chat GPTのGPTsでText Blazeのコード作成、解説をするボットを作りましたのでそれも紹介します。
https://chatgpt.com/g/g-waKRQOboH-text-blazenokotonituiteshuo-ming-xiu-zheng-surutiyatutohotuto
このText Blazeコードは、指定されたウェブページから情報を抽出し、その情報を使用してBMI(ボディマス指数)を計算し、ユーザーに適切なメッセージを表示するスニペットです。コードの詳細は以下の通りです。
### 構成要素の説明
1. **データ抽出:**
- `{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.KlxeCpDfqdtdJcyXPd0h > :nth-child(2) section > div}`: この部分は、指定されたURLから指定されたCSSセレクターに基づいて情報を抽出します。
- `catch({site: text; ...}, "ERR") <> "ERR"`: `catch`関数でエラー処理が行われており、エラーが発生しない場合に処理が進行します。
2. **BMI測定判定:**
- `bmi_measure=testregex({=p_record}, "身長") and testregex({=p_record}, "体重")`: `testregex`を使って抽出した情報に「身長」と「体重」が含まれているかを確認します。両方が含まれている場合に`bmi_measure`は`true`になります。
3. **身長と体重の抽出:**
- `{run: `身長`=extractregex(p_record, "身長\\D{0,8}([0-9]{1,5}(?:\\.[0-9]{1,2})?)", "i")}`: `extractregex`で「身長」の値を抽出し、`身長`変数に代入します。
- `{run: `体重`=extractregex(p_record, "体重\\D{0,8}([0-9]{1,5}(?:\\.[0-9]{1,2})?)", "i")}`: 同様に「体重」の値を抽出し、`体重`変数に代入します。
4. **フォーム入力の設定:**
- `身長: {formtext: name=身長; cols=5; default=160}cm`と`体重: {formtext: name=体重; cols=5; default=60}kg`: これらはデフォルト値が設定されたフォーム入力フィールドです。ユーザーが自分の身長と体重を入力するためのフィールドです。
5. **BMI計算:**
- `{bmi=`体重`/(`身長`/100)^2}`: 身長と体重からBMIを計算し、`bmi`変数に格納します。
6. **理想体重の計算:**
- `{ideal_bw22=22*((`身長`)/100)^2; format=,.1f}`: BMIが22のときの理想体重を計算します。
- `{ideal_bw25=25*((`身長`)/100)^2; format=,.1f}`: BMIが25のときの理想体重を計算します。
7. **体重の評価:**
- `{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.names-name}さんは`: ユーザー名を取得し、それに基づいてメッセージを表示します。
- `{if: bmi>25}肥満です{elseif: bmi<18.5}痩せです{else}正常範囲内の体重です{endif}`: BMI値に基づいてユーザーの体重状態(肥満、痩せ、正常)を判定します。
8. **理想体重との差:**
- `理想体重との差は{=`体重`-ideal_bw22; format=,.1f}㎏です。`: 現在の体重とBMI22の理想体重との差を計算し表示します。
### 改善点
- 現在のコードは、エラーハンドリングを適切に行っていますが、抽出したデータがうまく取得できない場合やフォーマットが違う場合の処理を追加すると、さらに堅牢になります。
- また、BMIの判定基準やメッセージをカスタマイズすることで、よりパーソナライズされたフィードバックを提供できます。
コードをフローチャートで図示してみます。
- AIの活用と今後の展望
- ChatGPTのAPIや、Text Blazeに組み込まれたAIを活用して、さらに効率化を図る方法についても触れました。ただし、AIの活用はあくまで試験的な試みであり、実際の診療業務には使用しておりません。
AIを利用する方法は2通りあります。
1.Chat GPTのAPIを利用する方法
出力値を変数に格納できる、Open AIのAPIの取得が必要
2.Text Blaze組み込みのAIを利用する方法
早い、手軽
おすすめは2のText Blaze組み込みのAIを利用する方法です。下記のリンクからAI利用が可能となります。
https://dashboard.blaze.today/gallery/gVYYVFmbKweldxLhLNi8
例えば下記のコード(プロンプト)で紹介状を作成することもできます。
{formtext: name=紹介目的}
本日カルテ内容から診療情報提供書の文面を作成してください。本日のカルテ内容は{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=#app textarea}です。病名は#のあとに記載されているのでピックアップしてください。患者の年齢、性別は{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.profile}を参照してください。本日診察の項目は{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.cmmV0JdL9dGMiHkbuMgB}です。
前回の診察記録は{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=:nth-child(4) article div}、前々回の診察記録は{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=:nth-child(5) section > div}です。経過も考えて診療情報提供書を作成してください。
"本日の診察内容:
- 処置・行為
- 検査依頼
- 診察グループ
- 再診料
- 明細書発行体制等加算
- 時間外対応加算3
- 医療情報取得加算3(再診)
- 外来感染対策向上加算(再診)
- 連携強化加算(再診)
- サーベイランス強化加算(再診)
- 抗菌薬適正使用体制加算(再診)
- 生活習慣病管理料2"
のような情報は不要です。
[q], [/q]のようなタグは不要です。
処方内容も含めてください。
本日診察の投薬グループの内容を抽出、投薬内容および用法を紹介状記載用に,投薬内容ごとに”投薬内容+用法”の形式で簡潔に要約してください。本日診察の項目は{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.cmmV0JdL9dGMiHkbuMgB}です
*[q], [/q]のようなタグは無しでお願いします
例)
いつも大変お世話になっております。
{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.names-name}様は**のため通院中です。
以前から健診で高血圧を指摘されていたとのことですが、内科的治療は未治療で、当院での血圧測定では160/100 mmHgでした。
お忙しいところ大変恐縮ですが、ご高診の上ご加療の程よろしくお願い致します。
Text Blazeを用いると電子カルテ記載を取り込めますのでAIと組み合わせることによりより精度の高い情報を出力することが可能です。クリニクスを例にあげますと本日のカルテ記載はです。
{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.cmmV0JdL9dGMiHkbuMgB}
前回のカルテ記載の記載日、内容はそれぞれ
{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.KlxeCpDfqdtdJcyXPd0h > :nth-child(2) .xglUiKNcrghA08Es_hNx}
{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.KlxeCpDfqdtdJcyXPd0h > :nth-child(2) .guTKKG6Oybfn8NJ8v6AC}
となります。同様にn回前のカルテ記載の記載日、内容は
{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.KlxeCpDfqdtdJcyXPd0h > :nth-child(n) .xglUiKNcrghA08Es_hNx}
{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.KlxeCpDfqdtdJcyXPd0h > :nth-child(n) .guTKKG6Oybfn8NJ8v6AC}
となります。10回分のカルテ読み込み、本日の診察内容、処方内容を読み込んだうえで紹介状を作成するコード/プロンプトは下記のようになります。面白いのは実際に同じことをAI無しで実装するにはかなり複雑なコードが必要になりますが、AIと組み合わせると比較的短いプロンプトで実装可能です。そう考えるとプロンプトは自然言語プログラミングというほうが良いようにも思います。
{formtext: name=紹介目的}
カルテ内容から診療情報提供書の文面を作成してください。病名は#のあとに記載されているのでピックアップしてください。患者の年齢、性別は{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.profile}を参照してください。本日診察の項目は{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.cmmV0JdL9dGMiHkbuMgB}です。
本日のカルテ内容は{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=#app textarea}です。
以前のカルテ記録は
前回:{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.KlxeCpDfqdtdJcyXPd0h > :nth-child(2) .xglUiKNcrghA08Es_hNx}{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.KlxeCpDfqdtdJcyXPd0h > :nth-child(2) .guTKKG6Oybfn8NJ8v6AC}
2回前:{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.active :nth-child(3) .xglUiKNcrghA08Es_hNx}{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.active :nth-child(3) .guTKKG6Oybfn8NJ8v6AC}
3回前:{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=:nth-child(4) .xglUiKNcrghA08Es_hNx}{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=:nth-child(4) .guTKKG6Oybfn8NJ8v6AC}
4回前:{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.active :nth-child(5) .xglUiKNcrghA08Es_hNx}{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=:nth-child(5) .guTKKG6Oybfn8NJ8v6AC}
5回前:{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.active :nth-child(6) .xglUiKNcrghA08Es_hNx}{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=:nth-child(6) .guTKKG6Oybfn8NJ8v6AC}
6回前:{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.active :nth-child(7) .xglUiKNcrghA08Es_hNx}{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=:nth-child(7) .guTKKG6Oybfn8NJ8v6AC}
7回前:{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.active :nth-child(8) .xglUiKNcrghA08Es_hNx}{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=:nth-child(8) .guTKKG6Oybfn8NJ8v6AC}
8回前:{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.active :nth-child(9) .xglUiKNcrghA08Es_hNx}{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=:nth-child(9) .guTKKG6Oybfn8NJ8v6AC}
9回前:{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.active :nth-child(10) .xglUiKNcrghA08Es_hNx}{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=:nth-child(10) .guTKKG6Oybfn8NJ8v6AC}
10回前:{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.active :nth-child(11) .xglUiKNcrghA08Es_hNx}
"本日の診察内容:
- 処置・行為
- 検査依頼
- 診察グループ
- 再診料
- 明細書発行体制等加算
- 時間外対応加算3
- 医療情報取得加算3(再診)
- 外来感染対策向上加算(再診)
- 連携強化加算(再診)
- サーベイランス強化加算(再診)
- 抗菌薬適正使用体制加算(再診)
- 生活習慣病管理料2"
のような情報は不要です。
[q], [/q]のようなタグは不要です。
処方内容も含めてください。
本日診察の投薬グループの内容を抽出、投薬内容および用法を紹介状記載用に,投薬内容ごとに”投薬内容+用法”の形式で簡潔に要約してください。本日診察の項目は{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.cmmV0JdL9dGMiHkbuMgB}です
*[q], [/q]のようなタグは無しでお願いします
例)
いつも大変お世話になっております。
{site: text; page=https://karte.medley.life/*; select=ifneeded; selector=.names-name}様は**のため通院中です。
以前から健診で高血圧を指摘されていたとのことですが、内科的治療は未治療で、当院での血圧測定では160/100 mmHgでした。
お忙しいところ大変恐縮ですが、ご高診の上ご加療の程よろしくお願い致します。
セミナーの成果
セミナー後、参加者からは「これまで手作業で行っていた作業が短縮できそう」「も導入を検討したい」といった前向きなフィードバックをいただきました。今回のセミナーで使用したスライドをブログにて公開しております、参加できなかった方もぜひご参照ください。
今回はハンズオン形式というよりはウェビナー形式になりました、もしハンズオントレーニングにニーズあるようならまた次回セミナーを検討したいと考えています。実際に手を動かしてコードを書いて自分に必要なものを作成するのがプログラミング上達の秘訣かと思います。
Text Blazeのインストールとスニペットの共有
Text Blazeは下記からインストールできます。コマンドをすべて使用するにはPro版の契約が必要です。
https://blaze.today/?ref=UZEGFD8L
作成したスニペットは下記からダウンロードできるようにしています。生活習慣病療養計画書のテンプレートもここからダウンロードできます。
https://dashboard.blaze.today/gallery/IukR13Jz4qeJGdH61LCF
スライド
コメント