Demo การแลกเปลี่ยนข้อมูลการสั่งยาผ่าน FHIR R4 – แบบ Basic

ช่วงนี้ผมเข้าไปมีส่วนร่วมกับโครงการต่าง ๆ ที่ใช้ HL7 FHIR เป็นมาตรฐานในการแลกเปลี่ยนข้อมูลค่อนข้างเยอะ แทบทุกโครงการต้องการแลกเปลี่ยนข้อมูลการสั่งยาครับ ผมเลยคิดว่าเดี๋ยวลองทำให้ดูดีกว่าว่าถ้าอยากแลกเปลี่ยนข้อมูลการสั่งยาผ่าน FHIR เราต้องทำอย่างไรบ้าง แบบทำตามแล้วใช้ได้จริง ๆ เลย แม้จะว่าเป็นขั้น basic ก็ตาม

ที่ผมเรียกว่าขั้น basic เพราะผมรู้สึกว่าถ้าจะทำให้สมบูรณ์จริง ๆ มันอาจต้องใช้ FHIR resource อื่น ๆ ที่ผมยังไม่รู้จัก หรือมีท่าแปลก ๆ ในการส่งข้อมูล มีการทำพวก conformance validation หรือมีการใช้พวกเครื่องมือ หรือ library ทั้งหลายเข้ามาช่วยในการพัฒนา ซึ่งสิ่งเหล่านั้นผมยังไม่มีความรู้เท่าไหร่เหมือนกันครับ แต่ผมว่าในเบื้องต้น ทำแบบที่ผมทำนี้ก็สามารถแลกเปลี่ยนได้เหมือนกันครับ

หลายท่านดูไปอาจจะรู้สึกว่าแค่แลกเปลี่ยนข้อมูลไม่กี่อย่างทำไมต้องทำให้ซับซ้อนขนาดนี้ คือหากเราจะทำระบบการแลกเปลี่ยนที่ยืดหยุ่นมากพอที่คนจำนวนมากจะเข้ามาใช้ด้วยได้ (FHIR เชื่อว่า 80% ของผู้มีส่วนได้ส่วนเสียใช้โครงสร้างแบบนี้ได้) แต่ละคนก็มีวิธีการเก็บข้อมูลของตนเอง มันก็ต้องแลกมาด้วยความซับซ้อนแบบนี้แหละครับ 😅 ถ้าเราออกแบบให้ simple มาก ๆ ก็จะไม่ตอบโจทย์ของหลายคน

เกริ่นมายาวแล้ว เดี๋ยวเริ่มกันเลยครับ


ก่อนเริ่ม: ติดตั้ง FHIR server

หากท่านใดยังไม่มี FHIR server ไว้ใช้ รบกวนติดตั้งก่อนค่อยทำตามนะครับ วิธีติดตั้งแบบง่าย ๆ ทำได้ดังนี้

ติดตั้ง Apache Maven ให้เรียบร้อยก่อน ถ้าติดตั้งแล้วตรวจสอบโดย

mvn -v

จะมีรายละเอียดขึ้นมา แสดงว่าติดตั้งเรียบร้อยดี จากนั้นติดตั้ง HAPI FHIR แบบ JPA Server (มี data storage ในตัว)

git clone https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git

ไปที่ directory ที่ clone มาแล้วสั่งรัน

mvn jetty:run

ใช้เวลาซักพัก เสร็จแล้วจะเข้าหน้า web admin ของ HAPI FHIR ได้ที่ http://localhost:8080/hapi-fhir-jpaserver/ ถ้า install ใน host อื่นที่ไม่ใช่เครื่องตัวเองก็เปลี่ยน path เป็น url ที่เราลงไว้

สังเกตุว่าอันนี้เป็น url สำหรับเข้า web admin แต่ url สำหรับใช้ HTTP method จะเป็น FHIR Base url คือ http://localhost:8080/hapi-fhir-jpaserver/fhir/

เกริ่นนำ

คือเวลาเราส่งข้อมูลไปที่ FHIR server จริง ๆ มันก็คือการส่ง JSON หรือ XML ไปตาม HTTP POST method นี่แหละครับ ดังนั้นเราก็เลยต้องมาเตรียม JSON สำหรับส่งกัน ซึ่ง JSON ทั้งหมดนี้จะเตรียมแยกกันแล้วส่งทีละอัน หรือรวมกันเป็น Bundle (FHIR resource ประเภทหนึ่ง) แล้วส่งครั้งเดียวก็ได้เช่นกัน ในที่นี้ขอทำแยกก่อนเพื่อสร้างความเข้าใจ resource ต่าง ๆ ครับ (หัวข้อ 1.) แต่โดยปกติแล้วเรามักจะส่งเป็น Bundle นะครับ (หัวข้อ 2. ด้านล่าง)

และทั้งหมดที่ผมทำนี้เป็นแค่ตัวอย่างที่มีข้อมูลพื้นฐานบางส่วนเท่านั้นนะครับ หากต้องการใส่ข้อมูลเข้าไปมากกว่านี้อาจดูใน official documentation ของ FHIR ประกอบว่ามี element ที่เราต้องการอยู่แล้วหรือยัง หรือไม่ก็ต้องทำเป็น extension ครับ

Coding และ CodeableConcept

ก่อนจะเริ่มทำเตรียม JSON กัน ผมอยากอธิบายเรื่องการใช้ Terminology ก่อนเล็กน้อยครับ เพราะว่ามีใช้เยอะมากใน FHIR และเวลาเราแลกเปลี่ยนข้อมูล ข้อมูลหลาย ๆ อย่างต้องเป็นค่าที่เราจำกัดไว้ให้เลือกจากตัวเลือกแค่จำนวนหนึ่ง ซึ่งการจะทำแบบนี้ เราใช้ data type ประเภท Coding และ CodeableConcept ครับ

Coding

ก็คือการกำหนดรหัสมาใช้แทนสิ่งต่าง ๆ อันนี้เรานึกถึงรหัสสินค้าของร้านค้าก็ได้ครับ สมมติเราจะเปิดร้านแล้วกำหนดรหัสแทนสินค้า เราก็ต้องมีข้อมูล 3 อย่างเป็นอย่างน้อย คือ 1) ชื่อร้าน ว่ารหัสเหล่านี้เป็นของร้านเรา 2) รหัสสินค้า 3) คำอธิบายรหัส ยกตัวอย่างเช่น 1) รหัสร้านของชำนายรัฐ 2) รหัส A001 ซึ่งหมายถึง 3) คอมพิวเตอร์ Macbook 1 เครื่อง เป็นต้น

อันนี้แหละครับหลักการของ data type ประเภท Coding ซึ่งประกอบด้วย 1) system ว่าเป็นระบบรหัสอะไร 2) code: รหัส 3) display: ค่าที่แสดง จริง ๆ ยังมี element อื่นอีกนะครับคือ version และ userSelected (บอกว่าค่านี้ user เลือกเองหรือไม่) แต่ไม่ต้องสนมากก็ได้

