tricc-oo 1.6.5.dev1__tar.gz → 1.6.6__tar.gz

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 (57) hide show
  1. {tricc_oo-1.6.5.dev1/tricc_oo.egg-info → tricc_oo-1.6.6}/PKG-INFO +1 -1
  2. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/pyproject.toml +1 -1
  3. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/converters/tricc_to_xls_form.py +2 -0
  4. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/models/base.py +2 -2
  5. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/serializers/xls_form.py +2 -2
  6. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/strategies/output/dhis2_form.py +76 -27
  7. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/strategies/output/xls_form.py +13 -4
  8. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/strategies/output/xlsform_cht.py +3 -0
  9. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/visitors/tricc.py +78 -35
  10. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6/tricc_oo.egg-info}/PKG-INFO +1 -1
  11. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo.egg-info/top_level.txt +0 -1
  12. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/LICENSE +0 -0
  13. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/README.md +0 -0
  14. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/setup.cfg +0 -0
  15. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tests/build.py +0 -0
  16. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tests/test_build.py +0 -0
  17. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tests/test_cql.py +0 -0
  18. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tests/to_ocl.py +0 -0
  19. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/__init__.py +0 -0
  20. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/converters/__init__.py +0 -0
  21. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/converters/codesystem_to_ocl.py +0 -0
  22. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/converters/cql/cqlLexer.py +0 -0
  23. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/converters/cql/cqlListener.py +0 -0
  24. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/converters/cql/cqlParser.py +0 -0
  25. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/converters/cql/cqlVisitor.py +0 -0
  26. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/converters/cql_to_operation.py +0 -0
  27. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/converters/datadictionnary.py +0 -0
  28. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/converters/drawio_type_map.py +0 -0
  29. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/converters/utils.py +0 -0
  30. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/converters/xml_to_tricc.py +0 -0
  31. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/models/__init__.py +0 -0
  32. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/models/calculate.py +0 -0
  33. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/models/lang.py +0 -0
  34. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/models/ocl.py +0 -0
  35. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/models/ordered_set.py +0 -0
  36. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/models/tricc.py +0 -0
  37. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/parsers/__init__.py +0 -0
  38. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/parsers/xml.py +0 -0
  39. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/serializers/__init__.py +0 -0
  40. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/serializers/planuml.py +0 -0
  41. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/strategies/__init__.py +0 -0
  42. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/strategies/input/__init__.py +0 -0
  43. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/strategies/input/base_input_strategy.py +0 -0
  44. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/strategies/input/drawio.py +0 -0
  45. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/strategies/output/base_output_strategy.py +0 -0
  46. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/strategies/output/fhir_form.py +0 -0
  47. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/strategies/output/html_form.py +0 -0
  48. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/strategies/output/openmrs_form.py +0 -0
  49. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/strategies/output/spice.py +0 -0
  50. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/strategies/output/xlsform_cdss.py +0 -0
  51. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/strategies/output/xlsform_cht_hf.py +0 -0
  52. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/visitors/__init__.py +0 -0
  53. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/visitors/utils.py +0 -0
  54. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo/visitors/xform_pd.py +0 -0
  55. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo.egg-info/SOURCES.txt +0 -0
  56. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo.egg-info/dependency_links.txt +0 -0
  57. {tricc_oo-1.6.5.dev1 → tricc_oo-1.6.6}/tricc_oo.egg-info/requires.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tricc-oo
3
- Version: 1.6.5.dev1
3
+ Version: 1.6.6
4
4
  Summary: Python library that converts CDSS L2 in L3
5
5
  Project-URL: Homepage, https://github.com/SwissTPH/tricc
6
6
  Project-URL: Issues, https://github.com/SwissTPH/tricc/issues
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tricc-oo"
7
- version = "1.6.5.dev001"
7
+ version = "1.6.6"
8
8
  description = "Python library that converts CDSS L2 in L3"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -43,6 +43,8 @@ def get_export_name(node, replace_dots=True):
43
43
  value = node.name
44
44
  elif isinstance(node, TriccStatic):
45
45
  value = node.value
46
+ if isinstance(value, TriccNodeSelectOption):
47
+ value = value.name
46
48
  else:
47
49
  value = node
48
50
  if isinstance(value, bool): # or r.value in ('true', 'false')
@@ -11,7 +11,7 @@ from tricc_oo.models.ordered_set import OrderedSet
11
11
 
