Skip to content

Commit

Permalink
Merge pull request #2740 from FirelyTeam/feature/support-other-filters
Browse files Browse the repository at this point in the history
Implement DescendantOf and isNotA filter
  • Loading branch information
mmsmits authored Apr 2, 2024
2 parents ebb205e + a1bcf5a commit d33ed77
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 57 deletions.
28 changes: 28 additions & 0 deletions src/Hl7.Fhir.Shims.Base/Model/ValueSetExpansionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,34 @@ public static class ValueSetExpansionExtensions
return null;
}

internal static List<CSDC> RemoveCode(this ICollection<CSDC> concepts, string code)
{
return concepts.removeCodeByPredicate(c => c.Code == code).ToList();
}

private static ICollection<CSDC> removeCodeByPredicate(this ICollection<CSDC> concepts, Predicate<CSDC> predicate)
{
foreach (var concept in concepts)
{
var result = concept.removeCodeByPredicate(concepts, predicate);
if (result != null) return result;
}
return concepts;
}
private static ICollection<CSDC>? removeCodeByPredicate(this CSDC concept, ICollection<CSDC> concepts, Predicate<CSDC> predicate)
{
// Direct hit
if (predicate(concept))
concepts.Remove(concept);
else if (concept.Concept.Any())
{
removeCodeByPredicate(concept.Concept, predicate);
}
return concepts;
}



/// <summary>
/// Loops through all concepts and descendants and returns a flat list of concepts, without a nested hierarchy
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using Hl7.Fhir.Model;
using Hl7.Fhir.Specification.Source;
using Hl7.Fhir.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using CSDC = Hl7.Fhir.Model.CodeSystem.ConceptDefinitionComponent;
using T = System.Threading.Tasks;

#nullable enable
Expand All @@ -24,7 +26,7 @@ internal static class CodeSystemFilterProcessor
/// <exception cref="ValueSetExpansionTooComplexException">Thrown when a filter is applied that is not supported (yet)</exception>
/// <exception cref="CodeSystemUnknownException">Thrown when no resource resolver was set in ValueSetExpanderSettings.ValueSetSource</exception>
/// <exception cref="CodeSystemUnknownException">Thrown when the requested CodeSystem can not be found by the resource resolver in ValueSetExpanderSettings</exception>
internal async static T.Task<IEnumerable<ValueSet.ContainsComponent>> FilterConceptsFromCodeSystem(string codeSystemUri, List<ValueSet.FilterComponent> filters, ValueSetExpanderSettings settings)
internal async static T.Task<IEnumerable<ValueSet.ContainsComponent>> FilterConceptsFromCodeSystem(string codeSystemUri, IEnumerable<ValueSet.FilterComponent> filters, ValueSetExpanderSettings settings)
{
if (settings.ValueSetSource == null)
throw Error.InvalidOperation($"No valueset resolver available to resolve codesystem '{codeSystemUri}', so the expansion cannot be completed.");
Expand All @@ -33,15 +35,15 @@ internal static class CodeSystemFilterProcessor
?? throw new CodeSystemUnknownException($"Cannot find codesystem '{codeSystemUri}', so the defined filter(s) cannot be applied.");

if (codeSystem.Content.GetLiteral() != "complete")
throw new CodeSystemIncompleteException($"CodeSystem {codeSystemUri} is marked incomplete, so the defines filter(s) cannot be applied.");
throw new CodeSystemIncompleteException($"CodeSystem `{codeSystemUri}` is marked incomplete, so the defined filter(s) cannot be applied.");


var result = applyFilters(filters, codeSystem);

return result.Select(c => c.ToContainsComponent(codeSystem, settings));
}

private static List<CodeSystem.ConceptDefinitionComponent> applyFilters(List<ValueSet.FilterComponent> filters, CodeSystem codeSystem)
private static List<CSDC> applyFilters(IEnumerable<ValueSet.FilterComponent> filters, CodeSystem codeSystem)
{
var result = codeSystem.Concept;
var properties = codeSystem.Property;
Expand All @@ -54,44 +56,91 @@ internal static class CodeSystemFilterProcessor
return result;
}

private static IEnumerable<CodeSystem.ConceptDefinitionComponent> applyFilter(List<CodeSystem.ConceptDefinitionComponent> concepts, List<CodeSystem.PropertyComponent> properties, ValueSet.FilterComponent filter)
private static IEnumerable<CSDC> applyFilter(IEnumerable<CSDC> concepts, IEnumerable<CodeSystem.PropertyComponent> properties, ValueSet.FilterComponent filter)
{
return filter.Op switch
{
FilterOperator.IsA => applyIsAFilter(concepts, properties, filter),
_ => throw new ValueSetExpansionTooComplexException($"ConceptSets with a filter {filter.Op} are not yet supported.")
FilterOperator.IsA => applyFilterBasedOnHierarchy(concepts, properties, filter, applyIsAfFilterUsingSubsumedBy, applyIsAFilterToANestedHierarchy),
FilterOperator.IsNotA => applyFilterBasedOnHierarchy(concepts, properties, filter, applyIsNotAFilterUsingSubsumedBy, applyIsNotAFilterToANestedHierarchy),
FilterOperator.DescendentOf => applyFilterBasedOnHierarchy(concepts, properties, filter, applyDescendantsOfFilterUsingSubsumedBy, applyDescentantsOfFilterToANestedHierarchy),
_ => throw new ValueSetExpansionTooComplexException($"ConceptSets with filter `{filter.Op.GetLiteral()}` are not yet supported.")
};
}

