hapi-fhir-validation-resources là thư viện cung cấp các tài nguyên cần thiết cho việc validation FHIR resources, bao gồm các StructureDefinition, ValueSet, CodeSystem và các tài nguyên terminologies khác. Thư viện này đóng vai trò quan trọng trong việc đảm bảo tính chính xác và tuân thủ của dữ liệu FHIR trong ứng dụng y tế.
Các tính năng chính
1. Cung cấp tài nguyên validation chuẩn
Thư viện cung cấp các tài nguyên validation chuẩn theo đặc tả FHIR R5, bao gồm:
- Các StructureDefinition cho tất cả resource types
- ValueSets và CodeSystems tiêu chuẩn
- Các datatypes và extensions định nghĩa trong FHIR R5
2. Hỗ trợ nhiều profiles
- Profiles tiêu chuẩn từ HL7 FHIR
- Profiles quốc tế (ví dụ: US Core, AU Base, UK Core)
- Khả năng nạp và sử dụng profiles tùy chỉnh
3. Tích hợp với HAPI FHIR Validator
- Cung cấp các tài nguyên cần thiết cho
FhirInstanceValidator - Tích hợp liền mạch với
ValidationSupportChain - Hỗ trợ cả validation offline và trực tuyến
Cài đặt và cấu hình
Thêm dependency vào Maven project
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation-resources-r5</artifactId>
<version>6.4.0</version>
</dependency>
Cấu hình cơ bản với Spring Boot
@Configuration
public class FhirValidationConfig {
@Bean
public FhirContext fhirContext() {
return FhirContext.forR5();
}
@Bean
public DefaultProfileValidationSupport defaultProfileValidationSupport() {
return new DefaultProfileValidationSupport(fhirContext());
}
@Bean
public PrePopulatedValidationSupport prePopulatedValidationSupport() {
PrePopulatedValidationSupport validationSupport = new PrePopulatedValidationSupport(fhirContext());
// Có thể thêm các resource tùy chỉnh ở đây
return validationSupport;
}
@Bean
public InMemoryTerminologyServerValidationSupport terminologyServerValidationSupport() {
return new InMemoryTerminologyServerValidationSupport(fhirContext());
}
@Bean
public ValidationSupportChain validationSupportChain() {
ValidationSupportChain validationSupportChain = new ValidationSupportChain(
defaultProfileValidationSupport(),
prePopulatedValidationSupport(),
terminologyServerValidationSupport()
);
return validationSupportChain;
}
@Bean
public FhirInstanceValidator fhirInstanceValidator() {
FhirInstanceValidator validator = new FhirInstanceValidator(validationSupportChain());
return validator;
}
@Bean
public FhirValidator fhirValidator() {
FhirValidator validator = fhirContext().newValidator();
validator.registerValidatorModule(fhirInstanceValidator());
return validator;
}
}
Ví dụ sử dụng
Validation cơ bản
@Service
public class FhirValidationService {
private final FhirValidator validator;
private final FhirContext fhirContext;
public FhirValidationService(FhirValidator validator, FhirContext fhirContext) {
this.validator = validator;
this.fhirContext = fhirContext;
}
public ValidationResult validateResource(IBaseResource resource) {
return validator.validateWithResult(resource);
}
public ValidationResult validateResourceAgainstProfile(IBaseResource resource, String profileUrl) {
ValidationOptions options = new ValidationOptions();
options.addProfileForValidation(profileUrl);
return validator.validateWithResult(resource, options);
}
public ValidationResult validateJsonResource(String jsonResource) {
IBaseResource resource = fhirContext.newJsonParser().parseResource(jsonResource);
return validator.validateWithResult(resource);
}
public boolean isValid(IBaseResource resource) {
ValidationResult result = validator.validateWithResult(resource);
return result.isSuccessful();
}
public List<SingleValidationMessage> getValidationErrors(IBaseResource resource) {
ValidationResult result = validator.validateWithResult(resource);
return result.getMessages().stream()
.filter(m -> m.getSeverity() == ResultSeverityEnum.ERROR)
.collect(Collectors.toList());
}
}
Ví dụ tích hợp với REST API
@RestController
@RequestMapping("/api/validation")
public class ValidationController {
private final FhirValidationService validationService;
private final FhirContext fhirContext;
public ValidationController(FhirValidationService validationService, FhirContext fhirContext) {
this.validationService = validationService;
this.fhirContext = fhirContext;
}
@PostMapping("/patient")
public ResponseEntity<?> validatePatient(@RequestBody String patientJson) {
try {
Patient patient = fhirContext.newJsonParser().parseResource(Patient.class, patientJson);
ValidationResult result = validationService.validateResource(patient);
if (result.isSuccessful()) {
return ResponseEntity.ok(Map.of("valid", true));
} else {
List<Map<String, Object>> issues = result.getMessages().stream()
.map(msg -> Map.of(
"severity", msg.getSeverity().name(),
"location", msg.getLocationString(),
"message", msg.getMessage()
))
.collect(Collectors.toList());
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY)
.body(Map.of(
"valid", false,
"issues", issues
));
}
} catch (Exception e) {
return ResponseEntity.badRequest()
.body(Map.of(
"valid", false,
"error", "Failed to parse resource: " + e.getMessage()
));
}
}
@PostMapping("/patient/profile/{profileName}")
public ResponseEntity<?> validatePatientAgainstProfile(
@RequestBody String patientJson,
@PathVariable String profileName) {
String profileUrl;
switch (profileName) {
case "basic":
profileUrl = "http://hl7.org/fhir/StructureDefinition/Patient";
break;
case "uscore":
profileUrl = "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient";
break;
default:
return ResponseEntity.badRequest().body("Unknown profile: " + profileName);
}
try {
Patient patient = fhirContext.newJsonParser().parseResource(Patient.class, patientJson);
ValidationResult result = validationService.validateResourceAgainstProfile(patient, profileUrl);
if (result.isSuccessful()) {
return ResponseEntity.ok(Map.of("valid", true));
} else {
List<Map<String, Object>> issues = result.getMessages().stream()
.map(msg -> Map.of(
"severity", msg.getSeverity().name(),
"location", msg.getLocationString(),
"message", msg.getMessage()
))
.collect(Collectors.toList());
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY)
.body(Map.of(
"valid", false,
"profile", profileUrl,
"issues", issues
));
}
} catch (Exception e) {
return ResponseEntity.badRequest()
.body(Map.of(
"valid", false,
"error", "Failed to parse resource: " + e.getMessage()
));
}
}
}
Tùy chỉnh validation
Nạp profiles tùy chỉnh
@Component
public class CustomProfileLoader {
private final FhirContext fhirContext;
private final PrePopulatedValidationSupport validationSupport;
public CustomProfileLoader(
FhirContext fhirContext,
PrePopulatedValidationSupport validationSupport) {
this.fhirContext = fhirContext;
this.validationSupport = validationSupport;
}
@PostConstruct
public void loadProfiles() {
try {
// Nạp StructureDefinition
IParser parser = fhirContext.newJsonParser();
// Đọc profile từ file
String profileJson = Files.readString(Paths.get("path/to/custom-patient-profile.json"));
StructureDefinition profile = parser.parseResource(StructureDefinition.class, profileJson);
// Thêm vào validation support
validationSupport.addStructureDefinition(profile);
// Nạp ValueSet
String valueSetJson = Files.readString(Paths.get("path/to/custom-valueset.json"));
ValueSet valueSet = parser.parseResource(ValueSet.class, valueSetJson);
validationSupport.addValueSet(valueSet);
// Nạp CodeSystem
String codeSystemJson = Files.readString(Paths.get("path/to/custom-codesystem.json"));
CodeSystem codeSystem = parser.parseResource(CodeSystem.class, codeSystemJson);
validationSupport.addCodeSystem(codeSystem);
} catch (Exception e) {
throw new RuntimeException("Error loading custom profiles", e);
}
}
}
Tạo custom validation module
@Component
public class CustomPatientValidator implements IValidatorModule {
@Override
public ValidationResult validateResource(IBaseResource resource) {
List<SingleValidationMessage> messages = new ArrayList<>();
// Kiểm tra chỉ áp dụng cho Patient resources
if (resource instanceof Patient) {
Patient patient = (Patient) resource;
// Kiểm tra quy tắc tùy chỉnh: tên phải có ít nhất 2 từ
if (patient.hasName() && patient.getNameFirstRep().hasGiven()) {
String givenName = patient.getNameFirstRep().getGivenAsSingleString();
if (givenName.split(" ").length < 2) {
SingleValidationMessage message = new SingleValidationMessage();
message.setLocationString("Patient.name.given");
message.setSeverity(ResultSeverityEnum.WARNING);
message.setMessage("Given name should have at least 2 words for Vietnamese patients");
messages.add(message);
}
}
// Kiểm tra yêu cầu về địa chỉ
if (patient.hasAddress() && !patient.getAddressFirstRep().hasCity()) {
SingleValidationMessage message = new SingleValidationMessage();
message.setLocationString("Patient.address");
message.setSeverity(ResultSeverityEnum.ERROR);
message.setMessage("Address must include city");
messages.add(message);
}
}
return new ValidationResult(messages);
}
}
Đăng ký custom validator
@Configuration
public class CustomValidationConfig {
@Bean
public CustomPatientValidator customPatientValidator() {
return new CustomPatientValidator();
}
@Bean
public FhirValidator fhirValidatorWithCustomModules(
FhirContext fhirContext,
FhirInstanceValidator instanceValidator,
CustomPatientValidator customPatientValidator) {
FhirValidator validator = fhirContext.newValidator();
validator.registerValidatorModule(instanceValidator);
validator.registerValidatorModule(customPatientValidator);
return validator;
}
}
Bài tập thực hành cho Khóa học
Bài tập 1: Xây dựng Validation Service
Xây dựng một service để validation các FHIR resources theo yêu cầu của hệ thống y tế Việt Nam:
- Validation Patient resource với các quy tắc:
- Họ và tên phải có đầy đủ
- Mã định danh phải là CMND/CCCD hoặc Hộ chiếu
- Địa chỉ phải có đầy đủ thông tin: số nhà, đường phố, phường/xã, quận/huyện, tỉnh/thành phố
- Số điện thoại phải theo định dạng Việt Nam
- Validation Observation resource với các quy tắc:
- Phải có reference đến Patient
- Code phải có trong hệ thống mã LOINC hoặc SNOMED CT
- Effective time phải có giá trị
- Các chỉ số sinh hiệu phải nằm trong ngưỡng bình thường
Bài tập 2: Tạo Custom Profile
Tạo custom profile cho Patient resource dành riêng cho bệnh nhân Việt Nam:
- Sử dụng FHIR Profiler hoặc Forge để tạo profile
- Định nghĩa các extension cho:
- Mã BHYT
- Dân tộc
- Nơi sinh
- Nghề nghiệp theo danh mục Việt Nam
- Áp dụng các ràng buộc (constraints) phù hợp
- Nạp profile vào hệ thống và validation
Bài tập 3: Xây dựng REST API cho Validation
Xây dựng REST API để client có thể gửi resources để validation:
- Endpoint cho validation resource theo profile tiêu chuẩn
- Endpoint cho validation resource theo profile tùy chỉnh
- Endpoint để liệt kê các profile có sẵn trong hệ thống
- Xử lý và trả về kết quả validation theo định dạng chuẩn
Kết luận
HAPI FHIR Validation Resources là một thành phần quan trọng trong việc đảm bảo tính chính xác và tuân thủ của dữ liệu FHIR trong ứng dụng y tế. Thông qua validation, bạn có thể:
- Đảm bảo dữ liệu tuân thủ tiêu chuẩn FHIR R5
- Áp dụng các quy tắc nghiệp vụ đặc thù cho hệ thống y tế Việt Nam
- Tạo và sử dụng các profile tùy chỉnh cho các use cases cụ thể
- Xác thực dữ liệu trước khi lưu trữ hoặc trao đổi với các hệ thống khác
Trong các module tiếp theo của khóa học, chúng ta sẽ đi sâu hơn vào cách sử dụng validation trong các flows nghiệp vụ cụ thể và tích hợp với các thành phần khác của hệ thống.