firelyteam / firely-cql-sdk Goto Github PK
View Code? Open in Web Editor NEWLicense: BSD 3-Clause "New" or "Revised" License
License: BSD 3-Clause "New" or "Revised" License
When calling the PackagerCLI, CQL files with annotations aren't having the CqlTag attribute added to the output C# files.
To Reproduce
The following CQL will not generate the CqlTag attributes in the converted C# files.
/*
* @group: 1
* @population: initial-population
*/
define "Initial population":
true
Expected behavior
The CqlTag attributes will be present for group and population attributes.
Version used:
Latest code branch available
We have implemented the operation, but the ExpressionBuilder will not recognize the Age/AgeAt ELM nodes.
Our SDK performs unit equivalency for syntactic date units by normalizing them immediately to their UCUM counterparts.
For example, CqlDate line 208
This behavior is contrary to the Author's Guide §4.2.1 Quantities.
In all cases, we should preserve the original unit and instead whenever it is required to compare units as equal or equivalent, we should invoke a not yet implemented UnitComparer that will compare UCUM and syntactic units per the specification.
Here's some CQL that could reproduce it:
define function "Is year precision"(quantity System.Quantity): quantity.unit = 'a'
define "Year equal to 'a'": "Is year precision"(1 year)
In our current implementation, Year equal to 'a'
returns true
where pursuant to the specification, it should return false
:
1 year is not equal (=) to 1 'a' (defined in UCUM as 365.25 'd'), but it is equivalent (~) to 1 'a'.
CodeSystemRef isn't implemented currently, which means this code will not work:
library RefTest version '1.0.0'
private codesystem CS: 'http://fire.ly' version '1.0.0'
define private "Code system ref": "CS"
define "in codesystem": System.Code { code: 'sample', system: 'http://fire.ly' } in CS
This is valid code that does not work.
We need a CodeSystemFacade implementation to go along with ValueSetFacade. In addition to in
checks against code systems, you can also enumerate the codes in a code system as well with a query pattern e.g.
from CS code return code
See #129
We should create some test code that:
Copy T
functions for all T
of type ClassInfo
present in a ModelInfo
class which creates a new named tuple and assigns all of its properties to obj
's value, e.g.:define function "Copy Patient"(obj FHIR.Patient):
FHIR.Patient {
id: obj.id,
birthDate: obj.birthDate,
... all other elements of Patient follow ...
}
ArgumentException
that gets thrown when a conversion required for the member binding is missing, with information about which conversion is being attempted and not availableThis will allow us to be sure that we can dynamically create FHIR model tuples in CQL.
During the development of the PackageCLI tool, bugs are discovered on new input sets and fixed, however, as part of fixing one bug in one file, another one pops up in another file. Since there is no output generated until all input files are processed successfully in-memory, it is impossible to know if the first one was fixed correctly.
Generate files as soon as possible during the processing of input files. Input files need to be ordered deterministically and such that files with no dependencies are run first. Temporary files must be created during the process, after which original output files can be overwritten.
Propose adding another argument for the PackageCLI specifying where input files are generated. Temp files must have a temp file extension, and only after everything is generated, can they replace the orginal files.
We saw a performance issue when converting to Firely SDK that took a deck from 20 minutes to 72 minutes to run. The issue was traced down to FhirDateTime objects.
The FhirTypeConverter.cs contained an FhirdateTime to CqlDateTime converter but never used.
add((M.FhirDateTime f) => f.TryToDateTime(out var dt) ?
new CqlDateTime(
dt!.Years!.Value, dt.Months,
dt.Days, dt.Hours, dt.Minutes, dt.Seconds, dt.Millis,
dt.HasOffset ? dt.Offset!.Value.Hours : null, dt.HasOffset ? dt.Offset!.Value.Minutes : null) : null);
In the FhirHelpers cql (https://fhir.org/guides/cqf/common/Library-FHIRHelpers.html) we have ToInterval
define function ToInterval(period FHIR.Period):
if period is null then
null
else
if period."start" is null then
Interval(period."start".value, period."end".value]
else
Interval[period."start".value, period."end".value]
The ".value" portion of the CQL creates lambda's like this where the value is referenced.
.If ($var8 != null) {
(.Extension<Hl7.Cql.Compiler.NullConditionalMemberExpression> {
.Block(Hl7.Fhir.Model.Period $var10) {
$var10 = $period;
.If ($var10 != null) {
$period.StartElement
} .Else {
.Default(Hl7.Fhir.Model.FhirDateTime)
}
}
}).Value
In the ExpressionBuilder.cs file there is a PropertyHelper the value is accessed as a string and eventually drills down and calls CqlOperators.ConversionFunctionName to get "ConvertStringToFhirDateTime" which parses the date into the DateTimeIso8601.TryParse. This uses regex to parse the date. After profiling this is indicative of a huge performance issue.
Need to implement a way to indicate not to drill down into the "value" property and to use the source object (FhirDateTime) so the converter is used to parse the date.
Describe the bug
Hl7.Cql.Compiler.ExpressionBuilder[0] Building expressions for FollowUpCareforChildrenPrescribedADHDMedicationADDFHIR-0.1.000 Unhandled exception. System.NotImplementedException: The method or operation is not implemented. at Hl7.Cql.Compiler.ExpressionBuilder.GetFunctionRefReturnType(FunctionRef op, IEnumerable`1 operandTypes, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 2466 at Hl7.Cql.Compiler.ExpressionBuilder.FunctionRef(FunctionRef op, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 2325 at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 584 at Hl7.Cql.Compiler.ExpressionBuilder.As(As as, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.TypeOperators.cs:line 55 at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 443 at Hl7.Cql.Compiler.ExpressionBuilder.<>c__DisplayClass115_0.<FunctionRef>b__0(Expression operand) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 2320 at System.Linq.Enumerable.SelectArrayIterator`2.ToArray() at Hl7.Cql.Compiler.ExpressionBuilder.FunctionRef(FunctionRef op, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 2319 at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 584 at Hl7.Cql.Compiler.ExpressionBuilder.IncludedIn(IncludedIn e, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.IntervalOperators.cs:line 174 at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 623 at Hl7.Cql.Compiler.ExpressionBuilder.SingleSourceQuery(Query query, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 1102 at Hl7.Cql.Compiler.ExpressionBuilder.Query(Query query, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 1029 at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 806 at Hl7.Cql.Compiler.ExpressionBuilder.Build() in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 360 at Hl7.Cql.Packaging.LibraryPackager.PackageResources(DirectoryInfo elmDirectory, DirectoryInfo cqlDirectory, DirectedGraph packageGraph, TypeResolver typeResolver, OperatorBinding operatorBinding, TypeManager typeManager, Func`2 canon, ILoggerFactory logFactory) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Packaging/LibraryPackager.cs:line 157 at Hl7.Cql.Packaging.ResourcePackager.Package(DirectoryInfo elmDir, DirectoryInfo cqlDir, Action`1 afterPackageMutator) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Packaging/ResourcePackager.cs:line 42 at Hl7.Cql.Packager.Program.Package(DirectoryInfo elmDir, DirectoryInfo cqlDir, DirectoryInfo csDir, DirectoryInfo fhirDir) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/PackagerCLI/Program.cs:line 109 at Hl7.Cql.Packager.Program.Main(String[] args) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/PackagerCLI/Program.cs:line 77
To Reproduce
Measures are committed to branch inspect-cms-measures. Executing the PackagerCli in that branch will trigger the error.
Version used:
Current develop version of the PackagerCli
Describe the bug
We tried running the demo project on a Mac laptop, this is what we found:
bin
directory included with the Maven distribution, and even if it was, we cannot use it on Mac.PackagerCLI.exe
, while this suffix does not work on Mac.Describe the bug
info: Hl7.Cql.Compiler.ExpressionBuilder[0] Building expressions for QICoreCommon-2.0.000 Unhandled exception. System.InvalidOperationException: Duplicate code detected: billing from Diagnosis Role (http://terminology.hl7.org/CodeSystem/diagnosis-role) at Hl7.Cql.Compiler.ExpressionBuilder.Build() in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 169 at Hl7.Cql.Packaging.LibraryPackager.PackageResources(DirectoryInfo elmDirectory, DirectoryInfo cqlDirectory, DirectedGraph packageGraph, TypeResolver typeResolver, OperatorBinding operatorBinding, TypeManager typeManager, Func`2 canon, ILoggerFactory logFactory) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Packaging/LibraryPackager.cs:line 157 at Hl7.Cql.Packaging.ResourcePackager.Package(DirectoryInfo elmDir, DirectoryInfo cqlDir, Action`1 afterPackageMutator) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Packaging/ResourcePackager.cs:line 42 at Hl7.Cql.Packager.Program.Package(DirectoryInfo elmDir, DirectoryInfo cqlDir, DirectoryInfo csDir, DirectoryInfo fhirDir) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/PackagerCLI/Program.cs:line 109 at Hl7.Cql.Packager.Program.Main(String[] args) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/PackagerCLI/Program.cs:line 77
To Reproduce
Measures are committed to branch inspect-cms-measures. Executing the PackagerCli in that branch will trigger the error.
Version used:
Current develop version of the PackagerCli
The packaging library should produce artifacts that conform to the profiles defined in the CRMI IG.
Describe the bug
The unit tests only worked on the build server in the US, but not on our Dutch machines. This turns out to be caused by code that does not account for localization, especially where objects are converted to strings and vice versa.
To Reproduce
The code contains constructs like the following:
Convert.ChangeType(object, Type)
, where the object can be a string, and the Type a decimal. In a non-US locale like the Netherlands, Convert.ChangeType("7.0", typeof(decimal))
will result in the decimal 70M, not 7.0MToString()
is called on objects. This will almost always fail. I.e. 18.55.ToString() = "18,55" (COMMA!) in the Netherlands.We will need to find all places in the code where this happens. Probably mostly the parse/ToString() routines of the Cql datatypes.
We don't implement uncertainty in the same way as the other two open source implementations, and this will cause testing differences that would be otherwise unresolvable.
We need to replace the use of int?
, long?
, and decimal?
as the default bound types for the ELM System.Integer
, System.Long
, and System.Decimal
types respectively to CQL types like CqlInteger
, CqlLong
, and CqlDecimal
. These new structures need to contain their primitive value, and must also contain a property that can contain the uncertainty range. When the uncertainty range is needed and is populated, the result value is considered uncertain. Uncertainty will need to be taken into account in a myriad of operators, mainly boolean comparison operators.
These types should be implicitly convertible from
their respective primitive types. It is not clear whether they should be implicitly convertible to
their primitive types, since when they are uncertain, we would be collapsing a range down to a scalar value. It may be reasonable to return null
in such cases, or throw a runtime exception. It may also be preferable to make the cast possible but explicit.
We will need comparers and type converters implemented for these new types.
The signatures of all of the ICqlOperators
interface will need to have their primitive return types changed to use these structures so that uncertainty can be propagated.
This is a significant change and will result in a large pull request.
The following CQL code behaves differently when a ValueSetFacade is passed in and when a List<Code>
is passed in.
To Reproduce
define fluent function "Code in"(coding FHIR.Coding, codes List<System.Code>):
coding in codes
The C# falls into context.Operators.CodeInList() function and it behaves differently for ValueSetFacade and List<Code>
. When ValueSetFacade is passed in, only the code & system are checked. When a List<Code>
is passed in then the code, system, version and display are checked.
Expected behavior
The function should behave the same. There needs to be a way to specify that you want the CodeInList operator to behave like Equivalent and not Equals.
When the library resource json files are generated, the current datetime is used for the "date" property in the output e.g.
This is all good for production purposes, but during development of the code generator, it would be more beneficial to fix this value so that the file stays the same as new features are added, and won't appear as hundreds of files changing in the source repo.
Propose adding an argument for packageCLI to take a datetime value for this, so that a fixed value can be used during development
Hello,
I'm not sure if this is a bug, but would like to get some feedback on what I'm seeing.
Attached is a CQL function that returns a MedicationRequest resource. After converting it into ELM and running it through the PackagerCLI, I receive the following error:
The error occurs in CQLOperatorsBinding.BindConvert(), approximately line 752.
Tracing through the code, it looks like it attempts to determine if it can perform a conversion on MedicationRequest.status prior to the thrown exception. The check returns the conversion type as 'incompatible' because there doesn't appear to be a matching type in the TypeConverter dictionary.
I've attached a CSV export of any references to 'MedicationRequest' found in the TypeConverter dictionary.
Just wanted to see if there is a way to resolve the conversion error. Are there additional type references that need to be added to the TypeConverter in CQL.Conversion? Please let me know if any additional information is needed.
Any help would be appreciated!
CQL and Elm files:
cql_and_elm_example.zip
Call stack:
call_stack.txt
When an expression contains the unary '-', our current parser only accepts literals and quantities and uses string concatenation and multiplication to execute the negation. However, negation is not limited to literals as the following is valid CQL:
library BareMinimum version '0.0.1'
using FHIR version '4.0.1'
include cq_core.FHIRHelpers version '4.0.1' called FHIRHelpers
context Patient
define x: 4
define y: -"x"
define z: -5
define q: 4 'kg'
define m: -"q"
The ELM tree should simply use the Negate
ELM node instead, and accept any expression with the correct type. See https://cql.hl7.org/04-logicalspecification.html#negate
Describe the bug
There's a strange developer usability issue with DateTimeIso8601.
In the DateTimeIso8601 class, there's an internal String constructor, we did this likely to keep people from using it. However, passing a string to the ctor is still semantically correct because there's an implicit operator override on DateIso8601 (no time) that converts string -> DateIso8601.
So, I have a datetime value as a string, I try to pass it to:
new DateTimeIso8601("2024-01-01T00:00:00")
This implicitly tries to convert it to a DateIso which fails since it has time values in the string.
To Reproduce
new DateTimeIso8601("2024-01-01T00:00:00")
Expected behavior
Instantiation of a DateTimeIso8601
Version used:
In addition to the operators with specific syntax, there is a whole list of "normal" standard functions which are represented using their own ELM nodes. We need to create a "System" library with all these functions consisting of:
Since the System library (much like the System types) is always "included" we do need to add some magic to make sure these are always in scope as well.
Bryn's compiler supports conversions from List<X>
to a List<Y>
, where X is convertible to Y. Lacking a foreach
construct, the actual conversion is carried out using a query.
The fact that this is possible, means it is also allowed to mix List<X>
and List<Y>
, as in the following List constructor:
define y: { {4}, {6.0} }
The ELM required to make this work looks like this:
"expression": {
"type": "List",
"element": [
{
"type": "Query",
"source": [
{
"type": "AliasedQuerySource",
"expression": {
"type": "List",
"element": [
{
"type": "Literal",
"locator": "11:14",
"resultTypeName": "{urn:hl7-org:elm-types:r1}Integer",
"valueType": "{urn:hl7-org:elm-types:r1}Integer",
"value": "4"
}
],
"resultTypeSpecifier": {
"type": "ListTypeSpecifier",
"elementType": {
"type": "NamedTypeSpecifier",
"name": "{urn:hl7-org:elm-types:r1}Integer"
}
},
"locator": "11:13-11:15"
},
"alias": "X"
}
],
"return": {
"type": "ReturnClause",
"expression": {
"type": "ToDecimal",
"operand": {
"type": "AliasRef",
"name": "X"
}
},
"distinct": false
}
},
{
"type": "List",
"element": [
{
"type": "Literal",
"locator": "11:19-11:21",
"resultTypeName": "{urn:hl7-org:elm-types:r1}Decimal",
"valueType": "{urn:hl7-org:elm-types:r1}Decimal",
"value": "6.0"
}
],
"resultTypeSpecifier": {
"type": "ListTypeSpecifier",
"elementType": {
"type": "NamedTypeSpecifier",
"name": "{urn:hl7-org:elm-types:r1}Decimal"
}
},
"locator": "11:18-11:22"
}
],
"resultTypeSpecifier": {
"type": "ListTypeSpecifier",
"elementType": {
"type": "ListTypeSpecifier",
"elementType": {
"type": "NamedTypeSpecifier",
"name": "{urn:hl7-org:elm-types:r1}Decimal"
}
}
},
"locator": "11:11-11:24"
},
"resultTypeSpecifier": {
"type": "ListTypeSpecifier",
"elementType": {
"type": "ListTypeSpecifier",
"elementType": {
"type": "NamedTypeSpecifier",
"name": "{urn:hl7-org:elm-types:r1}Decimal"
}
}
},
"locator": "11:1-11:24",
"name": "y",
"context": "Patient",
"accessLevel": "Public"
}
]
},
Note how the type of the outer list is determined to be List<List<Decimal>>
, and the elements of the List<Integer>
are translated to a List<Decimal>
using a query construct.
I have added a check in the ExpressionBuilder so it refuses to build ELM that has error annotations. This makes many of our unit tests fail, since the generated ELM by the java tooling contains errors.
Is this known?
I will now try to add a setting to enable/disable compiling elm with errors.
The PackageCLI generates library resources as json files (as described here). These files need to be manually updated before sending them to the Create Library endpoint on Firely Server. The url property starts with a # sign, and this needs to be replaced with the actual root url.
Look at specs and update the PackageCLI to take an additional argument for the root uri and replace the # with that value if it is provided.
We need to go over the rules in the CQL grammar one by one to make sure we
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
---> System.ArgumentException: Cannot generate a hash code for Encounter (Parameter 'x')
at Hl7.Cql.Comparers.CqlComparers.GetHashCode(Object x)
at Hl7.Cql.Runtime.CqlOperators.GetHashCode(Object x)
at Hl7.Cql.Comparers.CqlComparerBridge`1.GetHashCode(T obj)
at System.Collections.Generic.HashSet`1.AddIfNotPresent(T value, Int32& location)
at System.Collections.Generic.HashSet`1.Add(T item)
at System.Linq.Enumerable.UnionIterator`1.StoreFirst()
at System.Linq.Enumerable.UnionIterator`1.MoveNext()
at System.Linq.Enumerable.CastIterator[TResult](IEnumerable source)+MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Hl7.Cql.Runtime.CqlOperators.ListUnion[T](IEnumerable`1 left, IEnumerable`1 right)
at AdultOutpatientEncountersFHIR4_2_2_000.Qualifying_Encounters_Value()
at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
at System.Lazy`1.CreateValue()
at System.Lazy`1.get_Value()
at AdultOutpatientEncountersFHIR4_2_2_000.Qualifying_Encounters()
A common usecase is filtering Bundle.Entry entries by type of resource. Filtering on type is currently O(n), since the entries are in a list. We should build a dictionary when adding entries to make it possible to do performant selections on resource type.
Describe the bug
Method is intended to handle calendar duration calculations for differing precisions.
if (DateTime.IsLeapYear(firstDto.Year) && firstDayInYear > 60)
firstDayInYear -= 1;
else if (DateTime.IsLeapYear(secondDto.Year) && secondDayInYear > 60)
secondDayInYear -= 1;
The else
on 145 prevents day count from being updated in the event that both years are leap years. This causes downstream calculations to produce unreliable results for cases where 'low' and 'high' are both leap years.
To Reproduce
Example arguments:
low: 4/11/2008
high: 4/10/2024
precision: year/'a'
Both 2008 and 2024 are leap years.
Whole calendar period difference: 15 years (1 day short of 16 calendar years)
Initial case values:
yearDiff: 16
firstDayInYear: 102
secondDayInYear: 101
As written, only firstDayInYear is updated from 102 to 101 resulting in firstDayInYear == secondDayInYear. As a result, yearDiff is not adjusted from 16 to 15. Removing the 'else' allows both day count values to be conditionally updated and resolves the issue.
Expected behavior
Since 'low' and 'high' arguments are leap years, day counts should be adjusted down to account for leap days in cases where firstDayInYear/secondDayInYear fall after 2/29.
Description
The current implementation of Collapse does not handle cases when one interval properly includes the other.
Currently, after sorting the intervals using their low value, the intervals are combined two at a time if the intervals meet or overlap.
The resulting interval always uses the low value from the first interval and the high value from the second.
return new CqlInterval<T?>(x.low, y.high, x.lowClosed, y.highClosed);
However, in a scenario when the first interval properly includes the second this causes a bug as the first interval has both the lowest and highest value.
To Reproduce
Steps to reproduce the behavior:
Create two intervals where one is properly included in the other
define "Interval 1": Interval[22, 28]
define "Interval 2": Interval[23, 25]
Collapse the intervals
define "Result": collapse("Interval 1", "Interval 2")
Expected behavior
The expected result is an interval from the lowest value to the highest:
Interval[22,28]
The actual result is an interval with the lowest value from the first interval and the highest value from the second.
Interval[22,25]
As nicely functional as our generated code is, we call functions like:
which has side-effects. The LocalVariableDeduper does not account for this and will delete expressions with these function calls.
We should probably mark functions with side-effects with an attribute and then, in the deduper, make sure we do not remove assignments where any child of the rhs has a MethodInvocation for a method that carries that attribute.
The Library resources produced by the PackagerCLI are not complete, we need to produce resources that are conformant to the CRMI profiles, like e.g. the profiles in the MCT (https://github.com/cqframework/mct/blob/main/java/src/main/resources/configuration/measures/measures-bundle.json).
The CQL spec never explicitly states that the arguments to is/as/cast as
are exempt from automatic promotion/demotion, although it probably makes sense (and would be consistent with the behaviour of, say, the C# as
and is
, which also will not do implicit conversions).
Trying to reverse engineer the Java engines behaviour I noticed this:
// This works
define "y": {true} is Boolean
// This does not
define "z": {true} as Boolean
which leads me to think that in the Java engine promotion does apply to the is
operator, but not to the as
, which seems inconsistent to me. ("So some x is of type Y but I cannot actually cast it to Y?").
Describe the bug
Take the following CQL expressions:
define "Qualifying Encounters":
(
[Encounter: "Office Visit"]
union [Encounter: "Annual Wellness Visit"]
union [Encounter: "Preventive Care Services - Established Office Visit, 18 and Up"]
union [Encounter: "Preventive Care Services-Initial Office Visit, 18 and Up"]
union [Encounter: "Home Healthcare Services"]
) ValidEncounter
where ValidEncounter.status = 'finished'
and Global."Normalize Interval"(ValidEncounter.period) during "Measurement Period"
The SDK will produce a library resource based on this where "Qualifying Encounters" is mentioned as a parameter. However according to it should show up as a data requirement in the Library resource. Additionally, the "type" element in the "parameter" element of the Library is set to "List". Make sure to use the correct resource type.
Expected behavior
Only parameters declared as parameters should be in the parameters section of the Library.
Version used:
1.0.0-rc2
Architecture testing is a crucial practice in software development that ensures a solid and robust foundation for building applications.
Architecture rules maintain design quality and consistency in a growing code base, especially where a team is distributed.
Suggest [https://www.infoworld.com/article/3656703/how-to-enforce-architecture-rules-in-csharp.html](introducing architecture testing) to the solution
We support context Patient
by creating a method inside the measure class that returns a single Patient.
But we don't support context Population
meaningfully. We don't add attributes to methods to indicate that they are context Population
methods. We don't pass the type of context to the IDataSource
interface to help implementers to understand whether they need to apply a Patient filter or not.
Although in many places CQL allows substitutability of types, this is not the case for return types of functions and their bodies. E.g.
define function x() returns Decimal: 4
is not allowed, only strict subtyping:
define function y() returns DomainResource: Patient { ... }
Given the flexibility in other places of the CQL language, we think the first case should be allowed too.
Describe the bug
Hl7.Cql.Compiler.ExpressionBuilder[0] Building expressions for ProstateCaAvoidanceBoneScanOveruseFHIR-0.2.000 warn: Hl7.Cql.Compiler.ExpressionBuilder[0] ProstateCaAvoidanceBoneScanOveruseFHIR-0.2.000 line 100:11-100:38: Potentially unsafe cast from Object to type CqlConcept Unhandled exception. System.ArgumentException: Cannot convert System.Nullable`1[Hl7.Fhir.Model.ObservationStatus] to Hl7.Fhir.Model.Code`1[Hl7.Fhir.Model.ObservationStatus] (Parameter 'source') at Hl7.Cql.Compiler.CqlOperatorsBinding.BindConvert(MemberExpression operators, Expression source, Expression typeExpression) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/CqlOperatorsBinding.cs:line 752 at Hl7.Cql.Compiler.CqlOperatorsBinding.Bind(CqlOperator operator, Expression runtimeContext, Expression[] parameters) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/CqlOperatorsBinding.cs:line 79 at Hl7.Cql.Compiler.ExpressionBuilder.ChangeType(Expression input, Type outputType, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.TypeOperators.cs:line 254 at Hl7.Cql.Compiler.ExpressionBuilder.PropertyHelper(Expression source, String path, Type expectedType, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 2312 at Hl7.Cql.Compiler.ExpressionBuilder.Property(Property op, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 2264 at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 800 at Hl7.Cql.Compiler.ExpressionBuilder.In(In e, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.IntervalOperators.cs:line 117 at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 644 at Hl7.Cql.Compiler.ExpressionBuilder.BinaryOperator(CqlOperator operator, BinaryExpression be, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 950 at Hl7.Cql.Compiler.ExpressionBuilder.And(And e, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.LogicalOperators.cs:line 24 at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 440 at Hl7.Cql.Compiler.ExpressionBuilder.WithToSelectManyBody(String outerScope, Type outerElementType, RelationshipClause with, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 2056 at Hl7.Cql.Compiler.ExpressionBuilder.SingleSourceQuery(Query query, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 1061 at Hl7.Cql.Compiler.ExpressionBuilder.Query(Query query, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 1029 at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 806 at Hl7.Cql.Compiler.ExpressionBuilder.Last(Last e, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.ListOperators.cs:line 48 at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 659 at Hl7.Cql.Compiler.ExpressionBuilder.SingleSourceQuery(Query query, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 1043 at Hl7.Cql.Compiler.ExpressionBuilder.Query(Query query, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 1029 at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 806 at Hl7.Cql.Compiler.ExpressionBuilder.Build() in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Compiler/ExpressionBuilder.cs:line 360 at Hl7.Cql.Packaging.LibraryPackager.PackageResources(DirectoryInfo elmDirectory, DirectoryInfo cqlDirectory, DirectedGraph packageGraph, TypeResolver typeResolver, OperatorBinding operatorBinding, TypeManager typeManager, Func`2 canon, ILoggerFactory logFactory) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Packaging/LibraryPackager.cs:line 157 at Hl7.Cql.Packaging.ResourcePackager.Package(DirectoryInfo elmDir, DirectoryInfo cqlDir, Action`1 afterPackageMutator) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/Cql.Packaging/ResourcePackager.cs:line 42 at Hl7.Cql.Packager.Program.Package(DirectoryInfo elmDir, DirectoryInfo cqlDir, DirectoryInfo csDir, DirectoryInfo fhirDir) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/PackagerCLI/Program.cs:line 109 at Hl7.Cql.Packager.Program.Main(String[] args) in /Users/alex/Dev/Firely/SDK/firely-cql-sdk/Cql/PackagerCLI/Program.cs:line 77
To Reproduce
Measures are committed to branch inspect-cms-measures. Executing the PackagerCli in that branch will trigger the error.
Version used:
Current develop version of the PackagerCli
In this wiki page https://github.com/FirelyTeam/firely-cql-sdk/wiki/Scopes-and-Resolution, I described the behaviour of Bryn's compiler. It is useful to turn this text into a set of tests that we can use to verify our own behaviour.
Currently, we can define exactly one function with a given name, as the key in the symbol table is the name of the function. To fix this, let's follow .NET's solution, where functions are part of a method group, so when we add a function to the symbol table of a library, we introduce a single MethodGroup with that name, which contains all overloaded functions with the same name.
When invoking a function, we resolve the method group, then use the existing InvocationBuilder
to select the best match amongst the overloads, based on score. This will also make sure we cast all arguments to the required types of the function's parameters (which we also currently do not yet do).
To make this work, we define our own MethodGroup(Def), which implements IDefinitionElement
, so it can be added to a symboltable. The Add
of the symbol table will create the MethodGroup when the first function in the group gets added, and will add overloads to that group in subsequent adds.
Describe the bug
According to Getting started with the Firely SDK CQL repository, building the Demo project is all that is necessary to get going. In particular, a full install of Maven should be present in the repository, and there is a build task called Download CQL to ELM CLI via Maven.
However, the bin
folder in the path Demo\Cql\Build\apache-maven-3.8.8\bin\mvn
of the build task is not present in the repo. So upon attempting to build, the following error occurs:
To Reproduce
Confirm the Build\apache-maven-3.8.8\bin
folder is missing in the repo.
Expected behavior
Add the bin
folder to the repo so that a build error does not occur.
Alternatively, list Java (the build task already implicitly assumes java is installed with a correct %PATH%
) and Maven as external prerequisites and change the build task to use mvn
relying on the %PATH%
having been set in the user's operating system. In other words,
<Exec Command="Build\apache-maven-3.8.8\bin\mvn -f Build\pom.xml dependency:copy-dependencies"></Exec>
to <Exec Command="mvn -f Build\pom.xml dependency:copy-dependencies"></Exec>
Demo\Cql\Build\apache-maven-3.8.8
folder and rely on the user to install Maven correctly with %PATH%
already set.Currently services leak out their dependencies via properties e.g ExpressionBuildsr leaks out TypeManager and TypeManager leaks out TypeResolver.
Use dependencies via DI and keep them private.
The solution is over two parts:
We should make the ModelInfo an abstract property on TypeResolver and require all implementations to provide one. ExpressionBuilder can refer to it through its TypeResolver. Theoretically, we need 1 TypeResolver per model declared in using statements in the provided library, e.g. to handle
using FHIR version '4.0.1'
using QICore version '1.0.0'
A single TypeResolver won't be enough for this.
When we've done this, we should correct the current implementation of the Binder, which now has a hard-coded reference on FHIR Model 4.0.x. (at https://github.com/FirelyTeam/firely-cql-sdk/pull/44/files/cedac43551a3d3603d6dc62f33d3e8b2413e7f3f#diff-b08896773220dcd7ca3e029d3c661ef480a7dd3730ef4fe8e467048f75afe32a, line 2043 more or less).
In the InvocationBuilder
, I have made placeholders where we still need to add support for some implicit casts (most notably Choice<X,Y> -> X), including links to the spec where the cast is described.
We need to go over these and complete the casting logic.
Getting ArgumentException while building statement for definition 'Initial Population 'in library 'HFBetaBlockerTherapyforLVSDFHIR-1.3.000'.
System.ArgumentException: Cannot resolve type for expression
at Hl7.Cql.Compiler.TypeManager.TypeFor(Element element, ExpressionBuilderContext context, Boolean throwIfNotFound)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\TypeManager.cs:line 130
at Hl7.Cql.Compiler.TypeManager.TypeFor(Element element, ExpressionBuilderContext context, Boolean throwIfNotFound)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\TypeManager.cs:line 104
at Hl7.Cql.Compiler.ExpressionBuilder.Property(Property op, ExpressionBuilderContext ctx)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.cs:line 2266
at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.cs:line 803
at Hl7.Cql.Compiler.ExpressionBuilder.Property(Property op, ExpressionBuilderContext ctx)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.cs:line 2247
at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.cs:line 803
at Hl7.Cql.Compiler.ExpressionBuilder.CalculateAgeAt(CalculateAgeAt e, ExpressionBuilderContext ctx)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.ClinicalOperators.cs:line 34
at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.cs:line 461
at Hl7.Cql.Compiler.ExpressionBuilder.BinaryOperator(CqlOperator operator, BinaryExpression be, ExpressionBuilderContext ctx)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.cs:line 952
at Hl7.Cql.Compiler.ExpressionBuilder.GreaterOrEqual(GreaterOrEqual e, ExpressionBuilderContext ctx)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.ComparisonOperators.cs:line 123
at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.cs:line 608
at Hl7.Cql.Compiler.ExpressionBuilder.BinaryOperator(CqlOperator operator, BinaryExpression be, ExpressionBuilderContext ctx)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.cs:line 952
at Hl7.Cql.Compiler.ExpressionBuilder.And(And e, ExpressionBuilderContext ctx)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.LogicalOperators.cs:line 24
at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.cs:line 443
at Hl7.Cql.Compiler.ExpressionBuilder.BinaryOperator(CqlOperator operator, BinaryExpression be, ExpressionBuilderContext ctx)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.cs:line 952
at Hl7.Cql.Compiler.ExpressionBuilder.And(And e, ExpressionBuilderContext ctx)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.LogicalOperators.cs:line 24
at Hl7.Cql.Compiler.ExpressionBuilder.TranslateExpression(Element op, ExpressionBuilderContext ctx)
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.cs:line 443
at Hl7.Cql.Compiler.ExpressionBuilder.Build()
in C:\Dev\firely-cql-sdk\Cql\Cql.Compiler\ExpressionBuilder.cs:line 363
at Hl7.Cql.Packaging.LibraryPackager.PackageResources(DirectoryInfo elmDirectory, DirectoryInfo cqlDirectory, DirectedGraph packageGraph, TypeResolver typeResolver, OperatorBinding operatorBinding, TypeManager typeManager, Func`2 canon, ILoggerFactory logFactory)
in C:\Dev\firely-cql-sdk\Cql\Cql.Packaging\LibraryPackager.cs:line 161
at Hl7.Cql.Packaging.ResourcePackager.PackageCore(DirectoryInfo elmDir, DirectoryInfo cqlDir, Action`1 afterPackageMutator, String resourceCanonicalRootUrl)
in C:\Dev\firely-cql-sdk\Cql\Cql.Packaging\ResourcePackager.cs:line 53
at Hl7.Cql.Packaging.ResourcePackager.Package(PackageArgs args)
in C:\Dev\firely-cql-sdk\Cql\Cql.Packaging\ResourcePackager.cs:line 42
at Hl7.Cql.Packager.Program.Package(DirectoryInfo elmDir, DirectoryInfo cqlDir, DirectoryInfo csDir, DirectoryInfo fhirDir, String resourceCanonicalRootUrl)
in C:\Dev\firely-cql-sdk\Cql\PackagerCLI\Program.cs:line 133
at Hl7.Cql.Packager.Program.Main(String[] args)
in C:\Dev\firely-cql-sdk\Cql\PackagerCLI\Program.cs:line 101
To Reproduce
Measures are committed to branch inspect-cms-measures
. Executing the PackagerCli in that branch will trigger the error.
Version used:
Current develop version of the PackagerCli
There are a number of locations throughout the code that refer to NCQA, or are using a pattern that is specific to an implementation requirement of NCQA products. These should be removed and alternatives created.
This issue supercedes #97.
When converting the following CQL to C#, the Cql.Packaging fails on the crosswalk due to the casing being incorrect.
Steps to reproduce the behavior:
Return the id element of any resource so the result would be a List
define "Claim Ids":
[Claim] c
return c.id
Generated response ELM is
"resultTypeSpecifier" : {
"type" : "ListTypeSpecifier",
"elementType" : {
"type" : "NamedTypeSpecifier",
"name" : "{http://hl7.org/fhir}id"
}
}
Enum contains "Id" and not "id" so it is not found when parsing.
It is legal to do
parameter X Type decimal default 4
In which case the literal is cast to a decimal.
Currently, SystemLibrary is a static class containing the definition of "all" CQL functions.
It is preferable to turn this into an instance, so it can be injected as a singleton in services that need it.
The Visitor for the Retrieve
takes both the contextIdentifier
(which should be a resolvable identifier) and codePath
at face value, while the compiler should verify the validity of both.
Describe the bug
When using a default value for a period, we will write an extension "https://ncqa.org/fhir/StructureDefinition/ext-parameter.cql-default-value to the Library.parameter" containing a period. Unfortunately, the timestamp for the parameter is formatted incorrectly: it does not have a "Z" suffix:
{
"url": "https://ncqa.org/fhir/StructureDefinition/ext-parameter.cql-default-value",
"valuePeriod": {
"start": "2019-01-01T00:00:00.000",
"end": "2019-12-31T23:59:59.999Z"
}
},
If the user made an error in such a way that an overloaded methodgroup either has no exact match or an ambiguous match, we can improve our error reporting by typing the erroneous node with a (subtype) of ChoiceType, including all possible types found in the return types of the highest scoring selected overloads.
E.g. if we have a function f<T>(T,T)
:T, and the CQL text reads f('string', 4)
, T
cannot be determined. In this case we could type the node with all possible types, i.e. give it a type of Choice(int,string)
, this way, we would correctly deduce the type in a surrounding statement, e.g. f('hello', 4) + 5
.
The same is true for a group of overloads of course.
Our current invocationBuilder is almost ready to handle this situation, simply by taking the possible generic parameter assignments and/or return types of the highest scoring alternatives.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.