12
12
  logger = logging.getLogger("default")
13
13
 
14
- Expression = Annotated[str, StringConstraints(pattern=r"^[^\\/\:]+$")]
14
+ Expression = Annotated[str, StringConstraints(pattern=r".+")]
15
15
 
16
16
  triccId = Annotated[str, StringConstraints(pattern=r"^[^\\/\: ]+$")]
17
17
 
@@ -274,7 +274,7 @@ class TriccNodeBaseModel(TriccBaseModel):
274
274
 
275
275
 
276
276
  class TriccStatic(BaseModel):
277
- value: Union[str, float, int, bool]
277
+ value: Union[str, float, int, bool, TriccNodeBaseModel]
278
278
 
279
279
  def __init__(self, value):
280
280
  super().__init__(value=value)
@@ -123,7 +123,7 @@ def start_group(
123
123
  value = get_export_name(calc)
124
124
  calc_values.append(value)
125
125
  elif column == "calculation":
126
- calc_values.append(strategy.get_tricc_operation_expression(calc.expression))
126
+ calc_values.append(f"number({strategy.get_tricc_operation_expression(calc.expression)}")
127
127
  elif column == "relevance":
128
128
  calc_values.append("")
129
129
  else:
@@ -415,7 +415,7 @@ def get_more_info_select(strategy, node):
415
415
  if column == "type":
416
416
  values.append("select_one more_info")
417
417
  elif column == "label":
418
- values.append("NO_LABEL")
418
+ values.append(strategy.get_empty_label())
419
419
  elif column == "name":
420
420
  values.append(get_export_name(node) + "_optin")
421
421
  elif column == "hint":
@@ -25,6 +25,8 @@ from tricc_oo.models.tricc import (
25
25
  TriccNodeDisplayModel,
26
26
  TriccNodeCalculateBase,
27
27
  TriccNodeActivity,
28
+ TriccNodeSelect,
29
+ TriccNodeSelectYesNo,
28
30
  )
29
31
  from tricc_oo.models.calculate import TriccNodeDisplayCalculateBase
30
32
  from tricc_oo.models.ordered_set import OrderedSet
@@ -66,18 +68,22 @@ class DHIS2Strategy(BaseOutPutStrategy):
66
68
 
67
69
  def get_export_name(self, r):
68
70
  if isinstance(r, TriccNodeSelectOption):
69
- return self.get_option_value(r.name)
71
+ ret = self.get_option_value(r.name)
70
72
  elif isinstance(r, str):
71
- return self.get_option_value(r)
73
+ ret = self.get_option_value(r)
72
74
  elif isinstance(r, TriccStatic):
73
75
  if isinstance(r.value, str):
74
- return self.get_option_value(r.value)
76
+ ret = self.get_option_value(r.value)
75
77
  elif isinstance(r.value, bool):
76
- return str(r.value).lower()
78
+ ret = str(r.value).lower()
77
79
  else:
78
- return r.value
80
+ ret = r.value
81
+ else:
82
+ ret = get_export_name(r)
83
+ if isinstance(ret, str):
84
+ return ret[:50]
79
85
  else:
80
- return get_export_name(r)
86
+ return ret
81
87
 
82
88
  def generate_id(self, name):
83
89
  """Generate DHIS2-compliant UID: 1 letter + 10 alphanumeric characters"""
@@ -139,7 +145,15 @@ class DHIS2Strategy(BaseOutPutStrategy):
139
145
  raise NotImplementedError(
140
146
  f"This type of operation '{operation.operator}' is not supported in this strategy"
141
147
  )
142
-
148
+ def get_display(self, node):
149
+ if hasattr(node, 'label') and node.label:
150
+ ret = node.label
151
+ elif hasattr(node, 'name') and node.name:
152
+ ret = node.name
153
+ else:
154
+ ret = str(node.id)
155
+ return ret.replace('\u00a0', ' ').strip()
156
+
143
157
  def execute(self):
144
158
  version = datetime.datetime.now().strftime("%Y%m%d%H%M")
145
159
  logger.info(f"build version: {version}")
@@ -223,11 +237,13 @@ class DHIS2Strategy(BaseOutPutStrategy):
223
237
  self.program_rule_actions.append(program_rule_action)
224
238
 
225
239
  # Create program rule referencing the action
240
+ condition = self.simplify_expression(f"!({relevance_str})") # Negate for hide when true
241
+ condition = self.simplify_expression(condition)
226
242
  self.program_rules.append({
227
243
  "id": rule_id,
228
- "name": f"Hide {node.get_name()} when condition met",
229
- "description": f"Hide {node.get_name()} based on relevance",
230
- "condition": f"!({relevance_str})", # Negate for hide when true
244
+ "name": f"Hide `{self.get_export_name(node)}` when condition met",
245
+ "description": f"Hide `{self.get_display(node)}` based on relevance",
246
+ "condition": condition,
231
247
  "programRuleActions": [{"id": action_id}]
232
248
  })
233
249
  return True
@@ -257,11 +273,15 @@ class DHIS2Strategy(BaseOutPutStrategy):
257
273
  "id": de_id,
258
274
  "name": self.get_export_name(node),
259
275
  "shortName": node.name[:50],
260
- "displayFormName": getattr(node, 'label', node.name).replace('\u00a0', ' ').strip(),
276
+ "displayFormName":self.get_display(node),
277
+ "formName": self.get_display(node),
261
278
  "valueType": value_type,
262
279
  "domainType": "TRACKER",
263
280
  "aggregationType": "NONE"
264
281
  }
