Terminology Binding: CodeSystem, ValueSet, ConceptMap trong FHIR

Nhiều element coded trong FHIR có terminology binding tới một ValueSet, nhưng mức ràng buộc phụ thuộc binding strength. Binding giới hạn hoặc khuyến nghị tập mã cho element; nó không tự tạo một nghĩa duy nhất. Một Coding được định danh bằng system + code và, khi hợp đồng yêu cầu, version; CodeableConcept có thể mang nhiều coding và text. VN Core v0.9.0 hiện xuất bản 165 CodeSystems, 168 ValueSets, 17 ConceptMaps và 36 NamingSystems cho thực tế y tế Việt Nam.

Đối tượng: DEV, Vendor, Regulator — đã quen FHIR base, đang gặp lỗi "code not in valueset" hoặc cần hiểu vì sao chuẩn hóa terminology là điều kiện sống còn của interoperability.

Tóm tắt nhanh

  • CodeSystem định danh một hệ thống mã và có thể công bố nội dung hoặc supplement; canonical của Resource khác với URI dùng trong Coding.system khi đặc tả quy định khác.
  • ValueSet mô tả tập giá trị được chọn từ một hoặc nhiều CodeSystem bằng include/exclude hoặc danh sách code, rồi được dùng trong binding.
  • ConceptMap mô tả quan hệ giữa các code/khái niệm theo chiều và ngữ cảnh xác định, ví dụ mã chỉ số CLS Việt Nam sang LOINC; nó không phải mapping toàn bộ schema XML sang Resource FHIR.
  • NamingSystem đăng ký URI cho identifier (CCCD, BHYT, BHXH, MRN bệnh viện), không phải cho clinical code.
  • Binding strength có 4 mức: required, extensible, preferred, example — quyết định mức độ ràng buộc của ValueSet với field.

1. Bốn khái niệm cốt lõi

FHIR Terminology Module (R4 §3.6) chia tài nguyên quản lý mã làm bốn nhóm: CodeSystem định nghĩa bộ mã gốc, ValueSet chọn ra tập con dùng được, ConceptMap dịch giữa các hệ thống mã, và NamingSystem đăng ký URI định danh. Hiểu rõ phân vai này là điều kiện cần để đọc bất kỳ Implementation Guide nào, từ US Core, JP Core đến VN Core.

CodeSystem — bộ mã gốc

CodeSystem là tài nguyên định nghĩa chính xác mã nào tồn tại và mỗi mã nghĩa là gì. Mỗi CodeSystem có một URL canonical duy nhất toàn cầu, đóng vai trò định danh bộ mã. VN Core dùng quy ước http://fhir.hl7.org.vn/core/CodeSystem/ kèm hậu tố -cs, ví dụ vn-ethnicity-cs cho danh mục 54 dân tộc Việt Nam hoặc vn-icd10-cs cho ICD-10 phiên bản Việt Nam.

{
  "resourceType": "CodeSystem",
  "url": "http://fhir.hl7.org.vn/core/CodeSystem/vn-ethnicity-cs",
  "version": "1.0.0",
  "name": "VNEthnicity",
  "title": "Dân tộc Việt Nam",
  "status": "active",
  "content": "complete",
  "concept": [
    { "code": "01", "display": "Kinh" },
    { "code": "02", "display": "Tày" },
    { "code": "03", "display": "Thái" }
  ]
}

Trường content chỉ ra mức độ đầy đủ của resource: complete (toàn bộ mã đều có trong resource), fragment (chỉ một phần), example (minh họa), hoặc not-present (chỉ định nghĩa metadata, mã ở nơi khác — phù hợp cho những bộ mã rất lớn như SNOMED CT).

ValueSet — tập con dùng được

