Search in sources :

Example 1 with Operation

use of org.hl7.fhir.utilities.graphql.Operation in project beneficiary-fhir-data by CMSgov.

the class ExplanationOfBenefitResourceProvider method findByPatient.

/**
 * Adds support for the FHIR "search" operation for {@link ExplanationOfBenefit}s, allowing users
 * to search by {@link ExplanationOfBenefit#getPatient()}.
 *
 * <p>The {@link Search} annotation indicates that this method supports the search operation.
 * There may be many different methods annotated with this {@link Search} annotation, to support
 * many different search criteria.
 *
 * @param patient a {@link ReferenceParam} for the {@link ExplanationOfBenefit#getPatient()} to
 *     try and find matches for {@link ExplanationOfBenefit}s
 * @param type a list of {@link ClaimType} to include in the result. Defaults to all types.
 * @param startIndex an {@link OptionalParam} for the startIndex (or offset) used to determine
 *     pagination
 * @param excludeSamhsa an {@link OptionalParam} that, if <code>"true"</code>, will use {@link
 *     Stu3EobSamhsaMatcher} to filter out all SAMHSA-related claims from the results
 * @param lastUpdated an {@link OptionalParam} that specifies a date range for the lastUpdated
 *     field.
 * @param serviceDate an {@link OptionalParam} that specifies a date range for {@link
 *     ExplanationOfBenefit}s that completed
 * @param requestDetails a {@link RequestDetails} containing the details of the request URL, used
 *     to parse out pagination values
 * @return Returns a {@link Bundle} of {@link ExplanationOfBenefit}s, which may contain multiple
 *     matching resources, or may also be empty.
 */