282
+
283
+ if issubclass(node.__class__, TriccNodeSelect) and not isinstance(node, TriccNodeSelectYesNo):
284
+ data_element["optionSetValue"] = True
265
285
 
266
286
  # Only create optionSet for non-boolean select questions
267
287
  if node.tricc_type in ['select_one', 'select_multiple'] and not is_boolean_question:
@@ -273,7 +293,7 @@ class DHIS2Strategy(BaseOutPutStrategy):
273
293
  # Create the actual optionSet definition
274
294
  option_set = {
275
295
  "id": option_set_id,
276
- "name": f"{node.name} Options",
296
+ "name": f"{self.get_export_name(node)} Options",
277
297
  "shortName": f"{node.name}_opts"[:50],
278
298
  "valueType": "TEXT",
279
299
  "options": []
@@ -288,10 +308,10 @@ class DHIS2Strategy(BaseOutPutStrategy):
288
308
  option_name = option_name.replace('\u00a0', ' ').strip()
289
309
  elif isinstance(option_name, TriccStatic):
290
310
  option_name = str(option_name.value)
291
- # Create separate option entity
311
+ # Create separate option entityif
292
312
  option_def = {
293
313
  "id": option_id,
294
- "name": option_name,
314
+ "name": self.get_display(option),
295
315
  "shortName": option.name[:50],
296
316
  "code": str(self.get_export_name(option))
297
317
  }
@@ -303,6 +323,20 @@ class DHIS2Strategy(BaseOutPutStrategy):
303
323
  self.option_sets[option_set_id] = option_set
304
324
 
305
325
  self.data_elements[node.name] = data_element
326
+
327
+ # Create program rule variable for this data element
328
+ var_id = self.generate_id(f"var_{node.name}")
329
+ var_name = self.get_export_name(node)
330
+ program_rule_variable = {
331
+ "id": var_id,
332
+ "name": var_name,
333
+ "programRuleVariableSourceType": "DATAELEMENT_CURRENT_EVENT",
334
+ "dataElement": {"id": de_id},
335
+ "program": {"id": self.program_metadata["id"]}
336
+ }
337
+ self.program_rule_variables.append(program_rule_variable)
338
+ self.concept_map[node.name] = var_name # Store variable name for #{var_name} references
339
+
306
340
  return data_element
307
341
  return None
308
342
 
@@ -325,9 +359,10 @@ class DHIS2Strategy(BaseOutPutStrategy):
325
359
  mock_node = MockNode(operation_datatype)
326
360
  data_type = self.map_tricc_type_to_dhis2_value_type(mock_node)
327
361
 
362
+ var_name = self.get_export_name(node)
328
363
  program_rule_variable = {
329
364
  "id": var_id,
330
- "name": self.get_export_name(node.name)[:50],
365
+ "name": var_name,
331
366
  "programRuleVariableSourceType": "CALCULATED_VALUE",
332
367
  "calculatedValueScript": expression_str,
333
368
  "dataType": data_type,
@@ -336,7 +371,7 @@ class DHIS2Strategy(BaseOutPutStrategy):
336
371
  }
337
372
  self.program_rule_variables.append(program_rule_variable)
338
373
  # Add to concept map for potential referencing
339
- self.concept_map[node.name] = var_id
374
+ self.concept_map[node.name] = var_name # Store variable name
340
375
  return True
341
376
  return False
342
377
 
@@ -674,7 +709,7 @@ class DHIS2Strategy(BaseOutPutStrategy):
674
709
  if isinstance(r, TriccOperation):
675
710
  return self.get_tricc_operation_expression(r)
676
711
  elif isinstance(r, TriccReference):
677
- # Use DHIS2 ID from concept_map instead of name
712
+ # Use variable name from concept_map
678
713
  node_id = self.concept_map.get(r.value.name, self.get_export_name(r.value))
679
714
  return f"#{{{node_id}}}"
680
715
  elif isinstance(r, TriccStatic):
@@ -696,25 +731,39 @@ class DHIS2Strategy(BaseOutPutStrategy):
696
731
  return option
697
732
  return f"'{option}'"
698
733
  elif issubclass(r.__class__, TriccNodeDisplayCalculateBase):
699
- # Use DHIS2 ID from concept_map instead of name
700
- node_id = self.concept_map.get(r.name, self.get_export_name(r))
701
- return f"#{node_id}"
734
+ # Use variable name from concept_map
735
+ node_id = self.get_export_name(r)
736
+ return f"#{{{node_id}}}"
737
+ elif issubclass(r.__class__, TriccNodeCalculateBase):
738
+ # Use variable name from concept_map
739
+ node_id = self.get_export_name(r)
740
+ return f"#{{{node_id}}}"
702
741
  elif issubclass(r.__class__, TriccNodeInputModel):
703
- # Use DHIS2 ID from concept_map instead of name
704
- node_id = self.concept_map.get(r.name, self.get_export_name(r))
742
+ # Use variable name from concept_map
743
+ node_id = self.get_export_name(r)
705
744
  return f"#{{{node_id}}}"
706
745
  elif issubclass(r.__class__, TriccNodeBaseModel):
707
- # Use DHIS2 ID from concept_map instead of name
708
- node_id = self.concept_map.get(r.name, self.get_export_name(r))
746
+ # Use variable name from concept_map
747
+ node_id = self.get_export_name(r)
709
748
  return f"#{{{node_id}}}"
710
749
  else:
711
750
  raise NotImplementedError(f"This type of node {r.__class__.__name__} is not supported within an operation")
712
751
 
752
+ def simplify_expression(self, expr):
753
+ while expr.startswith('!(!(') and expr.endswith('))'):
754
+ expr = expr[4:-2]
755
+ return expr
756
+
713
757
  def convert_expression_to_string(self, expression):
714
758
  if isinstance(expression, TriccOperation):
715
- return self.get_tricc_operation_expression(expression)
759
+ expr = self.get_tricc_operation_expression(expression)
716
760
  else:
717
- return self.get_tricc_operation_operand(expression)
761
+ expr = self.get_tricc_operation_operand(expression)
762
+
763
+ # Simplify double negations
764
+ expr = self.simplify_expression(expr)
765
+
766
+ return expr
718
767
 
719
768
  # Operation methods for DHIS2 expressions
720
769
  def tricc_operation_equal(self, ref_expressions):
@@ -765,7 +814,7 @@ class DHIS2Strategy(BaseOutPutStrategy):
765
814
 
766
815
  def tricc_operation_selected(self, ref_expressions):
767
816
  # For DHIS2, check if value is selected in multi-select
768
- return f"d2:hasValue({ref_expressions[0]}) && d2:contains({ref_expressions[0]}, {ref_expressions[1]})"
817
+ return f"d2:countIfValue({ref_expressions[0]}, {ref_expressions[1]})>0"
769
818
 
770
819
  def tricc_operation_count(self, ref_expressions):
771
820
  return f"d2:count({ref_expressions[0]})"
@@ -495,7 +495,12 @@ class XLSFormStrategy(BaseOutPutStrategy):
495
495
  parts = []
496
496
  for s in ref_expressions[1:]:
497
497
  # for option with numeric value
498
- cleaned_s = s if isinstance(s, str) else "'" + str(s) + "'"
498
+ if isinstance(s, str):
499
+ cleaned_s = s
500
+ elif isinstance(s, TriccNodeSelectOption):
501
+ cleaned_s = s.name
502
+ else:
503
+ cleaned_s = "'" + str(s) + "'"
499
504
  parts.append(f"selected({self.clean_coalesce(ref_expressions[0])}, {cleaned_s})")
500
505
  if len(parts) == 1:
501
506
  return parts[0]
@@ -589,11 +594,14 @@ class XLSFormStrategy(BaseOutPutStrategy):
589
594
  exp += ")"
590
595
  return exp
591
596
 
597
+ def get_empty_label(self):
598
+ return "."
599
+
592
600
  def tricc_operation_if(self, ref_expressions):
593
601
  return f"if({ref_expressions[0]},{ref_expressions[1]},{ref_expressions[2]})"
594
602
 
595
603
  def tricc_operation_contains(self, ref_expressions):
596
- return f"contains('{self.clean_coalesce(ref_expressions[0])}', '{self.clean_coalesce(ref_expressions[1])}')"
604
+ return f"contains({self.clean_coalesce(ref_expressions[0])}, {self.clean_coalesce(ref_expressions[1])})"
597
605
 
598
606
  def tricc_operation_exists(self, ref_expressions):
599
607
  parts = []
@@ -721,11 +729,12 @@ class XLSFormStrategy(BaseOutPutStrategy):
721
729
  # @param r reference to be translated
722
730
  if isinstance(r, TriccOperation):
723
731
  return self.get_tricc_operation_expression(r)
732
+ elif isinstance(r, (TriccStatic, str, int, float)):
733
+ return get_export_name(r)
724
734
  elif isinstance(r, TriccReference):
725
735
  logger.warning(f"reference `{r.value}` still used in a calculate")
726
736
  return f"${{{get_export_name(r.value)}}}"
727
- elif isinstance(r, (TriccStatic, str, int, float)):
728
- return get_export_name(r)
737
+
729
738
  elif isinstance(r, TriccNodeSelectOption):
730
739
  logger.debug(f"select option {r.get_name()} from {r.select.get_name()} was used as a reference")
731
740
  return get_export_name(r)
@@ -33,6 +33,9 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
33
33
 
34
34
  self.inject_version()
35
35
 
36
+ def get_empty_label(self):
37
+ return "NO_LABEL"
38
+
36
39
  def get_cht_input(self, start_pages, **kwargs):
37
40
  empty = langs.get_trads("", force_dict=True)
38
41
  df_input = pd.DataFrame(columns=SURVEY_MAP.keys())
@@ -542,6 +542,32 @@ def generate_calculates(node, calculates, used_calculates, processed_nodes, proc
542
542
  for calc in list_calc:
543
543
  node.activity.nodes[calc.id] = calc
544
544
  add_calculate(calculates, calc)
545
+
546
+ # Add CONTAINS calculations for each option in select multiple (except opt_none)
547
+ if isinstance(node, TriccNodeSelectMultiple):
548
+ for option in node.options.values():
549
+ if not option.name.startswith("opt_"):
550
+ calc_id = generate_id(f"contains_{node.id}_{option.name}")
551
+ expression = TriccOperation(TriccOperator.CONTAINS, [node, TriccStatic(option.name)])
552
+ calc_node = TriccNodeCalculate(
553
+ name=option.name,
554
+ id=calc_id,
555
+ group=node.group,
556
+ activity=node.activity,
557
+ label=f"contains: {node.get_name()} contains '{option.name}'",
558
+ path_len=node.path_len + 1,
559
+ last=True,
560
+ expression=expression,
561
+ )
562
+ node.activity.calculates.append(calc_node)
563
+ last_version = set_last_version_false(calc_node, processed_nodes)
564
+ if last_version:
565
+ calc_node.expression = merge_expression(calc_node.expression, last_version)
566
+ processed_nodes.add(calc_node)
567
+ list_calc.append(calc_node)
568
+ node.activity.nodes[calc_node.id] = calc_node
569
+ add_calculate(calculates, calc_node)
570
+
545
571
  return list_calc
546
572
 
547
573
 
@@ -1711,6 +1737,7 @@ PARENT_GROUP_PRIORITY = 6000
1711
1737
  ACTIVE_ACTIVITY_PRIORITY = 5000
1712
1738
  NON_START_ACTIVITY_PRIORITY = 4000
1713
1739
  ACTIVE_ACTIVITY_LOWER_PRIORITY = 3000
1740
+ FLOW_CALCULATE_NODE_PRIORITY = 6500
1714
1741
  RHOMBUS_PRIORITY = 1000
1715
1742
  DEFAULT_PRIORITY = 2000
1716
1743
 
@@ -1737,6 +1764,9 @@ def reorder_node_list(node_list, group, processed_nodes):
1737
1764
  # Check for non main activities
1738
1765
  elif activity and isinstance(activity.root, TriccNodeActivityStart):
1739
1766
  priority += NON_START_ACTIVITY_PRIORITY
1767
+ # Check for display calculate and end nodes with prev_nodes
1768
+ elif (issubclass(node.__class__, TriccNodeDisplayCalculateBase) or isinstance(node, TriccNodeEnd)) and not isinstance(node, TriccNodeActivityEnd) and hasattr(node, 'prev_nodes') and len(node.prev_nodes) > 0:
1769
+ priority += FLOW_CALCULATE_NODE_PRIORITY
1740
1770
  # Check for active activities (lower priority)
1741
1771
  elif activity and activity in active_activities:
1742
1772
  priority += ACTIVE_ACTIVITY_LOWER_PRIORITY
@@ -1998,7 +2028,7 @@ def get_accept_diagnostic_node(code, display, severity, priority, activity):
1998
2028
  return node
1999
2029
 
2000
2030
 
2001
- def get_diagnostic_node(code, display, severity, priority, activity):
2031
+ def get_diagnostic_node(code, display, severity, priority, activity, option):
2002
2032
  node = TriccNodeCalculate(
2003
2033
  id=generate_id("final." + code),
2004
2034
  name="final." + code,
@@ -2009,7 +2039,7 @@ def get_diagnostic_node(code, display, severity, priority, activity):
2009
2039
  expression_reference=or_join(
2010
2040
  [
2011
2041
  TriccOperation(TriccOperator.ISTRUE, [TriccReference("pre_final." + code)]),
2012
- TriccOperation(TriccOperator.SELECTED, [TriccReference("tricc.manual.diag"), TriccStatic(code)]),
2042
+ TriccOperation(TriccOperator.SELECTED, [TriccReference("tricc.manual.diag"), TriccStatic(option)]),
2013
2043
  ]
2014
2044
  ),
2015
2045
  )
@@ -2068,9 +2098,18 @@ def create_determine_diagnosis_activity(diags):
2068
2098
  group=activity,
2069
2099
  required=TriccStatic(False),
2070
2100
  )
2101
+ options = []
2071
2102
  for proposed in diags:
2103
+ option = TriccNodeSelectOption(
2104
+ id=generate_id(proposed.name),
2105
+ name=proposed.name,
2106
+ label=proposed.label,
2107
+ list_name=f.list_name,
2108
+ relevance=proposed.activity.applicability,
2109
+ select=f,
2110
+ )
2072
2111
  d = get_accept_diagnostic_node(proposed.name, proposed.label, proposed.severity, proposed.priority, activity)
2073
- c = get_diagnostic_node(proposed.name, proposed.label, proposed.severity, proposed.priority, activity)
2112
+ c = get_diagnostic_node(proposed.name, proposed.label, proposed.severity, proposed.priority, activity, option)
2074
2113
  diags_conf.append(d)
2075
2114
  r = TriccNodeRhombus(
2076
2115
  path=start,
@@ -2095,17 +2134,7 @@ def create_determine_diagnosis_activity(diags):
2095
2134
  activity.nodes[wait2.id] = wait2
2096
2135
  # fallback
2097
2136
 
2098
- options = [
2099
- TriccNodeSelectOption(
2100
- id=generate_id(d.name),
2101
- name=d.name,
2102
- label=d.label,
2103
- list_name=f.list_name,
2104
- relevance=d.activity.applicability,
2105
- select=f,
2106
- )
2107
- for d in diags
2108
- ]
2137
+
2109
2138
  f.options = dict(zip(range(0, len(options)), options))
2110
2139
  activity.nodes[f.id] = f
2111
2140
  set_prev_next_node(f, end, edge_only=False)
@@ -2213,10 +2242,22 @@ def get_count_terms(node, processed_nodes, get_overall_exp, negate=False, proces
2213
2242
  return TriccOperation(TriccOperator.PLUS, [TriccOperation(TriccOperator.CAST_NUMBER, [term]) for term in terms])
2214
2243
 
2215
2244
 
2245
+ def get_none_option(node):
2246
+ if hasattr(node, "options"):
2247
+ for opt in node.options.values():
2248
+ if opt.name == "opt_none":
2249
+ return opt
2250
+ return None
2251
+
2252
+
2216
2253
  def get_count_terms_details(prev_node, processed_nodes, get_overall_exp, negate=False, process=None):
2217
- operation_none = TriccOperation(TriccOperator.SELECTED, [prev_node, TriccStatic("opt_none")])
2254
+ opt_none = get_none_option(prev_node)
2255
+ if opt_none:
2256
+ operation_none = TriccOperation(TriccOperator.SELECTED, [prev_node, TriccStatic(opt_none)])
2257
+ else:
2258
+ operation_none = TriccOperation(TriccOperator.SELECTED, [prev_node, TriccStatic("opt_none")])
2218
2259
  if isinstance(prev_node, TriccNodeSelectYesNo):
2219
- return TriccOperation(TriccOperator.SELECTED, [prev_node, TriccStatic(prev_node.options[0].name)])
2260
+ return TriccOperation(TriccOperator.SELECTED, [prev_node, TriccStatic(prev_node.options[0])])
2220
2261
  elif issubclass(prev_node.__class__, TriccNodeSelect):
2221
2262
  if negate:
2222
2263
  return
@@ -2528,7 +2569,7 @@ def get_selected_option_expression_single(option_node, negate):
2528
2569
 
2529
2570
  def get_selected_option_expression_multiple(option_node, negate):
2530
2571
 
2531
- selected = TriccOperation(TriccOperator.SELECTED, [option_node.select, option_node])
2572
+ selected = TriccOperation(TriccOperator.SELECTED, [option_node.select, TriccStatic(option_node)])
2532
2573
 
2533
2574
  if negate:
2534
2575
  return TriccOperation(
@@ -2598,24 +2639,26 @@ def generate_base(node, processed_nodes, **kwargs):
2598
2639
  # we don't overright if define in the diagram
2599
2640
  if node.constraint is None:
2600
2641
  if isinstance(node, TriccNodeSelectMultiple):
2601
- node.constraint = or_join(
2602
- [
2603
- TriccOperation(
2604
- TriccOperator.EQUAL,
2605
- ["$this", TriccStatic("opt_none")],
2606
- ),
2607
- TriccOperation(
2608
- TriccOperator.NOT,
2609
- [
2610
- TriccOperation(
2611
- TriccOperator.SELECTED,
2612
- ["$this", TriccStatic("opt_none")],
2613
- )
2614
- ],
2615
- ),
2616
- ]
2617
- ) # '.=\'opt_none\' or not(selected(.,\'opt_none\'))'
2618
- node.constraint_message = "**None** cannot be selected together with choice."
2642
+ none_opt = get_none_option(node)
2643
+ if none_opt:
2644
+ node.constraint = or_join(
2645
+ [
2646
+ TriccOperation(
2647
+ TriccOperator.EQUAL,
2648
+ ["$this", TriccStatic(none_opt)],
2649
+ ),
2650
+ TriccOperation(
2651
+ TriccOperator.NOT,
2652
+ [
2653
+ TriccOperation(
2654
+ TriccOperator.SELECTED,
2655
+ ["$this", TriccStatic(none_opt)],
2656
+ )
2657
+ ],
2658
+ ),
2659
+ ]
2660
+ ) # '.=\'opt_none\' or not(selected(.,\'opt_none\'))'
2661
+ node.constraint_message = "**None** cannot be selected together with choice."
2619
2662
  elif node.tricc_type in (
2620
2663
  TriccNodeType.integer,
2621
2664
  TriccNodeType.decimal,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tricc-oo
3
- Version: 1.6.5.dev1
3
+ Version: 1.6.6
4
4
  Summary: Python library that converts CDSS L2 in L3
5
5
  Project-URL: Homepage, https://github.com/SwissTPH/tricc
6
6
  Project-URL: Issues, https://github.com/SwissTPH/tricc/issues
@@ -2,7 +2,6 @@ build
2
2
  diagrams
3
3
  dist
4
4
  locales
5
- test_output
6
5
  tests
7
6
  tricc_oo
8
7
  uploads
File without changes
File without changes
File without changes
File without changes
File without changes