追記(2021年7月):この記事のアルゴリズムを使って、ウェブアプリ+デスクトップアプリを作って以下に公開しました。無料です。
電子カルテの音声入力ツールを作ってみました。
利用方法は、ばーっと音声入力したら、後は1クリックで項目ごとに整理したものがエクセル形式でダウンロードされます。
例えば、「名前ハチ、次は、体温37度、次は、体重5kg、次は、所見、嘔吐、下痢、次〜〜。」といった後、エクセルで名前、体温、体重、所見のそれぞれの項目に入力される設計です。音声入力なので間違いが発生するという短所はありますが、このツールの長所は、無言の時間が長めにあっても、飼い主さんとの会話が途中に入っても、大丈夫なところです。
利用の際には、Google documentを使用するのでGoogleアカウントが必要です。無料で使えます。実行する際には、実行権限の承認をしてください。
Google documentを開き、ツール → スクリプトエディタを開き、以下のコード.gsとsidebar_create.htmlを作成してください。
function onOpen() { //ドキュメントを開いたときの動作。リボンにカルテ用に整理すると使用方法の説明のリボンを作る
var ui = DocumentApp.getUi();
//リボン作成
ui.createMenu('カルテ用に整理する')
.addItem('実行', 'recognize_text')
.addToUi();
//リボン作成
ui.createMenu('使用方法の説明')
.addItem('使用方法の説明', 'explaining')
.addToUi();
//リボン作成
ui.createMenu('リセット')
.addItem('リセット実行', 'allreset')
.addToUi();
//alert作成
ui.alert("1.使い方\n\n上のリボンから、「ファイル」を選択→「コピーを作成」を押し、コピーを作ってコピーの方を使用してください。このファイルは全員が見れてしまいます。\n\nリボンから「ツール」を選択→「音声入力」を選択→音声入力のマイクを押して、口述してください。\nSafariは音声入力の項目がでてきませんので、Google chromeをお使いください。\n\n入力を終えたら、リボンの「カルテ用に整理する」を選択→「実行」を押す→EXCELがダウンロードされます。実行するにあたって、承認が必要です。\n\n2.口述の仕方\n\n「〇〇(後述する項目)、△△。つぎへ。」のように、項目を宣言してから、話して、「次へ」or「次は」で話に区切りをつけてください。\n例:「体温37度。つぎ。体重7kg。つぎへ。症状、2日前から下痢。今日から嘔吐。つぎへ。」項目のあとに「は」「が」を入れないでください。\nダメな例:「体温は、37度、次へ」\n\n3.項目\n\n担当\n患者番号\n名前\n住所\n電話番号\n動物\n性別\n生年月日\n年齢\nブリード\n主訴\n既往歴\n体重\nボディコンデションスコア\n体温\nCRT(毛細血管再充満時間)\n心拍数\n呼吸数\n聴診\n触診\n症状\n所見\n処置\n問題点\n計画\n検査結果\n投薬\n次回");
}
function explaining(){ //説明用のリボンを押したときの動作
//サイドバー作成
var htmlOutput = HtmlService.createHtmlOutputFromFile('sidebar_create');
DocumentApp.getUi().showSidebar(htmlOutput);
}
function allreset(){
var app = DocumentApp.getUi();
var body = DocumentApp.getActiveDocument().getBody();
var asText = body.asText();
var text = asText.getText();
var text1 = body.editAsText() ;
text1.deleteText(0,text.length-1)
}
function recognize_text(){ //メインの関数
//配列を作成
var ary = [
["1.担当",""],
["2.患者番号",""],
["3.名前",""],
["4.住所",""],
["5.電話番号",""],
["6.動物",""],
["7.性別",""],
["8.生年月日",""],
["9.年齢",""],
["10.ブリード",""],
["11.主訴",""],
["12.既往歴",""],
["13.体重",""],
["14.ボディコンデションスコア",""],
["15.体温",""],
["16.CRT(毛細血管再充満時間)",""],
["17.心拍数",""],
["18.呼吸数",""],
["19.聴診",""],
["20.触診",""],
["21.症状",""],
["22.所見",""],
["23.処置",""],
["24.問題点",""],
["25.計画",""],
["26.検査結果",""],
["27.投薬",""],
["28.次回",""],
["29.記入日",""]
];
//テキスト情報を得る
var app = DocumentApp.getUi();
var body = DocumentApp.getActiveDocument().getBody();
var asText = body.asText();
var text = asText.getText();
/*音声入力の項目を拾い集めるアルゴリズムの例
const kanjyabango_start= text.indexOf("患者番号"); はじめの位置を得る
if(kanjyabango_start!=-1){ はじめの位置が−1は、ないということなので、あった場合のみその後の処理を行う
const kanjyabango_stop= text.indexOf("次",kanjyabango_start); 上の位置から「次」が出てくるまで
const kanjyabango_content=text.slice(kanjyabango_start+4,kanjyabango_stop); 項目の終わりから次までの言葉を取得
ary[0].pop(); 2次元配列の(a,b)のbを消す
ary[0].push(kanjyabango_content); 2次元配列の(a,c)のcを入れる
}
*/
const kanjyabango_start_1= text.indexOf("担当");
if(kanjyabango_start_1!=-1){
const kanjyabango_stop_1= text.indexOf("次",kanjyabango_start_1);
const kanjyabango_content_1=text.slice(kanjyabango_start_1+2,kanjyabango_stop_1);
ary[0].pop();
ary[0].push(kanjyabango_content_1);
}
const kanjyabango_start_2= text.indexOf("患者番号");
if(kanjyabango_start_2!=-1){
const kanjyabango_stop_2= text.indexOf("次",kanjyabango_start_2);
const kanjyabango_content_2=text.slice(kanjyabango_start_2+4,kanjyabango_stop_2);
ary[1].pop();
ary[1].push(kanjyabango_content_2);
}
const kanjyabango_start_3= text.indexOf("名前");
if(kanjyabango_start_3!=-1){
const kanjyabango_stop_3= text.indexOf("次",kanjyabango_start_3);
const kanjyabango_content_3=text.slice(kanjyabango_start_3+2,kanjyabango_stop_3);
ary[2].pop();
ary[2].push(kanjyabango_content_3);
}
const kanjyabango_start_4= text.indexOf("住所");
if(kanjyabango_start_4!=-1){
const kanjyabango_stop_4= text.indexOf("次",kanjyabango_start_4);
const kanjyabango_content_4=text.slice(kanjyabango_start_4+2,kanjyabango_stop_4);
ary[3].pop();
ary[3].push(kanjyabango_content_4);
}
const kanjyabango_start_5= text.indexOf("電話番号");
if(kanjyabango_start_5!=-1){
const kanjyabango_stop_5= text.indexOf("次",kanjyabango_start_5);
const kanjyabango_content_5=text.slice(kanjyabango_start_5+4,kanjyabango_stop_5);
ary[4].pop();
ary[4].push(kanjyabango_content_5);
}
const kanjyabango_start_6= text.indexOf("動物");
if(kanjyabango_start_6!=-1){
const kanjyabango_stop_6= text.indexOf("次",kanjyabango_start_6);
const kanjyabango_content_6=text.slice(kanjyabango_start_6+2,kanjyabango_stop_6);
ary[5].pop();
ary[5].push(kanjyabango_content_6);
}
const kanjyabango_start_7= text.indexOf("性別");
if(kanjyabango_start_7!=-1){
const kanjyabango_stop_7= text.indexOf("次",kanjyabango_start_7);
const kanjyabango_content_7=text.slice(kanjyabango_start_7+2,kanjyabango_stop_7);
ary[6].pop();
ary[6].push(kanjyabango_content_7);
}
const kanjyabango_start_8= text.indexOf("生年月日");
if(kanjyabango_start_8!=-1){
const kanjyabango_stop_8= text.indexOf("次",kanjyabango_start_8);
const kanjyabango_content_8=text.slice(kanjyabango_start_8+4,kanjyabango_stop_8);
ary[7].pop();
ary[7].push(kanjyabango_content_8);
}
const kanjyabango_start_9= text.indexOf("年齢");
if(kanjyabango_start_9!=-1){
const kanjyabango_stop_9= text.indexOf("次",kanjyabango_start_9);
const kanjyabango_content_9=text.slice(kanjyabango_start_9+2,kanjyabango_stop_9);
ary[8].pop();
ary[8].push(kanjyabango_content_9);
}
const kanjyabango_start_10= text.indexOf("ブリード");
if(kanjyabango_start_10!=-1){
const kanjyabango_stop_10= text.indexOf("次",kanjyabango_start_10);
const kanjyabango_content_10=text.slice(kanjyabango_start_10+4,kanjyabango_stop_10);
ary[9].pop();
ary[9].push(kanjyabango_content_10);
}
const kanjyabango_start_11= text.indexOf("主訴");
if(kanjyabango_start_11!=-1){
const kanjyabango_stop_11= text.indexOf("次",kanjyabango_start_11);
const kanjyabango_content_11=text.slice(kanjyabango_start_11+2,kanjyabango_stop_11);
ary[10].pop();
ary[10].push(kanjyabango_content_11);
}
const kanjyabango_start_12= text.indexOf("既往歴");
if(kanjyabango_start_12!=-1){
const kanjyabango_stop_12= text.indexOf("次",kanjyabango_start_12);
const kanjyabango_content_12=text.slice(kanjyabango_start_12+3,kanjyabango_stop_12);
ary[11].pop();
ary[11].push(kanjyabango_content_12);
}
const kanjyabango_start_13= text.indexOf("体重");
if(kanjyabango_start_13!=-1){
const kanjyabango_stop_13= text.indexOf("次",kanjyabango_start_13);
const kanjyabango_content_13=text.slice(kanjyabango_start_13+2,kanjyabango_stop_13);
ary[12].pop();
ary[12].push(kanjyabango_content_13);
}
const kanjyabango_start_14= text.indexOf("ボディコンデションスコア");
if(kanjyabango_start_14!=-1){
const kanjyabango_stop_14= text.indexOf("次",kanjyabango_start_14);
const kanjyabango_content_14=text.slice(kanjyabango_start_14+12,kanjyabango_stop_14);
ary[13].pop();
ary[13].push(kanjyabango_content_14);
}
const kanjyabango_start_15= text.indexOf("体温");
if(kanjyabango_start_15!=-1){
const kanjyabango_stop_15= text.indexOf("次",kanjyabango_start_15);
const kanjyabango_content_15=text.slice(kanjyabango_start_15+2,kanjyabango_stop_15);
ary[14].pop();
ary[14].push(kanjyabango_content_15);
}
const kanjyabango_start_16= text.indexOf("CRT");
if(kanjyabango_start_16!=-1){
const kanjyabango_stop_16= text.indexOf("次",kanjyabango_start_16);
const kanjyabango_content_16=text.slice(kanjyabango_start_16+3,kanjyabango_stop_16);
ary[15].pop();
ary[15].push(kanjyabango_content_16);
}
const kanjyabango_start_17= text.indexOf("心拍数");
if(kanjyabango_start_17!=-1){
const kanjyabango_stop_17= text.indexOf("次",kanjyabango_start_17);
const kanjyabango_content_17=text.slice(kanjyabango_start_17+3,kanjyabango_stop_17);
ary[16].pop();
ary[16].push(kanjyabango_content_17);
}
const kanjyabango_start_18= text.indexOf("呼吸数");
if(kanjyabango_start_18!=-1){
const kanjyabango_stop_18= text.indexOf("次",kanjyabango_start_18);
const kanjyabango_content_18=text.slice(kanjyabango_start_18+3,kanjyabango_stop_18);
ary[17].pop();
ary[17].push(kanjyabango_content_18);
}
const kanjyabango_start_19= text.indexOf("聴診");
if(kanjyabango_start_19!=-1){
const kanjyabango_stop_19= text.indexOf("次",kanjyabango_start_19);
const kanjyabango_content_19=text.slice(kanjyabango_start_19+2,kanjyabango_stop_19);
ary[18].pop();
ary[18].push(kanjyabango_content_19);
}
const kanjyabango_start_20= text.indexOf("触診");
if(kanjyabango_start_20!=-1){
const kanjyabango_stop_20= text.indexOf("次",kanjyabango_start_20);
const kanjyabango_content_20=text.slice(kanjyabango_start_20+2,kanjyabango_stop_20);
ary[19].pop();
ary[19].push(kanjyabango_content_20);
}
const kanjyabango_start_21= text.indexOf("症状");
if(kanjyabango_start_21!=-1){
const kanjyabango_stop_21= text.indexOf("次",kanjyabango_start_21);
const kanjyabango_content_21=text.slice(kanjyabango_start_21+2,kanjyabango_stop_21);
ary[20].pop();
ary[20].push(kanjyabango_content_21);
}
const kanjyabango_start_22= text.indexOf("所見");
if(kanjyabango_start_22!=-1){
const kanjyabango_stop_22= text.indexOf("次",kanjyabango_start_22);
const kanjyabango_content_22=text.slice(kanjyabango_start_22+2,kanjyabango_stop_22);
ary[21].pop();
ary[21].push(kanjyabango_content_22);
}
const kanjyabango_start_23= text.indexOf("処置");
if(kanjyabango_start_23!=-1){
const kanjyabango_stop_23= text.indexOf("次",kanjyabango_start_23);
const kanjyabango_content_23=text.slice(kanjyabango_start_23+2,kanjyabango_stop_23);
ary[22].pop();
ary[22].push(kanjyabango_content_23);
}
const kanjyabango_start_24= text.indexOf("問題点");
if(kanjyabango_start_24!=-1){
const kanjyabango_stop_24= text.indexOf("次",kanjyabango_start_24);
const kanjyabango_content_24=text.slice(kanjyabango_start_24+3,kanjyabango_stop_24);
ary[23].pop();
ary[23].push(kanjyabango_content_24);
}
const kanjyabango_start_25= text.indexOf("計画");
if(kanjyabango_start_25!=-1){
const kanjyabango_stop_25= text.indexOf("次",kanjyabango_start_25);
const kanjyabango_content_25=text.slice(kanjyabango_start_25+2,kanjyabango_stop_25);
ary[24].pop();
ary[24].push(kanjyabango_content_25);
}
const kanjyabango_start_26= text.indexOf("検査結果");
if(kanjyabango_start_26!=-1){
const kanjyabango_stop_26= text.indexOf("次",kanjyabango_start_26);
const kanjyabango_content_26=text.slice(kanjyabango_start_26+4,kanjyabango_stop_26);
ary[25].pop();
ary[25].push(kanjyabango_content_26);
}
const kanjyabango_start_27= text.indexOf("投薬");
if(kanjyabango_start_27!=-1){
const kanjyabango_stop_27= text.indexOf("次",kanjyabango_start_27);
const kanjyabango_content_27=text.slice(kanjyabango_start_27+2,kanjyabango_stop_27);
ary[26].pop();
ary[26].push(kanjyabango_content_27);
}
const kanjyabango_start_28= text.indexOf("次回");
if(kanjyabango_start_28!=-1){
const kanjyabango_stop_28= text.indexOf("次",kanjyabango_start_28);
const kanjyabango_content_28=text.slice(kanjyabango_start_28+2,kanjyabango_stop_28);
ary[27].pop();
ary[27].push(kanjyabango_content_28);
}
var currentTime = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/MM/dd');
ary[28].pop();
ary[28].push(currentTime);
//テーブルに配列を書き出し
table = body.appendTable(ary);
//スプレッドシートに書き出し
//シートの名前は、患者番号がある場合は患者番号優先。優先順位は 患者番号>名前。これらがなければカルテとする
if(ary[1][1]!=""){
var ss = SpreadsheetApp.create(ary[1][1]);
console.log(ss.getName());
}else if(ary[2][1]!=""){
var ss = SpreadsheetApp.create(ary[2][1]);
console.log(ss.getName());
}else{
var ss = SpreadsheetApp.create("カルテ");
console.log(ss.getName());
}
//配列書き込み
var sheet = ss.getSheetByName("シート1");
sheet.getRange(2,1,29,2).setValues(ary);
//調整
sheet.getRange(1,1).setValue(["項目"]); //タイトル
sheet.getRange(1,2).setValue(["内容"]); //タイトル
sheet.setColumnWidth(1, 200); //セルの幅
sheet.setColumnWidth(2, 450); //セルの幅
ss.getRange("B2:B30").setHorizontalAlignment("left"); //左配置
ss.getRange("A1:B1").setHorizontalAlignment("center"); //右配置
ss.getRange("A1:B1").setBackground("#797979"); //色
//エクセルとしてダウンロード
var ID =ss.getId();
var URL = 'https://docs.google.com/spreadsheets/d/'+ID+'/export?format=xlsx';
var options = {
method: "get",
headers: {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
muteHttpExceptions: true
};
var htmlOutput = HtmlService
.createHtmlOutput('<a href="'+URL+'">エクセルをダウンロード</a>')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setWidth(80)
.setHeight(60);
DocumentApp.getUi().showModalDialog(htmlOutput, 'エクセルをダウンロード');
}
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
</head>
<body>
<div class="block">
<h3>1.使い方</h3>
<h5>○上のリボンから、「ファイル」を選択→「コピーを作成」を押し、コピーを作って使用してください。<br>このファイルは全員が見れてしまいます。</h5>
<h5>○リボンから「ツール」を選択→「音声入力」を選択→音声入力のマイクを押して、口述してください。<br>safariは、音声入力の項目がでてきません。Google chromeをお使いください。</h5>
<h5>○入力を終えたら、リボンの「カルテ用に整理する」を選択→「実行」を押す→表が作られ、EXCELがダウンロードされます。ダウンロードがされない場合は、Google drive内のGoogle spreadsheetを見てください。</h5>
<h5>○上記を実行するにあたり、承認が必要です。「このアプリは確認されていません」と出ますが、詳細を押し、安全ではないページに進み認証を進めてください。一度認証したらその後はでてきません。</h5>
<h3>2.口述の仕方</h3>
<h5>「〇〇(後述する項目)、△△。つぎへ。」のように、項目を宣言してから、話して、「次へ」or「次は」で区切って下さい。</h5>
<h5>例:「体温37度。<br>つぎは、体重7kg。つぎへ。<br>症状、2日前から下痢。今日から嘔吐。つぎへ。」<br>項目のあとに「は」「が」を入れないでください。</h5>
<h5>ダメな例:「体温は、37度、次へ」<br>この場合には、「は、37度」が登録されます。</h5>
<h3>3.項目</h3>
<h5>担当<br>患者番号<br>名前<br>住所<br>電話番号<br>動物<br>性別<br>生年月日<br>年齢<br>ブリード<br>主訴<br>既往歴<br>体重<br>ボディコンデションスコア<br>体温
<br>CRT(毛細血管再充満時間)<br>心拍数<br>呼吸数<br>聴診<br>触診<br>症状<br>所見<br>処置<br>問題点<br>計画<br>検査結果<br>投薬<br>次回
</h5>
</div>
</body>
</html>
ダウンロードしたエクセルと電カルをRPAでつないだら、結構使えるのではないか思います!