tricc-oo 1.6.6__tar.gz → 1.6.8__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.6/tricc_oo.egg-info → tricc_oo-1.6.8}/PKG-INFO +1 -1
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/pyproject.toml +1 -1
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/converters/tricc_to_xls_form.py +2 -3
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/converters/xml_to_tricc.py +2 -2
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/serializers/xls_form.py +10 -10
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/strategies/output/base_output_strategy.py +7 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/strategies/output/dhis2_form.py +8 -8
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/strategies/output/openmrs_form.py +2 -2
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/strategies/output/xls_form.py +34 -3
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/strategies/output/xlsform_cht.py +104 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/visitors/tricc.py +19 -9
- {tricc_oo-1.6.6 → tricc_oo-1.6.8/tricc_oo.egg-info}/PKG-INFO +1 -1
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/LICENSE +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/README.md +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/setup.cfg +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tests/build.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tests/test_build.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tests/test_cql.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tests/to_ocl.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/__init__.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/converters/__init__.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/converters/codesystem_to_ocl.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/converters/cql/cqlLexer.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/converters/cql/cqlListener.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/converters/cql/cqlParser.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/converters/cql/cqlVisitor.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/converters/cql_to_operation.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/converters/datadictionnary.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/converters/drawio_type_map.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/converters/utils.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/models/__init__.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/models/base.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/models/calculate.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/models/lang.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/models/ocl.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/models/ordered_set.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/models/tricc.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/parsers/__init__.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/parsers/xml.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/serializers/__init__.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/serializers/planuml.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/strategies/__init__.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/strategies/input/__init__.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/strategies/input/base_input_strategy.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/strategies/input/drawio.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/strategies/output/fhir_form.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/strategies/output/html_form.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/strategies/output/spice.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/strategies/output/xlsform_cdss.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/strategies/output/xlsform_cht_hf.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/visitors/__init__.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/visitors/utils.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo/visitors/xform_pd.py +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo.egg-info/SOURCES.txt +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo.egg-info/dependency_links.txt +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/tricc_oo.egg-info/requires.txt +0 -0
- {tricc_oo-1.6.6 → tricc_oo-1.6.8}/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,
|
|
@@ -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")
|
|
@@ -145,15 +145,16 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
145
145
|
raise NotImplementedError(
|
|
146
146
|
f"This type of operation '{operation.operator}' is not supported in this strategy"
|
|
147
147
|
)
|
|
148
|
+
|
|
148
149
|
def get_display(self, node):
|
|
149
150
|
if hasattr(node, 'label') and node.label:
|
|
150
|
-
ret =
|
|
151
|
+
ret = node.label
|
|
151
152
|
elif hasattr(node, 'name') and node.name:
|
|
152
153
|
ret = node.name
|
|
153
154
|
else:
|
|
154
155
|
ret = str(node.id)
|
|
155
|
-
return
|
|
156
|
-
|
|
156
|
+
return ret.replace('\u00a0', ' ').strip()
|
|
157
|
+
|
|
157
158
|
def execute(self):
|
|
158
159
|
version = datetime.datetime.now().strftime("%Y%m%d%H%M")
|
|
159
160
|
logger.info(f"build version: {version}")
|
|
@@ -273,13 +274,12 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
273
274
|
"id": de_id,
|
|
274
275
|
"name": self.get_export_name(node),
|
|
275
276
|
"shortName": node.name[:50],
|
|
276
|
-
"displayFormName":self.get_display(node),
|
|
277
|
+
"displayFormName": self.get_display(node),
|
|
277
278
|
"formName": self.get_display(node),
|
|
278
279
|
"valueType": value_type,
|
|
279
280
|
"domainType": "TRACKER",
|
|
280
281
|
"aggregationType": "NONE"
|
|
281
282
|
}
|
|
282
|
-
|
|
283
283
|
if issubclass(node.__class__, TriccNodeSelect) and not isinstance(node, TriccNodeSelectYesNo):
|
|
284
284
|
data_element["optionSetValue"] = True
|
|
285
285
|
|
|
@@ -736,15 +736,15 @@ class DHIS2Strategy(BaseOutPutStrategy):
|
|
|
736
736
|
return f"#{{{node_id}}}"
|
|
737
737
|
elif issubclass(r.__class__, TriccNodeCalculateBase):
|
|
738
738
|
# Use variable name from concept_map
|
|
739
|
-
node_id =
|
|
739
|
+
node_id = self.get_export_name(r)
|
|
740
740
|
return f"#{{{node_id}}}"
|
|
741
741
|
elif issubclass(r.__class__, TriccNodeInputModel):
|
|
742
742
|
# Use variable name from concept_map
|
|
743
|
-
node_id =
|
|
743
|
+
node_id = self.get_export_name(r)
|
|
744
744
|
return f"#{{{node_id}}}"
|
|
745
745
|
elif issubclass(r.__class__, TriccNodeBaseModel):
|
|
746
746
|
# Use variable name from concept_map
|
|
747
|
-
node_id =
|
|
747
|
+
node_id = self.get_export_name(r)
|
|
748
748
|
return f"#{{{node_id}}}"
|
|
749
749
|
else:
|
|
750
750
|
raise NotImplementedError(f"This type of node {r.__class__.__name__} is not supported within an operation")
|
|
@@ -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
|
|
@@ -1765,7 +1768,10 @@ def reorder_node_list(node_list, group, processed_nodes):
|
|
|
1765
1768
|
elif activity and isinstance(activity.root, TriccNodeActivityStart):
|
|
1766
1769
|
priority += NON_START_ACTIVITY_PRIORITY
|
|
1767
1770
|
# Check for display calculate and end nodes with prev_nodes
|
|
1768
|
-
elif (
|
|
1771
|
+
elif (
|
|
1772
|
+
issubclass(node.__class__, TriccNodeDisplayCalculateBase) or
|
|
1773
|
+
isinstance(node, TriccNodeEnd)
|
|
1774
|
+
) and not isinstance(node, TriccNodeActivityEnd) and hasattr(node, 'prev_nodes') and len(node.prev_nodes) > 0:
|
|
1769
1775
|
priority += FLOW_CALCULATE_NODE_PRIORITY
|
|
1770
1776
|
# Check for active activities (lower priority)
|
|
1771
1777
|
elif activity and activity in active_activities:
|
|
@@ -2100,7 +2106,7 @@ def create_determine_diagnosis_activity(diags):
|
|
|
2100
2106
|
)
|
|
2101
2107
|
options = []
|
|
2102
2108
|
for proposed in diags:
|
|
2103
|
-
option =
|
|
2109
|
+
option = TriccNodeSelectOption(
|
|
2104
2110
|
id=generate_id(proposed.name),
|
|
2105
2111
|
name=proposed.name,
|
|
2106
2112
|
label=proposed.label,
|
|
@@ -2108,6 +2114,7 @@ def create_determine_diagnosis_activity(diags):
|
|
|
2108
2114
|
relevance=proposed.activity.applicability,
|
|
2109
2115
|
select=f,
|
|
2110
2116
|
)
|
|
2117
|
+
options.append(option)
|
|
2111
2118
|
d = get_accept_diagnostic_node(proposed.name, proposed.label, proposed.severity, proposed.priority, activity)
|
|
2112
2119
|
c = get_diagnostic_node(proposed.name, proposed.label, proposed.severity, proposed.priority, activity, option)
|
|
2113
2120
|
diags_conf.append(d)
|
|
@@ -2134,7 +2141,6 @@ def create_determine_diagnosis_activity(diags):
|
|
|
2134
2141
|
activity.nodes[wait2.id] = wait2
|
|
2135
2142
|
# fallback
|
|
2136
2143
|
|
|
2137
|
-
|
|
2138
2144
|
f.options = dict(zip(range(0, len(options)), options))
|
|
2139
2145
|
activity.nodes[f.id] = f
|
|
2140
2146
|
set_prev_next_node(f, end, edge_only=False)
|
|
@@ -2307,7 +2313,11 @@ def get_count_terms_details(prev_node, processed_nodes, get_overall_exp, negate=
|
|
|
2307
2313
|
TriccOperator.CAST_NUMBER,
|
|
2308
2314
|
[
|
|
2309
2315
|
get_node_expression(
|
|
2310
|
-
prev_node,
|
|
2316
|
+
prev_node,
|
|
2317
|
+
processed_nodes=processed_nodes,
|
|
2318
|
+
get_overall_exp=get_overall_exp,
|
|
2319
|
+
is_prev=True,
|
|
2320
|
+
process=process
|
|
2311
2321
|
)
|
|
2312
2322
|
],
|
|
2313
2323
|
)
|
|
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
|
|
File without changes
|