ValueSet không chứa mã mới; nó chọn ra tập con từ một hoặc nhiều CodeSystem để bind vào một field. Một CodeSystem có thể sinh nhiều ValueSet khác nhau tuỳ ngữ cảnh. Ví dụ ICD-10 VN có hơn 14.000 mã, nhưng ValueSet cho chẩn đoán nhi khoa hay ValueSet bệnh truyền nhiễm chỉ chọn vài trăm mã liên quan.

{
  "resourceType": "ValueSet",
  "url": "http://fhir.hl7.org.vn/core/ValueSet/vn-ethnicity-vs",
  "version": "1.0.0",
  "name": "VNEthnicityVS",
  "status": "active",
  "compose": {
    "include": [
      {
        "system": "http://fhir.hl7.org.vn/core/CodeSystem/vn-ethnicity-cs"
      }
    ]
  }
}

Lưu ý FHIR R4 yêu cầu trường status là bắt buộc (cardinality 1..1) cho cả CodeSystem, ValueSet, ConceptMap và NamingSystem. Bỏ quên status sẽ khiến validator báo lỗi ngay.

ConceptMap — ánh xạ giữa hai hệ

ConceptMap mô tả mối quan hệ giữa các mã của hai hệ thống. Ứng dụng phù hợp tại Việt Nam gồm ánh xạ ICD-10 VN với đúng phiên bản ICD-10 WHO được áp dụng hoặc mã chỉ số cận lâm sàng Việt Nam với LOINC. Chuyển đổi trường và cấu trúc XML đầu ra KCB sang Claim/Coverage/EOB cần đặc tả ánh xạ cấu trúc hoặc adapter được quản trị và kiểm thử; ConceptMap chỉ áp dụng cho các trường mã hóa. Trong R4, sourceUritargetUri là phạm vi của mapping (thường là URL ValueSet), còn cụm group.sourcegroup.target mới chứa URL CodeSystem cụ thể.

{
  "resourceType": "ConceptMap",
  "url": "http://fhir.hl7.org.vn/core/ConceptMap/vn-icd10-to-who",
  "version": "1.0.0",
  "name": "VNICD10ToWHO",
  "status": "active",
  "sourceUri": "http://fhir.hl7.org.vn/core/ValueSet/vn-icd10-vs",
  "targetUri": "http://hl7.org/fhir/ValueSet/icd-10",
  "group": [
    {
      "source": "http://fhir.hl7.org.vn/core/CodeSystem/vn-icd10-cs",
      "target": "http://hl7.org/fhir/sid/icd-10",
      "element": [
        {
          "code": "I10.0",
          "target": [
            { "code": "I10", "equivalence": "wider" }
          ]
        }
      ]
    }
  ]
}

Trường equivalence nhận các giá trị như equal, equivalent, wider, narrower, inexact, unmatched — phản ánh mức độ tương đương ngữ nghĩa, không chỉ map "1-1".

NamingSystem — đăng ký URI cho identifier

NamingSystem khác với ba tài nguyên trên: nó không quản lý mã lâm sàng mà đăng ký URI cho hệ thống định danh. CCCD, số thẻ BHYT, mã BHXH, MRN bệnh viện đều là identifier system, mỗi cái cần một URI duy nhất để Patient.identifier có thể trỏ tới.

{
  "resourceType": "NamingSystem",
  "name": "VNCCCD",
  "status": "active",
  "kind": "identifier",
  "date": "2026-04-30",
  "uniqueId": [
    {
      "type": "uri",
      "value": "http://fhir.hl7.org.vn/core/sid/cccd",
      "preferred": true
    }
  ]
}

Quy ước VN Core dùng http://fhir.hl7.org.vn/core/sid/{cccd|bhyt|bhxh|gks|passport}. Lưu ý NamingSystem cũng yêu cầu cả statusdate.

2. Coding và CodeableConcept

Hai datatype thường gây nhầm lẫn nhất khi mới tiếp cận FHIR. Coding là một mã đơn lẻ gồm system, version, code, display. CodeableConcept bao gồm một mảng nhiều Coding cùng nghĩa, kèm trường text chứa diễn giải tự do.

