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

IMPS-160: Alpha Billings: Location.type Concept Map Investigation Needed #704

Merged
merged 6 commits into from
Mar 6, 2024
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 @@ -19,6 +19,14 @@ public class ApplyConceptMaps {

private List<com.lantanagroup.link.db.model.ConceptMap> 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();
}
Expand Down
1 change: 1 addition & 0 deletions core/src/main/java/com/lantanagroup/link/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
6 changes: 4 additions & 2 deletions core/src/main/java/com/lantanagroup/link/PeriodDateFixer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

});
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CodeableConcept> 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<StringType> aliases = locationResource.getAlias();
List<CodeableConcept> 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<CodeableConcept> 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<String, Location> 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<DomainResource> data, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) throws RuntimeException {
throw new RuntimeException("Not yet implemented");
public void execute(TenantService tenantService, List<DomainResource> data, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.lantanagroup.link.events;

public class CopyLocationAliasToTypeIteratively extends CopyLocationAliasToType {
public CopyLocationAliasToTypeIteratively() {
super(true);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Coding> 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<Identifier> identifiers = locationResource.getIdentifier();
List<CodeableConcept> types = locationResource.getType();
if (identifiers.size() > 0) {
for (Identifier identifier : identifiers) {
String idValue = identifier.getValue();
String idSystem = identifier.getSystem();

List<CodeableConcept> 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<DomainResource> data, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) throws RuntimeException {
throw new RuntimeException("Not yet implemented");
public void execute(TenantService tenantService, List<DomainResource> data, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ public void execute(TenantService tenantService, Bundle data, ReportCriteria cri
}

public void execute(TenantService tenantService, List<DomainResource> resourceList, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) {
throw new RuntimeException("Not Implemented yet.");
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,5 @@ public void execute(TenantService tenantService, Bundle data, ReportCriteria cri
}

public void execute(TenantService tenantService, List<DomainResource> resourceList, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) {
throw new RuntimeException("Not Implemented yet.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,5 @@ public void execute(TenantService tenantService, Bundle bundle, ReportCriteria c
@SuppressWarnings("unused")
@Override
public void execute(TenantService tenantService, List<DomainResource> data, ReportCriteria criteria, ReportContext context, ReportContext.MeasureContext measureContext) {
throw new RuntimeException("Not yet implemented");
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -25,6 +26,7 @@ public class ReportContext {
private List<PatientOfInterestModel> patientsOfInterest = new ArrayList<>();
private List<MeasureContext> measureContexts = new ArrayList<>();
private QueryPlan queryPlan;
private IGenericClient client;
private List<String> debugPatients = new ArrayList<>();

public ReportContext() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,16 @@ public void execute(TenantService tenantService, ReportCriteria criteria, Report
List<PatientOfInterestModel> 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);
}
}
}
Expand Down