CodeableConcept

คือเวลาที่เราหมายถึงสิ่ง ๆ หนึ่ง เราอาจจะกำหนดรหัสมาแทนสิ่งเดียวได้หลายค่าครับ เช่น ระบบ A ให้รหัสสินค้า A ว่า A001 ส่วนระบบ B ให้รหัส B009 แม้ว่าทั้งสองระบบจะหมายถึงสิ่งเดียวกัน ซึ่งในทางการแพทย์มีอะไรแบบนี้เยอะมาก เช่น รหัสยา 24 หลัก กับ รหัส TMT หรือ รหัส ICD-10 กับ SNOMED-CT ส่วน condition เป็นต้น

CodeableConcept ก็คือการจับ Coding ของสิ่ง ๆ เดียวกันทุกค่ามาไว้ในก้อนเดียวกัน แล้วเพิ่ม element ชื่อว่า text ซึ่งเป็น plain text ไว้อธิบายสิ่ง ๆ นั้นครับ ลองดูตัวอย่างจาก FHIR official docs

"valueCodeableConcept": {
  "coding": [
    {
      "system": "http://snomed.info/sct",
      "code": "260385009",
      "display": "Negative"
    },
    {
      "system": "https://acme.lab/resultcodes",
      "code": "NEG",
      "display": "Negative"
    }
  ],
  "text": "Negative for Chlamydia Trachomatis rRNA"
}

เนื่องจากตาม FHIR core จริง ๆ ทุก element ใน CodeableConcept และ Coding เป็น optional หมดเลย คือไม่ใส่ก็ได้ ดังนั้นเวลาเราดูตัวอย่าง FHIR resource ที่อื่น เราอาจจะงง ๆ นิดว่าบางทีก็มาแบบมีแค่ text เลย บางทีก็มาแบบอีรุงตุงนัง อันนี้คือแล้วแต่การนำไปใช้ของแต่ละที่ครับ

มีประเด็นที่ผมอยากกล่าวเพิ่มอีกก็คือในการแลกเปลี่ยนข้อมูลกันจริง ๆ ไม่ว่าจะทำเป็น Profile หรือแค่ตกลงกันปากเปล่า เขามักจะกำหนดมาให้เรื่องหนึ่ง ๆ เราสามารถเลือก code ได้จากตัวเลือกที่กำหนดเท่านั้น (เลือกจาก data catalog ที่กำหนด) เพราะฉะนั้น พวก system, code, display เหล่านี้มักจะต้องมาตกลงร่วมกันก่อน

ในตัวอย่างด้านล่าง จะมีหลาย ๆ element ที่ผมระบุ ValueSet ตามที่ FHIR core แนะนำไปเลย เช่น ผมเขียนว่า “เลือกจากค่า phone | fax | email” ค่าพวกนี้ส่วนใหญ่เป็น Required คือต้องเลือกมาจากค่าใดค่าหนึ่งในตัวเลือกเหล่านี้ถึงจะถือว่าสอดคล้อง (comply) ตาม FHIR แต่บางอันอาจเป็น Extensible คือเขียนเพิ่มเองได้ ยังถือว่าสอดคล้องอยู่

เสริม: หากต้องการบังคับการเลือก code ผ่านทาง Profile อันนี้ต้องไปทำ ValueSet แล้วกำหนด terminology binding ให้ element ที่เราต้องการว่าเราอนุญาตให้ใส่ค่าจาก ValueSet นี้เท่านั้น จากนั้นเวลาเราสร้าง resource ขึ้นมา ระบบก็จะสามารถตรวจสอบความสอดคล้อง (conformance) ของ resource นั้นกับ Profile ได้ ถ้าเราใส่ค่าอื่นก็จะไม่ conform ก็อาจเขียนให้ระบบทำ action อื่น ๆ ต่อไป

ทีนี้ก็มาเริ่มเตรียม FHIR resource กันได้เลยครับ หากต้องการ download ตัวอย่าง code ทั้งหมด สามารถโหลดได้จาก Github repo ของผมครับ

ตัวอย่างข้อมูลที่เราจะส่ง

นายสมชาย ใจดี HN 62-5666 เลขประจำตัวประชาชน 1000000000999 เลขพาสปอร์ต X87660005X เบอร์มือถือ: 0889998888 เพศชาย เกิดวันที่ 3 มกราคม 1945 ที่อยู่ 300 ถ.พหลโยธิน เขตพญาไท จังหวัดกรุงเทพมหานคร 10400 ประเทศไทย สถานภาพ: โสด

มารับการตรวจที่โรงพยาบาลขอนแก่น 54 ถ.ศรีจันทร์ ต.ในเมือง อ.เมืองขอนแก่น จ.ขอนแก่น 40000 ประเทศไทย โทร. 043236974 โดยตรวจกับพญ.สมหญิง จริงใจ เพศหญิง เลขว. 99999 อีเมล: somying.jingjai@example.com

มารับการตรวจที่ OPD ได้ visit number: v6751 ได้พบพญ.สมหญิงระหว่างเวลา 7:01 น. ถึง 7:20 น. วันที่ 31 ส.ค. 2563 ณ เวลา 7:11 น. พญ.สมหญิงได้สั่งยา ARCOXIA 60 mg (MERCK) โดยให้กิน 1 เม็ด วันละ 1 ครั้ง จำนวน 30 เม็ด

1. ส่ง resource ทีละชนิด

ในการส่งข้อมูลเกี่ยวกับเรื่องยา เราจำเป็นต้องใช้ resource หลัก ๆ ทั้งหมด 5 ชนิดด้วยกันเป็นอย่างน้อยนะครับ ได้แก่

  1. Patient – ข้อมูลคนไข้
  2. Organization – ข้อมูลรพ.
  3. Practitioner – ข้อมูลแพทย์
  4. Encounter – ข้อมูลการ visit
  5. MedicationRequest – ข้อมูล order ยาที่สั่ง

1.1. Patient

ก็เริ่มที่ resource Patient ซึ่งเอาไว้เก็บข้อมูลทั่วไปและ demographic ของคนไข้ เตรียม JSON ที่มีโครงสร้างตามนี้ครับ

resourceType
ใส่ชื่อ resource นั้น ๆ กรณีนี้เป็น Patient จึงใส่ว่า “Patient” ครับ

identifier
ใส่ได้หลายค่า โดยแต่ละค่าต้องมี data type เป็น Identifier ซึ่งประกอบด้วย element หลัก ๆ คือ

identifier.use: เอาไว้บอกว่าตัว identify นี้ใช้ในสถานการณ์ไหน (ตัวเลือกเหล่านี้เป็น Required ดูรายละเอียดได้ที่ ValueSet: IdentifierUse) การใส่ค่าให้เลือกจากตัวเลือกเหล่านี้ หลัก ๆ ที่ใช้คือ official กับ usual

  • official: identifier ที่เชื่อถือได้มากที่สุดของ resource นี้ เรียกได้ว่าเป็น primary identifier
  • usual: เอาไว้ใช้งานโดยทั่ว ๆ ไป
  • temp: เอาไว้ใช้ชั่วคราว
  • secondary: อันนี้ผมไม่ค่อยเข้าใจ ตาม docs บอกว่าเอาไว้ใช้ในบางบริบท
  • old: identifier ที่ไม่ได้ใช้แล้ว