@Search
@Trace
public Bundle findByPatient(@RequiredParam(name = ExplanationOfBenefit.SP_PATIENT) @Description(shortDefinition = "The patient identifier to search for") ReferenceParam patient, @OptionalParam(name = "type") @Description(shortDefinition = "A list of claim types to include") TokenAndListParam type, @OptionalParam(name = "startIndex") @Description(shortDefinition = "The offset used for result pagination") String startIndex, @OptionalParam(name = "excludeSAMHSA") @Description(shortDefinition = "If true, exclude all SAMHSA-related resources") String excludeSamhsa, @OptionalParam(name = "_lastUpdated") @Description(shortDefinition = "Include resources last updated in the given range") DateRangeParam lastUpdated, @OptionalParam(name = "service-date") @Description(shortDefinition = "Include resources that completed in the given range") DateRangeParam serviceDate, RequestDetails requestDetails) {
    /*
     * startIndex is an optional parameter here because it must be declared in the
     * event it is passed in. However, it is not being used here because it is also
     * contained within requestDetails and parsed out along with other parameters
     * later.
     */
    String beneficiaryId = patient.getIdPart();
    Set<ClaimType> claimTypes = parseTypeParam(type);
    Boolean includeTaxNumbers = returnIncludeTaxNumbers(requestDetails);
    OffsetLinkBuilder paging = new OffsetLinkBuilder(requestDetails, "/ExplanationOfBenefit?");
    Operation operation = new Operation(Operation.Endpoint.V1_EOB);
    operation.setOption("by", "patient");
    operation.setOption("types", (claimTypes.size() == ClaimType.values().length) ? "*" : claimTypes.stream().sorted(Comparator.comparing(ClaimType::name)).collect(Collectors.toList()).toString());
    operation.setOption("IncludeTaxNumbers", "" + includeTaxNumbers);
    operation.setOption("pageSize", paging.isPagingRequested() ? "" + paging.getPageSize() : "*");
    operation.setOption("_lastUpdated", Boolean.toString(lastUpdated != null && !lastUpdated.isEmpty()));
    operation.setOption("service-date", Boolean.toString(serviceDate != null && !serviceDate.isEmpty()));
    operation.publishOperationName();
    List<IBaseResource> eobs = new ArrayList<IBaseResource>();
    // Optimize when the lastUpdated parameter is specified and result set is empty
    if (loadedFilterManager.isResultSetEmpty(beneficiaryId, lastUpdated)) {
        return TransformerUtils.createBundle(paging, eobs, loadedFilterManager.getTransactionTime());
    }
    /*
     * The way our JPA/SQL schema is setup, we have to run a separate search for
     * each claim type, then combine the results. It's not super efficient, but it's
     * also not so inefficient that it's worth fixing.
     */
    if (claimTypes.contains(ClaimType.CARRIER))
        eobs.addAll(transformToEobs(ClaimType.CARRIER, findClaimTypeByPatient(ClaimType.CARRIER, beneficiaryId, lastUpdated, serviceDate), Optional.of(includeTaxNumbers)));
    if (claimTypes.contains(ClaimType.DME))
        eobs.addAll(transformToEobs(ClaimType.DME, findClaimTypeByPatient(ClaimType.DME, beneficiaryId, lastUpdated, serviceDate), Optional.of(includeTaxNumbers)));
    if (claimTypes.contains(ClaimType.HHA))
        eobs.addAll(transformToEobs(ClaimType.HHA, findClaimTypeByPatient(ClaimType.HHA, beneficiaryId, lastUpdated, serviceDate), Optional.of(includeTaxNumbers)));
    if (claimTypes.contains(ClaimType.HOSPICE))
        eobs.addAll(transformToEobs(ClaimType.HOSPICE, findClaimTypeByPatient(ClaimType.HOSPICE, beneficiaryId, lastUpdated, serviceDate), Optional.of(includeTaxNumbers)));
    if (claimTypes.contains(ClaimType.INPATIENT))
        eobs.addAll(transformToEobs(ClaimType.INPATIENT, findClaimTypeByPatient(ClaimType.INPATIENT, beneficiaryId, lastUpdated, serviceDate), Optional.of(includeTaxNumbers)));
    if (claimTypes.contains(ClaimType.OUTPATIENT))
        eobs.addAll(transformToEobs(ClaimType.OUTPATIENT, findClaimTypeByPatient(ClaimType.OUTPATIENT, beneficiaryId, lastUpdated, serviceDate), Optional.of(includeTaxNumbers)));
    if (claimTypes.contains(ClaimType.PDE))
        eobs.addAll(transformToEobs(ClaimType.PDE, findClaimTypeByPatient(ClaimType.PDE, beneficiaryId, lastUpdated, serviceDate), Optional.of(includeTaxNumbers)));
    if (claimTypes.contains(ClaimType.SNF))
        eobs.addAll(transformToEobs(ClaimType.SNF, findClaimTypeByPatient(ClaimType.SNF, beneficiaryId, lastUpdated, serviceDate), Optional.of(includeTaxNumbers)));
    if (Boolean.parseBoolean(excludeSamhsa))
        filterSamhsa(eobs);
    eobs.sort(ExplanationOfBenefitResourceProvider::compareByClaimIdThenClaimType);
    // Add bene_id to MDC logs
    TransformerUtils.logBeneIdToMdc(Arrays.asList(beneficiaryId));
    return TransformerUtils.createBundle(paging, eobs, loadedFilterManager.getTransactionTime());
}
Also used : OffsetLinkBuilder(gov.cms.bfd.server.war.commons.OffsetLinkBuilder) ArrayList(java.util.ArrayList) Operation(gov.cms.bfd.server.war.Operation) IBaseResource(org.hl7.fhir.instance.model.api.IBaseResource) Trace(com.newrelic.api.agent.Trace) Search(ca.uhn.fhir.rest.annotation.Search)

Example 2 with Operation

use of org.hl7.fhir.utilities.graphql.Operation 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;
}
Also used : CriteriaBuilder(javax.persistence.criteria.CriteriaBuilder) Patient(org.hl7.fhir.dstu3.model.Patient) Operation(gov.cms.bfd.server.war.Operation) NoResultException(javax.persistence.NoResultException) Timer(com.codahale.metrics.Timer) RequestHeaders(gov.cms.bfd.server.war.commons.RequestHeaders) ResourceNotFoundException(ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException) Beneficiary(gov.cms.bfd.model.rif.Beneficiary) Read(ca.uhn.fhir.rest.annotation.Read) Trace(com.newrelic.api.agent.Trace)

