tricc-oo 1.5.13__py3-none-any.whl → 1.6.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tests/build.py +20 -28
- tests/test_build.py +260 -0
- tests/test_cql.py +48 -109
- tests/to_ocl.py +15 -17
- tricc_oo/__init__.py +0 -6
- tricc_oo/converters/codesystem_to_ocl.py +51 -40
- tricc_oo/converters/cql/cqlLexer.py +1 -0
- tricc_oo/converters/cql/cqlListener.py +1 -0
- tricc_oo/converters/cql/cqlParser.py +1 -0
- tricc_oo/converters/cql/cqlVisitor.py +1 -0
- tricc_oo/converters/cql_to_operation.py +129 -123
- tricc_oo/converters/datadictionnary.py +45 -54
- tricc_oo/converters/drawio_type_map.py +146 -65
- tricc_oo/converters/tricc_to_xls_form.py +58 -28
- tricc_oo/converters/utils.py +4 -4
- tricc_oo/converters/xml_to_tricc.py +296 -235
- tricc_oo/models/__init__.py +2 -1
- tricc_oo/models/base.py +333 -305
- tricc_oo/models/calculate.py +66 -51
- tricc_oo/models/lang.py +26 -27
- tricc_oo/models/ocl.py +146 -161
- tricc_oo/models/ordered_set.py +15 -19
- tricc_oo/models/tricc.py +149 -89
- tricc_oo/parsers/xml.py +15 -30
- tricc_oo/serializers/planuml.py +4 -6
- tricc_oo/serializers/xls_form.py +110 -153
- tricc_oo/strategies/input/base_input_strategy.py +28 -32
- tricc_oo/strategies/input/drawio.py +59 -71
- tricc_oo/strategies/output/base_output_strategy.py +151 -65
- tricc_oo/strategies/output/dhis2_form.py +908 -0
- tricc_oo/strategies/output/fhir_form.py +377 -0
- tricc_oo/strategies/output/html_form.py +224 -0
- tricc_oo/strategies/output/openmrs_form.py +694 -0
- tricc_oo/strategies/output/spice.py +106 -127
- tricc_oo/strategies/output/xls_form.py +322 -244
- tricc_oo/strategies/output/xlsform_cdss.py +627 -142
- tricc_oo/strategies/output/xlsform_cht.py +252 -125
- tricc_oo/strategies/output/xlsform_cht_hf.py +13 -24
- tricc_oo/visitors/tricc.py +1424 -1033
- tricc_oo/visitors/utils.py +16 -16
- tricc_oo/visitors/xform_pd.py +91 -89
- {tricc_oo-1.5.13.dist-info → tricc_oo-1.6.8.dist-info}/METADATA +128 -84
- tricc_oo-1.6.8.dist-info/RECORD +52 -0
- tricc_oo-1.6.8.dist-info/licenses/LICENSE +373 -0
- {tricc_oo-1.5.13.dist-info → tricc_oo-1.6.8.dist-info}/top_level.txt +0 -0
- tricc_oo-1.5.13.dist-info/RECORD +0 -46
- {tricc_oo-1.5.13.dist-info → tricc_oo-1.6.8.dist-info}/WHEEL +0 -0
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
from antlr4 import
|
|
1
|
+
from antlr4.error.ErrorListener import ErrorListener
|
|
2
|
+
from antlr4 import CommonTokenStream, InputStream
|
|
2
3
|
from tricc_oo.converters.cql.cqlLexer import cqlLexer
|
|
3
4
|
from tricc_oo.converters.cql.cqlParser import cqlParser
|
|
4
5
|
from tricc_oo.converters.cql.cqlVisitor import cqlVisitor
|
|
5
6
|
from tricc_oo.converters.utils import clean_name
|
|
6
|
-
from tricc_oo.models.base import
|
|
7
|
+
from tricc_oo.models.base import (
|
|
8
|
+
TriccOperator,
|
|
9
|
+
TriccOperation,
|
|
10
|
+
TriccStatic,
|
|
11
|
+
TriccReference,
|
|
12
|
+
not_clean,
|
|
13
|
+
or_join,
|
|
14
|
+
and_join,
|
|
15
|
+
string_join,
|
|
16
|
+
)
|
|
7
17
|
import logging
|
|
8
18
|
|
|
9
19
|
logger = logging.getLogger("default")
|
|
@@ -14,15 +24,19 @@ NUMBER = 2
|
|
|
14
24
|
ANY = 3
|
|
15
25
|
|
|
16
26
|
FUNCTION_MAP = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
"AgeInYears": TriccOperator.AGE_YEAR,
|
|
28
|
+
"AgeInMonths": TriccOperator.AGE_MONTH,
|
|
29
|
+
"AgeInDays": TriccOperator.AGE_DAY,
|
|
30
|
+
"Coalesce": TriccOperator.COALESCE,
|
|
31
|
+
"Concatenate": TriccOperator.CONCATENATE,
|
|
32
|
+
"Izscore": TriccOperator.IZSCORE,
|
|
33
|
+
"Zscore": TriccOperator.ZSCORE,
|
|
34
|
+
"Round": TriccOperator.ROUND,
|
|
35
|
+
"Integer": TriccOperator.CAST_INTEGER,
|
|
36
|
+
"DrugDosage": TriccOperator.DRUG_DOSAGE,
|
|
37
|
+
"HasQualifier": TriccOperator.HAS_QUALIFIER,
|
|
38
|
+
"DateTimeToDecimal": TriccOperator.DATETIME_TO_DECIMAL,
|
|
39
|
+
"Count": TriccOperator.COUNT,
|
|
26
40
|
}
|
|
27
41
|
# TODO
|
|
28
42
|
# Min
|
|
@@ -30,41 +44,44 @@ FUNCTION_MAP = {
|
|
|
30
44
|
# Round
|
|
31
45
|
# this need to be done by contribution to DMN
|
|
32
46
|
|
|
47
|
+
|
|
33
48
|
class cqlToXlsFormVisitor(cqlVisitor):
|
|
34
49
|
def __init__(self):
|
|
35
50
|
self.xlsform_rows = []
|
|
36
|
-
self.errors= []
|
|
37
|
-
|
|
51
|
+
self.errors = []
|
|
52
|
+
|
|
38
53
|
def resolve_scv(self, arg):
|
|
39
|
-
|
|
40
|
-
# TODO
|
|
54
|
+
|
|
55
|
+
# TODO
|
|
41
56
|
# look for the system, if not found fallback on default system
|
|
42
57
|
# look for the code in the system
|
|
43
58
|
# if no code or not found return None
|
|
44
59
|
if arg.startswith('"') and arg.endswith('"'):
|
|
45
60
|
return TriccReference(arg[1:-1])
|
|
46
|
-
elif arg.lower() in [
|
|
47
|
-
return TriccStatic(arg.lower() ==
|
|
48
|
-
elif arg !=
|
|
61
|
+
elif arg.lower() in ["true", "false"]:
|
|
62
|
+
return TriccStatic(arg.lower() == "true")
|
|
63
|
+
elif arg != "runner":
|
|
49
64
|
self.errors.append(f"'{arg}' will be poccessed as reference ")
|
|
50
65
|
return TriccReference(arg)
|
|
51
|
-
|
|
66
|
+
|
|
52
67
|
else:
|
|
53
|
-
return
|
|
54
|
-
|
|
68
|
+
return "runner"
|
|
69
|
+
|
|
55
70
|
def translate(self, arg, type=ANY):
|
|
56
71
|
return self.resolve_scv(arg) or str(arg)
|
|
57
|
-
|
|
72
|
+
|
|
58
73
|
def visitExpressionDefinition(self, ctx):
|
|
59
74
|
identifier = ctx.identifier().getText()
|
|
60
75
|
expression = self.visit(ctx.expression())
|
|
61
|
-
self.xlsform_rows.append(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
self.xlsform_rows.append(
|
|
77
|
+
{
|
|
78
|
+
"type": "calculate",
|
|
79
|
+
"name": clean_name(identifier[1:-1].lower()),
|
|
80
|
+
"calculation": expression,
|
|
81
|
+
}
|
|
82
|
+
)
|
|
66
83
|
return expression
|
|
67
|
-
|
|
84
|
+
|
|
68
85
|
def visitIdentifier(self, arg):
|
|
69
86
|
return self.translate(arg.getText(), 1)
|
|
70
87
|
|
|
@@ -77,27 +94,25 @@ class cqlToXlsFormVisitor(cqlVisitor):
|
|
|
77
94
|
return aggregate
|
|
78
95
|
else:
|
|
79
96
|
aggregate = aggregate if isinstance(aggregate, list) else [aggregate]
|
|
80
|
-
return [
|
|
81
|
-
*aggregate,
|
|
82
|
-
nextResult
|
|
83
|
-
]
|
|
97
|
+
return [*aggregate, nextResult]
|
|
84
98
|
else:
|
|
85
99
|
return nextResult
|
|
86
100
|
|
|
87
101
|
def visitExpression(self, ctx):
|
|
88
102
|
return self.visitChildren(ctx)
|
|
103
|
+
|
|
89
104
|
def visitThisInvocation(self, ctx):
|
|
90
|
-
return
|
|
91
|
-
|
|
105
|
+
return "$this"
|
|
106
|
+
|
|
92
107
|
def visitBooleanLiteral(self, ctx):
|
|
93
|
-
literal =
|
|
94
|
-
if literal ==
|
|
108
|
+
literal = ctx.getChild(0).getText()
|
|
109
|
+
if literal == "true":
|
|
95
110
|
return TriccStatic(True)
|
|
96
|
-
elif literal ==
|
|
111
|
+
elif literal == "false":
|
|
97
112
|
return TriccStatic(False)
|
|
98
113
|
else:
|
|
99
114
|
return None
|
|
100
|
-
|
|
115
|
+
|
|
101
116
|
def visitFunctionInvocation(self, ctx, operator=TriccOperator.NATIVE):
|
|
102
117
|
if ctx.getChildCount() == 1:
|
|
103
118
|
return self.visitFunctionInvocation(ctx.getChild(0))
|
|
@@ -113,54 +128,45 @@ class cqlToXlsFormVisitor(cqlVisitor):
|
|
|
113
128
|
args = ctx.paramList()
|
|
114
129
|
if args:
|
|
115
130
|
op.reference += [self.visit(arg) for arg in args.expression() if arg]
|
|
116
|
-
|
|
117
131
|
|
|
118
132
|
return op
|
|
119
|
-
|
|
133
|
+
|
|
120
134
|
def __std_function(self, ctx, operator=TriccOperator.NATIVE):
|
|
121
135
|
args = ctx.expressions
|
|
122
136
|
if args:
|
|
123
137
|
args = [self.visit(arg) for arg in ctx.expression() if arg]
|
|
124
138
|
op = TriccOperation(operator)
|
|
125
|
-
op.reference = [
|
|
126
|
-
*args
|
|
127
|
-
]
|
|
139
|
+
op.reference = [*args]
|
|
128
140
|
|
|
129
141
|
def visitParenthesizedTerm(self, ctx):
|
|
130
142
|
return TriccOperation(TriccOperator.PARENTHESIS, [self.visitChildren(ctx)])
|
|
131
|
-
|
|
143
|
+
|
|
132
144
|
def visitMemberInvocation(self, ctx):
|
|
133
145
|
return self.visitChildren(ctx)
|
|
134
146
|
|
|
135
147
|
def visitMembershipExpression(self, ctx):
|
|
136
148
|
function_name = ctx.getChild(1).getText()
|
|
137
149
|
return self._get_membership_expression(ctx, function_name)
|
|
138
|
-
|
|
150
|
+
|
|
139
151
|
def _get_membership_expression(self, ctx, function_name):
|
|
140
152
|
left = self.visit(ctx.expression(0))
|
|
141
153
|
right = self.visit(ctx.expression(1))
|
|
142
|
-
if function_name ==
|
|
154
|
+
if function_name == "in":
|
|
143
155
|
op = TriccOperation(TriccOperator.SELECTED)
|
|
144
|
-
op.reference = [
|
|
145
|
-
|
|
146
|
-
left
|
|
147
|
-
]
|
|
148
|
-
elif function_name == 'contains':
|
|
156
|
+
op.reference = [right, left]
|
|
157
|
+
elif function_name == "contains":
|
|
149
158
|
op = TriccOperation(TriccOperator.CONTAINS)
|
|
150
|
-
op.reference = [
|
|
151
|
-
left,
|
|
152
|
-
right
|
|
153
|
-
]
|
|
159
|
+
op.reference = [left, right]
|
|
154
160
|
return op
|
|
155
|
-
|
|
161
|
+
|
|
156
162
|
def visitNegateMembershipExpression(self, ctx):
|
|
157
163
|
function_name = ctx.getChild(2).getText()
|
|
158
164
|
return not_clean(self._get_membership_expression(ctx, function_name))
|
|
159
|
-
|
|
165
|
+
|
|
160
166
|
def visitInvocationExpressionTerm(self, ctx):
|
|
161
167
|
result = super().visitInvocationExpressionTerm(ctx)
|
|
162
168
|
if isinstance(result, list) and all(isinstance(x, TriccStatic) for x in result):
|
|
163
|
-
value =
|
|
169
|
+
value = ".".join([x.value for x in result])
|
|
164
170
|
logger.warning(f"guessed reference for '{value}'")
|
|
165
171
|
return TriccReference(value)
|
|
166
172
|
return result
|
|
@@ -177,19 +183,19 @@ class cqlToXlsFormVisitor(cqlVisitor):
|
|
|
177
183
|
expr = self.visit(ctx.expression())
|
|
178
184
|
params = [c.getText() for c in list(ctx.getChildren())[2:]]
|
|
179
185
|
op = TriccOperation(
|
|
180
|
-
operator
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
186
|
+
operator={
|
|
187
|
+
"true": TriccOperator.ISTRUE,
|
|
188
|
+
"false": TriccOperator.ISFALSE,
|
|
189
|
+
"null": TriccOperator.ISNULL,
|
|
184
190
|
}[params[-1]],
|
|
185
|
-
reference
|
|
191
|
+
reference=[expr],
|
|
186
192
|
)
|
|
187
|
-
|
|
188
|
-
if params[0] ==
|
|
193
|
+
|
|
194
|
+
if params[0] == "not":
|
|
189
195
|
if isinstance(op, TriccStatic) and isinstance(op.value, str):
|
|
190
196
|
logger.warning(f"not operator on a string {op.value}")
|
|
191
197
|
op = not_clean(op)
|
|
192
|
-
|
|
198
|
+
|
|
193
199
|
return op
|
|
194
200
|
|
|
195
201
|
def visitExistenceExpression(self, ctx):
|
|
@@ -203,18 +209,22 @@ class cqlToXlsFormVisitor(cqlVisitor):
|
|
|
203
209
|
|
|
204
210
|
def visitOrExpression(self, ctx):
|
|
205
211
|
return self.__std_operator(TriccOperator.OR, ctx)
|
|
206
|
-
|
|
212
|
+
|
|
207
213
|
def __std_operator(self, operator, ctx):
|
|
208
|
-
if hasattr(ctx,
|
|
214
|
+
if hasattr(ctx, "expression"):
|
|
209
215
|
left = self.visit(ctx.expression(0))
|
|
210
216
|
right = self.visit(ctx.expression(1))
|
|
211
|
-
elif hasattr(ctx,
|
|
217
|
+
elif hasattr(ctx, "expressionTerm"):
|
|
212
218
|
left = self.visit(ctx.expressionTerm(0))
|
|
213
219
|
right = self.visit(ctx.expressionTerm(1))
|
|
214
|
-
if
|
|
220
|
+
if operator == TriccOperator.AND:
|
|
215
221
|
return and_join([left, right])
|
|
216
|
-
elif
|
|
217
|
-
return or_join([left, right])
|
|
222
|
+
elif operator == TriccOperator.OR:
|
|
223
|
+
return or_join([left, right])
|
|
224
|
+
elif operator == TriccOperator.CONCATENATE:
|
|
225
|
+
left = "" if left is None else left
|
|
226
|
+
right = "" if right is None else right
|
|
227
|
+
return string_join(left, right)
|
|
218
228
|
else:
|
|
219
229
|
op = TriccOperation(operator, [left, right])
|
|
220
230
|
return op
|
|
@@ -244,96 +254,91 @@ class cqlToXlsFormVisitor(cqlVisitor):
|
|
|
244
254
|
right = self.visit(ctx.expression(1))
|
|
245
255
|
op_text = ctx.getChild(1).getText()
|
|
246
256
|
op_map = {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
257
|
+
"<": TriccOperator.LESS,
|
|
258
|
+
"<=": TriccOperator.LESS_OR_EQUAL,
|
|
259
|
+
">": TriccOperator.MORE,
|
|
260
|
+
">=": TriccOperator.MORE_OR_EQUAL,
|
|
261
|
+
"=": TriccOperator.EQUAL,
|
|
262
|
+
"!=": TriccOperator.NOTEQUAL,
|
|
253
263
|
}
|
|
254
264
|
op = TriccOperation(op_map[op_text])
|
|
255
265
|
op.reference = [left, right]
|
|
256
266
|
return op
|
|
257
267
|
|
|
258
268
|
def visitInvocationExpression(self, ctx):
|
|
259
|
-
raise NotImplementedError(
|
|
260
|
-
|
|
269
|
+
raise NotImplementedError("Invocation not supported")
|
|
270
|
+
|
|
261
271
|
def visitIndexerExpression(self, ctx):
|
|
262
|
-
raise NotImplementedError(
|
|
263
|
-
|
|
272
|
+
raise NotImplementedError("Indexer not supported")
|
|
273
|
+
|
|
264
274
|
def visitCastExpression(self, ctx):
|
|
265
275
|
# TODO
|
|
266
|
-
raise NotImplementedError(
|
|
267
|
-
|
|
276
|
+
raise NotImplementedError("Cast not supported")
|
|
277
|
+
|
|
268
278
|
def visitPolarityExpressionTerm(self, ctx):
|
|
269
|
-
if ctx.getChild(0).getText() ==
|
|
279
|
+
if ctx.getChild(0).getText() == "-":
|
|
270
280
|
return TriccOperation(TriccOperator.MINUS, [self.visit(ctx.getChild(1))])
|
|
271
|
-
|
|
281
|
+
|
|
272
282
|
def visitMultiplicationExpressionTerm(self, ctx):
|
|
273
283
|
op_text = ctx.getChild(1).getText()
|
|
274
284
|
op_map = {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
285
|
+
"*": TriccOperator.MULTIPLIED,
|
|
286
|
+
"div": TriccOperator.DIVIDED,
|
|
287
|
+
"/": TriccOperator.DIVIDED,
|
|
288
|
+
"%": TriccOperator.MODULO,
|
|
289
|
+
"mod": TriccOperator.MODULO,
|
|
280
290
|
}
|
|
281
291
|
return self.__std_operator(op_map.get(op_text), ctx)
|
|
282
|
-
|
|
292
|
+
|
|
283
293
|
def visitAdditionExpressionTerm(self, ctx):
|
|
284
294
|
op_text = ctx.getChild(1).getText()
|
|
285
295
|
op_map = {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
296
|
+
"+": TriccOperator.PLUS,
|
|
297
|
+
"-": TriccOperator.MINUS,
|
|
298
|
+
"&": TriccOperator.CONCATENATE,
|
|
289
299
|
}
|
|
290
300
|
return self.__std_operator(op_map.get(op_text), ctx)
|
|
291
301
|
|
|
292
|
-
|
|
293
302
|
def visitTypeExpression(self, ctx):
|
|
294
303
|
to_type = ctx.getChild(2).getText()
|
|
295
304
|
expression = self.visit(ctx.getChild(0))
|
|
296
|
-
if to_type ==
|
|
305
|
+
if to_type == "int" or to_type == "integer":
|
|
297
306
|
return TriccOperation(TriccOperator.CAST_INTEGER, [expression])
|
|
298
|
-
elif to_type ==
|
|
307
|
+
elif to_type == "float" or to_type == "number":
|
|
299
308
|
return TriccOperation(TriccOperator.CAST_NUMBER, [expression])
|
|
300
|
-
elif to_type ==
|
|
309
|
+
elif to_type == "date":
|
|
301
310
|
return TriccOperation(TriccOperator.CAST_DATE, [expression])
|
|
302
311
|
else:
|
|
303
|
-
raise NotImplementedError(f
|
|
304
|
-
|
|
305
|
-
def visitUnionExpression(self, ctx):
|
|
306
|
-
raise NotImplementedError('union not supported')
|
|
312
|
+
raise NotImplementedError(f"cast {to_type} not supported")
|
|
307
313
|
|
|
308
|
-
def
|
|
309
|
-
|
|
310
|
-
raise NotImplementedError('Implies not supported')
|
|
314
|
+
def visitUnionExpression(self, ctx):
|
|
315
|
+
raise NotImplementedError("union not supported")
|
|
311
316
|
|
|
312
317
|
def visitQuantity(self, ctx):
|
|
313
318
|
# TODO
|
|
314
|
-
raise NotImplementedError(
|
|
319
|
+
raise NotImplementedError("Indexer not supported")
|
|
315
320
|
|
|
316
321
|
def visitUnit(self, ctx):
|
|
317
|
-
raise NotImplementedError(
|
|
322
|
+
raise NotImplementedError("Indexer not supported")
|
|
318
323
|
|
|
319
324
|
def visistDateTimePrecision(self, ctx):
|
|
320
325
|
# TODO
|
|
321
|
-
raise NotImplementedError(
|
|
326
|
+
raise NotImplementedError("Indexer not supported")
|
|
322
327
|
|
|
323
328
|
def visitPluralDateTimePrecision(self, ctx):
|
|
324
329
|
# TODO
|
|
325
|
-
raise NotImplementedError(
|
|
330
|
+
raise NotImplementedError("Indexer not supported")
|
|
326
331
|
|
|
327
|
-
#def visitQualifiedIdentifier(self, ctx):
|
|
332
|
+
# def visitQualifiedIdentifier(self, ctx):
|
|
328
333
|
# raise NotImplementedError('qualifiedIdentifier not supported')
|
|
329
334
|
|
|
330
335
|
def visitTypeSpecifier(self, ctx):
|
|
331
|
-
raise NotImplementedError(
|
|
336
|
+
raise NotImplementedError("typeSpecifier not supported")
|
|
332
337
|
|
|
333
338
|
def visitRetrieve(self, ctx):
|
|
334
339
|
# TODO
|
|
335
|
-
raise NotImplementedError(
|
|
336
|
-
|
|
340
|
+
raise NotImplementedError("retrieve not supported")
|
|
341
|
+
|
|
337
342
|
def visitEqualityExpression(self, ctx):
|
|
338
343
|
return self.visitExpressionComparison(ctx)
|
|
339
344
|
|
|
@@ -358,30 +363,30 @@ class cqlToXlsFormVisitor(cqlVisitor):
|
|
|
358
363
|
op = TriccOperation(TriccOperator.IF)
|
|
359
364
|
op.reference = [condition, true_value, false_value]
|
|
360
365
|
return op
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
from antlr4.error.ErrorListener import ErrorListener
|
|
366
|
+
|
|
364
367
|
|
|
365
368
|
class CQLErrorListener(ErrorListener):
|
|
366
369
|
context = None
|
|
370
|
+
|
|
367
371
|
def __init__(self, context=None):
|
|
368
372
|
super(CQLErrorListener, self).__init__()
|
|
369
373
|
self.errors = []
|
|
370
374
|
self.context = context
|
|
371
375
|
|
|
372
376
|
def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
|
|
373
|
-
error = f"{self.context} \n" if self.context else
|
|
377
|
+
error = f"{self.context} \n" if self.context else ""
|
|
374
378
|
error += f"Line {line}:{column} - {msg}"
|
|
375
379
|
self.errors.append(error)
|
|
376
380
|
|
|
381
|
+
|
|
377
382
|
def transform_cql_to_operation(cql_input, context=None):
|
|
378
383
|
lib_input = f"""
|
|
379
384
|
library runner
|
|
380
|
-
|
|
385
|
+
|
|
381
386
|
define "calc":
|
|
382
387
|
{cql_input.replace('−', '-')}
|
|
383
388
|
"""
|
|
384
|
-
input_stream = InputStream(chr(10).join(lib_input.split(
|
|
389
|
+
input_stream = InputStream(chr(10).join(lib_input.split("\n")))
|
|
385
390
|
lexer = cqlLexer(input_stream)
|
|
386
391
|
stream = CommonTokenStream(lexer)
|
|
387
392
|
parser = cqlParser(stream)
|
|
@@ -402,14 +407,15 @@ def transform_cql_to_operation(cql_input, context=None):
|
|
|
402
407
|
|
|
403
408
|
# If no errors, proceed with visitor
|
|
404
409
|
visitor = cqlToXlsFormVisitor()
|
|
405
|
-
|
|
410
|
+
|
|
406
411
|
visitor.visit(tree)
|
|
407
412
|
if visitor.errors:
|
|
408
413
|
logger.warning(f"while visiting cql: \n{cql_input}")
|
|
409
414
|
for e in visitor.errors:
|
|
410
415
|
logger.warning(e)
|
|
411
|
-
|
|
412
|
-
return visitor.xlsform_rows[0][
|
|
416
|
+
|
|
417
|
+
return visitor.xlsform_rows[0]["calculation"]
|
|
418
|
+
|
|
413
419
|
|
|
414
420
|
def transform_cql_lib_to_operations(cql_input):
|
|
415
421
|
input_stream = InputStream(cql_input)
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
from fhir.resources.codesystem import (
|
|
2
2
|
CodeSystem,
|
|
3
3
|
CodeSystemConcept,
|
|
4
|
-
|
|
5
|
-
CodeSystemConceptProperty
|
|
4
|
+
CodeSystemConceptProperty,
|
|
6
5
|
)
|
|
7
|
-
from fhir.resources.codeableconcept import CodeableConcept
|
|
8
|
-
from fhir.resources.range import Range
|
|
9
|
-
from fhir.resources.quantity import Quantity
|
|
10
|
-
from fhir.resources.coding import Coding
|
|
11
6
|
|
|
12
|
-
from fhir.resources.valueset import ValueSet
|
|
7
|
+
from fhir.resources.valueset import ValueSet
|
|
13
8
|
import logging
|
|
9
|
+
import uuid
|
|
14
10
|
|
|
15
11
|
logger = logging.getLogger("default")
|
|
16
12
|
|
|
13
|
+
# Namespace for deterministic UUIDs
|
|
14
|
+
UUID_NAMESPACE = uuid.UUID('12345678-1234-5678-9abc-def012345678')
|
|
15
|
+
|
|
17
16
|
|
|
18
17
|
def lookup_codesystems_code(codesystems, ref):
|
|
19
|
-
if ref.startswith(
|
|
18
|
+
if ref.startswith("final."):
|
|
20
19
|
concept = lookup_codesystems_code(codesystems, ref[6:])
|
|
21
20
|
if concept:
|
|
22
21
|
return concept
|
|
@@ -30,41 +29,39 @@ def add_concept(codesystems, system, code, display, attributes):
|
|
|
30
29
|
if system and system not in codesystems:
|
|
31
30
|
logger.info(f"New codesystem {system} added to project")
|
|
32
31
|
codesystems[system] = init_codesystem(system, system)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
|
|
36
33
|
return check_and_add_concept(codesystems[system], code, display, attributes)
|
|
37
|
-
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
|
|
41
36
|
def init_codesystem(code, name):
|
|
42
37
|
return CodeSystem(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
38
|
+
id=code.replace("_", "-"),
|
|
39
|
+
url=f"http://example.com/fhir/CodeSystem/{code}",
|
|
40
|
+
version="1.0.0",
|
|
41
|
+
name=name,
|
|
42
|
+
title=name,
|
|
43
|
+
status="draft",
|
|
44
|
+
description=f"Code system for {name}",
|
|
45
|
+
content="complete",
|
|
46
|
+
concept=[],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
54
50
|
def init_valueset(code, name):
|
|
55
51
|
return ValueSet(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
52
|
+
id=code,
|
|
53
|
+
url=f"http://example.com/fhir/ValueSet/{code}",
|
|
54
|
+
version="1.0.0",
|
|
55
|
+
name=name,
|
|
56
|
+
title=name,
|
|
57
|
+
status="draft",
|
|
58
|
+
description=f"Valueset for {name}",
|
|
59
|
+
content="complete",
|
|
60
|
+
conatains=[],
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def check_and_add_concept(code_system: CodeSystem, code: str, display: str, attributes: dict = {}):
|
|
68
65
|
"""
|
|
69
66
|
Checks if a concept with the given code already exists in the CodeSystem.
|
|
70
67
|
If it exists with a different display, raises an error. Otherwise, adds the concept.
|
|
@@ -81,39 +78,33 @@ def check_and_add_concept(code_system: CodeSystem, code: str, display: str, attr
|
|
|
81
78
|
# Check if the concept already exists
|
|
82
79
|
for concept in code_system.concept or []:
|
|
83
80
|
if concept.code == code:
|
|
84
|
-
|
|
81
|
+
|
|
85
82
|
if concept.display.lower() != display.lower():
|
|
86
83
|
logger.warning(
|
|
87
|
-
f"Code {code} already exists with a different display
|
|
84
|
+
f"""Code {code} already exists with a different display:
|
|
85
|
+
Concept:{concept.display}\n Current:{display}"""
|
|
88
86
|
)
|
|
89
87
|
new_concept = concept
|
|
90
88
|
if not new_concept:
|
|
91
89
|
# Add the new concept if it does not exist
|
|
92
|
-
|
|
90
|
+
concept_id = str(uuid.uuid5(UUID_NAMESPACE, display))
|
|
91
|
+
new_concept = CodeSystemConcept.construct(code=code, display=display, id=concept_id)
|
|
93
92
|
if not hasattr(code_system, "concept"):
|
|
94
93
|
code_system.concept = []
|
|
95
94
|
code_system.concept.append(new_concept)
|
|
96
|
-
|
|
95
|
+
|
|
97
96
|
if attributes and not new_concept.property:
|
|
98
97
|
new_concept.property = []
|
|
99
|
-
|
|
100
|
-
for k,v in attributes.items():
|
|
98
|
+
|
|
99
|
+
for k, v in attributes.items():
|
|
101
100
|
existing_attributes = False
|
|
102
101
|
for p in new_concept.property:
|
|
103
102
|
if p.code == k:
|
|
104
|
-
#TODO support other type of Codesystem Concept Property Value
|
|
103
|
+
# TODO support other type of Codesystem Concept Property Value
|
|
105
104
|
existing_attributes
|
|
106
105
|
if p.valueString != v:
|
|
107
106
|
logger.warning(f"conflicting value for property {k}: {p.valueString} != {v}")
|
|
108
107
|
if not existing_attributes:
|
|
109
|
-
new_concept.property.append(
|
|
110
|
-
CodeSystemConceptProperty(
|
|
111
|
-
code=k,
|
|
112
|
-
valueString=v
|
|
113
|
-
)
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
return new_concept
|
|
117
|
-
|
|
108
|
+
new_concept.property.append(CodeSystemConceptProperty(code=k, valueString=v))
|
|
118
109
|
|
|
119
|
-
return
|
|
110
|
+
return new_concept
|