記事
· 2024年5月23日 11m read

FHIR アダプターを使ってレガシーシステムに FHIR サービスを提供 - リソースの読み取り編

HealthShare、HealthConnect、および InterSystems IRIS ユーザーが使用できる FHIR アダプターツールに関する連載記事を再開しましょう。

前回の記事では、ワークショップをセットアップした小さなアプリケーションを紹介し、FHIR アダプターをインストールした後に IRIS インスタンスにデプロイされたアーキテクチャを説明しました。 この記事では、最も一般的な CRUD(作成、読み取り、更新、削除)操作の 1 つである読み取り操作を実行する方法の例を確認します。ここではリソースの取得によって行います。

リソースとは?

FHIR のリソースはある種の臨床情報です。この情報は、患者(Patient)、臨床検査へのリクエスト(ServiceRequest)、または診断(Condition)などです。 各リソースはそれを構成するデータのタイプのほか、データの制約や他のリソースタイプとの関係を定義します。 リソースごとに、それが格納する情報の拡張が可能であるため、FHIR ポリシーの範囲外のニーズに 80% 対応できます(ユーザーの 80% が使用する要件に対応)。

この記事の例では、最も一般的なリソースである Patient を使用します。 その定義を確認しましょう。

{
  "resourceType" : "Patient",
  // from Resource: id, meta, implicitRules, and language
  // from DomainResource: text, contained, extension, and modifierExtension
  "identifier" : [{ Identifier }], // An identifier for this patient
  "active" : <boolean>, // Whether this patient's record is in active use
  "name" : [{ HumanName }], // A name associated with the patient
  "telecom" : [{ ContactPoint }], // A contact detail for the individual
  "gender" : "<code>", // male | female | other | unknown
  "birthDate" : "<date>", // The date of birth for the individual
  // deceased[x]: Indicates if the individual is deceased or not. One of these 2:
  "deceasedBoolean" : <boolean>,
  "deceasedDateTime" : "<dateTime>",
  "address" : [{ Address }], // An address for the individual
  "maritalStatus" : { CodeableConcept }, // Marital (civil) status of a patient
  // multipleBirth[x]: Whether patient is part of a multiple birth. One of these 2:
  "multipleBirthBoolean" : <boolean>,
  "multipleBirthInteger" : <integer>,
  "photo" : [{ Attachment }], // Image of the patient
  "contact" : [{ // A contact party (e.g. guardian, partner, friend) for the patient
    "relationship" : [{ CodeableConcept }], // The kind of relationship
    "name" : { HumanName }, // I A name associated with the contact person
    "telecom" : [{ ContactPoint }], // I A contact detail for the person
    "address" : { Address }, // I Address for the contact person
    "gender" : "<code>", // male | female | other | unknown
    "organization" : { Reference(Organization) }, // I Organization that is associated with the contact
    "period" : { Period } // The period during which this contact person or organization is valid to be contacted relating to this patient
  }],
  "communication" : [{ // A language which may be used to communicate with the patient about his or her health
    "language" : { CodeableConcept }, // R!  The language which can be used to communicate with the patient about his or her health
    "preferred" : <boolean> // Language preference indicator
  }],
  "generalPractitioner" : [{ Reference(Organization|Practitioner|
   PractitionerRole) }], // Patient's nominated primary care provider
  "managingOrganization" : { Reference(Organization) }, // Organization that is the custodian of the patient record
  "link" : [{ // Link to a Patient or RelatedPerson resource that concerns the same actual individual
    "other" : { Reference(Patient|RelatedPerson) }, // R!  The other patient or related person resource that the link refers to
    "type" : "<code>" // R!  replaced-by | replaces | refer | seealso
  }]
}

ご覧のように、実質的に患者のすべての管理情報のニーズに対応しています。

HIS から患者の取得

前回の記事を覚えていれば、HIS システムのデータベースをシミュレーションする PostgreSQL データベースをデプロイしました。特定の HIS にあるサンプルテーブルを確認してみましょう。

多くはありませんが、ここでの例には十分です。 patient テーブルをもう少し詳しく見てみましょう。

ここに、3 つの患者の例があります。ご覧のように、それぞれに一意の識別子(ID)のほか、医療組織に関連性のある一連の管理データがあります。 最初の目標は、1 人の患者の FHIR リソースを取得することです。

患者のクエリ

サーバーから患者データをリクエストするにはどうすればよいでしょうか? FHIR が作成した実装仕様によると、サーバーのアドレス、リソース名、および識別子を使って REST 経由で URL に GET を実行する必要があり、 以下のように呼び出す必要があります。

http://SERVER_PATH/Patient/{id}