Example 3 with Operation

use of org.hl7.fhir.utilities.graphql.Operation in project beneficiary-fhir-data by CMSgov.

the class PatientResourceProvider method searchByIdentifier.

/**
 * Adds support for the FHIR "search" operation for {@link Patient}s, allowing users to search by
 * {@link Patient#getIdentifier()}. Specifically, the following criteria are supported:
 *
 * <ul>
 *   <li>Matching a {@link Beneficiary#getHicn()} hash value: when {@link TokenParam#getSystem()}
 *       matches one of the {@link #SUPPORTED_HASH_IDENTIFIER_SYSTEMS} entries.
 * </ul>
 *
 * <p>Searches that don't match one of the above forms are not supported.
 *
 * <p>The {@link Search} annotation indicates that this method supports the search operation.
 * There may be many different methods annotated with this {@link Search} annotation, to support
 * many different search criteria.
 *
 * @param identifier an {@link Identifier} {@link TokenParam} for the {@link
 *     Patient#getIdentifier()} to try and find a matching {@link Patient} for
 * @param startIndex an {@link OptionalParam} for the startIndex (or offset) used to determine
 *     pagination
 * @param lastUpdated an {@link OptionalParam} to filter the results based on the passed date
 *     range
 * @param requestDetails a {@link RequestDetails} containing the details of the request URL, used
 *     to parse out pagination values
 * @return Returns a {@link List} of {@link Patient}s, which may contain multiple matching
 *     resources, or may also be empty.
 */
@Search
@Trace
public Bundle searchByIdentifier(@RequiredParam(name = Patient.SP_IDENTIFIER) @Description(shortDefinition = "The patient identifier to search for") TokenParam identifier, @OptionalParam(name = "startIndex") @Description(shortDefinition = "The offset used for result pagination") String startIndex, @OptionalParam(name = "_lastUpdated") @Description(shortDefinition = "Include resources last updated in the given range") DateRangeParam lastUpdated, RequestDetails requestDetails) {
    if (identifier.getQueryParameterQualifier() != null)
        throw new InvalidRequestException("Unsupported query parameter qualifier: " + identifier.getQueryParameterQualifier());
    if (!SUPPORTED_HASH_IDENTIFIER_SYSTEMS.contains(identifier.getSystem()))
        throw new InvalidRequestException("Unsupported identifier system: " + identifier.getSystem());
    RequestHeaders requestHeader = RequestHeaders.getHeaderWrapper(requestDetails);
    Operation operation = new Operation(Operation.Endpoint.V1_PATIENT);
    operation.setOption("by", "identifier");
    requestHeader.getNVPairs().forEach((n, v) -> operation.setOption(n, v.toString()));
    operation.setOption("_lastUpdated", Boolean.toString(lastUpdated != null && !lastUpdated.isEmpty()));
    operation.publishOperationName();
    List<IBaseResource> patients;
    try {
        Patient patient;
        switch(identifier.getSystem()) {
            case TransformerConstants.CODING_BBAPI_BENE_HICN_HASH:
            case TransformerConstants.CODING_BBAPI_BENE_HICN_HASH_OLD:
                patient = queryDatabaseByHicnHash(identifier.getValue(), requestHeader);
                break;
            case TransformerConstants.CODING_BBAPI_BENE_MBI_HASH:
                patient = queryDatabaseByMbiHash(identifier.getValue(), requestHeader);
                break;
            default:
                throw new InvalidRequestException("Unsupported identifier system: " + identifier.getSystem());
        }
        patients = QueryUtils.isInRange(patient.getMeta().getLastUpdated().toInstant(), lastUpdated) ? Collections.singletonList(patient) : Collections.emptyList();
    } catch (NoResultException e) {
        patients = new LinkedList<>();
    }
    OffsetLinkBuilder paging = new OffsetLinkBuilder(requestDetails, "/Patient?");
    Bundle bundle = TransformerUtils.createBundle(paging, patients, loadedFilterManager.getTransactionTime());
    return bundle;
}
Also used : OffsetLinkBuilder(gov.cms.bfd.server.war.commons.OffsetLinkBuilder) Bundle(org.hl7.fhir.dstu3.model.Bundle) Patient(org.hl7.fhir.dstu3.model.Patient) InvalidRequestException(ca.uhn.fhir.rest.server.exceptions.InvalidRequestException) Operation(gov.cms.bfd.server.war.Operation) NoResultException(javax.persistence.NoResultException) RequestHeaders(gov.cms.bfd.server.war.commons.RequestHeaders) IBaseResource(org.hl7.fhir.instance.model.api.IBaseResource) LinkedList(java.util.LinkedList) Trace(com.newrelic.api.agent.Trace) Search(ca.uhn.fhir.rest.annotation.Search)

