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