EHRbase入門２ - REST API

# はじめに
第２回目はREST APIを使ってEHRbaseとデータのやりとりを行います。EHRbaseには開発中を含めて多くのREST APIがあります。今回はそのうちでTemplate, Compositionに関係する以下のAPIを紹介します。

* POST EHR (EHRレコードの作成)
* POST EHR（外部IDつきでEHRレコードを作成）
* GET EHR (EHR IDでEHRレコードを読み出す)
* GET EHR (外部IDでEHRレコードを読み出す)
* POST Template(Tamplateの登録)
* GET Template (個別のテンプレートの読み出し)
* GET Template (テンプレート一覧の読み出し)
* POST Composition (Compositionの登録)
* GET Composition (Compositionの読み出し)
* PUT Composition (Compositionの更新)
* GET COMPOSITION (VersionごとにCompositionを読み出す)
* DELETE Composition(Compositionの論理削除)

---
# 作業環境

EHRbaseのREST APIを扱うのはEHRbaseが提供するWebのSwagger UIを利用するのがお手軽です。しかし、あとが残らないのでPostmanを利用するのが開発にはよいかと思います。

openEHRのテンプレートを作成するにはたくさんのツール類があり、Web上で作成できるものもありますが、Ocean Template Designerは歴史も古く使用者も多いです。

* Postman: https://www.postman.com/
* Ocean Template Designer: http://downloads.oceaninformatics.com/downloads/TemplateDesigner/
* このチュートリアルで使用したPostman collection: https://gist.github.com/skoba/0120873e2d31345ff86981115922300c


--- 
# openEHRのREST API
openEHRの開発が始まった1990年代から2000年代のはじめのほうはRPCやSOAPでインターフェースが実装されていました。外部インターフェースとしてはAQL(Archetype Query Language)という呼び出し言語が整備されていてそれを利用して外部とのやり取りが行うというのがopenEHRでは一般的でした。しかし、2010年代に入るとREST APIが主流となり、FHIRとの連携を含めてopenEHRを実装した各社がREST APIを実装していきました。

openEHRとしては下記のようにREST APIの標準化を行い公開しています。

https://specifications.openehr.org/releases/ITS-REST/latest

しかし、簡略化したJSONフォーマットを利用したAPIがMarand社から発表され、それが便利なので他の実装も従った結果、パラメータや簡略化方法でばらつきが見られるようになりました。openEHR仕様策定委員会で協議した結果、簡略化したデータフォーマットも標準化することとなり、下記のように公開されております。

簡略化データテンプレート標準
https://specifications.openehr.org/releases/ITS-REST/latest/simplified_data_template.html

Marand社EHRScape API
https://dev.ehrscape.com/api-explorer.html

