2026年2月19日 星期四

匯入 FHIR 範例資料集

匯入 FHIR 範例資料集

Synthea™ Patient Generator (GitHub) 是一個 開源的合成病人(synthetic patient)資料生成器(Synthetic Patient Population Simulator),用來在不涉及真實個人隱私資料的情況下,模擬完整的病人健康歷程與電子健康紀錄(EHR)

合成患者(synthetic patient)是由電腦模型生成的虛構個體,看起來像真實患者但不對應任何真實個人。這種資料不含任何實際個人資訊,因此沒有隱私風險。Synthea 的使命是生成:逼真但不是真實的健康資料(realistic synthetic data)、* 涵蓋病人從出生到死亡的完整醫療歷程、* 各種與健康照護相關的資料,包括症狀、診斷、處方、檢驗、就診事件等, 生成的資料可用於研究、測試、教育和開發,而不受 HIPAA 或其他隱私法律限制。

🛠 主要特色與功能 📌 全生命周期模擬 從出生到死亡,逐步模擬個體醫療事件及健康狀態。 📌 可配置的人口統計與統計模型 預設使用馬薩諸塞州人口統計資料,也可以配置其他區域或人口設定。 📌 模組化規則系統 採用「疾病模組(disease modules)」和通用規則框架(Generic Module Framework)來模擬疾病發生與演進。這些規則參考公開資料與統計資料(如 CDC、NIH)。 📌 輸出格式多樣性 支援多種業界標準與格式輸出,包括:

  • HL7 FHIR(R4、STU3、DSTU2)
  • C-CDA、CSV、Bulk FHIR(ndjson)

下載與使用 Synthea 生成 FHIR 資料集

  • 打開 Sample FHIR Bulk Export Datasets 的 GitHub 頁面
  • 找到 [Small] 連結,點選這個連結
  • 如此,將會下載 10 個模擬病人資料集,這些資料集以 ndjson 格式存儲,並且包含了 FHIR 資源的完整結構,可以用來測試和開發 FHIR API 的功能。
  • 打開並且解壓縮這個 [sample-bulk-fhir-datasets-10-patients.zip] 檔案
  • 這個檔案內將會有底下的FHIR資源檔案:

    • Organization : 模擬的醫療機構資料。
    • Location : 模擬的醫療機構地點資料。
    • Practitioner : 模擬的醫療人員資料。
    • PractitionerRole : 模擬的醫療人員角色資料。
    • Patient : 模擬的病人資料。
    • Encounter : 模擬的就診事件資料。
    • Condition : 模擬的疾病或健康狀態資料。
    • AllergyIntolerance : 模擬的過敏或不耐受資料。
    • Immunization : 模擬的疫苗接種資料。
    • Device : 模擬的醫療設備資料。
    • Observation : 模擬的觀察資料。
    • Procedure : 模擬的醫療程序資料。
    • DiagnosticReport : 模擬的診斷報告資料。
    • DocumentReference : 模擬的文件參考資料。
    • MedicationRequest : 模擬的用藥請求資料。
  • 在這個目錄下,建立一個 PowerShell 檔案 [import.ps1]
  • 在這個檔案內輸入底下的程式碼:
param(
  [string]$Base   = "https://hapi.fhir.org/baseR4",
  [string]$Folder = "."
)

$ErrorActionPreference = "Stop"

# 判斷物件是否「有內容」(不是空物件/空陣列/空字串/null)
function Test-HasContent {
  param($Value)

  if ($null -eq $Value) { return $false }

  # string
  if ($Value -is [string]) {
    return -not [string]::IsNullOrWhiteSpace($Value)
  }

  # array
  if ($Value -is [System.Collections.IEnumerable] -and -not ($Value -is [PSCustomObject])) {
    # 注意:string 也是 IEnumerable,上面已處理
    try {
      $arr = @($Value)
      return ($arr.Count -gt 0)
    } catch {
      return $true
    }
  }

  # object (PSCustomObject)
  if ($Value -is [PSCustomObject] -or $Value -is [hashtable]) {
    $props = $Value.PSObject.Properties
    if ($props.Count -eq 0) { return $false }

    foreach ($p in $props) {
      if (Test-HasContent $p.Value) { return $true }
    }
    return $false
  }

  # other primitive types
  return $true
}

# 清洗 FHIR JSON:
# - 移除 extension(synthea 常見 unknown extension)
# - 移除 meta.profile;若 meta 變空 => 移除整個 meta(避免 dom-6 / meta empty)
function Clean-FhirJsonLine {
  param([string]$JsonLine)

  $obj = $JsonLine | ConvertFrom-Json

  # 1) 移除 extension
  if ($obj.PSObject.Properties.Name -contains "extension") {
    $obj.PSObject.Properties.Remove("extension")
  }

  # 2) meta: 移除 profile;meta 若空就整個移除
  if ($obj.PSObject.Properties.Name -contains "meta") {
    $meta = $obj.meta

    if ($meta -and ($meta.PSObject.Properties.Name -contains "profile")) {
      $meta.PSObject.Properties.Remove("profile")
    }

    # 有些資料可能還留 profile: [],確保刪乾淨
    if ($meta -and ($meta.PSObject.Properties.Name -contains "profile")) {
      $meta.PSObject.Properties.Remove("profile")
    }

    # 若 meta 沒有任何有效內容,移除 meta
    if (-not (Test-HasContent $meta)) {
      $obj.PSObject.Properties.Remove("meta")
    }
  }

  # 3) 可選:移除空的 text(某些 server 對空 narrative 也會抱怨)
  if ($obj.PSObject.Properties.Name -contains "text") {
    if (-not (Test-HasContent $obj.text)) {
      $obj.PSObject.Properties.Remove("text")
    }
  }

  return ($obj | ConvertTo-Json -Depth 100 -Compress)
}

