Repository navigation
The engine's answers depend on the server's time zone, and a dormant translator bug (CQL issue 1871) has no guard #845
Copy link
Copy link
Closed
Labels
bugSomething isn't workingSomething isn't workingcql-engineCQL / measure-evaluation engineCQL / measure-evaluation engine
Milestone
Description
Two engine behaviours that CI cannot see today: results that depend on the server's time zone, and a translator bug that stays dormant only until the CQL engine is upgraded.
1. The server's time zone changes results
- cql-exec-fhir parses a FHIR
dateTimewithcql.DateTime.parse(data). When the string has no offset, such as a date-only"2026-06-30", cql-execution'sDateTimeconstructor fills in the host's current offset (new Date().getTimezoneOffset()). - fqm-execution builds the measurement period and the execution time at offset 0.
- CQL says an offset-less value takes the evaluation request's offset. Upstream agreed in Birthdate Timezone projecttacoma/fqm-execution#259, and Use execution timezone offset for ToDateTime(String) cqframework/cql-execution#387 fixed
ToDateTime(String)but deliberately left patient-data parsing unchanged. - Measured through
calculateOfficial: on a CMS165 test case with the hypertension onset made date-only2026-06-30, the patient is out of the IPP under UTC, US Eastern and Hawaii, and in the IPP under Asia/Tokyo. - US offsets change answers at the primitive level too. A date-only
2026-06-30before2026-07-01T00:00Zis true at offset 0 and null at −5 or −10, while the mirroredafterstays true. Whether a measure flips depends on which side of a comparison the ELM puts the value. - The authored engine (
packages/measure-engine/src/cql/cql-execution-engine.ts) builds its period and "now" from offset-less strings, so it takes the host offset as well. - Nothing sets
TZ: not the image (backend-ts/Dockerfile), the deploy workflows, or the test scripts. The base image defaults to UTC and CI runners are UTC, so production is probably UTC, but the hosting platform could override it and nothing reports it. A laptop in US Eastern runs the suite at −4/−5. - The deck gate cannot catch this. The CMS decks hold no date-only clinical values (only birth dates, which are
Dates), and the gate scores identically under UTC, −5 and −10.
2. A dormant translator bug becomes live on any engine upgrade (cqframework/clinical_quality_language#1871)
- The shipped QICoreCommon 4.0.000 ELM compiles
abatementInterval's finalelse nullinto an interval with closed null bounds. Every official measure and every 2027 translation carries it. - On cql-execution 3.3.2 (ours), a resolved Condition with no abatement date has a null prevalence interval, which is correct: never prevalent.
- On cql-execution 4.0.0-beta.1, the same ELM gives
[onset, 9999-12-31], so the Condition is prevalent forever. Repairing the else branch toNullfixes 4.0. - It is used by:
- the IPP/exclusion diagnoses of CMS122, CMS125, CMS130 and CMS165;
- hospice, frailty, advanced illness and palliative care;
- CMS2's bipolar history;
- CMS137's first SUD episode.
- It is safe today: fqm-execution 1.8.5 and its master both pin cql-execution ^3.3.2. But an engine upgrade would change results with nothing failing.
Fix
- Pin UTC wherever the process starts:
ENV TZ=UTCin the image, plus the server entry and the test runner, so a laptop or a platform setting cannot change it. - Report the process's UTC offset on
/health, and warn when it is not 0. - Add a test: the CMS165 date-only onset, evaluated in a child process under a non-UTC
TZ, gives the UTC answer. Mutation-check it: the test must fail without the pin. - Add a test: the shipped
QICoreCommon.prevalenceIntervalreturns null for a resolved Condition with no abatement date, so an engine change that alters it fails CI.
Acceptance
- Both tests are in the default suite.
/healthshows offset 0 on TWH and Maui after deploy.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't workingcql-engineCQL / measure-evaluation engineCQL / measure-evaluation engine
Activity
[-]The engine's answers depend on the server's time zone, and a dormant translator bug (#1871) has no guard[/-][+]The engine's answers depend on the server's time zone, and a dormant translator bug (CQL issue 1871) has no guard[/+]fix(engine): keep the published measure engine in its caller's zone (#…
fix(engine): pin the .mjs scripts that run a measure, and find them b…