2018年8月30日木曜日

①患者基本情報項目3

前回カラダパレッドに「検査情報を入力したがグラフが表示されなかった」としたが、私の予想では、検査情報を入力することによって、検査データの値の推移が見れるような画面が表示されるのではないかと考えた。

図1の(今回で言えば体重)をタップすることによって図2の画面が表示されるのだが、この画面には特に表示されているものもなかった。

図1 からだパレッドのトップ画面

図2 検査データのグラフ画面
次回、改めて検査データの入力を2日以上行いグラフが表示できないか検証してみたい。

もう一点今回からcacooでのデザイン作成とワイヤーフレームの作成を開始した
テンプレートのフレームからあったものを参考にして患者氏名、性別、生年月日、住所を打ち込んだが、下にスクロールできるようするにはどのようにしたら良いのか分からなかったので、cacooというアプリケーションを理解していかなければならないと感じた。

図3 患者基本情報のデザインについて
まだまだ使い方が分からない点も多いが調べながらより良いものにしていきたい。

またコメントにありました、excelに調べたPHRの情報をまとめるという事ですが、とりあえず今までに調べたものの情報を次回までにまとめようと思います。

【コメント】

図2を見ると、明らかにこれは体重と体脂肪率の時間(日)変化のグラフ表示画面です。ですから、毎日体重と体脂肪率を入力するとグラフが表示されると思いますので入力してみてください。
また、体重だけでなく、他の検査結果も入力してみてください。検査項目は定期健診でよく検査される項目(血圧、肝機能検査、血中脂質検査、血糖検査、尿検査、尿たんぱくなど)を、値は基準値をネットで調べ適当に入力してみてください。 

Cacooでスクロールの仕方がわからないということですが、Cacooは画面をデザインするだけのアプリケーションなので、スクロールなどの動きは表現できないのではと思います。

②患者基本情報データの取得3

前回、ブログのコメントに書かれている通り、ORCAのWebAPIを利用して患者基本情報をJSON形式で取得する作業を行っていく。

http://172.16.108.251:8000/api01rv2/patientgetv2?id=00001&format=json
このURLを書き込んでみ、IDを「ormaster」、パスワードをいつも使っているやつを打ち込んだら、図1のようなエラーが表示された。
図1:上記のURLを書き込んだ時の画面
調べても、対処法が分からなかった。あと、先生の画面には表示されているはずの画面がここでは表示されなかったのが謎だった。
研究するペースを上げていかないと、間に合わないと感じざるをえない、状況になった。
先生のような画面が表示されるよう、調べていきたい。

また、ORCAから患者基本情報を取得するにあたり、いちいち認証画面が表示されるので、スマホアプリのプログラムで認証情報をORCAへ渡したい。
あるブログを参考にしてみると、この記事が参考になると考えた。この記事によると、ダミーのwebサーバと通信していたところを、プログラムを通じて、ORCAと通信して、本物のデータを引っ張てくるようにしていることが分かった。なので、このプログラムを参考にしていったら、認証情報をORCAに渡せれるのではないかと考えている。

だが、図1のようなエラーがある以上、実験することもできないので、研究するペースを上げて、1つ1つの問題を解決して、早め早めに終わらせられるようにしていきたい。

【コメント】

図1のエラーが出た原因がわかりました。ORCAの動作がおかしくなっていたようです。原因はわかりません。ORCAクライアントを立ち上げると画面は出るものの、ボタンが灰色になって選択できませんでした。
以前も似たようなことがあり、その時はORCAを再起動することにより解消しました。そこで、以下のコマンドを入力してORCAを再起動しました。
 sudo service jma-receipt restart
すると、今度は無事ORCAクライアントからORCAサーバへ接続できました。
その後、ブラウザから 下記URLを入力しました。
http://172.16.108.251:8000/api01rv2/patientgetv2?id=00001&format=json
すると、パスワード認証画面が現れ、IDとパスワードを入力すると、2018年8月9日木曜日のブログ「②患者基本情報データの取得2 」の図1の画面が表示されました。
ということで、図1のエラーの原因はORCAサーバにあったわけです。以後、こういった現象に出くわしたら、まず、ORCAサーバはちゃんと動いているかどうかをORCAクライアントを起動して確かめてください。そしてORCAサーバの調子がおかしいようだったらORCAサーバを再起動してみてください。

次に認証画面ですが、ご指摘の通り、「ゼミの轍」の2016年10月1日土曜日の記事「いよいよORCAとの通信です。」に掲載されたプログラムが参考になります。
そこに掲載されたプログラム(例えば「患者基本情報」)を見てください。4行目以降は、Ajaxを使ってORCAサーバにWebAPIを投げているところです。6行目~8行目はHTTPリクエストに Authorization ヘッダを追加しているところです。 ヘッダの値は
Basic ユーザID:パスワード
において「ユーザID:パスワード」の部分をBase64エンコードしたものです(Javascript関数window.btoaはBase64エンコード関数)。
このようにHTTPリクエストのヘッダ中に認証情報を設定することにより、いちいち認証画面が表示されることを回避できます。

なお、私たちが作成しているスマホアプリはAjaxをそのまま使っているのではなくAngularJSで$httpサービスを利用してWebAPIを投げていますから、その流儀に従ってHTTPヘッダを組み立ててやる必要があります。HTTPリクエスト時のヘッダー情報の設定にはこのサイトが参考になります。試してみてください。





④検査結果表示について3

