tricc-oo 1.6.7__tar.gz → 1.6.9__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.
- {tricc_oo-1.6.7/tricc_oo.egg-info → tricc_oo-1.6.9}/PKG-INFO +1 -1
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/pyproject.toml +1 -1
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/converters/tricc_to_xls_form.py +2 -3
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/converters/xml_to_tricc.py +4 -2
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/serializers/xls_form.py +10 -10
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/strategies/output/base_output_strategy.py +7 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/strategies/output/dhis2_form.py +174 -19
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/strategies/output/fhir_form.py +1 -1
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/strategies/output/openmrs_form.py +2 -2
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/strategies/output/xls_form.py +34 -3
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/strategies/output/xlsform_cht.py +104 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/visitors/tricc.py +25 -10
- {tricc_oo-1.6.7 → tricc_oo-1.6.9/tricc_oo.egg-info}/PKG-INFO +1 -1
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/LICENSE +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/README.md +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/setup.cfg +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tests/build.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tests/test_build.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tests/test_cql.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tests/to_ocl.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/__init__.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/converters/__init__.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/converters/codesystem_to_ocl.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/converters/cql/cqlLexer.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/converters/cql/cqlListener.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/converters/cql/cqlParser.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/converters/cql/cqlVisitor.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/converters/cql_to_operation.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/converters/datadictionnary.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/converters/drawio_type_map.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/converters/utils.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/models/__init__.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/models/base.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/models/calculate.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/models/lang.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/models/ocl.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/models/ordered_set.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/models/tricc.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/parsers/__init__.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/parsers/xml.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/serializers/__init__.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/serializers/planuml.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/strategies/__init__.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/strategies/input/__init__.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/strategies/input/base_input_strategy.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/strategies/input/drawio.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/strategies/output/html_form.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/strategies/output/spice.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/strategies/output/xlsform_cdss.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/strategies/output/xlsform_cht_hf.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/visitors/__init__.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/visitors/utils.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo/visitors/xform_pd.py +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo.egg-info/SOURCES.txt +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo.egg-info/dependency_links.txt +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo.egg-info/requires.txt +0 -0
- {tricc_oo-1.6.7 → tricc_oo-1.6.9}/tricc_oo.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from tricc_oo.converters.utils import clean_name
|
|
2
|
+
from tricc_oo.converters.utils import clean_name
|
|
3
3
|
from tricc_oo.models.tricc import TriccNodeSelectOption, TRICC_TRUE_VALUE, TRICC_FALSE_VALUE, TriccNodeActivity
|
|
4
4
|
from tricc_oo.models.calculate import TriccNodeInput
|
|
5
5
|
from tricc_oo.models.base import TriccNodeBaseModel, TriccStatic, TriccReference
|
|
@@ -66,14 +66,13 @@ def get_export_name(node, replace_dots=True):
|
|
|
66
66
|
return node
|
|
67
67
|
else:
|
|
68
68
|
node.gen_name()
|
|
69
|
-
if isinstance(node, TriccNodeActivity) and getattr(node, 'instance', 1)>1:
|
|
69
|
+
if isinstance(node, TriccNodeActivity) and getattr(node, 'instance', 1) > 1:
|
|
70
70
|
node.export_name = clean_name(
|
|
71
71
|
node.name + INSTANCE_SEPARATOR + str(node.instance),
|
|
72
72
|
replace_dots=replace_dots,
|
|
73
73
|
)
|
|
74
74
|
elif isinstance(node, TriccNodeSelectOption):
|
|
75
75
|
node.export_name = node.name
|
|
76
|
-
|
|
77
76
|
elif node.last is False:
|
|
78
77
|
node.export_name = clean_name(
|
|
79
78
|
node.name + VERSION_SEPARATOR + str(node.version),
|
|
@@ -7,7 +7,7 @@ import re
|
|
|
7
7
|
|
|
8
8
|
from tricc_oo.converters.utils import remove_html, clean_str
|
|
9
9
|
from tricc_oo.converters.cql_to_operation import transform_cql_to_operation
|
|
10
|
-
from tricc_oo.converters.utils import generate_id
|
|
10
|
+
from tricc_oo.converters.utils import generate_id
|
|
11
11
|
from tricc_oo.models.base import (
|
|
12
12
|
TriccOperator, TriccOperation,
|
|
13
13
|
TriccStatic, TriccReference, TriccNodeType, TriccEdge, OPERATION_LIST
|
|
@@ -107,7 +107,7 @@ def create_activity(diagram, media_path, project):
|
|
|
107
107
|
if root is not None:
|
|
108
108
|
activity = TriccNodeActivity(
|
|
109
109
|
root=root,
|
|
110
|
-
name=name, #
|
|
110
|
+
name=name, # start node 'name' is saved in label
|
|
111
111
|
id=id,
|
|
112
112
|
external_id=external_id,
|
|
113
113
|
label=label,
|
|
@@ -625,6 +625,8 @@ def enrich_node(diagram, media_path, edge, node, activity, help_before=False):
|
|
|
625
625
|
name=f"{node.name}.more_info",
|
|
626
626
|
label=message,
|
|
627
627
|
parent=node,
|
|
628
|
+
group=node.group,
|
|
629
|
+
activity=node.activity,
|
|
628
630
|
required=None,
|
|
629
631
|
)
|
|
630
632
|
# node.help = message
|
|
@@ -16,7 +16,7 @@ from tricc_oo.models.calculate import (
|
|
|
16
16
|
TriccNodeCalculate
|
|
17
17
|
)
|
|
18
18
|
from tricc_oo.models.tricc import (
|
|
19
|
-
|
|
19
|
+
TriccNodeBaseModel, TriccNodeSelectMultiple, TriccNodeSelectOption,
|
|
20
20
|
TriccNodeSelectOne,
|
|
21
21
|
TriccNodeSelect,
|
|
22
22
|
TriccNodeMoreInfo,
|
|
@@ -39,7 +39,9 @@ logger = logging.getLogger("default")
|
|
|
39
39
|
langs = SingletonLangClass()
|
|
40
40
|
TRICC_CALC_EXPRESSION = "${{{0}}}>0"
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
def get_export_group_name(in_node): return f"gcalc_{get_export_name(in_node)}"
|
|
44
|
+
|
|
43
45
|
|
|
44
46
|
def start_group(
|
|
45
47
|
strategy,
|
|
@@ -60,13 +62,12 @@ def start_group(
|
|
|
60
62
|
|
|
61
63
|
else:
|
|
62
64
|
groups[name] = 0
|
|
63
|
-
is_activity = isinstance(cur_group, TriccNodeActivity)
|
|
64
65
|
relevance = relevance and cur_group.relevance is not None and cur_group.relevance != ""
|
|
65
66
|
past_instances = len(getattr(cur_group.base_instance, "instances", []))
|
|
66
|
-
group_calc_required = relevance is not None and (len(str(relevance)) > 100 or past_instances>1)
|
|
67
|
+
group_calc_required = relevance is not None and (len(str(relevance)) > 100 or past_instances > 1)
|
|
67
68
|
calc = None
|
|
68
69
|
if group_calc_required and getattr(cur_group.relevance, 'operator', None) != TriccOperator.ISTRUE:
|
|
69
|
-
|
|
70
|
+
|
|
70
71
|
calc = TriccNodeCalculate(
|
|
71
72
|
id=generate_id(get_export_group_name(name)),
|
|
72
73
|
group=cur_group,
|
|
@@ -77,23 +78,22 @@ def start_group(
|
|
|
77
78
|
if calc not in cur_group.calculates:
|
|
78
79
|
cur_group.calculates.append(calc)
|
|
79
80
|
processed_nodes.add(calc)
|
|
80
|
-
|
|
81
|
+
|
|
81
82
|
cur_group.relevance = TriccOperation(
|
|
82
83
|
TriccOperator.ISTRUE,
|
|
83
84
|
[calc]
|
|
84
85
|
)
|
|
85
|
-
|
|
86
|
+
|
|
86
87
|
relevance_expression = cur_group.relevance
|
|
87
88
|
relevance_expression = get_applicability_expression(cur_group, processed_nodes, process, relevance_expression)
|
|
88
89
|
relevance_expression = get_prev_instance_skip_expression(cur_group, processed_nodes, process, relevance_expression)
|
|
89
90
|
relevance_expression = get_process_skip_expression(cur_group, processed_nodes, process, relevance_expression)
|
|
90
91
|
|
|
91
|
-
|
|
92
92
|
if not relevance:
|
|
93
93
|
relevance_expression_str = ""
|
|
94
94
|
elif isinstance(relevance_expression, (TriccOperation, TriccStatic)):
|
|
95
95
|
relevance_expression_str = strategy.get_tricc_operation_expression(relevance_expression)
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
# group
|
|
98
98
|
values = []
|
|
99
99
|
for column in SURVEY_MAP:
|
|
@@ -114,7 +114,7 @@ def start_group(
|
|
|
114
114
|
df_survey.loc[len(df_survey)] = values
|
|
115
115
|
|
|
116
116
|
# calc
|
|
117
|
-
if calc and len(df_calculate[df_calculate["name"] ==
|
|
117
|
+
if calc and len(df_calculate[df_calculate["name"] == get_export_group_name(name)]) == 0:
|
|
118
118
|
calc_values = []
|
|
119
119
|
for column in SURVEY_MAP:
|
|
120
120
|
if column == "type":
|
|
@@ -43,6 +43,9 @@ class BaseOutPutStrategy:
|
|
|
43
43
|
|
|
44
44
|
self.export(self.project.start_pages, version=version)
|
|
45
45
|
|
|
46
|
+
logger.info("validate the output")
|
|
47
|
+
self.validate()
|
|
48
|
+
|
|
46
49
|
# walking function
|
|
47
50
|
def process_base(self, start_pages, **kwargs):
|
|
48
51
|
# for each node, check if condition is required issubclass(TriccNodeDisplayModel)
|
|
@@ -106,6 +109,10 @@ class BaseOutPutStrategy:
|
|
|
106
109
|
def export(self, **kwargs):
|
|
107
110
|
pass
|
|
108
111
|
|
|
112
|
+
@abc.abstractmethod
|
|
113
|
+
def validate(self):
|
|
114
|
+
pass
|
|
115
|
+
|
|
109
116
|
def tricc_operation_equal(self, ref_expressions):
|
|
110
117
|
# r[0] = r[1]
|
|
111
118
|
raise NotImplementedError("This type of opreration is not supported in this strategy")
|
|
@@ -16,7 +16,7 @@ import datetime
|
|
|
16
16
|
from tricc_oo.strategies.output.base_output_strategy import BaseOutPutStrategy
|
|
17
17
|
from tricc_oo.models.base import (
|
|
18
18
|
not_clean, TriccOperation,
|
|
19
|
-
TriccStatic, TriccReference
|
|
19
|
+
TriccStatic, TriccReference, TriccOperator
|
|
20
20
|
)
|
|
21
21
|
from tricc_oo.models.tricc import (
|
|
22
22
|
TriccNodeSelectOption,
|
|
@@ -27,6 +27,8 @@ from tricc_oo.models.tricc import (
|
|
|
27
27
|
TriccNodeActivity,
|
|
28
28
|
TriccNodeSelect,
|
|
29
29
|
TriccNodeSelectYesNo,
|
|
30
|
+
TriccNodeNote,
|
|
31
|
+
TriccNodeMoreInfo,
|
|
30
32
|
)
|
|
31
33
|
from tricc_oo.models.calculate import TriccNodeDisplayCalculateBase
|
|
32
34
|
from tricc_oo.models.ordered_set import OrderedSet
|
|
@@ -44,7 +46,7 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
44
46
|
|
|
45
47
|
def __init__(self, project, output_path):
|
|
46
48
|
super().__init__(project, output_path)
|
|
47
|
-
form_id = getattr(self.project.start_pages["main"], 'form_id', 'dhis2_program')
|
|
49
|
+
form_id = getattr(self.project.start_pages["main"].root, 'form_id', 'dhis2_program')
|
|
48
50
|
self.program_metadata = {
|
|
49
51
|
"id": self.generate_id(form_id),
|
|
50
52
|
"name": form_id,
|
|
@@ -145,15 +147,16 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
145
147
|
raise NotImplementedError(
|
|
146
148
|
f"This type of operation '{operation.operator}' is not supported in this strategy"
|
|
147
149
|
)
|
|
150
|
+
|
|
148
151
|
def get_display(self, node):
|
|
149
152
|
if hasattr(node, 'label') and node.label:
|
|
150
|
-
ret =
|
|
153
|
+
ret = node.label
|
|
151
154
|
elif hasattr(node, 'name') and node.name:
|
|
152
155
|
ret = node.name
|
|
153
156
|
else:
|
|
154
157
|
ret = str(node.id)
|
|
155
|
-
return
|
|
156
|
-
|
|
158
|
+
return ret.replace('\u00a0', ' ').strip()
|
|
159
|
+
|
|
157
160
|
def execute(self):
|
|
158
161
|
version = datetime.datetime.now().strftime("%Y%m%d%H%M")
|
|
159
162
|
logger.info(f"build version: {version}")
|
|
@@ -203,6 +206,10 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
203
206
|
return False
|
|
204
207
|
|
|
205
208
|
if node not in processed_nodes:
|
|
209
|
+
# Skip relevance generation for TriccNodeMoreInfo as they don't create DataElements
|
|
210
|
+
if isinstance(node, TriccNodeMoreInfo):
|
|
211
|
+
return True
|
|
212
|
+
|
|
206
213
|
relevance = None
|
|
207
214
|
if hasattr(node, 'relevance') and node.relevance:
|
|
208
215
|
relevance = node.relevance
|
|
@@ -224,8 +231,10 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
224
231
|
"activity_ref": node, # Temporary reference to be replaced with section ID
|
|
225
232
|
"programRule": {"id": rule_id},
|
|
226
233
|
}
|
|
227
|
-
|
|
228
|
-
|
|
234
|
+
self.program_rule_actions.append(program_rule_action)
|
|
235
|
+
elif not issubclass(node.__class__, TriccNodeCalculateBase) and not isinstance(node, (TriccNodeNote, TriccNodeMoreInfo)):
|
|
236
|
+
# For regular nodes that get DataElements, use HIDEFIELD action
|
|
237
|
+
# Exclude TriccNodeNote and TriccNodeMoreInfo as they don't get DataElements
|
|
229
238
|
program_rule_action = {
|
|
230
239
|
"id": action_id,
|
|
231
240
|
"programRuleActionType": "HIDEFIELD",
|
|
@@ -234,18 +243,45 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
234
243
|
},
|
|
235
244
|
"programRule": {"id": rule_id}
|
|
236
245
|
}
|
|
237
|
-
|
|
246
|
+
self.program_rule_actions.append(program_rule_action)
|
|
238
247
|
|
|
239
248
|
# Create program rule referencing the action
|
|
240
|
-
condition = self.simplify_expression(f"
|
|
249
|
+
condition = self.simplify_expression(f"({relevance_str})==false") # Negate for hide when true
|
|
241
250
|
condition = self.simplify_expression(condition)
|
|
242
251
|
self.program_rules.append({
|
|
243
252
|
"id": rule_id,
|
|
244
253
|
"name": f"Hide `{self.get_export_name(node)}` when condition met",
|
|
245
|
-
"description": f"Hide `{self.get_display(node)}` based on relevance",
|
|
254
|
+
"description": f"Hide `{self.get_display(node)[:128]}` based on relevance",
|
|
246
255
|
"condition": condition,
|
|
247
256
|
"programRuleActions": [{"id": action_id}]
|
|
248
257
|
})
|
|
258
|
+
|
|
259
|
+
# Check if field should be mandatory based on 'required' attribute
|
|
260
|
+
if bool(getattr(node, 'required', False)):
|
|
261
|
+
# Create program rule for mandatory field using relevance function
|
|
262
|
+
mandatory_rule_id = self.generate_id(f"rule_{node.get_name()}_mandatory")
|
|
263
|
+
mandatory_action_id = self.generate_id(f"action_{mandatory_rule_id}")
|
|
264
|
+
|
|
265
|
+
mandatory_program_rule_action = {
|
|
266
|
+
"id": mandatory_action_id,
|
|
267
|
+
"programRuleActionType": "SETMANDATORYFIELD",
|
|
268
|
+
"dataElement": {
|
|
269
|
+
"id": self.generate_id(self.get_export_name(node))
|
|
270
|
+
},
|
|
271
|
+
"programRule": {"id": mandatory_rule_id}
|
|
272
|
+
}
|
|
273
|
+
self.program_rule_actions.append(mandatory_program_rule_action)
|
|
274
|
+
|
|
275
|
+
# Create program rule for mandatory field - use relevance condition if available
|
|
276
|
+
mandatory_condition = relevance_str if relevance_str and relevance_str != 'false' else "true"
|
|
277
|
+
self.program_rules.append({
|
|
278
|
+
"id": mandatory_rule_id,
|
|
279
|
+
"name": f"Make `{self.get_export_name(node)}` mandatory",
|
|
280
|
+
"description": f"Set `{self.get_display(node)[:128]}` as mandatory field",
|
|
281
|
+
"condition": mandatory_condition,
|
|
282
|
+
"programRuleActions": [{"id": mandatory_action_id}]
|
|
283
|
+
})
|
|
284
|
+
|
|
249
285
|
return True
|
|
250
286
|
|
|
251
287
|
def generate_data_element(self, node):
|
|
@@ -273,13 +309,12 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
273
309
|
"id": de_id,
|
|
274
310
|
"name": self.get_export_name(node),
|
|
275
311
|
"shortName": node.name[:50],
|
|
276
|
-
"displayFormName":self.get_display(node),
|
|
312
|
+
"displayFormName": self.get_display(node),
|
|
277
313
|
"formName": self.get_display(node),
|
|
278
314
|
"valueType": value_type,
|
|
279
315
|
"domainType": "TRACKER",
|
|
280
316
|
"aggregationType": "NONE"
|
|
281
317
|
}
|
|
282
|
-
|
|
283
318
|
if issubclass(node.__class__, TriccNodeSelect) and not isinstance(node, TriccNodeSelectYesNo):
|
|
284
319
|
data_element["optionSetValue"] = True
|
|
285
320
|
|
|
@@ -512,8 +547,16 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
512
547
|
return False
|
|
513
548
|
|
|
514
549
|
if node not in processed_nodes:
|
|
550
|
+
logger.debug(f"generate_export processing node: {node.get_name()} type: {type(node)}")
|
|
551
|
+
# Special handling for TriccNodeNote - transform into section description
|
|
552
|
+
if isinstance(node, TriccNodeNote):
|
|
553
|
+
logger.info(f"Found TriccNodeNote: {node.get_name()}")
|
|
554
|
+
self.handle_note_as_section_description(node, processed_nodes, **kwargs)
|
|
555
|
+
elif isinstance(node, TriccNodeMoreInfo):
|
|
556
|
+
logger.info(f"Found TriccNodeMoreInfo: {node.get_name()}")
|
|
557
|
+
self.handle_note_as_section_description(node, processed_nodes, **kwargs)
|
|
515
558
|
# Skip creating data elements for calculate nodes - they should only be program rule variables
|
|
516
|
-
|
|
559
|
+
elif not issubclass(node.__class__, TriccNodeCalculateBase):
|
|
517
560
|
data_element = self.generate_data_element(node)
|
|
518
561
|
if data_element:
|
|
519
562
|
# Add to program stage
|
|
@@ -522,7 +565,7 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
522
565
|
psde = {
|
|
523
566
|
"id": psde_id,
|
|
524
567
|
"dataElement": {"id": data_element["id"]},
|
|
525
|
-
"compulsory":
|
|
568
|
+
"compulsory": False
|
|
526
569
|
}
|
|
527
570
|
self.program_metadata["programStages"][-1]["programStageDataElements"].append(psde)
|
|
528
571
|
|
|
@@ -532,6 +575,115 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
532
575
|
|
|
533
576
|
return True
|
|
534
577
|
|
|
578
|
+
def handle_note_as_section_description(self, node, processed_nodes, **kwargs):
|
|
579
|
+
"""Transform TriccNodeNote into section description and create hide logic with section duplication"""
|
|
580
|
+
logger.info(f"Processing note {node.get_name()} for section description")
|
|
581
|
+
if not self.current_section or self.current_section not in self.sections:
|
|
582
|
+
logger.warning(f"No current section found for note {node.get_name()}")
|
|
583
|
+
return
|
|
584
|
+
|
|
585
|
+
# 1. Duplicate the previous section with incremented ID (like XLS form group numbering)
|
|
586
|
+
original_section = self.sections[self.current_section]
|
|
587
|
+
section_name = f"{original_section['name']}_{node.name}"
|
|
588
|
+
duplicated_section_id = self.generate_id(section_name)
|
|
589
|
+
duplicated_section = {
|
|
590
|
+
"id": duplicated_section_id,
|
|
591
|
+
"name": section_name, # Use incremented naming pattern
|
|
592
|
+
"sortOrder": len(self.sections),
|
|
593
|
+
"programStage": {"id": self.program_metadata["programStages"][-1]["id"]},
|
|
594
|
+
"dataElements": original_section.get("dataElements", []).copy(), # Copy data elements
|
|
595
|
+
"activity_ref": original_section.get("activity_ref")
|
|
596
|
+
}
|
|
597
|
+
self.sections[duplicated_section_id] = duplicated_section
|
|
598
|
+
|
|
599
|
+
# Add duplicated section to program stage
|
|
600
|
+
if self.program_metadata["programStages"]:
|
|
601
|
+
self.program_metadata["programStages"][-1]["programStageSections"].append({"id": duplicated_section_id})
|
|
602
|
+
|
|
603
|
+
logger.info(f"Duplicated section {self.current_section} as {duplicated_section_id}")
|
|
604
|
+
|
|
605
|
+
# 2. Create a new section for the note/moreinfo with name and ID from the node
|
|
606
|
+
note_section_id = self.generate_id(self.get_export_name(node))
|
|
607
|
+
note_section_name = self.get_export_name(node)
|
|
608
|
+
|
|
609
|
+
note_section = {
|
|
610
|
+
"id": note_section_id,
|
|
611
|
+
"name": note_section_name,
|
|
612
|
+
"sortOrder": len(self.sections),
|
|
613
|
+
"programStage": {"id": self.program_metadata["programStages"][-1]["id"]},
|
|
614
|
+
"dataElements": [],
|
|
615
|
+
"activity_ref": original_section.get("activity_ref") # Same activity reference
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
# Set section description to the note's label
|
|
619
|
+
if hasattr(node, 'label') and node.label:
|
|
620
|
+
note_section["description"] = node.label.replace('\u00a0', ' ').strip()
|
|
621
|
+
logger.info(f"Set note section {note_section_id} description to: {note_section['description']}")
|
|
622
|
+
elif hasattr(node, 'name') and node.name:
|
|
623
|
+
note_section["description"] = node.name.replace('\u00a0', ' ').strip()
|
|
624
|
+
logger.info(f"Set note section {note_section_id} description to: {note_section['description']}")
|
|
625
|
+
else:
|
|
626
|
+
note_section["description"] = str(node.id)
|
|
627
|
+
logger.info(f"Set note section {note_section_id} description to: {note_section['description']}")
|
|
628
|
+
|
|
629
|
+
self.sections[note_section_id] = note_section
|
|
630
|
+
|
|
631
|
+
# Add note section to program stage
|
|
632
|
+
if self.program_metadata["programStages"]:
|
|
633
|
+
self.program_metadata["programStages"][-1]["programStageSections"].append({"id": note_section_id})
|
|
634
|
+
|
|
635
|
+
logger.info(f"Created note section {note_section_id} with name '{note_section_name}'")
|
|
636
|
+
|
|
637
|
+
# 3. Inject the duplicated section as the new current section
|
|
638
|
+
self.current_section = duplicated_section_id
|
|
639
|
+
logger.info(f"Set current section to duplicated section {duplicated_section_id}")
|
|
640
|
+
|
|
641
|
+
# Create hide logic for the note section based on combined relevance
|
|
642
|
+
combined_relevance = None
|
|
643
|
+
|
|
644
|
+
# Get parent activity relevance from the original section's activity_ref
|
|
645
|
+
parent_activity = original_section.get("activity_ref")
|
|
646
|
+
if parent_activity and hasattr(parent_activity, 'relevance') and parent_activity.relevance:
|
|
647
|
+
combined_relevance = parent_activity.relevance
|
|
648
|
+
|
|
649
|
+
# Combine with note relevance if it exists
|
|
650
|
+
if hasattr(node, 'relevance') and node.relevance:
|
|
651
|
+
if combined_relevance:
|
|
652
|
+
# Combine using AND operation
|
|
653
|
+
combined_relevance = TriccOperation(
|
|
654
|
+
TriccOperator.AND,
|
|
655
|
+
[combined_relevance, node.relevance]
|
|
656
|
+
)
|
|
657
|
+
else:
|
|
658
|
+
combined_relevance = node.relevance
|
|
659
|
+
|
|
660
|
+
# Create hide logic if there's relevance - applied to the note section
|
|
661
|
+
if combined_relevance:
|
|
662
|
+
relevance_str = self.convert_expression_to_string(not_clean(combined_relevance))
|
|
663
|
+
if relevance_str and relevance_str != 'false':
|
|
664
|
+
# Create program rule action for hiding the note section
|
|
665
|
+
rule_id = self.generate_id(f"rule_{node.get_name()}_note_hide_section")
|
|
666
|
+
action_id = self.generate_id(f"action_{rule_id}")
|
|
667
|
+
|
|
668
|
+
program_rule_action = {
|
|
669
|
+
"id": action_id,
|
|
670
|
+
"programRuleActionType": "HIDESECTION",
|
|
671
|
+
"activity_ref": parent_activity, # Use activity reference like other HIDESECTION actions
|
|
672
|
+
"programRule": {"id": rule_id},
|
|
673
|
+
}
|
|
674
|
+
self.program_rule_actions.append(program_rule_action)
|
|
675
|
+
|
|
676
|
+
# Create program rule referencing the action
|
|
677
|
+
condition = self.simplify_expression(f"({relevance_str})==false") # Negate for hide when true
|
|
678
|
+
condition = self.simplify_expression(condition)
|
|
679
|
+
self.program_rules.append({
|
|
680
|
+
"id": rule_id,
|
|
681
|
+
"name": f"Hide note section `{note_section_name}` based on relevance",
|
|
682
|
+
"description": f"Hide note section `{self.get_display(node)[:128]}` based on combined relevance",
|
|
683
|
+
"condition": condition,
|
|
684
|
+
"programRuleActions": [{"id": action_id}]
|
|
685
|
+
})
|
|
686
|
+
|
|
535
687
|
def clean_section(self, program_stages_payload):
|
|
536
688
|
"""Clean sections by removing empty ones and merging sections with same activity_ref"""
|
|
537
689
|
sections_to_remove = set()
|
|
@@ -542,11 +694,11 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
542
694
|
section_id = section["id"]
|
|
543
695
|
activity_ref = section.get("activity_ref")
|
|
544
696
|
# Remove empty sections
|
|
545
|
-
if not section.get("dataElements"):
|
|
697
|
+
if not section.get("dataElements") and not section.get("description"):
|
|
546
698
|
sections_to_remove.add(section_id)
|
|
547
699
|
|
|
548
700
|
# Check for sections with same activity_ref
|
|
549
|
-
elif activity_ref == prev_activity_ref:
|
|
701
|
+
elif activity_ref == prev_activity_ref and not section.get("description"):
|
|
550
702
|
# Merge this section into the existing one
|
|
551
703
|
existing_section = self.sections[prev_section_id]
|
|
552
704
|
|
|
@@ -645,6 +797,8 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
645
797
|
# Non-activity actions (HIDEFIELD) can be added directly
|
|
646
798
|
program_rule_actions_payload.append(action)
|
|
647
799
|
|
|
800
|
+
# Filter out rules that reference non-existent actions
|
|
801
|
+
valid_action_ids = {action["id"] for action in program_rule_actions_payload}
|
|
648
802
|
if self.program_rules:
|
|
649
803
|
program_rules_payload = [
|
|
650
804
|
{
|
|
@@ -652,6 +806,7 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
652
806
|
"program": {"id": self.program_metadata["id"]}
|
|
653
807
|
}
|
|
654
808
|
for rule in self.program_rules
|
|
809
|
+
if all(action_ref["id"] in valid_action_ids for action_ref in rule["programRuleActions"])
|
|
655
810
|
]
|
|
656
811
|
|
|
657
812
|
if self.program_rule_variables:
|
|
@@ -736,15 +891,15 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
736
891
|
return f"#{{{node_id}}}"
|
|
737
892
|
elif issubclass(r.__class__, TriccNodeCalculateBase):
|
|
738
893
|
# Use variable name from concept_map
|
|
739
|
-
node_id =
|
|
894
|
+
node_id = self.get_export_name(r)
|
|
740
895
|
return f"#{{{node_id}}}"
|
|
741
896
|
elif issubclass(r.__class__, TriccNodeInputModel):
|
|
742
897
|
# Use variable name from concept_map
|
|
743
|
-
node_id =
|
|
898
|
+
node_id = self.get_export_name(r)
|
|
744
899
|
return f"#{{{node_id}}}"
|
|
745
900
|
elif issubclass(r.__class__, TriccNodeBaseModel):
|
|
746
901
|
# Use variable name from concept_map
|
|
747
|
-
node_id =
|
|
902
|
+
node_id = self.get_export_name(r)
|
|
748
903
|
return f"#{{{node_id}}}"
|
|
749
904
|
else:
|
|
750
905
|
raise NotImplementedError(f"This type of node {r.__class__.__name__} is not supported within an operation")
|
|
@@ -97,7 +97,7 @@ class FHIRStrategy(BaseOutPutStrategy):
|
|
|
97
97
|
"type": self.map_tricc_type_to_fhir(node.tricc_type if hasattr(node, 'tricc_type') else 'text')
|
|
98
98
|
}
|
|
99
99
|
if hasattr(node, 'options') and node.options:
|
|
100
|
-
item["answerOption"] = [{"valueString": opt.name} for opt in node.options]
|
|
100
|
+
item["answerOption"] = [{"valueString": opt.name} for opt in node.options.values()]
|
|
101
101
|
self.questionnaires[segment]["item"].append(item)
|
|
102
102
|
return True
|
|
103
103
|
|
|
@@ -410,7 +410,7 @@ class OpenMRSStrategy(BaseOutPutStrategy):
|
|
|
410
410
|
elif issubclass(r.__class__, TriccNodeInputModel):
|
|
411
411
|
return self.get_export_name(r)
|
|
412
412
|
elif issubclass(r.__class__, TriccNodeSelect):
|
|
413
|
-
return "(" + self.get_export_name(r) + " ?? [])"
|
|
413
|
+
return "(" + self.get_export_name(r) + " ?? [])"
|
|
414
414
|
elif issubclass(r.__class__, TriccNodeBaseModel):
|
|
415
415
|
return self.get_export_name(r)
|
|
416
416
|
else:
|
|
@@ -458,7 +458,7 @@ class OpenMRSStrategy(BaseOutPutStrategy):
|
|
|
458
458
|
return f"!({ref_expressions[0]})"
|
|
459
459
|
|
|
460
460
|
def tricc_operation_plus(self, ref_expressions):
|
|
461
|
-
return "(" + " + ".join(ref_expressions) +")"
|
|
461
|
+
return "(" + " + ".join(ref_expressions) + ")"
|
|
462
462
|
|
|
463
463
|
def tricc_operation_minus(self, ref_expressions):
|
|
464
464
|
if len(ref_expressions) > 1:
|
|
@@ -8,6 +8,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
13
|
from tricc_oo.converters.utils import clean_name
|
|
13
14
|
from tricc_oo.models.base import (
|
|
@@ -409,7 +410,7 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
409
410
|
return f"count-selected({self.clean_coalesce(ref_expressions[0])})"
|
|
410
411
|
|
|
411
412
|
def tricc_operation_multiplied(self, ref_expressions):
|
|
412
|
-
return "*".join(map(str,ref_expressions))
|
|
413
|
+
return "*".join(map(str, ref_expressions))
|
|
413
414
|
|
|
414
415
|
def tricc_operation_divided(self, ref_expressions):
|
|
415
416
|
return f"{ref_expressions[0]} div {ref_expressions[1]}"
|
|
@@ -430,7 +431,7 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
430
431
|
return f"-{ref_expressions[0]}"
|
|
431
432
|
|
|
432
433
|
def tricc_operation_plus(self, ref_expressions):
|
|
433
|
-
return " + ".join(map(str,ref_expressions))
|
|
434
|
+
return " + ".join(map(str, ref_expressions))
|
|
434
435
|
|
|
435
436
|
def tricc_operation_not(self, ref_expressions):
|
|
436
437
|
return f"not({ref_expressions[0]})"
|
|
@@ -474,7 +475,7 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
474
475
|
return "0"
|
|
475
476
|
# return f"jr:choice-name({','.join(ref_expressions[1:])})"
|
|
476
477
|
else:
|
|
477
|
-
return f"{ref_expressions[0]}({','.join(map(str,ref_expressions[1:]))})"
|
|
478
|
+
return f"{ref_expressions[0]}({','.join(map(str, ref_expressions[1:]))})"
|
|
478
479
|
|
|
479
480
|
def tricc_operation_istrue(self, ref_expressions):
|
|
480
481
|
if str(BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]).isnumeric():
|
|
@@ -747,3 +748,33 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
747
748
|
|
|
748
749
|
def tricc_operation_concatenate(self, ref_expressions):
|
|
749
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
|
|
@@ -2,6 +2,7 @@ import datetime
|
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
4
|
import shutil
|
|
5
|
+
import subprocess
|
|
5
6
|
import pandas as pd
|
|
6
7
|
|
|
7
8
|
from tricc_oo.models.lang import SingletonLangClass
|
|
@@ -616,6 +617,9 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
|
|
|
616
617
|
newpath = os.path.join(self.output_path, newfilename)
|
|
617
618
|
media_path = os.path.join(self.output_path, form_id + "-media")
|
|
618
619
|
|
|
620
|
+
# Track all generated XLS files for validation
|
|
621
|
+
generated_files = [newpath]
|
|
622
|
+
|
|
619
623
|
settings = {
|
|
620
624
|
"form_title": title,
|
|
621
625
|
"form_id": form_id,
|
|
@@ -660,6 +664,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
|
|
|
660
664
|
new_form_id = f"{form_id}_{clean_name(e.name)}"
|
|
661
665
|
newfilename = f"{new_form_id}.xlsx"
|
|
662
666
|
newpath = os.path.join(self.output_path, newfilename)
|
|
667
|
+
generated_files.append(newpath) # Track additional XLS files
|
|
663
668
|
settings = {
|
|
664
669
|
"form_title": title,
|
|
665
670
|
"form_id": f"{new_form_id}",
|
|
@@ -708,6 +713,105 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
|
|
|
708
713
|
shutil.move(os.path.join(media_path_tmp, file_name), media_path)
|
|
709
714
|
shutil.rmtree(media_path_tmp)
|
|
710
715
|
|
|
716
|
+
return generated_files
|
|
717
|
+
|
|
718
|
+
def execute(self):
|
|
719
|
+
"""Override execute to handle multiple output files from CHT strategy."""
|
|
720
|
+
version = datetime.datetime.now().strftime("%Y%m%d%H%M")
|
|
721
|
+
logger.info(f"build version: {version}")
|
|
722
|
+
if "main" in self.project.start_pages:
|
|
723
|
+
self.process_base(self.project.start_pages, pages=self.project.pages, version=version)
|
|
724
|
+
else:
|
|
725
|
+
logger.critical("Main process required")
|
|
726
|
+
|
|
727
|
+
logger.info("generate the relevance based on edges")
|
|
728
|
+
|
|
729
|
+
# create relevance Expression
|
|
730
|
+
|
|
731
|
+
# create calculate Expression
|
|
732
|
+
self.process_calculate(self.project.start_pages, pages=self.project.pages)
|
|
733
|
+
logger.info("generate the export format")
|
|
734
|
+
# create calculate Expression
|
|
735
|
+
self.process_export(self.project.start_pages, pages=self.project.pages)
|
|
736
|
+
|
|
737
|
+
logger.info("print the export")
|
|
738
|
+
|
|
739
|
+
# Export returns list of generated files for CHT strategy
|
|
740
|
+
generated_files = self.export(self.project.start_pages, version=version)
|
|
741
|
+
|
|
742
|
+
logger.info("validate the output")
|
|
743
|
+
self.validate(generated_files)
|
|
744
|
+
|
|
745
|
+
def validate(self, generated_files=None):
|
|
746
|
+
"""Validate the generated XLS form(s) using xls2xform-medic."""
|
|
747
|
+
if generated_files is None:
|
|
748
|
+
# Fallback for single file validation
|
|
749
|
+
if self.project.start_pages["main"].root.form_id is not None:
|
|
750
|
+
form_id = str(self.project.start_pages["main"].root.form_id)
|
|
751
|
+
generated_files = [os.path.join(self.output_path, form_id + ".xlsx")]
|
|
752
|
+
else:
|
|
753
|
+
logger.error("Form ID not found for validation")
|
|
754
|
+
return False
|
|
755
|
+
|
|
756
|
+
# Ensure xls2xform-medic is available
|
|
757
|
+
medic_tool = self._ensure_xls2xform_medic()
|
|
758
|
+
if not medic_tool:
|
|
759
|
+
logger.error("xls2xform-medic tool not available, skipping CHT validation")
|
|
760
|
+
return False
|
|
761
|
+
|
|
762
|
+
all_valid = True
|
|
763
|
+
for xls_file in generated_files:
|
|
764
|
+
if not os.path.exists(xls_file):
|
|
765
|
+
logger.error(f"XLS file not found: {xls_file}")
|
|
766
|
+
all_valid = False
|
|
767
|
+
continue
|
|
768
|
+
|
|
769
|
+
try:
|
|
770
|
+
# Run xls2xform-medic validation
|
|
771
|
+
result = subprocess.run(
|
|
772
|
+
[medic_tool, xls_file],
|
|
773
|
+
capture_output=True,
|
|
774
|
+
text=True,
|
|
775
|
+
cwd=self.output_path
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
if result.returncode == 0:
|
|
779
|
+
logger.info(f"CHT XLSForm validation successful: {os.path.basename(xls_file)}")
|
|
780
|
+
else:
|
|
781
|
+
logger.error(f"CHT XLSForm validation failed for {os.path.basename(xls_file)}: {result.stderr}")
|
|
782
|
+
all_valid = False
|
|
783
|
+
|
|
784
|
+
except Exception as e:
|
|
785
|
+
logger.error(f"CHT XLSForm validation error for {os.path.basename(xls_file)}: {str(e)}")
|
|
786
|
+
all_valid = False
|
|
787
|
+
|
|
788
|
+
return all_valid
|
|
789
|
+
|
|
790
|
+
def _ensure_xls2xform_medic(self):
|
|
791
|
+
"""Ensure xls2xform-medic tool is available."""
|
|
792
|
+
# Check if it's in PATH
|
|
793
|
+
medic_tool = shutil.which("xls2xform-medic")
|
|
794
|
+
if medic_tool:
|
|
795
|
+
return medic_tool
|
|
796
|
+
|
|
797
|
+
# Check if we need to download it
|
|
798
|
+
medic_path = os.path.join(os.path.dirname(__file__), "xls2xform-medic")
|
|
799
|
+
if os.path.exists(medic_path):
|
|
800
|
+
return medic_path
|
|
801
|
+
|
|
802
|
+
# Try to download from the provided URL
|
|
803
|
+
try:
|
|
804
|
+
import urllib.request
|
|
805
|
+
medic_url = "https://github.com/medic/pyxform/releases/download/v4.0.0-medic/xls2xform-medic"
|
|
806
|
+
logger.info(f"Downloading xls2xform-medic from {medic_url}")
|
|
807
|
+
urllib.request.urlretrieve(medic_url, medic_path)
|
|
808
|
+
# Make executable
|
|
809
|
+
os.chmod(medic_path, 0o755)
|
|
810
|
+
return medic_path
|
|
811
|
+
except Exception as e:
|
|
812
|
+
logger.error(f"Failed to download xls2xform-medic: {str(e)}")
|
|
813
|
+
return None
|
|
814
|
+
|
|
711
815
|
def tricc_operation_zscore(self, ref_expressions):
|
|
712
816
|
y, ll, m, s = self.get_zscore_params(ref_expressions)
|
|
713
817
|
# return ((Math.pow((y / m), l) - 1) / (s * l));
|
|
@@ -121,7 +121,10 @@ def get_last_version(name, processed_nodes, _list=None):
|
|
|
121
121
|
# node is the node to calculate
|
|
122
122
|
# processed_nodes are the list of processed nodes
|
|
123
123
|
def get_node_expressions(node, processed_nodes, process=None):
|
|
124
|
-
get_overall_exp = issubclass(
|
|
124
|
+
get_overall_exp = issubclass(
|
|
125
|
+
node.__class__,
|
|
126
|
+
(TriccNodeDisplayCalculateBase, TriccNodeProposedDiagnosis, TriccNodeDiagnosis)
|
|
127
|
+
) and not isinstance(node, (TriccNodeDisplayBridge))
|
|
125
128
|
expression = None
|
|
126
129
|
# in case of recursive call processed_nodes will be None
|
|
127
130
|
if processed_nodes is None or is_ready_to_process(node, processed_nodes=processed_nodes):
|
|
@@ -173,8 +176,8 @@ def get_version_inheritance(node, last_version, processed_nodes):
|
|
|
173
176
|
node.relevance = expression
|
|
174
177
|
else:
|
|
175
178
|
node.last = False
|
|
176
|
-
|
|
177
|
-
|
|
179
|
+
|
|
180
|
+
# Create a calculate node that coalesces the previous saved value with the current node value
|
|
178
181
|
calc_id = generate_id(f"save_{node.save}")
|
|
179
182
|
calc = TriccNodeCalculate(
|
|
180
183
|
id=calc_id,
|
|
@@ -184,10 +187,10 @@ def get_version_inheritance(node, last_version, processed_nodes):
|
|
|
184
187
|
TriccOperator.COALESCE,
|
|
185
188
|
[TriccReference(node.save), last_version],
|
|
186
189
|
),
|
|
187
|
-
reference=[TriccReference(
|
|
190
|
+
reference=[TriccReference(node.name)],
|
|
188
191
|
activity=node.activity,
|
|
189
192
|
group=node.group,
|
|
190
|
-
label=f"Save calculation for {
|
|
193
|
+
label=f"Save calculation for {node.label}",
|
|
191
194
|
last=True,
|
|
192
195
|
)
|
|
193
196
|
node.activity.nodes[calc.id] = calc
|
|
@@ -1737,7 +1740,7 @@ PARENT_GROUP_PRIORITY = 6000
|
|
|
1737
1740
|
ACTIVE_ACTIVITY_PRIORITY = 5000
|
|
1738
1741
|
NON_START_ACTIVITY_PRIORITY = 4000
|
|
1739
1742
|
ACTIVE_ACTIVITY_LOWER_PRIORITY = 3000
|
|
1740
|
-
FLOW_CALCULATE_NODE_PRIORITY =
|
|
1743
|
+
FLOW_CALCULATE_NODE_PRIORITY = 6500
|
|
1741
1744
|
RHOMBUS_PRIORITY = 1000
|
|
1742
1745
|
DEFAULT_PRIORITY = 2000
|
|
1743
1746
|
|
|
@@ -1755,6 +1758,11 @@ def reorder_node_list(node_list, group, processed_nodes):
|
|
|
1755
1758
|
# Check for same group
|
|
1756
1759
|
if group is not None and node_group and node_group.id == group.id:
|
|
1757
1760
|
priority += SAME_GROUP_PRIORITY
|
|
1761
|
+
elif (
|
|
1762
|
+
issubclass(node.__class__, TriccNodeDisplayCalculateBase) or
|
|
1763
|
+
isinstance(node, TriccNodeEnd)
|
|
1764
|
+
) and not isinstance(node, TriccNodeActivityEnd) and hasattr(node, 'prev_nodes') and len(node.prev_nodes) > 0:
|
|
1765
|
+
priority += FLOW_CALCULATE_NODE_PRIORITY
|
|
1758
1766
|
# Check for parent group
|
|
1759
1767
|
elif hasattr(group, "group") and group.group and node_group and node_group.id == group.group.id:
|
|
1760
1768
|
priority += PARENT_GROUP_PRIORITY
|
|
@@ -1765,7 +1773,10 @@ def reorder_node_list(node_list, group, processed_nodes):
|
|
|
1765
1773
|
elif activity and isinstance(activity.root, TriccNodeActivityStart):
|
|
1766
1774
|
priority += NON_START_ACTIVITY_PRIORITY
|
|
1767
1775
|
# Check for display calculate and end nodes with prev_nodes
|
|
1768
|
-
elif (
|
|
1776
|
+
elif (
|
|
1777
|
+
issubclass(node.__class__, TriccNodeDisplayCalculateBase) or
|
|
1778
|
+
isinstance(node, TriccNodeEnd)
|
|
1779
|
+
) and not isinstance(node, TriccNodeActivityEnd) and hasattr(node, 'prev_nodes') and len(node.prev_nodes) > 0:
|
|
1769
1780
|
priority += FLOW_CALCULATE_NODE_PRIORITY
|
|
1770
1781
|
# Check for active activities (lower priority)
|
|
1771
1782
|
elif activity and activity in active_activities:
|
|
@@ -2100,7 +2111,7 @@ def create_determine_diagnosis_activity(diags):
|
|
|
2100
2111
|
)
|
|
2101
2112
|
options = []
|
|
2102
2113
|
for proposed in diags:
|
|
2103
|
-
option =
|
|
2114
|
+
option = TriccNodeSelectOption(
|
|
2104
2115
|
id=generate_id(proposed.name),
|
|
2105
2116
|
name=proposed.name,
|
|
2106
2117
|
label=proposed.label,
|
|
@@ -2108,6 +2119,7 @@ def create_determine_diagnosis_activity(diags):
|
|
|
2108
2119
|
relevance=proposed.activity.applicability,
|
|
2109
2120
|
select=f,
|
|
2110
2121
|
)
|
|
2122
|
+
options.append(option)
|
|
2111
2123
|
d = get_accept_diagnostic_node(proposed.name, proposed.label, proposed.severity, proposed.priority, activity)
|
|
2112
2124
|
c = get_diagnostic_node(proposed.name, proposed.label, proposed.severity, proposed.priority, activity, option)
|
|
2113
2125
|
diags_conf.append(d)
|
|
@@ -2134,7 +2146,6 @@ def create_determine_diagnosis_activity(diags):
|
|
|
2134
2146
|
activity.nodes[wait2.id] = wait2
|
|
2135
2147
|
# fallback
|
|
2136
2148
|
|
|
2137
|
-
|
|
2138
2149
|
f.options = dict(zip(range(0, len(options)), options))
|
|
2139
2150
|
activity.nodes[f.id] = f
|
|
2140
2151
|
set_prev_next_node(f, end, edge_only=False)
|
|
@@ -2307,7 +2318,11 @@ def get_count_terms_details(prev_node, processed_nodes, get_overall_exp, negate=
|
|
|
2307
2318
|
TriccOperator.CAST_NUMBER,
|
|
2308
2319
|
[
|
|
2309
2320
|
get_node_expression(
|
|
2310
|
-
prev_node,
|
|
2321
|
+
prev_node,
|
|
2322
|
+
processed_nodes=processed_nodes,
|
|
2323
|
+
get_overall_exp=get_overall_exp,
|
|
2324
|
+
is_prev=True,
|
|
2325
|
+
process=process
|
|
2311
2326
|
)
|
|
2312
2327
|
],
|
|
2313
2328
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|