Example 4 with Operation

use of org.hl7.fhir.utilities.graphql.Operation in project beneficiary-fhir-data by CMSgov.

the class CoverageResourceProvider method searchByBeneficiary.

/**
 * Adds support for the FHIR "search" operation for {@link Coverage}s, allowing users to search by
 * {@link Coverage#getBeneficiary()}.
 *
 * <p>The {@link Search} annotation indicates that this method supports the search operation.
 * There may be many different methods annotated with this {@link Search} annotation, to support
 * many different search criteria.
 *
 * @param beneficiary a {@link ReferenceParam} for the {@link Coverage#getBeneficiary()} to try
 *     and find matches for
 * @param startIndex an {@link OptionalParam} for the startIndex (or offset) used to determine
 *     pagination
 * @param lastUpdated an {@link OptionalParam} to filter the results based on the passed date
 *     range
 * @param requestDetails a {@link RequestDetails} containing the details of the request URL, used
 *     to parse out pagination values
 * @return Returns a {@link List} of {@link Coverage}s, which may contain multiple matching
 *     resources, or may also be empty.
 */
@Search
@Trace
public Bundle searchByBeneficiary(@RequiredParam(name = Coverage.SP_BENEFICIARY) @Description(shortDefinition = "The patient identifier to search for") ReferenceParam beneficiary, @OptionalParam(name = "startIndex") @Description(shortDefinition = "The offset used for result pagination") String startIndex, @OptionalParam(name = "_lastUpdated") @Description(shortDefinition = "Include resources last updated in the given range") DateRangeParam lastUpdated, RequestDetails requestDetails) {
    List<IBaseResource> coverages;
    try {
        Beneficiary beneficiaryEntity = findBeneficiaryById(beneficiary.getIdPart(), lastUpdated);
        if (!beneficiaryEntity.getBeneEnrollmentReferenceYear().isPresent()) {
            throw new ResourceNotFoundException("Cannot find coverage for non present enrollment year");
        }
        coverages = CoverageTransformer.transform(metricRegistry, beneficiaryEntity);
    } catch (NoResultException e) {
        coverages = new LinkedList<IBaseResource>();
    }
    OffsetLinkBuilder paging = new OffsetLinkBuilder(requestDetails, "/Coverage?");
    Operation operation = new Operation(Operation.Endpoint.V1_COVERAGE);
    operation.setOption("by", "beneficiary");
    operation.setOption("pageSize", paging.isPagingRequested() ? "" + paging.getPageSize() : "*");
    operation.setOption("_lastUpdated", Boolean.toString(lastUpdated != null && !lastUpdated.isEmpty()));
    operation.publishOperationName();
    // Add bene_id to MDC logs
    TransformerUtils.logBeneIdToMdc(Arrays.asList(beneficiary.getIdPart()));
    return TransformerUtils.createBundle(paging, coverages, loadedFilterManager.getTransactionTime());
}
Also used : OffsetLinkBuilder(gov.cms.bfd.server.war.commons.OffsetLinkBuilder) NoResultException(javax.persistence.NoResultException) Operation(gov.cms.bfd.server.war.Operation) IBaseResource(org.hl7.fhir.instance.model.api.IBaseResource) ResourceNotFoundException(ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException) LinkedList(java.util.LinkedList) Beneficiary(gov.cms.bfd.model.rif.Beneficiary) Trace(com.newrelic.api.agent.Trace) Search(ca.uhn.fhir.rest.annotation.Search)

Example 5 with Operation

