tricc-oo 1.0.1__py3-none-any.whl → 1.4.15__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 +213 -0
- tests/test_cql.py +197 -0
- tests/to_ocl.py +51 -0
- {tricc → tricc_oo}/__init__.py +3 -1
- tricc_oo/converters/codesystem_to_ocl.py +169 -0
- tricc_oo/converters/cql/cqlLexer.py +822 -0
- tricc_oo/converters/cql/cqlListener.py +1632 -0
- tricc_oo/converters/cql/cqlParser.py +11204 -0
- tricc_oo/converters/cql/cqlVisitor.py +913 -0
- tricc_oo/converters/cql_to_operation.py +402 -0
- tricc_oo/converters/datadictionnary.py +115 -0
- tricc_oo/converters/drawio_type_map.py +222 -0
- tricc_oo/converters/tricc_to_xls_form.py +61 -0
- tricc_oo/converters/utils.py +65 -0
- tricc_oo/converters/xml_to_tricc.py +1003 -0
- tricc_oo/models/__init__.py +4 -0
- tricc_oo/models/base.py +732 -0
- tricc_oo/models/calculate.py +216 -0
- tricc_oo/models/ocl.py +281 -0
- tricc_oo/models/ordered_set.py +125 -0
- tricc_oo/models/tricc.py +418 -0
- tricc_oo/parsers/xml.py +138 -0
- tricc_oo/serializers/__init__.py +0 -0
- tricc_oo/serializers/xls_form.py +745 -0
- tricc_oo/strategies/__init__.py +0 -0
- tricc_oo/strategies/input/__init__.py +0 -0
- tricc_oo/strategies/input/base_input_strategy.py +111 -0
- tricc_oo/strategies/input/drawio.py +317 -0
- tricc_oo/strategies/output/base_output_strategy.py +148 -0
- tricc_oo/strategies/output/spice.py +365 -0
- tricc_oo/strategies/output/xls_form.py +697 -0
- tricc_oo/strategies/output/xlsform_cdss.py +189 -0
- tricc_oo/strategies/output/xlsform_cht.py +200 -0
- tricc_oo/strategies/output/xlsform_cht_hf.py +334 -0
- tricc_oo/visitors/__init__.py +0 -0
- tricc_oo/visitors/tricc.py +2198 -0
- tricc_oo/visitors/utils.py +17 -0
- tricc_oo/visitors/xform_pd.py +264 -0
- tricc_oo-1.4.15.dist-info/METADATA +219 -0
- tricc_oo-1.4.15.dist-info/RECORD +46 -0
- {tricc_oo-1.0.1.dist-info → tricc_oo-1.4.15.dist-info}/WHEEL +1 -1
- tricc_oo-1.4.15.dist-info/top_level.txt +2 -0
- tricc/converters/mc_to_tricc.py +0 -542
- tricc/converters/tricc_to_xls_form.py +0 -553
- tricc/converters/utils.py +0 -44
- tricc/converters/xml_to_tricc.py +0 -740
- tricc/models/tricc.py +0 -1093
- tricc/parsers/xml.py +0 -81
- tricc/serializers/xls_form.py +0 -364
- tricc/strategies/input/base_input_strategy.py +0 -80
- tricc/strategies/input/drawio.py +0 -246
- tricc/strategies/input/medalcreator.py +0 -168
- tricc/strategies/output/base_output_strategy.py +0 -92
- tricc/strategies/output/xls_form.py +0 -194
- tricc/strategies/output/xlsform_cdss.py +0 -46
- tricc/strategies/output/xlsform_cht.py +0 -106
- tricc/visitors/tricc.py +0 -375
- tricc_oo-1.0.1.dist-info/LICENSE +0 -78
- tricc_oo-1.0.1.dist-info/METADATA +0 -229
- tricc_oo-1.0.1.dist-info/RECORD +0 -26
- tricc_oo-1.0.1.dist-info/top_level.txt +0 -2
- venv/bin/vba_extract.py +0 -78
- {tricc → tricc_oo}/converters/__init__.py +0 -0
- {tricc → tricc_oo}/models/lang.py +0 -0
- {tricc/serializers → tricc_oo/parsers}/__init__.py +0 -0
- {tricc → tricc_oo}/serializers/planuml.py +0 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
from antlr4 import *
|
|
2
|
+
from tricc_oo.converters.cql.cqlLexer import cqlLexer
|
|
3
|
+
from tricc_oo.converters.cql.cqlParser import cqlParser
|
|
4
|
+
from tricc_oo.converters.cql.cqlVisitor import cqlVisitor
|
|
5
|
+
from tricc_oo.converters.utils import clean_name
|
|
6
|
+
from tricc_oo.models.base import TriccOperator, TriccOperation, TriccStatic, TriccReference, not_clean, or_join, and_join
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("default")
|
|
10
|
+
|
|
11
|
+
EXPRESSION = 0
|
|
12
|
+
STRING = 1
|
|
13
|
+
NUMBER = 2
|
|
14
|
+
ANY = 3
|
|
15
|
+
|
|
16
|
+
FUNCTION_MAP = {
|
|
17
|
+
'AgeInYears': TriccOperator.AGE_YEAR,
|
|
18
|
+
'AgeInMonths': TriccOperator.AGE_MONTH,
|
|
19
|
+
'AgeInDays': TriccOperator.AGE_DAY,
|
|
20
|
+
'Coalesce': TriccOperator.COALESCE,
|
|
21
|
+
'Concatenate': TriccOperator.CONCATENATE,
|
|
22
|
+
'Izscore': TriccOperator.IZSCORE,
|
|
23
|
+
'Zscore': TriccOperator.ZSCORE,
|
|
24
|
+
'DrugDosage': TriccOperator.DRUG_DOSAGE,
|
|
25
|
+
'HasQualifier': TriccOperator.HAS_QUALIFIER,
|
|
26
|
+
}
|
|
27
|
+
# TODO
|
|
28
|
+
# Min
|
|
29
|
+
# Max
|
|
30
|
+
# Round
|
|
31
|
+
# this need to be done by contribution to DMN
|
|
32
|
+
|
|
33
|
+
class cqlToXlsFormVisitor(cqlVisitor):
|
|
34
|
+
def __init__(self):
|
|
35
|
+
self.xlsform_rows = []
|
|
36
|
+
|
|
37
|
+
def resolve_scv(self, arg):
|
|
38
|
+
|
|
39
|
+
# TODO
|
|
40
|
+
# look for the system, if not found fallback on default system
|
|
41
|
+
# look for the code in the system
|
|
42
|
+
# if no code or not found return None
|
|
43
|
+
if arg.startswith('"') and arg.endswith('"'):
|
|
44
|
+
return TriccReference(arg[1:-1])
|
|
45
|
+
else:
|
|
46
|
+
return TriccReference(arg)
|
|
47
|
+
|
|
48
|
+
def translate(self, arg, type=ANY):
|
|
49
|
+
return self.resolve_scv(arg) or str(arg)
|
|
50
|
+
|
|
51
|
+
def visitExpressionDefinition(self, ctx):
|
|
52
|
+
identifier = ctx.identifier().getText()
|
|
53
|
+
expression = self.visit(ctx.expression())
|
|
54
|
+
self.xlsform_rows.append({
|
|
55
|
+
'type': 'calculate',
|
|
56
|
+
'name': clean_name(identifier[1:-1].lower()),
|
|
57
|
+
'calculation': expression
|
|
58
|
+
})
|
|
59
|
+
return expression
|
|
60
|
+
|
|
61
|
+
def visitIdentifier(self, arg):
|
|
62
|
+
return self.translate(arg.getText(), 1)
|
|
63
|
+
|
|
64
|
+
def visitChildren(self, ctx):
|
|
65
|
+
return super().visitChildren(ctx)
|
|
66
|
+
|
|
67
|
+
def aggregateResult(self, aggregate, nextResult):
|
|
68
|
+
if aggregate is not None:
|
|
69
|
+
if nextResult is None:
|
|
70
|
+
return aggregate
|
|
71
|
+
else:
|
|
72
|
+
aggregate = aggregate if isinstance(aggregate, list) else [aggregate]
|
|
73
|
+
return [
|
|
74
|
+
*aggregate,
|
|
75
|
+
nextResult
|
|
76
|
+
]
|
|
77
|
+
else:
|
|
78
|
+
return nextResult
|
|
79
|
+
|
|
80
|
+
def visitExpression(self, ctx):
|
|
81
|
+
return self.visitChildren(ctx)
|
|
82
|
+
def visitThisInvocation(self, ctx):
|
|
83
|
+
return '$this'
|
|
84
|
+
|
|
85
|
+
def visitBooleanLiteral(self, ctx):
|
|
86
|
+
literal = ctx.getChild(0).getText()
|
|
87
|
+
if literal == 'true':
|
|
88
|
+
return TriccStatic(True)
|
|
89
|
+
elif literal == 'false':
|
|
90
|
+
return TriccStatic(False)
|
|
91
|
+
else:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
def visitFunctionInvocation(self, ctx, operator=TriccOperator.NATIVE):
|
|
95
|
+
if ctx.getChildCount() == 1:
|
|
96
|
+
return self.visitFunctionInvocation(ctx.getChild(0))
|
|
97
|
+
function_name = ctx.getChild(0).getText()
|
|
98
|
+
if function_name in FUNCTION_MAP:
|
|
99
|
+
operator = FUNCTION_MAP[function_name]
|
|
100
|
+
# Add more function transformations here
|
|
101
|
+
op = TriccOperation(operator)
|
|
102
|
+
if operator == TriccOperator.NATIVE:
|
|
103
|
+
op.reference = [
|
|
104
|
+
function_name,
|
|
105
|
+
]
|
|
106
|
+
args = ctx.paramList()
|
|
107
|
+
if args:
|
|
108
|
+
op.reference += [self.visit(arg) for arg in args.expression() if arg]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
return op
|
|
112
|
+
|
|
113
|
+
def __std_function(self, ctx, operator=TriccOperator.NATIVE):
|
|
114
|
+
args = ctx.expressions
|
|
115
|
+
if args:
|
|
116
|
+
args = [self.visit(arg) for arg in ctx.expression() if arg]
|
|
117
|
+
op = TriccOperation(operator)
|
|
118
|
+
op.reference = [
|
|
119
|
+
*args
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
def visitParenthesizedTerm(self, ctx):
|
|
123
|
+
return TriccOperation(TriccOperator.PARENTHESIS, [self.visitChildren(ctx)])
|
|
124
|
+
|
|
125
|
+
def visitMemberInvocation(self, ctx):
|
|
126
|
+
return self.visitChildren(ctx)
|
|
127
|
+
|
|
128
|
+
def visitMembershipExpression(self, ctx):
|
|
129
|
+
function_name = ctx.getChild(1).getText()
|
|
130
|
+
return self._get_membership_expression(ctx, function_name)
|
|
131
|
+
|
|
132
|
+
def _get_membership_expression(self, ctx, function_name):
|
|
133
|
+
left = self.visit(ctx.expression(0))
|
|
134
|
+
right = self.visit(ctx.expression(1))
|
|
135
|
+
if function_name == 'in':
|
|
136
|
+
op = TriccOperation(TriccOperator.SELECTED)
|
|
137
|
+
op.reference = [
|
|
138
|
+
right,
|
|
139
|
+
left
|
|
140
|
+
]
|
|
141
|
+
elif function_name == 'contains':
|
|
142
|
+
op = TriccOperation(TriccOperator.CONTAINS)
|
|
143
|
+
op.reference = [
|
|
144
|
+
left,
|
|
145
|
+
right
|
|
146
|
+
]
|
|
147
|
+
return op
|
|
148
|
+
|
|
149
|
+
def visitNegateMembershipExpression(self, ctx):
|
|
150
|
+
function_name = ctx.getChild(2).getText()
|
|
151
|
+
return not_clean(self._get_membership_expression(ctx, function_name))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def visitBetweenExpression(self, ctx):
|
|
155
|
+
ref = self.visit(ctx.expression(0))
|
|
156
|
+
lower = self.visit(ctx.expression(1))
|
|
157
|
+
higher = self.visit(ctx.expression(2))
|
|
158
|
+
op = TriccOperation(TriccOperator.BETWEEN)
|
|
159
|
+
op.reference = [ref, lower, higher]
|
|
160
|
+
return op
|
|
161
|
+
|
|
162
|
+
def visitBooleanExpression(self, ctx):
|
|
163
|
+
expr = self.visit(ctx.expression())
|
|
164
|
+
params = [c.getText() for c in list(ctx.getChildren())[2:]]
|
|
165
|
+
op = TriccOperation(
|
|
166
|
+
operator = {
|
|
167
|
+
'true': TriccOperator.ISTRUE,
|
|
168
|
+
'false': TriccOperator.ISFALSE,
|
|
169
|
+
'null': TriccOperator.ISNULL
|
|
170
|
+
}[params[-1]],
|
|
171
|
+
reference = [expr]
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if params[0] == 'not':
|
|
175
|
+
if isinstance(op, TriccStatic) and isinstance(op.value, str):
|
|
176
|
+
logger.warning(f"not operator on a string {op.value}")
|
|
177
|
+
op = not_clean(op)
|
|
178
|
+
|
|
179
|
+
return op
|
|
180
|
+
|
|
181
|
+
def visitExistenceExpression(self, ctx):
|
|
182
|
+
expr = self.visit(ctx.expression())
|
|
183
|
+
op = TriccOperation(TriccOperator.EXISTS)
|
|
184
|
+
op.reference = [expr]
|
|
185
|
+
return op
|
|
186
|
+
|
|
187
|
+
def visitAndExpression(self, ctx):
|
|
188
|
+
return self.__std_operator(TriccOperator.AND, ctx)
|
|
189
|
+
|
|
190
|
+
def visitOrExpression(self, ctx):
|
|
191
|
+
return self.__std_operator(TriccOperator.OR, ctx)
|
|
192
|
+
|
|
193
|
+
def __std_operator(self, operator, ctx):
|
|
194
|
+
if hasattr(ctx, 'expression'):
|
|
195
|
+
left = self.visit(ctx.expression(0))
|
|
196
|
+
right = self.visit(ctx.expression(1))
|
|
197
|
+
elif hasattr(ctx, 'expressionTerm'):
|
|
198
|
+
left = self.visit(ctx.expressionTerm(0))
|
|
199
|
+
right = self.visit(ctx.expressionTerm(1))
|
|
200
|
+
if operator == TriccOperator.AND:
|
|
201
|
+
return and_join([left, right])
|
|
202
|
+
elif operator == TriccOperator.OR:
|
|
203
|
+
return or_join([left, right])
|
|
204
|
+
else:
|
|
205
|
+
op = TriccOperation(operator, [left, right])
|
|
206
|
+
return op
|
|
207
|
+
|
|
208
|
+
def visitNotExpression(self, ctx):
|
|
209
|
+
return not_clean(self.visit(ctx.expression()))
|
|
210
|
+
|
|
211
|
+
def visitIsTrueOrFalseExpression(self, ctx):
|
|
212
|
+
expr = self.visit(ctx.expression())
|
|
213
|
+
op = TriccOperation(TriccOperator.ISTRUE if ctx.TRUE() else TriccOperator.ISFALSE)
|
|
214
|
+
op.reference = [expr]
|
|
215
|
+
return op
|
|
216
|
+
|
|
217
|
+
def visitInequalityExpression(self, ctx):
|
|
218
|
+
return self.visitExpressionComparison(ctx)
|
|
219
|
+
|
|
220
|
+
def visitNumberLiteral(self, ctx):
|
|
221
|
+
value = float(ctx.getText())
|
|
222
|
+
value_int = int(value)
|
|
223
|
+
return TriccStatic(value=value_int if value == value_int else value)
|
|
224
|
+
|
|
225
|
+
def visitStringLiteral(self, ctx):
|
|
226
|
+
return TriccStatic(value=ctx.getText().strip("'"))
|
|
227
|
+
|
|
228
|
+
def visitExpressionComparison(self, ctx):
|
|
229
|
+
left = self.visit(ctx.expression(0))
|
|
230
|
+
right = self.visit(ctx.expression(1))
|
|
231
|
+
op_text = ctx.getChild(1).getText()
|
|
232
|
+
op_map = {
|
|
233
|
+
'<': TriccOperator.LESS,
|
|
234
|
+
'<=': TriccOperator.LESS_OR_EQUAL,
|
|
235
|
+
'>': TriccOperator.MORE,
|
|
236
|
+
'>=': TriccOperator.MORE_OR_EQUAL,
|
|
237
|
+
'=': TriccOperator.EQUAL,
|
|
238
|
+
'!=': TriccOperator.NOTEQUAL
|
|
239
|
+
}
|
|
240
|
+
op = TriccOperation(op_map[op_text])
|
|
241
|
+
op.reference = [left, right]
|
|
242
|
+
return op
|
|
243
|
+
|
|
244
|
+
def visitInvocationExpression(self, ctx):
|
|
245
|
+
raise NotImplementedError('Invocation not supported')
|
|
246
|
+
|
|
247
|
+
def visitIndexerExpression(self, ctx):
|
|
248
|
+
raise NotImplementedError('Indexer not supported')
|
|
249
|
+
|
|
250
|
+
def visitCastExpression(self, ctx):
|
|
251
|
+
# TODO
|
|
252
|
+
raise NotImplementedError('Cast not supported')
|
|
253
|
+
|
|
254
|
+
def visitPolarityExpressionTerm(self, ctx):
|
|
255
|
+
if ctx.getChild(0).getText() == '-':
|
|
256
|
+
return TriccOperation(TriccOperator.MINUS, [self.visit(ctx.getChild(1))])
|
|
257
|
+
|
|
258
|
+
def visitMultiplicationExpressionTerm(self, ctx):
|
|
259
|
+
op_text = ctx.getChild(1).getText()
|
|
260
|
+
op_map = {
|
|
261
|
+
'*': TriccOperator.MULTIPLIED,
|
|
262
|
+
'div': TriccOperator.DIVIDED,
|
|
263
|
+
'/': TriccOperator.DIVIDED,
|
|
264
|
+
'%': TriccOperator.MODULO,
|
|
265
|
+
'mod': TriccOperator.MODULO,
|
|
266
|
+
}
|
|
267
|
+
return self.__std_operator(op_map.get(op_text), ctx)
|
|
268
|
+
|
|
269
|
+
def visitAdditionExpressionTerm(self, ctx):
|
|
270
|
+
op_text = ctx.getChild(1).getText()
|
|
271
|
+
op_map = {
|
|
272
|
+
'+': TriccOperator.PLUS,
|
|
273
|
+
'-': TriccOperator.MINUS,
|
|
274
|
+
'&': TriccOperator.AND
|
|
275
|
+
}
|
|
276
|
+
return self.__std_operator(op_map.get(op_text), ctx)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def visitTypeExpression(self, ctx):
|
|
280
|
+
to_type = ctx.getChild(2).getText()
|
|
281
|
+
expression = self.visit(ctx.getChild(0))
|
|
282
|
+
if to_type == 'int' or to_type == 'integer':
|
|
283
|
+
return TriccOperation(TriccOperator.CAST_INTEGER, [expression])
|
|
284
|
+
elif to_type == 'float' or to_type == 'number':
|
|
285
|
+
return TriccOperation(TriccOperator.CAST_NUMBER, [expression])
|
|
286
|
+
elif to_type == 'date':
|
|
287
|
+
return TriccOperation(TriccOperator.CAST_DATE, [expression])
|
|
288
|
+
else:
|
|
289
|
+
raise NotImplementedError(f'cast {to_type} not supported')
|
|
290
|
+
|
|
291
|
+
def visitUnionExpression(self, ctx):
|
|
292
|
+
raise NotImplementedError('union not supported')
|
|
293
|
+
|
|
294
|
+
def visitThisInvocation(self, ctx):
|
|
295
|
+
# TODO
|
|
296
|
+
raise NotImplementedError('Implies not supported')
|
|
297
|
+
|
|
298
|
+
def visitQuantity(self, ctx):
|
|
299
|
+
# TODO
|
|
300
|
+
raise NotImplementedError('Indexer not supported')
|
|
301
|
+
|
|
302
|
+
def visitUnit(self, ctx):
|
|
303
|
+
raise NotImplementedError('Indexer not supported')
|
|
304
|
+
|
|
305
|
+
def visistDateTimePrecision(self, ctx):
|
|
306
|
+
# TODO
|
|
307
|
+
raise NotImplementedError('Indexer not supported')
|
|
308
|
+
|
|
309
|
+
def visitPluralDateTimePrecision(self, ctx):
|
|
310
|
+
# TODO
|
|
311
|
+
raise NotImplementedError('Indexer not supported')
|
|
312
|
+
|
|
313
|
+
#def visitQualifiedIdentifier(self, ctx):
|
|
314
|
+
# raise NotImplementedError('qualifiedIdentifier not supported')
|
|
315
|
+
|
|
316
|
+
def visitTypeSpecifier(self, ctx):
|
|
317
|
+
raise NotImplementedError('typeSpecifier not supported')
|
|
318
|
+
|
|
319
|
+
def visitRetrieve(self, ctx):
|
|
320
|
+
# TODO
|
|
321
|
+
raise NotImplementedError('retrieve not supported')
|
|
322
|
+
|
|
323
|
+
def visitEqualityExpression(self, ctx):
|
|
324
|
+
return self.visitExpressionComparison(ctx)
|
|
325
|
+
|
|
326
|
+
def visitCaseExpressionTerm(self, ctx, operator=TriccOperator.CASE):
|
|
327
|
+
op = TriccOperation(operator)
|
|
328
|
+
op.reference = []
|
|
329
|
+
for child in ctx.getChildren():
|
|
330
|
+
c = self.visit(child)
|
|
331
|
+
if c is not None:
|
|
332
|
+
op.append(c)
|
|
333
|
+
return op
|
|
334
|
+
|
|
335
|
+
def visitCaseExpressionItem(self, ctx):
|
|
336
|
+
test = self.visit(ctx.expression(0))
|
|
337
|
+
result = self.visit(ctx.expression(1))
|
|
338
|
+
return [test, result]
|
|
339
|
+
|
|
340
|
+
def visitIfThenElseExpressionTerm(self, ctx):
|
|
341
|
+
condition = self.visit(ctx.expression(0))
|
|
342
|
+
true_value = self.visit(ctx.expression(1))
|
|
343
|
+
false_value = self.visit(ctx.expression(2))
|
|
344
|
+
op = TriccOperation(TriccOperator.IF)
|
|
345
|
+
op.reference = [condition, true_value, false_value]
|
|
346
|
+
return op
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
from antlr4.error.ErrorListener import ErrorListener
|
|
350
|
+
|
|
351
|
+
class CQLErrorListener(ErrorListener):
|
|
352
|
+
context = None
|
|
353
|
+
def __init__(self, context=None):
|
|
354
|
+
super(CQLErrorListener, self).__init__()
|
|
355
|
+
self.errors = []
|
|
356
|
+
self.context = context
|
|
357
|
+
|
|
358
|
+
def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
|
|
359
|
+
error = f"{self.context} \n" if self.context else ''
|
|
360
|
+
error += f"Line {line}:{column} - {msg}"
|
|
361
|
+
self.errors.append(error)
|
|
362
|
+
|
|
363
|
+
def transform_cql_to_operation(cql_input, context=None):
|
|
364
|
+
cql_input = f"""
|
|
365
|
+
library runner
|
|
366
|
+
|
|
367
|
+
define "calc":
|
|
368
|
+
{cql_input.replace('−', '-')}
|
|
369
|
+
"""
|
|
370
|
+
input_stream = InputStream(chr(10).join(cql_input.split('\n')))
|
|
371
|
+
lexer = cqlLexer(input_stream)
|
|
372
|
+
stream = CommonTokenStream(lexer)
|
|
373
|
+
parser = cqlParser(stream)
|
|
374
|
+
|
|
375
|
+
# Remove default error listeners and add custom listener
|
|
376
|
+
parser.removeErrorListeners()
|
|
377
|
+
lexer.removeErrorListeners()
|
|
378
|
+
error_listener = CQLErrorListener(context)
|
|
379
|
+
parser.addErrorListener(error_listener)
|
|
380
|
+
lexer.addErrorListener(error_listener)
|
|
381
|
+
tree = parser.library()
|
|
382
|
+
|
|
383
|
+
# Check for errors
|
|
384
|
+
if error_listener.errors:
|
|
385
|
+
for error in error_listener.errors:
|
|
386
|
+
print(f"CQL Grammar Error: {error}")
|
|
387
|
+
return None # Or handle errors as appropriate for your use case
|
|
388
|
+
|
|
389
|
+
# If no errors, proceed with visitor
|
|
390
|
+
visitor = cqlToXlsFormVisitor()
|
|
391
|
+
visitor.visit(tree)
|
|
392
|
+
return visitor.xlsform_rows[0]['calculation']
|
|
393
|
+
|
|
394
|
+
def transform_cql_lib_to_operations(cql_input):
|
|
395
|
+
input_stream = InputStream(cql_input)
|
|
396
|
+
lexer = cqlLexer(input_stream)
|
|
397
|
+
stream = CommonTokenStream(lexer)
|
|
398
|
+
parser = cqlParser(stream)
|
|
399
|
+
tree = parser.library()
|
|
400
|
+
visitor = cqlToXlsFormVisitor()
|
|
401
|
+
visitor.visit(tree)
|
|
402
|
+
return visitor.xlsform_rows
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from fhir.resources.codesystem import (
|
|
2
|
+
CodeSystem,
|
|
3
|
+
CodeSystemConcept,
|
|
4
|
+
CodeSystemConceptDesignation,
|
|
5
|
+
CodeSystemConceptProperty
|
|
6
|
+
)
|
|
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
|
+
|
|
12
|
+
from fhir.resources.valueset import ValueSet, ValueSetCompose, ValueSetComposeInclude
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("default")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def lookup_codesystems_code(codesystems, ref):
|
|
19
|
+
for code_system in codesystems.values():
|
|
20
|
+
for concept in code_system.concept or []:
|
|
21
|
+
if concept.code == ref:
|
|
22
|
+
return concept
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def add_concept(codesystems, system, code, display, attributes):
|
|
26
|
+
if system and system not in codesystems:
|
|
27
|
+
logger.info(f"New codesystem {system} added to project")
|
|
28
|
+
codesystems[system] = init_codesystem(system, system)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
return check_and_add_concept(codesystems[system], code, display, attributes)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def init_codesystem(code, name):
|
|
38
|
+
return CodeSystem(
|
|
39
|
+
id=code.replace('_','-'),
|
|
40
|
+
url=f"http://example.com/fhir/CodeSystem/{code}",
|
|
41
|
+
version="1.0.0",
|
|
42
|
+
name=name,
|
|
43
|
+
title=name,
|
|
44
|
+
status="draft",
|
|
45
|
+
description=f"Code system for {name}",
|
|
46
|
+
content="complete",
|
|
47
|
+
concept=[]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def init_valueset(code, name):
|
|
51
|
+
return ValueSet(
|
|
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
|
+
def check_and_add_concept(code_system: CodeSystem, code: str, display: str, attributes: dict={}):
|
|
64
|
+
"""
|
|
65
|
+
Checks if a concept with the given code already exists in the CodeSystem.
|
|
66
|
+
If it exists with a different display, raises an error. Otherwise, adds the concept.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
code_system (CodeSystem): The CodeSystem to check and update.
|
|
70
|
+
code (str): The code of the concept to add.
|
|
71
|
+
display (str): The display of the concept to add.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
ValueError: If a concept with the same code exists but has a different display.
|
|
75
|
+
"""
|
|
76
|
+
new_concept = None
|
|
77
|
+
# Check if the concept already exists
|
|
78
|
+
for concept in code_system.concept or []:
|
|
79
|
+
if concept.code == code:
|
|
80
|
+
|
|
81
|
+
if concept.display.lower() != display.lower():
|
|
82
|
+
logger.warning(
|
|
83
|
+
f"Code {code} already exists with a different display:\n Concept:{concept.display}\n Current:{display}"
|
|
84
|
+
)
|
|
85
|
+
new_concept = concept
|
|
86
|
+
if not new_concept:
|
|
87
|
+
# Add the new concept if it does not exist
|
|
88
|
+
new_concept = CodeSystemConcept.construct(code=code, display=display)
|
|
89
|
+
if not hasattr(code_system, "concept"):
|
|
90
|
+
code_system.concept = []
|
|
91
|
+
code_system.concept.append(new_concept)
|
|
92
|
+
|
|
93
|
+
if attributes and not new_concept.property:
|
|
94
|
+
new_concept.property = []
|
|
95
|
+
|
|
96
|
+
for k,v in attributes.items():
|
|
97
|
+
existing_attributes = False
|
|
98
|
+
for p in new_concept.property:
|
|
99
|
+
if p.code == k:
|
|
100
|
+
#TODO support other type of Codesystem Concept Property Value
|
|
101
|
+
existing_attributes
|
|
102
|
+
if p.valueString != v:
|
|
103
|
+
logger.warning(f"conflicting value for property {k}: {p.valueString} != {v}")
|
|
104
|
+
if not existing_attributes:
|
|
105
|
+
new_concept.property.append(
|
|
106
|
+
CodeSystemConceptProperty(
|
|
107
|
+
code=k,
|
|
108
|
+
valueString=v
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return new_concept
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
return value_set
|