9 ก.ค. 2022 เวลา 02:00 • วิทยาศาสตร์ & เทคโนโลยี
FHIR Server in action
ถ้าใครได้มีโอกาสไปใช้บริการโรงพยาบาลจะสังเกตได้ว่าในปัจจุบันสถานพยาบาลหลาย ๆ แห่งเริ่มจัดเก็บข้อมูลของผู้ป่วยในรูปแบบ Electronic มากขึ้น ไม่ต้องไปรื้อค้นหาแฟ้มประวัติให้วุ่นวายอีกต่อไปแล้ว ซึ่งระบบที่สถานพยาบาลใช้นี้มีชื่อว่า HIS หรือ Health Information System
แล้วถ้าสถานพยาบาลหนึ่งต้องการแลกเปลี่ยนข้อมูลกับสถานพยาบาลอีกที่หนึ่ง ระบบนี้จะสามารถทำได้หรือไม่?
คำตอบคือทำได้ ถ้า “HIS ของทั้งสองที่เป็นยี่ห้อเดียวกัน” ซึ่งแปลว่าถ้าต่างยี่ห้อกันแล้วระบบจำเป็นต้องมี “ตัวกลาง”​ เข้ามาช่วยเพื่อให้ทำงานได้อย่างราบรื่น
ซึ่งตัวกลางนั้นก็คือ FHIR (Fast Health Interoperability Resources) ในบทความนี้เราจะมาทำความเข้าใจกันว่า FHIR คืออะไร แล้วใช้ยังไง
FHIR Server in action
*บทความนี้เหมาะสำหรับผู้มีพื้นฐานด้านการพัฒนา software มาบ้าง เช่นรู้จัก Git รู้จัก Docker เป็นต้น
เกริ่นนำ
ในระยะหลังมานี้สถานพยาบาลในเมืองไทยจัดการกับข้อมูลผู้ป่วยในรูปแบบ electronic มากขึ้น ไม่ว่าจะเป็นสถานพยาบาลของรัฐหรือเอกชน สังเกตจากเวลาเราไปหาหมอตามโรงพยาบาลใหญ่ ๆ ฝ่ายลงทะเบียนจะไม่ถามหาบัตรผู้ป่วยของสถานพยาบาลอีกต่อไปแล้ว
แต่จะเป็นการถามหาบัตรประชาชนกับบัตรประกัน(ถ้ามี)แทน หลังจากนั้นพนักงานของแผนกลงทะเบียนก็จะป้อนข้อมูลเข้าสู่ระบบ HIS (Health Information System) เพื่อเพิ่มระเบียนการเข้าพบแพทย์ (Visit) และต่อจากนั้นระบบของสถานพยาบาลอาจจะออกบัตรคิวให้เราถือเพียงแผ่นเดียวเพื่อเดินไปยังแผนกต่าง ๆ
ถึงตรงนี้อาจมีคำถามในใจหลายท่านว่า “แล้ว FHIR คืออะไร? ทำไมเราถึงต้องใช้ FHIR?” ก่อนจะตอบคำถามนี้ผู้เขียนต้องขอวาดผังนักแสดงในละครเรื่องนี้กันสักเล็กน้อย
สถานพยาบาลที่ใช้ HIS
นี่คือภาพรวมของระบบ HIS ที่โรงพยาบาลหนึ่งตั้งขึ้นใช้งาน ผู้ป่วยเข้าพบบุคลากรทางการแพทย์ในแผนกต่าง ๆ และบุคลากรทางการแพทย์จึงเข้าใช้ระบบ HIS ผ่านเครื่อง HIS Client เพื่อจัดการข้อมูลผู้ป่วย จากนั้น HIS Sever จึงดำเนินการตามคำขอของ HIS Client
ถึงตรงนี้จะเกิดอะไรขึ้นถ้า สถานพยาบาลที่ใช้ HIS ยี่ห้อหนึ่งต้องการแลกเปลี่ยนข้อมูลกับสถานพยาบาลที่ใช้ HIS อีกยี่ห้อหนึ่ง ทุกอย่างจะไม่มีปัญหาถ้า โครงสร้างของฐานข้อมูลของ HIS สองยี่ห้อนั้นหน้าตาเหมือนกันชื่อผู้ป่วยถูกจัดเก็บไว้ในตาราง ชื่อเดียวกัน Columns ต่าง ๆ ใช้ชื่อเดียวกัน
ซึ่งในโลกแห่งความเป็นจริงนั้น HIS เกิดขึ้นมานานก่อนที่มาตรฐานข้อมูลสุขภาพในประเทศไทยจะเป็นรูปเป็นร่าง และ HIS แต่ละเจ้าก็ต่างคนต่างทำเป็นสูตรของใครก็ของเจ้านั้น ดังนั้นถ้าหากเราต้องการแลกเปลี่ยนกันได้ก็จำเป็นต้องมี “คนกลาง” ในการพูดคุยกันระหว่าง HIS ยี่ห้อ ก. และ HIS ยี่ห้อ ข.
ผู้เขียนขอกลับมาตอบคำถามว่า “FHIR คืออะไร?” FHIR (Fast Health Interoperability Resources) คือมาตรฐานข้อมูลสุขภาพซึ่งเขียนขึ้นโดยองค์กร HL7 คิดง่าย ๆ ก็คือ FHIR เป็น “คนกลาง”, “ล่าม” หรือ “วุ้นแปลภาษา” ให้ระบบที่มีโครงสร้างข้อมูลที่ต่างกันให้สื่อสารกันกายใต้โครงสร้างข้อมูล FHIR นั่นเอง
ทีนี้สำหรับคำถาม “ทำไมเราต้องใช้ FHIR?” ในประเทศไทยเริ่มมีการใช้มาตรฐานข้อมูลสุขภาพขึ้นมาบ้างแล้ว เริ่มมีบทความเกี่ยวกับเรื่องนี้ขึ้น เช่น บทความ “4 ขั้นตอนสู่การแลกเปลี่ยนข้อมูลสุขภาพ ด้วย HL7 FHIR” โดยอาจารย์รัฐ ปัญโญวัฒน์ หรือแม้แต่โครงการระดับประเทศอย่างโครงการ Health Link เองก็มีการนำเอา มาตรฐาน FHIR มาใช้เช่นกัน ดังนั้นอาจจะตอบแบบกำปั้นทุบดินก็ว่าได้ว่า “ผู้บุกเบิกในด้านนี้ในเมืองไทยลองใช้มาก่อน”
ลองเล่นกับไฟ (FHIR Server)
ได้อ่านสิ่งที่ผู้เขียนเกริ่นมาพอจะทำให้ผู้อ่านมีไฟในการลองเล่นกับ “ไฟ” หรือไม่ครับ? หากเป็นเช่นนั้นแล้วเรามาทดลองดูกันครับว่าเราทำอะไรกับมันได้บ้าง ก่อนที่ผู้อ่านจะสามารถลองเล่นกับไฟได้ ก็ต้องจุดไฟก่อนครับ (ไม่ใช่ไฟนั้นน ><) ขอโทษครับไม่ใช่ไฟแบบนั้น (แต่ FHIR ออกเสียงว่า “ไฟร์” จริง ๆ นะเออ) เอาเป็นว่าขอให้ติดตั้งเครื่องมือดังต่อไปนี้บนเครื่องคอมพิวเตอร์ของผู้อ่านให้เรียบร้อยครับ
  • Visual Studio Code ขอเรียกสั้น ๆ ว่า VSCode หรือ Code สำหรับใครที่ใช้ Windows ตอนติดตั้งให้เลือก Add “Open with Code” ทั้งสอง checkbox ด้วยนะครับ
  • Visual Studio Code Plugins: Remote Development & REST Client
  • Docker Desktop
  • Git/Git GUI อันนี้แล้วแต่ผู้อ่านเลือกเจ้าได้ตามใจชอบ ตัวอย่างเช่น Command-line, Sourcetree, GitHub Desktop