今回も引き続き、グループ毎のカテゴライズを試みた
前回のコメントを参考に、下記のコードをCountries.get()の内部に記述した。
var group = null;
that.group = [];
 if(element.itemName =[0]){
  "groupName" = element.groupName,
  "items" = []
 } else if (element.groupName = [n+1]){
  that.group.push(group)
  that.group =[];
 } else if (group = !null ){
  that.group.push(group)
 }
items.push({
  itemName:element.itemName,
  value:Math.round(element.value *100) /100 ,
  unit:element.unit,
  bgColor:bgColor
 })
まず、1行目でグループを定義し、2行目でそのグループを空配列とした。
次にif文を使い、3行目では検査項目名が最初のものであれば、groupNameにグループ名、検査データを流し込むitemsは空配列とする文章を定義する。
6行目では検査グループ名が変更された場合の条件文を記述したいのだが、どの様な記述をすれば良いのか分からなかった為、今回は適当な数値にした。条件文の内容としては、グループ名が変更された場合、グループ配列にグループを挿入し、かつグループ配列を空配列にするといった処理である。
9行目は同じく条件式で、groupが空でなければ、6行目と同様の処理を行うといったものである。
12行目は検査データを格納したitemsに検査項目名、検査値、値、背景色を追加する処理を行うといったものである。
これらのコードを記述した後確認を行ったのだが、「Error:Can't find variable: element
と表示され、今回も思うようにプログラムの記述を行う事が出来なかった。

【コメント】