EHRbaseの前身の一つであるEtherCISは独自の簡略化を行っていたのですが、今は外されており新しい簡略化データテンプレート標準に基づいた実装が進められております。(2020年12月頃公開予定とのこと）

---
# REST APIの表記

REST APIについては改めての解説は行いません。ものすごく単純化すれば、特定のURLにHTTPメソッドを使ってアクセスしてデータをやりとりすることですが、EHRbaseは各自それぞれの環境でデプロイされていると思いますので、URL表記を統一した書き方ができません。したがって、今回は下記のURLのうちhttp://からehrbase/までの部分を省略して下記のようにREST APIを表記します（良い書き方があったら教えてください）。

例： http://localhost:8080/ehrbase/rest/openehr/v1/ehr　にGETメソッドを使って ehr_id に関連する情報をパラメータ付きで取り入れようとする場合

```
GET /rest/openehr/v1/ehr/{{ehr_id}}
```

パラメータ

| key| value |
|:-:|:-:|
| subject_id | ins01  |
| subject_namespace| ehr_craft   |

実際に発行されるリクエストは下記です。URL中に出てくる{{}}は変数部分であることを示します。

```shell
curl -X GET "http://localhost:8080/ehrbase/rest/openehr/v1/ehr/f2e3ebf3-596b-4067-9f76-8f4f19c0c474?subject_id=ehr_craft&subject_namespace=ins01" -H  "accept: application/xml"
```

---
# EHR class
openEHRでは患者ごとにデータを管理します。患者ごとにデータを保存する単位がEHRです。EHRといってもEHRのシステムそのものではなく患者さんのデータを入れる箱を作ることをイメージしてください。したがって、REST APIを使ってEHRを作ると言っても、EHRのシステムを作るわけではありません。openEHRのアーキテクチャでは患者氏名、住所、性別などの属性情報はEHRには直接保存せず、外部の患者情報データベースからEHRクラスのIDを呼び出すことで連結します。

これはEHRを匿名データベースとして使用することを想定しての設計です。

---
# POST EHR (EHRレコードの作成)

単に新しいEHRレコードを作成すだけであれば、パラメータ無しで下記のようにPOSTするだけです。

```
POST /rest/openehr/v1/ehr
```

URLの中の localhost:8080 の部分はそれぞれの環境で読み替えください。下記のようにレスポンスがありますので、ehr_idを保存しておく必要があります。

```json
{
    "system_id": {
        "_type": "HIER_OBJECT_ID",
        "value": "b1718dd8-a45a-4ebf-a1d6-9a9fd7ca36fb"
    },
    "ehr_id": {
        "_type": "HIER_OBJECT_ID",
        "value": "fa95a254-feb0-4b03-9fe3-193d7d485d45"
    },
    "ehr_status": {
        "_type": "EHR_STATUS",
        "subject": {
            "_type": "PARTY_SELF",
            "external_ref": {
                "_type": "PARTY_REF",
                "namespace": "default",
                "id": {
                    "_type": "HIER_OBJECT_ID",
                    "value": "a258c07e-7b64-4062-b449-f96504e54a94"
                }
            }
        },
        "uid": {
            "_type": "HIER_OBJECT_ID",
            "value": "1220e446-b637-4c39-a62b-1e53f479ffea"
        },
        "is_queryable": true,
        "is_modifiable": true
    },
    "time_created": "2020-09-26T02:11:21.214052"
}
```

EHRのIDは以下の部分です。

```json
    "ehr_id": {
        "_type": "HIER_OBJECT_ID",
        "value": "fa95a254-feb0-4b03-9fe3-193d7d485d45"
    },
```


EHR IDはEHRデータを読み出すときに必要となりますので保存しておいてください。わからなくなるとEHRbaseにはEHR IDの一覧を取得するAPIがないので、PostgreSQLの中を見ないといけなくなります。


---
# POST EHR （外部IDつきでEHRレコードを作成）

診察券番号のように既に患者IDが別に存在する場合は以下のようなJSONメッセージをつけて、/rest/v1/ehrにPOSTすると外部IDも保存されます。

```
POST /rest/openehr/v1/ehr
````

body

```json
{
  "_type": "EHR_STATUS",
  "subject": {
    "external_ref": {
      "id": {
        "_type": "GENERIC_ID",
        "value": "ins11",
        "scheme": "id_scheme"
      },
      "namespace": "ehr_craft",
      "type": "PERSON"
    }
  },
  "is_modifiable": "true",
  "is_queryable": "true"
}
```

EHRbaseのサンプルやSwagger UIだとITEMが空欄のother_detailsが入っていて、そこがエラーを出すので注意してください。

外部IDが重複すると下記のようなエラーが返ってきますのであまり心配せずにどんどんEHRを発行してください。

```json
{
    "error": "Specified party has already an EHR set (partyId=ce8bf586-125d-4c28-9970-465eacbaa8c4)",
    "status": "Conflict"
}
```

---
# GET EHR(EHR IDでEHRレコードを読み出す)

POSTで作ったEHRをEHR IDで呼び出してみましょう。{{EHR ID}}のところに、先ほど返ってきたehr_idを指定します。

```
GET /rest/openehr/v1/ehr/{{EHR ID}}
```

作成できていれば、登録した内容と同じデータが返ってきます。デフォルトはJSONですが、以下のようにHTTP HeaderにAccept application/xmlと指定するとXML形式でデータが返ってきます。


HTTP header

| key | value |
|:-:|:-:|
|Accept| application/xml|


---
# GET EHR (外部IDでEHRレコードを読み出す)
```
GET /rest/openehr/v1/ehr/
```

パラメータ

| key| value |
|:-:|:-:|
| subject_id | 0001  |
| subject_namespace| NPO openEHR Japan  |

---
# Templateの作成
openEHRではEHRに登録するデータの単位はCompositionで、カルテ記録や各種レポート、検査結果などがCompositionとして記録されます。TemplateにはCompositionをベースに各種制約が記述されています。

例として体温と症状をモニタするテンプレートを作成します。使用するArchetypeは以下のとおりです。

* openEHR-EHR-COMPOSITION.health_summary.v1
* openEHR-EHR-OBSERVATION.temperature.v1
* openEHR-EHR-OBSERVATION.story.v1
* openEHR-EHR-CLUSTER.symptom_sign.v1

Ocean Template Designerを使ってテンプレートを作成したら、「利用可能なテンプレート」としてexportしておいてください。ファイル識別子がoptとして出力されたファイルを Operational Template (以下OPT)と呼びます。このファイルには扱うデータがすべて定義され、XMLで記述されています。

Tempate IDにスペースを入れると、EHRbaseでは読み出しができなかったりとトラブルが発生しましたので、現時点では避けてください。

今回使用するOPTは[symptom_screening.opt](https://gist.github.com/skoba/14e9fa8f260d4bcdf9134ba83fa0ade2)として公開しております。

---
# POST Template(Templateの登録)

POSTでEHRbaseにTemplate(OPT)を登録しましょう。

```
POST /rest/openehr/v1/definition/template/adl1.4
```

bodyに先ほど作成したoptファイルの中身を貼り付けます。Templateは以template_idとuidで識別されていますので、どちらかがかぶるとエラーが返ってきます。

```xml
  <uid>
    <value>17bed299-2c1e-42cc-afb3-6d78002dcce3</value>
  </uid>
  <template_id>
    <value>symptom_screening</value>
  </template_id>
```

---
# GET Template (個別のテンプレートの読み出し)

POSTしたTemplateがEHRbaseに登録されているかどうか、GETメソッドで確認します。先ほど登録したtemplateのtemplate_idを使って読み出しましょう。{{template_id}}のところにsymptom_screeningと入力してください。

```
GET /rest/openehr/v1/definition/template/adl1.4/{{template_id}}
```

登録したOPTがresponseとして返ってきます。

---
# GET Template (テンプレート一覧の読み出し)

現在登録されているリストを取得して確認します。

```
GET /rest/openehr/v1/definition/template/adl1.4/
```

下記のようなリストが返ってきます

```json
[
    {
        "concept": "symptom_screening",
        "template_id": "symptom_screening",
        "archetype_id": "openEHR-EHR-COMPOSITION.health_summary.v1",
        "created_timestamp": "2020-09-25T03:17:54.134Z"
    }
]
```


---
# Template対応JSONインスタンスの作成

Templateで定義された内容をもとにJSONやXMLのインスタンスを生成するのは、それほど楽ではないので自動ツールを作成しています。

EHRbaseは[openEHR-SDK](https://github.com/ehrbase/openEHR_SDK)を提供していて、OPTからEHRbaseにアクセスできるクラス群を生成することができます。

今回使用するJSONインスタンスのテンプレートを自動生成するツール群を開発していたのですが、そもそもJSONインスタンスがあまり公開されていませんでしたので調査に時間を要しました。ようやく、1日前に動作するインスタンスが作成できましたので今回はそれをもとにCompositionアクセスを行います。

作成した手順は下記のとおりです。

1. EHRScape(Marand)のAPI経由でOPTを登録
2. EHRScapeのAPI経由でJSONテンプレートを取得
3. FLAT形式を最初に使用していたが通らないので、EHRbaseのコードを解析して現時点ではFLAT形式には対応していないというのを理解
4. STRUCTURED形式を指定してEHRScapeよりJSONテンプレートを取得。
5. インスタンスを作成しEHRbaseに登録したがcategoryでどうしてもErrorがでるのでEHRbaseの再構築やコードの解析を行いバグとして報告。
6. Christian Chevalleyのコメントがかすったのでそれを元にインスタンスの調整を行い可動。

作成したインスタンスはGISTに公開しています。

https://gist.github.com/skoba/cca9f69004a229e5922a0e3e73dca53e

---
# POST Composition(Compositionの登録)

作成したCompositionのインスタンスをREST APIを使って登録します。すでに登録しているEHR IDに紐づけます。

```
POST /rest/openehr/v1/ehr/{{EHR ID}}/composition
```

bodyにはGISTのJSONを貼り付けておきましょう。

resposeヘッダが重要です。ETagに"89b114ea-59bd-4d98-b9b8-ad8f819a5aa3::local.ehrbase.org::1"な形式のデータがかかれていますが、これはVersioned Object UIDというものです。先ほどのCompositionレコードに割り振られたUUIDとEHRに登録されている固有のURLとバージョン番号が::で区切られて書かれています。このUUIDがCompositionレコードの IDとなります。

---
# GET Composition(Compositionの読み出し)

EHR IDとComposition IDを使って登録したデータを読み出します。

```
GET /rest/openehr/v1/ehr/{{EHR ID}}/composition/{{Composition ID}}
```

{{EHR ID}}と{{Composition ID}}にはそれぞれ登録されたEHR IDとComposition IDを入れておいてください。登録したJSONと同じデータが返ってきます。

---
# PUT Composition (Compositionの更新)

修正事項があった場合に内容を変更する場合、PUTを使います。

```
PUT /rest/openehr/v1/ehr/{{EHR ID}}/composition/{{Composition ID}}
```

boodyには先ほどのJSONの一部を変更したものを使用します。

ここで重要なのは先ほどEtagにもあったCopositionのVersionObjectUIDです。HTTP request headerに以下のように指定します。

| key | value |
|:-:|:-:|
| If-Match | version_object_uid  |

成功するとresponse headerのETagに新しいversion object uidが返ってきます。

---
# GET COMPOSITION (VersionごとにCompositionを読み出す)

openEHRではCOMPOSITIONはすべてバージョン管理されています。先ほどのversioned object uidを使うと先ほど更新したCompositionが変更前、変更後もデータを取り出すことができます。

```
GET /rest/openehr/v1/ehr/{{EHR ID}}/composition/{versioned object uid}}
```

末尾のバージョンを変更してデータの違いを見てください。

---
# DELETE Composition(Compositionの論理削除)

openEHRではレコードの削除は論理的な削除にとどまり、読み出しができなくなるだけでデータベースとしては保存され続けます。めったに使う機能ではありませんが、削除を行う場合は最新版のversioned oject uidを指定してDELETEを使います。

```
DELETE /rest/openehr/v1/ehr/{{EHR ID}}/composition/{versioned object uid}}
```

最新版以外を指定するとそのバージョンが削除されますが、まだ他のバージョンを読み出すことはできます。最新版を削除すると旧バージョンを含めてAPIからはアクセスできなくなります。

---
# まとめ

* openEHRのREST APIを一通り紹介しました。まだ他にもAPIはありますので、関連文書などを参照してください。
* まだ、templateからインスタンスを作成するところが難しいのですが、製品版であれば対応していますしOpen Source版でもEtherCISやCabolab EHR serverはインスタンスサンプルを提供する機能があります。
* EHRbaseを例にopenEHRのREST APIとバージョン管理にいついて解説しました。基本的にはすべてのレコードはバージョン付きで保存されていて、上書きや削除は論理的なものであってデータベースには記録されています。
* 次はデータの読み出しを行うAQLを解説します。