ไปเอา Source Code จากนักพัฒนา
หลังจากติดตั้งเครื่องมือด้านบนเรียบร้อยแล้ว ก็ถึงเวลาที่เราจะไปเอา Source Code จากนักพัฒนามาลองรันกัน โดยเจ้าที่ผู้อ่านจะแนะนำนั้นเป็นหนึ่งใน Software ที่พัฒนาให้เป็นไปตามมาตรฐาน FHIR มาอย่างยาวนานคือ HAPI FHIR เบื้องหลังของ Software ตัวนี้พัฒนาโดยใช้ Java (Springboot Framework) นะครับ ผู้อ่านสามารถเข้าไป Clone ได้จาก Repository นี้ ถ้าใช้ Command Line ก็คือ
> cd your/workspace/folder
หลังจาก clone มาแล้ว เปิดเข้าไปใน folder ควรมีหน้าตาแบบนี้ครับ
Folder ที่ได้จากการ Clone
สร้าง DevContainer
หลังจากที่เราเปิด Folder hapi-fhir-jpaserver-starter ขึ้นมาแล้วให้ Click ขวาเลือก Open with Code จาก Context Menu ครับ
Open with Code
จากนั้นใน VSCode สร้าง Folder ชื่อ .devcontainer ขึ้นมา
สร้าง Folder ชื่อ .devcontainer
ใน Folder .devcontainer ให้สร้างไฟล์ขึ้นมาสามไฟล์ดังต่อไปนี้
และภายใน Folder .vscode (ซึ่งมีอยู่แล้ว) ให้สร้างไฟล์เพิ่มอีกสองไฟล์ดังนี้
สรุปไฟล์ที่เกิดขึ้นใหม่ใน Project จะมีตามนี้ครับ
ไฟล์ที่เกิดขึ้นใหม่
สุดท้ายภายในไฟล์ pom.xml ให้เพิ่ม Code ต่อไปนี้
ลงไปใน project/build โดยผลลัพธ์หลังแก้ไขจะเป็นแบบนี้
แก้ไขไฟล์ pom.xml
เมื่อถึงตอนนี้เราสามารถเปิด VSCode เข้าสู่ DevContainer ได้แล้วโดยการ Click ปุ่ม Open a Remote Window มุมล่างซ้ายของ VSCode และเลือก Reopen in Container
ปุ่ม Open a Remote Window
เมนู Reopen in Container
ทดลองรัน FHIR Server บนเครื่อง Localhost
ภายหลังจากเปิด DevContainer ภายใน VSCode ขึ้นมาได้สำเร็จแล้ว ให้ผู้อ่านดูใน Tab Java Project และ หน้าต่าง VSCode Terminal ว่าเสร็จกระบวนการหรือยัง ขอ Internet Speed แรง ๆ ด้วยก็จะทำให้เสร็จเร็วขึ้นได้มากครับ
รอเปิด Project บน DevContainer
ตรงนี้ใช้เวลานานนิดนึงนะครับ สำหรับการเปิดครั้งแรก เพราะ VSCode จะไปควานหา Maven Dependency มาให้เรา หากใครพบปัญหาว่ามันทำงานไม่เสร็จสักทีหลังจากที่รอนานมาก ๆ (ตัวเลข xxx/1000 ไม่ขยับ อาจจะด้วยเพราะการเปิดครั้งก่อนหน้า crash หรือ Internet ถูกตัดกลางคัน) ผู้อ่านแนะนำให้ปิด VSCode แล้วเปิดบน DevContainer ใหม่อีกครั้ง
หลังจากผ่านกระบวนการ Importing Maven Project ไปแล้ว การเปิดครั้งต่อ ๆ ไปควรจะเร็วขึ้นอย่างเห็นได้ชัด เพราะใน .devcontainer ผู้เขียนได้ mount .m2 repository เป็น volume ไว้ให้แล้ว เว้นเสียแต่ผู้อ่านใช้คำสั่ง docker volume prune หลังจากปิด VSCode ไป (ทำให้ .m2 volume หายไปด้วย ต้องโหลดกันใหม่นาจา ><)
หลังจาก VSCode ทำงานเสร็จเราจะพบว่าหน้าต่าง VSCode มีรายการ Java Projects ปรากฏขึ้นมาดังนี้
รายการ Java Projects
ทีนี้เราก็พร้อมรันแล้วครับ ไปที่เมนู Debug and Run จากนั้นกดปุ่มรันได้เลยครับ
เมนู Debug and Run
จากนั้นรอ VSCode รัน Debug
รอ VSCode รัน Debug
พอรันสำเร็จจะได้หน้าตาแบบนี้ครับ
หลังจากรันสำเร็จ
พอกดปุ่ม Open in Browser ก็จะได้พบกับ FHIR Tester Page หน้านี้ครับ (สำหรับใครที่กดไม่ทันก็เปิด browser แล้วใส่ URL localhost:8080 แทนได้นะครับ)
FHIR Test Page
การรับส่งข้อมูลกับ FHIR Server
ถึงเวลาของผู้ช่วยของเราอีกหนึ่งตัวที่จะมาแสดงบทบาทเพื่อคุยกับ FHIR Server กันแล้วครับนั้นก็คือ VSCode Plugin REST Client ก่อนอื่นขอให้ผู้อ่านเปิด VSCode หน้าต่างใหม่ขึ้นมา (โดยแยกจาก DevContainer) นะครับ
เปิด VSCode หน้าต่างใหม่
หลังจากนั้นให้สร้างไฟล์ใหม่ขึ้นมาโดยเลือกประเภทเป็น HTTP ครับ อาจจะตั้งชื่อว่า sample_request.http ก็ได้ครับ จากนั้นให้ใส่ Code ต่อไปนี้ลงในไฟล์ ในที่นี้คือข้อมูลใบรับรองแพทย์จำลองเพื่อนำไปขอสอบใบขับขี่
sample_request.http
### CREATE
Content-Type: application/json
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"request": {
"method": "POST",
"url": "Composition"
},
"resource": {
"resourceType": "Composition",
"identifier": {
"use": "official",
"value": "MJFPXKW"
},
"status": "final",
"type": {
"coding": [
{
"system": "http://loinc.org",
"code": "11503-0"
}
]
},
"date": "2000-01-01",
"title": "Medical Certification Composition",
"author": [
{
"type": "Patient",
"reference": "urn:uuid:b1bb49cf-86c9-470b-915e-fbc2989d1205"
},
{
"type": "Practitioner",
"reference": "urn:uuid:ce6f92f1-e7fe-489b-b118-04d33f250ee7"
}
],
"section": [
{
"title": "patient",
"entry": [
{
"type": "Patient",
"reference": "urn:uuid:b1bb49cf-86c9-470b-915e-fbc2989d1205"
}
]
},
{
"title": "patient_section_item_1",
"entry": [
{
"type": "Condition",
"reference": "urn:uuid:77286c28-32b2-468f-a30e-b437b8f2b355"
},
{
"type": "Condition",
"reference": "urn:uuid:ea24a921-f516-45cb-b6e0-c5f2a6991601"
},
{
"type": "Condition",
"reference": "urn:uuid:79217720-3a23-4c2a-9719-1960c8d107cb"
}
]
},
{
"title": "item_2",
"entry": []
},
{
"title": "item_3",
"entry": []
},
{
"title": "item_4",
"entry": []
},
{
"title": "vital_signs",
"entry": [
{
"type": "Condition",
"reference": "urn:uuid:77286c28-32b2-468f-a30e-b437b8f2b355"
},
{
"type": "Condition",
"reference": "urn:uuid:77286c28-32b2-468f-a30e-b437b8f2b355"
}
]
},
{
"title": "clinical_findings",
"entry": [
{
"type": "Observation",
"reference": "urn:uuid:aae58675-37a0-43fb-966c-79f9053230ad"
},
{
"type": "Observation",
"reference": "urn:uuid:c71fa639-aeb9-44a1-aa87-21e91dcc5105"
},
{
"type": "Observation",
"reference": "urn:uuid:2332ce65-35f8-4277-86bd-02e57bed14cc"
}
]
},
{
"title": "doctor_opinion",
"entry": [
{
"type": "Observation",
"reference": "urn:uuid:8c0a6dd3-de36-4314-a66a-9338f62ce6de"
},
{
"type": "DiagnosticReport",
"reference": "urn:uuid:fee34d6e-4a78-4c73-8038-cefc0d0b9361"
}
]
}
]
}
},
{
"fullUrl": "urn:uuid:b1bb49cf-86c9-470b-915e-fbc2989d1205",
"request": {
"method": "POST",
"url": "Patient"
},
"resource": {
"resourceType": "Patient",
"identifier": [
{
"use": "official",
"value": "0123456789123"
}
],
"name": [
{
"prefix": "นางสาว",
"given": [
"รติกาล"
],
"family": "ทศรัตนธรรมกูล"
}
],
"gender": "female",
"address": {
"line": "123 หมู่ 4 ถนนสู่สรวงสวรรค์",
"district": "เมือง",
"state": "นครสวรรค์",
"postalCode": "12345",
"country": "ไทย"
}
}
},
{
"fullUrl": "urn:uuid:ce6f92f1-e7fe-489b-b118-04d33f250ee7",
"request": {
"method": "POST",
"url": "Practitioner"
},
"resource": {
"resourceType": "Practitioner",
"identifier": [
{
"use": "official",
"value": "012345"
}
],
"name": [
{
"prefix": "นพ.",
"given": [
"ปราการ"
],
"family": "ป้องบัลลังค์"
}
],
"gender": "male"
}
},
{
"fullUrl": "urn:uuid:77286c28-32b2-468f-a30e-b437b8f2b355",
"request": {
"method": "POST",
"url": "Condition"
},
"resource": {
"resourceType": "Condition",
"code": {
"text": "หอบหืด"
}
}
},
{
"fullUrl": "urn:uuid:ea24a921-f516-45cb-b6e0-c5f2a6991601",
"request": {
"method": "POST",
"url": "Condition"
},
"resource": {
"resourceType": "Condition",
"code": {
"text": "เบาหวาน"
}
}
},
{
"fullUrl": "urn:uuid:79217720-3a23-4c2a-9719-1960c8d107cb",
"request": {
"method": "POST",
"url": "Condition"
},
"resource": {
"resourceType": "Condition",
"code": {
"text": "ความดันสูง"
}
}
},
{
"fullUrl": "urn:uuid:9f605cc1-d39f-4000-89bf-8a5399b51f4c",
"request": {
"method": "POST",
"url": "Organization"
},
"resource": {
"resourceType": "Organization",
"identifier": [
{
"use": "official",
"value": "001234567"
}
],
"name": "โรงพยาบาลศรีธัญญา"
}
},
{
"fullUrl": "urn:uuid:aae58675-37a0-43fb-966c-79f9053230ad",
"request": {
"method": "POST",
"url": "Observation"
},
"resource": {
"resourceType": "Observation",
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "29463-7"
}
]
},
"valueQuantity": {
"value": 75.1,
"unit": "kg"
}
}
},
{
"fullUrl": "urn:uuid:c71fa639-aeb9-44a1-aa87-21e91dcc5105",
"request": {
"method": "POST",
"url": "Observation"
},
"resource": {
"resourceType": "Observation",
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8302-2"
}
]
},
"valueQuantity": {
"value": 167,
"unit": "cm"
}
}
},
{
"fullUrl": "urn:uuid:2332ce65-35f8-4277-86bd-02e57bed14cc",
"request": {
"method": "POST",
"url": "Observation"
},
"resource": {
"resourceType": "Observation",
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "85354-9"
}
]
},
"component": [
{
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8480-6"
}
]
},
"valueQuantity": {
"value": 107,
"unit": "mmHg"
}
},
{
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8462-4"
}
]
},
"valueQuantity": {
"value": 60,
"unit": "mmHg"
}
}
]
}
},
{
"fullUrl": "urn:uuid:8c0a6dd3-de36-4314-a66a-9338f62ce6de",
"request": {
"method": "POST",
"url": "Observation"
},
"resource": {
"resourceType": "Observation",
"code": {
"coding": [
{
"system": "medcer-system",
"code": "clinical-finding"
}
]
},
"effectiveDateTime": "2000-01-01",
"valueString": "เจ็บท้อง"
}
},
{
"fullUrl": "urn:uuid:fee34d6e-4a78-4c73-8038-cefc0d0b9361",
"request": {
"method": "POST",
"url": "DiagnosticReport"
},
"resource": {
"resourceType": "DiagnosticReport",
"conclusion": "ขับได้ =__= มือเดียว!"
}
}
]
}
จากนั้นกด Send Request
เมนู Send Request
จะพบหน้าต่างผลลัพธ์ปรากฏขึ้นคู่กันดังนี้
ผลลัพธ์ HTTP Response
นั่นหมายความว่าข้อมูลที่เราส่งเข้าไป ถูก FHIR Server เก็บไว้ในฐานข้อมูลเรียบร้อยแล้วครับ หากกลับไปยัง Browser แล้วไปยัง Resource ที่เราส่งข้อมูลเข้าไปจะพบระเบียนอยู่อย่างแน่นอน
FHIR Sample Resource
จากไฟล์ HTTP นั้นเราสามารถส่ง Request ประเภทอื่น เพื่อคุยกับ FHIR Server ได้เช่นกันครับ หากเราเพิ่ม Code ลงในไฟล์ sample_request.http ดังนี้
sample_request.http (continue)
หรือ
sample_request.http (continue)
### Generate Document
หรือ
sample_request.http (continue)
### UPDATE
Content-Type: application/json
{
"resourceType": "Patient",
"id": 2,
"identifier": [
{
"use": "official",
"value": "0123456789123"
}
],
"name": [
{
"prefix": "นางสาว",
"given": [
"เปลี่ยนชื่อ"
],
"family": "เปลี่ยนนามสกุล"
}
],
"gender": "female",
"address": {
"line": "123 หมู่ 4 ถนนสู่แดนดิน",
"district": "เมือง",
"state": "นครสวรรค์",
"postalCode": "12345",
"country": "ไทย"
}
}
ก็จะสามารถใช้เล่นกับไฟที่อยู่บน localhost ของเราได้ตามใจชอบ (และสบายใจ) ตัวอย่าง Request สามอันหลังผู้เขียนไม่ได้แสดงผลลัพธ์ไว้ให้ดูเพราะผู้เขียนหวังว่าผู้อ่านอาจจะลองไปทำต่อ แล้วพบผลลัพธ์ด้วยตัวเองครับ
ของแถม
ถ้าผู้อ่านต้องการให้ล้างข้อมูลใน Database ทั้งหมดทุกครั้งที่รัน เราสามารถสั่ง Config ได้ในไฟล์ src/main/resources/application.yaml ดังนี้ครับ
ปรับแต่ง hibernate
การกำหนดค่าเป็น hibernate.hbm2ddl.auto: create คือการสั่งให้ Hibernate (ORM ของ Java) ลบตารางเก่า(ถ้ามี)และสร้างตารางใหม่ขึ้นมาตาม model ที่อยู่ภายใน FHIR Library ครับ (ของเดิม hibernate.hbm2ddl.auto: update นั้นจะเป็น update schema ไม่ได้ลบตาราง)
สรุป
หากผู้อ่านเดินทางมาจนถึงจุดนี้ได้ ผู้เขียนเชื่อเหลือเกินว่าผู้อ่านคงจะมีความสนใจ FHIR Server เป็นแน่แท้ (หรือไม่ก็อาจจะด้วยภาระหน้าที่บางประการจึงทำให้ต้องอ่าน) ผู้เขียนคิดว่าผู้อ่านคงจะได้ประโยชน์บางอย่างจากบทความนี้ไปบ้างไม่มากก็น้อย ทั้งในแง่มุมของการใช้เครื่องมือ และในแง่มุมของการที่ได้ศึกษามาตรฐาน FHIR ขอขอบคุณแหล่งความรู้ต่าง ๆ ที่อยู่ตามลิงค์ในบทความนี้ และจะขาดไปไม่ได้คือขอขอบคุณผู้อ่านทุกท่านอย่างยิ่งที่อ่าน (และทำตาม) มาจนจบบทความนี้ครับ
เนื้อหาโดย ประณิธาน ธรรมเจริญพร
ตรวจทานและปรับปรุงโดย อิสระพงศ์ เอกสินชล
โฆษณา