医療通訳支援Webアプリ(The record system)をHerokuにデプロイしました

はい徹夜してしまいました。こちらです↓ https://englishtranslationrecord.herokuapp.com 触ってみてください。パスワードとか特に設定しておりません。

GitHubにはコードを上げております。

github.com

いや〜、長い道のりでした。昨夜の10時から今朝6時までかかってしまいました。午後10時からプログラミングを始めるとろくなことがありません。こんな事ばかりしていると人生棒に振ってしまいそうです。 さて色んな山が出てきて一つ一つその場しのぎでやり過ごしてなんとかなったけどまた同じ事次やれと言われてできるか少し不安です。新しい知識を得たら反芻するために睡眠を取らなければなりません。

  • データベースをSQLite3からPostgreSQLに換装したけど、SQLite3に比べてflask_sqlalchemyはfetchall()からpandas.DataFrameにテーブル投げるのが下手

  • virtualenvの使い方(anacondaと標準Pythonの両方に入ってて最初うまくいかなかったけど、pip uninstall virtualenvやってanaconda一本にしておいてカレントディレクトリから

$ virtualenv venv
$ . venv/bin/activate

でうまく言った。今回は必要なライブラリだけpip installしたら後はpip freeze > requirements.txtするだけで後は用無しだった。 )

  • Herokuの環境だとmatplotlibが_tkinter使えなくなるので
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

としてやる必要があった。

$ heroku addons:add heroku-postgresql:dev

とやると「お前はselect民じゃないからダメだ」と言われるので

$ heroku addons:add heroku-postgresql:hobby-dev

と飽くまで趣味だと言い張る必要があった。

他にもあったと思うけどこの辺でやめとくか。爪が伸びて12インチMacbookのバタフライキーボードを打つのが辛い。そして本日は診療情報管理士通信教育後期スクーリング3日目。寝れない。

今回お世話になったページ↓

Making a Flask app using a PostgreSQL database and deploying to Heroku

医療通訳支援Webアプリ第2弾 医療通訳対応記録システム

The record system for medical interpretation in a hospital(English only)

医療通訳担当者(英語)のための通訳後記(反省録)保管システム

f:id:HealthcareIT_interpreter:20170514165043p:plain

医療機関のスタッフが医療通訳の現場経験を記録しておき、後学に生かすためのシステムです。

機能1: 対応記録記入(データベースへの書き込み)

以下の情報を記載可能です。(1案件ごとに書き込む方式です)

  • 通訳実施者氏名
  • 通訳開始日時
  • 通訳終了日時
  • 依頼者
  • 依頼部署
  • 外国人患者さんの入外区分
  • 外国人患者さんの国籍
  • サマリー

f:id:HealthcareIT_interpreter:20170514165055p:plain f:id:HealthcareIT_interpreter:20170514165106p:plain

データベースはSQLite3を使用。同時多発的書き込みに対応するため将来的にはPosgresに変更予定。

機能2: 対応記録表の表示(データベースのテーブルを表示)

機能1で書き込んだ内容を確認できます。検索機能あり。そのため例えば「眼科だけの対応記録」を眼科から通訳依頼を受けた直後にささっとおさらいをして現場に入るといった使い方が可能です。その他特定の文言が記載されたレコードだけ絞り込む機能もあり。f:id:HealthcareIT_interpreter:20170514165119p:plainf:id:HealthcareIT_interpreter:20170514165131p:plain

JavaScriptのライブラリVue.jsのグリッドコンポーネント機能を借用させていただきました。(中身については目下勉強中) https://jp.vuejs.org/v2/examples/grid-component.html

機能3: 累積対応時間のビジュアライゼーション(対応時間を月別にスタックバーチャートとして表示)

担当者別に出ます。医療通訳系の資格を取る際現場実習時間の報告とかに使えるかなと。集計したい期間を選択し、Getボタンを押すとpngファイルをダウンロードできます。f:id:HealthcareIT_interpreter:20170514165149p:plainf:id:HealthcareIT_interpreter:20170514165336p:plain

matplotlibを使っています。集計にはpandasを使っています。バーチャートの下にデータテーブルを表示させています。 こちらをほぼパクりました。 https://matplotlib.org/examples/pylab_examples/table_demo.html

Requirements(すみません、バージョンとか細かいことは省略させてください)

  • Python3
  • Flask
  • SQLite3
  • matplotlib
  • pandas
  • numpy
  • Vue.js
  • bootstrap.css
