Skip to content

tx.fhir.org SNOMED ECL bugs: memberOf sentinel leakage and cardinality counting #230

@jmandel

Description

@jmandel

Summary

While testing SNOMED ECL expansion against https://tx.fhir.org/r4 (FHIRsmith 0.9.6 from /metadata), I found several ECL behaviors that appear incorrect or incomplete.

Checklist

  • Bare memberOf expressions such as ^700043003 return fabricated inactive code 4294967295.
  • Bare memberOf over a non-reference-set concept such as ^404684003 also returns 4294967295 instead of rejecting or returning empty.
  • Wrapped memberOf operands such as ^(<<900000000000455006) are rejected as unsupported.
  • Attribute cardinality appears to count raw relationship rows rather than non-redundant matching attributes.
  • Add regression tests for the above cases.

References:

Bug 1: memberOf returns fabricated sentinel code 4294967295

Bare memberOf expressions return a one-code expansion containing inactive code 4294967295. This happens both for apparent reference-set concepts and for concepts that are plainly not reference sets.

Reproducer:

for ECL in '^700043003' '^900000000000497000' '^404684003'; do
  printf '%s\t' "$ECL"
  jq -nc --arg ecl "$ECL" '{
    resourceType:"ValueSet",
    compose:{include:[{
      system:"http://snomed.info/sct",
      filter:[{property:"constraint",op:"=",value:$ecl}]
    }]}
  }' |
  curl -sS -X POST 'https://tx.fhir.org/r4/ValueSet/$expand' \
    -H 'Content-Type: application/fhir+json' \
    --data-binary @- |
  jq -r '[.expansion.total, (.expansion.contains[0].code // "no-code")] | @tsv'
done

Observed:

^700043003           1   4294967295
^900000000000497000 1   4294967295
^404684003           1   4294967295

Expected:

  • For a valid concept-based reference set, ^refset should return the active referenced component concepts from that reference set.
  • For 404684003 |Clinical finding|, which is not a reference set, the server should reject the expression or return no members.
  • The expansion should never contain fabricated SCTID 4294967295.

Impact: clients using ECL memberOf expansion can receive a non-SNOMED sentinel as if it were a real inactive SNOMED concept.

Bug 2: wrapped memberOf operands are rejected as unsupported

Reproducer:

for ECL in '^(<<900000000000455006)' '^(404684003)' '^(<<404684003)'; do
  printf '\n%s\n' "$ECL"
  jq -nc --arg ecl "$ECL" '{
    resourceType:"ValueSet",
    compose:{include:[{
      system:"http://snomed.info/sct",
      filter:[{property:"constraint",op:"=",value:$ecl}]
    }]}
  }' |
  curl -sS -X POST 'https://tx.fhir.org/r4/ValueSet/$expand' \
    -H 'Content-Type: application/fhir+json' \
    --data-binary @- |
  jq -r '.issue[0].details.text'
done

Observed:

^(<<900000000000455006)
The ECL expression is not supported: '^(<<900000000000455006)': (ECL ^ (member-of) with a non-concept-reference refset is not yet supported)

^(404684003)
The ECL expression is not supported: '^(404684003)': (ECL ^ (member-of) with a non-concept-reference refset is not yet supported)

^(<<404684003)
The ECL expression is not supported: '^(<<404684003)': (ECL ^ (member-of) with a non-concept-reference refset is not yet supported)

Expected: ECL permits the memberOf operand to be a refset expression. For example, ^(<<900000000000455006) means members of all reference sets in the descendants-or-self closure of 900000000000455006 |Reference set|. The server should resolve the operand expression to reference-set concepts and return the union of their referenced component concepts.

Impact: clients cannot use standard ECL patterns that compute the set of reference sets dynamically.

Bug 3: cardinality appears to count raw relationship rows instead of non-redundant attributes

The witness concept is 903008 |Chorioretinal infarction|.

First, tx.fhir.org lookup reports one 116676008 |Associated morphology| value for the concept:

curl -sS 'https://tx.fhir.org/r4/CodeSystem/$lookup?system=http://snomed.info/sct&code=903008' |
jq -r '[.parameter[]
  | select(.name=="property")
  | select((.part[]? | select(.name=="code").valueCode) == "116676008")
  | (.part[] | select(.name=="value").valueCode)] | @json'

Observed:

["55641003"]

Now compare [1..1] and [2..*] cardinality for that same attribute/value constraint:

for ECL in \
  '<<404684003:[1..1]116676008=<<55641003' \
  '<<404684003:[2..*]116676008=<<55641003'
do
  printf '%s\t' "$ECL"
  jq -nc --arg ecl "$ECL" '{
    resourceType:"ValueSet",
    compose:{include:[{
      system:"http://snomed.info/sct",
      filter:[{property:"constraint",op:"=",value:$ecl}]
    }]}
  }' |
  curl -sS -X POST 'https://tx.fhir.org/r4/ValueSet/$expand' \
    -H 'Content-Type: application/fhir+json' \
    --data-binary @- |
  jq -r '[.expansion.total, ((.expansion.contains // []) | map(.code) | index("903008") != null)] | @tsv'
done

Observed:

<<404684003:[1..1]116676008=<<55641003  257  false
<<404684003:[2..*]116676008=<<55641003  11   true

Expected: if 903008 has one non-redundant associated morphology matching <<55641003 |Infarct|, it should satisfy [1..1] and should not satisfy [2..*].

Observed behavior is the opposite: 903008 is excluded from [1..1] and included in [2..*]. This is consistent with counting raw relationship instances instead of the non-redundant attribute values required by ECL.

Impact: cardinality-constrained ECL queries can include false positives and exclude true positives.

Sanity check

Ordinary attribute refinements are not simply ignored:

ECL='<<404684003:116676008=<<55641003'
jq -nc --arg ecl "$ECL" '{resourceType:"ValueSet",compose:{include:[{system:"http://snomed.info/sct",filter:[{property:"constraint",op:"=",value:$ecl}]}]}}' |
curl -sS -X POST 'https://tx.fhir.org/r4/ValueSet/$expand' -H 'Content-Type: application/fhir+json' --data-binary @- |
jq '{total: .expansion.total, first: [.expansion.contains[:5][] | {code, display}]}'

Observed total: 268, with infarction-related first results. The cardinality issue appears more specific than all refinements being ignored.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions