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

Aphl 300 valueset interceptor #757

Merged
merged 25 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f55c66c
[APHL-300] valueset interceptor initial implementation
Nov 16, 2023
d8e91ad
[APHL-300] preserve useContext extensions when releasing
Nov 17, 2023
7194f80
[APHL-300] package is aware of useContext
Nov 18, 2023
14047eb
[APHL-300] updated to use vsm-specific extension
Nov 20, 2023
8e99858
[APHL-300] fix test
Nov 20, 2023
f6cc025
[APHL-300][APHL-798][APHL-799] updates to package and release
Nov 21, 2023
aaaba88
[APHL-799] update package validation test for depends-on relatedArtifact
Nov 22, 2023
a973e50
remove interceptor
Nov 22, 2023
ff95d5b
[APHL-799] update package test data
Nov 22, 2023
e995f44
[APHL-798] preserve all RelatedArtifact extensions to keep it generic
Nov 22, 2023
2c215ee
[APHL-799] replace existing useContexts for condition and priority
Nov 22, 2023
b70efe8
comments
Nov 22, 2023
09551de
cleanup
Nov 22, 2023
dbfe18a
fix bad assignment
Nov 22, 2023
8c7c826
simplify
Nov 22, 2023
edf67b9
cleanup
Nov 22, 2023
4bdabc9
[APHL-300] forgot to update reference
Nov 24, 2023
d39fd32
Merge branch 'vsm_operations' into aphl-300-valueset-interceptor
Dec 20, 2023
b946411
[APHL-300] add interceptor
Dec 20, 2023
957e7c5
[APHL-300] update library with new text elements
Dec 21, 2023
176feb2
[APHL-300] update interceptor
Dec 22, 2023
cacb5a8
Merge branch 'vsm_operations' into aphl-300-valueset-interceptor
sliver007 Jan 5, 2024
e9798ce
Fixed missing semicolon from merge
sliver007 Jan 5, 2024
a03a5bf
[APHL-300] update to make more generic
Mar 22, 2024
8990d2a
Merge branch 'vsm_operations' into aphl-300-valueset-interceptor
TahaAttari Mar 22, 2024
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 @@ -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
Loading