identifier.type
เป็น CodeableConcept จากในตัวอย่างของ FHIR core คือเขามี terminology ของประเภทการ identify คนไข้อยู่ ลองดูได้ที่ ValueSet: identifierType นี้ครับ (เป็น Extensible เขียนเพิ่มได้) ซึ่งจะเห็นว่ามี code และ display สำหรับการ identify คนไข้ประเภทต่าง ๆ แต่อาจจะเหมาะกับทางอเมริกามากกว่า ผมคิดว่าของไทยเราเองก็อาจต้องมาทำ terminology ของเราเองครับ

identifier.system
อันนี้เป็น uri (namespace) ของระบบการ identify นั้น ๆ คิดว่าอันนี้ก็ต้องมีการมากำหนดร่วมกันเช่นกัน เช่น ถ้า identify ด้วย HN ของรพ. ก็อาจใช้ url หนึ่งใน domain ของรพ.นั้นมาเป็น identifier

identifier.value
ก็คือค่าที่ใช้ identify ถ้าใช้บัตรประชาชนก็คือเลข 13 หลัก ถ้าใช้ HN ของรพ. ก็คือค่า HN

จาก element ข้างต้น ลองเอามาสร้าง JSON ได้ประมาณนี้ครับ

"identifier": [
  {
    "use": "official",
    "type": {
      "coding": [
        {
          "system": "http://thai.go.th/codesystem/patient-identifier",
          "code": "ID",
          "display": "Thai Identification Number"
        }
      ],
      "text": "เลขประจำตัวประชาชนไทย"
    },
    "system": "http://thai.go.th/thai-id",
    "value": "1000000000999"
  },
  {
    "use": "usual",
    "type": {
      "coding": [
        {
          "system": "http://thai.go.th/codesystem/patient-identifier",
          "code": "MR",
          "display": "Medical Record Number"
        }
      ],
      "text": "Medical Record Number"
    },
    "system": "http://rathhospital.com/hn",
    "value": "62-5666"
  },
  {
    "use": "usual",
    "type": {
      "coding": [
        {
          "system": "http://thai.go.th/codesystem/patient-identifier",
          "code": "PPN",
          "display": "Passport Number"
        }
      ],
      "text": "Passport Number"
    },
    "system": "http://thai.go.th/thai-passport",
    "value": "X87660005X"
  }
]

name
เป็น data type ประเภท HumanName ใส่ได้หลายค่าเช่นกันเพราะ 1 คนมีได้หลายชื่อ แต่สมมติเอาแค่คำนำหน้า ชื่อ นามสกุล แค่นี้ก่อนนะครับ ก็จะประกอบด้วย

  • name.use: คล้าย ๆ กับ identifier ครับ ใส่ official ไปเลยก็ได้ ส่วนค่าอื่น ๆ ดูได้จาก docs
  • name.family: นามสกุล
  • name.given: ชื่อ
  • name.prefix: คำนำหน้า
"name": [
  {
    "use": "official",
    "family": "ใจดี",
    "given": [
      "สมชาย"
    ],
    "prefix": [
      "นาย"
    ]
  }
]

telecom
เป็น data type ประเภท ContactPoint ถ้าใส่ข้อมูลแบบพื้น ๆ เลยก็จะประกอบด้วย

  • telecom.system: เลือกจาก phone | fax | email | pager | url | sms | other ทั้งหมดนี้นำมาจาก ValueSet: ContactPointSystem (Required)
  • telecom.value: เบอร์โทรศัพท์
  • use: เลือกจาก home (เบอร์บ้าน) | work (เบอร์ที่ทำงาน) | temp (เบอร์ชั่วคราว) | old (เบอร์ไม่ใช้แล้ว) | mobile (เบอร์มือถือ) ตัวเลือกเหล่านี้นำมาจาก ValueSet: ContactPointUse (Required)
"telecom": [
  {
    "system": "phone",
    "value": "0889998888",
    "use": "mobile"
  }
]

gender
เพศ ที่คนไข้อยากให้บันทึก อาจไม่ตรงกับ biological sex ตัวเลือกเลือกมาจาก ValueSet: AdministrativeGender (Required) ซึ่งประกอบด้วย male | female | other | unknown

"gender": "male",

birthDate
วันเกิด อันนี้ตรงตัว ซึ่ง default format เป็น YYYY-MM-DD

address
เป็น data type ประเภท Address ซึ่งหากใส่ข้อมูลสำคัญ ๆ ก็จะประกอบด้วย

  • address.line: บ้านเลขที่ ถนน และอื่น ๆ
  • address.city: อำเภอ/แขวง
  • address.state: จังหวัด
  • address.postalCode: รหัสไปรษณีย์
  • address.country: เลือกจาก ISO 3166 ได้ทั้ง 2 และ 3 ตัวอักษร สำหรับไทยคือ TH และ THA
"address": [
  {
    "line": [
      "300 ถ.พหลโยธิน"
    ],
    "city": "พญาไท",
    "state": "กรุงเทพมหานคร",
    "postalCode": "10140",
    "country": "TH"
  }
]

ถ้าอยากใส่ latitude, longitude ก็เพิ่ม extension แบบนี้

"address": [
  {
    "extension": [
      {
        "url": "http://hl7.org/fhir/StructureDefinition/geolocation",
        "extension": [
          {
            "url": "latitude",
            "valueDecimal": 41.62853299580235
          },
          {
            "url": "longitude",
            "valueDecimal": -70.26833403203315
          }
        ]
      }
    ],
    "line": [
      "300 ถ.พหลโยธิน"
    ],
    "city": "พญาไท",
    "state": "กรุงเทพมหานคร",
    "postalCode": "10140",
    "country": "TH"
  }
]

maritalStatus
เป็น CodeableConcept เลือกเอาจาก ValueSet: Marital Status Codes (Extensible) หากไม่เคยสมรสก็ใส่ค่าแบบนี้ ส่วนตรง text แล้วแต่เราจะใส่ ใช้ display มาใส่ก็ได้

"maritalStatus": {
  "coding": [
    {
      "system": "http://terminology.hl7.org/CodeSystem/v3-MaritalStatus",
      "code": "S",
      "display": "Never Married"
    }
  ],
  "text": "Never Married"
}

จาก structure ด้านบน ลองทำเป็น Patient resource ในรูปแบบ JSON ได้ดังนี้ครับ ดาวน์โหลดเอาเลยได้ครับ

เรานำข้อมูลใน json นี้ไปใส่ใน body แล้วส่ง POST request ไปที่ <FHIR base ของเรา>/Patient โดยตั้ง Content-Type เป็น application/fhir+json ก็จะสร้าง patient resource ขึ้นมาได้สำเร็จ ให้จด ID ที่ HAPI ให้กับ instance ของเราไว้ครับ จากในภาพได้ ID เป็น 302