改修予定リスト
  • 機能3で集計期間に対応情報が皆無だった場合Flaskのエラーページに飛んでしまう(functool.reduceのエラー) が、エラー処理を書き加えてポップアップメッセージ「集計期間にデータはありませんでした。」を出すなどエレガントな対応をしたい。

  • 機能2の対応記録表で一番必要になるのはサマリーの列だが、左端で列幅が狭いなどやや見にくいので何とかする。

  • 5人程度の少人数で使用するならばSQLite3で問題ないと思うが、それ以上になるとやはりサーバ型のデータベースが欲しい。近いうちにPosgresバージョンを公開したい。

以上GitHubからの転記でした。

github.com

医療通訳支援Webアプリ第1弾 視力換算システム

以前投稿で視力の伝え方について書きました。 healthcareit-interpreter.hatenablog.com

iOSのアプリでも作ってみるかと構想しておりましたが、友人(こちらのブログの著者

stagira.hatenablog.com

)からWebアプリにした方が楽だという助言をいただきました。 それで出来上がったのがこちらです。

視力換算システム

https://riow1983.github.io/visual_acuity_conversion/

JavaScriptのライブラリvue.jsを使った実装の大方はその友人によるものです。私はレイアウトを少しいじった程度です。じっくり腰を据えてFlask(Pythonのライブラリ)あたりを使って作ろうと思っていたのにこんなに早く出来上がるなんて。JavaScripterに完敗です。

さてさて、それではこれを眼科の先生に見せて感想を聞いてみることにしますか。

病院の医療情報部門の端末払出しアルゴリズム

スマホ5台新規で払出してください。」 「電子カルテ端末が足りないのよ。あと3台出して。」

こんな要望を受けて、さて在庫数は○○台だから、いくつ払出せるな・・・とその度に頭を悩ませることが多いのが病院の医療情報部門のあるあるではないでしょうか?基準が無くて恣意的で人治主義的なさじ加減で無駄に幅を効かせている総務課の事務員ならまだしも、病院のIT化を推進する医療情報部門の医療情報技師ならドクターの診断アルゴリズムも顔負けな”端末払出しアルゴリズム”で払出業務の透明化を図るべきでしょう。

f:id:HealthcareIT_interpreter:20170425223051p:plain

さて、数学を使わなければならない局面というのはこういう所に出てくるんですね。払出優先度指数の算出はともかく、当月最大払出可能数については数式を考える前にフリーハンドで「在庫管理にはこういう曲線が望ましいよな」と線を描いてから、さてこういうカーブの曲線を描く式ってなんだ?とネットで調べて行き当たるという体験をしました。(今回はロジスティック曲線というものらしいです。)兵站業務(logistics)に向いている曲線だからlogistic曲線というのか?と一瞬思ってしまうほどの運命的な出会いでした。 そういった意味合いはないのですが語源としては同源のようですね。 http://www.weblio.jp/content/%E3%83%AD%E3%82%B8%E3%82%B9%E3%83%86%E3%82%A3%E3%83%83%E3%82%AF

外国人観光客が病院に受診した際、病院の医事課が旅行保険会社に確認すべき事項

通訳に呼ばれた外国人患者が観光のため来日した旅行者だった場合、医療通訳担当者にお会計のことで医事課から色々と注文がついて面倒なことになります。 (うちの病院はスキー場に近いことから、スキー観光客が多いようです。) 外国人観光客が旅行保険に加入していた場合、旅行保険会社から病院に電話してもらいます。日本人スタッフがいれば日本語で電話対応可能ですが(その場合は速攻で医事課に転送しましょう)、英語で対応する必要がある可能性もあります。医療通訳担当者が旅行保険会社に確認すべき事項をまとめてみました。

旅行保険会社に確認すべき事項 (Things to confirm with a travel insurance company):

「まず最初に1つ確認させてください。日本語を話せるスタッフはいますか?」(まだ諦めていません)

"First let me verify one thing. Does your company have any personnel who speak Japanese?"

・会計の方法(The method for billing):

「会計に必要なものを教えてください。請求書、診断書、その他にありますか?」

"Let me know things we must have to prepare for billing. We suppose an invoice, a certificate, and others?"

