# Docker 建置具有 PAS IG 驗證的 HAPI FHIR Server ![docker-建置具有PAS IG驗證的hapi-fhir-server](https://hackmd.io/_uploads/BksBLBKLlg.png) # 前言 目前 FHIR 正在持續發展,但筆者自認大家普遍都遇到官方或網路提供的驗證器驗證的很完整,結果在自己的機器建置時,卻遇到不知道如何匯入 IG (ImplementationGuide 實作指引)或 Terminology (專門術語)的問題,這份文件最終將架設出一台可以正確驗證 PAS IG 範例的 HAPI FHIR 教學涵括以下內容: - 架設 HAPI FHIR - 匯入 IG - 匯入 Terminology - 特殊案例 # 前置條件 - 具備 docker 的機器 - 基本的程式知識 - 基本的作業系統操作知識 - 擁有 JAVA 以及 postman 的機器 (與架設的機器分開) - 認真思考的腦袋以及誠摯的心 :::info 以下教學所使用的作業系統為 debian 12 ::: # Docker Compose 架設 HAPI FHIR ## 準備 IG 檔案 在設定之前,我們需要先準備 IG 檔案以方便我們後續進行設定。 ### 如何知道要準備哪些 IG 的檔案 在下載主 PAS IG 前,因為 IG 其實還會依賴其他 IG,所以我們需要先知道要準備哪些 IG 的檔案。 - 首先,打開 [PAS IG 的網站](https://nhicore.nhi.gov.tw/pas/),將頁面滾到最下方開啟 QA Report 網頁 ![image](https://hackmd.io/_uploads/rkw_WrKLxx.png) - 找到 `Dependency Checks`區塊,旁邊會列出依賴的 IG,通常來說準備第一層的 IG 依賴檔案即可,另外 `terminology.r4` 因為實際上好像不怎麼影響,所以可以不用準備 ![image](https://hackmd.io/_uploads/By-qbSt8gx.png) ### 下載 IG 檔案 以下是 PAS IG 需要的 IG 檔案 (`package.tgz`),如果不放心的話,可以從自行官方下載 :::info 通常 IG 檔案都會在 IG 網站的結構定義與範例檔下載或Downloads 頁面找到 ::: - PAS: https://nhicore.nhi.gov.tw/pas/package.tgz - TWCore: [tw.gov.mohw.twcore-0.3.2.tgz](https://1drv.ms/u/c/79252f41717afb63/EZeGhVikVp9NtmvkGOO3Fu0BdupRYIrCpR0sAXEw-nGSRA?e=Iu8rm5) - 目前`CodeSystem-icd-10-cm-2023-tw.json`與`CodeSystem-icd-10-pcs-2023-tw.json` 有點問題,這邊一樣提供自製檔給大家下載 - 更改步驟: - 解壓縮 twcore 的 package 檔案 - 找到 `CodeSystem-icd-10-cm-2023-tw.json` 和`CodeSystem-icd-10-pcs-2023-tw.json`,把裡面的 url 數值中間的 ValueSet 改成 CodeSystem,e.g. `https://twcore.mohw.gov.tw/ig/twcore/ValueSet/icd-10-pcs-2023-tw` 改成 `https://twcore.mohw.gov.tw/ig/twcore/CodeSystem/icd-10-pcs-2023-tw` - 更改完畢後放到原本的 package 檔案中 - davinci-pas: [hl7.fhir.us.davinci-pas-2.1.0.tgz](https://1drv.ms/u/c/79252f41717afb63/EVmFA5P8nYtFoULX4xDdnxkBn3D_o0chzyXI_p9WMK746g?e=XJGy6d) - 不知為何,在官網找不到 package.tgz 檔案,所以這邊自製 tgz,提供大家下載 - 自製步驟如下: - 使用 npm 下載 package 的內容 ```bash npm --registry https://packages.simplifier.net install hl7.fhir.us.davinci-pas@2.1.0 ``` - 將下載的 `hl7.fhir.us.davinci-pas` 資料夾用 7z 壓縮成 tar 檔案,壓縮後再使用 gzip進行一次壓縮 :::warning 下載的檔案,請記得都放在當前目錄的 data 資料夾裡面 ::: ## 準備 application.yaml `application.yaml`檔案是 HAPI FHIR 的設定檔,因為後續要將設定檔映射到 Docker 容器內,所以我們先準備設定檔 - 建立資料夾 ```bash mkdir configs ``` - 進入 `configs` 資料夾當中 - 新增檔案 `application.yaml` :::info 以下設定檔是精簡過的,如果要官方完整範例請參見 [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml) ::: :::danger 注意! 以下的 `datasource.password` 為範例,生產環境請使用更有強度的密碼 ::: ```yaml! #Uncomment the "servlet" and "context-path" lines below to make the fhir endpoint available at /example/path/fhir instead of the default value of /fhir server: port: 8080 #Adds the option to go to eg. http://localhost:8080/actuator/health for seeing the running configuration #see https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints management: endpoint: endpoints: enabled-by-default: false web: exposure: include: health,prometheus health: enabled: true probes: enabled: true livenessState: enabled: true readinessState: enabled: true prometheus: enabled: true metrics: export: enabled: true spring: main: allow-circular-references: true flyway: enabled: false baselineOnMigrate: true fail-on-missing-locations: false datasource: #url: 'jdbc:h2:file:./target/database/h2' #url: jdbc:h2:mem:test_mem url: 'jdbc:postgresql://postgres:5432/hapi' username: hapifhir password: hapifhir driverClassName: org.postgresql.Driver max-active: 15 # database connection pool size hikari: maximum-pool-size: 10 jpa: properties: hibernate.format_sql: false hibernate.show_sql: false hibernate.dialect: ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgresDialect ### These settings will enable fulltext search with lucene or elastic hibernate.search.enabled: true hapi: fhir: ### This flag when enabled to true, will avail evaluate measure operations from CR Module. ### Flag is false by default, can be passed as command line argument to override. cr: enabled: false cdshooks: enabled: false ### This enables the swagger-ui at /fhir/swagger-ui/index.html as well as the /fhir/api-docs (see https://hapifhir.io/hapi-fhir/docs/server_plain/openapi.html) openapi_enabled: true ### This is the FHIR version. Choose between, DSTU2, DSTU3, R4 or R5 fhir_version: R4 ### Flag is false by default. This flag enables runtime installation of IG's. ig_runtime_upload_enabled: false ### enable to set the Server URL server_address: http://localhost:8080/fhir # 要匯入的 IG 設定區塊 implementationguides: twcore: name: tw.gov.mohw.twcore version: 0.3.2 reloadExisting: false installMode: STORE_AND_INSTALL packageUrl: file:///data/tw.gov.mohw.twcore-0.3.2.tgz davinci-pas: name: hl7.fhir.us.davinci-pas version: 2.1.0 reloadExisting: false installMode: STORE_AND_INSTALL packageUrl: file:///data/hl7.fhir.us.davinci-pas-2.1.0.tgz pas: name: tw.gov.mohw.nhi.pas version: 1.0.5 reloadExisting: false installMode: STORE_AND_INSTALL packageUrl: file:///data/tw.gov.mohw.nhi.pas-1.0.5.tgz ################################################## # Allowed Bundle Types for persistence (defaults are: COLLECTION,DOCUMENT,MESSAGE) ################################################## allowed_bundle_types: COLLECTION,DOCUMENT,MESSAGE,TRANSACTION,TRANSACTIONRESPONSE,BATCH,BATCHRESPONSE,HISTORY,SEARCHSET allow_cascading_deletes: true allow_contains_searches: true allow_external_references: true allow_multiple_delete: true allow_override_default_search_params: false advanced_lucene_indexing: false bulk_export_enabled: false bulk_import_enabled: false # language_search_parameter_enabled: true # enforce_referential_integrity_on_delete: false # This is an experimental feature, and does not fully support _total and other FHIR features. # enforce_referential_integrity_on_delete: false # enforce_referential_integrity_on_write: false # etag_support_enabled: true # expunge_enabled: true client_id_strategy: ANY # server_id_strategy: SEQUENTIAL_NUMERIC # fhirpath_interceptor_enabled: false # filter_search_enabled: true # graphql_enabled: true narrative_enabled: false mdm_enabled: false mdm_rules_json_location: "mdm-rules.json" # local_base_urls: # - https://hapi.fhir.org/baseR4 logical_urls: - http://terminology.hl7.org/* - https://terminology.hl7.org/* - http://snomed.info/* - https://snomed.info/* - http://unitsofmeasure.org/* - https://unitsofmeasure.org/* - http://loinc.org/* - https://loinc.org/* # partitioning: # allow_references_across_partitions: false # partitioning_include_in_search_hashes: false cors: allow_Credentials: true # These are allowed_origin patterns, see: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/cors/CorsConfiguration.html#setAllowedOriginPatterns-java.util.List- allowed_origin: - '*' # Search coordinator thread pool sizes search-coord-core-pool-size: 20 search-coord-max-pool-size: 100 search-coord-queue-capacity: 200 tester: home: name: Local Tester server_address: 'http://localhost:8080/fhir' refuse_to_fetch_third_party_urls: false fhir_version: R4 global: name: Global Tester server_address: "http://hapi.fhir.org/baseR4" refuse_to_fetch_third_party_urls: false fhir_version: R4 validation: requests_enabled: true # responses_enabled: true # binary_storage_enabled: true inline_resource_storage_below_size: 4000 ``` ### 匯入 IG 設定說明 在上面的設定檔當中,可以找到 `hapi.fhir.implementationguides`區塊的設定,此區塊就是用於設定你的 HAPI FHIR 在啟動時需要匯入哪些 IG。如果從官方的範例參考的話你會看到這樣的設定 ```yaml # 官方範例 # 第一種 swiss: name: swiss.mednet.fhir version: 0.8.0 reloadExisting: false installMode: STORE_AND_INSTALL # 第二種 example not from registry ips_1_0_0: packageUrl: https://build.fhir.org/ig/HL7/fhir-ips/package.tgz name: hl7.fhir.uv.ips version: 1.0.0 ``` 第一種範例是從官方 IG registry 抓取,而第二種方式是提供網址讓 HAPI FHIR 判斷並下載。 筆者自己比較喜歡自己想到的第三種方式,**用本地檔案匯入**,這樣不只可以保持最新,且避免網路問題,更加提高使用的穩定性。 ## 準備 docker-compose.yaml 檔案內容如下: :::danger 注意! 以下的 `POSTGRES_PASSWORD` 為範例,生產環境請使用更有強度的密碼 ::: ```yaml! services: db: container_name: postgres image: postgres:16-alpine restart: always volumes: - ./db:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: hapifhir POSTGRES_USER: hapifhir POSTGRES_DB: hapi fhir: container_name: hapi image: gitlab-registry.dicom.tw/a5566qq123/hapi-fhir-jpaserver-starter:v7.4.0 user: root ports: - "8080:8080" volumes: - ./configs/:/configs - ./data/:/data depends_on: - db environment: SPRING_CONFIG_LOCATION: 'file:///configs/application.yaml' entrypoint: ["java", "-Xmx8G", "--class-path", "/app/main.war", "-Dloader.path=main.war!/WEB-INF/classes/,main.war!/WEB-INF/,/app/extra-classes", "org.springframework.boot.loader.PropertiesLauncher"] ``` 你可以發現,裡面所使用的 image 是 `gitlab-registry.dicom.tw/a5566qq123/hapi-fhir-jpaserver-starter:v7.4.0`,這是因為 PAS 有一些特殊的 ValueSet 沒辦法被 expand,而自製的 image,原因可以參考這篇「[修正 HAPI FHIR ValueSet 無法正常 Expand 特殊符號的 Code](https://hackmd.io/@chinlinblog/BJUlT_CH1x?utm_source=preview-mode&utm_medium=rec)」 ## 啟動 - 進到擁有 `docker-compose.yaml` 檔案的目錄並輸入以下指令啟動 HAPI FHIR :::info 這裡沒有使用 -d 參數是因為方便查看 log ::: ```bash! sudo docker compose up ``` - 啟動後,觀察 log 應該就可以看到關於安裝 IG 的訊息囉 ![image](https://hackmd.io/_uploads/SkZkmrY8ex.png) :::warning 匯入 IG 過程中,建議不要進行任何操作,在最後會有很多的 Saved 1000 deferred 的 LOG 訊息,請等待到沒看到這類的訊息之後再進行操作 ::: ## 小驗證 在進行匯入 terminology 的動作前,我們先進行 HAPI FHIR 驗證功能狀態是否正常運作。 ### 驗證 Patient patient 作為最常用的 resource,當然要來驗證一下 - 首先打開 postman,網址輸入 `http://${baseUrl}/fhir/Patient/$validate`,其中`${baseUrl}` 請替換成自己的 HAPI FHIR Server 的基本網址 - Method 選擇 POST ![image](https://hackmd.io/_uploads/BysbXBKIeg.png) - 下面的 tab 切換成 Body,格式改成 raw 選擇 JSON ![image](https://hackmd.io/_uploads/B1JXQHYIgg.png) - 下面黑框貼上 [PAS IG 提供的 Patient 範例](https://nhicore.nhi.gov.tw/pas/Patient-pat-min.json) ```json { "resourceType" : "Patient", "id" : "pat-min", "meta" : { "profile" : ["https://nhicore.nhi.gov.tw/pas/StructureDefinition/Patient-twpas"] }, "text" : { "status" : "generated", "div" : "<div xmlns=\"http://www.w3.org/1999/xhtml\"><div style=\"display: inline-block; background-color: #d9e0e7; padding: 6px; margin: 4px; border: 1px solid #8da1b4; border-radius: 5px; line-height: 60%\"><p style=\"margin-bottom: 0px\">Profile: <a href=\"StructureDefinition-Patient-twpas.html\">病人資訊-Patient TWPAS</a></p></div><blockquote><p><b>識別碼型別</b>:National Person Identifier <span style=\"background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki\">( <a href=\"http://terminology.hl7.org/CodeSystem/v2-0203\">Identifier Type Codes</a>#NNxxx)</span><br/><b>身分證字號(official)</b>:A123456789 (http://www.moi.gov.tw)</p></blockquote><blockquote><p><b>識別碼型別</b>:Medical record number <span style=\"background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki\">( <a href=\"http://terminology.hl7.org/CodeSystem/v2-0203\">Identifier Type Codes</a>#MR)</span><br/><b>病歷號(official)</b>:123456 (https://tpech.gov.taipei)</p></blockquote><p><b>姓名(usual)</b>:王大明</p><p><b>性別</b>:男</p><p><b>出生日期</b>:2001-01-01</p></div>" }, "identifier" : [{ "use" : "official", "type" : { "coding" : [{ "system" : "http://terminology.hl7.org/CodeSystem/v2-0203", "code" : "NNxxx" }] }, "system" : "http://www.moi.gov.tw", "value" : "A123456789" }, { "use" : "official", "type" : { "coding" : [{ "system" : "http://terminology.hl7.org/CodeSystem/v2-0203", "code" : "MR" }] }, "system" : "https://tpech.gov.taipei", "value" : "123456" }], "name" : [{ "use" : "usual", "text" : "王大明" }], "gender" : "male", "birthDate" : "2001-01-01" } ``` - 點擊 Send 送出 - 驗證完畢後,就會收到 200 OK 的訊息,就代表 Patient 沒問題,且 HAPI FHIR 有正確匯入 IG 啦 ![image](https://hackmd.io/_uploads/BJOHXrFIll.png) ### 驗證 Claim 因為 [PAS IG 提供的 Claim 範例](https://nhicore.nhi.gov.tw/pas/Claim-cla-1.json)中,有 CodeSystem 為 `https://twcore.mohw.gov.tw/ig/twcore/CodeSystem/icd-10-pcs-2023-tw` 的資料,可以間接驗證我們匯入的 TWCore IG 的 icd-10 code 是否正常 :::warning 如果出現驗證失敗,且在錯誤訊息當中有看到 ICD-10 值集相關的內容,請確認 CodeSystem/icd-10-cm-2023-tw 與 CodeSystem/icd-10-pcs-2023-tw 的 url 是否正常,你可以透過 postman 用 GET http://${baseUrl]/fhir/CodeSystem/icd-10-cm-2023-tw 查看 url 數值 ::: - 前面步驟與驗證 Patient 一樣,所以下面只貼出驗證結果 ![image](https://hackmd.io/_uploads/SJKoXrK8lx.png) ### 驗證 Observation 以下使用的是[檢驗檢查-單項的 Observation 範例](https://nhicore.nhi.gov.tw/pas/Observation-obs-diagnostic-min.json)**,**因為它涵蓋從 LOINC 來的 Code,比較好做匯入 Terminology 之後的比較 - 與前面的步驟都相同,下面只附上驗證結果 - 你可以發現,這次的結果為驗證失敗 (422),因為我們還沒匯入 LOINC Code,HAPI FHIR 的索引認不出來導致失敗了 ![image](https://hackmd.io/_uploads/H1xaQBYLll.png) # 匯入 Terminology ## 下載 HAPI FHIR CLI 在匯入 Terminology 之前,我們要先去下載匯入工具 HAPI FHIR CLI - 進到 [HAPI FHIR 的 release 頁面](https://github.com/hapifhir/hapi-fhir/releases/tag/v8.2.1) - 下載 [**hapi-fhir-8.2.1-cli.zip](https://github.com/hapifhir/hapi-fhir/releases/download/v8.2.1/hapi-fhir-8.2.1-cli.zip)** 檔案 ![image](https://hackmd.io/_uploads/SJ1AQSF8lx.png) - 下載完畢後,進行解壓縮 ![image](https://hackmd.io/_uploads/HyqCXHtIxe.png) ## 下載 LOINC Code :::info 請先自行創建帳號,並登入 ::: - 進到 LOINC Code [下載頁面](https://loinc.org/downloads/) - 點擊 Download 圖示 ![image](https://hackmd.io/_uploads/Hy3xNrK8lg.png) - 點擊後,進去看規章並按下載 :::warning 本教學只涵蓋 LOINC Code 的匯入,因 ICD-10 TWCore 已經有內建在 package,而 SNOMED 正常須為會員國或收費才能使用 ::: ## 準備 loincupload.properties 檔案 - 創建 `loincupload.properties` 檔案,內容如下 ```json loinc.hierarchy.file=AccessoryFiles/ComponentHierarchyBySystem/ComponentHierarchyBySystem.csv loinc.codesystem.version=2.80 ``` ## 進行匯入 ```json .\hapi-fhir-cli upload-terminology -d ${loinc-zip} -d ${loinc-properties} -v r4 -t http://${baseUrl}/fhir -u http://loinc.org -s 10GB ``` - `${loinc-zip}`: 為上一步下載的 loinc zip 壓縮檔的路徑 - `${loinc-properties}`: 為上一步驟創建的 loinc.properties 檔案路徑 - `${baseUrl}`: 你的 HAPI FHIR 的基本網址,e.g. localhost:8080 - 出現以下訊息,就代表在匯入中囉 ![image](https://hackmd.io/_uploads/HyoG4SYIgl.png) - 等到出現 Upload Complete 就代表匯入完成囉! ![image](https://hackmd.io/_uploads/rkr7EBFLlg.png) - 請注意,雖然 Terminology 匯入完成,但 HAPI FHIR 會在背景執行將 Code 進行索引,請等待 c.u.f.j.term.TermDeferredStorageSvcImpl 這類的處理訊息不再跳出為止在操作 ![image](https://hackmd.io/_uploads/S18E4HKLxl.png) :::info 索引過程需要大量時間,請耐心等待 ::: - 最後出現 Saved xxxxxxx concepts in hh:mm:ss 就代表索引完成囉 ![image](https://hackmd.io/_uploads/BkiI4rKUgl.png) ## 再度驗證 我們一樣使用[驗證 Observation](https://www.notion.so/Observation-2356d2290ebc807fa0b7c11985fde043?pvs=21) 章節的[檢驗檢查-單項的 Observation 範例](https://nhicore.nhi.gov.tw/pas/Observation-obs-diagnostic-min.json)進行驗證 - 驗證後,可以發現驗證通過囉! ![image](https://hackmd.io/_uploads/r1Xd4rY8ee.png) - 如果把中間的 777-3 的 loinc code 改掉再進行一次驗證,就會發生失敗的狀況,這樣就代表我們 loinc code 有匯入成功,可以正常驗證 loinc code 囉 ![image](https://hackmd.io/_uploads/H1IK4HKIex.png) # Support Me 文件創作花費了很多心血製作,如果你覺得很有幫助 不妨贊助我一下喝杯咖啡唄,[Support Me](https://portaly.cc/Li070/support)