ถ้าเราลองส่ง GET request ไปที่ <FHIR base ของเรา>/Patient/302 เราก็จะได้ข้อมูลคนไข้ที่เราส่งเข้าไปเมื่อกี้มา

หลายท่านอาจสงสัยว่า ถ้าแบบนี้เรากำหนด ID ไว้ล่วงหน้าก่อนจะ POST เข้าไปเลยได้ไหม จริง ๆ ได้นะครับแต่ผมว่าแอบลำบาก วิธีการทำคือต้องสร้างด้วย PUT method แทนที่จะเป็น POST และ id นั้นต้องมีตัวอักษรอย่างน้อย 1 ตัวครับ อันนี้แล้วแต่ config ของ server ครับ บาง server ก็ไม่อนุญาตให้สร้าง resource ด้วย PUT ครับ สำหรับ HAPI โดย default แล้วอนุญาต แต่มีเงื่อนไขเรื่องตัวอักษรที่ผมกล่าวไป

1.2. Organization

resource Organization นี้ก็เอาไว้ใส่ข้อมูลของสถานพยาบาลแหละครับ ส่วนตัวผมว่าไม่ต้องใส่อะไรเยอะก็ได้ แต่ต้องมีเพื่อจะได้ทราบว่าเป็นข้อมูลจากสถานพยาบาลไหน ซึ่งโดยรวมแทบจะเหมือน Patient เลยครับ ผมขอรวบทำตัวอย่างทีเดียวเลย มีสิ่งที่ต่างอยู่ดังต่อไปนี้ครับ

เสริม: จริง ๆ ถ้าเราอยากระบุเป็นแผนกแทนที่จะเป็นทั้งรพ. เราก็ใช้ resource นี้เหมือนกันนะครับ แต่อันนี้เพื่อความง่ายขอเป็นแทนรพ.ก่อน

identifier
อันนี้ต้องมาสรุปว่าเราจะระบุรพ.ในประเทศได้อย่างไร เหมือนทางภาครัฐเขาจะมี hospital code อยู่แล้วนะครับ

active
เป็น boolean (true/false) บอกว่า organization นี้ยัง active อยู่ไหม ก็มักจะใส่ว่า true เป็นหลัก

type
เป็น CodeableConcept เลือกจาก ValueSet: OrganizationType ซึ่งถ้าเป็นสถานพยาบาลก็ต้องเป็น code: prov, display: Healthcare Provider

รวม ๆ แล้วสมมติรพ.ขอนแก่น ก็จะเป็นประมาณนี้ครับ

{
  "resourceType": "Organization",
  "identifier": [
    {
      "system": "http://demo.thai-hospital-database.go.th",
      "value": "KK0001"
    }
  ],
  "active": true,
  "type": [
    {
      "coding": [
        {
          "system": "http://terminology.hl7.org/CodeSystem/organization-type",
          "code": "prov",
          "display": "Healthcare Provider"
        }
      ],
      "text": "Healthcare Provider"
    }
  ],
  "name": "โรงพยาบาลขอนแก่น",
  "telecom": [
    {
      "system": "phone",
      "value": "043236974"
    }
  ],
  "address": [
    {
      "line": [
        "54 ถ.ศรีจันทร์ ต.ในเมือง"
      ],
      "city": "เมืองขอนแก่น",
      "state": "ขอนแก่น",
      "postalCode": "40000",
      "country": "TH"
    }
  ]
}

ดาวน์โหลด

แล้ว POST ใส่ <FHIR base ของเรา>/Organization ก็จะสร้างเสร็จเรียบร้อยครับ อย่าลืมจด id ที่ระบบสร้างไว้ให้

ได้กลับมาเป็น 303 ครับ เพราะสร้างต่อจาก 302 เมื่อกี้ (FHIR server จะไล่ ID ไปเรื่อย ๆ ไม่ว่าจะ resource ไหน)

1.3. Practitioner

Practitioner อันนี้ก็คล้าย ๆ Organization ครับ ไม่ต้องใส่เยอะก็ได้ ในเบื้องต้นน่าจะเอาไว้อ้างอิงเป็นหลัก

identifier
เช่นกันครับ ต้องมาสรุปว่าเราจะ identify หมอกันอย่างไร อาจใช้เลขว.

อย่างอื่นเหมือน ๆ อันอื่นครับ จริง ๆ ผมว่าไม่ต้องใส่เยอะก็ได้ ประมาณนี้ยังได้เลยครับ

{
  "resourceType": "Practitioner",
  "identifier": [
    {
      "system": "http://thai-medical-council.or.th/doctor-id",
      "value": "99999"
    }
  ],
  "active": true,
  "name": [
    {
      "family": "จริงใจ",
      "given": [
        "สมหญิง"
      ],
      "prefix": [
        "พญ."
      ]
    }
  ],
  "telecom": [
    {
      "system": "email",
      "value": "somying.jingjai@example.com",
      "use": "work"
    }
  ],
  "gender": "female"
}

ดาวน์โหลด

แล้ว POST ใส่ <FHIR base ของเรา>/Practitioner ก็จะสร้างเสร็จเรียบร้อยครับ อย่าลืมจด id ที่ระบบสร้างไว้ให้

ได้ ID มาเป็น 304

1.4. Encounter

3 resource ด้านบนเป็นสิ่งที่อยู่โดด ๆ ได้ แต่ Encounter นี่แหละครับที่จะเชื่อมทั้งสามอันไว้ด้วยกัน Encounter เป็น resource ที่เอาไว้บันทึกการพบกันระหว่างผู้ป่วยและผู้ให้บริการทางการแพทย์ (healthcare provider) ซึ่งจะมีข้อมูลที่เพิ่มหรือปรับเปลี่ยนจาก resource อื่น ๆ ดังนี้

identifier
เนื่องจาก encounter มันจะเป็นการเก็บ record การมา visit สถานพยาบาลแต่ละครั้งของคนไข้ อันนี้ผมว่าใช้ system เป็น local code ของรพ.นั้น ๆ ก็น่าจะได้ครับ แต่อันนี้ก็แล้วแต่ตกลงกันเลยนะครับ

status
เลือกจาก ValueSet: EncounterStatus (Required) ซึ่งประกอบด้วย

  • planned: encounter นั้นอยู่ในแผน แต่ยังไม่เกิดขึ้น
  • arrived: คนไข้มาถึงแล้ว แต่ยังไม่เจอ provider
  • triaged: ผ่านการคัดกรองความรุนแรงแล้ว
  • in-progress: หมอ (หรือ practitioner อื่น) กำลังตรวจ
  • onleave: เริ่มตรวจไปแล้ว แต่คนไข้ไม่อยู่ชั่วคราว
  • finished: เสร็จสิ้น encounter นั้นแล้ว
  • cancelled: ยกเลิกก่อนพบแพทย์
  • entered-in-error: ลงข้อมูลผิดพลาด
  • unknown: ไม่ทราบ