この例では、Juan López Hurtado という患者を検索します。この患者の ID は 1 であるため、呼び出す URL は以下のようになります。

http://localhost:52774/Adapter/r4/Patient/1

テストするには、クライアントとして Postman を使用します。 サーバーのレスポンスを確認しましょう。

{
    "resourceType": "Patient",
    "address": [
        {
            "city": "TERUEL",
            "line": [
                "CALLE SUSPIROS 39 2ºA"
            ],
            "postalCode": "98345"
        }
    ],
    "birthDate": "1966-11-23",
    "gender": "M",
    "id": "1",
    "identifier": [
        {
            "type": {
                "text": "ID"
            },
            "value": "1"
        },
        {
            "type": {
                "text": "NHC"
            },
            "value": "588392"
        },
        {
            "type": {
                "text": "DNI"
            },
            "value": "12345678X"
        }
    ],
    "name": [
        {
            "family": "LÓPEZ HURTADO",
            "given": [
                "JUAN"
            ]
        }
    ],
    "telecom": [
        {
            "system": "phone",
            "value": "844324239"
        },
        {
            "system": "email",
            "value": "juanitomaravilla@terra.es"
        }
    ]
}

次に、本番環境内でリクエストが通過した経路を詳しく見てみましょう。

ルートは以下のようになっています。

  1. BS InteropService にリクエストが到着。
  2. BS の宛先として構成し、受信した呼び出しの患者識別子がクエリされる BP に転送。
  3. BO FromAdapterToHIS から HIS データベースにクエリ。
  4. 患者データを BP に転送し、それを FHIR Patient リソースに変換。
  5. レスポンスを BS に転送。

BP ProcessFHIRBP で受信するメッセージのタイプを見てみましょう。

クライアントからどの種類のオペレーションをリクエストされたかを特定するための鍵となる 3 つの属性を見てみましょう。

  • Request.RequestMethod: どの種のオペレーションを実行するかを示します。 この例では、患者の検索は GET になります。
  • Request.RequestPath: この属性には、サーバーに届いたリクエストのパスが含まれ、操作するリソースを示します。この場合、取得するための具体的な識別子が含まれます。
  • Quick.StreamId: FHIR Adapter は Stream に届いたすべての FHIR メッセージを変換し、この属性に保存される識別子が割り当てられます。 この例では、GET を実行しており、FHIR オブジェクトを送信していないため、これは必要ありません。

処理を行う GLP を詳しく分析して、メッセージの流れをさらに見ていきましょう。

ProcessFHIRBP:

本番環境に、ビジネスサービスから受け取る FHIR メッセージングを管理する BPL を実装しました。 どのように実装されているか見てみましょう。

ステップごとに実行する操作を見てみましょう。

FHIR オブジェクトの管理:

HIS データベースへの接続とデータベースクエリを処理する BO FromAdapterToHIS を呼び出します。

Method ManageFHIR(requestData As HS.FHIRServer.Interop.Request, response As Adapter.Message.FHIRResponse) As %Status
{
  set sc = $$$OK
  set response = ##class(Adapter.Message.FHIRResponse).%New()

  if (requestData.Request.RequestPath = "Bundle")
  {
    If requestData.QuickStreamId '= "" {
        Set quickStreamIn = ##class(HS.SDA3.QuickStream).%OpenId(requestData.QuickStreamId,, .tSC)
        
        set dynamicBundle = ##class(%DynamicAbstractObject).%FromJSON(quickStreamIn)

        set sc = ..GetBundle(dynamicBundle, .response)
      }
    
  }
  elseif (requestData.Request.RequestPath [ "Patient")
  {
    if (requestData.Request.RequestMethod = "POST")
    {
      If requestData.QuickStreamId '= "" {
        Set quickStreamIn = ##class(HS.SDA3.QuickStream).%OpenId(requestData.QuickStreamId,, .tSC)
        
        set dynamicPatient = ##class(%DynamicAbstractObject).%FromJSON(quickStreamIn)

        set sc = ..InsertPatient(dynamicPatient, .response)
      }      
    }
    elseif (requestData.Request.RequestMethod = "GET")
    {
      set patientId = $Piece(requestData.Request.RequestPath,"/",2)
      set sc = ..GetPatient(patientId, .response)
    }

  }
  Return sc
}

BO は受信した HS.FHIRServer.Interop.Request タイプのメッセージを確認します。この場合は、GET を設定して、Partient リソースに対応するパスに、以下の GetPatient メソッドが呼び出されることを示して確認します。

