diff --git a/core/src/main/java/com/lantanagroup/link/ApplyConceptMaps.java b/core/src/main/java/com/lantanagroup/link/ApplyConceptMaps.java index b65f04f49..5cb0053a6 100644 --- a/core/src/main/java/com/lantanagroup/link/ApplyConceptMaps.java +++ b/core/src/main/java/com/lantanagroup/link/ApplyConceptMaps.java @@ -19,6 +19,14 @@ public class ApplyConceptMaps { private List conceptMaps; + public static boolean isMapped(Coding coding, String system, String code) { + return coding.getExtensionsByUrl(Constants.ConceptMappingExtension).stream() + .map(Extension::getValue) + .filter(value -> value instanceof Coding) + .map(value -> (Coding) value) + .anyMatch(mappedCoding -> mappedCoding.is(system, code)); + } + public ApplyConceptMaps() { validationSupport.fetchAllStructureDefinitions(); } diff --git a/core/src/main/java/com/lantanagroup/link/Constants.java b/core/src/main/java/com/lantanagroup/link/Constants.java index 3a8f603d2..e452cbbe3 100644 --- a/core/src/main/java/com/lantanagroup/link/Constants.java +++ b/core/src/main/java/com/lantanagroup/link/Constants.java @@ -32,6 +32,7 @@ public class Constants { public static final String OriginalElementValueExtension = "http://www.cdc.gov/nhsn/fhirportal/dqm/ig/StructureDefinition/link-original-element-value-extension"; public static final String OriginalResourceIdExtension = "http://www.cdc.gov/nhsn/fhirportal/dqm/ig/StructureDefinition/link-original-resource-id-extension"; public static final String BundlingFullUrlFormat = "http://www.cdc.gov/nhsn/fhirportal/dqm/ig/%s"; + public static final String LocationAliasCodeSystem = "https://nhsnlink.org/location-alias"; //Metric taskName Constants public static final String TASK_SUBMIT = "submit"; diff --git a/core/src/main/java/com/lantanagroup/link/PeriodDateFixer.java b/core/src/main/java/com/lantanagroup/link/PeriodDateFixer.java index a43d3a6d0..5de18fdff 100644 --- a/core/src/main/java/com/lantanagroup/link/PeriodDateFixer.java +++ b/core/src/main/java/com/lantanagroup/link/PeriodDateFixer.java @@ -25,14 +25,16 @@ public void FixDates() DateTimeType end = p.getEndElement(); if (start.getPrecision() == TemporalPrecisionEnum.DAY && end.getPrecision().getCalendarConstant() > TemporalPrecisionEnum.DAY.getCalendarConstant()) { + p.getStartElement().addExtension() + .setUrl(Constants.OriginalElementValueExtension) + .setValue(p.getStartElement().copy()); + start.setPrecision(end.getPrecision()); start.setTimeZone(end.getTimeZone()); start.setHour(0); start.setMinute(0); start.setSecond(0); start.setMillis(0); - - p.setStartElement(start); } }); diff --git a/core/src/main/java/com/lantanagroup/link/events/CopyLocationAliasToType.java b/core/src/main/java/com/lantanagroup/link/events/CopyLocationAliasToType.java index 254930270..34688f404 100644 --- a/core/src/main/java/com/lantanagroup/link/events/CopyLocationAliasToType.java +++ b/core/src/main/java/com/lantanagroup/link/events/CopyLocationAliasToType.java @@ -1,66 +1,99 @@ package com.lantanagroup.link.events; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import com.lantanagroup.link.ApplyConceptMaps; import com.lantanagroup.link.Constants; -import com.lantanagroup.link.FhirHelper; import com.lantanagroup.link.IReportGenerationDataEvent; import com.lantanagroup.link.db.TenantService; import com.lantanagroup.link.model.ReportContext; import com.lantanagroup.link.model.ReportCriteria; +import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.r4.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; +import java.util.HashMap; import java.util.List; -import java.util.stream.Collectors; - +import java.util.Map; public class CopyLocationAliasToType implements IReportGenerationDataEvent { private static final Logger logger = LoggerFactory.getLogger(CopyLocationAliasToType.class); - @Override - public void execute(TenantService tenantService, Bundle bundle, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) { - //This is a specific transform to move data from a Location's alias to its type - //This must happen BEFORE ApplyConceptMaps as an event - logger.info("Called: " + CopyLocationAliasToType.class.getName()); - for (Bundle.BundleEntryComponent entry : bundle.getEntry()) { - if (entry.getResource().getResourceType().equals(ResourceType.Location)) { - Location locationResource = (Location) entry.getResource(); - //Check for ConceptMap extension indicating that the alias move to type and ConceptMapping has already been performed on this Location - List conceptMapExtensionCheck = - locationResource.getType().stream().filter(t -> - t.getCoding().stream().anyMatch(c -> - c.getExtension().stream().anyMatch(e -> - e.getUrl().equals(Constants.ConceptMappingExtension)))) - .collect(Collectors.toList()); + private static boolean anyAliasExistsInType(Location location) { + return location.getType().stream() + .flatMap(type -> type.getCoding().stream()) + .anyMatch(coding -> StringUtils.equals(coding.getSystem(), Constants.LocationAliasCodeSystem)); + } - //If not already performed, continue - if(conceptMapExtensionCheck.size() == 0){ + private static boolean existsInType(Location location, StringType alias) { + return location.getType().stream() + .flatMap(type -> type.getCoding().stream()) + .anyMatch(coding -> coding.is(Constants.LocationAliasCodeSystem, alias.asStringValue()) + || ApplyConceptMaps.isMapped(coding, Constants.LocationAliasCodeSystem, alias.asStringValue())); + } - List aliases = locationResource.getAlias(); - List types = locationResource.getType(); - if (aliases.size() > 0) { - for (StringType alias : aliases) { - String value = alias.toString(); + private static void copyToType(Location location, StringType alias) { + location.addType().addCoding() + .setSystem(Constants.LocationAliasCodeSystem) + .setCode(alias.asStringValue()); + } - List existingType = types.stream() - .filter(t -> t.getCoding().stream().anyMatch(c -> c.getCode().equals(value))) - .collect(Collectors.toList()); + private final boolean iterative; - //Check for a value in alias - if (value != null && existingType.isEmpty()) { - //Add type to list of existing types - types.add(FhirHelper.createCodeableConcept(value, "https://nhsnlink.org/location-alias")); - } - } + public CopyLocationAliasToType(boolean iterative) { + this.iterative = iterative; + } + + public CopyLocationAliasToType() { + this(false); + } + + @Override + public void execute(TenantService tenantService, Bundle bundle, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) { + Map locationsById = new HashMap<>(); + for (Bundle.BundleEntryComponent entry : bundle.getEntry()) { + Resource resource = entry.getResource(); + if (!(resource instanceof Location)) { + continue; + } + Location location = (Location) resource; + locationsById.putIfAbsent(location.getIdPart(), location); + if (anyAliasExistsInType(location)) { + continue; + } + Location ancestor = location; + IGenericClient client = context.getClient(); + while (true) { + for (StringType alias : ancestor.getAlias()) { + if (!existsInType(location, alias)) { + copyToType(location, alias); } } + if (!iterative || client == null) { + break; + } + String ancestorId = ancestor.getPartOf() + .getReferenceElement() + .getIdPart(); + if (ancestorId == null) { + break; + } + try { + ancestor = locationsById.computeIfAbsent( + ancestorId, + key -> client.read() + .resource(Location.class) + .withId(key) + .execute()); + } catch (Exception e) { + logger.error("Failed to retrieve ancestor: {}", ancestorId, e); + break; + } } } } @Override - public void execute(TenantService tenantService, List data, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) throws RuntimeException { - throw new RuntimeException("Not yet implemented"); + public void execute(TenantService tenantService, List data, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) { } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/lantanagroup/link/events/CopyLocationAliasToTypeIteratively.java b/core/src/main/java/com/lantanagroup/link/events/CopyLocationAliasToTypeIteratively.java new file mode 100644 index 000000000..bc894d84d --- /dev/null +++ b/core/src/main/java/com/lantanagroup/link/events/CopyLocationAliasToTypeIteratively.java @@ -0,0 +1,7 @@ +package com.lantanagroup.link.events; + +public class CopyLocationAliasToTypeIteratively extends CopyLocationAliasToType { + public CopyLocationAliasToTypeIteratively() { + super(true); + } +} diff --git a/core/src/main/java/com/lantanagroup/link/events/CopyLocationIdentifierToType.java b/core/src/main/java/com/lantanagroup/link/events/CopyLocationIdentifierToType.java index c93a9eef5..8def5c79d 100644 --- a/core/src/main/java/com/lantanagroup/link/events/CopyLocationIdentifierToType.java +++ b/core/src/main/java/com/lantanagroup/link/events/CopyLocationIdentifierToType.java @@ -1,6 +1,6 @@ package com.lantanagroup.link.events; -import com.lantanagroup.link.FhirHelper; +import com.lantanagroup.link.ApplyConceptMaps; import com.lantanagroup.link.IReportGenerationDataEvent; import com.lantanagroup.link.db.TenantService; import com.lantanagroup.link.model.ReportContext; @@ -9,59 +9,41 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; -import com.lantanagroup.link.Constants; -/** - * Populates the Location.type with the first value from Location.identifier - */ public class CopyLocationIdentifierToType implements IReportGenerationDataEvent { - private static final Logger logger = LoggerFactory.getLogger(CopyLocationIdentifierToType.class); + private static boolean existsInType(Location location, Identifier identifier) { + return location.getType().stream() + .flatMap(type -> type.getCoding().stream()) + .anyMatch(coding -> coding.is(identifier.getSystem(), identifier.getValue()) + || ApplyConceptMaps.isMapped(coding, identifier.getSystem(), identifier.getValue())); + } + + private static void copyToType(Location location, Identifier identifier) { + location.addType().addCoding() + .setSystem(identifier.getSystem()) + .setCode(identifier.getValue()); + } + @Override public void execute(TenantService tenantService, Bundle bundle, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) { - //This is a specific transform to move data from an extension to the type of a Location resource for UMich - //This must happen BEFORE ApplyConceptMaps as an event - logger.info("Called: " + CopyLocationIdentifierToType.class.getName()); for (Bundle.BundleEntryComponent entry : bundle.getEntry()) { - if (entry.getResource().getResourceType().equals(ResourceType.Location)) { - Location locationResource = (Location) entry.getResource(); - //Check for ConceptMap extension indicating that the identifier move to type and ConceptMapping has already been performed on this Location - List conceptMapExtensionCheck = - locationResource.getType().stream().flatMap(t -> t.getCoding().stream()).filter(t -> - t.getExtension().stream().anyMatch(e -> e.getUrl().equals(Constants.ConceptMappingExtension))) - .collect(Collectors.toList()); - //If not already performed, continue - if (conceptMapExtensionCheck.size() == 0) { - List identifiers = locationResource.getIdentifier(); - List types = locationResource.getType(); - if (identifiers.size() > 0) { - for (Identifier identifier : identifiers) { - String idValue = identifier.getValue(); - String idSystem = identifier.getSystem(); - - List existingType = types.stream() - .filter(t -> t.getCoding().stream().anyMatch(c -> c.getCode().equals(idValue) && c.getSystem().equals(idSystem))) - .collect(Collectors.toList()); - - //Check for a full identifier (both system and value) and no existing type element with the values from identifier - if (existingType.isEmpty() && idValue != null && idSystem != null) { - - //Add type to list of existing types - types.add(FhirHelper.createCodeableConcept(idValue, idSystem)); - } - } - } + Resource resource = entry.getResource(); + if (!(resource instanceof Location)) { + continue; + } + Location location = (Location) resource; + for (Identifier identifier : location.getIdentifier()) { + if (identifier.hasSystem() && !existsInType(location, identifier)) { + copyToType(location, identifier); } } } } @Override - public void execute(TenantService tenantService, List data, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) throws RuntimeException { - throw new RuntimeException("Not yet implemented"); + public void execute(TenantService tenantService, List data, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) { } } diff --git a/core/src/main/java/com/lantanagroup/link/events/FixPeriodDates.java b/core/src/main/java/com/lantanagroup/link/events/FixPeriodDates.java index 3494829c9..22ee615b5 100644 --- a/core/src/main/java/com/lantanagroup/link/events/FixPeriodDates.java +++ b/core/src/main/java/com/lantanagroup/link/events/FixPeriodDates.java @@ -27,7 +27,6 @@ public void execute(TenantService tenantService, Bundle data, ReportCriteria cri } public void execute(TenantService tenantService, List resourceList, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) { - throw new RuntimeException("Not Implemented yet."); } } diff --git a/core/src/main/java/com/lantanagroup/link/events/FixResourceId.java b/core/src/main/java/com/lantanagroup/link/events/FixResourceId.java index f91e452f7..033478d7c 100644 --- a/core/src/main/java/com/lantanagroup/link/events/FixResourceId.java +++ b/core/src/main/java/com/lantanagroup/link/events/FixResourceId.java @@ -25,6 +25,5 @@ public void execute(TenantService tenantService, Bundle data, ReportCriteria cri } public void execute(TenantService tenantService, List resourceList, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) { - throw new RuntimeException("Not Implemented yet."); } } \ No newline at end of file diff --git a/core/src/main/java/com/lantanagroup/link/events/PatientDataResourceFilter.java b/core/src/main/java/com/lantanagroup/link/events/PatientDataResourceFilter.java index d4c3a1239..3705ac72e 100644 --- a/core/src/main/java/com/lantanagroup/link/events/PatientDataResourceFilter.java +++ b/core/src/main/java/com/lantanagroup/link/events/PatientDataResourceFilter.java @@ -159,6 +159,5 @@ public void execute(TenantService tenantService, Bundle bundle, ReportCriteria c @SuppressWarnings("unused") @Override public void execute(TenantService tenantService, List data, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) { - throw new RuntimeException("Not yet implemented"); } } diff --git a/core/src/main/java/com/lantanagroup/link/model/ReportContext.java b/core/src/main/java/com/lantanagroup/link/model/ReportContext.java index e51a886be..727d1e505 100644 --- a/core/src/main/java/com/lantanagroup/link/model/ReportContext.java +++ b/core/src/main/java/com/lantanagroup/link/model/ReportContext.java @@ -1,5 +1,6 @@ package com.lantanagroup.link.model; +import ca.uhn.fhir.rest.client.api.IGenericClient; import com.lantanagroup.link.FhirHelper; import com.lantanagroup.link.auth.LinkCredentials; import com.lantanagroup.link.db.model.PatientList; @@ -25,6 +26,7 @@ public class ReportContext { private List patientsOfInterest = new ArrayList<>(); private List measureContexts = new ArrayList<>(); private QueryPlan queryPlan; + private IGenericClient client; private List debugPatients = new ArrayList<>(); public ReportContext() { diff --git a/core/src/test/java/com/lantanagroup/link/PeriodDateFixerTests.java b/core/src/test/java/com/lantanagroup/link/PeriodDateFixerTests.java index 8b32a6082..3612e2659 100644 --- a/core/src/test/java/com/lantanagroup/link/PeriodDateFixerTests.java +++ b/core/src/test/java/com/lantanagroup/link/PeriodDateFixerTests.java @@ -1,10 +1,7 @@ package com.lantanagroup.link; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.DateTimeType; -import org.hl7.fhir.r4.model.Encounter; -import org.hl7.fhir.r4.model.Period; +import org.hl7.fhir.r4.model.*; import org.junit.Assert; import org.junit.Test; @@ -61,6 +58,11 @@ public void fixDatesTest() Assert.assertEquals(start, period.getStart()); Assert.assertNotEquals(startString, period.getStartElement().asStringValue()); + Assert.assertTrue(period.getStartElement().hasExtension()); + Extension extension = period.getStartElement().getExtensionFirstRep(); + Assert.assertEquals(Constants.OriginalElementValueExtension, extension.getUrl()); + Assert.assertEquals("2023-05-25", extension.getValue().primitiveValue()); + Assert.assertEquals(end, period.getEnd()); Assert.assertEquals(endString, period.getEndElement().asStringValue()); } diff --git a/query/src/main/java/com/lantanagroup/link/query/uscore/Query.java b/query/src/main/java/com/lantanagroup/link/query/uscore/Query.java index 196441d53..702025729 100644 --- a/query/src/main/java/com/lantanagroup/link/query/uscore/Query.java +++ b/query/src/main/java/com/lantanagroup/link/query/uscore/Query.java @@ -60,12 +60,16 @@ public void execute(TenantService tenantService, ReportCriteria criteria, Report List patientsOfInterest = context.getPatientsOfInterest(queryPhase); if (patientsOfInterest.size() > 0) { try { + IGenericClient client = this.getFhirQueryClient(tenantService, context.getMasterIdentifierValue()); + context.setClient(client); PatientScoop scoop = this.applicationContext.getBean(PatientScoop.class); - scoop.setFhirQueryServer(this.getFhirQueryClient(tenantService, context.getMasterIdentifierValue())); + scoop.setFhirQueryServer(client); scoop.setTenantService(tenantService); scoop.execute(criteria, context, patientsOfInterest, queryPhase); } catch (Exception ex) { logger.error("Error scooping data for patients", ex); + } finally { + context.setClient(null); } } }