ในสถานการณ์การแลกเปลี่ยนของบ้านเรา finished น่าจะใช้บ่อยที่สุดครับ

class
การจำแนก encounter ซึ่งใส่ค่าโดยการเลือก code มาจาก ValueSet: ActEncounterCode (Extensible) ประกอบด้วยหลายค่าอยู่ แต่ที่เราน่าจะได้ใช้กันก็คือ

  • AMB (ambulatory): ก็คือการมาตรวจ OPD แหละครับ
  • EMER (emergency): ตรวจห้องฉุกเฉิน (ER)
  • IMP (inpatient encounter): admit เป็นผู้ป่วยใน (IPD)
  • OBSENC (observation encounter): อยู่ห้อง observe

จริง ๆ ยังมีค่าอื่น ๆ อีกนะครับ และ ValueSet นี้เป็น Extensible คือเราเขียนเพิ่มได้

type, serviceType, priority
พวกนี้ผมว่าใส่ก็ดี แต่ก็ไม่ถึงกับจำเป็นครับ

ลองเอาค่าด้านบนมาใส่ JSON เบื้องต้นจะได้ตัวอย่างประมาณนี้นะครับ

"resourceType": "Encounter",
"identifier": [
  {
    "use": "official",
    "system": "http://www.rathhospital.com/identifiers/encounters",
    "value": "v6751"
  }
],
"status": "finished",
"class": {
  "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
  "code": "AMB"
}

subject
หลัก ๆ ก็คือคนไข้แหละครับ (แต่ใน FHIR spec จริง ๆ เขียนว่าใส่กลุ่มของคนไข้ก็ได้) อันนี้เป็น element ประเภท Referenceซึ่งเราต้อง reference ไปที่คนไข้ที่เราเคย POST ไปก่อนหน้านี้ วิธี reference มีหลายวิธี แต่ผมว่าวิธีที่ผมทำเป็นมีแค่ Reference ด้วย URL คือไม่ว่าจะ absolute (ใส่มาเต็ม ๆ ทั้ง FHIR base path) หรือ relative (ไม่ต้องใส่ base path มา) วิธีการ reference แบบนี้เรียกว่า Literal References

ตัวอย่างเช่นหากคนไข้ที่เรา POST ไปก่อนหน้านี้ได้ ID กลับมาเป็น 302 (นี่คือสาเหตุที่ผมให้จดไว้ครับ) เวลา reference ก็จะเป็นประมาณนี้ครับ

"subject": {
    "reference": "Patient/302",
    "display": "นาย สมชาย ใจดี"
  }

ส่วน display ไม่มีก็ได้ครับ แต่เป็น plain text เอาไว้เผื่อได้ใช้ประโยชน์ครับ

ทีนี้บางท่านอาจมีคำถามว่าแล้วถ้าเราส่งข้อมูลเป็น Bundle ที่ใส่ทุกอย่างเข้าไปหมดแล้วส่งครั้งเดียว เราจะ reference อย่างไร อันนี้มีวิธีทำอยู่ครับคือใส่ UUID ไปก่อน พอ create resource แล้ว HAPI จะ convert เป็น ID ให้เราครับ เดี๋ยวผมทำให้ดูในส่วน Bundle ด้านล่างครับ

participant
คือ healthcare provider ที่มีส่วนเกี่ยวข้องกับการให้บริการคนไข้ที่เป็น subject หลัก ๆ แล้วก็คือหมอเจ้าของไข้แหละครับ แต่ถ้าเราอยากใส่ เราสามารถใส่หมอที่รับปรึกษา, หมอที่ admit, พยาบาลที่ดูแล, นักกายภาพบำบัด, ฯลฯ แล้วแต่เราได้เลย ซึ่งข้อมูลของ participant ก็จะประกอบด้วย

  • participant.type: เป็น CodeableConcept เลือกจาก ParticipantType (เป็น Extensible เขียนเพิ่มได้) หลัก ๆ ก็คือ primary performer หรือหมอเจ้าของไข้
  • participant.period: จริง ๆ อันนี้ไม่ใส่ก็ได้ คือเวลาที่ participant ท่านนั้นมีส่วนร่วม เช่น เวลาที่เริ่มตรวจจนตรวจเสร็จ เป็น data type ประเภท  Period ซึ่งประกอบด้วย start กับ end ซึ่งเป็น data type  dateTime ตาม ISO format
  • participant.individual: ก็คือการระบุตัวตน participant คนนั้น เป็นการ reference ไปที่ Practitioner ที่เราสร้างมาก่อนหน้านี้ (จากตัวอย่างคือได้ ID 304)

ตัวอย่างส่วนหนึ่งของ JSON

"participant": [
  {
    "type": [
      {
        "coding": [
          {
            "system": "http://terminology.hl7.org/CodeSystem/v3-ParticipationType",
            "code": "PPRF",
            "display": "primary performer"
          }
        ],
        "text": "primary performer"
      }
    ],
    "period": {
      "start": "2020-08-31T07:20:00+07:00",
      "end": "2020-08-31T07:01:00+07:00"
    },
    "individual": {
      "reference": "Practitioner/304",
      "display": "พญ. สมหญิง จริงใจ"
    }
  }
]

period
เช่นเดียวกับ period ของ participant แต่อันนี้คือ period ที่เกิด encounter นั้นขึ้น จริง ๆ ก็ไม่ได้จำเป็นเท่าไหร่

reasonCode
เหตุผลที่เกิด encounter นี้ขึ้น เป็น CodeableConcept ทาง FHIR core แนะนำให้ใช้ code จาก SNOMED-CT มาใช้ แต่เนื่องจากบ้านเราไม่มี license ผมว่าอันนี้ข้ามก็ได้ครับ หรือไม่ก็ใส่ free text

element อื่น ๆ ที่ผมว่าอาจต้องใส่ในบางบริบท

  • diagnosis: การวินิจฉัย
  • hospitalization: ข้อมูลเกี่ยวกับ admission
  • location: สถานที่ที่ subject ไปในระหว่างที่เกิด encounter

พวกนี้ลองดูเพิ่มเติมใน FHIR documentation ดูครับ

serviceProvider
เป็น Reference ไปยัง Organization ที่เราสร้างก่อนหน้านี้ ซึ่งจากตัวอย่างเราได้ ID 303

"serviceProvider": {
  "reference": "Organization/303",
  "display": "โรงพยาบาลขอนแก่น"
}

นำข้อมูลทุกอย่างที่เกี่ยวข้องมาประกอบกัน สมมติใช้ไฟล์ที่ผมเตรียมไว้ให้ก็ดาวน์โหลด

จากนั้นเปลี่ยน ID ของ Patient, Practitioner, Organization ให้ถูกต้อง แล้ว POST ใส่ <FHIR base ของเรา>/Encounter ก็จะสร้างเสร็จเรียบร้อยครับ อย่าลืมจด id ที่ระบบสร้างไว้ให้