Method GetPatient(patientId As %String, Output patient As Adapter.Message.FHIRResponse) As %Status
{
  Set tSC = $$$OK
  set sql="SELECT id, name, lastname, phone, address, city, email, nhc, postal_code, birth_date, dni, gender FROM his.patient WHERE id = ?"
  //perform the Select
  set tSC = ..Adapter.ExecuteQuery(.resultSet, sql, patientId)
  
  If resultSet.Next() {
     set personResult = {"id":(resultSet.GetData(1)), "name": (resultSet.GetData(2)), 
        "lastname": (resultSet.GetData(3)), "phone": (resultSet.GetData(4)), 
        "address": (resultSet.GetData(5)), "city": (resultSet.GetData(6)),
        "email": (resultSet.GetData(7)), "nhc": (resultSet.GetData(8)),
        "postalCode": (resultSet.GetData(9)), "birthDate": (resultSet.GetData(10)),
        "dni": (resultSet.GetData(11)), "gender": (resultSet.GetData(12)), "type": ("Patient")}

   } else {
     set personResult = {}
   }
  
  //create the response message
  do patient.Resource.Insert(personResult.%ToJSON())
 
    Return tSC
}

ご覧のように、このメソッドは HIS のデータベースにクエリを発行し、すべての患者情報を取得してから後で String に変換されて Adapter.Message.FHIRResponse タイプの変数に格納される DynamicObject を生成します。 後でトレースでレスポンスを表示できるように、Resource プロパティを String リストとして定義しました。 直接 DynamicObjects として定義し、後続の変換を省略することもできます。

Bundle かどうかの確認:

BO からのレスポンスによって、Bundle タイプであるか(今後公開される記事で説明します)単なる Resource であるかをチェックします。

DynamicObject の作成:

BO レスポンスを DynamicObject に変換し、それを一時コンテキスト変数(context.temporalDO)に割り当てます。 変換に使用される関数は以下のとおりです。

##class(%DynamicAbstractObject).%FromJSON(context.FHIRObject.Resource.GetAt(1))

FHIR の変換:

DynamicObject タイプの一時変数を使用して、クラス HS.FHIR.DTL.vR4.Model.Resource.Patient のオブジェクトへの変換を行います。 他のタイプのリソースを探す場合は、タイプごとに特定の変換を定義する必要があります。 では、変換を確認しましょう。

この変換によって、BS InteropService が解釈するオブジェクトが得られます。 結果を context.PatientResponse 変数に格納します。

Stream へのリソースの割り当て:

FHIR の変換で取得した変数 context.PatientResponse を Stream に変換します。

QuickStream への変換:

response 変数に、クライアントに戻す必要のあるすべてのデータを割り当てます。

 set qs=##class(HS.SDA3.QuickStream).%New()
 set response.QuickStreamId = qs.%Id()
 set copyStatus = qs.CopyFrom(context.JSONPayloadStream)
 set response.Response.ResponseFormatCode="JSON"
 set response.Response.Status=200
 set response.ContentType="application/fhir+json"
 set response.CharSet = "utf8"
  

この場合、200 レスポンスを常に返します。 本番環境では、検索されたリソースが正しく取得されたことを確認し、取得されていない場合は、レスポンスのステータスを 200 から「見つかりません」に対応する 404 に変更します。 このコード箇所で見られるように、オブジェクト HS.FHIR.DTL.vR4.Model.Resource.Patient
は Stream に変換されて、 HS.SDA3.QuickStream として格納されます。そのオブジェクトの識別子を QuickStreamID 属性に追加されるため、その後 InteropService サービスによって結果を JSON として正しく戻せるようになります。

まとめ:

この記事で行ったことをまとめましょう。

  1. GET タイプのリクエストを送信して、定義された ID を持つ Patient リソースを検索しいました。
  2. BS InteropService は構成済みの BP にリクエストを転送しました。
  3. BP は、HIS データベースを操作する BO を呼び出しました。
  4. 構成済みの BO は、HIS データベースから患者データを取得しました。
  5. BP は結果を、デフォルトの InteropService が作成された BS によって理解可能なオブジェクトに変換しました。
  6. BS はレスポンスを受け取り、クライアントに転送しました。

ご覧のように、操作は比較的単純で、より多くのタイプのリソースをサーバーに追加する場合、BO に取得される新しいリソースに対応するデータベースのテーブルにクエリを追加し、BO の結果を対応する HS.FHIR.DTL.vR4.Model.Resource.* タイプのオブジェクトに変換する処理を BP に含めます。

次の記事では、Patient タイプの新しい FHIR リソースを HIS データベースに追加する方法を説明します。

お読みいただきありがとうございました!

ディスカッション (0)0
続けるにはログインするか新規登録を行ってください