"code": {
  "coding": [
    {
      "system": "http://hl7.org/fhir/sid/icd-10",
      "version": "2019",
      "code": "I10",
      "display": "Essential hypertension"
    },
    {
      "system": "http://fhir.hl7.org.vn/core/CodeSystem/vn-icd10-cs",
      "code": "I10.0",
      "display": "Tăng huyết áp vô căn"
    }
  ],
  "text": "Tăng huyết áp"
}

Cùng một chẩn đoán có thể được mã hóa song song khi các Coding thực sự biểu diễn cùng khái niệm. CodeableConcept.text là phần tử tùy chọn trong FHIR base; profile hoặc IG có thể yêu cầu hay khuyến nghị nó để giữ cách diễn đạt của người ghi chép. text hỗ trợ con người đọc nhưng không thay thế code, system, version, display và provenance cần cho xử lý máy.

Quy tắc thực dụng: dùng CodeableConcept khi element cho phép và cần mang coding cùng diễn giải. Ghi text khi cần giữ ngôn ngữ lâm sàng, đồng thời quản trị phiên bản coding; không coi một trong hai là bản thay thế phổ quát cho phần còn lại.

3. Binding strength: bốn mức ràng buộc

Khi một field FHIR được bind tới một ValueSet, Implementer phải biết mức ràng buộc đến đâu. HL7 R4 định nghĩa bốn mức theo trang Terminology — Binding (hl7.org/fhir/R4/terminologies.html#strength):

Strength Ý nghĩa Ví dụ FHIR R4 base
required phải nằm trong ValueSet. Validator báo lỗi nếu nằm ngoài. Không cho phép text-only. Patient.gender bind tới AdministrativeGender (male/female/other/unknown).
extensible Nếu khái niệm đã có trong ValueSet thì phải dùng mã đó. Chỉ được mở rộng bằng mã ngoài khi ValueSet không cover khái niệm cần ghi. Patient.maritalStatus bind extensible tới MaritalStatus. Profile downstream có thể siết chặt.
preferred Khuyến nghị dùng ValueSet, nhưng không bắt buộc. Validator chỉ cảnh báo. Encounter.serviceType bind preferred tới ServiceType.
example ValueSet chỉ minh hoạ một bộ mã có thể dùng. Không có tính ràng buộc. Condition.code trong R4 base bind example tới Condition/Problem/Diagnosis Codes.

Một profile (như VN Core) hoàn toàn có thể siết chặt binding strength so với base. Ví dụ R4 base bind Condition.code ở mức example, nhưng VN Core có thể profile lại thành extensible với ValueSet ICD-10 VN, buộc các hệ thống ưu tiên dùng mã ICD-10 VN trừ khi không có mã phù hợp.

Lưu ý quan trọng: extensible nghiêm khắc hơn preferred. Nếu khái niệm cần ghi đã có trong ValueSet mà người dùng cố tình điền mã khác hoặc chỉ điền text, validator sẽ báo lỗi. preferred chỉ đưa ra khuyến nghị mà không có cơ chế cưỡng chế.

4. ValueSet — composition và expansion

ValueSet có hai mặt: composition (định nghĩa cách chọn) và expansion (kết quả expand ra danh sách mã thực tế).

Composition — định nghĩa

Phần compose của ValueSet liệt kê các nguyên tắc chọn mã: include chọn tập hợp, exclude loại trừ. Có thể chọn theo filter trên thuộc tính của CodeSystem (ví dụ: tất cả mã con của một parent, tất cả mã có status = active).

ValueSet: VNEncounterTypeVS
Id: vn-encounter-type-vs
Title: "Loại lượt khám"
Description: "Phân loại lượt khám theo TT 13/2025/TT-BYT"
* ^status = #active
* include codes from system VNEncounterTypeCS where status = #active

Expansion — kết quả

Khi terminology server expand ValueSet (qua operation $expand), kết quả là danh sách phẳng các mã thực tế:

{
  "resourceType": "ValueSet",
  "url": "http://fhir.hl7.org.vn/core/ValueSet/vn-encounter-type-vs",
  "status": "active",
  "expansion": {
    "timestamp": "2026-04-30T10:00:00+07:00",
    "contains": [
      { "system": "http://fhir.hl7.org.vn/core/CodeSystem/vn-encounter-type-cs", "code": "OUTPATIENT", "display": "Ngoại trú" },
      { "system": "http://fhir.hl7.org.vn/core/CodeSystem/vn-encounter-type-cs", "code": "INPATIENT", "display": "Nội trú" },
      { "system": "http://fhir.hl7.org.vn/core/CodeSystem/vn-encounter-type-cs", "code": "EMERGENCY", "display": "Cấp cứu" }
    ]
  }
}

Composition mô tả quy tắc chọn mã, còn expansion là kết quả tại một thời điểm với tham số và phiên bản terminology cụ thể. Nếu composition không khóa version, expansion tính lại có thể thay đổi khi CodeSystem thay đổi; vì vậy production phải lưu version, timestamp và tham số expansion thay vì giả định mã mới tự động an toàn để sử dụng.

5. Operations: $expand, $validate-code, $lookup, $translate, $subsumes

FHIR định nghĩa các operation terminology thường dùng dưới đây. Việc server hỗ trợ operation, tham số, paging, ngôn ngữ hay hệ mã cụ thể phải được công bố trong CapabilityStatement và kiểm thử; không phải mọi terminology server đều hỗ trợ toàn bộ:

  • $expand trên ValueSet — trả về danh sách mã thực tế. Hỗ trợ filter, paging, ngôn ngữ.
  • $validate-code trên ValueSet hoặc CodeSystem — kiểm tra một mã có hợp lệ trong ngữ cảnh không.
  • $lookup trên CodeSystem — tra display, designation đa ngôn ngữ, properties của một mã.
  • $translate trên ConceptMap — áp dụng mapping để dịch mã từ hệ này sang hệ khác.
  • $subsumes trên CodeSystem — kiểm tra quan hệ cha-con giữa hai mã (subsumption).
GET /ValueSet/vn-ethnicity-vs/$expand
GET /CodeSystem/vn-icd10-cs/$lookup?code=I10.0
GET /CodeSystem/vn-icd10-cs/$validate-code?code=I10.0
POST /ConceptMap/vn-icd10-to-who/$translate?code=I10.0&system=http://fhir.hl7.org.vn/core/CodeSystem/vn-icd10-cs

Một kiến trúc có thể dùng $expand để cấp danh sách chọn, $validate-code ở thời điểm nhập hoặc tại validation gate, và $translate khi có ConceptMap được quản trị. Tần suất gọi, cache, xử lý mất kết nối và kiểm tra equivalence phải được thiết kế theo SLA; kết quả $translate không mặc nhiên là ánh xạ tương đương hay định dạng BHXH hợp lệ.

6. Terminology server

Terminology server là dịch vụ chuyên biệt phục vụ các operation trên. Một số lựa chọn phổ biến:

  • Ontoserver (CSIRO, Úc) — sản phẩm thương mại trưởng thành, mạnh về SNOMED CT, được dùng trong AU Core và NHS.
  • HAPI FHIR JPA Server — open source, có module terminology đầy đủ, phù hợp triển khai trong nước.
  • Snowstorm (SNOMED International) — open source, thiết kế chuyên biệt cho SNOMED CT.
  • tx.fhir.org — terminology server công cộng do HL7 vận hành, dùng cho test và validate IG.

Dùng terminology service công cộng hay tự host phải dựa trên dữ liệu được gửi trong request, giấy phép thuật ngữ, SLA, threat model và phân tích chuyển dữ liệu xuyên biên giới. Không gửi dữ liệu bệnh nhân hoặc ngữ cảnh nhạy cảm tới service công cộng chỉ để kiểm tra mã. Các luật về an ninh mạng và bảo vệ dữ liệu không được diễn giải thành yêu cầu blanket rằng mọi terminology server phải đặt tại Việt Nam; đơn vị triển khai cần đánh giá phạm vi áp dụng cụ thể.

7. VN Core terminology landscape

Bộ terminology của VN Core được tổ chức theo hai trục: tái sử dụng chuẩn quốc tế và xây dựng CodeSystem đặc thù Việt Nam. Mã bắt nguồn từ văn bản pháp lý cần giữ citation/provenance tới văn bản và phụ lục tương ứng; terminology quốc tế phải giữ system, version, nguồn phát hành và điều kiện giấy phép. Không gán một căn cứ pháp lý Việt Nam giả tạo cho mọi code quốc tế.

Bộ mã CodeSystem ID Phạm vi trong v0.9.0 Văn bản pháp lý
ICD-10 VN vn-icd10-cs content = not-present; metadata trỏ tới nguồn được quản trị QĐ 4469/QĐ-BYT và các bổ sung được ghi trong artifact
ICD-9-CM 2026 vn-icd9-cm-cs 3.882 mã trong v0.9.0 QĐ 387/QĐ-BYT (05/02/2026)
Dân tộc vn-ethnicity-cs 56 mã: 54 dân tộc, người nước ngoài và không rõ Danh mục thống kê được artifact ghi provenance
Đơn vị hành chính hiện hành vn-province-cs, vn-ward-cs 34 tỉnh/thành và 3.321 xã/phường/đặc khu NQ 202/2025/QH15 và QĐ 19/2025/QĐ-TTg
Chỉ số cận lâm sàng vn-cls-cs 2.964 mã nguồn; ConceptMap sang LOINC cần đọc equivalence QĐ 1227/QĐ-BYT (11/04/2025)
Bản dịch bổ sung LOINC vn-loinc-cs CodeSystem supplement cho http://loinc.org; không dùng URL supplement làm Coding.system LOINC 2.78 trong package v0.9.0; phiên bản LOINC hiện hành phải đối chiếu riêng
Bản dịch bổ sung SNOMED CT vn-snomed-subset-cs Supplement cho 76.066 SCTID riêng biệt; coding vẫn dùng http://snomed.info/sct QĐ 2427/QĐ-BYT, 2493 và 2805/QĐ-BYT; áp dụng điều kiện giấy phép SNOMED CT
Văn bản pháp lý tham chiếu vn-legal-document-ref-cs 172 concept trong v0.9.0 Registry do dự án tổng hợp; trạng thái từng văn bản được quản trị riêng

Quan hệ giữa ICD-10 VN và bản WHO phải được đánh giá theo từng mã và phiên bản; không thể suy diễn mọi mã chi tiết thành một quan hệ wider. ConceptMap cần công bố source/target version và equivalence cho từng mapping, đồng thời giữ mã nguồn khi mapping chưa được thẩm định.

SNOMED CT có điều kiện giấy phép riêng. Các QĐ 2427/2493/2805 xác định phạm vi mapping/thuật ngữ của Việt Nam nhưng không nên được diễn giải thay cho quyền phân phối toàn bộ edition quốc tế. Đơn vị triển khai phải kiểm tra điều khoản hiện hành qua SNOMED International/MLDS cho lãnh thổ và use case của mình.

8. Khi nào tự tạo CodeSystem

Một sai lầm phổ biến của các dự án mới là tự tạo CodeSystem riêng cho mọi thứ, dẫn tới phân mảnh terminology và mất khả năng interoperate. Quy tắc chung từ HL7 Vocabulary Working Group:

Không tự tạo nếu

  • Đã có chuẩn quốc tế phù hợp (ICD-10, LOINC, SNOMED CT, ATC, ISO 3166).
  • Có CodeSystem trong FHIR core hoặc terminology.hl7.org đã cover (AdministrativeGender, MaritalStatus, ContactPointSystem).
  • Đã có IG khu vực mạnh (US Core, JP Core) định nghĩa CodeSystem reusable.

Nên tự tạo khi

  • Khái niệm là VN-specific: 54 dân tộc, đối tượng BHYT, tuyến KCB sau NQ 202/2025/QH15, hạng bệnh viện theo TT 06/2024/TT-BYT.
  • Văn bản pháp lý Việt Nam ban hành code list cụ thể (Quyết định Bộ Y tế, Thông tư BYT, Quyết định Tổng cục Thống kê).
  • Bộ mã quốc tế thiếu một số nhánh chi tiết mà thực tế Việt Nam cần (ví dụ ICD-10 VN mở rộng so với ICD-10 WHO).
  • Cần một CodeSystem internal cho IG (ví dụ VNLegalDocumentRefCS để tham chiếu văn bản pháp lý từ profile và extension).

Khi đã quyết định tự tạo, ba nguyên tắc cứng:

  1. Mã lấy từ danh mục pháp lý phải truy vết được tới văn bản/phụ lục nguồn; mã quốc tế hoặc mã quản trị nội bộ phải có provenance và namespace phù hợp với nguồn thực tế.
  2. Versioning rõ ràng: khi văn bản pháp lý sửa đổi, phải tăng version CodeSystem và đánh dấu mã cũ status = deprecated hoặc retirement-date.
  3. URL canonical theo pattern http://fhir.hl7.org.vn/core/CodeSystem/vn-{domain}-cs không thay đổi.

9. Câu hỏi thường gặp

Có thể bind cùng lúc nhiều ValueSet vào một field không?

FHIR R4 chỉ cho phép một binding chính trên mỗi element. Tuy nhiên, vì CodeableConcept.coding là mảng, một field có thể chứa nhiều Coding từ nhiều CodeSystem khác nhau, miễn là tất cả cùng diễn đạt một khái niệm. Đây là cơ chế chuẩn để vừa code ICD-10 VN vừa code SNOMED CT trên cùng một Condition.code.

ICD-10 VN khác ICD-10 WHO ở đâu?

ICD-10 VN được ban hành theo QĐ 4469/QĐ-BYT và có các bổ sung như mã COVID-19 theo QĐ 98/QĐ-BYT. Quan hệ với edition WHO phải được kiểm tra theo code, version và ConceptMap; không giả định toàn bộ mã ba ký tự tương đương hoặc sai biệt chỉ xuất hiện ở một cấp cố định.

Có bắt buộc mua giấy phép SNOMED CT không?

Việt Nam không có trong danh sách thành viên SNOMED International tại thời điểm đối chiếu 01/06/2026. Quyền sử dụng và mức phí tại lãnh thổ không thành viên phụ thuộc loại use case, lãnh thổ và các chương trình miễn/giảm; tổ chức phải kiểm tra điều kiện hiện hành qua SNOMED International/MLDS trước khi tải, triển khai hoặc phân phối. Các QĐ 2427/2493/2805 là nguồn mapping/phạm vi thuật ngữ của Việt Nam, không nên được diễn giải thay cho giấy phép phân phối SNOMED CT đầy đủ.

NamingSystem có thay thế CodeSystem được không?

Không. NamingSystem chỉ đăng ký URI cho identifier system (CCCD, BHYT, BHXH, MRN), không quản lý mã lâm sàng. Nhầm lẫn này là nguyên nhân thường gặp khiến validator báo lỗi: Patient.identifier.system phải là URI đã đăng ký qua NamingSystem; còn Condition.code.coding.system phải trỏ tới URL canonical của một CodeSystem.

Tài liệu tham chiếu