ในที่นี้ได้ ID มาเป็น 305 และก็มาถึง resource สุดท้าย

1.5. MedicationRequest

MedicationRequest เป็น resource ที่เอาไว้บันทึกการสั่งยาที่ทาง Practitioner สั่งแก่คนไข้ครับ resource นี้เราต้องสร้าง 1 instance ต่อรายการยา 1 ชนิดครับ

โดยมี element ที่สำคัญ ๆ ดังนี้

identifier
อันนี้ก็เหมือน identifier ของ resource อื่น ๆ ครับ ก็คือรหัสประจำ order item นี้ (คนละอันกับรหัสยานะครับ) ก็แล้วแต่ที่เลยว่าจะใส่ identifier อย่างไร หรือจะไม่ใส่ก็ได้

status
เป็นสถานะของ order ยานี้ เลือกค่ามาจาก ValueSet: medicationrequest Status (Required) โดยตัวเลือกประกอบด้วยหลายค่า แต่ปกติเราก็เลือกแค่ active หรือ completed

  • active: order ยานี้สามารถปฏิบัติตามได้ (แต่ไม่ได้หมายความว่าปฏิบัติไปหมดแล้ว)
  • on-hold: order ยานี้ให้หยุดไว้ก่อน แต่เดี๋ยวจะมาสั่งให้ทำอยู่
  • cancelled: ยกเลิก order นี้ ไม่ต้องทำแล้ว
  • completed: ทำสิ่งที่ระบุไว้ใน order หมดเรียบร้อย
  • draft: order นี้เป็นการร่าง ยังไม่ต้องให้ยาจริง
  • unknown: เราไม่ทราบสถานะของ order
  • อื่น ๆ คือ entered-in-error และ stopped ไม่ค่อยแน่ใจว่าคืออะไร

intent
บอกจุดประสงค์ของ order นี้ ตัวเลือกมาจาก ValueSet: medicationRequest Intent (Required) ปกติเราก็เลือกแค่ plan หรือว่า order ครับ

  • plan: เป็นแผนว่าจะให้ยานี้ มีโอกาสสูงที่จะเกิดขึ้นจริง
  • order: เป็น order จริงที่สั่งแล้ว
  • ค่าอื่น ๆ ดูได้จากใน Docs

category, priority มีก็ดี แต่ผมว่ายังไม่ต้องมีก็ได้

medication[x]
ระบุว่า order นี้เป็นการสั่งยาอะไร การมีเครื่องหมาย [x] ที่ด้านหลัง หมายความว่าเลือกใช้ data type ได้หลายแบบ ในกรณีนี้คือเลือกได้ระหว่าง

  • medicationCodeableConcept: เป็น CodeableConcept คือการเลือกจากรหัส อาจเป็นรหัส local code หรือรหัสมาตรฐาน เช่น TMT แล้วแต่เรา
  • medicationReference: Reference คือ reference ไป Medication resource ซึ่งผมยังไม่ได้กล่าวถึงในบทความนี้ ซึ่งก็จะเพิ่มความซับซ้อนอีกนิด ขอละไว้ก่อนครับ

ดังนั้นกรณีนี้ขอใช้ CodeableConcept ก่อน โดยใช้ TMT และใช้ fully specified name (FSN) เป็น display และ text (ขอสมมติ system ให้ TMT เลยนะครับ)

จากข้างต้น เราอาจสร้างส่วนหนึ่งของ JSON ได้แบบนี้

"resourceType": "MedicationRequest",
"identifier": [
  {
    "use": "official",
    "system": "http://www.rathhospital.com/identifiers/medicatonRequest",
    "value": "o6751"
  }
],
"status": "active",
"intent": "order",
"medicationCodeableConcept": {
  "coding": [
    {
      "system": "http://www.this.or.th/tmt",
      "code": "120496",
      "display": "ARCOXIA (MERCK, U.S.A.) (etoricoxib 60 mg) film-coated tablet, 1 tablet (TPU)"
    }
  ],
  "text": "ARCOXIA (MERCK, U.S.A.) (etoricoxib 60 mg) film-coated tablet, 1 tablet (TPU)"
}

ส่วนต่อมาเป็นพวกข้อมูลอ้างอิง

  • subject: reference ไปคนไข้ที่เราสร้างก่อนหน้านี้ (ในที่นี้ได้ ID 302)
  • encounter: reference ไป encounter ที่เราสร้างก่อนหน้านี้ (ในที่นี้ได้ ID 305)
  • requester: reference ไปหมอที่เราสร้างก่อนหน้านี้ (ในที่นี้ได้ ID 304)
  • performer: คนที่บริหารยา เช่น พยาบาล (ในที่นี้ขอตัดออกก่อน)
  • recorder: คนบันทึก order นี้ (ในที่นี้ขอตัดออกก่อน)

authoredOn
อันนี้เป็นเวลา (dateTime) ที่สั่ง order นี้

จับกลุ่มนี้มาใส่ JSON ก็จะได้ประมาณนี้

"subject": {
  "reference": "Patient/302",
  "display": "นาย สมชาย ใจดี"
},
"encounter": {
  "reference": "Encounter/305"
},
"authoredOn": "2020-08-31T07:11:00+07:00",
"requester": {
  "reference": "Practitioner/304",
  "display": "พญ. สมหญิง จริงใจ"
}

dosageInstruction
เป็น data type ประเภท Dosage เอาไว้ระบุวิธีการใช้ยานี้ครับ FHIR ค่อนข้างใส่ข้อมูลส่วนนี้ได้ซับซ้อนพอสมควรครับ ก่อนหน้านี้ผมเคยอธิบายเรื่องนี้ไว้ใน blog นี้ครับ ท่านใดอยากอ่านรายละเอียดเต็ม ๆ ลองอ่านที่ blog นะครับ ครั้งนี้ขอแบบสรุป ๆ

dosageInstruction.sequence
คือ order ยา 1 รายการ สามารถมีวิธีใช้ยาได้หลายแบบ เช่น กินวันละ 2 เม็ด ทุกวันไป 4 วัน แล้วเหลือ 1 เม็ดวันเว้นวันไปจนหมด แบบนี้แสดงว่ามี 2 sequence ครับ แต่ปกติยาโดยทั่วไปก็มี sequence เดียว และ CPOE ในไทยส่วนใหญ่ก็ไม่ได้มีระบบ sequence อยู่แล้ว

dosageInstruction.timing
เอาไว้ระบุเวลาที่จะใช้ยา ซึ่งระบุได้หลายวิธีมาก

  • frequency และ period: เช่น 4 ครั้ง (frequency) ต่อวัน (period)
  • when และ offset: เช่น กินทุกเช้าก่อนอาหาร (when) 60 นาที (offset)
  • daysOfWeek, timeOfDay: เช่น กินทุกวันพุธ (daysOfWeek) กินทุกสองทุ่ม (timeOfDay)
  • duration, bounds, count: เป็นเรื่องระยะเวลา
    • duration เช่น แปะผิวหนังนาน 8 ชม.
    • bound เช่น กินยานี้ไป 7 วัน
    • count เช่น กินยานี้ไป 3 ครั้ง