まず、エラーメッセージ「Error:Can't find variable: element」 ですが、これは「変数elementがみつかりません」という意味です。確かに、3行目でelementが初めて出てきますが、どこにも定義されていません。
検査結果は、変数 countries の list 属性の中に検査日ごとに入っていたことを思い出してください(以前のブログ記事「5.検査結果」の「#2 GET /dolphin/openSource/lab/module/00001,0,6 HTTP/1.1」)。したがって、直近の検査結果は変数 countries.list[0] の中にあり、その items 属性(配列)の配列要素が個々の検査項目結果(すなわち element)です。上記のプログラムにはこのitems配列の個々の要素elementに対するループ(繰り返し)がないのです。

したがって、プログラムは次のようになります。
// 直近の検査日の検査結果を変数 list に格納
var list = countries.list[0];
// 検査グループ配列を空配列に初期化
that.group = [];
// 個々の検査グループを格納するための一変数を null で初期化
var group = null;
// 検査結果項目配列 list.items の個々の要素 element に対してループ処理
list.items.forEach(function(element){
  // elementが一番最初の検査結果項目の場合、groupを初期化する
  if(group == null) {
    group = {
      'groupName': element.groupName,
      'items': []
    };
  // 検査グループが変わったとき、groupを検査グループ配列に追加してからgroupを初期化する
  } else if(group.groupName != element.groupName){
    that.group.push(group);
    group = {
      'groupName': element.groupName,
      'items': []
    };
  }
  // 基準値を求めて normalValue 変数に格納する
  var normalValue = getNormalValueRange(element.normalValue);
  // 基準値と検査結果を比較して背景色を決める
  var backgroundColor = (element.value < normalValue.lower || element.value > normalValue.upper ? 'outliers' : 'normal');
  // グループに検査結果を追加する
  group.items.push(
    {
      'itemName': element.itemName,
      'value': round(element.value),  //  四捨五入
      'unit': element.unit,
      'backgroundColor': backgroundColor,
    }
  );
});
// 最後のgroupを検査グループ配列に追加する
if(group != null) {
  that.group.push(group);
}
ここで、24行目に出てくる getNormalValueRange 関数および31行目に出てくる round 関数は、それぞれ自作の基準値取得関数、四捨五入関数で、次のように定義しています。
/*
      基準値の下限と上限を求める
*/
function getNormalValueRange(normalValue) {
  var normalValue = normalValue.split(/-/);
  var lower = parseFloat(normalValue[0]);
  var upper = parseFloat(normalValue[1]);
  return {
    lower: lower,
    upper: upper,
  };  
}

/*
  四捨五入(小数第3位)
*/
function round(value) {
  return Math.round(value * 100) / 100;
}
これらの関数は、プログラムを見やすくするために定義したものです。必ずしも関数にしなければならないというものではありません。関数にしない場合はプログラム本体にそのまま書けばよいでしょう。



2018年8月9日木曜日

②患者基本情報データの取得2

前回、WireSharkを使って、スマホとORCAまたは、OpenDolphinの通信内容を分析しようとしていたが、スマホとサーバの通信内容は、たとえ同じネットワーク内といえども他の通信機器でキャプチャすることはできないことが判明した。

なので、2年前のあるブログの中の、特に、ブログ1ブログ2を参考にして、これをAngularJS版に改編することで、スマホに表示する患者基本情報データを取得できるだろうと考えた。

.function getPatientData(patientId) {
        var url = 'http://hustler***.softether.net/~*******/api01rv2/
patientgetv2.xml';
        patientData = null;
        $.ajax({
            type: 'GET',
            url: url,
            async: false,
            cache: false,
            dataType: "xml"
        }).done(function(xml, status, error){
            if (status == 'success') {
                patientData = $(xml).find('Patient_Information');
            } else {
                alert('文書データ取得失敗');
            }
        }).fail(function(xhr, status, error){
            var message = "xhr.status = " + xhr.status + ", xhr.statusText = " + xhr.statusText + ", status = " + status + ", error = " + error;
            alert('サーバから応答がありません: ' + message);
        });
    }




2年前のあるブログを見る限り、患者情報をGETメソッドを使って、取得していることが分かった。GETメソッドを使うにあたって、アプリに渡すためのURLを指定しなければならない。ここでは、2,3行目に、URLが指定されていた。このURLはどんなのか、見てみると、このような画面が出てきた。
ORCAの患者基本情報がAPIとして処理されて、レスポンスされていた。
そこで、ORCAのサイトを見てみると、ここを参考にしたら、APIとして処理され、患者基本情報が返却されそうだと分かった。
また、ver5以降から引数のformatによってJson形式でも返却できることが可能になっていので、それを使う。

なので、ORCAのサイトのテスト方法にのっているURLをもとに、localhostをIPアドレス「172.16.108.251」に変更し、id=の後を、患者番号である1または0001を指定したURLにアクセスしてみた。
図1:URLにアクセスした結果


アクセスした結果、図1のような画面が出てきて、エラーが発生した。APIとして処理された患者基本情報が返却されないと、作業が進まないので、エラーがでた原因を追究し、遅れを取り戻していきたい。

今回は2年前のあるブログの患者基本情報の取得方法を理解することに時間がかかったり、図1のようなエラーが出てしまったりと、作業が思うように進まなかった。次回は、エラーの原因を解決し、APIとして処理された患者基本情報を入手し、URLを得た状態で、2年前のあるブログのようなプログラムを参考に、AngularJS版に改変し、患者基本情報データの取得が行えるのか検証していきたい。




【コメント】

日レセAPIを使ってORCAから患者基本情報を取得するには次のようなURLを書きます。
http://172.16.108.251:8000/api01rv2/patientgetv2?id=00001&format=json
これは、患者ID=00001の患者の基本情報をJSON形式で返却することをリクエストするURLです。ちなみに、この例が示すようにORCAのWebAPIはRESTfulではありません。通常のクエリストリングで患者IDなどのパラメタを渡しています。

 ブラウザに上記URLを打ち込むと、次のような認証要求画面が表示されます。
図2 ORCAの認証画面(Firefoxの場合)
 ユーザ名にormaster、パスワードに***を入力すると次のような画面が表示されます。
図3 ORCAからのレスポンス
 これは、ORCAからのレスポンスです。JSON形式を要求しているため「XMLパースエラー」と表示されます。ブラウザはJSONを解釈できないからです。ブラウザの機能を使ってソースを表示します。
図4 レスポンスのソース表示
 ソースをコピーしてJSON Pretty Printにかけます。
図5 レスポンスをJSON Pretty Printにかけたもの
こうして、次のようなJSONデータが得られます。
{
  "patientinfores": {
    "Information_Date": "2018-08-10",
    "Information_Time": "07:08:48",
    "Api_Result": "00",
    "Api_Result_Message": "処理終了",
    "Reskey": "Patient Info",
    "Patient_Information": {
      "Patient_ID": "00001",
      "WholeName": "山下 浩介",
      "WholeName_inKana": "ヤマシタ コウスケ",
      "BirthDate": "1970-12-06",
      "Sex": "1",
      "HouseHolder_WholeName": "山下 浩介",
      "Home_Address_Information": {
        "Address_ZipCode": "*******",
        "WholeAddress1": "岡山県**市**",
        "WholeAddress2": "*丁目*?*",
        "PhoneNumber1": "***-**-****"
      },
      "Contraindication1": "********",
      "Allergy1": "********",
      "Infection1": "********",
      "TestPatient_Flag": "0",
      "NickName": "カルテ例1 P56",
      "Reduction_Reason": "00",
      "Reduction_Reason_Name": "該当なし",
      "Discount": "00",
      "Discount_Name": "該当なし",
      "Condition1": "00",
      "Condition1_Name": "該当なし",
      "Condition2": "00",
      "Condition2_Name": "該当なし",
      "Condition3": "00",
      "Condition3_Name": "該当なし",
      "Ic_Code": "01",
      "Ic_Code_Name": "現金",
      "Community_Cid_Agree": "False",
      "FirstVisit_Date": "2017-12-18",
      "LastVisit_Date": "2018-01-17",
      "HealthInsurance_Information": [
        {
          "Insurance_Combination_Number": "0001",
          "InsuranceCombination_Rate_Admission": "0.30",
          "InsuranceCombination_Rate_Outpatient": "0.30",
          "Insurance_Nondisplay": "N",
          "InsuranceProvider_Class": "009",
          "InsuranceProvider_Number": "01400019",
          "InsuranceProvider_WholeName": "協会",
          "HealthInsuredPerson_Symbol": "41061242",
          "HealthInsuredPerson_Number": "9635",
          "RelationToInsuredPerson": "1",
          "HealthInsuredPerson_WholeName": "山下 浩介",
          "Certificate_StartDate": "2017-10-25",
          "Certificate_ExpiredDate": "9999-12-31",
          "Insurance_CheckDate": "2017-10-25"
        },
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {},
        {}
      ]
    }
  }
}
これを下記のORCAの患者登録画面と比較してみます。
図6 同じ患者番号のORCAの患者登録画面
 これらから、禁忌がJSONのContraindication1に、アレルギーがAllergy1に、感染症がInfection1に対応していることがわかります。コメントは入力されていませんが、ORCAのドキュメントからComment1であることがわかります。さらに、これらはいずれも2つずつあり、それがContraindication2, Allergy2, Infection2, Comment2であることもわかります。これらについては、実際にORCAにデータを入力して確認するとよいでしょう。

さて、 これでORCAのWebAPIを利用して患者基本情報をJSON形式で取得できることがわかりました。しかし、図2の認証はどうすればよいのでしょう?
まさか、患者基本情報を取得する都度、認証画面が現れて、ユーザ名とパスワードを入力するわけにもいきません。
なんとか、OpenDolphinのように、スマホアプリのプログラムで認証情報をORCAへ渡したいものです。
それについてはORCAのドキュメントを調べるか、2年前のブログから探し出すか、あるいはネット上の情報から見つけ出す以外にありません。挑戦してみましょう。

①患者基本情報項目2

今回は、前回に引き続き、私たちが作成している、スマホアプリの患者基本情報について参考となる資料を見つけるためPHRのスマホを使ったアプリケーションについて情報収集を行った。今回参考にしたアプリは下記の2つのアプリについて調べた。

1.からだパレッド
2.マイカルテ(weldy)

からだパレッドについて知るために、アプリをインストールし登録をおこなった。

図1 からだパレッドのスタート画面

図2 からだパレッドのトップ画面
以前取り上げた、MeDaKaのように基本情報としては登録した体重などは確認できたがそれ以外の項目は表示されなかったことから検査情報を入力することで、表示されるのではないかと思い検査データを図3、図4の通りデータを入力したがその後表示されなかった。

図3 からだパレッド 検査データの入力(1)
図4 からだパレッド 検査データの入力(2)



二つ目のマイカルテ(weldy)についても同様にホームページだけでなく実際にアプリをインストールし、登録と操作を行った。
無事登録し、アプリを起動したがMeDaKaのような簡潔な基本情報をまとめている画面は見当たらなかった。
図5 マイカルテのトップメニュー
検査データの入力は時間が足りず出来ずに終わったので、次回はダミー検査データを登録した上でこのアプリの特性を理解していきたい。図6は検査データの入力画面である。
図6 マイカルテの検査データの入力
また、目標としてデザインの作成も行っていきたい。


【コメント】

「からだパレッド」について
以前取り上げた、MeDaKaのように基本情報としては登録した体重などは確認できたがそれ以外の項目は表示されなかったことから検査情報を入力することで、表示されるのではないかと思い検査データを図3、図4の通りデータを入力したがその後表示されなかった。
と書いてありますが、何が、どこに「その後表示されなかった」のでしょうか?また、どのような表示を期待していたのでしょうか?

また、それぞれのアプリにおいて「基本情報を簡潔にまとめる画面」がないにしても、どのような基本情報が表示されているか、それがどこに表示されているか等の情報をエクセルに整理してはどうでしょうか。
その際、アプリ名、作成者、対応OS(iPad/Android)も合わせて記録に残しておくと、卒論の先行研究に使えます。

④検査結果表示について2

今回は前回のコメントで指摘された箇所を踏まえて階層化を試みたのだが、実現することが出来なかった。
まず、最初のコードに関してだが、これをcountries.listのthat.list.pushに記入されている書式に条件式として記入すればよいのでは
ないかと考え、まず、varを用いてgroupとbgColorの定義を行った後に、

that.list.push(
              {
                itemName:element.itemName,
                value:Math.round(element.value *100) /100 ,
                unit:element.unit,
                bgColor:bgColor
              }
            );
このコードを以下の様に書き換えた。
that.list.push(
              {group : [
                {
                groupName:"生化学的検査",
                   items: [
                itemName=element.itemName,
                value=Math.round(element.value *100) /100 ,
                unit=element.unit,
                bgColor=bgColor
                  ]}
              ]}
考え方としては、コメントの通りにリスト配列を挿入する際に、検査グループを定義し、
そこへ検査グループに属する検査項目を格納するといった階層構造の作成を試みたのだが、
この段階で表示することが出来れば、血液学的検査等の検査を同じように追加していく予定であったのだが、下記図1の様に何も表示されなくなってしまった。

図1 コードを書き換えた後のエラー
考えられる問題点として、コードの書き方を間違えているのではないかという事や、グループ分けを行っているといっても条件式等を指定しないため、結局の所全てのデータをそのまま流し込んでいるのではないか。
そもそもこのコードはcountries.list内に記載するべき記述なのであろうか。htmlのコメントに示されている「group.groupName」という記述から見ても、独立のコードとして定義するべきなのだろうか。
また、独立したコードとして定義するのであれば、直でデータを入力するわけにはいかないので、どの様にしてデータを取得すればよいのであろうか等様々な問題に直面してしまい、成果を上げることが出来なかった。

次回は今回直面した問題の解決と、停滞した分の遅れを取り戻せるように円滑に作業を行えるようにしたい。

【コメント】

前回のコメントに書いた検査データの階層構造を格納するgroupオブジェクトは、あくまでも加工後のイメージであって、あれをそのままコーディングするわけではありません。加工後のgroupオブジェクトが意図したとおりになっているかどうかはJSON.stringifyしたものをalertして確認するとよいでしょう。

さて、どのようにしてgroupオブジェクトを作成するか、疑似コードで示しましょう。
CountriesControllerのgroup属性を空配列で初期化する。
group要素を定義してnullで初期化する。
list.itemsに格納された個々の検査項目に対して、以下の処理を繰り返す。
  もし、先頭の検査項目なら
    group要素を、2つの属性「グループ名称」と「検査項目を格納する空の配列」を持つ連想配列で初期化する。
  そうでなくて、検査項目のグループ名称が変わったら
    group要素をCountriesControllerのgroup属性に追加する
    group要素を、2つの属性「グループ名称」と「検査項目を格納する空の配列」を持つ連想配列で初期化する。
  group要素の検査項目配列に検査項目情報(「検査項目名」「検査結果」「単位」「背景色」)を追加する
もしgroup要素がnullでなければgroup要素をCountriesControllerのgroup属性に追加する
この疑似コードで1行目のCountriesControllerのgroup属性というのは、that.listに代わるものです。例えば、that.groupといった変数名を付けておくのが良いでしょう。

2行目のgroup要素は、that.groupに挿入する要素を表すローカル変数です。list.itemsに入っている検査データをもとに作っていきます。 例えば
var group = null;
などと定義すればよいでしょう。ここで、nullというのは、オブジェクトの中身が空であることを示す特別な値です。

次いで、3行目から9行目まで、検査データの個々の検査項目について、検査項目がなくなるまで繰り返し処理を行います。

まず、初めての検査項目の場合、最初の検査グループ要素groupを作成します。それが5行目の処理です。「グループ名称」のキーをgroupName、「検査項目を格納する配列」のキーをitemsとした場合、次のようなコードになるでしょう。
group = {
  'groupName': element.groupName,
  'items': []
}
ここで、elementはlist.itemsに格納された個々の検査項目を表す変数名です。

次に 、検査グループ名称が変わった場合(6行目)、groupをthat.groupに追加(7行目)したのちに、先ほどと同様に検査グループ要素groupを作成(初期化)します。

分かりにくかったかもしれませんが、以上が階層構造のうちgroup階層を作成する部分です。検査項目ループ処理で、「検査グループ名称が変わったとき」というイベントをトラップして、新しい検査グループ要素を作成しています。

9行目は検査項目の設定部分です。これは従来のプログラムと同じことをやっています。ただし、検査項目を追加する配列はthat.listではなくgroup.itemsになっている点が違います。group.itemsはグループ要素の下層にある要素なので、上位層が検査グループ、下位層が検査項目という階層構造ができるわけです。

最後の10行目は、最後の検査グループ要素groupをthat.groupに追加するコードです。検査データの全ての検査項目の処理を終えたあと(3行目~9行目のループを抜けたあと)、検査グループ要素groupにはデータが残っているかもしれません(いえ、検査データが1つでもあれば必ず残っているはずです)。それを拾い上げているのがこのコードです。

このように、ある項目(今の場合は検査グループ名)をキー項目として、それが変わると何かある処理を行う(この場合は、検査グループ要素を配列の追加して、検査グループ要素を初期化する)ことを「コントロールブレイク処理」と言います。様々なアルゴリズムがありますが、ここで紹介したアルゴリズムは最もポピュラーなものです。

2018年8月2日木曜日

②患者基本情報データの取得1

患者基本情報データの取得では、スマホとOpenDolphinまたはORCAサーバの通信内容を分析して、スマホに表示する患者基本情報データを取得していく。


図1:ORCAの患者さんの基本情報を登録する画面


まずは、図1のように、患者さんの基本情報である住所、電話番号、禁忌、アレルギー、感染症の情報を適当にORCAで記載していった。


次に5月24日の研究で行ったようにWiresharkを用いて、通信内容の分析を行っていく。
どこをどう動かしたら、うまく通信内容の分析ができ、スマホに表示する患者基本情報データを取得できるのか、よく分からないので、まずは、試しとして、スマホの中にある、Monacaのアプリを図2の「OpenDolphinクライアント」を押して、図3の画面にした時に流れる通信内容をWiresharkを使って、分析してみることにした。
図2:Monacaのホーム画面




図3:「OpenDolphinクライアント」を押した後の画面


図4:図2から図3にした際のWiresharkの画面
取得したパケットは上記の図4の様なものである。ここからprtocolが「HTTP」、Infoが「GET~」から始まるパケットを探すと1つしかなかった。それに対して、[追跡]>[HTTPストリーム]でパケットの再構築を行った。するとポップアップが表示されるので、show and save data asの項目を「UTF-8形式」に変更し、save as...よりパケットの保存を行う。尚、この際拡張子は「.txt」とする。


そのパケットの結果が以下である(以下はJSON Pretty Printを用い、すでにデータを整形し置き換えている)。


#1 GET /dolphin/openSource/pvt2/pvtList HTTP/1.1

.GET /dolphin/openSource/pvt2/pvtList HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate
password: 6f8e646f95af8e79096477e877245664
userName: 1.3.6.1.4.1.9414.70.1:w3415016
Host: 172.16.108.251:8080
Connection: Keep-Alive

HTTP/1.1 200 OK
Connection: keep-alive
X-Powered-By: Undertow/1
Server: WildFly/9
Content-Type: application/json
Content-Length: 2414
Date: Thu, 02 Aug 2018 05:59:17 GMT

{
  "list": [
    {
      "memo": null,
      "facilityId": "1.3.6.1.4.1.9414.70.1",
      "patientModel": {
        "romanName": null,
        "nationality": null,
        "nationalityDesc": null,
        "maritalStatus": null,
        "jpegPhoto": null,
        "mobilePhone": "080-1234-5678",
        "relations": null,
        "reserve1": null,
        "reserve2": null,
        "reserve3": null,
        "reserve4": null,
        "reserve5": null,
        "reserve6": null,
        "memo": null,
        "email": "taro@google.co.jp",
        "facilityId": "1.3.6.1.4.1.9414.70.1",
        "telephone": null,
        "patientId": "00011",
        "healthInsurances": [
          {
            "beanBytes": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPGphdmEgdmVyc2lvbj0iMS44LjBfMTcxIiBjbGFzcz0iamF2YS5iZWFucy5YTUxEZWNvZGVyIj4KIDxvYmplY3QgY2xhc3M9Im9wZW4uZG9scGhpbi5pbmZvbW9kZWwuUFZUSGVhbHRoSW5zdXJhbmNlTW9kZWwiPgogIDx2b2lkIHByb3BlcnR5PSJHVUlEIj4KICAgPHN0cmluZz5jODhjNjZjZC0zNWE2LTQ5YTUtOGJjNS02MTgzNWQ3MmNkYzA8L3N0cmluZz4KICA8L3ZvaWQ+CiAgPHZvaWQgcHJvcGVydHk9ImNsaWVudEdyb3VwIj4KICAgPHN0cmluZz7vvJTvvJHvvJDvvJbvvJHvvJLvvJTvvJI8L3N0cmluZz4KICA8L3ZvaWQ+CiAgPHZvaWQgcHJvcGVydHk9ImNsaWVudE51bWJlciI+CiAgIDxzdHJpbmc+77yZ77yW77yT77yVPC9zdHJpbmc+CiAgPC92b2lkPgogIDx2b2lkIHByb3BlcnR5PSJleHBpcmVkRGF0ZSI+CiAgIDxzdHJpbmc+OTk5OS0xMi0zMTwvc3RyaW5nPgogIDwvdm9pZD4KICA8dm9pZCBwcm9wZXJ0eT0iZmFtaWx5Q2xhc3MiPgogICA8c3RyaW5nPnRydWU8L3N0cmluZz4KICA8L3ZvaWQ+CiAgPHZvaWQgcHJvcGVydHk9Imluc3VyYW5jZUNsYXNzIj4KICAgPHN0cmluZz7ljZTkvJrjgZHjgpPjgb08L3N0cmluZz4KICA8L3ZvaWQ+CiAgPHZvaWQgcHJvcGVydHk9Imluc3VyYW5jZUNsYXNzQ29kZSI+CiAgIDxzdHJpbmc+MDk8L3N0cmluZz4KICA8L3ZvaWQ+CiAgPHZvaWQgcHJvcGVydHk9Imluc3VyYW5jZUNsYXNzQ29kZVN5cyI+CiAgIDxzdHJpbmc+TU1MMDAzMTwvc3RyaW5nPgogIDwvdm9pZD4KICA8dm9pZCBwcm9wZXJ0eT0iaW5zdXJhbmNlTnVtYmVyIj4KICAgPHN0cmluZz4wMTQwMDAxOTwvc3RyaW5nPgogIDwvdm9pZD4KICA8dm9pZCBwcm9wZXJ0eT0icGF5T3V0UmF0aW8iPgogICA8c3RyaW5nPjAuMzA8L3N0cmluZz4KICA8L3ZvaWQ+CiAgPHZvaWQgcHJvcGVydHk9InN0YXJ0RGF0ZSI+CiAgIDxzdHJpbmc+MjAxNy0xMS0yNzwvc3RyaW5nPgogIDwvdm9pZD4KIDwvb2JqZWN0Pgo8L2phdmE+Cg==",
            "id": 2398
          }
        ],
        "kanaName": "テストタロウ ",
        "gender": "male",
        "genderDesc": "M",
        "birthday": "1996-05-16",
        "simpleAddressModel": null,
        "appMemo": "診察1",
        "pvtDate": null,
        "ownerUUID": null,
        "fullName": "テスト太郎",
        "id": 6
      },
      "deptName": "内科",
      "deptCode": "01",
      "doctorName": "○○ ○○",
      "doctorId": "10002",
      "jmariNumber": "JPN000000000000",
      "pvtDate": "2018-07-23T13:31:08",
      "department": "内科,01,○○ ○○,10002,JPN000000000000,",
      "firstInsurance": "09 協会けんぽ",
      "insuranceUid": "c88c66cd-35a6-49a5-8bc5-61835d72cdc0",
      "lastDocDate": null,
      "appointment": null,
      "id": 2399,
      "state": 0
    }
  ]
}

前回、通信内容を分析した際と同じようなパケットキャプチャが今回も取得された。
テストで作成した、テスト太郎の基本情報やテスト太郎さんを担当する医師の名前や科、「beanBytes」を解析すると、保険情報だったので、テスト太郎さんの保険情報がこの通信で流れていることが分かった。


今回は、少し、見当違いのことをしているかもしれないと感じたので、どこをどうする通信内容を分析したら、スマホに表示する患者基本情報データを取得することができるのか、これから、しっかりと調べていきたい。


【コメント】

この試みはMonacaで開発したスマホアプリとOpenDolphinまたはORCAサーバの通信をPCでキャプチャしようとしてますが、それは無理です。なぜなら、スマホとサーバの通信内容は、たとえ同じネットワーク内といえども他の通信機器(この場合はPC)でキャプチャすることはできないからです。理由はスイッチングハブが宛先ポート番号を見て、関係のないポートへはデータを流さないからです。
ですから、この実験で図4に示すようなパケットがWiresharkによってキャプチャされるはずはあり得ません。考えられる原因は、(Wiresharkでパケットキャプチャしている)このPCでもOpenDolphinクライアントを立ち上げており、PCとOpenDolphinサーバとの通信データを、スマホアプリの通信データと間違えてキャプチャしていることです。それを確認するにはキャプチャしたデータのIPアドレスを見ればわかります。もし、IPアドレスがPCのIPアドレスであれば、ここに書いた通りのことが起きています。キャプチャしたパケットデータを保存していたら(実験するときは必ずあとでわかるように適切なファイル名を付けて保存すること)それを開いてサーバ(172.16.108.251)と通信しているクライアントのIPアドレスを確認してください。

私が調べた限りでは、ORCAに登録した感染情報やアレルギー情報など、スマホアプリに取り込みたい情報はOpenDolphinへは転送されていないようです。したがって、OpenDolphinクライアントとOpenDolphinサーバの通信をいくら調べても欲しい情報は手に入りません。
ところが幸いにORCAも「日医標準レセプトAPI」というWebAPIを提供しています。これを使えばORCAに登録したすべての患者基本情報をスマホアプリで取得できます。[詳細仕様]→[日医標準レセプトソフトAPI仕様(Ver 4.8.0以降)]と進んでいくと、API一覧表が表示され、その先頭に[患者基本情報]というのがあります。その行の右端にある[患者基本情報の取得]をクリックするとリクエスト、レスポンスに関する詳細な仕様が出てきます。[レスポンス一覧]を見ればどの項目がどの項目名になっているかわかります。
ただし、このORCAのAPIはレスポンスがJSON形式ではなくXML形式になっている点に注意してください(ただし、ver 5.0以降では引数のformatによってJSON形式を返すことが出来るようになっています)。

ORCAのAPIを使ったスマホアプリの作成は2年前のゼミ生がやっています。彼らのブログを参考にしてください。アプリのソースプログラムは[ORCA Client Ver 0.1]にあります。彼らと私たちのプログラムの違いは、私たちはJavascriptのフレームワークとしてAngularJSを利用している点です。彼らはアプリ画面の編集やプログラムの制御にjQuery以外は何もフレームワークを使っていないため、プログラミングが結構煩雑です。ですから、彼らのプログラムを咀嚼して(何なら彼らのプログラムを実装して動かしてみて)、それを参考にしながらAngularJS版に改変していけばよいと思います。

①患者基本情報項目1

スマホアプリで患者の基本情報を見やすく表示させるよう試みる。
そのため参考として複数のスマホアプリを調べる必要がある、今回では3つのアプリケーションを参考として調べてみた。
1.電子お薬手帳 お薬情報玉手箱
2.jog Team PHR
3.MeDaCa

1.電子お薬手帳 お薬情報玉手箱
このアプリでは、会員カードを利用者が持つことが必須とされる、会員カードにIDとQRコードが記載されており、登録以降はインターネットが使える環境であればID、PW入力により端末を問わずに利用することが出来る。
またお薬手帳であるので、日々の薬剤情報の記録を継続的に記録できる。情報の取り込み方法は、処方箋に印字されたQRコードを読み取ることで取得できる。
その他機能として、健康日誌(体重推移、血圧推移、食事、禁煙、通院予定)
健康履歴(既往歴、副作用歴、アレルギー歴の管理)、免疫力アップレシピ集等の機能があった。

2.Jog Team PHR
上記のアプリとは違い、どのくらい運動したのか、睡眠時間はどの程度か、どのような食事を食べたかなどの普段生活で発生する情報を記録することを特徴としている。
スマートフォンのGPS機能をフルに活用し、ランニングやウォーキングの走行情報を記録出来るようになっている。また検診データとして検査のデータを確認できるようになっており、各種検査のデータを入力できるようになっている。

3.MeDaCa
MeDaCaは、私たちが作ろうと考えている、基本情報の画面に近いと感じ、実際に登録し、基本情報画面を確認してみた。


図1 MeDaCa メインメニュー

MeDaCaでは、患者の基本情報と医療情報のデータは図1と図2のようにを分けて登録されていた。編集の横のボタンを押すことでどちらも画面を移動できる。
図2 MeDaCa 患者基本情報

図3 MeDaCa 医療情報
MeDaCaの主な機能としては、予定表などで予約情報の管理、検査データや健康診断の記録、画像データ(レントゲン画像)の記録、領収書などの金額も管理、医療機関を検索できるマップ機能などもあり、多様なニーズに合わせたアプリといえる。

次回は、引き続き情報収集と出来れば基本情報のデザイン作成に着手していきたい。

【コメント】

なかなか興味深いアプリを見つけましたね。なかでもMeDaCaはかなり本格的でPHRの域を超えていますね。ただ、このMeDaCaといい、お薬情報玉手箱と言い、スマホではなくタブレットを意識したデザインですね。

その点、Jog Team PHR は明らかにスマホで利用するアプリのようですが、このHPの情報だけからでは詳しい情報が得られません。健診画面にはどのような情報がどのように表示されているのか見てみたいものです。それからこれを見て思ったことですが、やはりスマホアプリは文字ばかりでなくイラストやアイコンを多用して、わかりやすく親しみやすい画面にする必要性がありますね。

引き続き、いろいろなアプリを調べてください。


④検査結果表示について1

検査項目をわかりやすく提示出来るようなプログラムの修正を試みる。
まず、検査項目一覧についてだが、現在は検査名がそのまま表示されているだけで、パット見何がどの検査なのかをいまいち把握しづらい。
そこで、生化学的検査、血液学的検査といった検査グループ名でグルーピングを行い、直感的にどの検査なのかを把握できるようにしていきたい。
that.list.push(
              {
                itemName:element.itemName,
                value:Math.round(element.value *100) /100 ,
                unit:element.unit,
                bgColor:bgColor
              }
            );
上記のコードはjavascriptで記述している検査項目一覧画面を表示するプログラムであるが、
この文章に以下の様な条件を付与する。
if(element.groupName == "抽出したい検査グループ名"){
          that.list.push(
              {
                itemName:element.itemName,
                value:Math.round(element.value *100) /100 ,
                unit:element.unit,
                bgColor:bgColor
              }
            )};
まず行目に条件式としてif文を定義する。「element.groupName」とは
OpenDolphinに格納されている検査グループ名を指している。
そして、 == "抽出したい検査グループ名"と定義することで、
検査グループ名が抽出したいものと同様の値が含まれているデータをリストに表示するといった処理が可能になる。
例えば抽出したい検査グループ名を"血液学的検査"とすれば、血液学検査に含まれている検査項目名のみを表示させる事が出来る。
図1 血液学的検査のみを抽出した結果

上記図1は実際に条件式を「血液学的検査」として抽出を行った図である。
しかし、このプログラムを用いた抽出では検査グループ名を一つしか抽出できないため、目的であるグルーピングはこの様な修正では表現する事が少々難しい為、何か別の方策を講じる必要があるだろう。
プログラム全体の構成を大きく変更すれば不可能ではないのかもしれないが、非常に手間がかかる為、スマートな解決策はないだろうか。

【コメント】

なかなか良いところに目を付けましたね。ただし、ブログにも書いている通り、この方法だと特定の検査グループしか表示できません。では、どうすればいいのでしょうか?
こういう場合は、階層構造を考えます。現在のプログラムでは、OpenDolphinサーバから取得した検査項目を CountriesController コントローラのlist配列に「ベタで」保存しているだけです。したがってグループが違う検査項目も同列に扱ってしまいます。そこで、これに階層構造を導入します。まず、検査グループがあって、その下にその検査グループに属する検査項目を格納するのです。こんな感じです。
group = [
  {
    'groupName': '生化学的検査',
    'items': [
      {
        'itemName': '総ビリルビン',
        'value': '0.4',
        'unit': 'mg/dL',
        'backgroundColor': 'normal',
      },
      {
        'itemName': '直接ビリルビン',
        'value': '0.1',
        'unit': 'mg/dL',
        'backgroundColor': 'normal',
      },
      ・
      ・
      ・
    ]
  },
  ・
  ・
  ・
  {
    'groupName': '血液学的検査',
    'items': [
      {
        'itemName': 'HbA1c(JDS)',
        'value': '5.73',
        'unit': '%',
        'backgroundColor': 'normal',
      },
      {
        'itemName': '血小板',
        'value': '17.96',
        'unit': 'マン/mm^3',
        'backgroundColor': 'normal',
      },
      ・
      ・
      ・
    ]
  },
  ・
  ・
  ・
]

このJavascriptオブジェクトを見ると、group属性が検査グループの配列になっています。各グループには、検査グループ名を格納するためのgroupName属性と、その検査グループに属する検査項目を格納するitems属性があります。さらに、items属性は配列になっており、その属性には検査項目名を表すitemName属性、検査値を表すvalue属性、その単位を表すunit属性、そして異常値を表すbackgroundColor属性があります。実は、このitems配列が、現在のプログラムにおけるlist配列に対応しているのです。

こうして、検査結果情報を[グループ]→[検査項目]のような階層構造にすることができました。 次に、この検査結果情報を「階層的に」アプリの検査結果一覧画面に表示することを考えます。そのために使うのが<ons-list>タグです。<ons-list>タグの中にはリストの見出しを表す<ons-list-header>タグを書くことができます。ここに検査グループ名を書きます。その後に<ons-list-item>タグを使って検査項目を書きます。これを必要な検査グループの数だけ繰り返します。次のようなイメージになります。
<ons-list ng-repeat="group in countries.group">
  <ons-list-header>{{ group.groupName }}</ons-list-header>
  <ons-list-item class="{{country.backgroundColor}}" ng-click="countries.showCountry(country.itemName)" modifier="chevron" ng-repeat="country in group.items | filter:query">
    <ons-col width="65%">{{ country.itemName }}</ons-col>
    <ons-col>{{ country.value }}{{ country.unit }}</ons-col>
  </ons-list-item>
</ons-list>
このHTMLが示すように、このリスト表示は階層的になっています。一つは ons-list タグにつけられたAngularJSの ng-repeat 属性です。CountriesControllerコントローラの属性group (これは検査グループの配列)から一つずつ検査グループを取り出して一覧を作っていきます。その一覧には、ons-list-headerタグによって検査グループ名が表示され、その下にons-list-itemタグによってその検査グループに属するすべての検査項目が表示されます。

こうして作成した検査一覧画面は次のようになります。
図2 検査項目を階層的に表示した検査結果一覧画面
先頭の「生化学的検査」が<ons-list-header>タグを使って表示した検査グループ名で、その下にこの検査グループに属する検査項目の一覧が表示されています。

図3 検査項目を階層的に表示した検査結果画面(その2)
これは、検査一覧画面を「血液学的検査」までスクロールさせたものです。このように、検査グループの分かれ目に検査グループ名称を表示することができます。

レーダーチャートの表示2

前回 レーダーチャートの表示を行うことが出来たので、今回は実際の値を代入したグラフの描画を試みる。 .controller('RaderChartController', ['$scope', 'Countries', funct...