tricc-oo 1.0.2__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.2.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 -308
  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.2.dist-info/LICENSE +0 -78
  59. tricc_oo-1.0.2.dist-info/METADATA +0 -229
  60. tricc_oo-1.0.2.dist-info/RECORD +0 -26
  61. tricc_oo-1.0.2.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,697 @@
1
+ """
2
+ Strategy to build the skyp logic following the XLSForm way
3
+
4
+ """
5
+
6
+ import datetime
7
+ import logging
8
+ import os
9
+
10
+ import pandas as pd
11
+
12
+ from tricc_oo.converters.tricc_to_xls_form import *
13
+
14
+ from tricc_oo.models import (
15
+ TriccNodeActivity,
16
+ TriccGroup,
17
+ TriccOperation,
18
+ TriccOperator
19
+ )
20
+ from tricc_oo.models.lang import SingletonLangClass
21
+
22
+ from tricc_oo.visitors.tricc import (
23
+ check_stashed_loop,
24
+ walktrhough_tricc_node_processed_stached,
25
+ is_ready_to_process,
26
+ process_reference,
27
+ get_node_expressions,
28
+ TRICC_TRUE_VALUE,
29
+ TRICC_FALSE_VALUE
30
+ )
31
+ from tricc_oo.serializers.xls_form import (
32
+ CHOICE_MAP,
33
+ SURVEY_MAP,
34
+ end_group,
35
+ generate_xls_form_export,
36
+ start_group,
37
+ BOOLEAN_MAP
38
+ )
39
+ from tricc_oo.strategies.output.base_output_strategy import BaseOutPutStrategy
40
+
41
+ logger = logging.getLogger("default")
42
+
43
+
44
+
45
+ """
46
+ The XLSForm strategy is a strategy that will generate the XLSForm logic
47
+ The XLSForm logic is a logic that is based on the XLSForm format
48
+ The XLSForm format is a format that is used by the ODK Collect application
49
+ The ODK Collect application is an application that is used to collect data on mobile devices
50
+
51
+ document below function
52
+
53
+ generate_xls_form_condition
54
+ generate_xls_form_relevance
55
+ generate_xls_form_calculate
56
+ generate_xls_form_export
57
+ start_group
58
+ end_group
59
+ walktrhough_tricc_node_processed_stached
60
+ check_stashed_loop
61
+ generate_xls_form_export
62
+ generate_xls_form_export
63
+
64
+ """
65
+ langs = SingletonLangClass()
66
+
67
+ OPERATOR_COALESCE_FALLBACK = {
68
+ TriccOperator.ISTRUE: 0,
69
+ TriccOperator.ISFALSE: 1,
70
+ TriccOperator.ISNOTTRUE: 0,
71
+ TriccOperator.ISNOTFALSE: 1,
72
+ TriccOperator.MORE: -2147483648,
73
+ TriccOperator.MORE_OR_EQUAL: -2147483648,
74
+ TriccOperator.LESS: 2147483647,
75
+ TriccOperator.LESS_OR_EQUAL: 2147483647
76
+ }
77
+
78
+ class XLSFormStrategy(BaseOutPutStrategy):
79
+ pd.set_option('display.max_colwidth', None)
80
+ df_survey = pd.DataFrame(columns=SURVEY_MAP.keys())
81
+ df_calculate = pd.DataFrame(columns=SURVEY_MAP.keys())
82
+ df_choice = pd.DataFrame(columns=CHOICE_MAP.keys())
83
+ calculates = {}
84
+ # add save nodes and merge nodes
85
+
86
+
87
+ def clean_coalesce(self, expression):
88
+ if re.match(r"^coalesce\(\${[^}]+},''\)$", expression):
89
+ return expression[9:-4]
90
+ return expression
91
+
92
+ def generate_base(self, node, **kwargs):
93
+ return self.generate_xls_form_condition(node, **kwargs)
94
+
95
+ def generate_relevance(self, node, **kwargs):
96
+ return self.generate_xls_form_relevance(node, **kwargs)
97
+
98
+ def generate_calculate(self, node, **kwargs):
99
+ return self.generate_xls_form_calculate(node, **kwargs)
100
+
101
+ def __init__(self, project, output_path):
102
+ super().__init__(project, output_path)
103
+ self.do_clean()
104
+
105
+ def do_clean(self, **kwargs):
106
+ self.calculates = {}
107
+ self.used_calculates = {}
108
+
109
+ def get_kwargs(self):
110
+ return {
111
+ "df_survey": self.df_survey,
112
+ "df_choice": self.df_choice,
113
+ "df_calculate": self.df_calculate,
114
+ "calculates": self.calculates
115
+ }
116
+
117
+ def generate_export(self, node, **kwargs):
118
+ return generate_xls_form_export(self, node, **kwargs)
119
+
120
+
121
+ def export(self, start_pages, version):
122
+ if start_pages["main"].root.form_id is not None:
123
+ form_id = str(start_pages["main"].root.form_id)
124
+ else:
125
+ logger.critical("form id required in the first start node")
126
+ exit(1)
127
+ title = start_pages["main"].root.label
128
+ file_name = form_id + ".xlsx"
129
+ # make a 'settings' tab
130
+ now = datetime.datetime.now()
131
+ indx = [[1]]
132
+
133
+ settings = {
134
+ "form_title": title,
135
+ "form_id": form_id,
136
+ "version": version,
137
+ "default_language": "English (en)",
138
+ "style": "pages",
139
+ }
140
+ df_settings = pd.DataFrame(settings, index=indx)
141
+ df_settings.head()
142
+
143
+ newpath = os.path.join(self.output_path, file_name)
144
+ # create newpath if it not exists
145
+ if not os.path.exists(self.output_path):
146
+ os.makedirs(self.output_path)
147
+
148
+ # create a Pandas Excel writer using XlsxWriter as the engine
149
+ writer = pd.ExcelWriter(newpath, engine="xlsxwriter")
150
+ if len(self.df_survey[self.df_survey['name'] == 'version'] ):
151
+ self.df_survey.loc[ self.df_survey['name'] == 'version', 'label'] = f"v{version}"
152
+ self.df_survey.to_excel(writer, sheet_name="survey", index=False)
153
+ self.df_choice.to_excel(writer, sheet_name="choices", index=False)
154
+ df_settings.to_excel(writer, sheet_name="settings", index=False)
155
+
156
+ # close the Pandas Excel writer and output the Excel file
157
+ # writer.save()
158
+
159
+ # run this on a windows python instance because if not then the generated xlsx file remains open
160
+ writer.close()
161
+ # writer.handles = None
162
+
163
+ def process_export(self, start_pages, **kwargs):
164
+ self.activity_export(start_pages["main"], **kwargs)
165
+
166
+ def activity_export(self, activity, processed_nodes=None, **kwargs):
167
+ if processed_nodes is None:
168
+ processed_nodes = OrderedSet()
169
+ stashed_nodes = OrderedSet()
170
+ # The stashed node are all the node that have all their prevnode processed but not from the same group
171
+ # This logic works only because the prev node are ordered by group/parent ..
172
+ skip_header = 0
173
+ groups = {}
174
+ cur_group = activity
175
+ groups[activity.id] = 0
176
+ path_len = 0
177
+ # keep the vesrions on the group id, max version
178
+ start_group(self, cur_group=cur_group, groups=groups, **self.get_kwargs())
179
+ walktrhough_tricc_node_processed_stached(
180
+ activity.root,
181
+ self.generate_export,
182
+ processed_nodes,
183
+ stashed_nodes,
184
+ path_len,
185
+ cur_group=activity.root.group,
186
+ recursive=False,
187
+ **self.get_kwargs()
188
+ )
189
+ end_group(self, cur_group=activity, groups=groups, **self.get_kwargs())
190
+ # we save the survey data frame
191
+ df_survey_final = pd.DataFrame(columns=SURVEY_MAP.keys())
192
+ if len(self.df_survey) > (2 + skip_header):
193
+ df_survey_final = self.df_survey
194
+ ## MANAGE STASHED NODES
195
+ prev_stashed_nodes = stashed_nodes.copy()
196
+ loop_count = 0
197
+ len_prev_processed_nodes = 0
198
+ while len(stashed_nodes) > 0:
199
+ self.df_survey = pd.DataFrame(columns=SURVEY_MAP.keys())
200
+ loop_count = check_stashed_loop(
201
+ stashed_nodes,
202
+ prev_stashed_nodes,
203
+ processed_nodes,
204
+ len_prev_processed_nodes,
205
+ loop_count,
206
+ )
207
+ prev_stashed_nodes = stashed_nodes.copy()
208
+ len_prev_processed_nodes = len(processed_nodes)
209
+ if len(stashed_nodes) > 0:
210
+ s_node = stashed_nodes.pop()
211
+ # while len(stashed_nodes)>0 and isinstance(s_node,TriccGroup):
212
+ # s_node = stashed_nodes.pop()
213
+ if s_node.group is None:
214
+ logger.critical(
215
+ "ERROR group is none for node {}".format(s_node.get_name())
216
+ )
217
+ start_group(
218
+ self,
219
+ cur_group=s_node.group,
220
+ groups=groups,
221
+ relevance=True,
222
+ **self.get_kwargs()
223
+ )
224
+ # arrange empty group
225
+
226
+ walktrhough_tricc_node_processed_stached(
227
+ s_node,
228
+ self.generate_export,
229
+ processed_nodes,
230
+ stashed_nodes,
231
+ path_len,
232
+ groups=groups,
233
+ cur_group=s_node.group,
234
+ recursive=False,
235
+ **self.get_kwargs()
236
+ )
237
+ # add end group if new node where added OR if the previous end group was removed
238
+ end_group(self, cur_group=s_node.group, groups=groups, **self.get_kwargs())
239
+ # if two line then empty grou
240
+ if len(self.df_survey) > (2 + skip_header):
241
+ if cur_group == s_node.group:
242
+ # drop the end group (to merge)
243
+ logger.debug(
244
+ "printing same group {}::{}::{}::{}".format(
245
+ s_node.group.__class__,
246
+ s_node.group.get_name(),
247
+ s_node.id,
248
+ s_node.group.instance,
249
+ )
250
+ )
251
+ if len(df_survey_final):
252
+ df_survey_final.drop(
253
+ index=df_survey_final.index[-1], axis=0, inplace=True
254
+ )
255
+ self.df_survey = self.df_survey[(1 + skip_header) :]
256
+ df_survey_final = pd.concat(
257
+ [df_survey_final, self.df_survey], ignore_index=True
258
+ )
259
+
260
+ else:
261
+ logger.debug(
262
+ "printing group {}::{}::{}::{}".format(
263
+ s_node.group.__class__,
264
+ s_node.group.get_name(),
265
+ s_node.id,
266
+ s_node.group.instance,
267
+ )
268
+ )
269
+ df_survey_final = pd.concat(
270
+ [df_survey_final, self.df_survey], ignore_index=True
271
+ )
272
+ cur_group = s_node.group
273
+
274
+ # add the calulate
275
+ self.df_calculate = self.df_calculate.dropna(axis=0, subset=["calculation"])
276
+ df_empty_calc = self.df_calculate[self.df_calculate["calculation"] == ""]
277
+ self.df_survey.reset_index(drop=True, inplace=True)
278
+ self.df_calculate.reset_index(drop=True, inplace=True)
279
+ self.df_calculate = self.df_calculate.drop(df_empty_calc.index)
280
+ self.df_survey = pd.concat(
281
+ [df_survey_final, self.df_calculate], ignore_index=True
282
+ )
283
+ df_duplicate = self.df_calculate[
284
+ self.df_calculate.duplicated(subset=["calculation"], keep="first")
285
+ ]
286
+ # self.df_survey=self.df_survey.drop_duplicates(subset=['name'])
287
+ for index, drop_calc in df_duplicate.iterrows():
288
+ # remove the duplicate
289
+ replace_name = False
290
+ # find the actual calcualte
291
+ similar_calc = self.df_survey[
292
+ (drop_calc["calculation"] == self.df_survey["calculation"])
293
+ & (self.df_survey["type"] == "calculate")
294
+ ]
295
+ same_calc = self.df_survey[self.df_survey["name"] == drop_calc["name"]]
296
+ if len(same_calc) > 1:
297
+ # check if all calc have the same name
298
+ if len(same_calc) == len(similar_calc):
299
+ # drop all but one
300
+ self.df_survey.drop(same_calc.index[1:])
301
+ elif len(same_calc) < len(similar_calc):
302
+ self.df_survey.drop(same_calc.index)
303
+ replace_name = True
304
+ elif len(same_calc) == 1:
305
+ self.df_survey.drop(similar_calc.index)
306
+ replace_name = True
307
+
308
+ if replace_name:
309
+ save_calc = self.df_survey[
310
+ (drop_calc["calculation"] == self.df_survey["calculation"])
311
+ & (self.df_survey["type"] == "calculate")
312
+ ]
313
+ if len(save_calc) >= 1:
314
+ save_calc = save_calc.iloc[0]
315
+ if save_calc["name"] != drop_calc["name"]:
316
+ self.df_survey.replace(
317
+ "\$\{" + drop_calc["name"] + "\}",
318
+ "\$\{" + save_calc["name"] + "\}",
319
+ regex=True,
320
+ )
321
+ else:
322
+ logger.critical(
323
+ "duplicate reference not found for calculation: {}".format(
324
+ drop_calc["calculation"]
325
+ )
326
+ )
327
+ for index, empty_calc in df_empty_calc.iterrows():
328
+ self.df_survey.replace("\$\{" + empty_calc["name"] + "\}", "1", regex=True)
329
+
330
+ # TODO try to reinject calc to reduce complexity
331
+ for i, c in self.df_calculate[
332
+ ~self.df_calculate["name"].isin(self.df_survey["name"])
333
+ ].iterrows():
334
+ real_calc = re.find(r"^number\((.+)\)$", c["calculation"])
335
+ if real_calc is not None and real_calc != "":
336
+ self.df_survey[~self.df_survey["name"] == c["name"]].replace(
337
+ real_calc, "\$\{" + c["name"] + "\}"
338
+ )
339
+
340
+ df_duplicate = self.df_survey[
341
+ self.df_survey.duplicated(subset=["name"], keep="first")
342
+ ]
343
+ for index, duplicate in df_duplicate.iterrows():
344
+ logger.critical(f"duplicate survey name: {duplicate['name']}")
345
+ self.df_survey.reset_index(drop=True, inplace=True)
346
+ return processed_nodes
347
+
348
+ def get_tricc_operation_expression(self, operation):
349
+ ref_expressions = []
350
+ if not hasattr(operation, 'reference'):
351
+ return self.get_tricc_operation_operand(operation)
352
+
353
+ operator = getattr(operation, 'operator', '')
354
+ coalesce_fallback = OPERATOR_COALESCE_FALLBACK[operator] if operator in OPERATOR_COALESCE_FALLBACK else "''"
355
+ for r in operation.reference:
356
+ if isinstance(r, list):
357
+ r_expr = [
358
+ self.get_tricc_operation_expression(sr) if isinstance(sr, TriccOperation)
359
+ else self.get_tricc_operation_operand(sr,coalesce_fallback)
360
+ for sr in r
361
+ ]
362
+ elif isinstance(r, TriccOperation):
363
+ r_expr = self.get_tricc_operation_expression(r)
364
+ else:
365
+ r_expr = self.get_tricc_operation_operand(r,coalesce_fallback)
366
+ if isinstance(r_expr, TriccReference):
367
+ r_expr = self.get_tricc_operation_operand(r_expr,coalesce_fallback)
368
+ ref_expressions.append(r_expr)
369
+
370
+ # build lower level
371
+ if hasattr(self,f"tricc_operation_{operation.operator}"):
372
+ callable = getattr(self,f"tricc_operation_{operation.operator}")
373
+ return callable(ref_expressions)
374
+ else:
375
+ raise NotImplementedError(f"This type of opreation '{operation.operator}' is not supported in this strategy")
376
+
377
+ def tricc_operation_multiplied(self, ref_expressions):
378
+ return '*'.join(ref_expressions)
379
+ def tricc_operation_divided(self, ref_expressions):
380
+ return f"{ref_expressions[0]} div {ref_expressions[1]}"
381
+ def tricc_operation_modulo(self, ref_expressions):
382
+ return f"{ref_expressions[0]} mod {ref_expressions[1]}"
383
+ def tricc_operation_coalesce(self, ref_expressions):
384
+ return f"coalesce({','.join(map(self.clean_coalesce, ref_expressions))})"
385
+ def tricc_operation_module(self, ref_expressions):
386
+ return f"{ref_expressions[0]} mod {ref_expressions[1]}"
387
+ def tricc_operation_minus(self, ref_expressions):
388
+ if len(ref_expressions)>1:
389
+ return ' - '.join(ref_expressions)
390
+ elif len(ref_expressions)==1:
391
+ return f'-{ref_expressions[0]}'
392
+ def tricc_operation_plus(self, ref_expressions):
393
+ return ' + '.join(ref_expressions)
394
+ def tricc_operation_not(self, ref_expressions):
395
+ return f"not({ref_expressions[0]})"
396
+ def tricc_operation_and(self, ref_expressions):
397
+ if len(ref_expressions) == 1:
398
+ return ref_expressions[0]
399
+ if len(ref_expressions)>1:
400
+ ref_expressions = [f"({r})" if isinstance(r, str) and any(op in r for op in [' or ',' + ',' - '])else r for r in ref_expressions]
401
+ return ' and '.join(map(str, ref_expressions))
402
+ else:
403
+ return '1'
404
+
405
+ def tricc_operation_or(self, ref_expressions):
406
+ if len(ref_expressions) == 1:
407
+ return ref_expressions[0]
408
+ if len(ref_expressions)>1:
409
+ ref_expressions = [f"({r})" if isinstance(r, str) and any(op in r for op in [' and ',' + ',' - ']) else r for r in ref_expressions]
410
+ return ' or '.join(map(str, ref_expressions))
411
+ else:
412
+ return '1'
413
+
414
+
415
+
416
+ def tricc_operation_native(self, ref_expressions):
417
+ if len(ref_expressions)>0:
418
+ if ref_expressions[0] =='GetChoiceName':
419
+ return f"jr:choice-name({self.clean_coalesce(ref_expressions[1])}, '{self.clean_coalesce(ref_expressions[2])}')"
420
+ elif ref_expressions[0] =='GetFacilityParam':
421
+ return '0'
422
+ #return f"jr:choice-name({','.join(ref_expressions[1:])})"
423
+ else:
424
+ return f"{ref_expressions[0]}({','.join(ref_expressions[1:])})"
425
+
426
+ def tricc_operation_istrue(self, ref_expressions):
427
+ if str(BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]).isnumeric():
428
+ return f"{ref_expressions[0]}>={BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
429
+ else:
430
+ return f"{ref_expressions[0]}={BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
431
+ def tricc_operation_isfalse(self, ref_expressions):
432
+ if str(BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]).isnumeric():
433
+ return f"{ref_expressions[0]}={BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
434
+ else:
435
+ return f"{ref_expressions[0]}={BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
436
+ def tricc_operation_parenthesis(self, ref_expressions):
437
+ return f"({ref_expressions[0]})"
438
+ def tricc_operation_selected(self, ref_expressions):
439
+ parts = []
440
+ for s in ref_expressions[1:]:
441
+ # for option with numeric value
442
+ cleaned_s = s if isinstance(s, str) else '\''+str(s)+'\''
443
+ parts.append(f"selected({self.clean_coalesce(ref_expressions[0])}, {cleaned_s})")
444
+ if len(parts) == 1:
445
+ return parts[0]
446
+ else:
447
+ return self.tricc_operation_or(parts)
448
+ def tricc_operation_more_or_equal(self, ref_expressions):
449
+ return f"{ref_expressions[0]}>={ref_expressions[1]}"
450
+ def tricc_operation_less_or_equal(self, ref_expressions):
451
+ return f"{ref_expressions[0]}<={ref_expressions[1]}"
452
+ def tricc_operation_more(self, ref_expressions):
453
+ return f"{ref_expressions[0]}>{ref_expressions[1]}"
454
+ def tricc_operation_less(self, ref_expressions):
455
+ return f"{ref_expressions[0]}<{ref_expressions[1]}"
456
+ def tricc_operation_between(self, ref_expressions):
457
+ return f"{ref_expressions[0]}>={ref_expressions[1]} and {ref_expressions[0]} < {ref_expressions[2]}"
458
+ def tricc_operation_equal(self, ref_expressions):
459
+ return f"{ref_expressions[0]}={ref_expressions[1]}"
460
+ def tricc_operation_not_equal(self, ref_expressions):
461
+ return f"{ref_expressions[0]}!={ref_expressions[1]}"
462
+ def tricc_operation_isnull(self, ref_expressions):
463
+ return f"{ref_expressions[0]}=''"
464
+ def tricc_operation_isnotnull(self, ref_expressions):
465
+ return f"{ref_expressions[0]}!=''"
466
+
467
+ def tricc_operation_isnottrue(self, ref_expressions):
468
+ if str(BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]).isnumeric():
469
+ return f"{ref_expressions[0]}<{BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
470
+ else:
471
+ return f"{ref_expressions[0]}!={BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
472
+ def tricc_operation_isnotfalse(self, ref_expressions):
473
+ if str(BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]).isnumeric():
474
+ return f"{ref_expressions[0]}>{BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
475
+ else:
476
+ return f"{ref_expressions[0]}!={BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
477
+ def tricc_operation_notexist(self, ref_expressions):
478
+ return f"{ref_expressions[0]}=''"
479
+
480
+
481
+
482
+ def tricc_operation_case(self, ref_expressions):
483
+ ifs = 0
484
+ parts = []
485
+ else_found = False
486
+ if not isinstance(ref_expressions[0], list):
487
+ return self.tricc_operation_ifs(ref_expressions)
488
+ for i in range(int(len(ref_expressions))):
489
+ if isinstance(ref_expressions[i], list):
490
+ parts.append(f"if({ref_expressions[i][0]},{ref_expressions[i][1]}")
491
+ ifs += 1
492
+ else:
493
+ else_found = True
494
+ parts.append(ref_expressions[i])
495
+ #join the if
496
+ exp = ','.join(map(str,parts))
497
+ # in case there is no default put ''
498
+ if not else_found:
499
+ exp += ",''"
500
+ #add the closing )
501
+ for i in range(ifs):
502
+ exp += ")"
503
+ return exp
504
+
505
+ def tricc_operation_ifs(self, ref_expressions):
506
+ ifs = 0
507
+ parts = []
508
+ else_found = False
509
+ for i in range(int(len(ref_expressions[1:]))):
510
+ if isinstance(ref_expressions[i+1], list):
511
+ parts.append(f"if({ref_expressions[0]}={ref_expressions[i+1][0]},{ref_expressions[i+1][1]}")
512
+ ifs += 1
513
+ else:
514
+ else_found = True
515
+ parts.append(ref_expressions[i+1])
516
+ #join the if
517
+ exp = ','.join(parts)
518
+ # in case there is no default put ''
519
+ if not else_found:
520
+ exp += ",''"
521
+ #add the closing )
522
+ for i in range(ifs):
523
+ exp += ")"
524
+ return exp
525
+
526
+ def tricc_operation_if(self, ref_expressions):
527
+ return f"if({ref_expressions[0]},{ref_expressions[1]},{ref_expressions[2]})"
528
+
529
+ def tricc_operation_contains(self, ref_expressions):
530
+ return f"contains('{self.clean_coalesce(ref_expressions[0])}', '{self.clean_coalesce(ref_expressions[1])}')"
531
+
532
+ def tricc_operation_exists(self, ref_expressions):
533
+ parts = []
534
+ for ref in ref_expressions:
535
+ parts.append(self.tricc_operation_not_equal([self.tricc_operation_coalesce([ref, "''"]), "''"]))
536
+ return self.tricc_operation_and(parts)
537
+
538
+ def tricc_operation_cast_number(self, ref_expressions):
539
+ if isinstance(ref_expressions[0], (int, float,)):
540
+ return f"{ref_expressions[0]}"
541
+ elif not ref_expressions or ref_expressions[0] == '':
542
+ logger.warning("empty cast number")
543
+ return '0'
544
+ elif ref_expressions[0] == 'true' or ref_expressions[0] is True or ref_expressions[0] == BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]:
545
+ return 1
546
+ elif ref_expressions[0] == 'false' or ref_expressions[0] is False or ref_expressions[0] == BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]:
547
+ return 0
548
+ else:
549
+ return f"number({ref_expressions[0]})"
550
+
551
+ def tricc_operation_cast_integer(self, ref_expressions):
552
+ if isinstance(ref_expressions[0], (int, float,)):
553
+ return f"{ref_expressions[0]}"
554
+ elif not ref_expressions or ref_expressions[0] == '':
555
+ logger.warning("empty cast number")
556
+ return '0'
557
+ elif ref_expressions[0] == 'true' or ref_expressions[0] is True or ref_expressions[0] == BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]:
558
+ return 1
559
+ elif ref_expressions[0] == 'false' or ref_expressions[0] is False or ref_expressions[0] == BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]:
560
+ return 0
561
+ else:
562
+ return f"int({ref_expressions[0]})"
563
+ def tricc_operation_zscore(self, ref_expressions):
564
+ y, ll, m, s = self.get_zscore_params(ref_expressions)
565
+ # return ((Math.pow((y / m), l) - 1) / (s * l));
566
+ return f"(pow({y} div ({m}), {ll}) -1) div (({s}) div ({ll}))"
567
+
568
+
569
+ def tricc_operation_izscore(self, ref_expressions):
570
+ z, ll, m, s = self.get_zscore_params(ref_expressions)
571
+ # return (m * (z*s*l-1)^(1/l));
572
+ return f"pow({m} * ({z} * {s} * {ll} -1), 1 div {ll})"
573
+
574
+ def get_zscore_params(self, ref_expressions):
575
+ table = ref_expressions[0]
576
+ sex = clean_name(ref_expressions[1])
577
+ x = clean_name(ref_expressions[2])
578
+ yz = clean_name(ref_expressions[3])
579
+ ll = (
580
+ f"number(instance({table})/root/item[sex={sex} and x_max>"
581
+ + x
582
+ + " and x_min<="
583
+ + x
584
+ + "]/l)"
585
+ )
586
+ m = (
587
+ f"number(instance({table})/root/item[sex={sex} and x_max>"
588
+ + x
589
+ + " and x_min<="
590
+ + x
591
+ + "]/m)"
592
+ )
593
+ s = (
594
+ f"number(instance({table})/root/item[sex={sex} and x_max>"
595
+ + x
596
+ + " and x_min<="
597
+ + x
598
+ + "]/s)"
599
+ )
600
+ return yz, ll, m, s
601
+
602
+
603
+
604
+ # function update the calcualte in the XLSFORM format
605
+ # @param left part
606
+ # @param right part
607
+ def generate_xls_form_calculate(self, node, processed_nodes, stashed_nodes, **kwargs):
608
+ if is_ready_to_process(node, processed_nodes):
609
+ if node not in processed_nodes:
610
+ if kwargs.get('warn', True):
611
+ logger.debug("generation of calculate for node {}".format(node.get_name()))
612
+ if hasattr(node, 'expression') and (node.expression is None) and issubclass(node.__class__,TriccNodeCalculateBase):
613
+ node.expression = get_node_expressions(node, processed_nodes, process=kwargs.get('process', 'main '))
614
+ # continue walk
615
+ return True
616
+ return False
617
+
618
+ # function update the select node in the XLSFORM format
619
+ # @param left part
620
+ # @param right part
621
+ def generate_xls_form_condition(self, node, processed_nodes, stashed_nodes, calculates, **kwargs):
622
+ if is_ready_to_process(node, processed_nodes, strict=False) and process_reference(node, processed_nodes, calculates, replace_reference=False, codesystems= kwargs.get('codesystems', None)):
623
+ if node not in processed_nodes:
624
+ if issubclass(node.__class__, TriccRhombusMixIn) and isinstance(node.reference, str):
625
+ logger.warning("node {} still using the reference string".format(node.get_name()))
626
+ if issubclass(node.__class__, TriccNodeInputModel):
627
+ # we don't overright if define in the diagram
628
+ if node.constraint is None:
629
+ if isinstance(node, TriccNodeSelectMultiple):
630
+ node.constraint = TriccOperation(
631
+ TriccOperator.OR,
632
+ [
633
+ TriccOperation(TriccOperator.EQUAL, ['$this', TriccStatic('opt_none')]),
634
+ TriccOperation(TriccOperator.NOT, [
635
+ TriccOperation(TriccOperator.SELECTED,
636
+ [
637
+ '$this', TriccStatic('opt_none')
638
+ ])
639
+ ])
640
+ ]
641
+ )#'.=\'opt_none\' or not(selected(.,\'opt_none\'))'
642
+ node.constraint_message = '**None** cannot be selected together with choice.'
643
+ elif node.tricc_type in (TriccNodeType.integer, TriccNodeType.decimal):
644
+ constraints = []
645
+ constraints_min = ''
646
+ constraints_max = ''
647
+ if node.min is not None and node.min != '':
648
+ constraints.append(TriccOperation(TriccOperator.MORE_OR_EQUAL, ['$this', node.min]))
649
+ constraints_min= "The minimun value is {0}.".format(node.min)
650
+ if node.max is not None and node.max != '':
651
+ constraints.append(TriccOperation(TriccOperator.LESS_OR_EQUAL, ['$this', node.max]))
652
+ constraints_max="The maximum value is {0}.".format(node.max)
653
+ if len(constraints) > 1:
654
+ node.constraint = TriccOperation(TriccOperator.AND, constraints)
655
+ node.constraint_message = (constraints_min + " " + constraints_max).strip()
656
+ elif len(constraints) == 1:
657
+ node.constraint = constraints[0]
658
+ node.constraint_message = (constraints_min + " " + constraints_max).strip()
659
+ # continue walk
660
+ return True
661
+ return False
662
+
663
+ # function transform an object to XLSFORM value
664
+ # @param r reference to be translated
665
+ def get_tricc_operation_operand(self,r, coalesce_fallback="''"):
666
+ if isinstance(r, TriccOperation):
667
+ return self.get_tricc_operation_expression(r)
668
+ elif isinstance(r, TriccReference):
669
+ logger.warning(f"reference still used in the calculate {r.value}")
670
+ return f"${{{get_export_name(r.value)}}}"
671
+ elif isinstance(r, TriccStatic):
672
+ if isinstance(r.value, bool) :#or r.value in ('true', 'false')
673
+ return BOOLEAN_MAP[str(TRICC_TRUE_VALUE)] if r.value else BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]
674
+ if r.value == TRICC_TRUE_VALUE:
675
+ return BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]
676
+ if r.value == TRICC_FALSE_VALUE:
677
+ return BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]
678
+ if isinstance(r.value, str):
679
+ return f"'{r.value}'"
680
+ else:
681
+ return str(r.value)
682
+ elif isinstance(r, str):
683
+ return f"{r}"
684
+ elif isinstance(r, (int, float)):
685
+ return str(r)
686
+ elif isinstance(r, TriccNodeSelectOption):
687
+ logger.warning(f"select option {r.get_name()} from {r.select.get_name()} was used as a reference")
688
+ return f"'{r.name}'"
689
+ elif issubclass(r.__class__, TriccNodeInputModel):
690
+ return f"coalesce(${{{get_export_name(r)}}},{coalesce_fallback})"
691
+ elif issubclass(r.__class__, TriccNodeBaseModel):
692
+ return f"${{{get_export_name(r)}}}"
693
+ else:
694
+ raise NotImplementedError(f"This type of node {r.__class__} is not supported within an operation")
695
+
696
+ def tricc_operation_concatenate(self, ref_expressions):
697
+ return f"concat({','.join(ref_expressions)})"