package org.mitre.synthea.export; import ca.uhn.fhir.context.FhirContext; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import java.awt.geom.Point2D; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; import org.hl7.fhir.dstu3.model.Address; import org.hl7.fhir.dstu3.model.AllergyIntolerance; import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceCategory; import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceClinicalStatus; import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceCriticality; import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceType; import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceVerificationStatus; import org.hl7.fhir.dstu3.model.Basic; import org.hl7.fhir.dstu3.model.BooleanType; import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryRequestComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleType; import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; import org.hl7.fhir.dstu3.model.CarePlan.CarePlanActivityComponent; import org.hl7.fhir.dstu3.model.CarePlan.CarePlanActivityDetailComponent; import org.hl7.fhir.dstu3.model.CarePlan.CarePlanActivityStatus; import org.hl7.fhir.dstu3.model.CarePlan.CarePlanIntent; import org.hl7.fhir.dstu3.model.CarePlan.CarePlanStatus; import org.hl7.fhir.dstu3.model.Claim.ClaimStatus; import org.hl7.fhir.dstu3.model.Claim.ItemComponent; import org.hl7.fhir.dstu3.model.Claim.ProcedureComponent; import org.hl7.fhir.dstu3.model.Claim.SpecialConditionComponent; import org.hl7.fhir.dstu3.model.CodeType; import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.Condition; import org.hl7.fhir.dstu3.model.Condition.ConditionClinicalStatus; import org.hl7.fhir.dstu3.model.Condition.ConditionVerificationStatus; import org.hl7.fhir.dstu3.model.ContactPoint; import org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem; import org.hl7.fhir.dstu3.model.Coverage; import org.hl7.fhir.dstu3.model.DateTimeType; import org.hl7.fhir.dstu3.model.DateType; import org.hl7.fhir.dstu3.model.DecimalType; import org.hl7.fhir.dstu3.model.Device; import org.hl7.fhir.dstu3.model.Device.FHIRDeviceStatus; import org.hl7.fhir.dstu3.model.DiagnosticReport; import org.hl7.fhir.dstu3.model.DiagnosticReport.DiagnosticReportStatus; import org.hl7.fhir.dstu3.model.Dosage; import org.hl7.fhir.dstu3.model.Encounter.EncounterHospitalizationComponent; import org.hl7.fhir.dstu3.model.Encounter.EncounterStatus; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; import org.hl7.fhir.dstu3.model.ExplanationOfBenefit; import org.hl7.fhir.dstu3.model.Extension; import org.hl7.fhir.dstu3.model.Goal.GoalStatus; import org.hl7.fhir.dstu3.model.HumanName; import org.hl7.fhir.dstu3.model.Identifier; import org.hl7.fhir.dstu3.model.ImagingStudy.ImagingStudySeriesComponent; import org.hl7.fhir.dstu3.model.ImagingStudy.ImagingStudySeriesInstanceComponent; import org.hl7.fhir.dstu3.model.ImagingStudy.InstanceAvailability; import org.hl7.fhir.dstu3.model.Immunization; import org.hl7.fhir.dstu3.model.Immunization.ImmunizationStatus; import org.hl7.fhir.dstu3.model.IntegerType; import org.hl7.fhir.dstu3.model.Media.DigitalMediaType; import org.hl7.fhir.dstu3.model.MedicationAdministration; import org.hl7.fhir.dstu3.model.MedicationAdministration.MedicationAdministrationDosageComponent; import org.hl7.fhir.dstu3.model.MedicationAdministration.MedicationAdministrationStatus; import org.hl7.fhir.dstu3.model.MedicationRequest; import org.hl7.fhir.dstu3.model.MedicationRequest.MedicationRequestIntent; import org.hl7.fhir.dstu3.model.MedicationRequest.MedicationRequestRequesterComponent; import org.hl7.fhir.dstu3.model.MedicationRequest.MedicationRequestStatus; import org.hl7.fhir.dstu3.model.Meta; import org.hl7.fhir.dstu3.model.Money; import org.hl7.fhir.dstu3.model.Narrative; import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus; import org.hl7.fhir.dstu3.model.Observation.ObservationComponentComponent; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; import org.hl7.fhir.dstu3.model.Organization; import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Patient.PatientCommunicationComponent; import org.hl7.fhir.dstu3.model.Period; import org.hl7.fhir.dstu3.model.PositiveIntType; import org.hl7.fhir.dstu3.model.Practitioner; import org.hl7.fhir.dstu3.model.Procedure.ProcedureStatus; import org.hl7.fhir.dstu3.model.Quantity; import org.hl7.fhir.dstu3.model.Reference; import org.hl7.fhir.dstu3.model.ReferralRequest; import org.hl7.fhir.dstu3.model.Resource; import org.hl7.fhir.dstu3.model.SimpleQuantity; import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.SupplyDelivery; import org.hl7.fhir.dstu3.model.SupplyDelivery.SupplyDeliveryStatus; import org.hl7.fhir.dstu3.model.SupplyDelivery.SupplyDeliverySuppliedItemComponent; import org.hl7.fhir.dstu3.model.Timing; import org.hl7.fhir.dstu3.model.Timing.TimingRepeatComponent; import org.hl7.fhir.dstu3.model.Timing.UnitsOfTime; import org.hl7.fhir.dstu3.model.Type; import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.mitre.synthea.engine.Components; import org.mitre.synthea.engine.Components.Attachment; import org.mitre.synthea.helpers.Config; import org.mitre.synthea.helpers.SimpleCSV; import org.mitre.synthea.helpers.Utilities; import org.mitre.synthea.world.agents.Clinician; import org.mitre.synthea.world.agents.Payer; import org.mitre.synthea.world.agents.Person; import org.mitre.synthea.world.agents.Provider; import org.mitre.synthea.world.concepts.Claim; import org.mitre.synthea.world.concepts.Costs; import org.mitre.synthea.world.concepts.HealthRecord; import org.mitre.synthea.world.concepts.HealthRecord.CarePlan; import org.mitre.synthea.world.concepts.HealthRecord.Code; import org.mitre.synthea.world.concepts.HealthRecord.Encounter; import org.mitre.synthea.world.concepts.HealthRecord.EncounterType; import org.mitre.synthea.world.concepts.HealthRecord.ImagingStudy; import org.mitre.synthea.world.concepts.HealthRecord.Medication; import org.mitre.synthea.world.concepts.HealthRecord.Observation; import org.mitre.synthea.world.concepts.HealthRecord.Procedure; import org.mitre.synthea.world.concepts.HealthRecord.Report; public class FhirStu3 { // HAPI FHIR warns that the context creation is expensive, and should be performed // per-application, not per-record private static final FhirContext FHIR_CTX = FhirContext.forDstu3(); private static final String SNOMED_URI = "http://snomed.info/sct"; private static final String LOINC_URI = "http://loinc.org"; private static final String RXNORM_URI = "http://www.nlm.nih.gov/research/umls/rxnorm"; private static final String CVX_URI = "http://hl7.org/fhir/sid/cvx"; private static final String DISCHARGE_URI = "http://www.nubc.org/patient-discharge"; private static final String SHR_EXT = "http://standardhealthrecord.org/fhir/StructureDefinition/"; private static final String SYNTHEA_EXT = "http://synthetichealth.github.io/synthea/"; private static final String UNITSOFMEASURE_URI = "http://unitsofmeasure.org"; private static final String DICOM_DCM_URI = "http://dicom.nema.org/resources/ontology/DCM"; @SuppressWarnings("rawtypes") private static final Map raceEthnicityCodes = loadRaceEthnicityCodes(); @SuppressWarnings("rawtypes") private static final Map languageLookup = loadLanguageLookup(); private static final boolean USE_SHR_EXTENSIONS = Boolean.parseBoolean(Config.get("exporter.fhir.use_shr_extensions")); protected static boolean TRANSACTION_BUNDLE = Boolean.parseBoolean(Config.get("exporter.fhir.transaction_bundle")); private static final String COUNTRY_CODE = Config.get("generate.geography.country_code"); private static final Table<String,String,String> SHR_MAPPING = loadSHRMapping(); @SuppressWarnings("rawtypes") private static Map loadRaceEthnicityCodes() { String filename = "race_ethnicity_codes.json"; try { String json = Utilities.readResource(filename); Gson g = new Gson(); return g.fromJson(json, HashMap.class); } catch (Exception e) { System.err.println("ERROR: unable to load json: " + filename); e.printStackTrace(); throw new ExceptionInInitializerError(e); } } @SuppressWarnings("rawtypes") private static Map loadLanguageLookup() { String filename = "language_lookup.json"; try { String json = Utilities.readResource(filename); Gson g = new Gson(); return g.fromJson(json, HashMap.class); } catch (Exception e) { System.err.println("ERROR: unable to load json: " + filename); e.printStackTrace(); throw new ExceptionInInitializerError(e); } } private static Table<String, String, String> loadSHRMapping() { if (!USE_SHR_EXTENSIONS) { // don't bother creating the table unless we need it return null; } Table<String,String,String> mappingTable = HashBasedTable.create(); List<LinkedHashMap<String,String>> csvData; try { csvData = SimpleCSV.parse(Utilities.readResource("shr_mapping.csv")); } catch (IOException e) { e.printStackTrace(); return null; } for (LinkedHashMap<String,String> line : csvData) { String system = line.get("SYSTEM"); String code = line.get("CODE"); String url = line.get("URL"); mappingTable.put(system, code, url); } return mappingTable; } /** * Convert the given Person into a FHIR Bundle, containing the Patient and the * associated entries from their health record. * * @param person Person to generate the FHIR from * @param stopTime Time the simulation ended * @return FHIR Bundle containing the Person's health record. */ public static Bundle convertToFHIR(Person person, long stopTime) { Bundle bundle = new Bundle(); if (TRANSACTION_BUNDLE) { bundle.setType(BundleType.TRANSACTION); } else { bundle.setType(BundleType.COLLECTION); } BundleEntryComponent personEntry = basicInfo(person, bundle, stopTime); for (Encounter encounter : person.record.encounters) { BundleEntryComponent encounterEntry = encounter(person, personEntry, bundle, encounter); for (HealthRecord.Entry condition : encounter.conditions) { condition(personEntry, bundle, encounterEntry, condition); } for (HealthRecord.Entry allergy : encounter.allergies) { allergy(personEntry, bundle, encounterEntry, allergy); } for (Observation observation : encounter.observations) { // If the Observation contains an attachment, use a Media resource, since // Observation resources in stu3 don't support Attachments if (observation.value instanceof Attachment) { media(personEntry, bundle, encounterEntry, observation); } else { observation(personEntry, bundle, encounterEntry, observation); } } for (Procedure procedure : encounter.procedures) { procedure(personEntry, bundle, encounterEntry, procedure); } for (Medication medication : encounter.medications) { medication(personEntry, bundle, encounterEntry, medication); } for (HealthRecord.Entry immunization : encounter.immunizations) { immunization(personEntry, bundle, encounterEntry, immunization); } for (Report report : encounter.reports) { report(personEntry, bundle, encounterEntry, report); } for (CarePlan careplan : encounter.careplans) { careplan(personEntry, bundle, encounterEntry, careplan); } for (ImagingStudy imagingStudy : encounter.imagingStudies) { imagingStudy(personEntry, bundle, encounterEntry, imagingStudy); } for (HealthRecord.Device device : encounter.devices) { device(personEntry, bundle, device); } for (HealthRecord.Supply supply : encounter.supplies) { supplyDelivery(personEntry, bundle, supply, encounter); } // one claim per encounter BundleEntryComponent encounterClaim = encounterClaim(personEntry, bundle, encounterEntry, encounter.claim); explanationOfBenefit(personEntry,bundle,encounterEntry,person, encounterClaim, encounter); } return bundle; } /** * Convert the given Person into a JSON String, containing a FHIR Bundle of the Person and the * associated entries from their health record. * * @param person Person to generate the FHIR JSON for * @param stopTime Time the simulation ended * @return String containing a JSON representation of a FHIR Bundle containing the Person's * health record. */ public static String convertToFHIRJson(Person person, long stopTime) { Bundle bundle = convertToFHIR(person, stopTime); String bundleJson = FHIR_CTX.newJsonParser().setPrettyPrint(true) .encodeResourceToString(bundle); return bundleJson; } /** * Map the given Person to a FHIR Patient resource, and add it to the given Bundle. * * @param person The Person * @param bundle The Bundle to add to * @param stopTime Time the simulation ended * @return The created Entry */ @SuppressWarnings("rawtypes") private static BundleEntryComponent basicInfo(Person person, Bundle bundle, long stopTime) { Patient patientResource = new Patient(); patientResource.addIdentifier().setSystem("https://github.com/synthetichealth/synthea") .setValue((String) person.attributes.get(Person.ID)); Code mrnCode = new Code("http://hl7.org/fhir/v2/0203", "MR", "Medical Record Number"); patientResource.addIdentifier() .setType(mapCodeToCodeableConcept(mrnCode, "http://hl7.org/fhir/v2/0203")) .setSystem("http://hospital.smarthealthit.org") .setValue((String) person.attributes.get(Person.ID)); Code ssnCode = new Code("http://hl7.org/fhir/identifier-type", "SB", "Social Security Number"); patientResource.addIdentifier() .setType(mapCodeToCodeableConcept(ssnCode, "http://hl7.org/fhir/identifier-type")) .setSystem("http://hl7.org/fhir/sid/us-ssn") .setValue((String) person.attributes.get(Person.IDENTIFIER_SSN)); if (person.attributes.get(Person.IDENTIFIER_DRIVERS) != null) { Code driversCode = new Code("http://hl7.org/fhir/v2/0203", "DL", "Driver's License"); patientResource.addIdentifier() .setType(mapCodeToCodeableConcept(driversCode, "http://hl7.org/fhir/v2/0203")) .setSystem("urn:oid:2.16.840.1.113883.4.3.25") .setValue((String) person.attributes.get(Person.IDENTIFIER_DRIVERS)); } if (person.attributes.get(Person.IDENTIFIER_PASSPORT) != null) { Code passportCode = new Code("http://hl7.org/fhir/v2/0203", "PPN", "Passport Number"); patientResource.addIdentifier() .setType(mapCodeToCodeableConcept(passportCode, "http://hl7.org/fhir/v2/0203")) .setSystem(SHR_EXT + "passportNumber") .setValue((String) person.attributes.get(Person.IDENTIFIER_PASSPORT)); } // We do not yet account for mixed race Extension raceExtension = new Extension( "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"); String race = (String) person.attributes.get(Person.RACE); String raceDisplay; switch (race) { case "white": raceDisplay = "White"; break; case "black": raceDisplay = "Black or African American"; break; case "asian": raceDisplay = "Asian"; break; case "native": raceDisplay = "American Indian or Alaska Native"; break; default: // Other (Put Hawaiian and Pacific Islander here for now) raceDisplay = "Other"; break; } String raceNum = (String) raceEthnicityCodes.get(race); Extension raceCodingExtension = new Extension("ombCategory"); Coding raceCoding = new Coding(); if (raceDisplay.equals("Other")) { raceCoding.setSystem("http://hl7.org/fhir/v3/NullFlavor"); raceCoding.setCode("UNK"); raceCoding.setDisplay("Unknown"); } else { raceCoding.setSystem("urn:oid:2.16.840.1.113883.6.238"); raceCoding.setCode(raceNum); raceCoding.setDisplay(raceDisplay); } raceCodingExtension.setValue(raceCoding); raceExtension.addExtension(raceCodingExtension); Extension raceTextExtension = new Extension("text"); raceTextExtension.setValue(new StringType(raceDisplay)); raceExtension.addExtension(raceTextExtension); patientResource.addExtension(raceExtension); // We do not yet account for mixed ethnicity Extension ethnicityExtension = new Extension( "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"); String ethnicity = (String) person.attributes.get(Person.ETHNICITY); String ethnicityDisplay; if (ethnicity.equals("hispanic")) { ethnicity = "hispanic"; ethnicityDisplay = "Hispanic or Latino"; } else { ethnicity = "nonhispanic"; ethnicityDisplay = "Not Hispanic or Latino"; } String ethnicityNum = (String) raceEthnicityCodes.get(ethnicity); Extension ethnicityCodingExtension = new Extension("ombCategory"); Coding ethnicityCoding = new Coding(); ethnicityCoding.setSystem("urn:oid:2.16.840.1.113883.6.238"); ethnicityCoding.setCode(ethnicityNum); ethnicityCoding.setDisplay(ethnicityDisplay); ethnicityCodingExtension.setValue(ethnicityCoding); ethnicityExtension.addExtension(ethnicityCodingExtension); Extension ethnicityTextExtension = new Extension("text"); ethnicityTextExtension.setValue(new StringType(ethnicityDisplay)); ethnicityExtension.addExtension(ethnicityTextExtension); patientResource.addExtension(ethnicityExtension); String firstLanguage = (String) person.attributes.get(Person.FIRST_LANGUAGE); Map languageMap = (Map) languageLookup.get(firstLanguage); Code languageCode = new Code((String) languageMap.get("system"), (String) languageMap.get("code"), (String) languageMap.get("display")); List<PatientCommunicationComponent> communication = new ArrayList<PatientCommunicationComponent>(); communication.add(new PatientCommunicationComponent( mapCodeToCodeableConcept(languageCode, (String) languageMap.get("system")))); patientResource.setCommunication(communication); HumanName name = patientResource.addName(); name.setUse(HumanName.NameUse.OFFICIAL); name.addGiven((String) person.attributes.get(Person.FIRST_NAME)); name.setFamily((String) person.attributes.get(Person.LAST_NAME)); if (person.attributes.get(Person.NAME_PREFIX) != null) { name.addPrefix((String) person.attributes.get(Person.NAME_PREFIX)); } if (person.attributes.get(Person.NAME_SUFFIX) != null) { name.addSuffix((String) person.attributes.get(Person.NAME_SUFFIX)); } if (person.attributes.get(Person.MAIDEN_NAME) != null) { HumanName maidenName = patientResource.addName(); maidenName.setUse(HumanName.NameUse.MAIDEN); maidenName.addGiven((String) person.attributes.get(Person.FIRST_NAME)); maidenName.setFamily((String) person.attributes.get(Person.MAIDEN_NAME)); if (person.attributes.get(Person.NAME_PREFIX) != null) { maidenName.addPrefix((String) person.attributes.get(Person.NAME_PREFIX)); } if (person.attributes.get(Person.NAME_SUFFIX) != null) { maidenName.addSuffix((String) person.attributes.get(Person.NAME_SUFFIX)); } } Extension mothersMaidenNameExtension = new Extension( "http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName"); String mothersMaidenName = (String) person.attributes.get(Person.NAME_MOTHER); mothersMaidenNameExtension.setValue(new StringType(mothersMaidenName)); patientResource.addExtension(mothersMaidenNameExtension); long birthdate = (long) person.attributes.get(Person.BIRTHDATE); patientResource.setBirthDate(new Date(birthdate)); Extension birthSexExtension = new Extension( "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex"); if (person.attributes.get(Person.GENDER).equals("M")) { patientResource.setGender(AdministrativeGender.MALE); birthSexExtension.setValue(new CodeType("M")); } else if (person.attributes.get(Person.GENDER).equals("F")) { patientResource.setGender(AdministrativeGender.FEMALE); birthSexExtension.setValue(new CodeType("F")); } patientResource.addExtension(birthSexExtension); String state = (String) person.attributes.get(Person.STATE); Address addrResource = patientResource.addAddress(); addrResource.addLine((String) person.attributes.get(Person.ADDRESS)) .setCity((String) person.attributes.get(Person.CITY)) .setPostalCode((String) person.attributes.get(Person.ZIP)) .setState(state); if (COUNTRY_CODE != null) { addrResource.setCountry(COUNTRY_CODE); } Address birthplace = new Address(); birthplace.setCity((String) person.attributes.get(Person.BIRTH_CITY)) .setState((String) person.attributes.get(Person.BIRTH_STATE)) .setCountry((String) person.attributes.get(Person.BIRTH_COUNTRY)); Extension birthplaceExtension = new Extension( "http://hl7.org/fhir/StructureDefinition/birthPlace"); birthplaceExtension.setValue(birthplace); patientResource.addExtension(birthplaceExtension); if (person.attributes.get(Person.MULTIPLE_BIRTH_STATUS) != null) { patientResource.setMultipleBirth( new IntegerType((int) person.attributes.get(Person.MULTIPLE_BIRTH_STATUS))); } else { patientResource.setMultipleBirth(new BooleanType(false)); } patientResource.addTelecom().setSystem(ContactPoint.ContactPointSystem.PHONE) .setUse(ContactPoint.ContactPointUse.HOME) .setValue((String) person.attributes.get(Person.TELECOM)); String maritalStatus = ((String) person.attributes.get(Person.MARITAL_STATUS)); if (maritalStatus != null) { Code maritalStatusCode = new Code("http://hl7.org/fhir/v3/MaritalStatus", maritalStatus, maritalStatus); patientResource.setMaritalStatus( mapCodeToCodeableConcept(maritalStatusCode, "http://hl7.org/fhir/v3/MaritalStatus")); } else { Code maritalStatusCode = new Code("http://hl7.org/fhir/v3/MaritalStatus", "S", "Never Married"); patientResource.setMaritalStatus( mapCodeToCodeableConcept(maritalStatusCode, "http://hl7.org/fhir/v3/MaritalStatus")); } Point2D.Double coord = person.getLonLat(); if (coord != null) { Extension geolocation = addrResource.addExtension(); geolocation.setUrl("http://hl7.org/fhir/StructureDefinition/geolocation"); geolocation.addExtension("latitude", new DecimalType(coord.getY())); geolocation.addExtension("longitude", new DecimalType(coord.getX())); } if (!person.alive(stopTime)) { patientResource.setDeceased( convertFhirDateTime((Long) person.attributes.get(Person.DEATHDATE), true)); } String generatedBySynthea = "Generated by <a href=\"https://github.com/synthetichealth/synthea\">Synthea</a>." + "Version identifier: " + Utilities.SYNTHEA_VERSION + " . " + " Person seed: " + person.seed + " Population seed: " + person.populationSeed; patientResource.setText(new Narrative().setStatus(NarrativeStatus.GENERATED) .setDiv(new XhtmlNode(NodeType.Element).setValue(generatedBySynthea))); if (USE_SHR_EXTENSIONS) { patientResource.setMeta(new Meta().addProfile(SHR_EXT + "shr-entity-Patient")); // Patient profile requires race, ethnicity, birthsex, // MothersMaidenName, FathersName, Person-extension patientResource.addExtension() .setUrl(SHR_EXT + "shr-actor-FictionalPerson-extension") .setValue(new BooleanType(true)); String fathersName = (String) person.attributes.get(Person.NAME_FATHER); Extension fathersNameExtension = new Extension( SHR_EXT + "shr-entity-FathersName-extension", new HumanName().setText(fathersName)); patientResource.addExtension(fathersNameExtension); String ssn = (String) person.attributes.get(Person.IDENTIFIER_SSN); Extension ssnExtension = new Extension( SHR_EXT + "shr-demographics-SocialSecurityNumber-extension", new StringType(ssn)); patientResource.addExtension(ssnExtension); Basic personResource = new Basic(); // the only required field on this patient resource is code Coding fixedCode = new Coding( "http://standardhealthrecord.org/fhir/basic-resource-type", "shr-entity-Person", "shr-entity-Person"); personResource.setCode(new CodeableConcept().addCoding(fixedCode)); Meta personMeta = new Meta(); personMeta.addProfile(SHR_EXT + "shr-entity-Person"); personResource.setMeta(personMeta); BundleEntryComponent personEntry = newEntry(bundle, personResource); patientResource.addExtension() .setUrl(SHR_EXT + "shr-entity-Person-extension") .setValue(new Reference(personEntry.getFullUrl())); } // DALY and QALY values // we only write the last(current) one to the patient record Double dalyValue = (Double) person.attributes.get("most-recent-daly"); Double qalyValue = (Double) person.attributes.get("most-recent-qaly"); if (dalyValue != null) { Extension dalyExtension = new Extension(SYNTHEA_EXT + "disability-adjusted-life-years"); DecimalType daly = new DecimalType(dalyValue); dalyExtension.setValue(daly); patientResource.addExtension(dalyExtension); Extension qalyExtension = new Extension(SYNTHEA_EXT + "quality-adjusted-life-years"); DecimalType qaly = new DecimalType(qalyValue); qalyExtension.setValue(qaly); patientResource.addExtension(qalyExtension); } return newEntry(bundle, patientResource, (String) person.attributes.get(Person.ID)); } /** * Map the given Encounter into a FHIR Encounter resource, and add it to the given Bundle. * * @param personEntry Entry for the Person * @param bundle The Bundle to add to * @param encounter The current Encounter * @return The added Entry */ private static BundleEntryComponent encounter(Person person, BundleEntryComponent personEntry, Bundle bundle, Encounter encounter) { org.hl7.fhir.dstu3.model.Encounter encounterResource = new org.hl7.fhir.dstu3.model.Encounter(); encounterResource.setSubject(new Reference(personEntry.getFullUrl())); encounterResource.setStatus(EncounterStatus.FINISHED); if (encounter.codes.isEmpty()) { // wellness encounter encounterResource.addType().addCoding().setCode("185349003") .setDisplay("Encounter for check up").setSystem(SNOMED_URI); } else { Code code = encounter.codes.get(0); encounterResource.addType(mapCodeToCodeableConcept(code, SNOMED_URI)); } Coding classCode = new Coding(); classCode.setCode(EncounterType.fromString(encounter.type).code()); classCode.setSystem("http://terminology.hl7.org/CodeSystem/v3-ActCode"); encounterResource.setClass_(classCode); encounterResource .setPeriod(new Period() .setStart(new Date(encounter.start)) .setEnd(new Date(encounter.stop))); if (encounter.reason != null) { encounterResource.addReason().addCoding().setCode(encounter.reason.code) .setDisplay(encounter.reason.display).setSystem(SNOMED_URI); } if (encounter.provider != null) { String providerFullUrl = findProviderUrl(encounter.provider, bundle); if (providerFullUrl != null) { encounterResource.setServiceProvider(new Reference(providerFullUrl)); } else { BundleEntryComponent providerOrganization = provider(bundle, encounter.provider); encounterResource.setServiceProvider(new Reference(providerOrganization.getFullUrl())); } } else { // no associated provider, patient goes to wellness provider Provider provider = person.getProvider(EncounterType.WELLNESS, encounter.start); String providerFullUrl = findProviderUrl(provider, bundle); if (providerFullUrl != null) { encounterResource.setServiceProvider(new Reference(providerFullUrl)); } else { BundleEntryComponent providerOrganization = provider(bundle, provider); encounterResource.setServiceProvider(new Reference(providerOrganization.getFullUrl())); } } if (encounter.clinician != null) { String practitionerFullUrl = findPractitioner(encounter.clinician, bundle); if (practitionerFullUrl != null) { encounterResource.addParticipant().setIndividual(new Reference(practitionerFullUrl)); } else { BundleEntryComponent practitioner = practitioner(bundle, encounter.clinician); encounterResource.addParticipant().setIndividual(new Reference(practitioner.getFullUrl())); } } if (encounter.discharge != null) { EncounterHospitalizationComponent hospitalization = new EncounterHospitalizationComponent(); Code dischargeDisposition = new Code(DISCHARGE_URI, encounter.discharge.code, encounter.discharge.display); hospitalization .setDischargeDisposition(mapCodeToCodeableConcept(dischargeDisposition, DISCHARGE_URI)); encounterResource.setHospitalization(hospitalization); } if (USE_SHR_EXTENSIONS) { encounterResource.setMeta( new Meta().addProfile(SHR_EXT + "shr-encounter-EncounterPerformed")); // required fields for this profile are status & action-PerformedContext-extension Extension performedContext = new Extension(); performedContext.setUrl(SHR_EXT + "shr-action-PerformedContext-extension"); performedContext.addExtension( SHR_EXT + "shr-action-Status-extension", new CodeType("finished")); encounterResource.addExtension(performedContext); } return newEntry(bundle, encounterResource); } /** * Find the provider entry in this bundle, and return the associated "fullUrl" attribute. * @param provider A given provider. * @param bundle The current bundle being generated. * @return Provider.fullUrl if found, otherwise null. */ private static String findProviderUrl(Provider provider, Bundle bundle) { for (BundleEntryComponent entry : bundle.getEntry()) { if (entry.getResource().fhirType().equals("Organization")) { Organization org = (Organization) entry.getResource(); if (org.getIdentifierFirstRep().getValue().equals(provider.getResourceID())) { return entry.getFullUrl(); } } } return null; } /** * Find the Practitioner entry in this bundle, and return the associated "fullUrl" * attribute. * @param clinician A given clinician. * @param bundle The current bundle being generated. * @return Practitioner.fullUrl if found, otherwise null. */ private static String findPractitioner(Clinician clinician, Bundle bundle) { for (BundleEntryComponent entry : bundle.getEntry()) { if (entry.getResource().fhirType().equals("Practitioner")) { Practitioner doc = (Practitioner) entry.getResource(); if (doc.getIdentifierFirstRep().getValue().equals("" + clinician.identifier)) { return entry.getFullUrl(); } } } return null; } /** * Create an entry for the given Claim, which references a Medication. * * @param personEntry Entry for the person * @param bundle The Bundle to add to * @param encounterEntry The current Encounter * @param claim the Claim object * @param medicationEntry The Entry for the Medication object, previously created * @return the added Entry */ private static BundleEntryComponent medicationClaim(BundleEntryComponent personEntry, Bundle bundle, BundleEntryComponent encounterEntry, Claim claim, BundleEntryComponent medicationEntry) { org.hl7.fhir.dstu3.model.Claim claimResource = new org.hl7.fhir.dstu3.model.Claim(); org.hl7.fhir.dstu3.model.Encounter encounterResource = (org.hl7.fhir.dstu3.model.Encounter) encounterEntry.getResource(); claimResource.setStatus(ClaimStatus.ACTIVE); claimResource.setUse(org.hl7.fhir.dstu3.model.Claim.Use.COMPLETE); // duration of encounter claimResource.setBillablePeriod(encounterResource.getPeriod()); claimResource.setPatient(new Reference(personEntry.getFullUrl())); claimResource.setOrganization(encounterResource.getServiceProvider()); // add item for encounter claimResource.addItem(new org.hl7.fhir.dstu3.model.Claim.ItemComponent(new PositiveIntType(1)) .addEncounter(new Reference(encounterEntry.getFullUrl()))); // add prescription. claimResource.setPrescription(new Reference(medicationEntry.getFullUrl())); Money moneyResource = new Money(); moneyResource.setValue(claim.getTotalClaimCost()); moneyResource.setCode("USD"); moneyResource.setSystem("urn:iso:std:iso:4217"); claimResource.setTotal(moneyResource); return newEntry(bundle, claimResource); } /** * Create an entry for the given Claim, associated to an Encounter. * * @param personEntry Entry for the person * @param bundle The Bundle to add to * @param encounterEntry The current Encounter * @param claim the Claim object * @return the added Entry */ private static BundleEntryComponent encounterClaim(BundleEntryComponent personEntry, Bundle bundle, BundleEntryComponent encounterEntry, Claim claim) { org.hl7.fhir.dstu3.model.Claim claimResource = new org.hl7.fhir.dstu3.model.Claim(); org.hl7.fhir.dstu3.model.Encounter encounterResource = (org.hl7.fhir.dstu3.model.Encounter) encounterEntry.getResource(); claimResource.setStatus(ClaimStatus.ACTIVE); claimResource.setUse(org.hl7.fhir.dstu3.model.Claim.Use.COMPLETE); // duration of encounter claimResource.setBillablePeriod(encounterResource.getPeriod()); claimResource.setPatient(new Reference(personEntry.getFullUrl())); claimResource.setOrganization(encounterResource.getServiceProvider()); // add item for encounter claimResource.addItem(new ItemComponent(new PositiveIntType(1)) .addEncounter(new Reference(encounterEntry.getFullUrl()))); int itemSequence = 2; int conditionSequence = 1; int procedureSequence = 1; int informationSequence = 1; for (HealthRecord.Entry item : claim.items) { if (Costs.hasCost(item)) { // update claimItems list ItemComponent claimItem = new ItemComponent(new PositiveIntType(itemSequence)); Code primaryCode = item.codes.get(0); String system = ExportHelper.getSystemURI(primaryCode.system); CodeableConcept serviceProvided = new CodeableConcept() .addCoding(new Coding() .setCode(primaryCode.code) .setVersion("v1") .setSystem(system)); claimItem.setService(serviceProvided); // calculate the cost of the procedure Money moneyResource = new Money(); moneyResource.setCode("USD"); moneyResource.setSystem("urn:iso:std:iso:4217"); moneyResource.setValue(item.getCost()); claimItem.setNet(moneyResource); if (item instanceof HealthRecord.Procedure) { Type procedureReference = new Reference(item.fullUrl); ProcedureComponent claimProcedure = new ProcedureComponent( new PositiveIntType(procedureSequence), procedureReference); claimResource.addProcedure(claimProcedure); claimItem.addProcedureLinkId(procedureSequence); procedureSequence++; } else { Reference informationReference = new Reference(item.fullUrl); SpecialConditionComponent informationComponent = new SpecialConditionComponent(); informationComponent.setSequence(informationSequence); informationComponent.setValue(informationReference); CodeableConcept category = new CodeableConcept(); category.getCodingFirstRep() .setSystem("http://hl7.org/fhir/claiminformationcategory") .setCode("info"); informationComponent.setCategory(category); claimResource.addInformation(informationComponent); claimItem.addInformationLinkId(informationSequence); claimItem.setService(claimResource.getType()); informationSequence++; } claimResource.addItem(claimItem); } else { // assume it's a Condition, we don't have a Condition class specifically // add diagnosisComponent to claim Reference diagnosisReference = new Reference(item.fullUrl); org.hl7.fhir.dstu3.model.Claim.DiagnosisComponent diagnosisComponent = new org.hl7.fhir.dstu3.model.Claim.DiagnosisComponent( new PositiveIntType(conditionSequence), diagnosisReference); claimResource.addDiagnosis(diagnosisComponent); // update claimItems with diagnosis ItemComponent diagnosisItem = new ItemComponent(new PositiveIntType(itemSequence)); diagnosisItem.addDiagnosisLinkId(conditionSequence); claimResource.addItem(diagnosisItem); conditionSequence++; } itemSequence++; } Money moneyResource = new Money(); moneyResource.setCode("USD"); moneyResource.setSystem("urn:iso:std:iso:4217"); moneyResource.setValue(claim.getTotalClaimCost()); claimResource.setTotal(moneyResource); return newEntry(bundle, claimResource); } /** * Create an extension in with a valueMoney in USD. * @param url The url of the extension. * @param value The value in USD. * @return the Extension */ private static Extension createMoneyExtension(String url, double value) { Money money = new Money(); money.setValue(value); money.setSystem("urn:iso:std:iso:4217"); money.setCode("USD"); Extension extension = new Extension(); extension.setUrl(url); extension.setValue(money); return extension; } /** * Create an explanation of benefit resource for each claim, detailing insurance * information. * * @param personEntry Entry for the person * @param bundle The Bundle to add to * @param encounterEntry The current Encounter * @param claimEntry the Claim object * @param person the person the health record belongs to * @param encounter the current Encounter as an object * @return the added entry */ private static BundleEntryComponent explanationOfBenefit(BundleEntryComponent personEntry, Bundle bundle, BundleEntryComponent encounterEntry, Person person, BundleEntryComponent claimEntry, Encounter encounter) { boolean inpatient = false; boolean outpatient = false; EncounterType type = EncounterType.fromString(encounter.type); if (type == EncounterType.INPATIENT) { inpatient = true; // Provider enum doesn't include outpatient, but it can still be // an encounter type. } else if (type == EncounterType.AMBULATORY || type == EncounterType.WELLNESS) { outpatient = true; } ExplanationOfBenefit eob = new ExplanationOfBenefit(); org.hl7.fhir.dstu3.model.Encounter encounterResource = (org.hl7.fhir.dstu3.model.Encounter) encounterEntry.getResource(); Meta meta = new Meta(); if (inpatient) { meta.addProfile("https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-claim"); } else if (outpatient) { meta.addProfile("https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-outpatient-claim"); } eob.setMeta(meta); // First add the extensions // will have to deal with different claim types (e.g. inpatient vs outpatient) if (inpatient) { //https://www.cms.gov/Medicare/Medicare-Fee-for-Service-Payment/AcuteInpatientPPS/Indirect-Medical-Education-IME // Extra cost for educational hospitals eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-ime-op-clm-val-amt-extension", 400)); // DSH payment-- Massachusetts does not make DSH payments at all, so set to 0 for now // https://www.cms.gov/Medicare/Medicare-Fee-for-Service-Payment/AcuteInpatientPPS/dsh eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-dsh-op-clm-val-amt-extension", 0)); // The pass through per diem rate // not really defined by CMS eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-clm-pass-thru-per-diem-amt-extension", 0)); // Professional charge eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-nch-profnl-cmpnt-chrg-amt-extension", 0)); // total claim PPS charge eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-clm-tot-pps-cptl-amt-extension", 0)); // Deductible Amount eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-nch-bene-ip-ddctbl-amt-extension", 0)); // Coinsurance Liability eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-nch-bene-pta-coinsrnc-lblty-amt-extension", 0)); // Non-covered Charge Amount eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-nch-ip-ncvrd-chrg-amt-extension", 0)); // Total Deductible/Coinsurance Amount eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-nch-ip-tot-ddctn-amt-extension", 0)); // PPS Capital DSH Amount eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-clm-pps-cptl-dsprprtnt-shr-amt-extension", 0)); // PPS Capital Exception Amount eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-clm-pps-cptl-excptn-amt-extension", 0)); // PPS FSP eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-clm-pps-cptl-fsp-amt-extension", 0)); // PPS IME eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-clm-pps-cptl-ime-amt-extension", 400)); // PPS Capital Outlier Amount eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-clm-pps-cptl-outlier-amt-extension", 0)); // Old capital hold harmless amount eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-clm-pps-old-cptl-hld-hrmls-amt-extension", 0)); // NCH DRG Outlier Approved Payment Amount eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-nch-drg-outlier-aprvd-pmt-amt-extension", 0)); // NCH Beneficiary Blood Deductible Liability Amount eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-nch-bene-blood-ddctbl-lblty-am-extension", 0)); // Non-payment reason eob.addExtension() .setUrl("https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-clm-mdcr-non-pmt-rsn-cd-extension") .setValue(new Coding() .setSystem("https://bluebutton.cms.gov/assets/ig/CodeSystem-clm-mdcr-non-pmt-rsn-cd") .setDisplay("All other reasons for non-payment") .setCode("N")); // Prepayment eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-prpayamt-extension", 0)); // FI or MAC number eob.addExtension() .setUrl("https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-fi-num-extension") .setValue(new Identifier() .setValue("002000") // No system page exists yet .setSystem("https://bluebutton.cms.gov/assets/ig/CodeSystem-fi-num")); } else if (outpatient) { // Professional component charge amount eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-outpatient-nch-profnl-cmpnt-chrg-amt-extension", 0)); // Deductible amount eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-outpatient-nch-bene-ptb-ddctbl-amt-extension", 0)); // Coinsurance amount eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-outpatient-nch-bene-ptb-coinsrnc-amt-extension", 0)); // Provider Payment eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-outpatient-clm-op-prvdr-pmt-amt-extension", 0)); // Beneficiary payment eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-outpatient-clm-op-bene-pmt-amt-extension", 0)); // Beneficiary Blood Deductible Liability Amount eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-outpatient-nch-bene-blood-ddctbl-lblty-am-extension", 0)); // Claim Medicare Non Payment Reason Code eob.addExtension() .setUrl("https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-outpatient-clm-mdcr-non-pmt-rsn-cd-extension") .setValue(new Coding() .setDisplay("All other reasons for non-payment") .setSystem("https://bluebutton.cms.gov/assets/ig/CodeSystem-clm-mdcr-non-pmt-rsn-cd") .setCode("N")); // NCH Primary Payer Claim Paid Amount eob.addExtension(createMoneyExtension( "https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-outpatient-prpayamt-extension", 0)); // FI or MAC number eob.addExtension() .setUrl("https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-outpatient-fi-num-extension") .setValue(new Identifier() .setValue("002000") // No system page exists yet .setSystem("https://bluebutton.cms.gov/assets/ig/CodeSystem-fi-num")); } // according to CMS guidelines claims have 12 months to be // billed, so we set the billable period to 1 year after // services have ended (the encounter ends). Calendar cal = Calendar.getInstance(); cal.setTime(encounterResource.getPeriod().getEnd()); cal.add(Calendar.YEAR,1); Period billablePeriod = new Period() .setStart(encounterResource .getPeriod() .getEnd()) .setEnd(cal.getTime()); if (inpatient) { billablePeriod.addExtension(new Extension() .setUrl("https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-claim-query-cd-extension") .setValue(new Coding() .setCode("3") .setSystem("https://bluebutton.cms.gov/assets/ig/ValueSet-claim-query-cd") .setDisplay("Final Bill"))); } else if (outpatient) { billablePeriod.addExtension(new Extension() .setUrl("https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-outpatient-claim-query-cd-extension") .setValue(new Coding() .setCode("3") .setSystem("https://bluebutton.cms.gov/assets/ig/ValueSet-claim-query-cd") .setDisplay("Final Bill"))); } eob.setBillablePeriod(billablePeriod); // cost is hardcoded to be USD in claim so this should be fine as well Money totalCost = new Money(); totalCost.setSystem("urn:iso:std:iso:4217"); totalCost.setCode("USD"); totalCost.setValue(encounter.claim.getTotalClaimCost()); eob.setTotalCost(totalCost); // Set References eob.setPatient(new Reference(personEntry.getFullUrl())); if (encounter.provider != null) { // This is what should happen if BlueButton 2.0 wasn't needlessly restrictive // String providerUrl = findProviderUrl(encounter.provider, bundle); // eob.setOrganization(new Reference().setReference(providerUrl)); // Instead, we'll create the BlueButton 2.0 reference via identifier... Identifier identifier = new Identifier(); identifier.setValue(encounter.provider.getResourceID()); eob.setOrganization(new Reference().setIdentifier(identifier)); } // Get the insurance info at the time that the encounter happened. Payer payer = encounter.claim.payer; Coverage coverage = new Coverage(); coverage.setId("coverage"); coverage.setType(new CodeableConcept().setText(payer.getName())); eob.addContained(coverage); ExplanationOfBenefit.InsuranceComponent insuranceComponent = new ExplanationOfBenefit.InsuranceComponent(); insuranceComponent.setCoverage(new Reference("#coverage")); eob.setInsurance(insuranceComponent); org.hl7.fhir.dstu3.model.Claim claim = (org.hl7.fhir.dstu3.model.Claim) claimEntry.getResource(); eob.addIdentifier() .setSystem("https://bluebutton.cms.gov/resources/variables/clm_id") .setValue(claim.getId()); // Hardcoded group id eob.addIdentifier() .setSystem("https://bluebutton.cms.gov/resources/identifier/claim-group") .setValue("99999999999"); eob.setStatus(org.hl7.fhir.dstu3.model.ExplanationOfBenefit.ExplanationOfBenefitStatus.ACTIVE); if (!inpatient && !outpatient) { eob.setClaim(new Reference() .setReference(claimEntry.getFullUrl())); eob.setReferral(new Reference("#1")); eob.setCreated(encounterResource.getPeriod().getEnd()); } eob.setType(claim.getType()); List<ExplanationOfBenefit.DiagnosisComponent> eobDiag = new ArrayList<>(); for (org.hl7.fhir.dstu3.model.Claim.DiagnosisComponent claimDiagnosis : claim.getDiagnosis()) { ExplanationOfBenefit.DiagnosisComponent diagnosisComponent = new ExplanationOfBenefit.DiagnosisComponent(); diagnosisComponent.setDiagnosis(claimDiagnosis.getDiagnosis()); diagnosisComponent.getType().add(new CodeableConcept() .addCoding(new Coding() .setCode("principal") .setSystem("https://bluebutton.cms.gov/resources/codesystem/diagnosis-type"))); diagnosisComponent.setSequence(claimDiagnosis.getSequence()); diagnosisComponent.setPackageCode(claimDiagnosis.getPackageCode()); diagnosisComponent.addExtension() .setUrl("https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-clm-poa-ind-sw1-extension") .setValue(new Coding() .setCode("Y") .setSystem("https://bluebutton.cms.gov/assets/ig/CodeSystem-clm-poa-ind-sw1") .setDisplay("Diagnosis present at time of admission")); eobDiag.add(diagnosisComponent); } eob.setDiagnosis(eobDiag); List<ExplanationOfBenefit.ProcedureComponent> eobProc = new ArrayList<>(); for (ProcedureComponent proc : claim.getProcedure()) { ExplanationOfBenefit.ProcedureComponent p = new ExplanationOfBenefit.ProcedureComponent(); p.setDate(proc.getDate()); p.setSequence(proc.getSequence()); p.setProcedure(proc.getProcedure()); } eob.setProcedure(eobProc); List<ExplanationOfBenefit.ItemComponent> eobItem = new ArrayList<>(); double totalPayment = 0; // Get all the items info from the claim for (ItemComponent item : claim.getItem()) { ExplanationOfBenefit.ItemComponent itemComponent = new ExplanationOfBenefit.ItemComponent(); itemComponent.setSequence(item.getSequence()); itemComponent.setQuantity(item.getQuantity()); itemComponent.setUnitPrice(item.getUnitPrice()); itemComponent.setCareTeamLinkId(item.getCareTeamLinkId()); if (item.hasService()) { itemComponent .setService(item .getService()); } if (!inpatient && !outpatient) { itemComponent.setDiagnosisLinkId(item.getDiagnosisLinkId()); itemComponent.setInformationLinkId(item.getInformationLinkId()); itemComponent.setNet(item.getNet()); itemComponent.setEncounter(item.getEncounter()); itemComponent.setServiced(encounterResource.getPeriod()); itemComponent.setCategory(new CodeableConcept().addCoding(new Coding() .setSystem("https://bluebutton.cms.gov/resources/variables/line_cms_type_srvc_cd") .setCode("1") .setDisplay("Medical care"))); } if (inpatient) { itemComponent.addExtension(new Extension() .setUrl("https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-inpatient-rev-cntr-ndc-qty-extension") .setValue(new Quantity().setValue(0))); } else if (outpatient) { itemComponent.addExtension(new Extension() .setUrl("https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-outpatient-rev-cntr-ndc-qty-extension") .setValue(new Quantity().setValue(0))); if (itemComponent.hasService()) { itemComponent.getService().addExtension(new Extension() .setUrl("https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-outpatient-rev-cntr-ide-ndc-upc-num-extension") .setValue(new Coding() .setSystem("https://www.accessdata.fda.gov/scripts/cder/ndc") .setDisplay("Dummy") .setCode("0624"))); } } // Location of service, can use switch statement based on // encounter type String code; String display; CodeableConcept location = new CodeableConcept(); EncounterType encounterType = EncounterType.fromString(encounter.type); switch (encounterType) { case AMBULATORY: code = "21"; display = "Inpatient Hospital"; break; case EMERGENCY: code = "23"; display = "Emergency Room"; break; case INPATIENT: code = "21"; display = "Inpatient Hospital"; break; case URGENTCARE: code = "20"; display = "Urgent Care Facility"; break; case WELLNESS: code = "22"; display = "Outpatient Hospital"; break; default: code = "21"; display = "Inpatient Hospital"; } location.addCoding() .setCode(code) //.setSystem("http://hl7.org/fhir/ValueSet/service-place") > if we wanted hl7 .setSystem("https://bluebutton.cms.gov/resources/variables/line_place_of_srvc_cd") .setDisplay(display); itemComponent.setLocation(location); // Adjudication if (item.hasNet()) { // Assume that the patient has already paid deductible and // has 20/80 coinsurance ExplanationOfBenefit.AdjudicationComponent coinsuranceAmount = new ExplanationOfBenefit.AdjudicationComponent(); coinsuranceAmount.getCategory() .getCoding() .add(new Coding() .setCode("https://bluebutton.cms.gov/resources/variables/line_coinsrnc_amt") .setSystem("https://bluebutton.cms.gov/resources/codesystem/adjudication") .setDisplay("Line Beneficiary Coinsurance Amount")); coinsuranceAmount.getAmount() .setValue(0.2 * item.getNet().getValue().doubleValue()) //20% coinsurance .setSystem("urn:iso:std:iso:4217") //USD .setCode("USD"); ExplanationOfBenefit.AdjudicationComponent lineProviderAmount = new ExplanationOfBenefit.AdjudicationComponent(); lineProviderAmount.getCategory() .getCoding() .add(new Coding() .setCode("https://bluebutton.cms.gov/resources/variables/line_prvdr_pmt_amt") .setSystem("https://bluebutton.cms.gov/resources/codesystem/adjudication") .setDisplay("Line Provider Payment Amount")); lineProviderAmount.getAmount() .setValue(0.8 * item.getNet().getValue().doubleValue()) .setSystem("urn:iso:std:iso:4217") .setCode("USD"); // assume the allowed and submitted amounts are the same for now ExplanationOfBenefit.AdjudicationComponent submittedAmount = new ExplanationOfBenefit.AdjudicationComponent(); submittedAmount.getCategory() .getCoding() .add(new Coding() .setCode("https://bluebutton.cms.gov/resources/variables/line_sbmtd_chrg_amt") .setSystem("https://bluebutton.cms.gov/resources/codesystem/adjudication") .setDisplay("Line Submitted Charge Amount")); submittedAmount.getAmount() .setValue(item.getNet().getValue()) .setSystem("urn:iso:std:iso:4217") .setCode("USD"); ExplanationOfBenefit.AdjudicationComponent allowedAmount = new ExplanationOfBenefit.AdjudicationComponent(); allowedAmount.getCategory() .getCoding() .add(new Coding() .setCode("https://bluebutton.cms.gov/resources/variables/line_alowd_chrg_amt") .setSystem("https://bluebutton.cms.gov/resources/codesystem/adjudication") .setDisplay("Line Allowed Charge Amount")); allowedAmount.getAmount() .setValue(item.getNet().getValue()) .setSystem("urn:iso:std:iso:4217") .setCode("USD"); ExplanationOfBenefit.AdjudicationComponent indicatorCode = new ExplanationOfBenefit.AdjudicationComponent(); indicatorCode.getCategory() .getCoding() .add(new Coding() .setCode("https://bluebutton.cms.gov/resources/variables/line_prcsg_ind_cd") .setSystem("https://bluebutton.cms.gov/resources/codesystem/adjudication") .setDisplay("Line Processing Indicator Code")); if (!inpatient && !outpatient) { indicatorCode.getReason() .addCoding() .setCode("A") .setSystem("https://bluebutton.cms.gov/resources/variables/line_prcsg_ind_cd"); indicatorCode .getReason() .getCodingFirstRep() .setDisplay("Allowed"); } // assume deductible is 0 ExplanationOfBenefit.AdjudicationComponent deductibleAmount = new ExplanationOfBenefit.AdjudicationComponent(); deductibleAmount.getCategory() .getCoding() .add(new Coding() .setCode("https://bluebutton.cms.gov/resources/variables/line_bene_ptb_ddctbl_amt") .setSystem("https://bluebutton.cms.gov/resources/codesystem/adjudication") .setDisplay("Line Beneficiary Part B Deductible Amount")); deductibleAmount.getAmount() .setValue(0) .setSystem("urn:iso:std:iso:4217") .setCode("USD"); List<ExplanationOfBenefit.AdjudicationComponent> adjudicationComponents = new ArrayList<>(); adjudicationComponents.add(coinsuranceAmount); adjudicationComponents.add(lineProviderAmount); adjudicationComponents.add(submittedAmount); adjudicationComponents.add(allowedAmount); adjudicationComponents.add(deductibleAmount); adjudicationComponents.add(indicatorCode); itemComponent.setAdjudication(adjudicationComponents); // the total payment is what the insurance ends up paying totalPayment += 0.8 * item.getNet().getValue().doubleValue(); } eobItem.add(itemComponent); } eob.setItem(eobItem); // This will throw a validation error no matter what. The // payment section is required, and it requires a value. // The validator will complain that if there is a value, the payment // needs a code, but it will also complain if there is a code. // There is no way to resolve this error. Money payment = new Money(); payment.setValue(totalPayment) .setSystem("urn:iso:std:iso:4217") .setCode("USD"); eob.setPayment(new ExplanationOfBenefit.PaymentComponent() .setAmount(payment)); // Hardcoded List<Reference> recipientList = new ArrayList<>(); recipientList.add(new Reference() .setIdentifier(new Identifier() .setSystem("http://hl7.org/fhir/sid/us-npi") .setValue("99999999"))); eob.addContained(new ReferralRequest() .setStatus(ReferralRequest.ReferralRequestStatus.COMPLETED) .setIntent(ReferralRequest.ReferralCategory.ORDER) .setSubject(new Reference(personEntry.getFullUrl())) .setRequester(new ReferralRequest.ReferralRequestRequesterComponent() .setAgent(new Reference() .setIdentifier(new Identifier() .setSystem("http://hl7.org/fhir/sid/us-npi") .setValue("99999999")))) .setRecipient(recipientList) .setId("1")); if (encounter.clinician != null) { // This is what should happen if BlueButton 2.0 wasn't needlessly restrictive // String practitionerFullUrl = findPractitioner(encounter.clinician, bundle); // eob.setProvider(new Reference().setReference(practitionerFullUrl)); // Instead, we'll create the BlueButton 2.0 reference via identifier... Identifier identifier = new Identifier(); identifier.setValue(encounter.clinician.getResourceID()); eob.setProvider(new Reference().setIdentifier(identifier)); } else { Identifier identifier = new Identifier(); identifier.setValue("Unknown"); eob.setProvider(new Reference().setIdentifier(identifier)); } eob.addCareTeam(new ExplanationOfBenefit.CareTeamComponent() .setSequence(1) .setProvider(new Reference() // .setReference(findProviderUrl(provider, bundle)) .setIdentifier(new Identifier() .setSystem("http://hl7.org/fhir/sid/us-npi") // providers don't have an npi .setValue("99999999"))) .setRole(new CodeableConcept().addCoding(new Coding() .setCode("primary") .setSystem("http://hl7.org/fhir/claimcareteamrole") .setDisplay("Primary Care Practitioner")))); eob.setType(new CodeableConcept() .addCoding(new Coding() .setSystem("https://bluebutton.cms.gov/resources/variables/nch_clm_type_cd") // The code should be chosen from the // claim type, which is different from // the encounter type apparently. .setCode("71") .setDisplay("Local carrier non-durable medical equipment, prosthetics, orthotics, " + "and supplies (DMEPOS) claim")) .addCoding(new Coding() .setSystem("https://bluebutton.cms.gov/resources/codesystem/eob-type") // the code is chosen directly as // a result of the nch_clm_type_cd. .setCode("CARRIER") .setDisplay("EOB Type")) .addCoding(new Coding() .setSystem("http://hl7.org/fhir/ex-claimtype") // the ex-claimtype is also directly dependent on // the eob-type, making the clm_type the only real // category that needs to be dynamically chosen .setCode("professional") .setDisplay("Claim Type")) .addCoding(new Coding() .setSystem("https://bluebutton.cms.gov/resources/variables/nch_near_line_rec_ident_cd") // also dependent on clm-type .setCode("O") .setDisplay("Part B physician/supplier claim record (processed by local " + "carriers; can include DMEPOS services)"))); return newEntry(bundle,eob); } /** * Map the Condition into a FHIR Condition resource, and add it to the given Bundle. * * @param personEntry The Entry for the Person * @param bundle The Bundle to add to * @param encounterEntry The current Encounter entry * @param condition The Condition * @return The added Entry */ private static BundleEntryComponent condition(BundleEntryComponent personEntry, Bundle bundle, BundleEntryComponent encounterEntry, HealthRecord.Entry condition) { Condition conditionResource = new Condition(); conditionResource.setSubject(new Reference(personEntry.getFullUrl())); conditionResource.setContext(new Reference(encounterEntry.getFullUrl())); Code code = condition.codes.get(0); conditionResource.setCode(mapCodeToCodeableConcept(code, SNOMED_URI)); conditionResource.setVerificationStatus(ConditionVerificationStatus.CONFIRMED); conditionResource.setClinicalStatus(ConditionClinicalStatus.ACTIVE); conditionResource.setOnset(convertFhirDateTime(condition.start, true)); conditionResource.setAssertedDate(new Date(condition.start)); if (condition.stop != 0) { conditionResource.setAbatement(convertFhirDateTime(condition.stop, true)); conditionResource.setClinicalStatus(ConditionClinicalStatus.RESOLVED); } if (USE_SHR_EXTENSIONS) { // TODO: use different categories. would need to add a "category" to GMF Condition state // also potentially use Injury profile here, // once different codes map to different categories conditionResource.addCategory(new CodeableConcept().addCoding(new Coding( "http://standardhealthrecord.org/shr/condition/vs/ConditionCategoryVS", "disease", "Disease"))); conditionResource.setMeta(new Meta().addProfile(SHR_EXT + "shr-condition-Condition")); // required fields for this profile are clinicalStatus, assertedDate, category } BundleEntryComponent conditionEntry = newEntry(bundle, conditionResource); condition.fullUrl = conditionEntry.getFullUrl(); return conditionEntry; } /** * Map the Condition into a FHIR AllergyIntolerance resource, and add it to the given Bundle. * * @param personEntry The Entry for the Person * @param bundle The Bundle to add to * @param encounterEntry The current Encounter entry * @param allergy The Allergy Entry * @return The added Entry */ private static BundleEntryComponent allergy(BundleEntryComponent personEntry, Bundle bundle, BundleEntryComponent encounterEntry, HealthRecord.Entry allergy) { AllergyIntolerance allergyResource = new AllergyIntolerance(); allergyResource.setAssertedDate(new Date(allergy.start)); if (allergy.stop == 0) { allergyResource.setClinicalStatus(AllergyIntoleranceClinicalStatus.ACTIVE); } else { allergyResource.setClinicalStatus(AllergyIntoleranceClinicalStatus.INACTIVE); } allergyResource.setType(AllergyIntoleranceType.ALLERGY); AllergyIntoleranceCategory category = AllergyIntoleranceCategory.FOOD; allergyResource.addCategory(category); // TODO: allergy categories in GMF allergyResource.setCriticality(AllergyIntoleranceCriticality.LOW); allergyResource.setVerificationStatus(AllergyIntoleranceVerificationStatus.CONFIRMED); allergyResource.setPatient(new Reference(personEntry.getFullUrl())); Code code = allergy.codes.get(0); allergyResource.setCode(mapCodeToCodeableConcept(code, SNOMED_URI)); if (USE_SHR_EXTENSIONS) { Meta meta = new Meta(); meta.addProfile(SHR_EXT + "shr-allergy-AllergyIntolerance"); // required fields for AllergyIntolerance profile are: // verificationStatus, code, patient, assertedDate allergyResource.setMeta(meta); } BundleEntryComponent allergyEntry = newEntry(bundle, allergyResource); allergy.fullUrl = allergyEntry.getFullUrl(); return allergyEntry; } /** * Map the given Observation into a FHIR Observation resource, and add it to the given Bundle. * * @param personEntry The Person Entry * @param bundle The Bundle to add to * @param encounterEntry The current Encounter entry * @param observation The Observation * @return The added Entry */ private static BundleEntryComponent observation(BundleEntryComponent personEntry, Bundle bundle, BundleEntryComponent encounterEntry, Observation observation) { org.hl7.fhir.dstu3.model.Observation observationResource = new org.hl7.fhir.dstu3.model.Observation(); observationResource.setSubject(new Reference(personEntry.getFullUrl())); observationResource.setContext(new Reference(encounterEntry.getFullUrl())); observationResource.setStatus(ObservationStatus.FINAL); Code code = observation.codes.get(0); observationResource.setCode(mapCodeToCodeableConcept(code, LOINC_URI)); observationResource.addCategory().addCoding().setCode(observation.category) .setSystem("http://hl7.org/fhir/observation-category").setDisplay(observation.category); if (observation.value != null) { Type value = mapValueToFHIRType(observation.value, observation.unit); observationResource.setValue(value); } else if (observation.observations != null && !observation.observations.isEmpty()) { // multi-observation (ex blood pressure) for (Observation subObs : observation.observations) { ObservationComponentComponent comp = new ObservationComponentComponent(); comp.setCode(mapCodeToCodeableConcept(subObs.codes.get(0), LOINC_URI)); Type value = mapValueToFHIRType(subObs.value, subObs.unit); comp.setValue(value); observationResource.addComponent(comp); } } observationResource.setEffective(convertFhirDateTime(observation.start, true)); observationResource.setIssued(new Date(observation.start)); if (USE_SHR_EXTENSIONS) { Meta meta = new Meta(); meta.addProfile(SHR_EXT + "shr-finding-Observation"); // all Observations are Observations if ("vital-signs".equals(observation.category)) { meta.addProfile(SHR_EXT + "shr-vital-VitalSign"); } // add the specific profile based on code String codeMappingUri = SHR_MAPPING.get(LOINC_URI, code.code); if (codeMappingUri != null) { meta.addProfile(codeMappingUri); } observationResource.setMeta(meta); } BundleEntryComponent entry = newEntry(bundle, observationResource); observation.fullUrl = entry.getFullUrl(); return entry; } static Type mapValueToFHIRType(Object value, String unit) { if (value == null) { return null; } else if (value instanceof Condition) { Code conditionCode = ((HealthRecord.Entry) value).codes.get(0); return mapCodeToCodeableConcept(conditionCode, SNOMED_URI); } else if (value instanceof Code) { return mapCodeToCodeableConcept((Code) value, SNOMED_URI); } else if (value instanceof String) { return new StringType((String) value); } else if (value instanceof Number) { double dblVal = ((Number) value).doubleValue(); PlainBigDecimal bigVal = new PlainBigDecimal(dblVal); return new Quantity().setValue(bigVal) .setCode(unit).setSystem(UNITSOFMEASURE_URI) .setUnit(unit); } else if (value instanceof Components.SampledData) { return mapValueToSampledData((Components.SampledData) value, unit); } else { throw new IllegalArgumentException("unexpected observation value class: " + value.getClass().toString() + "; " + value); } } /** * Maps a Synthea internal SampledData object to the FHIR standard SampledData * representation. * * @param value Synthea internal SampledData instance * @param unit Observation unit value * @return */ static org.hl7.fhir.dstu3.model.SampledData mapValueToSampledData( Components.SampledData value, String unit) { org.hl7.fhir.dstu3.model.SampledData recordData = new org.hl7.fhir.dstu3.model.SampledData(); SimpleQuantity origin = new SimpleQuantity(); origin.setValue(new BigDecimal(value.originValue)) .setCode(unit).setSystem(UNITSOFMEASURE_URI) .setUnit(unit); recordData.setOrigin(origin); // Use the period from the first series. They should all be the same. // FHIR output is milliseconds so we need to convert from TimeSeriesData seconds. recordData.setPeriod(value.series.get(0).getPeriod() * 1000); // Set optional fields if they were provided if (value.factor != null) { recordData.setFactor(value.factor); } if (value.lowerLimit != null) { recordData.setLowerLimit(value.lowerLimit); } if (value.upperLimit != null) { recordData.setUpperLimit(value.upperLimit); } recordData.setDimensions(value.series.size()); recordData.setData(ExportHelper.sampledDataToValueString(value)); return recordData; } /** * Map the given Procedure into a FHIR Procedure resource, and add it to the given Bundle. * * @param personEntry The Person entry * @param bundle Bundle to add to * @param encounterEntry The current Encounter entry * @param procedure The Procedure * @return The added Entry */ private static BundleEntryComponent procedure(BundleEntryComponent personEntry, Bundle bundle, BundleEntryComponent encounterEntry, Procedure procedure) { org.hl7.fhir.dstu3.model.Procedure procedureResource = new org.hl7.fhir.dstu3.model.Procedure(); procedureResource.setStatus(ProcedureStatus.COMPLETED); procedureResource.setSubject(new Reference(personEntry.getFullUrl())); procedureResource.setContext(new Reference(encounterEntry.getFullUrl())); Code code = procedure.codes.get(0); CodeableConcept procCode = mapCodeToCodeableConcept(code, SNOMED_URI); procedureResource.setCode(procCode); if (procedure.stop != 0L) { Date startDate = new Date(procedure.start); Date endDate = new Date(procedure.stop); procedureResource.setPerformed(new Period().setStart(startDate).setEnd(endDate)); } else { procedureResource.setPerformed(convertFhirDateTime(procedure.start, true)); } if (!procedure.reasons.isEmpty()) { Code reason = procedure.reasons.get(0); // Only one element in list for (BundleEntryComponent entry : bundle.getEntry()) { if (entry.getResource().fhirType().equals("Condition")) { Condition condition = (Condition) entry.getResource(); Coding coding = condition.getCode().getCoding().get(0); // Only one element in list if (reason.code.equals(coding.getCode())) { procedureResource.addReasonReference().setReference(entry.getFullUrl()) .setDisplay(reason.display); } } } } if (USE_SHR_EXTENSIONS) { procedureResource.setMeta( new Meta().addProfile(SHR_EXT + "shr-procedure-ProcedurePerformed")); // required fields for this profile are action-PerformedContext-extension, // status, code, subject, performed[x] Extension performedContext = new Extension(); performedContext.setUrl(SHR_EXT + "shr-action-PerformedContext-extension"); performedContext.addExtension( SHR_EXT + "shr-action-Status-extension", new CodeType("completed")); procedureResource.addExtension(performedContext); } BundleEntryComponent procedureEntry = newEntry(bundle, procedureResource); procedure.fullUrl = procedureEntry.getFullUrl(); return procedureEntry; } private static BundleEntryComponent immunization(BundleEntryComponent personEntry, Bundle bundle, BundleEntryComponent encounterEntry, HealthRecord.Entry immunization) { Immunization immResource = new Immunization(); immResource.setStatus(ImmunizationStatus.COMPLETED); immResource.setDate(new Date(immunization.start)); immResource.setVaccineCode(mapCodeToCodeableConcept(immunization.codes.get(0), CVX_URI)); immResource.setNotGiven(false); immResource.setPrimarySource(true); immResource.setPatient(new Reference(personEntry.getFullUrl())); immResource.setEncounter(new Reference(encounterEntry.getFullUrl())); if (USE_SHR_EXTENSIONS) { immResource.setMeta(new Meta().addProfile(SHR_EXT + "shr-immunization-ImmunizationGiven")); // profile requires action-PerformedContext-extension, status, notGiven, vaccineCode, patient, // date, primarySource Extension performedContext = new Extension(); performedContext.setUrl(SHR_EXT + "shr-action-PerformedContext-extension"); performedContext.addExtension( SHR_EXT + "shr-action-Status-extension", new CodeType("completed")); immResource.addExtension(performedContext); } BundleEntryComponent immunizationEntry = newEntry(bundle, immResource); immunization.fullUrl = immunizationEntry.getFullUrl(); return immunizationEntry; } /** * Map the given Medication to a FHIR MedicationRequest resource, and add it to the given Bundle. * * @param personEntry The Entry for the Person * @param bundle Bundle to add the Medication to * @param encounterEntry Current Encounter entry * @param medication The Medication * @return The added Entry */ private static BundleEntryComponent medication(BundleEntryComponent personEntry, Bundle bundle, BundleEntryComponent encounterEntry, Medication medication) { MedicationRequest medicationResource = new MedicationRequest(); medicationResource.setSubject(new Reference(personEntry.getFullUrl())); medicationResource.setContext(new Reference(encounterEntry.getFullUrl())); Code code = medication.codes.get(0); String system = code.system.equals("SNOMED-CT") ? SNOMED_URI : RXNORM_URI; medicationResource.setMedication(mapCodeToCodeableConcept(code, system)); medicationResource.setAuthoredOn(new Date(medication.start)); medicationResource.setIntent(MedicationRequestIntent.ORDER); org.hl7.fhir.dstu3.model.Encounter encounter = (org.hl7.fhir.dstu3.model.Encounter) encounterEntry.getResource(); MedicationRequestRequesterComponent requester = new MedicationRequestRequesterComponent(); requester.setAgent(encounter.getParticipantFirstRep().getIndividual()); requester.setOnBehalfOf(encounter.getServiceProvider()); medicationResource.setRequester(requester); if (medication.stop != 0L) { medicationResource.setStatus(MedicationRequestStatus.STOPPED); } else { medicationResource.setStatus(MedicationRequestStatus.ACTIVE); } if (!medication.reasons.isEmpty()) { // Only one element in list Code reason = medication.reasons.get(0); for (BundleEntryComponent entry : bundle.getEntry()) { if (entry.getResource().fhirType().equals("Condition")) { Condition condition = (Condition) entry.getResource(); // Only one element in list Coding coding = condition.getCode().getCoding().get(0); if (reason.code.equals(coding.getCode())) { medicationResource.addReasonReference() .setReference(entry.getFullUrl()); } } } } if (medication.prescriptionDetails != null) { JsonObject rxInfo = medication.prescriptionDetails; Dosage dosage = new Dosage(); dosage.setSequence(1); // as_needed is true if present dosage.setAsNeeded(new BooleanType(rxInfo.has("as_needed"))); // as_needed is true if present if ((rxInfo.has("dosage")) && (!rxInfo.has("as_needed"))) { Timing timing = new Timing(); TimingRepeatComponent timingRepeatComponent = new TimingRepeatComponent(); timingRepeatComponent.setFrequency( rxInfo.get("dosage").getAsJsonObject().get("frequency").getAsInt()); timingRepeatComponent.setPeriod( rxInfo.get("dosage").getAsJsonObject().get("period").getAsDouble()); timingRepeatComponent.setPeriodUnit( convertUcumCode(rxInfo.get("dosage").getAsJsonObject().get("unit").getAsString())); timing.setRepeat(timingRepeatComponent); dosage.setTiming(timing); Quantity dose = new SimpleQuantity().setValue( rxInfo.get("dosage").getAsJsonObject().get("amount").getAsDouble()); dosage.setDose(dose); if (rxInfo.has("instructions")) { for (JsonElement instructionElement : rxInfo.get("instructions").getAsJsonArray()) { JsonObject instruction = instructionElement.getAsJsonObject(); Code instructionCode = new Code( SNOMED_URI, instruction.get("code").getAsString(), instruction.get("display").getAsString() ); dosage.addAdditionalInstruction(mapCodeToCodeableConcept(instructionCode, SNOMED_URI)); } } } List<Dosage> dosageInstruction = new ArrayList<Dosage>(); dosageInstruction.add(dosage); medicationResource.setDosageInstruction(dosageInstruction); } if (USE_SHR_EXTENSIONS) { medicationResource.addExtension() .setUrl(SHR_EXT + "shr-base-ActionCode-extension") .setValue(PRESCRIPTION_OF_DRUG_CC); medicationResource.setMeta(new Meta() .addProfile(SHR_EXT + "shr-medication-MedicationRequested")); // required fields for this profile are status, action-RequestedContext-extension, // medication[x]subject, authoredOn, requester Extension requestedContext = new Extension(); requestedContext.setUrl(SHR_EXT + "shr-action-RequestedContext-extension"); requestedContext.addExtension( SHR_EXT + "shr-action-Status-extension", new CodeType("completed")); requestedContext.addExtension( SHR_EXT + "shr-action-RequestIntent-extension", new CodeType("original-order")); medicationResource.addExtension(requestedContext); } BundleEntryComponent medicationEntry = newEntry(bundle, medicationResource); // create new claim for medication medicationClaim(personEntry, bundle, encounterEntry, medication.claim, medicationEntry); // Create new administration for medication, if needed if (medication.administration) { medicationAdministration(personEntry, bundle, encounterEntry, medication, medicationResource); } return medicationEntry; } /** * Add a MedicationAdministration if needed for the given medication. * * @param personEntry The Entry for the Person * @param bundle Bundle to add the MedicationAdministration to * @param encounterEntry Current Encounter entry * @param medication The Medication * @param medicationRequest The related medicationRequest * @return The added Entry */ private static BundleEntryComponent medicationAdministration( BundleEntryComponent personEntry, Bundle bundle, BundleEntryComponent encounterEntry, Medication medication, MedicationRequest medicationRequest) { MedicationAdministration medicationResource = new MedicationAdministration(); medicationResource.setSubject(new Reference(personEntry.getFullUrl())); medicationResource.setContext(new Reference(encounterEntry.getFullUrl())); Code code = medication.codes.get(0); String system = code.system.equals("SNOMED-CT") ? SNOMED_URI : RXNORM_URI; medicationResource.setMedication(mapCodeToCodeableConcept(code, system)); medicationResource.setEffective(new DateTimeType(new Date(medication.start))); medicationResource.setStatus(MedicationAdministrationStatus.fromCode("completed")); if (medication.prescriptionDetails != null) { JsonObject rxInfo = medication.prescriptionDetails; MedicationAdministrationDosageComponent dosage = new MedicationAdministrationDosageComponent(); // as_needed is true if present if ((rxInfo.has("dosage")) && (!rxInfo.has("as_needed"))) { Quantity dose = new SimpleQuantity().setValue( rxInfo.get("dosage").getAsJsonObject().get("amount").getAsDouble()); dosage.setDose((SimpleQuantity) dose); if (rxInfo.has("instructions")) { for (JsonElement instructionElement : rxInfo.get("instructions").getAsJsonArray()) { JsonObject instruction = instructionElement.getAsJsonObject(); dosage.setText(instruction.get("display").getAsString()); } } } medicationResource.setDosage(dosage); } if (!medication.reasons.isEmpty()) { // Only one element in list Code reason = medication.reasons.get(0); for (BundleEntryComponent entry : bundle.getEntry()) { if (entry.getResource().fhirType().equals("Condition")) { Condition condition = (Condition) entry.getResource(); // Only one element in list Coding coding = condition.getCode().getCoding().get(0); if (reason.code.equals(coding.getCode())) { medicationResource.addReasonReference().setReference(entry.getFullUrl()); } } } } BundleEntryComponent medicationAdminEntry = newEntry(bundle, medicationResource); return medicationAdminEntry; } private static final Code PRESCRIPTION_OF_DRUG_CODE = new Code("SNOMED-CT","33633005","Prescription of drug (procedure)"); private static final CodeableConcept PRESCRIPTION_OF_DRUG_CC = mapCodeToCodeableConcept(PRESCRIPTION_OF_DRUG_CODE, SNOMED_URI); /** * Map the given Report to a FHIR DiagnosticReport resource, and add it to the given Bundle. * * @param personEntry The Entry for the Person * @param bundle Bundle to add the Report to * @param encounterEntry Current Encounter entry * @param report The Report * @return The added Entry */ private static BundleEntryComponent report(BundleEntryComponent personEntry, Bundle bundle, BundleEntryComponent encounterEntry, Report report) { DiagnosticReport reportResource = new DiagnosticReport(); reportResource.setStatus(DiagnosticReportStatus.FINAL); reportResource.setCode(mapCodeToCodeableConcept(report.codes.get(0), LOINC_URI)); reportResource.setSubject(new Reference(personEntry.getFullUrl())); reportResource.setContext(new Reference(encounterEntry.getFullUrl())); reportResource.setEffective(convertFhirDateTime(report.start, true)); reportResource.setIssued(new Date(report.start)); for (Observation observation : report.observations) { Reference reference = new Reference(observation.fullUrl); reference.setDisplay(observation.codes.get(0).display); reportResource.addResult(reference); } // no SHR profile for DiagnosticReport return newEntry(bundle, reportResource); } /** * Map the given CarePlan to a FHIR CarePlan resource, and add it to the given Bundle. * * @param personEntry The Entry for the Person * @param bundle Bundle to add the CarePlan to * @param encounterEntry Current Encounter entry * @param carePlan The CarePlan to map to FHIR and add to the bundle * @return The added Entry */ private static BundleEntryComponent careplan(BundleEntryComponent personEntry, Bundle bundle, BundleEntryComponent encounterEntry, CarePlan carePlan) { org.hl7.fhir.dstu3.model.CarePlan careplanResource = new org.hl7.fhir.dstu3.model.CarePlan(); careplanResource.setIntent(CarePlanIntent.ORDER); careplanResource.setSubject(new Reference(personEntry.getFullUrl())); careplanResource.setContext(new Reference(encounterEntry.getFullUrl())); Code code = carePlan.codes.get(0); careplanResource.addCategory(mapCodeToCodeableConcept(code, SNOMED_URI)); CarePlanActivityStatus activityStatus; GoalStatus goalStatus; Period period = new Period().setStart(new Date(carePlan.start)); careplanResource.setPeriod(period); if (carePlan.stop != 0L) { period.setEnd(new Date(carePlan.stop)); careplanResource.setStatus(CarePlanStatus.COMPLETED); activityStatus = CarePlanActivityStatus.COMPLETED; goalStatus = GoalStatus.ACHIEVED; } else { careplanResource.setStatus(CarePlanStatus.ACTIVE); activityStatus = CarePlanActivityStatus.INPROGRESS; goalStatus = GoalStatus.INPROGRESS; } if (!carePlan.activities.isEmpty()) { for (Code activity : carePlan.activities) { CarePlanActivityComponent activityComponent = new CarePlanActivityComponent(); CarePlanActivityDetailComponent activityDetailComponent = new CarePlanActivityDetailComponent(); activityDetailComponent.setStatus(activityStatus); activityDetailComponent.setCode(mapCodeToCodeableConcept(activity, SNOMED_URI)); activityComponent.setDetail(activityDetailComponent); careplanResource.addActivity(activityComponent); } } if (!carePlan.reasons.isEmpty()) { // Only one element in list Code reason = carePlan.reasons.get(0); for (BundleEntryComponent entry : bundle.getEntry()) { if (entry.getResource().fhirType().equals("Condition")) { Condition condition = (Condition) entry.getResource(); // Only one element in list Coding coding = condition.getCode().getCoding().get(0); if (reason.code.equals(coding.getCode())) { careplanResource.addAddresses().setReference(entry.getFullUrl()); } } } } for (JsonObject goal : carePlan.goals) { BundleEntryComponent goalEntry = caregoal(bundle, goalStatus, goal); careplanResource.addGoal().setReference(goalEntry.getFullUrl()); } return newEntry(bundle, careplanResource); } /** * Map the given ImagingStudy to a FHIR ImagingStudy resource, and add it to the given Bundle. * * @param personEntry The Entry for the Person * @param bundle Bundle to add the ImagingStudy to * @param encounterEntry Current Encounter entry * @param imagingStudy The ImagingStudy to map to FHIR and add to the bundle * @return The added Entry */ private static BundleEntryComponent imagingStudy(BundleEntryComponent personEntry, Bundle bundle, BundleEntryComponent encounterEntry, ImagingStudy imagingStudy) { org.hl7.fhir.dstu3.model.ImagingStudy imagingStudyResource = new org.hl7.fhir.dstu3.model.ImagingStudy(); imagingStudyResource.setUid("urn:oid:" + imagingStudy.dicomUid); imagingStudyResource.setPatient(new Reference(personEntry.getFullUrl())); imagingStudyResource.setContext(new Reference(encounterEntry.getFullUrl())); Date startDate = new Date(imagingStudy.start); imagingStudyResource.setStarted(startDate); // Convert the series into their FHIR equivalents int numberOfSeries = imagingStudy.series.size(); imagingStudyResource.setNumberOfSeries(numberOfSeries); List<ImagingStudySeriesComponent> seriesResourceList = new ArrayList<ImagingStudySeriesComponent>(); int totalNumberOfInstances = 0; int seriesNo = 1; for (ImagingStudy.Series series : imagingStudy.series) { ImagingStudySeriesComponent seriesResource = new ImagingStudySeriesComponent(); seriesResource.setUid("urn:oid:" + series.dicomUid); seriesResource.setNumber(seriesNo); seriesResource.setStarted(startDate); seriesResource.setAvailability(InstanceAvailability.UNAVAILABLE); CodeableConcept modalityConcept = mapCodeToCodeableConcept(series.modality, DICOM_DCM_URI); seriesResource.setModality(modalityConcept.getCoding().get(0)); CodeableConcept bodySiteConcept = mapCodeToCodeableConcept(series.bodySite, SNOMED_URI); seriesResource.setBodySite(bodySiteConcept.getCoding().get(0)); // Convert the images in each series into their FHIR equivalents int numberOfInstances = series.instances.size(); seriesResource.setNumberOfInstances(numberOfInstances); totalNumberOfInstances += numberOfInstances; List<ImagingStudySeriesInstanceComponent> instanceResourceList = new ArrayList<ImagingStudySeriesInstanceComponent>(); int instanceNo = 1; for (ImagingStudy.Instance instance : series.instances) { ImagingStudySeriesInstanceComponent instanceResource = new ImagingStudySeriesInstanceComponent(); instanceResource.setUid("urn:oid:" + instance.dicomUid); instanceResource.setTitle(instance.title); instanceResource.setSopClass("urn:oid:" + instance.sopClass.code); instanceResource.setNumber(instanceNo); instanceResourceList.add(instanceResource); instanceNo += 1; } seriesResource.setInstance(instanceResourceList); seriesResourceList.add(seriesResource); seriesNo += 1; } imagingStudyResource.setSeries(seriesResourceList); imagingStudyResource.setNumberOfInstances(totalNumberOfInstances); return newEntry(bundle, imagingStudyResource); } /** * Map the given Media element to a FHIR Media resource, and add it to the given Bundle. * * @param personEntry The Entry for the Person * @param bundle Bundle to add the Media to * @param encounterEntry Current Encounter entry * @param obs The Observation to map to FHIR and add to the bundle * @return The added Entry */ private static BundleEntryComponent media(BundleEntryComponent personEntry, Bundle bundle, BundleEntryComponent encounterEntry, Observation obs) { org.hl7.fhir.dstu3.model.Media mediaResource = new org.hl7.fhir.dstu3.model.Media(); if (obs.codes != null && obs.codes.size() > 0) { List<CodeableConcept> reasonList = obs.codes.stream() .map(code -> mapCodeToCodeableConcept(code, SNOMED_URI)).collect(Collectors.toList()); mediaResource.setReasonCode(reasonList); } // Hard code as an image mediaResource.setType(DigitalMediaType.PHOTO); mediaResource.setSubject(new Reference(personEntry.getFullUrl())); Attachment content = (Attachment) obs.value; org.hl7.fhir.dstu3.model.Attachment contentResource = new org.hl7.fhir.dstu3.model.Attachment(); contentResource.setContentType(content.contentType); contentResource.setLanguage(content.language); if (content.data != null) { contentResource.setDataElement(new org.hl7.fhir.dstu3.model.Base64BinaryType(content.data)); } contentResource.setUrl(content.url); contentResource.setSize(content.size); contentResource.setTitle(content.title); if (content.hash != null) { contentResource.setHashElement(new org.hl7.fhir.dstu3.model.Base64BinaryType(content.hash)); } mediaResource.setWidth(content.width); mediaResource.setHeight(content.height); mediaResource.setContent(contentResource); return newEntry(bundle, mediaResource); } /** * Map the HealthRecord.Device into a FHIR Device and add it to the Bundle. * * @param personEntry The Person entry. * @param bundle Bundle to add to. * @param device The device to add. * @return The added Entry. */ private static BundleEntryComponent device(BundleEntryComponent personEntry, Bundle bundle, HealthRecord.Device device) { Device deviceResource = new Device(); Device.DeviceUdiComponent udi = new Device.DeviceUdiComponent() .setDeviceIdentifier(device.deviceIdentifier) .setCarrierHRF(device.udi); deviceResource.setUdi(udi); deviceResource.setStatus(FHIRDeviceStatus.ACTIVE); if (device.manufacturer != null) { deviceResource.setManufacturer(device.manufacturer); } if (device.model != null) { deviceResource.setModel(device.model); } deviceResource.setManufactureDate(new Date(device.manufactureTime)); deviceResource.setExpirationDate(new Date(device.expirationTime)); deviceResource.setLotNumber(device.lotNumber); deviceResource.setType(mapCodeToCodeableConcept(device.codes.get(0), SNOMED_URI)); deviceResource.setPatient(new Reference(personEntry.getFullUrl())); return newEntry(bundle, deviceResource); } /** * Map the JsonObject for a Supply into a FHIR SupplyDelivery and add it to the Bundle. * * @param personEntry The Person entry. * @param bundle Bundle to add to. * @param supply The supplied object to add. * @param encounter The encounter during which the supplies were delivered * @return The added Entry. */ private static BundleEntryComponent supplyDelivery(BundleEntryComponent personEntry, Bundle bundle, HealthRecord.Supply supply, Encounter encounter) { SupplyDelivery supplyResource = new SupplyDelivery(); supplyResource.setStatus(SupplyDeliveryStatus.COMPLETED); supplyResource.setPatient(new Reference(personEntry.getFullUrl())); CodeableConcept type = new CodeableConcept(); type.addCoding() .setCode("device") .setDisplay("Device") .setSystem("http://hl7.org/fhir/supply-item-type"); supplyResource.setType(type); SupplyDeliverySuppliedItemComponent suppliedItem = new SupplyDeliverySuppliedItemComponent(); suppliedItem.setItem(mapCodeToCodeableConcept(supply.codes.get(0), SNOMED_URI)); SimpleQuantity quantity = new SimpleQuantity(); quantity.setValue(supply.quantity); suppliedItem.setQuantity(quantity); supplyResource.setSuppliedItem(suppliedItem); supplyResource.setOccurrence(convertFhirDateTime(supply.start, true)); return newEntry(bundle, supplyResource); } /** * Map the Provider into a FHIR Organization resource, and add it to the given Bundle. * @param bundle The Bundle to add to * @param provider The Provider * @return The added Entry */ protected static BundleEntryComponent provider(Bundle bundle, Provider provider) { org.hl7.fhir.dstu3.model.Organization organizationResource = new org.hl7.fhir.dstu3.model.Organization(); List<CodeableConcept> organizationType = new ArrayList<CodeableConcept>(); organizationType.add( mapCodeToCodeableConcept( new Code( "http://hl7.org/fhir/organization-type", "prov", "Healthcare Provider"), "http://hl7.org/fhir/organization-type")); organizationResource.addIdentifier().setSystem("https://github.com/synthetichealth/synthea") .setValue((String) provider.getResourceID()); organizationResource.setId(provider.getResourceID()); organizationResource.setName(provider.name); organizationResource.setType(organizationType); Address address = new Address() .addLine(provider.address) .setCity(provider.city) .setPostalCode(provider.zip) .setState(provider.state); if (COUNTRY_CODE != null) { address.setCountry(COUNTRY_CODE); } organizationResource.addAddress(address); Point2D.Double coord = provider.getLonLat(); if (coord != null) { Extension geolocation = address.addExtension(); geolocation.setUrl("http://hl7.org/fhir/StructureDefinition/geolocation"); geolocation.addExtension("latitude", new DecimalType(coord.getY())); geolocation.addExtension("longitude", new DecimalType(coord.getX())); } if (provider.phone != null && !provider.phone.isEmpty()) { ContactPoint contactPoint = new ContactPoint() .setSystem(ContactPointSystem.PHONE) .setValue(provider.phone); organizationResource.addTelecom(contactPoint); } if (USE_SHR_EXTENSIONS) { organizationResource.setMeta(new Meta().addProfile(SHR_EXT + "shr-entity-Organization")); // required fields for this profile are identifier, type, address, and contact organizationResource.addIdentifier() .setSystem("urn:ietf:rfc:3986") .setValue(provider.getResourceID()); organizationResource.addContact().setName(new HumanName().setText("Synthetic Provider")); } return newEntry(bundle, organizationResource, provider.getResourceID()); } /** * Map the clinician into a FHIR Practitioner resource, and add it to the given Bundle. * @param bundle The Bundle to add to * @param clinician The clinician * @return The added Entry */ protected static BundleEntryComponent practitioner(Bundle bundle, Clinician clinician) { Practitioner practitionerResource = new Practitioner(); practitionerResource.addIdentifier().setSystem("http://hl7.org/fhir/sid/us-npi") .setValue("" + clinician.identifier); practitionerResource.setActive(true); practitionerResource.addName().setFamily( (String) clinician.attributes.get(Clinician.LAST_NAME)) .addGiven((String) clinician.attributes.get(Clinician.FIRST_NAME)) .addPrefix((String) clinician.attributes.get(Clinician.NAME_PREFIX)); Address address = new Address() .addLine((String) clinician.attributes.get(Clinician.ADDRESS)) .setCity((String) clinician.attributes.get(Clinician.CITY)) .setPostalCode((String) clinician.attributes.get(Clinician.ZIP)) .setState((String) clinician.attributes.get(Clinician.STATE)); if (COUNTRY_CODE != null) { address.setCountry(COUNTRY_CODE); } practitionerResource.addAddress(address); if (clinician.attributes.get(Person.GENDER).equals("M")) { practitionerResource.setGender(AdministrativeGender.MALE); } else if (clinician.attributes.get(Person.GENDER).equals("F")) { practitionerResource.setGender(AdministrativeGender.FEMALE); } return newEntry(bundle, practitionerResource, clinician.getResourceID()); } /** * Map the JsonObject into a FHIR Goal resource, and add it to the given Bundle. * @param bundle The Bundle to add to * @param goalStatus The GoalStatus * @param goal The JsonObject * @return The added Entry */ private static BundleEntryComponent caregoal( Bundle bundle, GoalStatus goalStatus, JsonObject goal) { String resourceID = UUID.randomUUID().toString(); org.hl7.fhir.dstu3.model.Goal goalResource = new org.hl7.fhir.dstu3.model.Goal(); goalResource.setStatus(goalStatus); goalResource.setId(resourceID); if (goal.has("text")) { CodeableConcept descriptionCodeableConcept = new CodeableConcept(); descriptionCodeableConcept.setText(goal.get("text").getAsString()); goalResource.setDescription(descriptionCodeableConcept); } else if (goal.has("codes")) { CodeableConcept descriptionCodeableConcept = new CodeableConcept(); JsonObject code = goal.get("codes").getAsJsonArray().get(0).getAsJsonObject(); descriptionCodeableConcept.addCoding() .setSystem(LOINC_URI) .setCode(code.get("code").getAsString()) .setDisplay(code.get("display").getAsString()); descriptionCodeableConcept.setText(code.get("display").getAsString()); goalResource.setDescription(descriptionCodeableConcept); } else if (goal.has("observation")) { CodeableConcept descriptionCodeableConcept = new CodeableConcept(); // build up our own text from the observation condition, similar to the graphviz logic JsonObject logic = goal.get("observation").getAsJsonObject(); String[] text = { logic.get("codes").getAsJsonArray().get(0) .getAsJsonObject().get("display").getAsString(), logic.get("operator").getAsString(), logic.get("value").getAsString() }; descriptionCodeableConcept.setText(String.join(" ", text)); goalResource.setDescription(descriptionCodeableConcept); } if (goal.has("addresses")) { for (JsonElement reasonElement : goal.get("addresses").getAsJsonArray()) { if (reasonElement instanceof JsonObject) { JsonObject reasonObject = reasonElement.getAsJsonObject(); String reasonCode = reasonObject.get("codes") .getAsJsonObject() .get("SNOMED-CT") .getAsJsonArray() .get(0) .getAsString(); for (BundleEntryComponent entry : bundle.getEntry()) { if (entry.getResource().fhirType().equals("Condition")) { Condition condition = (Condition) entry.getResource(); // Only one element in list Coding coding = condition.getCode().getCoding().get(0); if (reasonCode.equals(coding.getCode())) { goalResource.addAddresses() .setReference(entry.getFullUrl()); } } } } } } return newEntry(bundle, goalResource); } /** * Convert the unit into a UnitsOfTime. * * @param unit unit String * @return a UnitsOfTime representing the given unit */ private static UnitsOfTime convertUcumCode(String unit) { // From: http://hl7.org/fhir/ValueSet/units-of-time switch (unit) { case "seconds": return UnitsOfTime.S; case "minutes": return UnitsOfTime.MIN; case "hours": return UnitsOfTime.H; case "days": return UnitsOfTime.D; case "weeks": return UnitsOfTime.WK; case "months": return UnitsOfTime.MO; case "years": return UnitsOfTime.A; default: return null; } } /** * Convert the timestamp into a FHIR DateType or DateTimeType. * * @param datetime Timestamp * @param time If true, return a DateTime; if false, return a Date. * @return a DateType or DateTimeType representing the given timestamp */ private static Type convertFhirDateTime(long datetime, boolean time) { Date date = new Date(datetime); if (time) { return new DateTimeType(date); } else { return new DateType(date); } } /** * Helper function to convert a Code into a CodeableConcept. Takes an optional system, which * replaces the Code.system in the resulting CodeableConcept if not null. * * @param from The Code to create a CodeableConcept from. * @param system The system identifier, such as a URI. Optional; may be null. * @return The converted CodeableConcept */ private static CodeableConcept mapCodeToCodeableConcept(Code from, String system) { CodeableConcept to = new CodeableConcept(); system = system == null ? null : ExportHelper.getSystemURI(system); from.system = ExportHelper.getSystemURI(from.system); if (from.display != null) { to.setText(from.display); } Coding coding = new Coding(); coding.setCode(from.code); coding.setDisplay(from.display); if (from.system == null) { coding.setSystem(system); } else { coding.setSystem(from.system); } to.addCoding(coding); return to; } /** * Helper function to create an Entry for the given Resource within the given Bundle. Sets the * resourceID to a random UUID, sets the entry's fullURL to that resourceID, and adds the entry to * the bundle. * * @param bundle The Bundle to add the Entry to * @param resource Resource the new Entry should contain * @return the created Entry */ private static BundleEntryComponent newEntry(Bundle bundle, Resource resource) { String resourceID = UUID.randomUUID().toString(); return newEntry(bundle, resource, resourceID); } /** * Helper function to create an Entry for the given Resource within the given Bundle. * Sets the entry's fullURL to resourceID, and adds the entry to the bundle. * * @param bundle The Bundle to add the Entry to * @param resource Resource the new Entry should contain * @param resourceID The Resource ID to assign * @return the created Entry */ private static BundleEntryComponent newEntry(Bundle bundle, Resource resource, String resourceID) { BundleEntryComponent entry = bundle.addEntry(); resource.setId(resourceID); if (Boolean.parseBoolean(Config.get("exporter.fhir.bulk_data"))) { entry.setFullUrl(resource.fhirType() + "/" + resourceID); } else { entry.setFullUrl("urn:uuid:" + resourceID); } entry.setResource(resource); if (TRANSACTION_BUNDLE) { BundleEntryRequestComponent request = entry.getRequest(); request.setMethod(HTTPVerb.POST); request.setUrl(resource.getResourceType().name()); entry.setRequest(request); } return entry; } }