FHIR Integration¶
Deep dive into HL7 FHIR R4 implementation.
HAPI FHIR Server¶
Configuration¶
@Configuration
public class FhirConfig {
@Bean
public FhirContext fhirContext() {
return FhirContext.forR4();
}
@Bean
public IGenericClient fhirClient(FhirContext fhirContext) {
String serverBase = "http://localhost:8080/api/fhir";
return fhirContext.newRestfulGenericClient(serverBase);
}
}
13 Resources Implemented¶
- Patient - Demographics
- Practitioner - Providers
- Organization - Facilities
- Appointment - Scheduling
- Encounter - Visits
- Condition - Diagnoses
- MedicationRequest - Prescriptions
- AllergyIntolerance - Allergies
- Procedure - Procedures
- ServiceRequest - Orders
- DocumentReference - Documents
- FamilyMemberHistory - Family history
- AuditEvent - Audit trail
Resource Mappings¶
Patient Resource¶
FHIR → DTO:
public PatientDTO toDTO(Patient fhir) {
PatientDTO dto = new PatientDTO();
dto.setId(fhir.getIdElement().getIdPart());
dto.setFirstName(fhir.getNameFirstRep().getGivenAsSingleString());
dto.setLastName(fhir.getNameFirstRep().getFamily());
dto.setGender(fhir.getGender().toCode());
dto.setBirthDate(formatDate(fhir.getBirthDate()));
// Extract identifiers
for (Identifier id : fhir.getIdentifier()) {
if ("MRN".equals(id.getSystem())) {
dto.setMrn(id.getValue());
}
}
return dto;
}
DTO → FHIR:
public Patient toFHIR(PatientDTO dto) {
Patient fhir = new Patient();
fhir.setId(dto.getId());
HumanName name = new HumanName();
name.setFamily(dto.getLastName());
name.addGiven(dto.getFirstName());
fhir.addName(name);
fhir.setGender(Enumerations.AdministrativeGender.fromCode(dto.getGender()));
fhir.setBirthDate(parseDate(dto.getBirthDate()));
Identifier mrn = new Identifier();
mrn.setSystem("MRN");
mrn.setValue(dto.getMrn());
fhir.addIdentifier(mrn);
return fhir;
}
Condition Resource with ICD-10¶
public Condition createCondition(ConditionDTO dto) {
Condition condition = new Condition();
// Patient reference
condition.setSubject(new Reference("Patient/" + dto.getPatientId()));
// ICD-10 code
CodeableConcept code = new CodeableConcept();
Coding coding = new Coding();
coding.setSystem("http://hl7.org/fhir/sid/icd-10-cm");
coding.setCode(dto.getCode()); // e.g., "E11.9"
coding.setDisplay(dto.getDisplay()); // e.g., "Type 2 diabetes"
code.addCoding(coding);
condition.setCode(code);
// Clinical status
CodeableConcept clinicalStatus = new CodeableConcept();
clinicalStatus.addCoding()
.setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical")
.setCode(dto.getClinicalStatus());
condition.setClinicalStatus(clinicalStatus);
return condition;
}
MedicationRequest with SNOMED¶
public MedicationRequest createPrescription(PrescriptionDTO dto) {
MedicationRequest rx = new MedicationRequest();
// Patient reference
rx.setSubject(new Reference("Patient/" + dto.getPatientId()));
// SNOMED medication code
CodeableConcept medication = new CodeableConcept();
Coding coding = new Coding();
coding.setSystem("http://snomed.info/sct");
coding.setCode(dto.getMedicationCode()); // e.g., "108774000"
coding.setDisplay(dto.getMedicationDisplay()); // e.g., "Metformin"
medication.addCoding(coding);
rx.setMedication(medication);
// Dosage
Dosage dosage = new Dosage();
dosage.setText(dto.getInstructions());
dosage.setRoute(createRoute(dto.getRoute()));
dosage.setTiming(createTiming(dto.getFrequency()));
rx.addDosageInstruction(dosage);
return rx;
}
CRUD Operations¶
Create¶
@Service
public class ConditionService {
@Autowired
private IGenericClient fhirClient;
public String create(ConditionDTO dto) {
Condition condition = convertToFHIR(dto);
MethodOutcome outcome = fhirClient.create()
.resource(condition)
.execute();
return outcome.getId().getIdPart();
}
}
Read¶
public ConditionDTO read(String id) {
Condition condition = fhirClient.read()
.resource(Condition.class)
.withId(id)
.execute();
return convertToDTO(condition);
}
Update¶
public void update(String id, ConditionDTO dto) {
Condition condition = convertToFHIR(dto);
condition.setId(id);
fhirClient.update()
.resource(condition)
.execute();
}
Delete¶
Search¶
public List<ConditionDTO> searchByPatient(String patientId) {
Bundle bundle = fhirClient.search()
.forResource(Condition.class)
.where(Condition.SUBJECT.hasId(patientId))
.returnBundle(Bundle.class)
.execute();
return bundle.getEntry().stream()
.map(entry -> (Condition) entry.getResource())
.map(this::convertToDTO)
.collect(Collectors.toList());
}
Validation¶
Resource Validation¶
@Bean
public IValidatorModule validatorModule(FhirContext ctx) {
FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ctx);
instanceValidator.setValidationSupport(new DefaultProfileValidationSupport(ctx));
return instanceValidator;
}
Custom Validation¶
public void validateCondition(Condition condition) {
// Ensure ICD-10 code format
String code = condition.getCode().getCodingFirstRep().getCode();
if (!code.matches("[A-Z]\\d{2}(\\.\\d+)?")) {
throw new InvalidRequestException("Invalid ICD-10 code format");
}
}
Search Parameters¶
Patient Search¶
GET /api/fhir/Patient?name=John
GET /api/fhir/Patient?birthdate=1980-01-15
GET /api/fhir/Patient?identifier=MRN-12345
GET /api/fhir/Patient?_count=50
Condition Search¶
GET /api/fhir/Condition?patient=123
GET /api/fhir/Condition?code=E11.9
GET /api/fhir/Condition?clinical-status=active