Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAT-7990 Include "display" values for related artifacts of libraries #278

Merged
merged 3 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,21 @@ public Library getModuleDefinitionLibrary(
elmTranslatorRestTemplate
.exchange(uri, HttpMethod.PUT, bundleEntity, String.class)
.getBody();
return fhirContextForR5.newJsonParser().parseResource(Library.class, effectiveDrJson);
Library effectiveDataRequirements =
fhirContextForR5.newJsonParser().parseResource(Library.class, effectiveDrJson);
// update relative urls of Library artifacts to be canonical
effectiveDataRequirements
.getRelatedArtifact()
.forEach(
relatedArtifact -> {
if (relatedArtifact.getDisplay().startsWith("Library")) {
relatedArtifact.setResource(
elmTranslatorClientConfig.getMadieUrl()
+ "/"
+ relatedArtifact.getResource());
}
});
return effectiveDataRequirements;
} catch (Exception ex) {
log.error(
"An error occurred getting effective data requirements "
Expand All @@ -63,16 +77,6 @@ public Library getEffectiveDataRequirements(
CqlLibraryDetails libraryDetails, boolean recursive, String accessToken) {
Library effectiveDataRequirements =
getModuleDefinitionLibrary(libraryDetails, recursive, accessToken);
// update relative urls of Library artifacts to be canonical
effectiveDataRequirements
.getRelatedArtifact()
.forEach(
relatedArtifact -> {
if (relatedArtifact.getDisplay().startsWith("Library")) {
relatedArtifact.setResource(
elmTranslatorClientConfig.getMadieUrl() + "/" + relatedArtifact.getResource());
}
});
// effectiveDataRequirements needs to have fixed id: effective-data-requirements
effectiveDataRequirements.setId("effective-data-requirements");
return effectiveDataRequirements;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,19 @@

import gov.cms.madie.madiefhirservice.cql.LibraryCqlVisitor;
import gov.cms.madie.madiefhirservice.cql.LibraryCqlVisitorFactory;
import gov.cms.madie.madiefhirservice.dto.CqlLibraryDetails;
import gov.cms.madie.madiefhirservice.exceptions.LibraryAttachmentNotFoundException;
import gov.cms.madie.madiefhirservice.exceptions.MissingCqlException;
import gov.cms.madie.madiefhirservice.utils.BundleUtil;
import gov.cms.madie.models.library.CqlLibrary;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50;
import org.hl7.fhir.convertors.conv40_50.VersionConvertor_40_50;
import org.hl7.fhir.r4.model.Attachment;
import org.hl7.fhir.r4.model.DataRequirement;
import org.hl7.fhir.r4.model.Library;
import org.hl7.fhir.r4.model.Narrative;
import org.hl7.fhir.r4.model.Narrative.NarrativeStatus;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

@Service
Expand All @@ -32,45 +26,16 @@ public class LibraryService {
private final LibraryTranslatorService libraryTranslatorService;
private final LibraryCqlVisitorFactory libCqlVisitorFactory;
private final HumanReadableService humanReadableService;
private final ElmTranslatorClient elmTranslatorClient;

public String getLibraryCql(String name, String version, final String accessToken) {
CqlLibrary library = cqlLibraryService.getLibrary(name, version, accessToken);
if (StringUtils.isBlank(library.getCql())) {
throw new MissingCqlException(library);
}
return cqlLibraryService.getLibrary(name, version, accessToken).getCql();
}

public Library cqlLibraryToFhirLibrary(
CqlLibrary cqlLibrary, final String bundleType, String accessToken) {
// Use the DataRequirementsProcessor to typecast Profile types
// (e.g. "QICore Simple Observation") to FHIR types (e.g. "Observation").
List<DataRequirement> fhirTypedDataRequirements =
retrieveLibraryDataRequirements(
CqlLibraryDetails.builder()
.libraryName(cqlLibrary.getCqlLibraryName())
.cql(cqlLibrary.getCql())
.build(),
accessToken);
Library library = libraryTranslatorService.convertToFhirLibrary(cqlLibrary);
library.setDataRequirement(fhirTypedDataRequirements);
Library library = libraryTranslatorService.convertToFhirLibrary(cqlLibrary, null, accessToken);
if (BundleUtil.MEASURE_BUNDLE_TYPE_EXPORT.equals(bundleType)) {
library.setText(createLibraryNarrativeText(library));
}
return library;
}

private List<DataRequirement> retrieveLibraryDataRequirements(
CqlLibraryDetails cqlLibraryDetails, String accessToken) {
org.hl7.fhir.r5.model.Library r5moduleDefinition =
elmTranslatorClient.getModuleDefinitionLibrary(cqlLibraryDetails, false, accessToken);
var versionConvertor_40_50 = new VersionConvertor_40_50(new BaseAdvisor_40_50());
org.hl7.fhir.r4.model.Library r4moduleDefinitionLibrary =
(org.hl7.fhir.r4.model.Library) versionConvertor_40_50.convertResource(r5moduleDefinition);
return r4moduleDefinitionLibrary.getDataRequirement();
}

public void getIncludedLibraries(
String cql,
Map<String, Library> libraryMap,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,24 @@

import gov.cms.madie.madiefhirservice.constants.UriConstants;
import gov.cms.madie.madiefhirservice.cql.LibraryCqlVisitorFactory;
import gov.cms.madie.madiefhirservice.dto.CqlLibraryDetails;
import gov.cms.madie.madiefhirservice.utils.FhirResourceHelpers;
import gov.cms.madie.models.library.CqlLibrary;
import gov.cms.madie.models.common.Version;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50;
import org.hl7.fhir.convertors.conv40_50.VersionConvertor_40_50;
import org.hl7.fhir.r4.model.Attachment;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.DataRequirement;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Library;
import org.hl7.fhir.r4.model.Meta;
import org.hl7.fhir.r4.model.RelatedArtifact;
import org.hl7.fhir.r4.model.Identifier.IdentifierUse;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.*;

@Slf4j
@Service
Expand All @@ -38,12 +31,16 @@ public class LibraryTranslatorService {
public static final String UNKNOWN_VALUE = "UNKNOWN";

private final LibraryCqlVisitorFactory libCqlVisitorFactory;
private final ElmTranslatorClient elmTranslatorClient;

public LibraryTranslatorService(LibraryCqlVisitorFactory libCqlVisitorFactory) {
public LibraryTranslatorService(
LibraryCqlVisitorFactory libCqlVisitorFactory, ElmTranslatorClient elmTranslatorClient) {
this.libCqlVisitorFactory = libCqlVisitorFactory;
this.elmTranslatorClient = elmTranslatorClient;
}

public Library convertToFhirLibrary(CqlLibrary cqlLibrary) {
public Library convertToFhirLibrary(
CqlLibrary cqlLibrary, Set<String> expressions, String accessToken) {
var visitor = libCqlVisitorFactory.visit(cqlLibrary.getCql());
Library library = new Library();
library.setId(cqlLibrary.getCqlLibraryName());
Expand All @@ -64,7 +61,6 @@ public Library convertToFhirLibrary(CqlLibrary cqlLibrary) {
library.setUrl(
FhirResourceHelpers.buildResourceFullUrl("Library", cqlLibrary.getCqlLibraryName()));
library.getExtension().addAll(visitor.getDrcExtensions());
library.setRelatedArtifact(distinctArtifacts(visitor.getRelatedArtifacts()));
library.setMeta(createLibraryMeta());
library.setTitle(cqlLibrary.getCqlLibraryName());
library.setPublisher(cqlLibrary.getPublisher());
Expand All @@ -73,39 +69,26 @@ public Library convertToFhirLibrary(CqlLibrary cqlLibrary) {
identifier.setSystem("https://madie.cms.gov/login");
identifier.setValue(cqlLibrary.getId());
library.setIdentifier(List.of(identifier));
// Use the DataRequirementsProcessor to construct data requirements and related artifacts.
Library libraryModuleDefinition =
retrieveLibraryModuleDefinition(
CqlLibraryDetails.builder()
.libraryName(cqlLibrary.getCqlLibraryName())
.cql(cqlLibrary.getCql())
.expressions(expressions)
.build(),
accessToken);
library.setRelatedArtifact(libraryModuleDefinition.getRelatedArtifact());
library.setDataRequirement(libraryModuleDefinition.getDataRequirement());
return library;
}

public CqlLibrary convertToCqlLibrary(Library library) {
return CqlLibrary.builder()
.id(library.getMeta().getId())
.cqlLibraryName(library.getName())
.version(Version.parse(library.getVersion()))
.publisher(UNKNOWN_VALUE.equals(library.getPublisher()) ? null : library.getPublisher())
.description(
UNKNOWN_VALUE.equals(library.getDescription()) ? null : library.getDescription())
.experimental(library.getExperimental())
.cql(attachmentToString(findAttachmentOfContentType(library, CQL_CONTENT_TYPE)))
.elmJson(attachmentToString(findAttachmentOfContentType(library, JSON_ELM_CONTENT_TYPE)))
.elmXml(attachmentToString(findAttachmentOfContentType(library, XML_ELM_CONTENT_TYPE)))
.build();
}

public String attachmentToString(Attachment attachment) {
if (attachment == null) {
return null;
}
return new String(attachment.getData());
}

public Attachment findAttachmentOfContentType(Library library, String contentType) {
if (library == null || library.getContent() == null) {
return null;
}
return library.getContent().stream()
.filter(a -> a.getContentType().equals(contentType))
.findFirst()
.orElse(null);
private Library retrieveLibraryModuleDefinition(
CqlLibraryDetails cqlLibraryDetails, String accessToken) {
org.hl7.fhir.r5.model.Library r5moduleDefinition =
elmTranslatorClient.getModuleDefinitionLibrary(cqlLibraryDetails, false, accessToken);
var versionConvertor_40_50 = new VersionConvertor_40_50(new BaseAdvisor_40_50());
return (Library) versionConvertor_40_50.convertResource(r5moduleDefinition);
}

private Meta createLibraryMeta() {
Expand Down Expand Up @@ -137,69 +120,6 @@ private List<Attachment> createContent(String cql, String elmJson, String elmXml
return attachments;
}

private List<RelatedArtifact> distinctArtifacts(List<RelatedArtifact> artifacts) {
List<RelatedArtifact> result = new ArrayList<>(artifacts.size());
// Remove duplicates.
artifacts.forEach(
a -> {
if (result.stream().noneMatch(ar -> Objects.deepEquals(a, ar))) {
result.add(a);
}
});
result.sort(Comparator.comparing(RelatedArtifact::getResource));
return result;
}

private List<DataRequirement> distinctDataRequirements(List<DataRequirement> reqs) {
List<DataRequirement> result = new ArrayList<>(reqs.size());
// Remove duplicates.
for (DataRequirement req : reqs) {
if (result.stream()
.noneMatch(r -> matchType(req.getType()).and(matchCodeFilter(req)).test(r))) {
result.add(req);
}
}
return result;
}

private Predicate<DataRequirement> matchType(String type) {
return d -> StringUtils.equals(d.getType(), type);
}

private Predicate<DataRequirement> matchCodeFilter(DataRequirement o) {
return d -> {
if ((CollectionUtils.isEmpty(d.getCodeFilter())
&& CollectionUtils.isEmpty(o.getCodeFilter()))) {
// Match when both code filters are empty
return true;
} else if ((CollectionUtils.isEmpty(d.getCodeFilter())
|| CollectionUtils.isEmpty(o.getCodeFilter()))) {
// No match if either code filter is empty
return false;
} else {
// Match on path AND (code or value set)
return StringUtils.equals(
d.getCodeFilter().get(0).getPath(), o.getCodeFilter().get(0).getPath())
&& (hasMatchingValueSet(d, o) || hasMatchingCode(o, d));
}
};
}

private boolean hasMatchingCode(DataRequirement o, DataRequirement d) {
return (!CollectionUtils.isEmpty(d.getCodeFilter().get(0).getCode())
&& !CollectionUtils.isEmpty(o.getCodeFilter().get(0).getCode()))
&& StringUtils.equals(
d.getCodeFilter().get(0).getCode().get(0).getCode(),
o.getCodeFilter().get(0).getCode().get(0).getCode());
}

private boolean hasMatchingValueSet(DataRequirement d, DataRequirement o) {
return (d.getCodeFilter().get(0).getValueSet() != null
&& o.getCodeFilter().get(0).getValueSet() != null)
&& StringUtils.equals(
d.getCodeFilter().get(0).getValueSet(), o.getCodeFilter().get(0).getValueSet());
}

private CodeableConcept createType(String type, String code) {
return new CodeableConcept().setCoding(Collections.singletonList(new Coding(type, code, null)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,25 +130,15 @@ public List<Bundle.BundleEntryComponent> createBundleComponentsForLibrariesOfMad
* Creates a Library resource for main library of MADiE Measure
*
* @param expressions- measure populations, SDEs, Stratification
* @param madieMeasure
* @param accessToken
* @param madieMeasure -> instance of MADiE measure
* @param accessToken -> okta token
* @return library- r4 library
*/
public Library getMeasureLibraryResourceForMadieMeasure(
Set<String> expressions, Measure madieMeasure, String accessToken) {
log.info("Preparing Measure library resource for measure: {}", madieMeasure.getId());
CqlLibrary cqlLibrary = createCqlLibraryForMadieMeasure(madieMeasure);
CqlLibraryDetails libraryDetails =
CqlLibraryDetails.builder()
.libraryName(cqlLibrary.getCqlLibraryName())
.cql(cqlLibrary.getCql())
.expressions(expressions)
.build();
Library library = libraryTranslatorService.convertToFhirLibrary(cqlLibrary);
org.hl7.fhir.r5.model.Library r5moduleDefinition =
elmTranslatorClient.getModuleDefinitionLibrary(libraryDetails, false, accessToken);
updateLibraryDataRequirements(library, r5moduleDefinition);
return library;
return libraryTranslatorService.convertToFhirLibrary(cqlLibrary, expressions, accessToken);
}

/**
Expand Down Expand Up @@ -215,14 +205,4 @@ private Set<String> getExpressions(org.hl7.fhir.r4.model.Measure r5Measure) {
});
return expressionSet;
}

private void updateLibraryDataRequirements(
org.hl7.fhir.r4.model.Library library,
org.hl7.fhir.r5.model.Library r5moduleDefinitionLibrary) {
var versionConvertor_40_50 = new VersionConvertor_40_50(new BaseAdvisor_40_50());
org.hl7.fhir.r4.model.Library r4moduleDefinitionLibrary =
(org.hl7.fhir.r4.model.Library)
versionConvertor_40_50.convertResource(r5moduleDefinitionLibrary);
library.setDataRequirement(r4moduleDefinitionLibrary.getDataRequirement());
}
}
Loading
Loading