「御社が指定する様式の診断書はありますか?(御社のメールアドレスをメールしてもらえますか?当院のメールアドレスはfoo@hogeです。

"Should we make a private style document you specify? (Please send your e-mail to our address. The address is foo@hoge.)"

「患者の要望で作成した書類は保険適用されますか?」

"Are documents on patient’s demand covered by the insurance?"

「請求書の送付先を教えてください。患者による立替払いになりますか?それとも保険会社による直接支払いでしょうか?」

"To whom should we bill to? Is it an advance payment on behalf of the insurer or a direct payment from the insurer?"

(保険会社による直接支払いの場合)「保険会社様の社名、ご住所、患者様のお名前、案件番号を確認させてください。」

(In case of a direct payment) "Can I verify the company’s name and address, the patient’s name, and case No.?"

「他に準備すべきものはありますか?」

"Is there anything else we should prepare?"

・その他の費用について (About other expenses):

「松葉杖や付き添い寝具など入院中に発生する(した)その他の費用はどのように扱われますでしょうか?それらは患者様負担でしょうか?それとも保険会社様支払いでしょうか?」

"How do you deal with other expenses incurred during admission, namely a crutch, a bedding for rent? Are these things to be paid by the patient himself (herself) or by the the insurer? "

・・・と、こんな感じです。こうやって準備万端にしておけば少しは気が楽になります。

ランダムパスワードの生成

電子カルテの利用者IDの初期パスワードとして、大文字アルファベットと数字で構成された6桁のランダムパスワードを設定する方針が決定されました。え!今更かよ! さて生成コードをPythonで実装してみます。

import string
from random import choice

def get_PW(figures):
    pw = ''.join([choice(string.ascii_uppercase.replace('I','').replace('O','').replace('Z','')+string.digits.replace('0','').replace('1','').replace('2','')) for i in range(figures)])
    if pw.isdigit():#数字だけの場合
        return 'A'+pw[1:]
    elif set(pw).issubset(set(string.ascii_uppercase)):#文字だけの場合
        return "9"+pw[1:]
    else:#数字と文字ともに含有している場合
        return pw

get_PW(6)

数字だけの場合はint(pw)としても良いです。文字だけの場合はどうするか悩みましたが、文字列を集合に変換し、集合「パスワード」が集合「大文字アルファベット」のサブセット(部分集合)であればすなわちパスワードが全て大文字アルファベットで構成されていることになる、という性質を利用することにしました。 あとは、紛らわしい文字と数字の組み合わせ(1とI, 0とO,2とZ)を除外しておいて視認性を高める工夫をしています。 後はこれをVBAで実装し直すだけですね(笑)!

疾病別患者分布をジオプロットする方法

4月になり新採用職員の皆さんが多数入職されました。そんな彼らを前に電子カルテ研修と題してjupyter notebookのスライド機能(Reveal.js)を使ったプレゼンを行ってきました。それについてはまた別途記事にしたいと思いますが、ここではそのプレゼン資料にさらっと挟んでおいた疾病別患者分布の作り方を覚書きしておきます。

ステップ1:ジオコーディング

私の使用言語はPythonです。DWHから抽出してきた疾病別郵便番号一覧をあらかじめ五大疾病だけに絞り込んでおきます。絞り込みが完了したファイルを"五大疾病データ.xlsx"とします。それをPythonのライブラリpandasで読み取り、dataframeにした上で、ジオコーディング(郵便番号を緯度経度に変換)用公開API(http://geoapi.heartrails.com/api/json)に郵便番号を一つずつ投げていく関数を作り実行させます。

import requests
import json
import pandas as pd

df = pd.read_excel("五大疾病データ.xlsx")
df["latitude"] = ""
df["longitude"] = ""

def getlocation(dx):
    url = 'http://geoapi.heartrails.com/api/json'
    payload = {'method':'searchByPostal'}
    for i in dx.index:
        payload['postal'] = dx.loc[i,"郵便番号"]
        res = requests.get(url, params=payload).json()["response"]["location"][0]
        dx.loc[i,"latitude"] = res['y']
        dx.loc[i,"longitude"] = res['x']

getlocation(df)

さらっと書きましたが、実際には一気に処理させるとエラーになってしまいました。全部で9000レコード超だったのですが、500レコードずつ分割してgetlocation()を複数回実行させるという地道な作業が必要でした。(daskというpandasのdataframeを分割並列処理するライブラリが功を奏すか考えもしたのですが実際には試しませんでした。)

ステップ2:ジオプロット

次に、取得した経緯緯度を元に地図上に点描(ジオプロット)していく処理を行います。 前処理として、ステップ1で取得したdataframeを疾病ごとに分割し余分なカラムをそぎ落とします。

mf = df
cancer = mf[mf["疾病分類"] == "がん"]
ci = mf[mf["疾病分類"] == "脳卒中"]
mi = mf[mf["疾病分類"] == "急性心筋梗塞"]
di = mf[mf["疾病分類"] == "糖尿病"]
si = mf[mf["疾病分類"] == "精神疾患"]

ll_cancer = cancer[["latitude","longitude"]]
ll_ci = ci[["latitude","longitude"]]
ll_mi = mi[["latitude","longitude"]]
ll_di = di[["latitude","longitude"]]
ll_si = si[["latitude","longitude"]]

疾病ごとに緯度経度のみのdataframeが取得できました。これで準備万端です。

gmapsでやる場合

googlemapにジオプロットできるgmapsというライブラリを使う場合は以下の通りコードを書いていきます。

import gmaps
gmaps.configure(api_key="YOUR_GOOGLE_MAPS_API")

add_layer_cancer = gmaps.symbol_layer(ll_cancer, fill_color="red", stroke_color="red", scale=2)
add_layer_ci = gmaps.symbol_layer(ll_ci, fill_color="blue", stroke_color="blue", scale=2)
add_layer_mi = gmaps.symbol_layer(ll_mi, fill_color="green", stroke_color="green", scale=2)
add_layer_di = gmaps.symbol_layer(ll_di, fill_color="yellow", stroke_color="yellow", scale=2)
add_layer_si = gmaps.symbol_layer(ll_si, fill_color="purple", stroke_color="purple", scale=2)

m = gmaps.Map()

m.add_layer(add_layer_cancer) 
m.add_layer(add_layer_ci) 
m.add_layer(add_layer_mi) 
m.add_layer(add_layer_di) 
m.add_layer(add_layer_si) 

m

これで疾病ごとに色分けされたドットがプロットされたグーグルマップを表示することができます。(筆者はjupyter notebook上で実行し、inlineにマップが表示されることを確認しました。)

Foliumでやる場合

gmapsと異なりライブラリ内蔵の専用地図にジオプロットできるのが、Foliumというライブラリです。こちらはアウトプットをHTMLファイル保存できます。以下の通りコードを書いていきます。

import folium

map_ = folium.Map(location=[YOUR_LAT, YOUR_LON],
           tiles='Stamen Terrain',
           zoom_start=9)

for i in ll_cancer.index:
    x = ll_cancer.loc[i,"latitude"]
    y = ll_cancer.loc[i,"longitude"]
    folium.CircleMarker([x,y],
                 radius=4,
                 popup="がん",
                 color="#011efe",
                 fill_color="#011efe"
                 ).add_to(map_)

for i in ll_ci.index:
    x = ll_ci.loc[i,"latitude"]
    y = ll_ci.loc[i,"longitude"]
    folium.CircleMarker([x,y],
                 radius=4,
                 popup="脳卒中",
                 color="#fe0000",
                 fill_color="#fe0000"
                 ).add_to(map_)

for i in ll_mi.index:
    x = ll_mi.loc[i,"latitude"]
    y = ll_mi.loc[i,"longitude"]
    folium.CircleMarker([x,y],
                 radius=4,
                 popup="急性心筋梗塞",
                 color="#0bff01",
                 fill_color="#0bff01"
                 ).add_to(map_)

for i in ll_di.index:
    x = ll_di.loc[i,"latitude"]
    y = ll_di.loc[i,"longitude"]
    folium.CircleMarker([x,y],
                 radius=4,
                 popup="糖尿病",
                 color="#fdfe02",
                 fill_color="#fdfe02"
                 ).add_to(map_)

for i in ll_si.index:
    x = ll_si.loc[i,"latitude"]
    y = ll_si.loc[i,"longitude"]
    folium.CircleMarker([x,y],
                 radius=4,
                 popup="精神疾患",
                 color="#fe00f6",
                 fill_color="#fe00f6"
                 ).add_to(map_)

map_.save(outfile="fivemap_circle.html")

ちなみに保存したHTMLファイルをjupyter notebook上に表示する場合は

from IPython.display import HTML
HTML('<iframe src=./fivemap_circle.html width=800 height=500></iframe>')

とすればOKです。

考察:患者分布図で個人が特定されることはないか?

経度緯度を取得すると言っても、郵便番号が素ですから個人宅がピンポイントに指し示されることはありません。また病名についても五大疾病という大きな分類で表していますので、マイナーかつ詳細病名が地図上に表示されて推定されてしまうこともまずありません。(ただしアウトプットをネット公開することは控えたほうがいいかも知れません。あくまで院内資料ということで。)

患者分布図を作成する有料サービスも存在しているようですが今やオープンソースのライブラリで手軽に実行できる時代になっています。病院のデータ分析担当者は自らのスキルを院内にアピールし、病院長が無駄なコンサルタント料を支払うことの無いよう監視して行かなければなりません。