dosageInstruction.asNeeded
เอาไว้ระบุว่ายานี้เป็นยา PRN หรือไม่ ถ้าใส่เป็น boolean ก็ใช้ asNeededBoolean ถ้าอยากใส่รายละเอียด เช่น กินเมื่อมีอาการไข้ แบบนี้ต้องใส่เป็น asNeededCodeableConcept ซึ่ง FHIR core เลือก bind กับ SNOMED-CT เช่นกัน ผมว่าตอนนี้ก็ใช้ boolean ไปก่อน

dosageInstruction.route
เป็น CodeableConcept ใช้ระบุเส้นทางการให้ยา เช่น กินทางปาก, ฉีดทางหลอดเลือด FHIR core เลือก bind กับ SNOMED-CT (แบบ Example คือไม่ต้องใช้ก็ได้) ของไทยเราคงต้องมากำหนด terminology เอง

dosageInstruction.doseAndRate
ใช้ระบุขนาดของยา หรืออัตราการให้ยา ซึ่งระบุได้หลายวิธี

  • ระบุเป็น dose: ถ้าระบุเป็นช่วง เช่น 1-2 เม็ด ใช้ doseRange แต่ถ้าระบุเป็นค่าเลย เช่น 30 mg หรือ 1 เม็ด ใช้ doseQuantity
  • ระบุเป็น rate: rateRatio เช่น 30 ml/hour (ใช้ตัวตั้งตัวหาร), rateRange เช่น 1-2 litres per minute, rateQuantity 30ml/hour (ใช้ค่าหน่วยคงที่)

ไล่มาซะยาว ถ้าเราจะสั่ง Arcoxia 60 mg 1 เม็ดต่อวัน จริง ๆ มันสั้น ๆ เองนะครับ FHIR แค่เตรียมไว้ให้เราเผื่อเราอยากซับซ้อน

"dosageInstruction": [
  {
    "sequence": 1,
    "timing": {
      "repeat": {
        "frequency": 1,
        "period": 1.0,
        "periodUnit": "d"
      }
    },
    "asNeededBoolean": false,
    "doseAndRate": [
      {
        "doseQuantity": {
          "value": 60.0,
          "unit": "milligram",
          "code":"mg",
          "system":"http://unitsofmeasure.org"
        }
      }
    ]
  }
]

หรือถ้าแบบทำ structured data ไม่ไหวจริง ๆ ใส่มาเป็น free text เลยครับ อย่างน้อยดีกว่าไม่มีอะไร และจริง ๆ ในบางสถานการณ์ free text ก็อาจจะเพียงพอแล้วนะครับ (ผมว่าขนาดยา 60 mg นี่จริง ๆ ไม่ใส่ยังได้เลยครับ เพราะรู้รหัส TMT ก็รู้ขนาดอยู่แล้ว)

"dosageInstruction": [
  {
    "sequence": 1,
    "text": "1 tab oral OD"
  }
]

dispenseRequest
เป็นข้อมูลว่าจะจ่ายยาให้คนไข้อย่างไร ซึ่งก็มีรายละเอียดของเขา แต่ว่าสนใจแค่ว่าให้ไปกี่เม็ดก็ได้ครับ ซึ่งได้แก่

dispenseRequest.initialFill
เลือกใส่ข้อมูลได้ 2 แบบ คือใส่เป็นค่าคงที่ quantity (30 เม็ด) หรือใส่เป็นช่วง duration (10 วัน)

สมมติเราจะให้ Arcoxia เมื่อกี้ไป 30 เม็ด ก็จะเป็นแบบนี้ (สมมติเราไม่ระบุระบบ code ของ unit เลย)

"dispenseRequest": {
  "initialFill": {
    "quantity": {
      "value": 30.0,
      "unit": "tablet"
    }
  }
}

นำทุกอย่างมาประกอบร่างกัน ก็จะได้เป็นไฟล์ JSON เหมือนไฟล์

นี้ครับ สามารถดาวน์โหลดแล้ว POST ใส่ <FHIR base ของเรา>/MedicationRequest ก็จะสร้างเสร็จเรียบร้อยครับ (อย่าลืมแก้พวกที่ reference ไป resource อื่นนะครับ)

MedicationRequest นี้ 1 instance ต่อ 1 รายการยาครับ สมมติในวันที่เขามารพ. เขาได้ยากลับไป 10 ตัว ก็ต้องทำ MedicationRequest นี้ 10 instance ครับ (จริง ๆ เหมือนจะมีวิธี grouping บางอย่างนะครับ แต่ยังทำไม่เป็นเหมือนกัน)

คือทั้งหมดนี้ดูเหมือนเยอะนะครับ แต่จริง ๆ ถ้าเราสร้าง tool ขึ้นมาครั้งเดียวในการดึงข้อมูลจาก HIS มาแล้วมาแปลงเป็น JSON เหล่านี้แล้วค่อย POST ไปที่ FHIR server มันก็เป็นกระบวนการที่พอจะ automate ได้อยู่ครับ

ทั้งหมดที่ผมเขียนมาข้างบน อันนี้เพื่อความเข้าใจเฉย ๆ นะครับ ปกติเวลาเราส่ง FHIR resource จริง ๆ เรามักจะส่งรวดเดียวเป็น Bundle มากกว่าครับ ซึ่ง Bundle นี้ก็ auto-generated มาจากระบบที่ดึงข้อมูลมาจาก HIS เช่นกัน

2. การสร้าง Bundle สำหรับส่งข้อมูลทั้งชุดในครั้งเดียว

Bundle จริง ๆ ก็เป็น FHIR resource ประเภทหนึ่งครับ ใช้สำหรับส่งข้อมูลแบบเป็นชุด จริง ๆ มีอยู่หลาย type แต่ในที่นี้เราจะใช้ type ที่เรียกว่า transaction ในการส่งข้อมูลครับ

วิธีการทำ Bundle จริง ๆ ก็ไม่ยากครับ เราต้องจัดโครงสร้างตามนี้ ซึ่ง entry ก็คือแต่ละ resource ที่เราจะส่ง โดยต้องเพิ่มข้อมูล fullUrl และ request เข้าไปแนบหัวท้าย

{
  "resourceType": "Bundle",
  "type": "transaction",
  "entry": [
    {
      "fullUrl": "urn:uuid:GUID",
      "resource": {"resource ที่เราจะส่ง"},
      "request": {
        "method": "POST หรือ PUT",
        "url": "url ปลายทาง"
      }
    }
  ]
}

fullUrl
อย่างที่ผมกล่าวไปหัวข้อ 1. ถ้าเราสร้าง resource ทีละอัน เราจะได้ ID มาเพื่อใช้ reference ใน resource อันถัดไปได้ใช่ไหมครับ แต่ถ้าเราจะสร้างรวดเดียว เราไม่รู้ล่วงหน้าว่า resource ที่เราส่งไปจะได้ ID อะไรกลับมา ดังนั้นเราจึงต้องใส่ ID ชั่วคราวไปก่อน เวลา reference ก็ใช้ ID นี้