private static IEnumerable<CodeSystem.ConceptDefinitionComponent> applyIsAFilter(List<CodeSystem.ConceptDefinitionComponent> concepts, List<CodeSystem.PropertyComponent> properties, ValueSet.FilterComponent filter)
private static IEnumerable<CSDC> applyFilterBasedOnHierarchy(IEnumerable<CSDC> concepts, IEnumerable<CodeSystem.PropertyComponent> properties, ValueSet.FilterComponent filter, Func<IEnumerable<CSDC>, ValueSet.FilterComponent, IEnumerable<CSDC>> applySubsumedByFilter, Func<IEnumerable<CSDC>, ValueSet.FilterComponent, IEnumerable<CSDC>> applyFilterToANestedHierarchy)
{
var result = new List<CodeSystem.ConceptDefinitionComponent>();

//find descendants based on subsumedBy
if (properties.Any(p => p.Code == SUBSUMEDBYCODE))
{
//first find the parent itself (if it's in the CodeSystem)
if (concepts.FindCode(filter.Value) is { } concept)
result.Add(concept);

//Create a lookup which lists children by parent.
//first flatten the codes
var flattened = concepts.Flatten();
var childrenLookup = CreateSubsumedByLookup(flattened);

//find descendants based on that lookup
var descendants = applySubsumedBy(childrenLookup, filter);
result.AddRange(descendants);
return applySubsumedByFilter(concepts, filter);
}
else
{
//SubsumedBy is not used, we should only check for a nested hierarchy, and include the code and it's descendants
if (concepts.FindCode(filter.Value) is { } concept)
result.Add(concept);
return applyFilterToANestedHierarchy(concepts, filter);
}
}


private static IEnumerable<CSDC> applyDescentantsOfFilterToANestedHierarchy(IEnumerable<CSDC> concepts, ValueSet.FilterComponent filter)
{
// look for the code and return it's children (which have their children included already)
return (concepts.FindCode(filter.Value) is { } concept) ? concept.Concept : [];
}

private static IEnumerable<CSDC> applyIsAfFilterUsingSubsumedBy(IEnumerable<CSDC> flattened, ValueSet.FilterComponent filter)
{
var result = new List<CSDC>();

//first find the parent itself (if it's in the CodeSystem)
if (flattened.FindCode(filter.Value) is { } concept)
result.Add(concept);

//then find the descendants
List<CSDC> descendants = applyDescendantsOfFilterUsingSubsumedBy(flattened, filter);
result.AddRange(descendants);

return result;
}

private static ILookup<string, CodeSystem.ConceptDefinitionComponent> CreateSubsumedByLookup(List<CodeSystem.ConceptDefinitionComponent> flattenedConcepts)
private static IEnumerable<CSDC> applyIsAFilterToANestedHierarchy(IEnumerable<CSDC> concepts, ValueSet.FilterComponent filter)
{
//just look for the code, because descendants are included
return concepts.FindCode(filter.Value) is { } concept ? [concept] : [];
}

private static IEnumerable<CSDC> applyIsNotAFilterUsingSubsumedBy(IEnumerable<CSDC> flattened, ValueSet.FilterComponent filter)
{
var result = flattened.ToList();
//first find the parent itself (if it's in the CodeSystem) and remove it
if (flattened.FindCode(filter.Value) is { } concept)
result.Remove(concept);
//then find the descendants, and remove them
List<CSDC> descendants = applyDescendantsOfFilterUsingSubsumedBy(flattened, filter);

result.RemoveAll(descendants.Contains);
return result;
}

private static IEnumerable<CSDC> applyIsNotAFilterToANestedHierarchy(IEnumerable<CSDC> concepts, ValueSet.FilterComponent filter)
{
//We should only check for a nested hierarchy, and exclude the code and thereby it's included descendants
return concepts.ToList().RemoveCode(filter.Value);
}

private static List<CSDC> applyDescendantsOfFilterUsingSubsumedBy(IEnumerable<CSDC> flattened, ValueSet.FilterComponent filter)
{
//Create a lookup which lists children by parent.
var childrenLookup = CreateSubsumedByLookup(flattened);

//find descendants based on that lookup
var descendants = applySubsumedBy(childrenLookup, filter);
return descendants;
}

private static ILookup<string, CSDC> CreateSubsumedByLookup(IEnumerable<CSDC> flattenedConcepts)
{
return flattenedConcepts
.SelectMany(concept => concept.Property
Expand All @@ -100,9 +149,9 @@ internal static class CodeSystemFilterProcessor
.ToLookup(x => x.SubsumedByValue, x => x.Concept);
}

private static List<CodeSystem.ConceptDefinitionComponent> applySubsumedBy(ILookup<string, CodeSystem.ConceptDefinitionComponent> lookup, ValueSet.FilterComponent filter)
private static List<CSDC> applySubsumedBy(ILookup<string, CSDC> lookup, ValueSet.FilterComponent filter)
{
var result = new List<CodeSystem.ConceptDefinitionComponent>();
var result = new List<CSDC>();
var root = filter.Value;
if (root != null)
{
Expand All @@ -112,7 +161,7 @@ internal static class CodeSystemFilterProcessor
}

//recursively loop through all the children to eventually find all descendants.
private static void addDescendants(ILookup<string, CodeSystem.ConceptDefinitionComponent> lookup, string parent, List<CodeSystem.ConceptDefinitionComponent> result)
private static void addDescendants(ILookup<string, CSDC> lookup, string parent, List<CSDC> result)
{
if (lookup[parent] is { } children)
{
Expand Down
Loading

0 comments on commit d33ed77

Please sign in to comment.