tricc-oo 1.6.8__py3-none-any.whl → 1.6.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tricc_oo/converters/codesystem_to_ocl.py +4 -4
- tricc_oo/converters/datadictionnary.py +1 -1
- tricc_oo/converters/drawio_type_map.py +11 -11
- tricc_oo/converters/tricc_to_xls_form.py +1 -1
- tricc_oo/converters/xml_to_tricc.py +32 -15
- tricc_oo/serializers/xls_form.py +28 -5
- tricc_oo/strategies/output/xlsform_cht.py +64 -37
- tricc_oo/visitors/tricc.py +45 -29
- {tricc_oo-1.6.8.dist-info → tricc_oo-1.6.14.dist-info}/METADATA +1 -1
- {tricc_oo-1.6.8.dist-info → tricc_oo-1.6.14.dist-info}/RECORD +13 -14
- tests/test_build.py +0 -260
- {tricc_oo-1.6.8.dist-info → tricc_oo-1.6.14.dist-info}/WHEEL +0 -0
- {tricc_oo-1.6.8.dist-info → tricc_oo-1.6.14.dist-info}/licenses/LICENSE +0 -0
- {tricc_oo-1.6.8.dist-info → tricc_oo-1.6.14.dist-info}/top_level.txt +0 -0
|
@@ -31,16 +31,16 @@ def extract_properties_metadata(fhir_cs: CodeSystem) -> Dict[str, Dict]:
|
|
|
31
31
|
|
|
32
32
|
property_types[prop.code] = {
|
|
33
33
|
"name": prop.code,
|
|
34
|
-
"
|
|
34
|
+
"dataType": ocl_type,
|
|
35
35
|
"description": prop.description if hasattr(prop, "description") else "",
|
|
36
36
|
}
|
|
37
37
|
return property_types
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def get_fhir_concept_datatype(concept):
|
|
41
|
-
datatype = extract_concept_properties(concept, ["
|
|
41
|
+
datatype = extract_concept_properties(concept, ["dataType"])
|
|
42
42
|
if datatype:
|
|
43
|
-
return datatype["
|
|
43
|
+
return datatype["dataType"]
|
|
44
44
|
else:
|
|
45
45
|
return OclConstants.DATA_TYPE_NONE
|
|
46
46
|
|
|
@@ -84,7 +84,7 @@ def get_attributes_from_concept_properties(concept, property_types: Dict) -> Lis
|
|
|
84
84
|
"type": "Attribute",
|
|
85
85
|
"attribute_type": code,
|
|
86
86
|
"value": value,
|
|
87
|
-
"value_type": property_types[code]["
|
|
87
|
+
"value_type": property_types[code]["dataType"],
|
|
88
88
|
}
|
|
89
89
|
)
|
|
90
90
|
return attributes
|
|
@@ -103,7 +103,7 @@ def check_and_add_concept(code_system: CodeSystem, code: str, display: str, attr
|
|
|
103
103
|
# TODO support other type of Codesystem Concept Property Value
|
|
104
104
|
existing_attributes
|
|
105
105
|
if p.valueString != v:
|
|
106
|
-
logger.warning(f"conflicting value for property {k}
|
|
106
|
+
logger.warning(f"conflicting value for concept `{concept.code}` property ` {k}`: {p.valueString} != {v}")
|
|
107
107
|
if not existing_attributes:
|
|
108
108
|
new_concept.property.append(CodeSystemConceptProperty(code=k, valueString=v))
|
|
109
109
|
|
|
@@ -47,7 +47,7 @@ TYPE_MAP = {
|
|
|
47
47
|
},
|
|
48
48
|
TriccNodeType.note: {
|
|
49
49
|
"objects": ["UserObject", "object"],
|
|
50
|
-
"attributes": ["relevance", "priority", "
|
|
50
|
+
"attributes": ["relevance", "priority", "concept_type"],
|
|
51
51
|
"mandatory_attributes": ["label", "name"],
|
|
52
52
|
"model": TriccNodeNote,
|
|
53
53
|
},
|
|
@@ -76,7 +76,7 @@ TYPE_MAP = {
|
|
|
76
76
|
"priority",
|
|
77
77
|
"trigger",
|
|
78
78
|
"default",
|
|
79
|
-
"
|
|
79
|
+
"concept_type",
|
|
80
80
|
],
|
|
81
81
|
"mandatory_attributes": ["label", "name", "list_name"],
|
|
82
82
|
"model": TriccNodeSelectOne,
|
|
@@ -94,7 +94,7 @@ TYPE_MAP = {
|
|
|
94
94
|
"priority",
|
|
95
95
|
"trigger",
|
|
96
96
|
"default",
|
|
97
|
-
"
|
|
97
|
+
"concept_type",
|
|
98
98
|
],
|
|
99
99
|
"mandatory_attributes": ["label", "name", "list_name"],
|
|
100
100
|
"model": TriccNodeSelectMultiple,
|
|
@@ -112,7 +112,7 @@ TYPE_MAP = {
|
|
|
112
112
|
"priority",
|
|
113
113
|
"trigger",
|
|
114
114
|
"default",
|
|
115
|
-
"
|
|
115
|
+
"concept_type",
|
|
116
116
|
],
|
|
117
117
|
"mandatory_attributes": ["label", "name"],
|
|
118
118
|
"model": TriccNodeDecimal,
|
|
@@ -130,7 +130,7 @@ TYPE_MAP = {
|
|
|
130
130
|
"priority",
|
|
131
131
|
"trigger",
|
|
132
132
|
"default",
|
|
133
|
-
"
|
|
133
|
+
"concept_type",
|
|
134
134
|
],
|
|
135
135
|
"mandatory_attributes": ["label", "name"],
|
|
136
136
|
"model": TriccNodeInteger,
|
|
@@ -145,7 +145,7 @@ TYPE_MAP = {
|
|
|
145
145
|
"default",
|
|
146
146
|
"constraint",
|
|
147
147
|
"constraint_message",
|
|
148
|
-
"
|
|
148
|
+
"concept_type",
|
|
149
149
|
],
|
|
150
150
|
"mandatory_attributes": ["label", "name"],
|
|
151
151
|
"model": TriccNodeText,
|
|
@@ -160,7 +160,7 @@ TYPE_MAP = {
|
|
|
160
160
|
"default",
|
|
161
161
|
"constraint",
|
|
162
162
|
"constraint_message",
|
|
163
|
-
"
|
|
163
|
+
"concept_type",
|
|
164
164
|
],
|
|
165
165
|
"mandatory_attributes": ["label", "name"],
|
|
166
166
|
"model": TriccNodeDate,
|
|
@@ -183,7 +183,7 @@ TYPE_MAP = {
|
|
|
183
183
|
"save",
|
|
184
184
|
"reference",
|
|
185
185
|
"trigger",
|
|
186
|
-
"
|
|
186
|
+
"concept_type",
|
|
187
187
|
"remote_reference",
|
|
188
188
|
],
|
|
189
189
|
"mandatory_attributes": ["name", "label"],
|
|
@@ -217,7 +217,7 @@ TYPE_MAP = {
|
|
|
217
217
|
},
|
|
218
218
|
TriccNodeType.not_available: {
|
|
219
219
|
"objects": ["UserObject", "object"],
|
|
220
|
-
"attributes": ["
|
|
220
|
+
"attributes": ["concept_type"],
|
|
221
221
|
"mandatory_attributes": ["label", "name", "list_name"],
|
|
222
222
|
"model": TriccNodeSelectNotAvailable,
|
|
223
223
|
},
|
|
@@ -233,7 +233,7 @@ TYPE_MAP = {
|
|
|
233
233
|
"priority",
|
|
234
234
|
"trigger",
|
|
235
235
|
"default",
|
|
236
|
-
"
|
|
236
|
+
"concept_type",
|
|
237
237
|
],
|
|
238
238
|
"mandatory_attributes": ["label", "name", "list_name"],
|
|
239
239
|
"model": TriccNodeSelectYesNo,
|
|
@@ -295,7 +295,7 @@ TYPE_MAP = {
|
|
|
295
295
|
},
|
|
296
296
|
TriccNodeType.input: {
|
|
297
297
|
"objects": ["UserObject", "object"],
|
|
298
|
-
"attributes": ["save", "reference", "
|
|
298
|
+
"attributes": ["save", "reference", "data_type", "concept_type"],
|
|
299
299
|
"mandatory_attributes": ["name", "label"],
|
|
300
300
|
"model": TriccNodeInput,
|
|
301
301
|
},
|
|
@@ -36,7 +36,7 @@ def get_export_name(node, replace_dots=True):
|
|
|
36
36
|
elif isinstance(node, bool):
|
|
37
37
|
return BOOLEAN_MAP[str(TRICC_TRUE_VALUE)] if node else BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]
|
|
38
38
|
elif isinstance(node, TriccReference):
|
|
39
|
-
logger.warning(f"Reference {node.value} use in export, bad
|
|
39
|
+
logger.warning(f"Reference {node.value} use in export, bad serialization probable")
|
|
40
40
|
return str(node.value)
|
|
41
41
|
elif isinstance(node, (str, TriccStatic, TriccNodeSelectOption)):
|
|
42
42
|
if isinstance(node, TriccNodeSelectOption):
|
|
@@ -128,7 +128,7 @@ def create_activity(diagram, media_path, project):
|
|
|
128
128
|
for n in nodes.values():
|
|
129
129
|
|
|
130
130
|
if (
|
|
131
|
-
issubclass(n.__class__, (TriccNodeDisplayModel, TriccNodeDisplayCalculateBase))
|
|
131
|
+
issubclass(n.__class__, (TriccNodeDisplayModel, TriccNodeDisplayCalculateBase, TriccNodeInput))
|
|
132
132
|
and not isinstance(n, (TriccRhombusMixIn, TriccNodeRhombus, TriccNodeDisplayBridge))
|
|
133
133
|
and not n.name.startswith("label_") # FIXME
|
|
134
134
|
):
|
|
@@ -139,7 +139,7 @@ def create_activity(diagram, media_path, project):
|
|
|
139
139
|
system,
|
|
140
140
|
n.select.name,
|
|
141
141
|
n.label,
|
|
142
|
-
{"
|
|
142
|
+
{"dataType": "Boolean", "conceptType": get_concept_type(n)},
|
|
143
143
|
)
|
|
144
144
|
elif not isinstance(n, TriccNodeSelectNotAvailable):
|
|
145
145
|
add_concept(
|
|
@@ -148,8 +148,20 @@ def create_activity(diagram, media_path, project):
|
|
|
148
148
|
n.name,
|
|
149
149
|
n.label,
|
|
150
150
|
{
|
|
151
|
-
"
|
|
152
|
-
"
|
|
151
|
+
"dataType": get_data_type(n.tricc_type),
|
|
152
|
+
"conceptType": get_concept_type(n),
|
|
153
|
+
},
|
|
154
|
+
)
|
|
155
|
+
elif not issubclass(n.__class__, TriccNodeCalculate):
|
|
156
|
+
system = n.name.split(".")[0] if "." in n.name else "calculate"
|
|
157
|
+
add_concept(
|
|
158
|
+
project.code_systems,
|
|
159
|
+
system,
|
|
160
|
+
n.name,
|
|
161
|
+
n.label,
|
|
162
|
+
{
|
|
163
|
+
"dataType": get_data_type(n.tricc_type),
|
|
164
|
+
"conceptType": get_concept_type(n),
|
|
153
165
|
},
|
|
154
166
|
)
|
|
155
167
|
if getattr(n, "save", None):
|
|
@@ -160,8 +172,8 @@ def create_activity(diagram, media_path, project):
|
|
|
160
172
|
n.save,
|
|
161
173
|
n.label,
|
|
162
174
|
{
|
|
163
|
-
"
|
|
164
|
-
"
|
|
175
|
+
"dataType": get_data_type(n.tricc_type),
|
|
176
|
+
"conceptType": get_concept_type(n),
|
|
165
177
|
},
|
|
166
178
|
)
|
|
167
179
|
|
|
@@ -175,7 +187,7 @@ def create_activity(diagram, media_path, project):
|
|
|
175
187
|
# link back the activity
|
|
176
188
|
activity.root.activity = activity
|
|
177
189
|
manage_dangling_calculate(activity)
|
|
178
|
-
|
|
190
|
+
# assign the process
|
|
179
191
|
if activity is not None:
|
|
180
192
|
if activity.root is not None:
|
|
181
193
|
project.pages[activity.id] = activity
|
|
@@ -197,6 +209,7 @@ def create_activity(diagram, media_path, project):
|
|
|
197
209
|
)
|
|
198
210
|
if images:
|
|
199
211
|
project.images += images
|
|
212
|
+
# Assign parent to NotAvailable
|
|
200
213
|
for node in list(
|
|
201
214
|
filter(
|
|
202
215
|
lambda p_node: isinstance(p_node, TriccNodeSelectNotAvailable),
|
|
@@ -477,10 +490,10 @@ def set_additional_attributes(attribute_names, elm, node):
|
|
|
477
490
|
setattr(node, attributename, attribute)
|
|
478
491
|
|
|
479
492
|
|
|
480
|
-
def
|
|
481
|
-
|
|
482
|
-
if
|
|
483
|
-
return
|
|
493
|
+
def get_concept_type(node):
|
|
494
|
+
concept_type = getattr(node, "concept_type", None)
|
|
495
|
+
if concept_type:
|
|
496
|
+
return concept_type
|
|
484
497
|
if isinstance(node, TriccNodeSelectMultiple):
|
|
485
498
|
return "Question"
|
|
486
499
|
elif isinstance(node, TriccNodeSelectOption):
|
|
@@ -535,7 +548,7 @@ def get_select_options(diagram, select_node, nodes):
|
|
|
535
548
|
activity=select_node.activity,
|
|
536
549
|
group=select_node.group,
|
|
537
550
|
)
|
|
538
|
-
set_additional_attributes(["save", "relevance", "
|
|
551
|
+
set_additional_attributes(["save", "relevance", "concept_type"], elm, option)
|
|
539
552
|
load_expressions(option)
|
|
540
553
|
options[i] = option
|
|
541
554
|
nodes[id] = option
|
|
@@ -625,6 +638,8 @@ def enrich_node(diagram, media_path, edge, node, activity, help_before=False):
|
|
|
625
638
|
name=f"{node.name}.more_info",
|
|
626
639
|
label=message,
|
|
627
640
|
parent=node,
|
|
641
|
+
group=node.group,
|
|
642
|
+
activity=node.activity,
|
|
628
643
|
required=None,
|
|
629
644
|
)
|
|
630
645
|
# node.help = message
|
|
@@ -800,9 +815,11 @@ def set_mandatory_attribute(elm, mandatory_attributes, diagram=None):
|
|
|
800
815
|
id = elm.attrib.get("id")
|
|
801
816
|
attribute_value = _get_name(name, id, diagram_id)
|
|
802
817
|
elif attributes == "list_name":
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
818
|
+
attribute_value = elm.attrib.get("list_name", None)
|
|
819
|
+
if not attribute_value:
|
|
820
|
+
name = elm.attrib.get("name")
|
|
821
|
+
id = elm.attrib.get("id")
|
|
822
|
+
attribute_value = TRICC_LIST_NAME.format(clean_str(_get_name(name, id, diagram_id), replace_dots=True))
|
|
806
823
|
else:
|
|
807
824
|
attribute_value = elm.attrib.get(attributes)
|
|
808
825
|
if attribute_value is None:
|
tricc_oo/serializers/xls_form.py
CHANGED
|
@@ -32,6 +32,7 @@ from tricc_oo.visitors.tricc import (
|
|
|
32
32
|
get_applicability_expression,
|
|
33
33
|
get_prev_instance_skip_expression,
|
|
34
34
|
get_process_skip_expression,
|
|
35
|
+
process_operation_reference,
|
|
35
36
|
)
|
|
36
37
|
|
|
37
38
|
logger = logging.getLogger("default")
|
|
@@ -70,13 +71,24 @@ def start_group(
|
|
|
70
71
|
|
|
71
72
|
calc = TriccNodeCalculate(
|
|
72
73
|
id=generate_id(get_export_group_name(name)),
|
|
73
|
-
group=cur_group,
|
|
74
|
+
group=cur_group.group,
|
|
74
75
|
activity=cur_group.activity,
|
|
75
76
|
name=get_export_group_name(name),
|
|
76
77
|
expression=cur_group.relevance
|
|
77
78
|
)
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
|
|
80
|
+
if calc not in cur_group.activity.calculates:
|
|
81
|
+
process_reference(
|
|
82
|
+
calc,
|
|
83
|
+
processed_nodes,
|
|
84
|
+
calculates=kwargs.get('calculates', None),
|
|
85
|
+
used_calculates=kwargs.get('used_calculates', None),
|
|
86
|
+
replace_reference=True,
|
|
87
|
+
warn=False,
|
|
88
|
+
codesystems=kwargs.get('codesystems', None)
|
|
89
|
+
)
|
|
90
|
+
cur_group.activity.calculates.append(calc)
|
|
91
|
+
cur_group.activity.nodes[calc.id] = calc
|
|
80
92
|
processed_nodes.add(calc)
|
|
81
93
|
|
|
82
94
|
cur_group.relevance = TriccOperation(
|
|
@@ -92,7 +104,18 @@ def start_group(
|
|
|
92
104
|
if not relevance:
|
|
93
105
|
relevance_expression_str = ""
|
|
94
106
|
elif isinstance(relevance_expression, (TriccOperation, TriccStatic)):
|
|
95
|
-
|
|
107
|
+
relevance_expression = process_operation_reference(
|
|
108
|
+
relevance_expression,
|
|
109
|
+
cur_group,
|
|
110
|
+
processed_nodes=processed_nodes,
|
|
111
|
+
calculates=kwargs.get('calculates', None),
|
|
112
|
+
used_calculates=kwargs.get('used_calculates', None),
|
|
113
|
+
replace_reference=True,
|
|
114
|
+
warn=False,
|
|
115
|
+
codesystems=kwargs.get('codesystems', None),
|
|
116
|
+
) or relevance_expression
|
|
117
|
+
if relevance_expression:
|
|
118
|
+
relevance_expression_str = strategy.get_tricc_operation_expression(relevance_expression)
|
|
96
119
|
|
|
97
120
|
# group
|
|
98
121
|
values = []
|
|
@@ -123,7 +146,7 @@ def start_group(
|
|
|
123
146
|
value = get_export_name(calc)
|
|
124
147
|
calc_values.append(value)
|
|
125
148
|
elif column == "calculation":
|
|
126
|
-
calc_values.append(f"number({strategy.get_tricc_operation_expression(calc.expression)}")
|
|
149
|
+
calc_values.append(f"number({strategy.get_tricc_operation_expression(calc.expression)})")
|
|
127
150
|
elif column == "relevance":
|
|
128
151
|
calc_values.append("")
|
|
129
152
|
else:
|
|
@@ -3,8 +3,12 @@ import logging
|
|
|
3
3
|
import os
|
|
4
4
|
import shutil
|
|
5
5
|
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
import zipfile
|
|
6
8
|
import pandas as pd
|
|
7
9
|
|
|
10
|
+
from pyxform.xls2xform import convert
|
|
11
|
+
|
|
8
12
|
from tricc_oo.models.lang import SingletonLangClass
|
|
9
13
|
from tricc_oo.models.calculate import TriccNodeEnd
|
|
10
14
|
from tricc_oo.models.tricc import TriccNodeDisplayModel
|
|
@@ -740,10 +744,12 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
|
|
|
740
744
|
generated_files = self.export(self.project.start_pages, version=version)
|
|
741
745
|
|
|
742
746
|
logger.info("validate the output")
|
|
743
|
-
self.validate(generated_files)
|
|
747
|
+
if not self.validate(generated_files):
|
|
748
|
+
logger.error("CHT validation failed - aborting build")
|
|
749
|
+
exit(1)
|
|
744
750
|
|
|
745
751
|
def validate(self, generated_files=None):
|
|
746
|
-
"""Validate the generated XLS form(s) using
|
|
752
|
+
"""Validate the generated XLS form(s) using pyxform conversion and ODK Validate JAR."""
|
|
747
753
|
if generated_files is None:
|
|
748
754
|
# Fallback for single file validation
|
|
749
755
|
if self.project.start_pages["main"].root.form_id is not None:
|
|
@@ -753,10 +759,10 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
|
|
|
753
759
|
logger.error("Form ID not found for validation")
|
|
754
760
|
return False
|
|
755
761
|
|
|
756
|
-
# Ensure
|
|
757
|
-
|
|
758
|
-
if not
|
|
759
|
-
logger.error("
|
|
762
|
+
# Ensure ODK Validate JAR is available
|
|
763
|
+
jar_path = self._ensure_odk_validate_jar()
|
|
764
|
+
if not jar_path:
|
|
765
|
+
logger.error("ODK Validate JAR not available, skipping CHT validation")
|
|
760
766
|
return False
|
|
761
767
|
|
|
762
768
|
all_valid = True
|
|
@@ -767,49 +773,70 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
|
|
|
767
773
|
continue
|
|
768
774
|
|
|
769
775
|
try:
|
|
770
|
-
#
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
+
# Convert XLS to XForm using pyxform (without validation)
|
|
777
|
+
xform_path = xls_file.replace('.xlsx', '.xml')
|
|
778
|
+
convert_result = convert(
|
|
779
|
+
xlsform=xls_file,
|
|
780
|
+
validate=False, # Don't validate during conversion
|
|
781
|
+
pretty_print=True
|
|
776
782
|
)
|
|
783
|
+
xform_content = convert_result.xform
|
|
784
|
+
|
|
785
|
+
# Write XForm to temporary file for validation
|
|
786
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as temp_file:
|
|
787
|
+
temp_file.write(xform_content)
|
|
788
|
+
temp_xform_path = temp_file.name
|
|
789
|
+
|
|
790
|
+
try:
|
|
791
|
+
# Run ODK Validate JAR on the XForm
|
|
792
|
+
result = subprocess.run(
|
|
793
|
+
["java", "-Djava.awt.headless=true", "-jar", jar_path, temp_xform_path],
|
|
794
|
+
capture_output=True,
|
|
795
|
+
text=True,
|
|
796
|
+
cwd=self.output_path
|
|
797
|
+
)
|
|
777
798
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
799
|
+
if result.returncode == 0 or "Cycle detected" in result.stderr:
|
|
800
|
+
logger.info(f"CHT XLSForm validation successful: {os.path.basename(xls_file)}")
|
|
801
|
+
else:
|
|
802
|
+
logger.error(f"CHT XLSForm validation failed for {os.path.basename(xls_file)}: {result.stderr}")
|
|
803
|
+
all_valid = False
|
|
804
|
+
|
|
805
|
+
finally:
|
|
806
|
+
# Clean up temporary XForm file
|
|
807
|
+
os.unlink(temp_xform_path)
|
|
783
808
|
|
|
784
809
|
except Exception as e:
|
|
785
810
|
logger.error(f"CHT XLSForm validation error for {os.path.basename(xls_file)}: {str(e)}")
|
|
786
811
|
all_valid = False
|
|
787
812
|
|
|
788
|
-
|
|
813
|
+
jar_in_zip = "site-packages/pyxform/validators/odk_validate/bin/ODK_Validate.jar"
|
|
814
|
+
zip_ref.extract(jar_in_zip, os.path.dirname(__file__))
|
|
815
|
+
|
|
816
|
+
# Move to final location
|
|
817
|
+
extracted_jar = os.path.join(os.path.dirname(__file__), jar_in_zip)
|
|
818
|
+
shutil.move(extracted_jar, jar_path)
|
|
819
|
+
|
|
820
|
+
logger.info(f"Extracted ODK Validate JAR to {jar_path}")
|
|
821
|
+
return jar_path
|
|
789
822
|
|
|
790
|
-
def
|
|
791
|
-
"""Ensure
|
|
792
|
-
|
|
793
|
-
medic_tool = shutil.which("xls2xform-medic")
|
|
794
|
-
if medic_tool:
|
|
795
|
-
return medic_tool
|
|
823
|
+
def _ensure_odk_validate_jar(self):
|
|
824
|
+
"""Ensure ODK Validate JAR is available by downloading from GitHub releases."""
|
|
825
|
+
jar_path = os.path.join(os.path.dirname(__file__), "ODK_Validate.jar")
|
|
796
826
|
|
|
797
|
-
# Check if
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
return medic_path
|
|
827
|
+
# Check if JAR already exists
|
|
828
|
+
if os.path.exists(jar_path):
|
|
829
|
+
return jar_path
|
|
801
830
|
|
|
802
|
-
#
|
|
831
|
+
# Download JAR from GitHub releases
|
|
832
|
+
jar_url = "https://github.com/getodk/validate/releases/download/v1.20.0/ODK-Validate-v1.20.0.jar"
|
|
803
833
|
try:
|
|
804
834
|
import urllib.request
|
|
805
|
-
|
|
806
|
-
logger.info(f"
|
|
807
|
-
|
|
808
|
-
# Make executable
|
|
809
|
-
os.chmod(medic_path, 0o755)
|
|
810
|
-
return medic_path
|
|
835
|
+
urllib.request.urlretrieve(jar_url, jar_path)
|
|
836
|
+
logger.info(f"Downloaded ODK Validate JAR to {jar_path}")
|
|
837
|
+
return jar_path
|
|
811
838
|
except Exception as e:
|
|
812
|
-
logger.error(f"Failed to download
|
|
839
|
+
logger.error(f"Failed to download ODK Validate JAR: {str(e)}")
|
|
813
840
|
return None
|
|
814
841
|
|
|
815
842
|
def tricc_operation_zscore(self, ref_expressions):
|
|
@@ -836,7 +863,7 @@ class XLSFormCHTStrategy(XLSFormCDSSStrategy):
|
|
|
836
863
|
self.clean_coalesce(ref_expressions[2])
|
|
837
864
|
} ,{
|
|
838
865
|
self.clean_coalesce(ref_expressions[3])
|
|
839
|
-
}, true
|
|
866
|
+
}, true"""
|
|
840
867
|
|
|
841
868
|
def tricc_operation_drug_dosage(self, ref_expressions):
|
|
842
869
|
# drug name
|
tricc_oo/visitors/tricc.py
CHANGED
|
@@ -185,9 +185,9 @@ def get_version_inheritance(node, last_version, processed_nodes):
|
|
|
185
185
|
path_len=node.path_len + 1,
|
|
186
186
|
expression_reference=TriccOperation(
|
|
187
187
|
TriccOperator.COALESCE,
|
|
188
|
-
[
|
|
188
|
+
[node, last_version],
|
|
189
189
|
),
|
|
190
|
-
reference=[
|
|
190
|
+
reference=[node, last_version],
|
|
191
191
|
activity=node.activity,
|
|
192
192
|
group=node.group,
|
|
193
193
|
label=f"Save calculation for {node.label}",
|
|
@@ -805,7 +805,7 @@ def process_operation_reference(
|
|
|
805
805
|
operation,
|
|
806
806
|
node,
|
|
807
807
|
processed_nodes,
|
|
808
|
-
calculates,
|
|
808
|
+
calculates=None,
|
|
809
809
|
used_calculates=None,
|
|
810
810
|
replace_reference=False,
|
|
811
811
|
warn=False,
|
|
@@ -1735,29 +1735,33 @@ def replace_next_node(prev_node, next_node, old_node):
|
|
|
1735
1735
|
|
|
1736
1736
|
|
|
1737
1737
|
# Priority constants
|
|
1738
|
-
SAME_GROUP_PRIORITY =
|
|
1739
|
-
PARENT_GROUP_PRIORITY =
|
|
1740
|
-
ACTIVE_ACTIVITY_PRIORITY =
|
|
1741
|
-
NON_START_ACTIVITY_PRIORITY =
|
|
1742
|
-
ACTIVE_ACTIVITY_LOWER_PRIORITY =
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1738
|
+
SAME_GROUP_PRIORITY = 70
|
|
1739
|
+
PARENT_GROUP_PRIORITY = 60
|
|
1740
|
+
ACTIVE_ACTIVITY_PRIORITY = 50
|
|
1741
|
+
NON_START_ACTIVITY_PRIORITY = 40
|
|
1742
|
+
ACTIVE_ACTIVITY_LOWER_PRIORITY = 30
|
|
1743
|
+
FLOW_CALCULATE_NODE_PRIORITY_TOP_UP = 3
|
|
1744
|
+
RHOMBUS_PRIORITY_TO_UP = 3
|
|
1745
|
+
|
|
1746
|
+
|
|
1748
1747
|
def reorder_node_list(node_list, group, processed_nodes):
|
|
1749
1748
|
# Cache active activities for O(1) lookup
|
|
1750
1749
|
active_activities = {n.activity for n in processed_nodes}
|
|
1751
|
-
|
|
1750
|
+
MAP_PRIORITIES = {}
|
|
1752
1751
|
def get_priority(node):
|
|
1752
|
+
if node.id in MAP_PRIORITIES:
|
|
1753
|
+
return MAP_PRIORITIES[node.id]
|
|
1754
|
+
if isinstance(node, (TriccNodeActivityStart, TriccNodeMainStart)):
|
|
1755
|
+
return get_priority(node.activity)
|
|
1756
|
+
|
|
1753
1757
|
# Cache attributes to avoid repeated getattr calls
|
|
1754
|
-
priority = int(getattr(node, "priority", 0) or 0)
|
|
1758
|
+
priority = int(getattr(node, "priority", 0) or 0)
|
|
1755
1759
|
node_group = getattr(node, "group", None)
|
|
1756
1760
|
activity = getattr(node, "activity", None)
|
|
1757
1761
|
|
|
1758
1762
|
# Check for same group
|
|
1759
1763
|
if group is not None and node_group and node_group.id == group.id:
|
|
1760
|
-
priority += SAME_GROUP_PRIORITY
|
|
1764
|
+
priority += SAME_GROUP_PRIORITY
|
|
1761
1765
|
# Check for parent group
|
|
1762
1766
|
elif hasattr(group, "group") and group.group and node_group and node_group.id == group.group.id:
|
|
1763
1767
|
priority += PARENT_GROUP_PRIORITY
|
|
@@ -1767,21 +1771,25 @@ def reorder_node_list(node_list, group, processed_nodes):
|
|
|
1767
1771
|
# Check for non main activities
|
|
1768
1772
|
elif activity and isinstance(activity.root, TriccNodeActivityStart):
|
|
1769
1773
|
priority += NON_START_ACTIVITY_PRIORITY
|
|
1770
|
-
# Check for display calculate and end nodes with prev_nodes
|
|
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:
|
|
1775
|
-
priority += FLOW_CALCULATE_NODE_PRIORITY
|
|
1776
1774
|
# Check for active activities (lower priority)
|
|
1777
1775
|
elif activity and activity in active_activities:
|
|
1778
1776
|
priority += ACTIVE_ACTIVITY_LOWER_PRIORITY
|
|
1779
1777
|
# Check for rhombus nodes
|
|
1778
|
+
|
|
1779
|
+
|
|
1780
|
+
if (
|
|
1781
|
+
issubclass(node.__class__, TriccNodeDisplayCalculateBase) or
|
|
1782
|
+
isinstance(node, TriccNodeEnd)
|
|
1783
|
+
) and not isinstance(node, TriccNodeActivityEnd) and hasattr(node, 'prev_nodes') and len(node.prev_nodes) > 0:
|
|
1784
|
+
priority += FLOW_CALCULATE_NODE_PRIORITY_TOP_UP
|
|
1780
1785
|
elif issubclass(node.__class__, TriccRhombusMixIn):
|
|
1781
|
-
priority +=
|
|
1782
|
-
else:
|
|
1783
|
-
priority += DEFAULT_PRIORITY
|
|
1786
|
+
priority += RHOMBUS_PRIORITY_TO_UP
|
|
1784
1787
|
|
|
1788
|
+
if node.prev_nodes:
|
|
1789
|
+
priority = max(priority, *[get_priority(p) for p in node.prev_nodes])
|
|
1790
|
+
|
|
1791
|
+
MAP_PRIORITIES[node.id] = priority
|
|
1792
|
+
|
|
1785
1793
|
return priority
|
|
1786
1794
|
|
|
1787
1795
|
# Sort in place, highest priority first
|
|
@@ -1977,7 +1985,7 @@ def get_prev_instance_skip_expression(node, processed_nodes, process, expression
|
|
|
1977
1985
|
|
|
1978
1986
|
# end def
|
|
1979
1987
|
def get_process_skip_expression(node, processed_nodes, process, expression=None):
|
|
1980
|
-
list_ends =
|
|
1988
|
+
list_ends = [x for x in processed_nodes if isinstance(x, TriccNodeEnd)]
|
|
1981
1989
|
if list_ends:
|
|
1982
1990
|
end_expressions = []
|
|
1983
1991
|
f_end_expression = get_end_expression(list_ends)
|
|
@@ -1986,8 +1994,11 @@ def get_process_skip_expression(node, processed_nodes, process, expression=None)
|
|
|
1986
1994
|
b_end_expression = get_end_expression(list_ends, "pause")
|
|
1987
1995
|
if b_end_expression:
|
|
1988
1996
|
end_expressions.append(b_end_expression)
|
|
1989
|
-
|
|
1990
|
-
|
|
1997
|
+
process_index = None
|
|
1998
|
+
if process and process[0] in PROCESSES:
|
|
1999
|
+
process_index = PROCESSES.index(process[0])
|
|
2000
|
+
if process_index is not None:
|
|
2001
|
+
for p in PROCESSES[process_index + 1:]:
|
|
1991
2002
|
p_end_expression = get_end_expression(list_ends, p)
|
|
1992
2003
|
if p_end_expression:
|
|
1993
2004
|
end_expressions.append(p_end_expression)
|
|
@@ -2259,7 +2270,12 @@ def get_none_option(node):
|
|
|
2259
2270
|
def get_count_terms_details(prev_node, processed_nodes, get_overall_exp, negate=False, process=None):
|
|
2260
2271
|
opt_none = get_none_option(prev_node)
|
|
2261
2272
|
if opt_none:
|
|
2262
|
-
|
|
2273
|
+
if isinstance(opt_none, str):
|
|
2274
|
+
operation_none = TriccOperation(TriccOperator.SELECTED, [prev_node, TriccStatic(opt_none)])
|
|
2275
|
+
elif issubclass(opt_none.__class__, TriccBaseModel):
|
|
2276
|
+
operation_none = TriccOperation(TriccOperator.SELECTED, [prev_node, opt_none])
|
|
2277
|
+
else:
|
|
2278
|
+
logger.critical(f"unexpected none option value {opt_none}")
|
|
2263
2279
|
else:
|
|
2264
2280
|
operation_none = TriccOperation(TriccOperator.SELECTED, [prev_node, TriccStatic("opt_none")])
|
|
2265
2281
|
if isinstance(prev_node, TriccNodeSelectYesNo):
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
tests/build.py,sha256=Qbxvjkj_Wk2nQ-WjaMGiE1FIe3SRmJMRIgeoMoxqlfQ,6748
|
|
2
|
-
tests/test_build.py,sha256=5t8iliPe_0XwoZjSGkHxUbZaNOWBfc6SpIQijh9DLUA,10037
|
|
3
2
|
tests/test_cql.py,sha256=dAsLMqVaS6qxnq62fg5KqTFu6UG6pHO6Ab3NZ1c9T3Y,5248
|
|
4
3
|
tests/to_ocl.py,sha256=4e-i65K3UM6wHgdVcrZcM9AyL1bahIsXJiZTXhhHgQk,2048
|
|
5
4
|
tricc_oo/__init__.py,sha256=oWCE1ubmC_6iqaWOMgTei4eXVQgV202Ia-tXS1NnW_4,139
|
|
6
5
|
tricc_oo/converters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
tricc_oo/converters/codesystem_to_ocl.py,sha256=
|
|
6
|
+
tricc_oo/converters/codesystem_to_ocl.py,sha256=V_oZNVUVaYHgzJtDiBMSrvIJnBWmDNFBDTTHEkOfpXk,6151
|
|
8
7
|
tricc_oo/converters/cql_to_operation.py,sha256=PUyV_YpUY98Ox0H_F_CN3UUf_I-BhFZVOcWWKTtwecM,14492
|
|
9
|
-
tricc_oo/converters/datadictionnary.py,sha256=
|
|
10
|
-
tricc_oo/converters/drawio_type_map.py,sha256=
|
|
11
|
-
tricc_oo/converters/tricc_to_xls_form.py,sha256=
|
|
8
|
+
tricc_oo/converters/datadictionnary.py,sha256=JasqlLKiZzKdidsA1xc2SJ_Af1Xr6A3sKfzDynto8Ho,3686
|
|
9
|
+
tricc_oo/converters/drawio_type_map.py,sha256=Zp8J9iHNSJkIVrmRSM0_d4vA1X8wFPLKb8nCMPUMXKU,9114
|
|
10
|
+
tricc_oo/converters/tricc_to_xls_form.py,sha256=39hwWgYNitGE-AuKtjUwNLz39tEpwc7nd9gT_gw5wjc,3842
|
|
12
11
|
tricc_oo/converters/utils.py,sha256=JZrtrvvOfXwdkw49pKauzinOcauWwsy-CVcw36TjyLo,1684
|
|
13
|
-
tricc_oo/converters/xml_to_tricc.py,sha256=
|
|
12
|
+
tricc_oo/converters/xml_to_tricc.py,sha256=ea8LNEPDe32q74AJCbEjxaLt_Po47oH45K_G8fo7TzE,40388
|
|
14
13
|
tricc_oo/converters/cql/cqlLexer.py,sha256=8HArbRphcrpnAG4uogJ2rHv4tc1WLzjN0B1uFeYILAc,49141
|
|
15
14
|
tricc_oo/converters/cql/cqlListener.py,sha256=fA7-8DcS2Q69ckwjdg57-OfFHBxjTZFdoSKrtw7Hffc,57538
|
|
16
15
|
tricc_oo/converters/cql/cqlParser.py,sha256=x3KdrwX9nwENSEJ5Ex7_l5NMnu3kWBO0uLdYu4moTq0,414745
|
|
@@ -26,7 +25,7 @@ tricc_oo/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|
|
26
25
|
tricc_oo/parsers/xml.py,sha256=uzkb1y18MHfqVFmZqVh0sKT4cx6u0-NcAT_lV_gHBt8,4208
|
|
27
26
|
tricc_oo/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
27
|
tricc_oo/serializers/planuml.py,sha256=t57587-6L3aDncpHh58lS77Zft8yxDE9DPtXx2BeUSU,132
|
|
29
|
-
tricc_oo/serializers/xls_form.py,sha256=
|
|
28
|
+
tricc_oo/serializers/xls_form.py,sha256=ydaD5l_CsMEhJlSO0XvXPdxrwsDw4HiRjTOPHogTQdw,23071
|
|
30
29
|
tricc_oo/strategies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
30
|
tricc_oo/strategies/input/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
31
|
tricc_oo/strategies/input/base_input_strategy.py,sha256=BEODXS74na1QRRcJVQ4cxiD8F7uRqaLyhE3QzKpGVvk,3891
|
|
@@ -39,14 +38,14 @@ tricc_oo/strategies/output/openmrs_form.py,sha256=ne6TwAyhafR-WDs27QTKKFl85VD5si
|
|
|
39
38
|
tricc_oo/strategies/output/spice.py,sha256=QMeoismVC3PdbvwTK0PtUjWX9jl9780fbQIXn76fMXw,10761
|
|
40
39
|
tricc_oo/strategies/output/xls_form.py,sha256=_pNTND7n-55EjRphJ1hSVtRYa-UkXlmwpam2OKQ8o_w,30860
|
|
41
40
|
tricc_oo/strategies/output/xlsform_cdss.py,sha256=X00Lt5MzV8TX14dR4dFI1MqllI5S1e13bKbeysWM9uA,17435
|
|
42
|
-
tricc_oo/strategies/output/xlsform_cht.py,sha256=
|
|
41
|
+
tricc_oo/strategies/output/xlsform_cht.py,sha256=Zy8aggR1sTBP0b33RGbfUpk8pRppI1LGQEif4E1l49A,28523
|
|
43
42
|
tricc_oo/strategies/output/xlsform_cht_hf.py,sha256=xm6SKirV3nMZvM2w54_zJcXAeAgAkq-EEqGEjnOWv6c,988
|
|
44
43
|
tricc_oo/visitors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
|
-
tricc_oo/visitors/tricc.py,sha256=
|
|
44
|
+
tricc_oo/visitors/tricc.py,sha256=QkVZpmDvSrMbMQeCxVn15zSk3iH-3-e-udMys96JkXE,110923
|
|
46
45
|
tricc_oo/visitors/utils.py,sha256=j83aAq5s5atXi3OC0jc_uJd54a8XrHHmizeeEbWZQJg,421
|
|
47
46
|
tricc_oo/visitors/xform_pd.py,sha256=ryAnI3V9x3eTmJ2LNsUZfvl0_yfCqo6oBgeSu-WPqaE,9613
|
|
48
|
-
tricc_oo-1.6.
|
|
49
|
-
tricc_oo-1.6.
|
|
50
|
-
tricc_oo-1.6.
|
|
51
|
-
tricc_oo-1.6.
|
|
52
|
-
tricc_oo-1.6.
|
|
47
|
+
tricc_oo-1.6.14.dist-info/licenses/LICENSE,sha256=Pz2eACSxkhsGfW9_iN60pgy-enjnbGTj8df8O3ebnQQ,16726
|
|
48
|
+
tricc_oo-1.6.14.dist-info/METADATA,sha256=_WkaLTsN1Yj8koCLVrOmdY9egk06du90gsMqRCcQ0Js,8600
|
|
49
|
+
tricc_oo-1.6.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
50
|
+
tricc_oo-1.6.14.dist-info/top_level.txt,sha256=NvbfMNAiy9m4b1unBsqpeOQWh4IgA1Xa33BtKA4abxk,15
|
|
51
|
+
tricc_oo-1.6.14.dist-info/RECORD,,
|
tests/test_build.py
DELETED
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
import subprocess
|
|
3
|
-
import sys
|
|
4
|
-
import os
|
|
5
|
-
import tempfile
|
|
6
|
-
import shutil
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
import pandas as pd
|
|
9
|
-
from pyxform import create_survey_from_xls
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TestBuildScript(unittest.TestCase):
|
|
13
|
-
"""Test cases for the build.py script with different argument combinations."""
|
|
14
|
-
|
|
15
|
-
def setUp(self):
|
|
16
|
-
"""Set up test fixtures."""
|
|
17
|
-
self.test_data_dir = Path(__file__).parent / "data"
|
|
18
|
-
self.test_output_dir = Path(__file__).parent / "output"
|
|
19
|
-
self.demo_file = self.test_data_dir / "demo.drawio"
|
|
20
|
-
|
|
21
|
-
# Ensure test data exists
|
|
22
|
-
self.assertTrue(self.demo_file.exists(), f"Test data file {self.demo_file} does not exist")
|
|
23
|
-
|
|
24
|
-
def run_build_script(self, args):
|
|
25
|
-
"""Helper method to run build.py with given arguments."""
|
|
26
|
-
cmd = [sys.executable, str(Path(__file__).parent / "build.py")] + args
|
|
27
|
-
result = subprocess.run(cmd, capture_output=True, text=True, cwd=Path(__file__).parent.parent)
|
|
28
|
-
return result
|
|
29
|
-
|
|
30
|
-
def test_basic_build_with_demo_file(self):
|
|
31
|
-
"""Test basic build with demo.drawio file."""
|
|
32
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
33
|
-
args = [
|
|
34
|
-
"-i", str(self.demo_file),
|
|
35
|
-
"-o", temp_dir,
|
|
36
|
-
"-l", "i"
|
|
37
|
-
]
|
|
38
|
-
result = self.run_build_script(args)
|
|
39
|
-
self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
40
|
-
|
|
41
|
-
def test_build_with_directory_input(self):
|
|
42
|
-
"""Test build with directory containing drawio files."""
|
|
43
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
44
|
-
args = [
|
|
45
|
-
"-i", str(self.test_data_dir),
|
|
46
|
-
"-o", temp_dir,
|
|
47
|
-
"-l", "i"
|
|
48
|
-
]
|
|
49
|
-
result = self.run_build_script(args)
|
|
50
|
-
self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
51
|
-
|
|
52
|
-
def test_build_with_xlsform_strategy(self):
|
|
53
|
-
"""Test build with XLSFormStrategy (default)."""
|
|
54
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
55
|
-
args = [
|
|
56
|
-
"-i", str(self.demo_file),
|
|
57
|
-
"-o", temp_dir,
|
|
58
|
-
"-O", "XLSFormStrategy",
|
|
59
|
-
"-l", "i"
|
|
60
|
-
]
|
|
61
|
-
result = self.run_build_script(args)
|
|
62
|
-
self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
63
|
-
|
|
64
|
-
def test_build_with_html_strategy(self):
|
|
65
|
-
"""Test build with HTMLStrategy."""
|
|
66
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
67
|
-
args = [
|
|
68
|
-
"-i", str(self.demo_file),
|
|
69
|
-
"-o", temp_dir,
|
|
70
|
-
"-O", "HTMLStrategy",
|
|
71
|
-
"-l", "i"
|
|
72
|
-
]
|
|
73
|
-
result = self.run_build_script(args)
|
|
74
|
-
self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
75
|
-
|
|
76
|
-
def test_build_with_fhir_strategy(self):
|
|
77
|
-
"""Test build with FHIRStrategy."""
|
|
78
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
79
|
-
args = [
|
|
80
|
-
"-i", str(self.demo_file),
|
|
81
|
-
"-o", temp_dir,
|
|
82
|
-
"-O", "FHIRStrategy",
|
|
83
|
-
"-l", "i"
|
|
84
|
-
]
|
|
85
|
-
result = self.run_build_script(args)
|
|
86
|
-
self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
87
|
-
|
|
88
|
-
def test_build_with_dhis2_strategy(self):
|
|
89
|
-
"""Test build with DHIS2Strategy."""
|
|
90
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
91
|
-
args = [
|
|
92
|
-
"-i", str(self.demo_file),
|
|
93
|
-
"-o", temp_dir,
|
|
94
|
-
"-O", "DHIS2Strategy",
|
|
95
|
-
"-l", "i"
|
|
96
|
-
]
|
|
97
|
-
result = self.run_build_script(args)
|
|
98
|
-
self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
99
|
-
|
|
100
|
-
def test_build_with_openmrs_strategy(self):
|
|
101
|
-
"""Test build with OpenMRSStrategy."""
|
|
102
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
103
|
-
args = [
|
|
104
|
-
"-i", str(self.demo_file),
|
|
105
|
-
"-o", temp_dir,
|
|
106
|
-
"-O", "OpenMRSStrategy",
|
|
107
|
-
"-l", "i"
|
|
108
|
-
]
|
|
109
|
-
result = self.run_build_script(args)
|
|
110
|
-
self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
111
|
-
|
|
112
|
-
def test_build_with_cht_strategy(self):
|
|
113
|
-
"""Test build with XLSFormCHTStrategy."""
|
|
114
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
115
|
-
args = [
|
|
116
|
-
"-i", str(self.demo_file),
|
|
117
|
-
"-o", temp_dir,
|
|
118
|
-
"-O", "XLSFormCHTStrategy",
|
|
119
|
-
"-l", "i"
|
|
120
|
-
]
|
|
121
|
-
result = self.run_build_script(args)
|
|
122
|
-
self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
123
|
-
|
|
124
|
-
def test_build_with_cht_hf_strategy(self):
|
|
125
|
-
"""Test build with XLSFormCHTHFStrategy."""
|
|
126
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
127
|
-
args = [
|
|
128
|
-
"-i", str(self.demo_file),
|
|
129
|
-
"-o", temp_dir,
|
|
130
|
-
"-O", "XLSFormCHTHFStrategy",
|
|
131
|
-
"-l", "i"
|
|
132
|
-
]
|
|
133
|
-
result = self.run_build_script(args)
|
|
134
|
-
self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
135
|
-
|
|
136
|
-
def test_build_with_cdss_strategy(self):
|
|
137
|
-
"""Test build with XLSFormCDSSStrategy."""
|
|
138
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
139
|
-
args = [
|
|
140
|
-
"-i", str(self.demo_file),
|
|
141
|
-
"-o", temp_dir,
|
|
142
|
-
"-O", "XLSFormCDSSStrategy",
|
|
143
|
-
"-l", "i"
|
|
144
|
-
]
|
|
145
|
-
result = self.run_build_script(args)
|
|
146
|
-
self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
147
|
-
|
|
148
|
-
# def test_build_with_spice_strategy(self):
|
|
149
|
-
# """Test build with SpiceStrategy."""
|
|
150
|
-
# with tempfile.TemporaryDirectory() as temp_dir:
|
|
151
|
-
# args = [
|
|
152
|
-
# "-i", str(self.demo_file),
|
|
153
|
-
# "-o", temp_dir,
|
|
154
|
-
# "-O", "SpiceStrategy",
|
|
155
|
-
# "-l", "i"
|
|
156
|
-
# ]
|
|
157
|
-
# result = self.run_build_script(args)
|
|
158
|
-
# self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
159
|
-
|
|
160
|
-
def test_build_with_debug_level(self):
|
|
161
|
-
"""Test build with debug logging level."""
|
|
162
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
163
|
-
args = [
|
|
164
|
-
"-i", str(self.demo_file),
|
|
165
|
-
"-o", temp_dir,
|
|
166
|
-
"-l", "d"
|
|
167
|
-
]
|
|
168
|
-
result = self.run_build_script(args)
|
|
169
|
-
self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
170
|
-
|
|
171
|
-
def test_build_with_trad_option(self):
|
|
172
|
-
"""Test build with translation option."""
|
|
173
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
174
|
-
args = [
|
|
175
|
-
"-i", str(self.demo_file),
|
|
176
|
-
"-o", temp_dir,
|
|
177
|
-
"-t",
|
|
178
|
-
"-l", "i"
|
|
179
|
-
]
|
|
180
|
-
result = self.run_build_script(args)
|
|
181
|
-
self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
182
|
-
|
|
183
|
-
def test_build_with_form_id(self):
|
|
184
|
-
"""Test build with custom form ID."""
|
|
185
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
186
|
-
args = [
|
|
187
|
-
"-i", str(self.demo_file),
|
|
188
|
-
"-o", temp_dir,
|
|
189
|
-
"-d", "test_form_123",
|
|
190
|
-
"-l", "i"
|
|
191
|
-
]
|
|
192
|
-
result = self.run_build_script(args)
|
|
193
|
-
self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
194
|
-
|
|
195
|
-
def test_build_missing_input(self):
|
|
196
|
-
"""Test build with missing input file."""
|
|
197
|
-
args = ["-o", "/tmp/test_output"]
|
|
198
|
-
result = self.run_build_script(args)
|
|
199
|
-
self.assertNotEqual(result.returncode, 0, "Build should fail with missing input")
|
|
200
|
-
|
|
201
|
-
def test_build_invalid_input_file(self):
|
|
202
|
-
"""Test build with invalid input file."""
|
|
203
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
204
|
-
args = [
|
|
205
|
-
"-i", "/nonexistent/file.drawio",
|
|
206
|
-
"-o", temp_dir,
|
|
207
|
-
"-l", "i"
|
|
208
|
-
]
|
|
209
|
-
result = self.run_build_script(args)
|
|
210
|
-
self.assertNotEqual(result.returncode, 0, "Build should fail with invalid input file")
|
|
211
|
-
|
|
212
|
-
def validate_xls_form(self, xls_path):
|
|
213
|
-
"""Helper method to validate XLS form using ODK libraries."""
|
|
214
|
-
try:
|
|
215
|
-
# Convert XLS to XML using pyxform
|
|
216
|
-
survey = create_survey_from_xls(xls_path)
|
|
217
|
-
xml_output = survey.to_xml()
|
|
218
|
-
|
|
219
|
-
# Basic validation - check if XML was generated successfully
|
|
220
|
-
# In a real scenario, you might want to use odk_validate command line tool
|
|
221
|
-
if xml_output and len(xml_output.strip()) > 0:
|
|
222
|
-
return True, "Validation successful"
|
|
223
|
-
else:
|
|
224
|
-
return False, "Empty XML output"
|
|
225
|
-
except Exception as e:
|
|
226
|
-
return False, str(e)
|
|
227
|
-
|
|
228
|
-
def test_xlsform_strategy_validation(self):
|
|
229
|
-
"""Test XLSFormStrategy output validation with ODK libraries."""
|
|
230
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
231
|
-
xls_output = Path(temp_dir) / "demo_tricc.xlsx"
|
|
232
|
-
args = [
|
|
233
|
-
"-i", str(self.demo_file),
|
|
234
|
-
"-o", temp_dir,
|
|
235
|
-
"-O", "XLSFormStrategy",
|
|
236
|
-
"-l", "i"
|
|
237
|
-
]
|
|
238
|
-
result = self.run_build_script(args)
|
|
239
|
-
self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
240
|
-
|
|
241
|
-
# Check if XLS file was created
|
|
242
|
-
self.assertTrue(xls_output.exists(), f"XLS file {xls_output} was not created")
|
|
243
|
-
|
|
244
|
-
def test_xlsform_cdss_strategy_validation(self):
|
|
245
|
-
"""Test XLSFormCDSSStrategy output validation with ODK libraries."""
|
|
246
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
247
|
-
xls_output = Path(temp_dir) / "demo_tricc.xlsx"
|
|
248
|
-
args = [
|
|
249
|
-
"-i", str(self.demo_file),
|
|
250
|
-
"-o", temp_dir,
|
|
251
|
-
"-O", "XLSFormCDSSStrategy",
|
|
252
|
-
"-l", "i"
|
|
253
|
-
]
|
|
254
|
-
result = self.run_build_script(args)
|
|
255
|
-
self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if __name__ == "__main__":
|
|
260
|
-
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|