Skip to content

Commit

Permalink
Aphl 300 valueset interceptor (#757)
Browse files Browse the repository at this point in the history
* ValueSet update interceptor for updating code synonyms
---------

Co-authored-by: taha.attari@smilecdr.com <taha.attari@smilecdr.com>
Co-authored-by: Adam Stevenson <stevenson_adam@yahoo.com>
  • Loading branch information
3 people authored Mar 22, 2024
1 parent f6af4a4 commit 72d426b
Show file tree
Hide file tree
Showing 7 changed files with 3,162 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ public RepositoryService repositoryService() {
return new RepositoryService();
}

@Bean
@Conditional(OnR4Condition.class)
public ValueSetSynonymUpdateInterceptor valueSetInterceptor() {
return new ValueSetSynonymUpdateInterceptor(this.crRulerProperties().getRckmsSynonymsUrl());
}

@Bean
@Conditional(OnR4Condition.class)
public KnowledgeArtifactProcessor knowledgeArtifactProcessor() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

private final String rckmsSynonymsUrl = "http://ersd.aimsplatform.org/fhir/ValueSet/rckms-condition-codes";

public String getRckmsSynonymsUrl() {
return this.rckmsSynonymsUrl;
}

private String vsacUsername;

public String getVsacUsername() { return vsacUsername; }
Expand All @@ -32,4 +38,5 @@ public void setEnabled(boolean enabled) {

public void setVsacApiKey(String vsacApiKey) { this.vsacApiKey = vsacApiKey; }


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package org.opencds.cqf.ruler.cr;

import java.util.ArrayList;
import java.util.List;

import org.cqframework.fhir.api.FhirDal;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Library;
import org.hl7.fhir.r4.model.RelatedArtifact;
import org.hl7.fhir.r4.model.ResourceType;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.ValueSet.ConceptReferenceComponent;
import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent;
import org.opencds.cqf.ruler.behavior.DaoRegistryUser;
import org.opencds.cqf.ruler.utility.TypedBundleProvider;
import org.springframework.beans.factory.annotation.Autowired;

import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;

public class ValueSetSynonymUpdateInterceptor implements org.opencds.cqf.ruler.api.Interceptor, DaoRegistryUser {
@Autowired
private DaoRegistry myDaoRegistry;

private String synonymUrl;

ValueSetSynonymUpdateInterceptor(String synonymUrl) {
this.synonymUrl = synonymUrl;
}

public DaoRegistry getDaoRegistry() {
return myDaoRegistry;
}

/** Handle updates */
@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED)
public void update(
IBaseResource theOldResource,
IBaseResource theResource,
RequestDetails theRequestDetails) throws UnprocessableEntityException {
if (theResource.fhirType().equals(ResourceType.ValueSet.toString())) {
ValueSet vs = (ValueSet) theResource;
if (vs.getUrl().equals(this.synonymUrl)) {
FhirDal fhirDal = new JpaFhirDal(myDaoRegistry,theRequestDetails);
List<ConceptSetComponent> sets = vs.getCompose().getInclude();
List<Library> librariesToUpdate = new ArrayList<Library>();
for (ConceptSetComponent set: sets) {
// for each system in the value set
String system = set.getSystem();
List<ConceptReferenceComponent> concepts = set.getConcept();
for (ConceptReferenceComponent concept:concepts) {
// for each concept.code need to update the extension.valueCodeableConcept.coding.display to concept.designation(where code = synonym).value
TokenParam token = new TokenParam(system,concept.getCode());
SearchParameterMap map = new SearchParameterMap("manifest-contains-code",token);
TypedBundleProvider<Library> result = search(Library.class, map);
result.getAllResourcesTyped().stream().forEach(lib -> {
Library getCachedLibraryOrAdd = librariesToUpdate
.stream().filter(existing -> existing.getId().equals(lib.getId())).findFirst()
.orElseGet(() -> {
librariesToUpdate.add(lib);
return lib;
});
String newText = concept.getDesignation()
.stream().filter(designation -> designation.getUse().getCode().equals("synonym"))
.findFirst().orElseThrow(()-> new UnprocessableEntityException("No synonym found for code: " + system + "|" + concept.getCode()))
.getValue();
updateConditionWithNewSynonym(getCachedLibraryOrAdd,system, concept.getCode(),newText );
});
}
}
for (Library library:librariesToUpdate) {
fhirDal.update(library);
}
}
}
}
private void updateConditionWithNewSynonym(Library library, String system, String code, String newText) {
List<RelatedArtifact> relatedArtifacts = library.getRelatedArtifact();
relatedArtifacts.stream()
.map(ra -> ra.getExtensionByUrl(KnowledgeArtifactProcessor.valueSetConditionUrl))
.filter(ext -> ext != null)
.map(ext -> (CodeableConcept)ext.getValue())
.filter(concept -> concept.getCoding().stream().anyMatch(coding -> coding.getSystem().equals(system) && coding.getCode().equals(code)))
.forEach(concept -> {
concept.setText(newText);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.Library;
import org.hl7.fhir.r4.model.Measure;
Expand Down Expand Up @@ -1446,6 +1447,48 @@ void validatePackageOutput() {
}

@Test
void update_rckms_valueset_updates_Libraries() {
// setup
ValueSet conditions = (ValueSet) loadResource("rckms-condition-codes.json");
IdType id = new IdType(conditions.getId());
// just remove the history part or it causes problems later
conditions.setId(id.getResourceType()+"/"+id.getIdPart());
loadResource("manifest-code-search-parameter.json");
loadTransaction("libraries-with-conditions-bundle.json");

// if we update 2 synonyms
updateSynonym(conditions, "767146004", "testUpdate1");
updateSynonym(conditions, "49649001", "testUpdate2");
getClient().update().resource(conditions).execute();
String valueSetUrl = "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1146.1506|1.0.0";
Library lib1 = (Library)getClient().read().resource("Library").withId("lib1").execute();
Library lib2 = (Library)getClient().read().resource("Library").withId("lib2").execute();
List<Extension> condition1 = getConditionExtensionForValueSet(valueSetUrl,lib1);
assertTrue(condition1.size() > 0);
// see a change in both libraries
assertTrue(((CodeableConcept)condition1.get(0).getValue()).getText().equals("testUpdate1"));
List<Extension> condition2 = getConditionExtensionForValueSet(valueSetUrl,lib2);
assertTrue(condition2.size() > 0);
// see a change in both libraries
assertTrue(((CodeableConcept)condition2.get(0).getValue()).getText().equals("testUpdate2"));

}
private void updateSynonym(ValueSet vs, String code, String newText) {
vs.getCompose()
.getInclude().get(0)
.getConcept()
.stream().filter(c -> c.getCode().equals(code)).findFirst().get()
.getDesignation().get(0)
.setValue(newText);
}
private List<Extension> getConditionExtensionForValueSet(String valueSetUrl, Library library) {
return library.getRelatedArtifact().stream()
.filter(ra -> ra.hasResource() && ra.getResource().equals(valueSetUrl))
.map(ra -> ra.getExtensionByUrl(KnowledgeArtifactProcessor.valueSetConditionUrl))
.filter(ext -> ext != null)
.collect(Collectors.toList());
}

void basic_artifact_diff() {
loadTransaction("ersd-small-active-bundle.json");
Bundle bundle = (Bundle) loadTransaction("small-drafted-ersd-bundle.json");
Expand Down
69 changes: 69 additions & 0 deletions plugin/cr/src/test/resources/libraries-with-conditions-bundle.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"resourceType": "Bundle",
"id": "bundle-ersd-specification-example",
"type": "transaction",
"entry": [
{
"fullUrl": "http://hl7.org/fhir/us/ecr/Library/SpecificationLibrary",
"resource": {
"resourceType": "Library",
"id": "lib1",
"relatedArtifact": [
{
"extension": [
{
"url": "http://aphl.org/fhir/vsm/StructureDefinition/vsm-valueset-condition",
"valueCodeableConcept": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "767146004"
}
],
"text": "Infection caused by Acanthamoeba (disorder)"
}
}
],
"type": "depends-on",
"resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1146.1506|1.0.0"
}
]
},
"request": {
"method": "PUT",
"url": "Library/lib1"
}
},
{
"fullUrl": "http://hl7.org/fhir/us/ecr/Library/SpecificationLibrary",
"resource": {
"resourceType": "Library",
"id": "lib2",
"relatedArtifact": [
{
"extension": [
{
"url": "http://aphl.org/fhir/vsm/StructureDefinition/vsm-valueset-condition",
"valueCodeableConcept": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "49649001"
}
],
"text": "Infection caused by Acanthamoeba (disorder)"
}
}
],
"type": "depends-on",
"resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1146.1506|1.0.0"
}
]
},
"request": {
"method": "PUT",
"url": "Library/lib2"
}
}
]
}
10 changes: 10 additions & 0 deletions plugin/cr/src/test/resources/manifest-code-search-parameter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"resourceType": "SearchParameter",
"id": "manifest-contains-code",
"status": "active",
"code": "manifest-contains-code",
"base": ["Library"],
"type": "token",
"description": "Return all Libraries which reference a particular code",
"expression": "Library.relatedArtifact.extension.where(url.contains('vsm-valueset-condition')).value as CodeableConcept"
}
Loading

0 comments on commit 72d426b

Please sign in to comment.