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.
Files changed (66) hide show
  1. tests/build.py +213 -0
  2. tests/test_cql.py +197 -0
  3. tests/to_ocl.py +51 -0
  4. {tricc → tricc_oo}/__init__.py +3 -1
  5. tricc_oo/converters/codesystem_to_ocl.py +169 -0
  6. tricc_oo/converters/cql/cqlLexer.py +822 -0
  7. tricc_oo/converters/cql/cqlListener.py +1632 -0
  8. tricc_oo/converters/cql/cqlParser.py +11204 -0
  9. tricc_oo/converters/cql/cqlVisitor.py +913 -0
  10. tricc_oo/converters/cql_to_operation.py +402 -0
  11. tricc_oo/converters/datadictionnary.py +115 -0
  12. tricc_oo/converters/drawio_type_map.py +222 -0
  13. tricc_oo/converters/tricc_to_xls_form.py +61 -0
  14. tricc_oo/converters/utils.py +65 -0
  15. tricc_oo/converters/xml_to_tricc.py +1003 -0
  16. tricc_oo/models/__init__.py +4 -0
  17. tricc_oo/models/base.py +732 -0
  18. tricc_oo/models/calculate.py +216 -0
  19. tricc_oo/models/ocl.py +281 -0
  20. tricc_oo/models/ordered_set.py +125 -0
  21. tricc_oo/models/tricc.py +418 -0
  22. tricc_oo/parsers/xml.py +138 -0
  23. tricc_oo/serializers/__init__.py +0 -0
  24. tricc_oo/serializers/xls_form.py +745 -0
  25. tricc_oo/strategies/__init__.py +0 -0
  26. tricc_oo/strategies/input/__init__.py +0 -0
  27. tricc_oo/strategies/input/base_input_strategy.py +111 -0
  28. tricc_oo/strategies/input/drawio.py +317 -0
  29. tricc_oo/strategies/output/base_output_strategy.py +148 -0
  30. tricc_oo/strategies/output/spice.py +365 -0
  31. tricc_oo/strategies/output/xls_form.py +697 -0
  32. tricc_oo/strategies/output/xlsform_cdss.py +189 -0
  33. tricc_oo/strategies/output/xlsform_cht.py +200 -0
  34. tricc_oo/strategies/output/xlsform_cht_hf.py +334 -0
  35. tricc_oo/visitors/__init__.py +0 -0
  36. tricc_oo/visitors/tricc.py +2198 -0
  37. tricc_oo/visitors/utils.py +17 -0
  38. tricc_oo/visitors/xform_pd.py +264 -0
  39. tricc_oo-1.4.15.dist-info/METADATA +219 -0
  40. tricc_oo-1.4.15.dist-info/RECORD +46 -0
  41. {tricc_oo-1.0.1.dist-info → tricc_oo-1.4.15.dist-info}/WHEEL +1 -1
  42. tricc_oo-1.4.15.dist-info/top_level.txt +2 -0
  43. tricc/converters/mc_to_tricc.py +0 -542
  44. tricc/converters/tricc_to_xls_form.py +0 -553
  45. tricc/converters/utils.py +0 -44
  46. tricc/converters/xml_to_tricc.py +0 -740
  47. tricc/models/tricc.py +0 -1093
  48. tricc/parsers/xml.py +0 -81
  49. tricc/serializers/xls_form.py +0 -364
  50. tricc/strategies/input/base_input_strategy.py +0 -80
  51. tricc/strategies/input/drawio.py +0 -246
  52. tricc/strategies/input/medalcreator.py +0 -168
  53. tricc/strategies/output/base_output_strategy.py +0 -92
  54. tricc/strategies/output/xls_form.py +0 -194
  55. tricc/strategies/output/xlsform_cdss.py +0 -46
  56. tricc/strategies/output/xlsform_cht.py +0 -106
  57. tricc/visitors/tricc.py +0 -375
  58. tricc_oo-1.0.1.dist-info/LICENSE +0 -78
  59. tricc_oo-1.0.1.dist-info/METADATA +0 -229
  60. tricc_oo-1.0.1.dist-info/RECORD +0 -26
  61. tricc_oo-1.0.1.dist-info/top_level.txt +0 -2
  62. venv/bin/vba_extract.py +0 -78
  63. {tricc → tricc_oo}/converters/__init__.py +0 -0
  64. {tricc → tricc_oo}/models/lang.py +0 -0
  65. {tricc/serializers → tricc_oo/parsers}/__init__.py +0 -0
  66. {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