use of org.hl7.fhir.utilities.graphql.Operation in project beneficiary-fhir-data by CMSgov.

the class R4CoverageResourceProvider method read.

/**
 * Adds support for the FHIR "read" operation, for {@link Coverage}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 coverageId The read operation takes one parameter, which must be of type {@link IdType}
 *     and must be annotated with the {@link IdParam} annotation.
 * @return Returns a resource matching the specified {@link IdDt}, or <code>null</code> if none
 *     exists.
 */
@Read(version = false)
@Trace
public Coverage read(@IdParam IdType coverageId) {
    if (coverageId == null)
        throw new IllegalArgumentException();
    if (coverageId.getVersionIdPartAsLong() != null)
        throw new IllegalArgumentException();
    String coverageIdText = coverageId.getIdPart();
    if (coverageIdText == null || coverageIdText.trim().isEmpty())
        throw new IllegalArgumentException();
    Operation operation = new Operation(Operation.Endpoint.V2_COVERAGE);
    operation.setOption("by", "id");
    operation.publishOperationName();
    Matcher coverageIdMatcher = COVERAGE_ID_PATTERN.matcher(coverageIdText);
    if (!coverageIdMatcher.matches())
        throw new IllegalArgumentException("Unsupported ID pattern: " + coverageIdText);
    String coverageIdSegmentText = coverageIdMatcher.group(1);
    Optional<MedicareSegment> coverageIdSegment = MedicareSegment.selectByUrlPrefix(coverageIdSegmentText);
    if (!coverageIdSegment.isPresent())
        throw new ResourceNotFoundException(coverageId);
    String coverageIdBeneficiaryIdText = coverageIdMatcher.group(2);
    Beneficiary beneficiaryEntity;
    try {
        beneficiaryEntity = findBeneficiaryById(coverageIdBeneficiaryIdText, null);
        if (!beneficiaryEntity.getBeneEnrollmentReferenceYear().isPresent()) {
            throw new ResourceNotFoundException("Cannot find coverage for non present enrollment year");
        }
    } catch (NoResultException e) {
        throw new ResourceNotFoundException(new IdDt(Beneficiary.class.getSimpleName(), coverageIdBeneficiaryIdText));
    }
    Coverage coverage = CoverageTransformerV2.transform(metricRegistry, coverageIdSegment.get(), beneficiaryEntity);
    return coverage;
}
Also used : Matcher(java.util.regex.Matcher) MedicareSegment(gov.cms.bfd.server.war.commons.MedicareSegment) IdDt(ca.uhn.fhir.model.primitive.IdDt) Coverage(org.hl7.fhir.r4.model.Coverage) Operation(gov.cms.bfd.server.war.Operation) NoResultException(javax.persistence.NoResultException) ResourceNotFoundException(ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException) Beneficiary(gov.cms.bfd.model.rif.Beneficiary) Read(ca.uhn.fhir.rest.annotation.Read) Trace(com.newrelic.api.agent.Trace)

Aggregations

Parameters (org.hl7.fhir.r4.model.Parameters)72 Test (org.junit.jupiter.api.Test)69 RestIntegrationTest (org.opencds.cqf.ruler.test.RestIntegrationTest)63 SpringBootTest (org.springframework.boot.test.context.SpringBootTest)63 StringType (org.hl7.fhir.r4.model.StringType)62 ArrayList (java.util.ArrayList)60 IOException (java.io.IOException)54 List (java.util.List)54 Bundle (org.hl7.fhir.r4.model.Bundle)53 IGenericClient (ca.uhn.fhir.rest.client.api.IGenericClient)52 Collectors (java.util.stream.Collectors)51 Beneficiary (gov.cms.bfd.model.rif.Beneficiary)50 RequestHeaders (gov.cms.bfd.server.war.commons.RequestHeaders)48 FileOutputStream (java.io.FileOutputStream)47 Collections (java.util.Collections)47 Optional (java.util.Optional)47 File (java.io.File)46 Arrays (java.util.Arrays)46 CommonHeaders (gov.cms.bfd.server.war.commons.CommonHeaders)45 TransformerConstants (gov.cms.bfd.server.war.commons.TransformerConstants)45