# PUT 單筆 Resource(保留 client-assigned id)
function Invoke-FhirPut {
  param(
    [string]$Url,
    [string]$JsonLine
  )

  $tmp = Join-Path $env:TEMP ("fhir_import_" + [Guid]::NewGuid().ToString("N") + ".json")

  # ✅ 用 UTF-8 無 BOM 寫入(避免 HAPI-0450 / HAPI-1861)
  $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
  [System.IO.File]::WriteAllText($tmp, $JsonLine, $utf8NoBom)

  try {
    $output = & curl.exe -sS `
      -X PUT $Url `
      -H "Content-Type: application/fhir+json; charset=utf-8" `
      --data-binary "@$tmp" `
      -w "`n%{http_code}"

    $lines = $output -split "`n"
    $http  = ($lines[-1]).Trim()
    $body  = ($lines[0..($lines.Length-2)] -join "`n").Trim()

    return @{
      Http = $http
      Body = $body
    }
  }
  finally {
    Remove-Item -Force -ErrorAction SilentlyContinue $tmp
  }
}

# 匯入單一 ndjson 檔
function Import-NdjsonFile {
  param([string]$Path)

  $fileName     = [System.IO.Path]::GetFileName($Path)
  $resourceType = $fileName.Split(".")[0]

  Write-Host "==> Importing $fileName -> $resourceType"

  $i = 0
  Get-Content -Path $Path -ReadCount 1 | ForEach-Object {
    $line = $_
    if ([string]::IsNullOrWhiteSpace($line)) { return }
    $i++

    try {
      $clean = Clean-FhirJsonLine -JsonLine $line
      $obj   = $clean | ConvertFrom-Json
    } catch {
      throw "Invalid JSON at $fileName line $i"
    }

    $id = $obj.id
    if ([string]::IsNullOrWhiteSpace($id)) {
      throw "Missing id at $fileName line $i"
    }

    $url  = "$Base/$resourceType/$id"
    $resp = Invoke-FhirPut -Url $url -JsonLine $clean

    if ($resp.Http -ne "200" -and $resp.Http -ne "201") {
      Write-Host ""
      Write-Host "FAILED: $fileName line $i => HTTP $($resp.Http)"
      if (-not [string]::IsNullOrWhiteSpace($resp.Body)) {
        Write-Host "Response body (first 1600 chars):"
        Write-Host ($resp.Body.Substring(0, [Math]::Min(1600, $resp.Body.Length)))
      } else {
        Write-Host "Response body: <empty>"
      }
      throw "Import failed."
    }

    if (($i % 200) -eq 0) {
      Write-Host "  ...$i resources imported"
    }
  }

  Write-Host "    OK ($i resources)"
}

# 匯入順序(先被參考者)
$order = @(
  "Organization*.ndjson",
  "Location*.ndjson",
  "Practitioner*.ndjson",
  "PractitionerRole*.ndjson",
  "Patient*.ndjson",
  "Encounter*.ndjson",
  "Condition*.ndjson",
  "AllergyIntolerance*.ndjson",
  "Immunization*.ndjson",
  "Device*.ndjson",
  "Observation*.ndjson",
  "Procedure*.ndjson",
  "DiagnosticReport*.ndjson",
  "DocumentReference*.ndjson",
  "MedicationRequest*.ndjson"
)

foreach ($pattern in $order) {
  $files = Get-ChildItem -Path $Folder -Filter $pattern -File | Sort-Object Name
  if ($files.Count -eq 0) {
    Write-Host "Skip (not found): $pattern"
    continue
  }

  foreach ($f in $files) {
    Import-NdjsonFile -Path $f.FullName
  }
}

Write-Host ""
Write-Host "=============================="
Write-Host "FHIR NDJSON IMPORT COMPLETED"
Write-Host "=============================="
  • 使用底下命令來執行這個 PowerShell 腳本
powershell -ExecutionPolicy Bypass -File .\import.ps1 -Base "https://server.fire.ly" -Folder "."

*將會把這些 FHIR 資源逐一匯入到指定的 FHIR Server(預設是 https://hapi.fhir.org/baseR4)。腳本會自動處理 JSON 清洗(移除不必要的 extension 和 meta.profile)並且使用 HTTP PUT 方法來上傳資源,保留原有的 client-assigned id。匯入過程中會顯示進度和任何可能的錯誤訊息。 




沒有留言:

張貼留言