diff --git a/.github/scripts/evaluate-measure-timeout.sh b/.github/scripts/evaluate-measure-timeout.sh new file mode 100755 index 000000000..0875b29e1 --- /dev/null +++ b/.github/scripts/evaluate-measure-timeout.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +BASE="http://localhost:8080/fhir" +NAME="$1" + +DIAGNOSTICS=$(blazectl --server "$BASE" evaluate-measure ".github/scripts/cql/$NAME.yml" 2> /dev/null | grep Diagnostics | cut -d: -f2 | xargs) + +if [ "$DIAGNOSTICS" = "Timeout of 10 millis eclipsed while evaluating." ]; then + echo "Success: timeout happened" +else + echo "Fail: no timeout" + exit 1 +fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 25b3005ee..b20ef7a83 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: - name: Setup Clojure uses: DeLaGuardo/setup-clojure@master with: - clj-kondo: '2022.10.05' + clj-kondo: '2023.01.20' - name: Check out Git repository uses: actions/checkout@v3 @@ -645,6 +645,44 @@ jobs: - name: Load Data run: blazectl --no-progress --server http://localhost:8080/fhir upload .github/test-data/big-tx + evaluate-measure-timeout-test: + needs: build + runs-on: ubuntu-22.04 + + steps: + - name: Check out Git repository + uses: actions/checkout@v3 + + - name: Install Blazectl + run: .github/scripts/install-blazectl.sh + + - name: Download Blaze Image + uses: actions/download-artifact@v3 + with: + name: blaze-image + path: /tmp + + - name: Load Blaze Image + run: docker load --input /tmp/blaze.tar + + - name: Run Blaze + run: docker run --name blaze -d -e JAVA_TOOL_OPTIONS=-Xmx2g -e FHIR_OPERATION_EVALUATE_MEASURE_TIMEOUT=10 -p 8080:8080 -v blaze-data:/app/data blaze:latest + + - name: Wait for Blaze + run: .github/scripts/wait-for-url.sh http://localhost:8080/health + + - name: Docker Logs + run: docker logs blaze + + - name: Check Capability Statement + run: .github/scripts/check-capability-statement.sh + + - name: Load Data + run: blazectl --no-progress --server http://localhost:8080/fhir upload .github/test-data/synthea + + - name: Evaluate CQL Query 1 + run: .github/scripts/evaluate-measure-timeout.sh q1 + include-without-referential-integrity-test: needs: build runs-on: ubuntu-22.04 @@ -1203,6 +1241,7 @@ jobs: - not-enforcing-referential-integrity-test - small-transactions-test - big-transaction-test + - evaluate-measure-timeout-test - include-without-referential-integrity-test - chaining-without-referential-integrity-test - bundle-with-references-test diff --git a/docs/cql-queries.md b/docs/cql-queries.md index cdfe68408..a27272398 100644 --- a/docs/cql-queries.md +++ b/docs/cql-queries.md @@ -11,7 +11,7 @@ If you like to use the command line, please look into [this section](cql-queries ## API Documentation -If yopu like to use the CQL Evaluation API directly, please read the [CQL API Documentation](cql-queries/api.md). +If you'd like to use the CQL Evaluation API directly, please read the [CQL API Documentation](cql-queries/api.md). ## Install the Quality Reporting UI diff --git a/docs/deployment/environment-variables.md b/docs/deployment/environment-variables.md index a736cbb71..8b91d5c56 100644 --- a/docs/deployment/environment-variables.md +++ b/docs/deployment/environment-variables.md @@ -86,6 +86,7 @@ More information about distributed deployment are available [here](distributed.m | LOG_LEVEL | info | v0.6 | — | one of trace, debug, info, warn or error | | JAVA_TOOL_OPTIONS | — | — | — | JVM options \(Docker only\) | | FHIR_OPERATION_EVALUATE_MEASURE_THREADS | 4 | v0.8 | — | The maximum number of parallel $evaluate-measure executions. | +| FHIR_OPERATION_EVALUATE_MEASURE_TIMEOUT | 3600000 (1h) | v0.19 | — | Timeout in milliseconds for $evaluate-measure executions. | | OPENID_PROVIDER_URL | — | v0.11 | — | [OpenID Connect][4] provider URL to enable [authentication][5] | | ENFORCE_REFERENTIAL_INTEGRITY | true | v0.14 | — | Enforce referential integrity on resource create, update and delete. | | DB_SYNC_TIMEOUT | 10000 | v0.15 | — | Timeout in milliseconds for all reading FHIR interactions acquiring the newest database state. | diff --git a/docs/performance/fhir-search/simple-code-search.sh b/docs/performance/fhir-search/simple-code-search.sh index a2dfee04c..9f51c4d69 100644 --- a/docs/performance/fhir-search/simple-code-search.sh +++ b/docs/performance/fhir-search/simple-code-search.sh @@ -10,7 +10,7 @@ RESOURCE_HANDLE_CACHE_SIZE=30000000 start-blaze() { echo "Starting Blaze..." docker run --name blaze --rm -v "$VOLUME:/app/data" \ - -e JAVA_TOOL_OPTIONS="-Xmx${HEAP_SIZE}g -Dclojure.compiler.direct-linking=true" \ + -e JAVA_TOOL_OPTIONS="-Xmx${HEAP_SIZE}g" \ -e LOG_LEVEL=debug \ -e DB_BLOCK_CACHE_SIZE=$BLOCK_CACHE_SIZE \ -e DB_RESOURCE_CACHE_SIZE=$RESOURCE_CACHE_SIZE \ @@ -19,7 +19,7 @@ start-blaze() { -e DB_RESOURCE_INDEXER_THREADS=16 \ -p 8080:8080 \ -p 8081:8081 \ - -d samply/blaze:pr-678 + -d samply/blaze:0.18 ../../.github/scripts/wait-for-url.sh http://localhost:8080/health echo "Finished" diff --git a/docs/performance/import.sh b/docs/performance/import.sh index 20b21af1d..7d6db519f 100644 --- a/docs/performance/import.sh +++ b/docs/performance/import.sh @@ -5,14 +5,14 @@ C=8 import-once() { docker run --name blaze --rm -v blaze-data:/app/data \ - -e JAVA_TOOL_OPTIONS="-Xmx4g -Dclojure.compiler.direct-linking=true" \ + -e JAVA_TOOL_OPTIONS="-Xmx4g" \ -e LOG_LEVEL=debug \ -e DB_BLOCK_CACHE_SIZE=8192 \ -e DB_MAX_BACKGROUND_JOBS=16 \ -e DB_RESOURCE_INDEXER_THREADS=16 \ -p 8080:8080 \ -p 8081:8081 \ - -d samply/blaze:pr-678 + -d samply/blaze:0.18 ../../.github/scripts/wait-for-url.sh http://localhost:8080/health diff --git a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure.clj b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure.clj index 2b1d87a68..291fbe0dd 100644 --- a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure.clj +++ b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure.clj @@ -19,6 +19,7 @@ [blaze.spec] [clojure.spec.alpha :as s] [integrant.core :as ig] + [java-time.api :as time] [reitit.core :as reitit] [ring.util.response :as ring] [taoensso.timbre :as log]) @@ -123,7 +124,8 @@ (defmethod ig/pre-init-spec ::handler [_] - (s/keys :req-un [:blaze.db/node ::executor :blaze/clock :blaze/rng-fn])) + (s/keys :req-un [:blaze.db/node ::executor :blaze/clock :blaze/rng-fn] + :opt-un [::timeout])) (defmethod ig/init-key ::handler [_ context] @@ -133,6 +135,14 @@ (wrap-observe-request-duration "operation-evaluate-measure"))) +(defmethod ig/pre-init-spec ::timeout [_] + (s/keys :req-un [:blaze.fhir.operation.evaluate-measure.timeout/millis])) + + +(defmethod ig/init-key ::timeout [_ {:keys [millis]}] + (time/millis millis)) + + (defmethod ig/pre-init-spec ::executor [_] (s/keys :opt-un [::num-threads])) diff --git a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/cql.clj b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/cql.clj index 28cee6bae..5e52b4e48 100644 --- a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/cql.clj +++ b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/cql.clj @@ -6,9 +6,11 @@ [blaze.elm.util :as elm-util] [blaze.fhir.spec :as fhir-spec] [clojure.core.reducers :as r] + [cognitect.anomalies :as anom] [taoensso.timbre :as log]) (:import - [java.lang AutoCloseable])) + [java.lang AutoCloseable] + [java.time Duration])) (set! *warn-on-reflection* true) @@ -37,7 +39,7 @@ (ex-message e))) -(defn- evaluate-expression-1 [context subject-handle name expression] +(defn- evaluate-expression-1* [context subject-handle name expression] (try (expr/eval context expression subject-handle) (catch Exception e @@ -54,6 +56,24 @@ (merge ex-data)))))) +(defn- timeout-millis [{:keys [timeout]}] + (.toMillis ^Duration timeout)) + + +(defn- timeout-eclipsed-msg [context] + (format "Timeout of %d millis eclipsed while evaluating." + (timeout-millis context))) + + +(defn- evaluate-expression-1 + [{:keys [timeout-eclipsed?] :as context} subject-handle name expression] + (if (timeout-eclipsed?) + {::anom/category ::anom/interrupted + ::anom/message (timeout-eclipsed-msg context) + :timeout (:timeout context)} + (evaluate-expression-1* context subject-handle name expression))) + + (defn- close-batch-db! [{:keys [db]}] (.close ^AutoCloseable db)) @@ -250,7 +270,7 @@ (stratum-combine-op context) (fn [context {:keys [subject-handle] :as handle}] (if-ok [stratum (evaluate-stratum-expression context subject-handle - name expression)] + name expression)] (update context ::result stratum-result-reduce-op stratum handle) #(reduced (assoc context ::result %)))) handles)) diff --git a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure.clj b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure.clj index 3dd84f7da..2a740128c 100644 --- a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure.clj +++ b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure.clj @@ -13,6 +13,7 @@ [blaze.handler.fhir.util :as fhir-util] [blaze.luid :as luid] [clojure.string :as str] + [java-time.api :as time] [prometheus.alpha :as prom] [taoensso.timbre :as log]) (:import @@ -329,14 +330,19 @@ (defn- enhance-context - [{:keys [clock db] :as context} measure {:keys [report-type subject-ref]}] - (let [subject-type (subject-type measure)] + [{:keys [clock db timeout] :as context :or {timeout (time/hours 1)}} measure + {:keys [report-type subject-ref]}] + (let [subject-type (subject-type measure) + now (now clock) + timeout-instant (time/instant (time/plus now timeout))] (when-ok [{:keys [expression-defs function-defs parameter-default-values]} (compile-primary-library db measure) subject-handle (some->> subject-ref (subject-handle db subject-type))] (cond-> (assoc context :db db - :now (now clock) + :now now + :timeout-eclipsed? #(not (.isBefore (.instant ^Clock clock) timeout-instant)) + :timeout timeout :expression-defs expression-defs :function-defs function-defs :parameters parameter-default-values diff --git a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/spec.clj b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/spec.clj index e4f43d43d..d8f3c7a3f 100644 --- a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/spec.clj +++ b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/spec.clj @@ -1,12 +1,22 @@ (ns blaze.fhir.operation.evaluate-measure.spec (:require [blaze.executors :as ex] - [clojure.spec.alpha :as s])) + [blaze.fhir.operation.evaluate-measure :as-alias measure] + [clojure.spec.alpha :as s] + [java-time.api :as time])) -(s/def :blaze.fhir.operation.evaluate-measure/executor +(s/def ::measure/executor ex/executor?) -(s/def :blaze.fhir.operation.evaluate-measure/num-threads +(s/def ::measure/num-threads + pos-int?) + + +(s/def ::measure/timeout + time/duration?) + + +(s/def :blaze.fhir.operation.evaluate-measure.timeout/millis nat-int?) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql/spec.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql/spec.clj new file mode 100644 index 000000000..b7392daa4 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql/spec.clj @@ -0,0 +1,31 @@ +(ns blaze.fhir.operation.evaluate-measure.cql.spec + (:require + [blaze.elm.compiler :as-alias compiler] + [blaze.fhir.operation.evaluate-measure.cql :as-alias cql] + [clojure.spec.alpha :as s] + [java-time.api :as time])) + + +(s/def ::cql/now + time/offset-date-time?) + + +(s/def ::cql/timeout-eclipsed? + ifn?) + + +(s/def ::cql/timeout + time/duration?) + + +(s/def ::cql/context + (s/keys :req-un [:blaze.db/db ::cql/now ::cql/timeout-eclipsed? ::cql/timeout + ::compiler/expression-defs])) + + +(s/def ::cql/parameters + (s/map-of string? any?)) + + +(s/def ::cql/individual-context + (s/merge ::cql/context (s/keys :opt-un [::cql/parameters]))) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_spec.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_spec.clj index 5b9e9656f..69abbd465 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_spec.clj +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_spec.clj @@ -1,43 +1,26 @@ (ns blaze.fhir.operation.evaluate-measure.cql-spec (:require [blaze.db.spec] - [blaze.elm.compiler :as-alias compiler] [blaze.elm.compiler.library-spec] [blaze.elm.expression-spec] [blaze.fhir.operation.evaluate-measure.cql :as cql] + [blaze.fhir.operation.evaluate-measure.cql.spec] [blaze.fhir.operation.evaluate-measure.measure :as-alias measure] [blaze.fhir.operation.evaluate-measure.measure.spec] [blaze.fhir.spec] [clojure.spec.alpha :as s] - [cognitect.anomalies :as anom] - [java-time.api :as time])) - - -(s/def ::now - time/offset-date-time?) - - -(s/def ::parameters - (s/map-of string? any?)) - - -(s/def ::context - (s/keys :req-un [:blaze.db/db ::now ::compiler/expression-defs])) - - -(s/def ::individual-context - (s/keys :req-un [:blaze.db/db ::now ::compiler/expression-defs] :opt-un [::parameters])) + [cognitect.anomalies :as anom])) (s/fdef cql/evaluate-expression - :args (s/cat :context ::context :name string? :subject-type :fhir.resource/type + :args (s/cat :context ::cql/context :name string? :subject-type :fhir.resource/type :population-basis (s/alt :subject-based #{:boolean} :other :fhir.resource/type)) :ret (s/or :handles ::measure/handles :anomaly ::anom/anomaly)) (s/fdef cql/evaluate-individual-expression - :args (s/cat :context ::individual-context + :args (s/cat :context ::cql/individual-context :subject-handle :blaze.db/resource-handle :name string?) :ret (s/or :value any? @@ -45,7 +28,7 @@ (s/fdef cql/calc-strata - :args (s/cat :context ::context + :args (s/cat :context ::cql/context :expression-name string? :handles ::measure/handles) :ret (s/or :strata (s/map-of any? ::measure/handles) @@ -53,7 +36,7 @@ (s/fdef cql/calc-function-strata - :args (s/cat :context ::context + :args (s/cat :context ::cql/context :function-name string? :handles ::measure/handles) :ret (s/or :strata (s/map-of any? ::measure/handles) @@ -61,7 +44,7 @@ (s/fdef cql/calc-multi-component-strata - :args (s/cat :context ::context + :args (s/cat :context ::cql/context :expression-names (s/coll-of string?) :handles ::measure/handles) :ret (s/or :strata (s/map-of (s/coll-of any?) ::measure/handles) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_test.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_test.clj index b1481882b..08a37c888 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_test.clj +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_test.clj @@ -17,6 +17,7 @@ [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [cognitect.anomalies :as anom] + [java-time.api :as time] [juxt.iota :refer [given]] [taoensso.timbre :as log]) (:import @@ -109,6 +110,8 @@ (let [{:keys [expression-defs function-defs]} (compile-library node library)] {:db (d/db node) :now (now clock) + :timeout-eclipsed? (constantly false) + :timeout (time/seconds 42) :expression-defs expression-defs :function-defs function-defs})) @@ -200,7 +203,16 @@ (with-redefs [expr/eval (failing-eval "msg-222453")] (given (cql/evaluate-expression context "InInitialPopulation" "Patient" :boolean) ::anom/category := ::anom/fault - ::anom/message := "Error while evaluating the expression `InInitialPopulation`: msg-222453")))))) + ::anom/message := "Error while evaluating the expression `InInitialPopulation`: msg-222453"))))) + + (testing "timeout eclipsed" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}]]] + + (let [context (assoc (context system library-gender) :timeout-eclipsed? (constantly true))] + (given (cql/evaluate-expression context "InInitialPopulation" "Patient" :boolean) + ::anom/category := ::anom/interrupted + ::anom/message := "Timeout of 42000 millis eclipsed while evaluating."))))) (deftest evaluate-individual-expression-test diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/population/spec.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/population/spec.clj new file mode 100644 index 000000000..4fb89d24e --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/population/spec.clj @@ -0,0 +1,21 @@ +(ns blaze.fhir.operation.evaluate-measure.measure.population.spec + (:require + [blaze.db.spec] + [blaze.fhir.operation.evaluate-measure.cql :as-alias cql] + [blaze.fhir.operation.evaluate-measure.cql.spec] + [blaze.fhir.operation.evaluate-measure.measure.population :as-alias population] + [blaze.fhir.spec.spec] + [clojure.spec.alpha :as s])) + + +(s/def ::population/subject-type + :fhir.resource/type) + + +(s/def ::population/subject-handle + :blaze.db/resource-handle) + + +(s/def ::population/context + (s/merge ::cql/context + (s/keys :req-un [(or ::population/subject-type ::population/subject-handle)]))) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/population_spec.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/population_spec.clj index 2d321e948..92ba49bae 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/population_spec.clj +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/population_spec.clj @@ -1,23 +1,9 @@ (ns blaze.fhir.operation.evaluate-measure.measure.population-spec (:require - [blaze.fhir.operation.evaluate-measure.cql-spec :as cql-spec] [blaze.fhir.operation.evaluate-measure.measure.population :as population] - [blaze.fhir.spec.spec] + [blaze.fhir.operation.evaluate-measure.measure.population.spec] [clojure.spec.alpha :as s])) -(s/def ::subject-type - :fhir.resource/type) - - -(s/def ::subject-handle - :blaze.db/resource-handle) - - -(s/def ::context - (s/merge (s/keys :req-un [(or ::subject-type ::subject-handle)]) - ::cql-spec/context)) - - (s/fdef population/evaluate - :args (s/cat :context ::context :idx nat-int? :population map?)) + :args (s/cat :context ::population/context :idx nat-int? :population map?)) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier/spec.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier/spec.clj new file mode 100644 index 000000000..07dfdd78e --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier/spec.clj @@ -0,0 +1,19 @@ +(ns blaze.fhir.operation.evaluate-measure.measure.stratifier.spec + (:require + [blaze.fhir.operation.evaluate-measure.cql :as-alias cql] + [blaze.fhir.operation.evaluate-measure.cql.spec] + [blaze.fhir.operation.evaluate-measure.measure :as-alias measure] + [blaze.fhir.operation.evaluate-measure.measure.stratifier :as-alias stratifier] + [clojure.spec.alpha :as s])) + + +(s/def ::stratifier/handles + (s/coll-of ::measure/handles)) + + +(s/def ::stratifier/evaluated-populations + (s/keys :req-un [::handles])) + + +(s/def ::stratifier/context + (s/merge ::cql/context (s/keys :req-un [::measure/report-type]))) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier_spec.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier_spec.clj index 048bad110..02bd35a41 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier_spec.clj +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier_spec.clj @@ -1,25 +1,14 @@ (ns blaze.fhir.operation.evaluate-measure.measure.stratifier-spec (:require [blaze.fhir.operation.evaluate-measure.cql-spec] - [blaze.fhir.operation.evaluate-measure.measure :as-alias measure] [blaze.fhir.operation.evaluate-measure.measure.spec] [blaze.fhir.operation.evaluate-measure.measure.stratifier :as stratifier] + [blaze.fhir.operation.evaluate-measure.measure.stratifier.spec] [blaze.fhir.operation.evaluate-measure.measure.util-spec] [clojure.spec.alpha :as s])) -(s/def ::handles - (s/coll-of ::measure/handles)) - - -(s/def ::evaluated-populations - (s/keys :req-un [::handles])) - - -(s/def ::context - (s/keys :req-un [::measure/report-type])) - - (s/fdef stratifier/evaluate - :args (s/cat :context ::context :evaluated-populations ::evaluated-populations + :args (s/cat :context ::stratifier/context + :evaluated-populations ::stratifier/evaluated-populations :stratifier map?)) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier_test.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier_test.clj index 0c2781c8e..ccc104fe9 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier_test.clj +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier_test.clj @@ -12,6 +12,7 @@ [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest testing]] [cognitect.anomalies :as anom] + [java-time.api :as time] [juxt.iota :refer [given]]) (:import [java.time Clock OffsetDateTime])) @@ -179,6 +180,8 @@ (let [{:keys [expression-defs function-defs]} (compile-library node library)] {:db (d/db node) :now (now clock) + :timeout-eclipsed? (constantly false) + :timeout (time/seconds 42) :expression-defs expression-defs :function-defs function-defs})) @@ -310,12 +313,12 @@ (let [context (context system empty-library) evaluated-populations {:handles [[]]}] (given (stratifier/evaluate - (assoc context - :report-type "population" - :group-idx 1 - :stratifier-idx 2) - evaluated-populations - stratifier-with-missing-expression) + (assoc context + :report-type "population" + :group-idx 1 + :stratifier-idx 2) + evaluated-populations + stratifier-with-missing-expression) ::anom/category := ::anom/incorrect ::anom/message := "Missing expression." :fhir/issue := "required" @@ -329,7 +332,21 @@ evaluated-populations gender-stratifier) ::anom/category := ::anom/incorrect ::anom/message := "Missing expression with name `Gender`." - :expression-name := "Gender")))))) + :expression-name := "Gender")))) + + (testing "gender" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}]]] + + (let [{:keys [db] :as context} (context system library-age-gender) + evaluated-populations {:handles [(mapv handle (d/type-list db "Patient"))]}] + + (given (stratifier/evaluate (assoc context + :report-type "population" + :timeout-eclipsed? (constantly true)) + evaluated-populations gender-stratifier) + ::anom/category := ::anom/interrupted + ::anom/message := "Timeout of 42000 millis eclipsed while evaluating.")))))) (testing "two components" (testing "subject-based measure" 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 4d9d4cb09..a020ace49 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 @@ -15,6 +15,7 @@ [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest is testing]] [cognitect.anomalies :as anom] + [java-time.api :as time] [juxt.iota :refer [given]] [reitit.core :as reitit] [taoensso.timbre :as log]) @@ -257,6 +258,33 @@ :fhir/issue := "required" :fhir.issue/expression := "Measure.group[0].population[0]")))) + (testing "evaluation timeout" + (with-system-data + [{:blaze.db/keys [node] :blaze.test/keys [clock fixed-rng-fn]} system] + [[[:put {:fhir/type :fhir/Patient :id "0"}]] + [[:put {:fhir/type :fhir/Library :id "0" :url #fhir/uri"0" + :content [(library-content library-gender)]}]]] + + (let [db (d/db node) + context {:clock clock :rng-fn fixed-rng-fn + :db db :timeout (time/seconds 0) + :blaze/base-url "" ::reitit/router router} + measure-id "measure-id-132321" + measure {:fhir/type :fhir/Measure :id measure-id + :library [#fhir/canonical"0"] + :group + [{:fhir/type :fhir.Measure/group + :population + [{:fhir/type :fhir.Measure.group/population + :code (population-concept "initial-population") + :criteria (cql-expression "InInitialPopulation")}]}]} + params {:period [#system/date"2000" #system/date"2020"] + :report-type "population"}] + (given (measure/evaluate-measure context measure params) + ::anom/category := ::anom/interrupted + ::anom/message := "Timeout of 0 millis eclipsed while evaluating." + :measure-id := measure-id)))) + (testing "single subject" (doseq [subject-ref ["0" ["Patient" "0"]]] (with-system-data diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure_test.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure_test.clj index ad533d8d8..2fbb14eb5 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure_test.clj +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure_test.clj @@ -14,6 +14,7 @@ [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [integrant.core :as ig] + [java-time.api :as time] [juxt.iota :refer [given]] [reitit.core :as reitit] [taoensso.timbre :as log]) @@ -103,6 +104,32 @@ [:explain ::s/problems 3 :val] := ::invalid))) +(deftest timeout-init-test + (testing "nil config" + (given-thrown (ig/init {::evaluate-measure/timeout nil}) + :key := ::evaluate-measure/timeout + :reason := ::ig/build-failed-spec + [:explain ::s/problems 0 :pred] := `map?)) + + (testing "missing config" + (given-thrown (ig/init {::evaluate-measure/timeout {}}) + :key := ::evaluate-measure/timeout + :reason := ::ig/build-failed-spec + [:explain ::s/problems 0 :pred] := `(fn ~'[%] (contains? ~'% :millis)))) + + (testing "invalid millis" + (given-thrown (ig/init {::evaluate-measure/timeout {:millis ::invalid}}) + :key := ::evaluate-measure/timeout + :reason := ::ig/build-failed-spec + [:explain ::s/problems 0 :pred] := `nat-int? + [:explain ::s/problems 0 :val] := ::invalid)) + + (testing "init" + (with-system [{::evaluate-measure/keys [timeout]} + {::evaluate-measure/timeout {:millis 154912}}] + (is (= (time/millis 154912) timeout))))) + + (deftest executor-init-test (testing "nil config" (given-thrown (ig/init {::evaluate-measure/executor nil}) @@ -114,7 +141,7 @@ (given-thrown (ig/init {::evaluate-measure/executor {:num-threads ::invalid}}) :key := ::evaluate-measure/executor :reason := ::ig/build-failed-spec - [:explain ::s/problems 0 :pred] := `nat-int? + [:explain ::s/problems 0 :pred] := `pos-int? [:explain ::s/problems 0 :val] := ::invalid)) (testing "with default num-threads" diff --git a/resources/blaze.edn b/resources/blaze.edn index b696db97e..cefedce07 100644 --- a/resources/blaze.edn +++ b/resources/blaze.edn @@ -144,7 +144,11 @@ {:node #blaze/ref :blaze.db/node :executor #blaze/ref :blaze.fhir.operation.evaluate-measure/executor :clock #blaze/ref :blaze/clock - :rng-fn #blaze/ref :blaze/rng-fn} + :rng-fn #blaze/ref :blaze/rng-fn + :timeout #blaze/ref :blaze.fhir.operation.evaluate-measure/timeout} + + :blaze.fhir.operation.evaluate-measure/timeout + {:millis #blaze/cfg ["FHIR_OPERATION_EVALUATE_MEASURE_TIMEOUT" nat-int? 3600000]} :blaze.fhir.operation.evaluate-measure/executor {:num-threads #blaze/cfg ["FHIR_OPERATION_EVALUATE_MEASURE_THREADS" pos-int? 4]}