HL7とopenEHRのObservation
はじめに
HL7とopenEHRはよく比較される1。相互に影響を受けているところもあり,共通点も多いのであるが,最も違いが大きいのは Observation だろう。openEHR CKM(Clinical Knowledge Manager)にはObservationから派生した470ものarchetypeが登録されているが,HL7 FHIRでは公式にはObservationリソースの下に派生したリソースはない。これはFHIRだけの特徴ではなく,HL7 V2から全ての観察項目がOBXレコードとして扱われていた伝統的なものである。バイタルサインや検査、問診内容や病歴など全ての「観察」をObservationで扱うことにはメリットデメリットがある。
- メリット
- スキーマの変更なしでほぼすべての観察データを扱うことができる。
- データの多くは項目名と値の組み合わせであるため、柔軟に扱う項目を増やすことができる。
- スキーマをコンパクトにまとめることができる。
- デメリット
- 観察項目の特性を反映させることが難しい。例えば,血圧のように拡張期と収縮期の2つの値をセットで扱うためには別途制約を設ける必要がある。
- 観察項目によって値が数値であったり文字列であったりとデータ型が一定ではない。
- データを統合して扱うためには用語集の整備が不可欠であり、場合によっては複雑なマッピング操作が必要となる。
このため,用語集を指定したり,値のデータ型を指定するなど適切な制約を設けるように実装ガイドラインが整備されていることが一般的となっている。HL7 V3以降ではObservation以下に観察項目の特性に合わせたデータモデルを組み込むことができるようになり,HL7 FHIRではComponentとして複数のデータをまとめることができるようになった。日本で利用されている
アメリカで提示されているガイドラインUS Coreの例を下記に示す。
- US Core
- 臨床検査結果,Clinical test result codes, https://www.hl7.org/fhir/us/core/StructureDefinition-us-core-observation-clinical-test.html
- 画像検査結果,Imaging result, https://www.hl7.org/fhir/us/core/StructureDefinition-us-core-observation-imaging.html
- 検体検査,Laboratory result, https://www.hl7.org/fhir/us/core/StructureDefinition-us-core-observation-lab.html
- バイタルサイン、Vital signs, https://www.hl7.org/fhir/us/core/StructureDefinition-us-core-vital-signs.html
- 血圧,Blood pressure, https://www.hl7.org/fhir/us/core/StructureDefinition-us-core-blood-pressure.html
- 体温,Body temperature, https://www.hl7.org/fhir/us/core/StructureDefinition-us-core-body-temperature.html
- 心拍数,Heart Rate,https://www.hl7.org/fhir/us/core/StructureDefinition-us-core-heart-rate.html
- 呼吸数,Respiratory Rate, https://www.hl7.org/fhir/us/core/StructureDefinition-us-core-respiratory-rate.html
- SpO2, Pulse Oximetry, https://www.hl7.org/fhir/us/core/StructureDefinition-us-core-pulse-oximetry.html
いずれも既存のObsertionに新しい制約を設けている。血圧や脈拍、心拍数などのバイタルサインは基本的データであるため、計測方法や計測時の状態についての関連情報も多く、それらをまとめて扱うことができるようにProfileが設計されている。
HL7とopenEHRのマッピング
上記のようにHL7のObservationは扱われるデータ項目が多岐にわたるため,openEHRにマッピングする場合にはどのようなデータなのかを内容を見て判定し,場合分けをする必要がある。openEHR -> HL7方向には比較的楽にデータを送信することができるため,openEHRで規定されているObservationクラスについての制約をHL7 FHIRのProfileを導出するツールが開発されている。
残念ながら万能とまではいかないが、このツールでProfileの設計をかなり省力化することができる。LOINCなどの用語集を使ってFHIRの項目とArhcetypeのエレメントの両方にコードを割り振っておくとマッピングが楽になる。主要なバイタルサインに相当するLOINCのコードを以下に供覧する。
表1 バイタルサインテーブル
項目名 | LOINC code | LOINC name | 単位 |
---|---|---|---|
バイタルサイン計測 | 29274-8 | Vital signs measurements | |
心拍数 | 8867-4 | Heart rate | /min |
脈拍 | 8893-0 | Heart rate Peripheral artery by palpation | /min |
呼吸数 | 9279-1 | Respiratory rate | /min |
収縮期動脈血圧(侵襲的測定) | 76215-3 | Invasive Systolic blood pressure | mm[Hg] |
収縮期動脈血圧(侵襲的測定) | 76215-3 | Invasive Systolic blood pressure | mm[Hg] |
平均動脈血圧(侵襲的測定) | 76214-6 | Invasive Mean blood pressure | mm[Hg] |
拡張期動脈血圧(侵襲的測定) | 76213-8 | Invasive Diastolic blood pressure | mm[Hg] |
収縮期動脈血圧(非侵襲的測定) | 76534-7 | Systolic blood pressure by Noninvasive | mm[Hg] |
平均動脈血圧(非侵襲的測定) | 76536-2 | Mean blood pressure by Noninvasive | mm[Hg] |
拡張期動脈血圧(非侵襲的測定) | 76535-4 | Diastolic blood pressure by Noninvasive | mm[Hg] |
収縮期中心静脈圧 | 60987-5 | Central venous pressure (CVP) Systolic | mm[Hg] |
拡張期中心静脈圧 | 60986-7 | Central venous pressure (CVP) Diastolic | mm[Hg] |
平均中心静脈圧 | 8591-0 | Central venous pressure (CVP) Mean | mm[Hg] |
収縮期肺動脈圧 | 8440-0 | Pulmonary artery Systolic blood pressure | mm[Hg] |
平均肺動脈圧 | 8414-5 | Pulmonary artery Mean blood pressure | mm[Hg] |
拡張期肺動脈圧 | 8385-7 | Pulmonary artery Diastolic blood pressure | mm[Hg] |
体温 | 8310-5 | Body temperature | Cel |
深部体温 | 8329-5 | Body temperature - Core | Cel |
直腸温 | 8332-9 | Rectal temperature | Cel |
血液温 | 60834-9 | Blood temperature | Cel |
腋窩温 | 8328-7 | axillary temperature | Cel |
膀胱温 | 8334-5 | Body temperature - Urinary bladder | Cel |
酸素飽和度(経皮的測定) | 59408-5 | Oxygen saturation in Arterial blood by Pulse oximetry | % |
以下のHL7 V2系バイタルサインメッセージをopenEHR形式に変換するスクリプト(一部)を示す。
MSH|^~\\&|JPN Kaisha|JPN Kaisha HL7|Kaisha ID|Kaisha NAME|||ORU^R01^ORU_R01|20211130000187|P|2.5||||||UNICODE UTF-8|||
PID|||2021081001||Kaisha^テスト^^^^^L^I~^^^^^^L^A||19700419000000|M|
PV1||I|^^71IC01|||||||||||||||||||||||||||||||||||||||||||
ORC|RE|
OBR|1|||29274-8^Vital Sign Measurement|||20211130184100||||||||||||||||||A|
OBX|1|NM|8867-4^Heart rate||134|/min|||||F|||20211130184100||||
OBX|2|NM||9279-1^Respiratory rate||17|/min|||||F|||20211130184100||||
OBX|3|NM|8310-5^Body temperature||39.2|Cel|||||F|||20211130184100||||
OBX|4|NM|76534-7^Systolic blood pressure by Noninvasive||138|mm[Hg]|||||F|||20211130184100||||
OBX|5|NM|76535-4^Diastolic blood pressure by Noninvasive||83|mm[Hg]|||||F|||20211130184100||||
OBX|6|NM|59408-5^Oxygen saturation in Arterial blood by Pulse oximetry||95|%|||||F|||20211130184100||||
openEHRでのArchetype, templateは以下で公開している。
使用しているものは以下のarchetype, templateである。Archetype Designerにimportすれば編集可能である。
Template: https://github.com/skoba/openehr-lesson-archetypes-jp/blob/main/local/vital_signs.t.json
心拍数: https://github.com/skoba/openehr-lesson-archetypes-jp/blob/main/local/openEHR-EHR-OBSERVATION.pulse.v2.adl
呼吸数: https://github.com/skoba/openehr-lesson-archetypes-jp/blob/main/local/openEHR-EHR-OBSERVATION.respiration.v2.adl
体温: https://github.com/skoba/openehr-lesson-archetypes-jp/blob/main/local/openEHR-EHR-OBSERVATION.body_temperature.v2.adl
血圧: https://github.com/skoba/openehr-lesson-archetypes-jp/blob/main/local/openEHR-EHR-OBSERVATION.blood_pressure.v2.adl
SPO2: https://github.com/skoba/openehr-lesson-archetypes-jp/blob/main/local/openEHR-EHR-OBSERVATION.pulse_oximetry.v1.adl
#! /bin/env ruby
# coding: utf-8
LIB_DIR = File.join(File.dirname(__FILE__) , 'lib')
$LOAD_PATH.unshift(LIB_DIR)
require 'logger'
require 'socket'
require_relative 'lib/ehrbase_client'
require_relative 'lib/vitalbox_message'
require_relative 'lib/vital_sign_message_factory'
ROOT_DIR = File.join(File.dirname(__FILE__), '../')
LOG_DIR = File.join(ROOT_DIR, '/log')
logger = Logger.new(File.join(LOG_DIR, 'rclient.log'))
CR = "\r".freeze
FS = "\x1c".freeze
socket = TCPSocket.open('192.168.183.100', 8102)
loop do
message = ''
logger.info 'receiving a message'
loop do
line = socket.gets(sep = CR) # socket.readline do |line|
logger.info line
message += line
break if line.nil? || line.include?("#{FS}#{CR}")
end
hl7message = VitalBoxMessage.new(message)
# msh_10 = hl7message.msh.message_control_id
ack_message = hl7message.ack # "MSH|^~\\&|||||||ACK^R01|#{msh_10}|P|||||||UNICODE UTF-8|||#{CR}MSA|AA|#{msh_10}|#{CR}#{FS}#{CR}"
logger.info 'send ACK'
logger.info(ack_message.tr("\r", "\n"))
# socket.close
# socket = TCPSocket.open("192.168.183.100", 8102)
socket.write(ack_message)
socket.flush
# confirm EHR id
patient_id = hl7message.pid.patient_id
ehr_client = EHRbaseClient.new('http://localhost', 8080)
response = ehr_client.get_ehr_with(subject_id: patient_id, issuer: 'ICU_AI_Project')
logger.info "Status: #{response[:status]}, EHR ID: #{response[:ehr_id]}"
if response[:status] == 404
logger.info "create a new EHR"
response = ehr_client.create_ehr_with(subject_id: patient_id, issuer: 'ICU_AI_Project')
logger.info 'created EHR', response[:ehr_id]
logger.info response[:status]
end
logger.info 'post EHR to EHRbase'
# create VitalSign composition with EHR ID
vitalsign_message = VitalSignMessageFactory.create(hl7message)
logger.info vitalsign_message
response = ehr_client.post_composition(ehr_id: response[:ehr_id], format: 'FLAT', template_id: 'vital_signs', body: vitalsign_message)
logger.info "EHRbase post status: #{response[:status]}"
#patient_id_issuer = "JPN Kaisha"
end
socket.close
class VitalSignMessageFactory
def self.create(hl7message)
<<"END"
{
"vital_signs/language|code": "en",
"vital_signs/language|terminology": "ISO_639-1",
"vital_signs/category|code": "433",
"vital_signs/category|value": "event",
"vital_signs/category|terminology": "openehr",
"vital_signs/territory|code": "US",
"vital_signs/territory|terminology": "ISO_3166-1",
"vital_signs/composer|name": "Shinji Kobayashi",
"vital_signs/body_temperature/language|code": "en",
"vital_signs/body_temperature/language|terminology": "ISO_639-1",
"vital_signs/body_temperature/any_event:0/temperature|magnitude": #{hl7message.obx.records['8310-5'].value},
"vital_signs/body_temperature/any_event:0/temperature|unit": "Cel",
"vital_signs/body_temperature/location_of_measurement|code": "at0060",
"vital_signs/pulse_heart_beat/language|code": "en",
"vital_signs/pulse_heart_beat/language|terminology": "ISO_639-1",
"vital_signs/pulse_heart_beat/any_event:0/rate|magnitude": #{hl7message.obx.records['8867-4'].value},
"vital_signs/pulse_heart_beat/any_event:0/rate|unit": "/min",
"vital_signs/respiration/language|code": "en",
"vital_signs/respiration/language|terminology": "ISO_639-1",
"vital_signs/respiration/any_event:0/rate|magnitude": #{hl7message.obx.records['9279-1'].value},
"vital_signs/respiration/any_event:0/rate|unit": "/min",
"vital_signs/blood_pressure/language|code": "en",
"vital_signs/blood_pressure/language|terminology": "ISO_639-1",
"vital_signs/blood_pressure/any_event:0/systolic|magnitude": #{hl7message.obx.records['76534-7'].value},
"vital_signs/blood_pressure/any_event:0/systolic|unit": "mm[Hg]",
"vital_signs/blood_pressure/any_event:0/diastolic|magnitude": #{hl7message.obx.records['76535-4'].value},
"vital_signs/blood_pressure/any_event:0/diastolic|unit": "mm[Hg]",
"vital_signs/blood_pressure/location_of_measurement|code": "at1020",
"vital_signs/pulse_oximetry/language|code": "en",
"vital_signs/pulse_oximetry/language|terminology": "ISO_639-1",
"vital_signs/pulse_oximetry/any_event:0/spo|numerator": #{hl7message.obx.records['59408-5'].value},
"vital_signs/pulse_oximetry/any_event:0/spo|denominator": 100.0,
"vital_signs/pulse_oximetry/any_event:0/spo|type": 2,
}
END
end
end
-
Thomas Beale, FHIR compared to openEHR, https://wolandscat.net/2017/01/29/fhir-compared-to-openehr/ , accessed June 23, 2022, 日本語訳:FHIRとopenEHRの違いについて,https://openehr.jp/projects/translation-jp/wiki/FHIR%E3%81%A8openEHR%E3%81%AE%E9%81%95%E3%81%84%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6 ↩