From b27d7aac525c86c130d4712663816fa58ed61a57 Mon Sep 17 00:00:00 2001 From: Alexander Kiel Date: Thu, 9 Apr 2026 15:35:38 +0200 Subject: [PATCH] Implement CQL ValueSet Retrieve Closes: #3279 --- docs/performance/cql.md | 65 +++++- docs/performance/cql/condition-all-vs.cql | 10 + docs/performance/cql/condition-all-vs.yml | 5 + .../cql/condition-ten-frequent-vs.cql | 10 + .../cql/condition-ten-frequent-vs.yml | 5 + .../performance/cql/condition-ten-rare-vs.cql | 10 + .../performance/cql/condition-ten-rare-vs.yml | 5 + docs/performance/fhir-search.md | 2 - docs/terminology-service/fhir.md | 1 + .../blaze/elm/compiler/clinical_operators.clj | 8 +- .../blaze/elm/compiler/clinical_values.clj | 4 +- .../src/blaze/elm/compiler/external_data.clj | 87 ++++--- modules/cql/src/blaze/elm/value_set.clj | 130 ++++++----- .../cql/src/blaze/elm/value_set/protocol.clj | 1 + .../blaze/elm/compiler/external_data_test.clj | 218 ++++++++++++++++++ .../test/blaze/elm/compiler/library_test.clj | 38 ++- .../elm/compiler/list_operators_test.clj | 11 +- .../test/blaze/elm/expression/cache_test.clj | 22 +- modules/cql/test/blaze/elm/literal.clj | 4 +- modules/cql/test/blaze/elm/value_set_spec.clj | 18 +- modules/cql/test/blaze/elm/value_set_test.clj | 25 +- modules/db/src/blaze/db/api.clj | 8 +- modules/db/src/blaze/db/api_spec.clj | 16 +- .../src/blaze/db/impl/search_param/token.clj | 10 +- modules/db/test/blaze/db/api_test.clj | 35 +++ .../evaluate_measure/measure_test.clj | 4 +- .../evaluate_measure/q67-icd10-value-set.cql | 10 + .../evaluate_measure/q67-icd10-value-set.json | 199 ++++++++++++++++ .../code_system/filter/descendent_of.clj | 22 +- .../local/code_system/filter/exists.clj | 6 +- .../local/code_system/filter/is_a.clj | 23 +- .../blaze/terminology_service/local_test.clj | 35 ++- 32 files changed, 884 insertions(+), 163 deletions(-) create mode 100644 docs/performance/cql/condition-all-vs.cql create mode 100644 docs/performance/cql/condition-all-vs.yml create mode 100644 docs/performance/cql/condition-ten-frequent-vs.cql create mode 100644 docs/performance/cql/condition-ten-frequent-vs.yml create mode 100644 docs/performance/cql/condition-ten-rare-vs.cql create mode 100644 docs/performance/cql/condition-ten-rare-vs.yml create mode 100644 modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q67-icd10-value-set.cql create mode 100644 modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q67-icd10-value-set.json diff --git a/docs/performance/cql.md b/docs/performance/cql.md index d8ee6a7a6..278ef1da4 100644 --- a/docs/performance/cql.md +++ b/docs/performance/cql.md @@ -317,6 +317,55 @@ define InInitialPopulation: cql/search.sh condition-ten-rare ``` +## Ten Code Search – ValueSet + +In this section, CQL queries for selecting patients which have conditions with one of 10 codes are analyzed. The same codes as in section Ten Code Search are used. However instead of multiple retrieve expressions, a single retrieve expression with a value set is used. + +### Data + +| Dataset | System | # Hits | Time (s) | StdDev | Pat./s | +|---------|--------|-------:|---------:|-------:|--------:| +| 1M | LEA47 | 4 k | 0.45 | 0.005 | 2.227 M | +| 1M | LEA47 | 954 k | 2.53 | 0.007 | 394.9 k | + +### CQL Query Frequent + +```text +library "condition-ten-frequent-vs" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +valueset vs: 'http://fhir.org/VCL?v1=(http://snomed.info/sct)(72892002;10509002;36955009;195662009;162864005;49727002;444814009;386661006;840539006;840544004)' + +context Patient + +define InInitialPopulation: + exists [Condition: vs] +``` + +```sh +cql/search.sh condition-ten-frequent-vs +``` + +### CQL Query Rare + +```text +library "condition-ten-rare-vs" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +valueset vs: 'http://fhir.org/VCL?v1=(http://snomed.info/sct)(62718007;234466008;288959006;47505003;698754002;157265008;15802004;14760008;36923009;45816000)' + +context Patient + +define InInitialPopulation: + exists [Condition: vs] +``` + +```sh +cql/search.sh condition-ten-rare-vs +``` + ## All Code Search ![](cql/all-code-search-1M.png) @@ -335,7 +384,7 @@ cql/search.sh condition-ten-rare | 100k-fh | LEA58 | 100 k | 0.10 | 0.001 | 1.038 M | | 1M | LEA25 | 995 k | 9.66 | 0.041 | 103.5 k | | 1M | LEA36 | 995 k | 5.96 | 0.015 | 167.7 k | -| 1M | LEA47 | 995 k | 1.02 | 0.015 | 981.3 k | +| 1M | LEA47 | 995 k | 0.89 | 0.011 | 1.120 M | | 1M | LEA58 | 995 k | 0.60 | 0.018 | 1.674 M | | 1M | LEA79 | 995 k | 0.38 | 0.003 | 2.667 M | | 1M | A5N46 | 995 k | 0.44 | 0.002 | 2.260 M | @@ -346,6 +395,20 @@ cql/search.sh condition-ten-rare cql/search.sh condition-all ``` +## All Code Search – ValueSet + +### Data + +| Dataset | System | # Hits | Time (s) | StdDev | Pat./s | +|---------|--------|-------:|---------:|-------:|--------:| +| 1M | LEA47 | 995 k | 37.31 | 0.387 | 26.8 k | + +### CQL Query + +```sh +cql/search.sh condition-all +``` + ## Inpatient Stress Search ![](cql/inpatient-stress-search-1M.png) diff --git a/docs/performance/cql/condition-all-vs.cql b/docs/performance/cql/condition-all-vs.cql new file mode 100644 index 000000000..cfb00971a --- /dev/null +++ b/docs/performance/cql/condition-all-vs.cql @@ -0,0 +1,10 @@ +library "condition-all-vs" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +valueset vs: 'http://fhir.org/VCL?v1=(http://snomed.info/sct)(234466008;65275009;241929008;75498004;10509002;132281000119108;706870000;67782005;65710008;195662009;7200002;300916003;26929004;271737000;198992004;74400008;195967001;225444004;24079001;49436004;287185009;87628006;6072007;35999006;60951000119105;162864005;408512008;275272006;262574004;840539006;92691004;410429000;128188000;373587001;192127007;233678006;43724002;235919008;88805009;124171000119105;431855005;431856006;433144002;278860009;1121000119107;185086009;82423001;698754002;40055000;359817006;110030002;62564004;62106007;302297009;14760008;40275004;53741008;49727002;190905008;38822007;44054006;427089005;127013003;422034002;267060006;157265008;62718007;55680006;55680006;267036007;15802004;84757009;84757009;301011002;53827007;370247008;230265002;84229001;707577004;156073000;386661006;203082005;403190006;16114001;58150001;65966004;33737001;1734006;15724005;263102004;235595009;90560007;25064002;84114007;66857006;429280009;428251008;429007001;161622006;399211009;703151001;230745008;80394007;55822004;59621000;302870006;389087006;83664006;196416002;11218009;406602003;444470001;86175003;40095003;444448004;307731004;110359009;57676002;414564002;284551006;283371005;284549007;283385000;201834006;36955009;200936003;97331000119101;370143000;36923009;427089005;254837009;363406005;171131006;414667000;237602007;314994000;90781000119102;19169002;68962001;22298006;68235000;422587007;126906006;368581000119106;47200007;424132000;254637007;1551000119108;72892002;5602001;239872002;239873007;64859006;65363002;109838007;22253000;246677007;443165006;446096008;232353008;233604007;233604007;68496003;398152000;47505003;15777000;398254007;399912005;95417003;93761005;67811000119102;1501000119109;157141000119108;236077008;87433001;45816000;713197008;197927001;271825005;267064002;69896004;47693006;30832001;298382003;367498001;403191005;94260004;128613002;91302008;448813005;448417001;770349000;76571007;27942005;36971009;254632001;449868002;267102003;221360009;76916001;44465007;70704007;248595008;43878008;230690007;86849004;840544004;162573006;239720000;403192003;427419006;127295002;79586000;288959006;68566005;444814009;249497008;56018004;39848009)' + +context Patient + +define InInitialPopulation: + exists [Condition: vs] diff --git a/docs/performance/cql/condition-all-vs.yml b/docs/performance/cql/condition-all-vs.yml new file mode 100644 index 000000000..366bce5c5 --- /dev/null +++ b/docs/performance/cql/condition-all-vs.yml @@ -0,0 +1,5 @@ +library: cql/condition-all-vs.cql +group: +- type: Patient + population: + - expression: InInitialPopulation diff --git a/docs/performance/cql/condition-ten-frequent-vs.cql b/docs/performance/cql/condition-ten-frequent-vs.cql new file mode 100644 index 000000000..c2dbf2fdd --- /dev/null +++ b/docs/performance/cql/condition-ten-frequent-vs.cql @@ -0,0 +1,10 @@ +library "condition-ten-frequent-vs" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +valueset vs: 'http://fhir.org/VCL?v1=(http://snomed.info/sct)(72892002;10509002;36955009;195662009;162864005;49727002;444814009;386661006;840539006;840544004)' + +context Patient + +define InInitialPopulation: + exists [Condition: vs] diff --git a/docs/performance/cql/condition-ten-frequent-vs.yml b/docs/performance/cql/condition-ten-frequent-vs.yml new file mode 100644 index 000000000..cd731495f --- /dev/null +++ b/docs/performance/cql/condition-ten-frequent-vs.yml @@ -0,0 +1,5 @@ +library: cql/condition-ten-frequent-vs.cql +group: +- type: Patient + population: + - expression: InInitialPopulation diff --git a/docs/performance/cql/condition-ten-rare-vs.cql b/docs/performance/cql/condition-ten-rare-vs.cql new file mode 100644 index 000000000..3e118c9f0 --- /dev/null +++ b/docs/performance/cql/condition-ten-rare-vs.cql @@ -0,0 +1,10 @@ +library "condition-ten-rare-vs" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +valueset vs: 'http://fhir.org/VCL?v1=(http://snomed.info/sct)(62718007;234466008;288959006;47505003;698754002;157265008;15802004;14760008;36923009;45816000)' + +context Patient + +define InInitialPopulation: + exists [Condition: vs] diff --git a/docs/performance/cql/condition-ten-rare-vs.yml b/docs/performance/cql/condition-ten-rare-vs.yml new file mode 100644 index 000000000..1147d45c9 --- /dev/null +++ b/docs/performance/cql/condition-ten-rare-vs.yml @@ -0,0 +1,5 @@ +library: cql/condition-ten-rare-vs.cql +group: +- type: Patient + population: + - expression: InInitialPopulation diff --git a/docs/performance/fhir-search.md b/docs/performance/fhir-search.md index 63a5d1efa..1e60f2713 100644 --- a/docs/performance/fhir-search.md +++ b/docs/performance/fhir-search.md @@ -187,8 +187,6 @@ The script `multiple-codes-search-vs.sh` is used. ### Downloading Resources -![](fhir-search/multiple-code-search-download-1M.png) - | System | Dataset | Codes | # Hits | Time (s) | StdDev | Res/s ¹ | |--------|---------|-------|-------:|---------:|-------:|--------:| | A5N46 | 1M | 10 | 29.1 M | 286.60 | 14.831 | 101.4 k | diff --git a/docs/terminology-service/fhir.md b/docs/terminology-service/fhir.md index fcb075ecb..8aa47f8fd 100644 --- a/docs/terminology-service/fhir.md +++ b/docs/terminology-service/fhir.md @@ -8,6 +8,7 @@ The following filters are supported. | Property | Operators | Values | |----------|---------------------|------------| +| concept | exists | true/false | | concept | is-a, descendent-of | code | | parent | exists | true/false | | parent | = | code | diff --git a/modules/cql/src/blaze/elm/compiler/clinical_operators.clj b/modules/cql/src/blaze/elm/compiler/clinical_operators.clj index 8ae7a6418..94de83745 100644 --- a/modules/cql/src/blaze/elm/compiler/clinical_operators.clj +++ b/modules/cql/src/blaze/elm/compiler/clinical_operators.clj @@ -10,7 +10,7 @@ [blaze.elm.compiler.macros :refer [reify-expr]] [blaze.elm.concept :as concept] [blaze.elm.protocols :as p] - [blaze.elm.value-set :as value-set])) + [blaze.elm.value-set :as vs])) ;; 23.3. CalculateAge ;; @@ -93,9 +93,9 @@ (if-some [code (core/-eval code context resource scope)] (let [value-set (core/-eval value-set context resource scope)] (cond - (string? code) (value-set/contains-string? value-set code) - (code/code? code) (value-set/contains-code? value-set code) - (concept/concept? code) (value-set/contains-concept? value-set code))) + (string? code) (vs/contains-string? value-set code) + (code/code? code) (vs/contains-code? value-set code) + (concept/concept? code) (vs/contains-concept? value-set code))) false)) (-form [_] (list 'in-value-set (core/-form code) (core/-form value-set))))) diff --git a/modules/cql/src/blaze/elm/compiler/clinical_values.clj b/modules/cql/src/blaze/elm/compiler/clinical_values.clj index 231109ccd..e0803cd2b 100644 --- a/modules/cql/src/blaze/elm/compiler/clinical_values.clj +++ b/modules/cql/src/blaze/elm/compiler/clinical_values.clj @@ -12,7 +12,7 @@ [blaze.elm.date-time :as date-time] [blaze.elm.quantity :refer [quantity]] [blaze.elm.ratio :refer [ratio]] - [blaze.elm.value-set :as value-set])) + [blaze.elm.value-set :as vs])) ;; 3.1. Code (defmethod core/compile* :elm.compiler.type/code @@ -128,5 +128,5 @@ [{:keys [library terminology-service] :as context} {:keys [name]}] ;; TODO: look into other libraries (:libraryName) (if-let [{:keys [id]} (find-value-set-def library name)] - (value-set/value-set terminology-service id) + (vs/value-set terminology-service id) (value-set-not-found-anom name context))) diff --git a/modules/cql/src/blaze/elm/compiler/external_data.clj b/modules/cql/src/blaze/elm/compiler/external_data.clj index a3506da74..2b2157b9e 100644 --- a/modules/cql/src/blaze/elm/compiler/external_data.clj +++ b/modules/cql/src/blaze/elm/compiler/external_data.clj @@ -16,6 +16,7 @@ [blaze.elm.resource :as cr] [blaze.elm.spec] [blaze.elm.util :as elm-util] + [blaze.elm.value-set :as vs] [blaze.fhir.spec.references :as fsr] [blaze.util :refer [str]] [prometheus.alpha :as prom :refer [defcounter]])) @@ -30,19 +31,15 @@ (defn- code->clause-value [{:keys [system code]}] (str system "|" code)) -(defn- code-expr - "Returns an expression which, when evaluated, returns all resources of type - `data-type` which have a code equivalent to `code` at `property` and are - reachable through `eval-context`. - - Example: - * data-type - \"Observation\" - * eval-context - \"Patient\" - * property - \"code\" - * codes - [(code \"http://loinc.org\" nil \"39156-5\")]" - [node eval-context data-type property codes] - (let [clauses [(into [property] (map code->clause-value) codes)] - type-query (ac/join (d/compile-type-query node data-type clauses)) +(defn- codes-clause [property codes] + (into [property] (map code->clause-value) codes)) + +(defn- value-set-clause [property value-set] + [(str property ":in") (vs/url value-set)]) + +(defn- compartment-query-expr + [node eval-context data-type clauses] + (let [type-query (ac/join (d/compile-type-query node data-type clauses)) compartment-query (ac/join (d/compile-compartment-query node eval-context data-type clauses))] (reify-expr core/Expression @@ -134,54 +131,73 @@ (list 'retrieve (core/-form related-context-expr) data-type (d/query-clauses query))))) (defn- related-context-expr - [node context-expr data-type code-property codes] - (if (seq codes) + [node context-expr data-type code-property codes-expr] + (cond + (or (vs/value-set? codes-expr) (seq codes-expr)) (if-let [result-type-name (:result-type-name (meta context-expr))] (let [[value-type-ns context-type] (elm-util/parse-qualified-name result-type-name)] (if (= "http://hl7.org/fhir" value-type-ns) - (let [clauses [(into [code-property] (map code->clause-value) codes)] + (let [clauses [(if (vs/value-set? codes-expr) + (value-set-clause code-property codes-expr) + (codes-clause code-property codes-expr))] query (ac/join (d/compile-compartment-query node context-type data-type clauses))] (related-context-expr-with-codes context-expr data-type query)) (throw-anom (unsupported-type-ns-anom value-type-ns)))) (throw-anom unsupported-related-context-expr-without-type-anom)) + + :else (related-context-expr-without-codes node context-expr data-type))) -(defn- unfiltered-context-expr [node data-type code-property codes] - (if (empty? codes) +(defn- type-query-expr [node data-type clauses] + (let [query (ac/join (d/compile-type-query node data-type clauses))] + (reify-expr core/Expression + (-eval [_ {:keys [db]} _ _] + (prom/inc! retrieve-total) + (coll/eduction (cr/resource-mapper db) (d/execute-query db query))) + (-form [_] + `(~'retrieve ~data-type ~(d/query-clauses query)))))) + +(defn- unfiltered-context-expr [node data-type code-property codes-expr] + (cond + (vs/value-set? codes-expr) + (type-query-expr node data-type [(value-set-clause code-property codes-expr)]) + + (empty? codes-expr) (reify-expr core/Expression (-eval [_ {:keys [db]} _ _] (prom/inc! retrieve-total) (coll/eduction (cr/resource-mapper db) (d/type-list db data-type))) (-form [_] `(~'retrieve ~data-type))) - (let [clauses [(into [code-property] (map code->clause-value) codes)]] - (let [query (ac/join (d/compile-type-query node data-type clauses))] - (reify-expr core/Expression - (-eval [_ {:keys [db]} _ _] - (prom/inc! retrieve-total) - (coll/eduction (cr/resource-mapper db) (d/execute-query db query))) - (-form [_] - `(~'retrieve ~data-type ~(d/query-clauses query)))))))) - -(defn- expr* [node eval-context data-type code-property codes] - (if (empty? codes) + + :else + (type-query-expr node data-type [(codes-clause code-property codes-expr)]))) + +(defn- expr* [node eval-context data-type code-property codes-expr] + (cond + (vs/value-set? codes-expr) + (compartment-query-expr node eval-context data-type [(value-set-clause code-property codes-expr)]) + + (empty? codes-expr) (if (= data-type eval-context) resource-expr (context-expr node eval-context data-type)) - (code-expr node eval-context data-type code-property codes))) + + :else + (compartment-query-expr node eval-context data-type [(codes-clause code-property codes-expr)]))) ;; 11.1. Retrieve (defn- expr - [{:keys [node eval-context]} context-expr data-type code-property codes] + [{:keys [node eval-context]} context-expr data-type code-property codes-expr] (cond context-expr - (related-context-expr node context-expr data-type code-property codes) + (related-context-expr node context-expr data-type code-property codes-expr) (= "Unfiltered" eval-context) - (unfiltered-context-expr node data-type code-property codes) + (unfiltered-context-expr node data-type code-property codes-expr) :else - (expr* node eval-context data-type code-property codes))) + (expr* node eval-context data-type code-property codes-expr))) (defn- unsupported-dynamic-codes-expr-anom [codes-expr] (ba/unsupported @@ -195,7 +211,8 @@ (defn- compile-codes-expr [context codes-expr] (let [codes-expr (core/compile* context codes-expr)] - (if (and (sequential? codes-expr) (every? code? codes-expr)) + (if (or (and (sequential? codes-expr) (every? code? codes-expr)) + (vs/value-set? codes-expr)) codes-expr (throw-anom (unsupported-dynamic-codes-expr-anom codes-expr))))) diff --git a/modules/cql/src/blaze/elm/value_set.clj b/modules/cql/src/blaze/elm/value_set.clj index 6528d185e..38c031c14 100644 --- a/modules/cql/src/blaze/elm/value_set.clj +++ b/modules/cql/src/blaze/elm/value_set.clj @@ -6,6 +6,71 @@ [blaze.fhir.spec.type :as type] [blaze.terminology-service :as ts])) +(defn- system-param [system] + {:fhir/type :fhir.Parameters/parameter + :name #fhir/string "system" + :value (type/uri system)}) + +(defrecord ValueSetImpl [terminology-service url url-param infer-system-param] + core/Expression + (-static [_] + true) + (-attach-cache [expr _] + [(fn [] [expr])]) + (-patient-count [_] + nil) + (-resolve-refs [expr _] + expr) + (-resolve-params [expr _] + expr) + (-optimize [expr _] + expr) + (-eval [this _ _ _] + this) + (-form [_] + (list 'value-set url)) + + p/ValueSet + (-url [_] + url) + (-contains-string [_ code] + (tu/extract-result + (ts/value-set-validate-code + terminology-service + {:fhir/type :fhir/Parameters + :parameter [url-param (tu/code-param code) infer-system-param]}) + (fn [cause-msg] + (format + "Error while testing that the code `%s` is in ValueSet `%s`. Cause: %s" + code url cause-msg)))) + (-contains-code [_ code] + (tu/extract-result + (ts/value-set-validate-code + terminology-service + {:fhir/type :fhir/Parameters + :parameter + [url-param (tu/code-param (:code code)) (system-param (:system code))]}) + (fn [cause-msg] + (format + "Error while testing that the %s is in ValueSet `%s`. Cause: %s" + code url cause-msg)))) + (-contains-concept [_ concept] + (tu/extract-result + (ts/value-set-validate-code + terminology-service + {:fhir/type :fhir/Parameters + :parameter [url-param (tu/codeable-concept-param concept)]}) + (fn [cause-msg] + (format + "Error while testing that the %s is in ValueSet `%s`. Cause: %s" + concept url cause-msg))))) + +(defn value-set? [x] + (instance? ValueSetImpl x)) + +(defn url [value-set] + (p/-url value-set)) + (defn contains-string? [value-set code] (p/-contains-string value-set code)) @@ -15,66 +80,11 @@ (defn contains-concept? [value-set concept] (p/-contains-concept value-set concept)) -(defn- system-param [system] - {:fhir/type :fhir.Parameters/parameter - :name #fhir/string "system" - :value (type/uri system)}) - (defn value-set [terminology-service url] - (let [url-param {:fhir/type :fhir.Parameters/parameter + (->ValueSetImpl terminology-service url + {:fhir/type :fhir.Parameters/parameter :name #fhir/string "url" :value (type/uri url)} - infer-system-param {:fhir/type :fhir.Parameters/parameter - :name #fhir/string "inferSystem" - :value #fhir/boolean true}] - (reify - core/Expression - (-static [_] - true) - (-attach-cache [expr _] - [(fn [] [expr])]) - (-patient-count [_] - nil) - (-resolve-refs [expr _] - expr) - (-resolve-params [expr _] - expr) - (-optimize [expr _] - expr) - (-eval [this _ _ _] - this) - (-form [_] - (list 'value-set url)) - - p/ValueSet - (-contains-string [_ code] - (tu/extract-result - (ts/value-set-validate-code - terminology-service - {:fhir/type :fhir/Parameters - :parameter [url-param (tu/code-param code) infer-system-param]}) - (fn [cause-msg] - (format - "Error while testing that the code `%s` is in ValueSet `%s`. Cause: %s" - code url cause-msg)))) - (-contains-code [_ code] - (tu/extract-result - (ts/value-set-validate-code - terminology-service - {:fhir/type :fhir/Parameters - :parameter - [url-param (tu/code-param (:code code)) (system-param (:system code))]}) - (fn [cause-msg] - (format - "Error while testing that the %s is in ValueSet `%s`. Cause: %s" - code url cause-msg)))) - (-contains-concept [_ concept] - (tu/extract-result - (ts/value-set-validate-code - terminology-service - {:fhir/type :fhir/Parameters - :parameter [url-param (tu/codeable-concept-param concept)]}) - (fn [cause-msg] - (format - "Error while testing that the %s is in ValueSet `%s`. Cause: %s" - concept url cause-msg))))))) + {:fhir/type :fhir.Parameters/parameter + :name #fhir/string "inferSystem" + :value #fhir/boolean true})) diff --git a/modules/cql/src/blaze/elm/value_set/protocol.clj b/modules/cql/src/blaze/elm/value_set/protocol.clj index 3eca6b6a1..ce23627f7 100644 --- a/modules/cql/src/blaze/elm/value_set/protocol.clj +++ b/modules/cql/src/blaze/elm/value_set/protocol.clj @@ -1,6 +1,7 @@ (ns blaze.elm.value-set.protocol) (defprotocol ValueSet + (-url [_]) (-contains-string [_ code]) (-contains-code [_ code]) (-contains-concept [_ concept])) diff --git a/modules/cql/test/blaze/elm/compiler/external_data_test.clj b/modules/cql/test/blaze/elm/compiler/external_data_test.clj index c8ed8a1ee..df7bfb4ce 100644 --- a/modules/cql/test/blaze/elm/compiler/external_data_test.clj +++ b/modules/cql/test/blaze/elm/compiler/external_data_test.clj @@ -364,6 +364,82 @@ "system-192253|code-192300" "system-192253|code-140541"]])))))) + (testing "with value set reference" + (with-system-data [{:blaze.db/keys [node] terminology-service ::ts/local} api-stub/mem-node-config] + [[[:put {:fhir/type :fhir/CodeSystem :id "0" + :url #fhir/uri "http://system-115910" + :content #fhir/code "complete" + :concept + [{:fhir/type :fhir.CodeSystem/concept + :code #fhir/code "code-115927"} + {:fhir/type :fhir.CodeSystem/concept + :code #fhir/code "code-140541"}]}]] + [[:put {:fhir/type :fhir/Patient :id "0"}] + [:put {:fhir/type :fhir/Observation :id "0" + :subject #fhir/Reference{:reference #fhir/string "Patient/0"}}] + [:put {:fhir/type :fhir/Observation :id "1" + :code + #fhir/CodeableConcept + {:coding + [#fhir/Coding + {:system #fhir/uri "http://system-115910" + :code #fhir/code "code-115927"}]} + :subject #fhir/Reference{:reference #fhir/string "Patient/0"}}] + [:put {:fhir/type :fhir/Observation :id "2" + :code + #fhir/CodeableConcept + {:coding + [#fhir/Coding + {:system #fhir/uri "http://system-115910" + :code #fhir/code "code-140541"}]} + :subject #fhir/Reference{:reference #fhir/string "Patient/0"}}]]] + + (let [context + {:node node + :eval-context "Patient" + :library + {:valueSets + {:def + [{:name "vs1" + :id "http://fhir.org/VCL?v1=(http://system-115910)*"}]}} + :terminology-service terminology-service} + elm #elm/retrieve + {:type "Observation" + :codes #elm/value-set-ref "vs1" + :codeComparator "in"} + expr (c/compile context elm) + db (d/db node) + patient (ctu/resource db "Patient" "0")] + + (testing "eval" + (given (expr/eval (eval-context db) expr patient) + count := 2 + [0 :fhir/type] := :fhir/Observation + [0 :id] := "1" + [1 :fhir/type] := :fhir/Observation + [1 :id] := "2")) + + (testing "expression is dynamic" + (is (false? (core/-static expr)))) + + (ctu/testing-constant-attach-cache expr) + + (ctu/testing-constant-patient-count expr) + + (ctu/testing-constant-resolve-refs expr) + + (ctu/testing-constant-resolve-params expr) + + (testing "optimize" + (is (= expr (c/optimize expr db)))) + + (testing "form" + (has-form expr + '(retrieve + "Observation" + [["code:in" + "http://fhir.org/VCL?v1=(http://system-115910)*"]])))))) + (testing "unknown code property" (with-system [{:blaze.db/keys [node] terminology-service ::ts/local} api-stub/mem-node-config] (let [context @@ -496,6 +572,82 @@ (has-form expr '(retrieve "Medication" [["code" "system-225806|code-225809"]])))))) + (testing "with value set reference" + (with-system-data [{:blaze.db/keys [node] terminology-service ::ts/local} api-stub/mem-node-config] + [[[:put {:fhir/type :fhir/CodeSystem :id "0" + :url #fhir/uri "http://system-115910" + :content #fhir/code "complete" + :concept + [{:fhir/type :fhir.CodeSystem/concept + :code #fhir/code "code-115927"} + {:fhir/type :fhir.CodeSystem/concept + :code #fhir/code "code-140541"}]}]] + [[:put {:fhir/type :fhir/Patient :id "0"}] + [:put {:fhir/type :fhir/Observation :id "0" + :subject #fhir/Reference{:reference #fhir/string "Patient/0"}}] + [:put {:fhir/type :fhir/Observation :id "1" + :code + #fhir/CodeableConcept + {:coding + [#fhir/Coding + {:system #fhir/uri "http://system-115910" + :code #fhir/code "code-115927"}]} + :subject #fhir/Reference{:reference #fhir/string "Patient/0"}}] + [:put {:fhir/type :fhir/Observation :id "2" + :code + #fhir/CodeableConcept + {:coding + [#fhir/Coding + {:system #fhir/uri "http://system-115910" + :code #fhir/code "code-140541"}]} + :subject #fhir/Reference{:reference #fhir/string "Patient/0"}}]]] + + (let [context + {:node node + :eval-context "Unfiltered" + :library + {:valueSets + {:def + [{:name "vs1" + :id "http://fhir.org/VCL?v1=(http://system-115910)*"}]}} + :terminology-service terminology-service} + elm #elm/retrieve + {:type "Observation" + :codes #elm/value-set-ref "vs1" + :codeComparator "in"} + expr (c/compile context elm) + db (d/db node) + patient (ctu/resource db "Patient" "0")] + + (testing "eval" + (given (expr/eval (eval-context db) expr patient) + count := 2 + [0 :fhir/type] := :fhir/Observation + [0 :id] := "1" + [1 :fhir/type] := :fhir/Observation + [1 :id] := "2")) + + (testing "expression is dynamic" + (is (false? (core/-static expr)))) + + (ctu/testing-constant-attach-cache expr) + + (ctu/testing-constant-patient-count expr) + + (ctu/testing-constant-resolve-refs expr) + + (ctu/testing-constant-resolve-params expr) + + (testing "optimize" + (is (= expr (c/optimize expr db)))) + + (testing "form" + (has-form expr + '(retrieve + "Observation" + [["code:in" + "http://fhir.org/VCL?v1=(http://system-115910)*"]])))))) + (testing "unknown code property" (with-system [{:blaze.db/keys [node] terminology-service ::ts/local} api-stub/mem-node-config] (let [context @@ -630,6 +782,72 @@ '(retrieve (singleton-from (retrieve-resource)) "Observation" [["code" "system-133620|code-133657"]])))))) + (testing "with pre-compiled database query" + (with-system-data [{:blaze.db/keys [node] :as system} api-stub/mem-node-config] + [[[:put {:fhir/type :fhir/CodeSystem :id "0" + :url #fhir/uri "http://system-133620" + :content #fhir/code "complete" + :concept + [{:fhir/type :fhir.CodeSystem/concept + :code #fhir/code "code-133657"} + {:fhir/type :fhir.CodeSystem/concept + :code #fhir/code "code-140541"}]}]] + [[:put {:fhir/type :fhir/Patient :id "0"}] + [:put {:fhir/type :fhir/Observation :id "0" + :code + #fhir/CodeableConcept + {:coding + [#fhir/Coding + {:system #fhir/uri "http://system-133620" + :code #fhir/code "code-133657"}]} + :subject #fhir/Reference{:reference #fhir/string "Patient/0"}}]]] + + (let [library (t/translate + "library test + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + valueset vs1: 'http://fhir.org/VCL?v1=(http://system-133620)*' + + context Patient + + define \"name-133730\": + singleton from ([Patient]) + + define InInitialPopulation: + [\"name-133730\" -> Observation: vs1] + ") + context (compile-context system) + {:keys [expression-defs]} (library/compile-library context library {}) + db (d/db node) + patient (ctu/resource db "Patient" "0") + eval-context (assoc (eval-context db) :expression-defs expression-defs) + expr (:expression (get expression-defs "InInitialPopulation"))] + + (testing "eval" + (given (expr/eval eval-context expr patient) + count := 1 + [0 :fhir/type] := :fhir/Observation + [0 :id] := "0")) + + (testing "expression is dynamic" + (is (false? (core/-static expr)))) + + (ctu/testing-constant-attach-cache expr) + + (ctu/testing-constant-patient-count expr) + + (ctu/testing-constant-resolve-refs expr) + + (ctu/testing-constant-resolve-params expr) + + (ctu/testing-constant-optimize expr) + + (testing "form" + (has-form expr + '(retrieve (singleton-from (retrieve-resource)) "Observation" + [["code:in" "http://fhir.org/VCL?v1=(http://system-133620)*"]])))))) + (testing "unknown code property" (with-system [{:blaze.db/keys [node] terminology-service ::ts/local} api-stub/mem-node-config] (let [library {:codeSystems diff --git a/modules/cql/test/blaze/elm/compiler/library_test.clj b/modules/cql/test/blaze/elm/compiler/library_test.clj index 13841e572..c12c75ca5 100644 --- a/modules/cql/test/blaze/elm/compiler/library_test.clj +++ b/modules/cql/test/blaze/elm/compiler/library_test.clj @@ -1086,7 +1086,43 @@ (testing "the whole expression will be optimized to false" (given (library/optimize (d/db node) expression-defs) ["InInitialPopulation" :context] := "Patient" - ["InInitialPopulation" expr-form] := false))))))) + ["InInitialPopulation" expr-form] := false)))))) + + (testing "with value set" + (let [library (t/translate "library test + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + valueset vs: 'http://fhir.org/VCL?v1=(http://system-115910)*' + + context Patient + + define InInitialPopulation: + exists [Condition: vs]")] + (with-system-data [{:blaze.db/keys [node] :as system} api-stub/mem-node-config] + [[[:put {:fhir/type :fhir/CodeSystem :id "0" + :url #fhir/uri "http://system-115910" + :content #fhir/code "complete" + :concept + [{:fhir/type :fhir.CodeSystem/concept + :code #fhir/code "code-115927"} + {:fhir/type :fhir.CodeSystem/concept + :code #fhir/code "code-140541"}]}]] + [[:put {:fhir/type :fhir/Condition :id "0" + :code (codeable-concept "http://system-115910" "code-115927")}]]] + + (let [{:keys [expression-defs]} (library/compile-library (compile-context system) library {})] + (given expression-defs + ["InInitialPopulation" :context] := "Patient" + ["InInitialPopulation" expr-form] := + '(exists + (retrieve "Condition" [["code:in" "http://fhir.org/VCL?v1=(http://system-115910)*"]]))) + + (testing "there are no references to resolve" + (is (= expression-defs (library/resolve-all-refs expression-defs)))) + + (testing "there are no optimizations available" + (is (= expression-defs (library/optimize (d/db node) expression-defs))))))))) (testing "query with where clause" (let [library (t/translate "library test diff --git a/modules/cql/test/blaze/elm/compiler/list_operators_test.clj b/modules/cql/test/blaze/elm/compiler/list_operators_test.clj index 32581903c..9baad9308 100644 --- a/modules/cql/test/blaze/elm/compiler/list_operators_test.clj +++ b/modules/cql/test/blaze/elm/compiler/list_operators_test.clj @@ -251,11 +251,12 @@ ;; ;; If the argument is null, the result is false. (def ^:private exists-config - (assoc api-stub/mem-node-config - ::expr/cache - {:node (ig/ref :blaze.db/node) - :executor (ig/ref :blaze.test/executor)} - :blaze.test/executor {})) + (assoc + api-stub/mem-node-config + ::expr/cache + {:node (ig/ref :blaze.db/node) + :executor (ig/ref :blaze.test/executor)} + :blaze.test/executor {})) (defmethod elm-spec/expression :elm.spec.type/exists-test [_] map?) diff --git a/modules/cql/test/blaze/elm/expression/cache_test.clj b/modules/cql/test/blaze/elm/expression/cache_test.clj index 3b03134eb..9e6e44ee8 100644 --- a/modules/cql/test/blaze/elm/expression/cache_test.clj +++ b/modules/cql/test/blaze/elm/expression/cache_test.clj @@ -46,11 +46,12 @@ (test/use-fixtures :each fixture) (def ^:private config - (assoc api-stub/mem-node-config - ::expr/cache - {:node (ig/ref :blaze.db/node) - :executor (ig/ref :blaze.test/executor)} - :blaze.test/executor {})) + (assoc + api-stub/mem-node-config + ::expr/cache + {:node (ig/ref :blaze.db/node) + :executor (ig/ref :blaze.test/executor)} + :blaze.test/executor {})) (deftest init-test (testing "nil config" @@ -136,11 +137,12 @@ (is (s/valid? :blaze.metrics/collector collector)))) (def ^:private config - (assoc api-stub/mem-node-config - ::expr/cache - {:node (ig/ref :blaze.db/node) - :executor (ig/ref :blaze.test/executor)} - :blaze.test/executor {})) + (assoc + api-stub/mem-node-config + ::expr/cache + {:node (ig/ref :blaze.db/node) + :executor (ig/ref :blaze.test/executor)} + :blaze.test/executor {})) (defn- compile-exists-expr [node resource-type] (let [elm (elm/exists (elm/retrieve {:type resource-type}))] diff --git a/modules/cql/test/blaze/elm/literal.clj b/modules/cql/test/blaze/elm/literal.clj index 60ec6bbd2..e9f6266b4 100644 --- a/modules/cql/test/blaze/elm/literal.clj +++ b/modules/cql/test/blaze/elm/literal.clj @@ -125,7 +125,9 @@ ;; 3.12. ValueSetRef (defn value-set-ref [name] - {:type "ValueSetRef" :name name}) + {:type "ValueSetRef" + :name name + :resultTypeName "{urn:hl7-org:elm-types:r1}ValueSet"}) ;; 7. Parameters diff --git a/modules/cql/test/blaze/elm/value_set_spec.clj b/modules/cql/test/blaze/elm/value_set_spec.clj index 265298d53..bd6f70557 100644 --- a/modules/cql/test/blaze/elm/value_set_spec.clj +++ b/modules/cql/test/blaze/elm/value_set_spec.clj @@ -2,23 +2,31 @@ (:require [blaze.elm.code :refer [code?]] [blaze.elm.concept :refer [concept?]] - [blaze.elm.value-set :as value-set] + [blaze.elm.value-set :as vs] [blaze.elm.value-set.spec] [blaze.terminology-service.spec] [clojure.spec.alpha :as s])) -(s/fdef value-set/contains-string? +(s/fdef vs/value-set? + :args (s/cat :x any?) + :ret boolean?) + +(s/fdef vs/url + :args (s/cat :value-set :blaze.elm/value-set) + :ret string?) + +(s/fdef vs/contains-string? :args (s/cat :value-set :blaze.elm/value-set :code string?) :ret boolean?) -(s/fdef value-set/contains-code? +(s/fdef vs/contains-code? :args (s/cat :value-set :blaze.elm/value-set :code code?) :ret boolean?) -(s/fdef value-set/contains-concept? +(s/fdef vs/contains-concept? :args (s/cat :value-set :blaze.elm/value-set :concept concept?) :ret boolean?) -(s/fdef value-set/value-set +(s/fdef vs/value-set :args (s/cat :terminology-service :blaze/terminology-service :url string?) :ret :blaze.elm/value-set) diff --git a/modules/cql/test/blaze/elm/value_set_test.clj b/modules/cql/test/blaze/elm/value_set_test.clj index e0c5c0533..46b994650 100644 --- a/modules/cql/test/blaze/elm/value_set_test.clj +++ b/modules/cql/test/blaze/elm/value_set_test.clj @@ -5,11 +5,11 @@ [blaze.elm.code :as code] [blaze.elm.concept :as concept] [blaze.elm.util-spec] - [blaze.elm.value-set :as value-set] + [blaze.elm.value-set :as vs] [blaze.terminology-service.protocols :as p] [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] - [clojure.test :as test :refer [deftest testing]] + [clojure.test :as test :refer [deftest is testing]] [cognitect.anomalies :as anom] [juxt.iota :refer [given]])) @@ -17,26 +17,37 @@ (test/use-fixtures :each tu/fixture) +(def ^:private terminology-service + (reify p/TerminologyService)) + +(deftest value-set?-test + (let [value-set (vs/value-set terminology-service "value-set-url-165123")] + (is (vs/value-set? value-set)))) + +(deftest url-test + (let [value-set (vs/value-set terminology-service "value-set-url-165123")] + (is (= "value-set-url-165123" (vs/url value-set))))) + (deftest contains-test (testing "with failing terminology service" (let [terminology-service (reify p/TerminologyService (-value-set-validate-code [_ _] (ac/completed-future (ba/fault "msg-094502")))) - value-set (value-set/value-set terminology-service "value-set-url-165123")] + value-set (vs/value-set terminology-service "value-set-url-165123")] - (given (ba/try-anomaly (value-set/contains-string? value-set "code-094531")) + (given (ba/try-anomaly (vs/contains-string? value-set "code-094531")) ::anom/category := ::anom/fault ::anom/message := "Error while testing that the code `code-094531` is in ValueSet `value-set-url-165123`. Cause: msg-094502") - (given (ba/try-anomaly (value-set/contains-code? value-set (code/code "system-165208" nil "code-094531"))) + (given (ba/try-anomaly (vs/contains-code? value-set (code/code "system-165208" nil "code-094531"))) ::anom/category := ::anom/fault ::anom/message := "Error while testing that the Code {system: `system-165208`, code: `code-094531`} is in ValueSet `value-set-url-165123`. Cause: msg-094502") - (given (ba/try-anomaly (value-set/contains-code? value-set (code/code "system-191937" "version-191942" "code-191945"))) + (given (ba/try-anomaly (vs/contains-code? value-set (code/code "system-191937" "version-191942" "code-191945"))) ::anom/category := ::anom/fault ::anom/message := "Error while testing that the Code {system: `system-191937`, version: `version-191942`, code: `code-191945`} is in ValueSet `value-set-url-165123`. Cause: msg-094502") - (given (ba/try-anomaly (value-set/contains-concept? value-set (concept/concept [(code/code "system-165208" nil "code-094531")]))) + (given (ba/try-anomaly (vs/contains-concept? value-set (concept/concept [(code/code "system-165208" nil "code-094531")]))) ::anom/category := ::anom/fault ::anom/message := "Error while testing that the Concept {Code {system: `system-165208`, code: `code-094531`}} is in ValueSet `value-set-url-165123`. Cause: msg-094502")))) diff --git a/modules/db/src/blaze/db/api.clj b/modules/db/src/blaze/db/api.clj index 99f81718f..6be6deca7 100644 --- a/modules/db/src/blaze/db/api.clj +++ b/modules/db/src/blaze/db/api.clj @@ -78,7 +78,7 @@ (p/-as-of db t)) (defn since - "Returns the value of `db` since some point `t`, inclusive." + "Returns the value of `db` since some `instant`, inclusive." [db instant] (p/-since db instant)) @@ -366,7 +366,9 @@ ;; ---- Common Matcher Functions ---------------------------------------------- -(defn matches? [db matcher resource-handle] +(defn matches? + "Returns `true` if `resource-handle` matches `matcher` in `db`." + [db matcher resource-handle] (transduce (p/-matcher-transducer db matcher) (fn ([r] r) ([_ _] true)) @@ -567,6 +569,8 @@ ;; ---- (Re) Index ------------------------------------------------------------ (defn re-index-total + "Returns the total number of resources to re-index for the search param with + `search-param-url`, or an anomaly in case of errors." [db search-param-url] (p/-re-index-total db search-param-url)) diff --git a/modules/db/src/blaze/db/api_spec.clj b/modules/db/src/blaze/db/api_spec.clj index f13565089..ec89123ec 100644 --- a/modules/db/src/blaze/db/api_spec.clj +++ b/modules/db/src/blaze/db/api_spec.clj @@ -2,6 +2,8 @@ (:require [blaze.async.comp :as ac] [blaze.async.comp-spec] + [blaze.async.flow :as flow] + [blaze.async.flow-spec] [blaze.coll.core-spec] [blaze.coll.spec :as cs] [blaze.db.api :as d] @@ -26,6 +28,10 @@ :args (s/cat :node :blaze.db/node :tx-ops :blaze.db/tx-ops) :ret ac/completable-future?) +(s/fdef d/changed-resources-publisher + :args (s/cat :node :blaze.db/node :type :fhir.resource/type) + :ret flow/publisher?) + (s/fdef d/node :args (s/cat :db :blaze.db/db) :ret :blaze.db/node) @@ -52,7 +58,7 @@ (s/fdef d/as-of-t :args (s/cat :db :blaze.db/db) - :ret :blaze.db/t) + :ret (s/nilable :blaze.db/t)) (s/fdef d/tx :args (s/cat :node-or-db (s/or :node :blaze.db/node :db :blaze.db/db) @@ -175,6 +181,10 @@ ;; ---- Common Query Functions ------------------------------------------------ +(s/fdef d/count-query + :args (s/cat :db :blaze.db/db :query :blaze.db/query) + :ret ac/completable-future?) + (s/fdef d/execute-query :args (s/cat :db :blaze.db/db :query :blaze.db/query :args (s/* any?)) :ret (cs/coll-of :blaze.db/resource-handle)) @@ -249,6 +259,10 @@ :args (s/cat :db :blaze.db/db) :ret nat-int?) +(s/fdef d/changes + :args (s/cat :db :blaze.db/db) + :ret (cs/coll-of :blaze.db/resource-handle)) + (s/fdef d/include :args (s/cat :db :blaze.db/db :resource-handle :blaze.db/resource-handle :code string? :type (s/? :fhir.resource/type)) diff --git a/modules/db/src/blaze/db/impl/search_param/token.clj b/modules/db/src/blaze/db/impl/search_param/token.clj index 55c437a6e..617cf3d2b 100644 --- a/modules/db/src/blaze/db/impl/search_param/token.clj +++ b/modules/db/src/blaze/db/impl/search_param/token.clj @@ -248,7 +248,10 @@ (-ordered-index-handles [search-param batch-db tid modifier compiled-values] (if (= "in" modifier) - (p/-ordered-index-handles search-param batch-db tid nil (flatten compiled-values)) + (let [all-compiled-values (flatten compiled-values)] + (if (empty? all-compiled-values) + [] + (p/-ordered-index-handles search-param batch-db tid nil all-compiled-values))) (if (= 1 (count compiled-values)) (p/-index-handles search-param batch-db tid modifier (first compiled-values)) (let [index-handles #(p/-index-handles search-param batch-db tid modifier %)] @@ -279,7 +282,10 @@ (-ordered-compartment-index-handles [search-param batch-db compartment tid modifier compiled-values] (if (= "in" modifier) - (p/-ordered-compartment-index-handles search-param batch-db compartment tid nil (flatten compiled-values)) + (let [all-compiled-values (flatten compiled-values)] + (if (empty? all-compiled-values) + [] + (p/-ordered-compartment-index-handles search-param batch-db compartment tid nil all-compiled-values))) (if (= 1 (count compiled-values)) (c-sp-vr/index-handles (:snapshot batch-db) compartment c-hash tid (first compiled-values)) (let [index-handles #(c-sp-vr/index-handles (:snapshot batch-db) compartment c-hash tid %)] diff --git a/modules/db/test/blaze/db/api_test.clj b/modules/db/test/blaze/db/api_test.clj index 4568438dc..2d7d9c5c6 100644 --- a/modules/db/test/blaze/db/api_test.clj +++ b/modules/db/test/blaze/db/api_test.clj @@ -9157,6 +9157,41 @@ {:system #fhir/uri "http://fhir.de/CodeSystem/bfarm/icd-10-gm" :code #fhir/code "C73"}]}}]]] + (testing "no code" + (with-redefs [ts/expand-value-set + (fn [_ params] + (assert (= "url-150142" (:value (:value (first (:parameter params)))))) + (ac/completed-future + {:fhir/type :fhir/ValueSet + :expansion + {:fhir/type :fhir.ValueSet/expansion + :contains []}}))] + (let [clauses [["code:in" "url-150142"]]] + (testing "type query" + (given-type-query node "Condition" clauses + count := 0) + + (given (explain-type-query node "Condition" clauses) + :scan-type := :ordered + [:scan-clauses count] := 1 + [:scan-clauses 0 :code] := "code" + [:scan-clauses 0 :modifier] := "in" + [:scan-clauses 0 :values] := ["url-150142"] + [:seek-clauses count] := 0)) + + (testing "compartment query" + (given (pull-compartment-query node "Patient" "0" "Condition" clauses) + count := 0) + + (given (explain-compartment-query node "Patient" "Condition" clauses) + :query-type := :compartment + :scan-type := :ordered + [:scan-clauses count] := 1 + [:scan-clauses 0 :code] := "code" + [:scan-clauses 0 :modifier] := "in" + [:scan-clauses 0 :values] := ["url-150142"] + [:seek-clauses count] := 0))))) + (testing "one code" (with-redefs [ts/expand-value-set (fn [_ params] diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure_test.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure_test.clj index 6d5550e4a..45c61e74c 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure_test.clj +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure_test.clj @@ -975,6 +975,8 @@ (testing-query "q66-observation-specimen" 2) + (testing-query "q67-icd10-value-set" 2) + (let [result (evaluate "q1" "subject-list")] (testing "MeasureReport is valid" (is (s/valid? :fhir/Resource (:resource result)))) @@ -1242,4 +1244,4 @@ (comment (log/set-min-level! :debug) - (evaluate "q65-in-code-system-gender")) + (evaluate "q67-icd10-value-set")) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q67-icd10-value-set.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q67-icd10-value-set.cql new file mode 100644 index 000000000..1fa5d2256 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q67-icd10-value-set.cql @@ -0,0 +1,10 @@ +library "q67-icd10-value-set" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +valueset icd10_e10: 'http://fhir.org/VCL?v1=(http://fhir.de/CodeSystem/bfarm/icd-10-gm)concept< % :code :value (= property)) properties))) + (condp = property + "concept" (fn [_] true) + (fn [{properties :property}] + (some #(-> % :code :value (= property)) properties)))) (ba/incorrect (format "Invalid %s exists filter value `%s` in code system `%s`. Should be one of `true` or `false`." property value url))) (ba/incorrect (format "Missing %s exists filter value in code system `%s`." property url))) (ba/incorrect (format "Missing exists filter property in code system `%s`." url)))) diff --git a/modules/terminology-service-local/src/blaze/terminology_service/local/code_system/filter/is_a.clj b/modules/terminology-service-local/src/blaze/terminology_service/local/code_system/filter/is_a.clj index 533e58a10..2061ffd2f 100644 --- a/modules/terminology-service-local/src/blaze/terminology_service/local/code_system/filter/is_a.clj +++ b/modules/terminology-service-local/src/blaze/terminology_service/local/code_system/filter/is_a.clj @@ -14,11 +14,12 @@ (graph/is-a graph value))) (defmethod core/filter-concepts :is-a - [{{url :value} :url :as code-system} {{property :value} :property {value :value} :value}] - (condp = property - "concept" (expand-filter code-system value) - nil (ba/incorrect (format "Missing is-a filter property in code system `%s`." url)) - (ba/unsupported (format "Unsupported is-a filter property `%s` in code system `%s`." property url)))) + [{{url :value} :url :as code-system} {:keys [property value]}] + (if-let [property (:value property)] + (condp = property + "concept" (expand-filter code-system (:value value)) + (ba/unsupported (format "Unsupported is-a filter property `%s` in code system `%s`." property url))) + (ba/incorrect (format "Missing is-a filter property in code system `%s`." url)))) (defn- find-filter [{{url :value} :url :default/keys [graph]} value code] @@ -27,9 +28,9 @@ (graph/find-is-a graph value code))) (defmethod core/find-concept :is-a - [{{url :value} :url :as code-system} - {{property :value} :property {value :value} :value} code] - (condp = property - "concept" (find-filter code-system value code) - nil (ba/incorrect (format "Missing is-a filter property in code system `%s`." url)) - (ba/unsupported (format "Unsupported is-a filter property `%s` in code system `%s`." property url)))) + [{{url :value} :url :as code-system} {:keys [property value]} code] + (if-let [property (:value property)] + (condp = property + "concept" (find-filter code-system (:value value) code) + (ba/unsupported (format "Unsupported is-a filter property `%s` in code system `%s`." property url))) + (ba/incorrect (format "Missing is-a filter property in code system `%s`." url)))) diff --git a/modules/terminology-service-local/test/blaze/terminology_service/local_test.clj b/modules/terminology-service-local/test/blaze/terminology_service/local_test.clj index 220e9c89e..630173456 100644 --- a/modules/terminology-service-local/test/blaze/terminology_service/local_test.clj +++ b/modules/terminology-service-local/test/blaze/terminology_service/local_test.clj @@ -3847,7 +3847,40 @@ [:expansion :contains count] := 1 [:expansion :contains 0 :system] := #fhir/uri "system-182822" [:expansion :contains 0 :code] := #fhir/code "code-182832" - [:expansion :contains 0 :display] := #fhir/string "display-182717")))))) + [:expansion :contains 0 :display] := #fhir/string "display-182717"))))) + + (testing "concept property" + (with-system-data [{ts ::ts/local} config] + [[[:put {:fhir/type :fhir/CodeSystem :id "0" + :url #fhir/uri "system-182822" + :content #fhir/code "complete" + :concept + [{:fhir/type :fhir.CodeSystem/concept + :code #fhir/code "code-182832" + :display #fhir/string "display-182717"} + {:fhir/type :fhir.CodeSystem/concept + :code #fhir/code "code-151446" + :display #fhir/string "display-151449"}]}] + [:put {:fhir/type :fhir/ValueSet :id "0" + :url #fhir/uri "value-set-182905" + :compose + {:fhir/type :fhir.ValueSet/compose + :include + [{:fhir/type :fhir.ValueSet.compose/include + :system #fhir/uri "system-182822" + :filter + [{:fhir/type :fhir.ValueSet.compose.include/filter + :property #fhir/code "concept" + :op #fhir/code "exists" + :value #fhir/string "true"}]}]}}]]] + + (given @(expand-value-set ts "url" #fhir/uri "value-set-182905") + :fhir/type := :fhir/ValueSet + [:expansion :contains count] := 2 + [:expansion :contains 0 :system] := #fhir/uri "system-182822" + [:expansion :contains (concept "code-182832") 0 :display] := #fhir/string "display-182717" + [:expansion :contains 1 :system] := #fhir/uri "system-182822" + [:expansion :contains (concept "code-151446") 0 :display] := #fhir/string "display-151449")))) (deftest expand-value-set-include-filter-equals-test (with-system-data [{ts ::ts/local} config]