use of org.hl7.elm.r1.Time in project beneficiary-fhir-data by CMSgov.
the class ExplanationOfBenefitResourceProvider method findClaimTypeByPatient.
/**
* @param claimType the {@link ClaimType} to find
* @param patientId the {@link Beneficiary#getBeneficiaryId()} to filter by
* @param lastUpdated the update time to filter by
* @return the matching claim/event entities
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Trace
private <T> List<T> findClaimTypeByPatient(ClaimType claimType, String patientId, DateRangeParam lastUpdated, DateRangeParam serviceDate) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery criteria = builder.createQuery((Class) claimType.getEntityClass());
Root root = criteria.from(claimType.getEntityClass());
claimType.getEntityLazyAttributes().stream().forEach(a -> root.fetch(a));
criteria.select(root).distinct(true);
// Search for a beneficiary's records. Use lastUpdated if present
Predicate wherePredicate = builder.equal(root.get(claimType.getEntityBeneficiaryIdAttribute()), patientId);
if (lastUpdated != null && !lastUpdated.isEmpty()) {
Predicate predicate = QueryUtils.createLastUpdatedPredicate(builder, root, lastUpdated);
wherePredicate = builder.and(wherePredicate, predicate);
}
criteria.where(wherePredicate);
List<T> claimEntities = null;
Long eobsByBeneIdQueryNanoSeconds = null;
Timer.Context timerEobQuery = metricRegistry.timer(MetricRegistry.name(metricRegistry.getClass().getSimpleName(), "query", "eobs_by_bene_id", claimType.name().toLowerCase())).time();
try {
claimEntities = entityManager.createQuery(criteria).getResultList();
} finally {
eobsByBeneIdQueryNanoSeconds = timerEobQuery.stop();
TransformerUtils.recordQueryInMdc(String.format("eobs_by_bene_id.%s", claimType.name().toLowerCase()), eobsByBeneIdQueryNanoSeconds, claimEntities == null ? 0 : claimEntities.size());
}
if (claimEntities != null && serviceDate != null && !serviceDate.isEmpty()) {
final Instant lowerBound = serviceDate.getLowerBoundAsInstant() != null ? serviceDate.getLowerBoundAsInstant().toInstant() : null;
final Instant upperBound = serviceDate.getUpperBoundAsInstant() != null ? serviceDate.getUpperBoundAsInstant().toInstant() : null;
final java.util.function.Predicate<LocalDate> lowerBoundCheck = lowerBound == null ? (date) -> true : (date) -> compareLocalDate(date, lowerBound.atZone(ZoneId.systemDefault()).toLocalDate(), serviceDate.getLowerBound().getPrefix());
final java.util.function.Predicate<LocalDate> upperBoundCheck = upperBound == null ? (date) -> true : (date) -> compareLocalDate(date, upperBound.atZone(ZoneId.systemDefault()).toLocalDate(), serviceDate.getUpperBound().getPrefix());
return claimEntities.stream().filter(entity -> lowerBoundCheck.test(claimType.getServiceEndAttributeFunction().apply(entity)) && upperBoundCheck.test(claimType.getServiceEndAttributeFunction().apply(entity))).collect(Collectors.toList());
}
return claimEntities;
}
use of org.hl7.elm.r1.Time in project beneficiary-fhir-data by CMSgov.
the class HospiceClaimTransformer method transform.
/**
* @param metricRegistry the {@link MetricRegistry} to use
* @param claim the CCW {@link HospiceClaim} to transform
* @param includeTaxNumbers whether or not to include tax numbers in the result (see {@link
* ExplanationOfBenefitResourceProvider#HEADER_NAME_INCLUDE_TAX_NUMBERS}, defaults to <code>
* false</code>)
* @return a FHIR {@link ExplanationOfBenefit} resource that represents the specified {@link
* HospiceClaim}
*/
@Trace
static ExplanationOfBenefit transform(MetricRegistry metricRegistry, Object claim, Optional<Boolean> includeTaxNumbers) {
Timer.Context timer = metricRegistry.timer(MetricRegistry.name(HospiceClaimTransformer.class.getSimpleName(), "transform")).time();
if (!(claim instanceof HospiceClaim))
throw new BadCodeMonkeyException();
ExplanationOfBenefit eob = transformClaim((HospiceClaim) claim);
timer.stop();
return eob;
}
use of org.hl7.elm.r1.Time in project beneficiary-fhir-data by CMSgov.
the class InpatientClaimTransformer method transform.
/**
* @param metricRegistry the {@link MetricRegistry} to use
* @param claim the CCW {@link InpatientClaim} to transform
* @param includeTaxNumbers whether or not to include tax numbers in the result (see {@link
* ExplanationOfBenefitResourceProvider#HEADER_NAME_INCLUDE_TAX_NUMBERS}, defaults to <code>
* false</code>)
* @return a FHIR {@link ExplanationOfBenefit} resource that represents the specified {@link
* InpatientClaim}
*/
@Trace
static ExplanationOfBenefit transform(MetricRegistry metricRegistry, Object claim, Optional<Boolean> includeTaxNumbers) {
Timer.Context timer = metricRegistry.timer(MetricRegistry.name(InpatientClaimTransformer.class.getSimpleName(), "transform")).time();
if (!(claim instanceof InpatientClaim))
throw new BadCodeMonkeyException();
ExplanationOfBenefit eob = transformClaim((InpatientClaim) claim);
timer.stop();
return eob;
}
use of org.hl7.elm.r1.Time 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.elm.r1.Time in project beneficiary-fhir-data by CMSgov.
the class PatientResourceProvider method read.
/**
* Adds support for the FHIR "read" operation, for {@link Patient}s. The {@link Read} annotation
* indicates that this method supports the read operation.
*
* <p>Read operations take a single parameter annotated with {@link IdParam}, and should return a
* single resource instance.
*
* @param patientId The read operation takes one parameter, which must be of type {@link IdType}
* and must be annotated with the {@link IdParam} annotation.
* @param requestDetails a {@link RequestDetails} containing the details of the request URL, used
* to parse out pagination values
* @return Returns a resource matching the specified {@link IdDt}, or <code>null</code> if none
* exists.
*/
@Read(version = false)
@Trace
public Patient read(@IdParam IdType patientId, RequestDetails requestDetails) {
if (patientId == null)
throw new IllegalArgumentException();
if (patientId.getVersionIdPartAsLong() != null)
throw new IllegalArgumentException();
String beneIdText = patientId.getIdPart();
if (beneIdText == null || beneIdText.trim().isEmpty())
throw new IllegalArgumentException();
RequestHeaders requestHeader = RequestHeaders.getHeaderWrapper(requestDetails);
Operation operation = new Operation(Operation.Endpoint.V1_PATIENT);
operation.setOption("by", "id");
// there is another method with exclude list: requestHeader.getNVPairs(<excludeHeaders>)
requestHeader.getNVPairs().forEach((n, v) -> operation.setOption(n, v.toString()));
operation.publishOperationName();
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Beneficiary> criteria = builder.createQuery(Beneficiary.class);
Root<Beneficiary> root = criteria.from(Beneficiary.class);
root.fetch(Beneficiary_.skippedRifRecords, JoinType.LEFT);
if (requestHeader.isHICNinIncludeIdentifiers())
root.fetch(Beneficiary_.beneficiaryHistories, JoinType.LEFT);
if (requestHeader.isMBIinIncludeIdentifiers())
root.fetch(Beneficiary_.medicareBeneficiaryIdHistories, JoinType.LEFT);
criteria.select(root);
criteria.where(builder.equal(root.get(Beneficiary_.beneficiaryId), beneIdText));
Beneficiary beneficiary = null;
Long beneByIdQueryNanoSeconds = null;
Timer.Context timerBeneQuery = metricRegistry.timer(MetricRegistry.name(getClass().getSimpleName(), "query", "bene_by_id")).time();
try {
beneficiary = entityManager.createQuery(criteria).getSingleResult();
} catch (NoResultException e) {
throw new ResourceNotFoundException(patientId);
} finally {
beneByIdQueryNanoSeconds = timerBeneQuery.stop();
TransformerUtils.recordQueryInMdc(String.format("bene_by_id.include_%s", String.join("_", (List<String>) requestHeader.getValue(HEADER_NAME_INCLUDE_IDENTIFIERS))), beneByIdQueryNanoSeconds, beneficiary == null ? 0 : 1);
}
// 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());
}
// Add bene_id to MDC logs
TransformerUtils.logBeneIdToMdc(Arrays.asList(beneIdText));
Patient patient = BeneficiaryTransformer.transform(metricRegistry, beneficiary, requestHeader);
return patient;
}
Aggregations