use of org.hl7.fhir.dstu3.model.ListResource.ListMode.WORKING in project beneficiary-fhir-data by CMSgov.
the class PatientResourceProvider method queryDatabaseByHash.
/**
* @param hash the {@link Beneficiary} hash value to match
* @param hashType a string to represent the hash type (used for logging purposes)
* @param requestHeader the {@link #RequestHeaders} where resource request headers are
* encapsulated
* @param beneficiaryHashField the JPA location of the beneficiary hash field
* @param beneficiaryHistoryHashField the JPA location of the beneficiary history hash field
* @return a FHIR {@link Patient} for the CCW {@link Beneficiary} that matches the specified
* {@link Beneficiary} hash value
* @throws NoResultException A {@link NoResultException} will be thrown if no matching {@link
* Beneficiary} can be found
*/
@Trace
private Patient queryDatabaseByHash(String hash, String hashType, SingularAttribute<Beneficiary, String> beneficiaryHashField, SingularAttribute<BeneficiaryHistory, String> beneficiaryHistoryHashField, RequestHeaders requestHeader) {
if (hash == null || hash.trim().isEmpty())
throw new IllegalArgumentException();
/*
* Beneficiaries' HICN/MBIs can change over time and those past HICN/MBIs may land in
* BeneficiaryHistory records. Accordingly, we need to search for matching HICN/MBIs in both the
* Beneficiary and the BeneficiaryHistory records.
*
* There's no sane way to do this in a single query with JPA 2.1, it appears: JPA doesn't
* support UNIONs and it doesn't support subqueries in FROM clauses. That said, the ideal query
* would look like this:
*
* SELECT * FROM ( SELECT DISTINCT "beneficiaryId" FROM "Beneficiaries" WHERE "hicn" =
* :'hicn_hash' UNION SELECT DISTINCT "beneficiaryId" FROM "BeneficiariesHistory" WHERE "hicn" =
* :'hicn_hash') AS matching_benes INNER JOIN "Beneficiaries" ON matching_benes."beneficiaryId"
* = "Beneficiaries"."beneficiaryId" LEFT JOIN "BeneficiariesHistory" ON
* "Beneficiaries"."beneficiaryId" = "BeneficiariesHistory"."beneficiaryId" LEFT JOIN
* "MedicareBeneficiaryIdHistory" ON "Beneficiaries"."beneficiaryId" =
* "MedicareBeneficiaryIdHistory"."beneficiaryId";
*
* ... with the returned columns and JOINs being dynamic, depending on IncludeIdentifiers.
*
* In lieu of that, we run two queries: one to find HICN/MBI matches in BeneficiariesHistory,
* and a second to find BENE_ID or HICN/MBI matches in Beneficiaries (with all of their data, so
* we're ready to return the result). This is bad and dumb but I can't find a better working
* alternative.
*
* (I'll just note that I did also try JPA/Hibernate native SQL queries but couldn't get the
* joins or fetch groups to work with them.)
*
* If we want to fix this, we need to move identifiers out entirely to separate tables:
* BeneficiaryHicns and BeneficiaryMbis. We could then safely query these tables and join them
* back to Beneficiaries (and hopefully the optimizer will play nice, too).
*/
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
// First, find all matching hashes from BeneficiariesHistory.
CriteriaQuery<String> beneHistoryMatches = builder.createQuery(String.class);
Root<BeneficiaryHistory> beneHistoryMatchesRoot = beneHistoryMatches.from(BeneficiaryHistory.class);
beneHistoryMatches.select(beneHistoryMatchesRoot.get(BeneficiaryHistory_.beneficiaryId));
beneHistoryMatches.where(builder.equal(beneHistoryMatchesRoot.get(beneficiaryHistoryHashField), hash));
List<String> matchingIdsFromBeneHistory = null;
Long hicnsFromHistoryQueryNanoSeconds = null;
Timer.Context beneHistoryMatchesTimer = metricRegistry.timer(MetricRegistry.name(getClass().getSimpleName(), "query", "bene_by_" + hashType, hashType + "s_from_beneficiarieshistory")).time();
try {
matchingIdsFromBeneHistory = entityManager.createQuery(beneHistoryMatches).getResultList();
} finally {
hicnsFromHistoryQueryNanoSeconds = beneHistoryMatchesTimer.stop();
TransformerUtils.recordQueryInMdc("bene_by_" + hashType + "." + hashType + "s_from_beneficiarieshistory", hicnsFromHistoryQueryNanoSeconds, matchingIdsFromBeneHistory == null ? 0 : matchingIdsFromBeneHistory.size());
}
// Then, find all Beneficiary records that match the hash or those BENE_IDs.
CriteriaQuery<Beneficiary> beneMatches = builder.createQuery(Beneficiary.class);
Root<Beneficiary> beneMatchesRoot = beneMatches.from(Beneficiary.class);
beneMatchesRoot.fetch(Beneficiary_.skippedRifRecords, JoinType.LEFT);
if (requestHeader.isHICNinIncludeIdentifiers())
beneMatchesRoot.fetch(Beneficiary_.beneficiaryHistories, JoinType.LEFT);
if (requestHeader.isMBIinIncludeIdentifiers())
beneMatchesRoot.fetch(Beneficiary_.medicareBeneficiaryIdHistories, JoinType.LEFT);
beneMatches.select(beneMatchesRoot);
Predicate beneHashMatches = builder.equal(beneMatchesRoot.get(beneficiaryHashField), hash);
if (matchingIdsFromBeneHistory != null && !matchingIdsFromBeneHistory.isEmpty()) {
Predicate beneHistoryHashMatches = beneMatchesRoot.get(Beneficiary_.beneficiaryId).in(matchingIdsFromBeneHistory);
beneMatches.where(builder.or(beneHashMatches, beneHistoryHashMatches));
} else {
beneMatches.where(beneHashMatches);
}
List<Beneficiary> matchingBenes = Collections.emptyList();
Long benesByHashOrIdQueryNanoSeconds = null;
Timer.Context timerHicnQuery = metricRegistry.timer(MetricRegistry.name(getClass().getSimpleName(), "query", "bene_by_" + hashType, "bene_by_" + hashType + "_or_id")).time();
try {
matchingBenes = entityManager.createQuery(beneMatches).getResultList();
} finally {
benesByHashOrIdQueryNanoSeconds = timerHicnQuery.stop();
TransformerUtils.recordQueryInMdc(String.format("bene_by_" + hashType + ".bene_by_" + hashType + "_or_id.include_%s", String.join("_", (List<String>) requestHeader.getValue(HEADER_NAME_INCLUDE_IDENTIFIERS))), benesByHashOrIdQueryNanoSeconds, matchingBenes.size());
}
// Then, if we found more than one distinct BENE_ID, or none, throw an error.
long distinctBeneIds = matchingBenes.stream().map(Beneficiary::getBeneficiaryId).filter(Objects::nonNull).distinct().count();
Beneficiary beneficiary = null;
if (distinctBeneIds <= 0) {
throw new NoResultException();
} else if (distinctBeneIds > 1) {
MDC.put("database_query.by_hash.collision.distinct_bene_ids", Long.toString(distinctBeneIds));
throw new ResourceNotFoundException("By hash query found more than one distinct BENE_ID: " + Long.toString(distinctBeneIds));
} else if (distinctBeneIds == 1) {
beneficiary = matchingBenes.get(0);
}
// Null out the unhashed HICNs if we're not supposed to be returning them
if (!requestHeader.isHICNinIncludeIdentifiers()) {
beneficiary.setHicnUnhashed(Optional.empty());
}
// Null out the unhashed MBIs if we're not supposed to be returning
if (!requestHeader.isMBIinIncludeIdentifiers()) {
beneficiary.setMedicareBeneficiaryId(Optional.empty());
}
Patient patient = BeneficiaryTransformer.transform(metricRegistry, beneficiary, requestHeader);
return patient;
}
use of org.hl7.fhir.dstu3.model.ListResource.ListMode.WORKING in project beneficiary-fhir-data by CMSgov.
the class R4ExplanationOfBenefitResourceProviderIT method assertEobEquals.
/**
* Compares two ExplanationOfBenefit objects in detail while working around serialization issues
* like comparing "0" and "0.0" or creation differences like using "Quantity" vs "SimpleQuantity"
*
* @param expected the expected
* @param actual the actual
*/
private static void assertEobEquals(ExplanationOfBenefit expected, ExplanationOfBenefit actual) {
// ID in the bundle will have `ExplanationOfBenefit/` in front, so make sure the last bit
// matches up
assertTrue(actual.getId().endsWith(expected.getId()));
// Clean them out so we can do a deep compare later
actual.setId("");
expected.setId("");
// If there are any contained resources, they might have lastupdate times in them too
assertEquals(expected.getContained().size(), actual.getContained().size());
for (int i = 0; i < expected.getContained().size(); i++) {
Resource expectedContained = expected.getContained().get(i);
Resource actualContained = actual.getContained().get(i);
expectedContained.getMeta().setLastUpdated(null);
actualContained.getMeta().setLastUpdated(null);
// TODO: HAPI seems to be inserting the `#` in the ID of the contained element.
// This is incorrect according to the FHIR spec:
// https://build.fhir.org/references.html#contained
// This works around that problem
assertEquals("#" + expectedContained.getId(), actualContained.getId());
expectedContained.setId("");
actualContained.setId("");
}
// Dates are not easy to compare so just make sure they are there
assertNotNull(actual.getMeta().getLastUpdated());
// We compared all of meta that we care about so get it out of the way
expected.getMeta().setLastUpdated(null);
actual.getMeta().setLastUpdated(null);
// Created is required, but can't be compared
assertNotNull(actual.getCreated());
expected.setCreated(null);
actual.setCreated(null);
// Extensions may have `valueMoney` elements
assertEquals(expected.getExtension().size(), actual.getExtension().size());
for (int i = 0; i < expected.getExtension().size(); i++) {
Extension expectedEx = expected.getExtension().get(i);
Extension actualEx = actual.getExtension().get(i);
// We have to deal with Money objects separately
if (expectedEx.hasValue() && expectedEx.getValue() instanceof Money) {
assertTrue(actualEx.getValue() instanceof Money);
assertCurrencyEquals((Money) expectedEx.getValue(), (Money) actualEx.getValue());
// Now remove since we validated so we can compare the rest directly
expectedEx.setValue(null);
actualEx.setValue(null);
}
}
// SupportingInfo can have `valueQuantity` that has the 0 vs 0.0 issue
assertEquals(expected.getSupportingInfo().size(), actual.getSupportingInfo().size());
for (int i = 0; i < expected.getSupportingInfo().size(); i++) {
SupportingInformationComponent expectedSup = expected.getSupportingInfo().get(i);
SupportingInformationComponent actualSup = actual.getSupportingInfo().get(i);
// We have to deal with Money objects separately
if (expectedSup.hasValueQuantity()) {
assertTrue(actualSup.hasValueQuantity());
assertEquals(expectedSup.getValueQuantity().getValue().floatValue(), actualSup.getValueQuantity().getValue().floatValue(), 0.0);
// Now remove since we validated so we can compare the rest directly
expectedSup.setValue(null);
actualSup.setValue(null);
}
}
// line items
assertEquals(expected.getItem().size(), actual.getItem().size());
for (int i = 0; i < expected.getItem().size(); i++) {
ItemComponent expectedItem = expected.getItem().get(i);
ItemComponent actualItem = actual.getItem().get(i);
// Compare value directly because SimpleQuantity vs Quantity can't be compared
assertEquals(expectedItem.getQuantity().getValue().floatValue(), actualItem.getQuantity().getValue().floatValue(), 0.0);
expectedItem.setQuantity(null);
actualItem.setQuantity(null);
assertEquals(expectedItem.getAdjudication().size(), actualItem.getAdjudication().size());
for (int j = 0; j < expectedItem.getAdjudication().size(); j++) {
AdjudicationComponent expectedAdj = expectedItem.getAdjudication().get(j);
AdjudicationComponent actualAdj = actualItem.getAdjudication().get(j);
// Here is where we start getting into trouble with "0" vs "0.0", so we do this manually
if (expectedAdj.hasAmount()) {
assertNotNull(actualAdj.getAmount());
assertCurrencyEquals(expectedAdj.getAmount(), actualAdj.getAmount());
} else {
// If expected doesn't have an amount, actual shouldn't
assertFalse(actualAdj.hasAmount());
}
// We just checked manually, so null them out and check the rest of the fields
expectedAdj.setAmount(null);
actualAdj.setAmount(null);
}
}
// Total has the same problem with values
assertEquals(expected.getTotal().size(), actual.getTotal().size());
for (int i = 0; i < expected.getTotal().size(); i++) {
TotalComponent expectedTot = expected.getTotal().get(i);
TotalComponent actualTot = actual.getTotal().get(i);
if (expectedTot.hasAmount()) {
assertNotNull(actualTot.getAmount());
assertCurrencyEquals(expectedTot.getAmount(), actualTot.getAmount());
} else {
// If expected doesn't have an amount, actual shouldn't
assertFalse(actualTot.hasAmount());
}
expectedTot.setAmount(null);
actualTot.setAmount(null);
}
// Benefit Balance Financial components
assertEquals(expected.getBenefitBalance().size(), actual.getBenefitBalance().size());
for (int i = 0; i < expected.getBenefitBalance().size(); i++) {
BenefitBalanceComponent expectedBen = expected.getBenefitBalance().get(i);
BenefitBalanceComponent actualBen = actual.getBenefitBalance().get(i);
assertEquals(expectedBen.getFinancial().size(), actualBen.getFinancial().size());
for (int j = 0; j < expectedBen.getFinancial().size(); j++) {
BenefitComponent expectedFinancial = expectedBen.getFinancial().get(j);
BenefitComponent actualFinancial = actualBen.getFinancial().get(j);
// Are we dealing with Money?
if (expectedFinancial.hasUsedMoney()) {
assertNotNull(actualFinancial.getUsedMoney());
assertCurrencyEquals(expectedFinancial.getUsedMoney(), actualFinancial.getUsedMoney());
// Clean up
expectedFinancial.setUsed(null);
actualFinancial.setUsed(null);
}
}
}
// As does payment
if (expected.hasPayment()) {
assertTrue(actual.hasPayment());
assertCurrencyEquals(expected.getPayment().getAmount(), actual.getPayment().getAmount());
} else {
// If expected doesn't have an amount, actual shouldn't
assertFalse(actual.hasPayment());
}
expected.getPayment().setAmount(null);
actual.getPayment().setAmount(null);
// Now for the grand finale, we can do an `equalsDeep` on the rest
assertTrue(expected.equalsDeep(actual));
}
use of org.hl7.fhir.dstu3.model.ListResource.ListMode.WORKING in project beneficiary-fhir-data by CMSgov.
the class R4ExplanationOfBenefitResourceProviderIT method searchForEobsIncludeTaxNumbersHandling.
/**
* Verifies that {@link
* gov.cms.bfd.server.war.r4.providers.ExplanationOfBenefitResourceProvider#findByPatient(ca.uhn.fhir.rest.param.ReferenceParam)}
* handles the {@link ExplanationOfBenefitResourceProvider#HEADER_NAME_INCLUDE_TAX_NUMBERS} header
* properly.
*
* @throws FHIRException (indicates test failure)
*/
// TODO: Fix this test .. it isn't working. Tax number is not showing up.
@Test
public void searchForEobsIncludeTaxNumbersHandling() throws FHIRException {
List<Object> loadedRecords = ServerTestUtils.get().loadData(Arrays.asList(StaticRifResourceGroup.SAMPLE_A.getResources()));
IGenericClient fhirClient = ServerTestUtils.get().createFhirClientV2();
Beneficiary beneficiary = loadedRecords.stream().filter(r -> r instanceof Beneficiary).map(r -> (Beneficiary) r).findFirst().get();
CarrierClaim carrierClaim = loadedRecords.stream().filter(r -> r instanceof CarrierClaim).map(r -> (CarrierClaim) r).findFirst().get();
DMEClaim dmeClaim = loadedRecords.stream().filter(r -> r instanceof DMEClaim).map(r -> (DMEClaim) r).findFirst().get();
Bundle searchResults;
ExplanationOfBenefit carrierEob;
ExplanationOfBenefit dmeEob;
// Run the search without requesting tax numbers.
searchResults = fhirClient.search().forResource(ExplanationOfBenefit.class).where(ExplanationOfBenefit.PATIENT.hasId(TransformerUtilsV2.buildPatientId(beneficiary))).returnBundle(Bundle.class).execute();
assertNotNull(searchResults);
// Verify that tax numbers aren't present for carrier claims.
carrierEob = filterToClaimType(searchResults, ClaimTypeV2.CARRIER).get(0);
assertNull(TransformerTestUtilsV2.findCareTeamEntryForProviderTaxNumber(carrierClaim.getLines().get(0).getProviderTaxNumber(), carrierEob.getCareTeam()));
// Verify that tax numbers aren't present for DME claims.
dmeEob = filterToClaimType(searchResults, ClaimTypeV2.DME).get(0);
assertNull(TransformerTestUtilsV2.findCareTeamEntryForProviderTaxNumber(dmeClaim.getLines().get(0).getProviderTaxNumber(), dmeEob.getCareTeam()));
RequestHeaders requestHeader = RequestHeaders.getHeaderWrapper(CommonHeaders.HEADER_NAME_INCLUDE_TAX_NUMBERS, "true");
fhirClient = ServerTestUtils.get().createFhirClientWithHeadersV2(requestHeader);
searchResults = fhirClient.search().forResource(ExplanationOfBenefit.class).where(ExplanationOfBenefit.PATIENT.hasId(TransformerUtilsV2.buildPatientId(beneficiary))).returnBundle(Bundle.class).execute();
assertNotNull(searchResults);
}
use of org.hl7.fhir.dstu3.model.ListResource.ListMode.WORKING in project hl7v2-fhir-converter by LinuxForHealth.
the class HL7EventTypeFHIRConversionTest method validate_evn_segment_no_period_override.
@Test
void validate_evn_segment_no_period_override() {
String hl7message = "MSH|^~\\&|||||||ADT^A01^ADT_A01|64322|P|2.6|123|456|ER|AL|USA|ASCII|en|2.6||||||\r" + // + "EVN||||7525||20210319134735|\r" // TODO, not working with this value
"EVN||||O||20210319134735|\r" + "PV1|1|I||R|||||||||R|1||||||||||||||||||||||||||||||200603150624|200603150625|||||||";
String json = ftv.convert(hl7message, OPTIONS);
assertThat(json).isNotBlank();
FHIRContext context = new FHIRContext();
IBaseResource bundleResource = context.getParser().parseResource(json);
assertThat(bundleResource).isNotNull();
Bundle b = (Bundle) bundleResource;
List<BundleEntryComponent> e = b.getEntry();
// Find the encounter from the FHIR bundle.
List<Resource> encounterResource = e.stream().filter(v -> ResourceType.Encounter == v.getResource().getResourceType()).map(BundleEntryComponent::getResource).collect(Collectors.toList());
assertThat(encounterResource).hasSize(1);
Encounter encounter = (Encounter) encounterResource.get(0);
// ENV.4 is used for reasonCode
List<CodeableConcept> reasonCodes = encounter.getReasonCode();
assertEquals(1, reasonCodes.size());
CodeableConcept encounterReason = encounter.getReasonCodeFirstRep();
Coding encounterReasonCoding = encounterReason.getCodingFirstRep();
// assertThat(encounterReasonCoding.getCode()).isEqualTo("7525"); // TODO, should be able to use user-defined values
assertThat(encounterReasonCoding.getCode()).isEqualTo("O");
// EVN.6 is used for start period if there is no PV1.44 but since we have a
// PV1.44 it should use that not EVN.6
Base period = encounter.getNamedProperty("period").getValues().get(0);
String startPeriod = period.getNamedProperty("start").getValues().get(0).toString();
// And use PV1.45 for end period.
String endPeriod = period.getNamedProperty("end").getValues().get(0).toString();
assertThat(startPeriod).isEqualTo("DateTimeType[2006-03-15T06:24:00+08:00]");
assertThat(endPeriod).isEqualTo("DateTimeType[2006-03-15T06:25:00+08:00]");
}
use of org.hl7.fhir.dstu3.model.ListResource.ListMode.WORKING in project hl7v2-fhir-converter by LinuxForHealth.
the class HL7EventTypeFHIRConversionTest method validate_evn_segment.
@Test
void validate_evn_segment() {
String hl7message = "MSH|^~\\&|||||||ADT^A01^ADT_A01|64322|P|2.6|123|456|ER|AL|USA|ASCII|en|2.6||||||\r" + // + "EVN||||851||20210319134735|\r" // TODO, not working with this value
"EVN||||O||20210319134735|\r" + "PV1|1|I||R|||||||||R|1||||||||||||||||||||||||||||||||||||||";
String json = ftv.convert(hl7message, OPTIONS);
assertThat(json).isNotBlank();
FHIRContext context = new FHIRContext(true, false);
IBaseResource bundleResource = context.getParser().parseResource(json);
assertThat(bundleResource).isNotNull();
Bundle b = (Bundle) bundleResource;
List<BundleEntryComponent> e = b.getEntry();
// Find the encounter from the FHIR bundle.
List<Resource> encounterResource = e.stream().filter(v -> ResourceType.Encounter == v.getResource().getResourceType()).map(BundleEntryComponent::getResource).collect(Collectors.toList());
assertThat(encounterResource).hasSize(1);
Encounter encounter = (Encounter) encounterResource.get(0);
// ENV.4 is used for reasonCode
List<CodeableConcept> reasonCodes = encounter.getReasonCode();
assertEquals(1, reasonCodes.size());
CodeableConcept encounterReason = encounter.getReasonCodeFirstRep();
Coding encounterReasonCoding = encounterReason.getCodingFirstRep();
// assertThat(encounterReasonCoding.getCode()).isEqualTo("851"); // TODO, should be able to use user-defined values
assertThat(encounterReasonCoding.getCode()).isEqualTo("O");
// EVN.6 is used for start period (with no end) if there is no PV1.44
Base period = encounter.getNamedProperty("period").getValues().get(0);
String startPeriod = period.getNamedProperty("start").getValues().get(0).toString();
int endPeriodSize = period.getNamedProperty("end").getValues().size();
assertThat(startPeriod).isEqualTo("DateTimeType[2021-03-19T13:47:35+08:00]");
assertThat(endPeriodSize).isZero();
}
Aggregations