สมมติเรา generate UUID ของคนไข้ แล้วใส่ใน fullUrl เป็น “urn:uuid:32e9ae3e-6e6f-4d60-803c-437a7ceb2805” (ในอินเตอร์เน็ตมี tool สำหรับสร้าง GUID/UUID เยอะครับ เช่น Online GUID/UUID Generator)

เช่น จากเดิมเรา reference แบบนี้

"subject": {
    "reference": "Patient/302",
    "display": "นาย สมชาย ใจดี"
  },

เราก็เปลี่ยนเป็นแบบนี้แทนครับ

"subject": {
    "reference": "urn:uuid:32e9ae3e-6e6f-4d60-803c-437a7ceb2805",
    "display": "นาย สมชาย ใจดี"
  },

ทำแบบนี้กับทุก resource ใน Bundle ที่มีการ reference

จะ POST หรือจะ PUT ใน request ของแต่ละ entry

ที่ผ่านมาถ้าเราจะสร้าง resource instance ผมใช้ POST ตลอดใช่ไหมครับ จริง ๆ เราสามารถสร้างด้วย PUT ได้ ปัญหาของการสร้างด้วย POST ก็คือ ถ้าเราส่ง POST request ไป server จะสร้าง instance ใหม่ขึ้นมาเรื่อย ๆ แม้จะเป็นข้อมูลชุดเดิม เช่น ส่งข้อมูลของนายสมชายไป 10 ครั้ง เราก็จะมี Patient instance นี้อยู่ 10 ID ซึ่งก็คงเป็นปัญหาอยู่ครับ

แต่หากเราสร้างโดยใช้ PUT ถ้าส่งไปแล้วเคยเจอ resource นี้สร้างมาก่อนแล้ว Server จะไปอัพเดทของเดิมแทนที่จะสร้างใหม่ครับ ทำให้ส่งไป 10 ครั้ง เราก็มีอยู่แค่ ID เดียว

คำถามคือแล้ว server จะทราบได้อย่างไรว่าเคยมี resource instance นั้นสร้างไว้หรือยัง คำตอบก็คือเราต้องใส่ identifier เข้าไปใน URL ครับ วิธีการนี้เรียกว่าการใส่ token parameter ซึ่งมี syntax ดังนี้

<FHIR base ของเรา>/[Resource]?[parameter]=[system]|[code]
เช่น
http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient?identifier=http://thai.go.th/thai-id|1000000000999

หากเราเขียนเป็น PUT request แบบนี้ ก็จะแปลว่า หาคนไข้ที่มีเลขประจำตัวประชาชน 1000000000999 จากใน server ก่อน ถ้าหาพบก็อัพเดท ถ้าไม่พบก็สร้างใหม่

"request": {
  "method": "PUT",
  "url": "http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient?identifier=http://thai.go.th/thai-id|1000000000999"
}

หลัก ๆ ก็มีเท่านี้เองครับวิธีการสร้าง Bundle หากเรานำ resource ทุกอย่างที่เคยสร้างไว้มาต่อกันเป็น Bundle เราก็จะได้แบบนี้ครับ (อาจต้องลองโหลดมานั่งไล่ code ตามนะครับจะเห็นภาพ)

เสร็จแล้วเราสามารถ POST ใส่ <FHIR base ของเรา> เช่น http://localhost:8080/hapi-fhir-jpaserver/fhir ได้เลยครับ (ตอนส่ง Bundle ใช้ POST นะครับ) ก็จะสร้างเสร็จเรียบร้อยครับ

สำหรับคณะทำงาน: สรุป ValueSet ที่ทุกฝ่ายต้องตกลงกันใน scenerio นี้

  • Patient.identifier.type – ประเภทของการ identify ที่ใช้ เช่น ถ้าใช้บัตรประชาชนจะลงรหัสว่าอะไร, passport, ประกันสังคม, บัตรอื่น ๆ
  • Patient.identifier.system – สมมติแค่บัตรประชาชนนี่เราจะเรียก system การ identify นี้ว่าอะไร ใช้ URL ของกระทรวงมหาดไทยได้หรือไม่
  • Patient.maritalStatus – รหัสสถานภาพสมรส จะใช้ของ HL7 เลยหรือจะทำเอง
  • Organization.identifier – วิธีการ identify สถานพยาบาลในไทย ใช้ system ใด รหัสใด
  • Organization.type – อันนี้อาจใช้ของ HL7 ได้ เพราะค่อนข้างครอบคลุม
  • Practitioner.identifier – วิธีการ identify แพทย์ อาจใช้เลขว. ได้ แต่ต้องมาตกลงกันเรื่องชื่อ system เช่น ใช้เว็บของแพทยสภาได้หรือไม่
  • Encounter.class – อาจใช้ของ HL7 ได้
  • Encounter.participant.type – อาจใช้ของ HL7 ได้
  • MedicationRequest.medicationCodeableConcept – หากใช้ TMT จะใช้ที่ granularity ใด หรือจะอนุญาตให้ผสม granularity ได้ และจะใช้อะไรมาเป็น display name ใช้ FSN จะยาวไปหรือไม่
  • หน่วยนับของยา – เช่น capsule, tablet ฯลฯ อนุญาตให้ใช้คำได้ได้บ้าง จะใช้ชื่อ system ว่าอย่างไร

จะเห็นว่าแค่แลกเปลี่ยนเรื่องเล็ก ๆ อย่างมาตรวจได้ยากลับไปกิน 1 ตัว ยังต้องมีเรื่องที่ต้องตกลงกันตั้งเยอะนะครับ นี่ยังไม่ได้ไปถึงพวกส่งค่าแล็บ ส่งผลการวินิจฉัย ส่งการแพ้ยา เลยครับ


สำหรับวันนี้ก็จบแล้วนะครับ อยากที่กล่าวไปข้างต้นว่าอันนี้เป็นแค่วิธีการแลกเปลี่ยนข้อมูลแบบพื้นฐานเท่านั้น ถ้าทำแบบ advance น่าจะต้องมี resource อื่น ๆ ที่ผมยังไม่รู้จักมาเกี่ยวอีกพอสมควรครับ แต่ในเบื้องต้นสำหรับการทำพวก prototype ผมว่าเท่านี้น่าจะใช้งานได้ครับ

และก็เวลาทำโปรแกรมเพื่อการ generate code จริง ๆ ผมยังรู้สึกว่าถ้าเอาพวก library สำเร็จรูปที่มีใน community มาใช้ น่าจะทำให้เรา dev พวกนี้ได้ง่ายขึ้นนะครับ แต่ผมก็ไม่รู้จักเหมือนกันครับ

มีข้อเสนอแนะ สอบถาม หรือแลกเปลี่ยนกันอย่างไร ติดต่อผมได้นะครับ 😄 ขอบคุณครับ

Leave a Reply

Your email address will not be published. Required fields are marked *