tricc-oo 1.5.28__py3-none-any.whl → 1.6.0__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.
- tests/build.py +1 -0
- tricc_oo/converters/tricc_to_xls_form.py +2 -2
- tricc_oo/converters/xml_to_tricc.py +8 -7
- tricc_oo/models/base.py +2 -0
- tricc_oo/strategies/output/dhis2_form.py +859 -0
- tricc_oo/strategies/output/openmrs_form.py +51 -4
- tricc_oo/visitors/tricc.py +38 -28
- {tricc_oo-1.5.28.dist-info → tricc_oo-1.6.0.dist-info}/METADATA +1 -1
- {tricc_oo-1.5.28.dist-info → tricc_oo-1.6.0.dist-info}/RECORD +12 -11
- {tricc_oo-1.5.28.dist-info → tricc_oo-1.6.0.dist-info}/WHEEL +0 -0
- {tricc_oo-1.5.28.dist-info → tricc_oo-1.6.0.dist-info}/licenses/LICENSE +0 -0
- {tricc_oo-1.5.28.dist-info → tricc_oo-1.6.0.dist-info}/top_level.txt +0 -0
tests/build.py
CHANGED
|
@@ -6,6 +6,7 @@ from tricc_oo.strategies.output.xls_form import XLSFormStrategy # noqa: F401
|
|
|
6
6
|
from tricc_oo.strategies.output.openmrs_form import OpenMRSStrategy # noqa: F401
|
|
7
7
|
from tricc_oo.strategies.output.fhir_form import FHIRStrategy # noqa: F401
|
|
8
8
|
from tricc_oo.strategies.output.html_form import HTMLStrategy # noqa: F401
|
|
9
|
+
from tricc_oo.strategies.output.dhis2_form import DHIS2Strategy # noqa: F401
|
|
9
10
|
from tricc_oo.strategies.input.drawio import DrawioStrategy # noqa: F401
|
|
10
11
|
import getopt
|
|
11
12
|
import logging
|
|
@@ -53,8 +53,8 @@ def get_export_name(node, replace_dots=True):
|
|
|
53
53
|
export_name = BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]
|
|
54
54
|
elif value == '$this':
|
|
55
55
|
export_name = '.'
|
|
56
|
-
elif isinstance(value, str):
|
|
57
|
-
export_name = f"'{
|
|
56
|
+
elif isinstance(value, str) and not isinstance(node, str):
|
|
57
|
+
export_name = f"'{value}'"
|
|
58
58
|
else:
|
|
59
59
|
export_name = value
|
|
60
60
|
if hasattr(node, 'export_name'):
|
|
@@ -101,16 +101,16 @@ def create_activity(diagram, media_path, project):
|
|
|
101
101
|
|
|
102
102
|
external_id = diagram.attrib.get("id")
|
|
103
103
|
id = get_id(external_id, diagram.attrib.get("id"))
|
|
104
|
-
root = create_root_node(diagram)
|
|
105
|
-
|
|
104
|
+
root, name = create_root_node(diagram)
|
|
105
|
+
label = diagram.attrib.get("name")
|
|
106
106
|
form_id = diagram.attrib.get("name", None)
|
|
107
107
|
if root is not None:
|
|
108
108
|
activity = TriccNodeActivity(
|
|
109
109
|
root=root,
|
|
110
|
-
name=
|
|
110
|
+
name=name, # start node 'name' is saved in label
|
|
111
111
|
id=id,
|
|
112
112
|
external_id=external_id,
|
|
113
|
-
label=
|
|
113
|
+
label=label,
|
|
114
114
|
form_id=form_id,
|
|
115
115
|
)
|
|
116
116
|
if root.relevance is not None:
|
|
@@ -130,7 +130,7 @@ def create_activity(diagram, media_path, project):
|
|
|
130
130
|
if (
|
|
131
131
|
issubclass(n.__class__, (TriccNodeDisplayModel, TriccNodeDisplayCalculateBase))
|
|
132
132
|
and not isinstance(n, (TriccRhombusMixIn, TriccNodeRhombus, TriccNodeDisplayBridge))
|
|
133
|
-
and not n.name.startswith("label_")
|
|
133
|
+
and not n.name.startswith("label_") # FIXME
|
|
134
134
|
):
|
|
135
135
|
system = n.name.split(".")[0] if "." in n.name else "tricc"
|
|
136
136
|
if isinstance(n, TriccNodeSelectOption) and isinstance(n.select, TriccNodeSelectNotAvailable):
|
|
@@ -444,17 +444,18 @@ def create_root_node(diagram):
|
|
|
444
444
|
if elm is not None:
|
|
445
445
|
external_id = elm.attrib.get("id")
|
|
446
446
|
id = get_id(external_id, diagram.attrib.get("id"))
|
|
447
|
+
name = generate_id("start"+external_id)
|
|
447
448
|
node = TriccNodeActivityStart(
|
|
448
449
|
id=id,
|
|
449
450
|
external_id=external_id,
|
|
450
451
|
# parent=elm.attrib.get("parent"),
|
|
451
|
-
name=
|
|
452
|
+
name=name,
|
|
452
453
|
label=diagram.attrib.get("name"),
|
|
453
454
|
relevance=elm.attrib.get("relevance"),
|
|
454
455
|
instance=int(elm.attrib.get("instance") if elm.attrib.get("instance") is not None else 1),
|
|
455
456
|
)
|
|
456
457
|
load_expressions(node)
|
|
457
|
-
return node
|
|
458
|
+
return node, elm.attrib.get("name", generate_id("act"+external_id))
|
|
458
459
|
|
|
459
460
|
|
|
460
461
|
# converter XML item to object
|
tricc_oo/models/base.py
CHANGED
|
@@ -0,0 +1,859 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import uuid
|
|
5
|
+
import string
|
|
6
|
+
from tricc_oo.visitors.tricc import (
|
|
7
|
+
is_ready_to_process,
|
|
8
|
+
process_reference,
|
|
9
|
+
generate_base,
|
|
10
|
+
generate_calculate,
|
|
11
|
+
walktrhough_tricc_node_processed_stached,
|
|
12
|
+
check_stashed_loop,
|
|
13
|
+
)
|
|
14
|
+
from tricc_oo.converters.tricc_to_xls_form import get_export_name
|
|
15
|
+
import datetime
|
|
16
|
+
from tricc_oo.strategies.output.base_output_strategy import BaseOutPutStrategy
|
|
17
|
+
from tricc_oo.models.base import (
|
|
18
|
+
not_clean, TriccOperation,
|
|
19
|
+
TriccStatic, TriccReference
|
|
20
|
+
)
|
|
21
|
+
from tricc_oo.models.tricc import (
|
|
22
|
+
TriccNodeSelectOption,
|
|
23
|
+
TriccNodeInputModel,
|
|
24
|
+
TriccNodeBaseModel,
|
|
25
|
+
TriccNodeDisplayModel,
|
|
26
|
+
TriccNodeCalculateBase,
|
|
27
|
+
TriccNodeActivity,
|
|
28
|
+
)
|
|
29
|
+
from tricc_oo.models.calculate import TriccNodeDisplayCalculateBase
|
|
30
|
+
from tricc_oo.models.ordered_set import OrderedSet
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger("default")
|
|
33
|
+
|
|
34
|
+
# Namespace for deterministic UUIDs
|
|
35
|
+
UUID_NAMESPACE = uuid.UUID('87654321-4321-8765-cba9-fed098765432')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DHIS2Strategy(BaseOutPutStrategy):
|
|
39
|
+
processes = ["main"]
|
|
40
|
+
project = None
|
|
41
|
+
output_path = None
|
|
42
|
+
|
|
43
|
+
def __init__(self, project, output_path):
|
|
44
|
+
super().__init__(project, output_path)
|
|
45
|
+
form_id = getattr(self.project.start_pages["main"], 'form_id', 'dhis2_program')
|
|
46
|
+
self.program_metadata = {
|
|
47
|
+
"id": self.generate_id(form_id),
|
|
48
|
+
"name": form_id,
|
|
49
|
+
"shortName": form_id[:50], # DHIS2 shortName limit
|
|
50
|
+
"programType": "WITHOUT_REGISTRATION",
|
|
51
|
+
"programStages": [],
|
|
52
|
+
"programRules": []
|
|
53
|
+
}
|
|
54
|
+
self.option_sets = {}
|
|
55
|
+
self.options = {}
|
|
56
|
+
self.data_elements = {}
|
|
57
|
+
self.program_rules = []
|
|
58
|
+
self.program_rule_actions = []
|
|
59
|
+
self.program_rule_variables = []
|
|
60
|
+
self.field_counter = 1
|
|
61
|
+
self.current_section = None
|
|
62
|
+
self.concept_map = {}
|
|
63
|
+
# Track programRuleActions per stage
|
|
64
|
+
self.stage_rule_actions = {}
|
|
65
|
+
self.sections = {}
|
|
66
|
+
|
|
67
|
+
def get_export_name(self, r):
|
|
68
|
+
if isinstance(r, TriccNodeSelectOption):
|
|
69
|
+
return self.get_option_value(r.name)
|
|
70
|
+
elif isinstance(r, str):
|
|
71
|
+
return self.get_option_value(r)
|
|
72
|
+
elif isinstance(r, TriccStatic):
|
|
73
|
+
if isinstance(r.value, str):
|
|
74
|
+
return self.get_option_value(r.value)
|
|
75
|
+
elif isinstance(r.value, bool):
|
|
76
|
+
return str(r.value).lower()
|
|
77
|
+
else:
|
|
78
|
+
return r.value
|
|
79
|
+
else:
|
|
80
|
+
return get_export_name(r)
|
|
81
|
+
|
|
82
|
+
def generate_id(self, name):
|
|
83
|
+
"""Generate DHIS2-compliant UID: 1 letter + 10 alphanumeric characters"""
|
|
84
|
+
# Convert UUID to base62-like string and take first 11 chars, ensuring starts with letter
|
|
85
|
+
# Create DHIS2 UID: start with letter, followed by 10 alphanum chars
|
|
86
|
+
letters = string.ascii_letters
|
|
87
|
+
alphanum = string.ascii_letters + string.digits
|
|
88
|
+
|
|
89
|
+
# Use hash of the name to get deterministic but varied results
|
|
90
|
+
import hashlib
|
|
91
|
+
hash_obj = hashlib.md5(name.encode('utf-8')).digest()
|
|
92
|
+
hash_int = int.from_bytes(hash_obj, byteorder='big')
|
|
93
|
+
|
|
94
|
+
# First character: letter
|
|
95
|
+
first_char = letters[hash_int % len(letters)]
|
|
96
|
+
|
|
97
|
+
# Remaining 10 characters: alphanumeric
|
|
98
|
+
remaining_chars = ''
|
|
99
|
+
for i in range(10):
|
|
100
|
+
remaining_chars += alphanum[(hash_int >> (i * 6)) % len(alphanum)]
|
|
101
|
+
|
|
102
|
+
return first_char + remaining_chars
|
|
103
|
+
|
|
104
|
+
def get_option_value(self, option_name):
|
|
105
|
+
if option_name == 'true':
|
|
106
|
+
return TriccStatic(True)
|
|
107
|
+
elif option_name == 'false':
|
|
108
|
+
return TriccStatic(False)
|
|
109
|
+
return self.concept_map.get(option_name, option_name)
|
|
110
|
+
|
|
111
|
+
def get_tricc_operation_expression(self, operation):
|
|
112
|
+
ref_expressions = []
|
|
113
|
+
if not hasattr(operation, "reference"):
|
|
114
|
+
return self.get_tricc_operation_operand(operation)
|
|
115
|
+
for r in operation.reference:
|
|
116
|
+
if isinstance(r, list):
|
|
117
|
+
r_expr = [
|
|
118
|
+
(
|
|
119
|
+
self.get_tricc_operation_expression(sr)
|
|
120
|
+
if isinstance(sr, TriccOperation)
|
|
121
|
+
else self.get_tricc_operation_operand(sr)
|
|
122
|
+
)
|
|
123
|
+
for sr in r
|
|
124
|
+
]
|
|
125
|
+
elif isinstance(r, TriccOperation):
|
|
126
|
+
r_expr = self.get_tricc_operation_expression(r)
|
|
127
|
+
else:
|
|
128
|
+
r_expr = self.get_tricc_operation_operand(r)
|
|
129
|
+
if isinstance(r_expr, TriccReference):
|
|
130
|
+
r_expr = self.get_tricc_operation_operand(r_expr)
|
|
131
|
+
elif isinstance(r_expr, TriccStatic) and isinstance(r_expr.value, bool):
|
|
132
|
+
r_expr = str(r_expr.value).lower()
|
|
133
|
+
ref_expressions.append(r_expr)
|
|
134
|
+
|
|
135
|
+
if hasattr(self, f"tricc_operation_{operation.operator}"):
|
|
136
|
+
callable = getattr(self, f"tricc_operation_{operation.operator}")
|
|
137
|
+
return callable(ref_expressions)
|
|
138
|
+
else:
|
|
139
|
+
raise NotImplementedError(
|
|
140
|
+
f"This type of operation '{operation.operator}' is not supported in this strategy"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def execute(self):
|
|
144
|
+
version = datetime.datetime.now().strftime("%Y%m%d%H%M")
|
|
145
|
+
logger.info(f"build version: {version}")
|
|
146
|
+
if "main" in self.project.start_pages:
|
|
147
|
+
self.process_base(self.project.start_pages, pages=self.project.pages, version=version)
|
|
148
|
+
else:
|
|
149
|
+
logger.critical("Main process required")
|
|
150
|
+
|
|
151
|
+
logger.info("generate the relevance based on edges")
|
|
152
|
+
self.process_relevance(self.project.start_pages, pages=self.project.pages)
|
|
153
|
+
|
|
154
|
+
logger.info("generate the calculate based on edges")
|
|
155
|
+
self.process_calculate(self.project.start_pages, pages=self.project.pages)
|
|
156
|
+
|
|
157
|
+
logger.info("generate the export format")
|
|
158
|
+
self.process_export(self.project.start_pages, pages=self.project.pages)
|
|
159
|
+
|
|
160
|
+
logger.info("print the export")
|
|
161
|
+
self.export(self.project.start_pages, version=version)
|
|
162
|
+
|
|
163
|
+
def map_tricc_type_to_dhis2_value_type(self, node):
|
|
164
|
+
mapping = {
|
|
165
|
+
'text': 'TEXT',
|
|
166
|
+
'integer': 'INTEGER',
|
|
167
|
+
'decimal': 'NUMBER',
|
|
168
|
+
'date': 'DATE',
|
|
169
|
+
'datetime': 'DATETIME',
|
|
170
|
+
'select_one': 'TEXT', # DHIS2 handles options via optionSets
|
|
171
|
+
'select_multiple': 'TEXT', # Multiple selections as comma-separated
|
|
172
|
+
'select_yesno': 'BOOLEAN',
|
|
173
|
+
'yesno': 'BOOLEAN',
|
|
174
|
+
'boolean': 'BOOLEAN',
|
|
175
|
+
'not_available': 'BOOLEAN',
|
|
176
|
+
'note': 'LONG_TEXT'
|
|
177
|
+
}
|
|
178
|
+
return mapping.get(node.tricc_type, 'TEXT')
|
|
179
|
+
|
|
180
|
+
def generate_base(self, node, processed_nodes, **kwargs):
|
|
181
|
+
if generate_base(node, processed_nodes, **kwargs):
|
|
182
|
+
if getattr(node, 'name', '') not in ('true', 'false'):
|
|
183
|
+
self.concept_map[node.name] = self.generate_id(self.get_export_name(node))
|
|
184
|
+
return True
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
def generate_relevance(self, node, processed_nodes, **kwargs):
|
|
188
|
+
if not is_ready_to_process(node, processed_nodes, strict=True):
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
if node not in processed_nodes:
|
|
192
|
+
relevance = None
|
|
193
|
+
if hasattr(node, 'relevance') and node.relevance:
|
|
194
|
+
relevance = node.relevance
|
|
195
|
+
if hasattr(node, 'expression') and node.expression:
|
|
196
|
+
relevance = node.expression
|
|
197
|
+
if relevance:
|
|
198
|
+
relevance_str = self.convert_expression_to_string(not_clean(relevance))
|
|
199
|
+
if relevance_str and relevance_str != 'false':
|
|
200
|
+
# Create program rule action for hiding/showing based on relevance
|
|
201
|
+
rule_id = self.generate_id(f"rule_{node.get_name()}_relevance")
|
|
202
|
+
action_id = self.generate_id(f"action_{rule_id}")
|
|
203
|
+
|
|
204
|
+
if isinstance(node, TriccNodeActivity):
|
|
205
|
+
# For activities, use HIDESECTION action instead of HIDEFIELD
|
|
206
|
+
# Store activity reference for later section ID assignment
|
|
207
|
+
program_rule_action = {
|
|
208
|
+
"id": action_id,
|
|
209
|
+
"programRuleActionType": "HIDESECTION",
|
|
210
|
+
"activity_ref": node, # Temporary reference to be replaced with section ID
|
|
211
|
+
"programRule": {"id": rule_id},
|
|
212
|
+
}
|
|
213
|
+
else:
|
|
214
|
+
# For regular nodes, use HIDEFIELD action
|
|
215
|
+
program_rule_action = {
|
|
216
|
+
"id": action_id,
|
|
217
|
+
"programRuleActionType": "HIDEFIELD",
|
|
218
|
+
"dataElement": {
|
|
219
|
+
"id": self.generate_id(self.get_export_name(node))
|
|
220
|
+
},
|
|
221
|
+
"programRule": {"id": rule_id}
|
|
222
|
+
}
|
|
223
|
+
self.program_rule_actions.append(program_rule_action)
|
|
224
|
+
|
|
225
|
+
# Create program rule referencing the action
|
|
226
|
+
self.program_rules.append({
|
|
227
|
+
"id": rule_id,
|
|
228
|
+
"name": f"Hide {node.get_name()} when condition met",
|
|
229
|
+
"description": f"Hide {node.get_name()} based on relevance",
|
|
230
|
+
"condition": f"!({relevance_str})", # Negate for hide when true
|
|
231
|
+
"programRuleActions": [{"id": action_id}]
|
|
232
|
+
})
|
|
233
|
+
return True
|
|
234
|
+
|
|
235
|
+
def generate_data_element(self, node):
|
|
236
|
+
if issubclass(node.__class__, TriccNodeDisplayModel) and not isinstance(node, TriccNodeSelectOption):
|
|
237
|
+
de_id = self.generate_id(self.get_export_name(node))
|
|
238
|
+
|
|
239
|
+
# Check if this is a boolean question (yes/no with boolean options)
|
|
240
|
+
is_boolean_question = False
|
|
241
|
+
if hasattr(node, 'options') and node.options:
|
|
242
|
+
option_names = [
|
|
243
|
+
str(self.get_export_name(opt)).lower()
|
|
244
|
+
for opt in node.options.values()
|
|
245
|
+
if isinstance(opt, TriccNodeSelectOption)]
|
|
246
|
+
# If options are only true/false or yes/no variants, treat as boolean
|
|
247
|
+
boolean_options = {'true', 'false', 'yes', 'no', '1', '0'}
|
|
248
|
+
if all(opt in boolean_options for opt in option_names):
|
|
249
|
+
is_boolean_question = True
|
|
250
|
+
|
|
251
|
+
# Override valueType for boolean questions
|
|
252
|
+
value_type = self.map_tricc_type_to_dhis2_value_type(node)
|
|
253
|
+
if is_boolean_question:
|
|
254
|
+
value_type = "BOOLEAN"
|
|
255
|
+
|
|
256
|
+
data_element = {
|
|
257
|
+
"id": de_id,
|
|
258
|
+
"name": self.get_export_name(node),
|
|
259
|
+
"shortName": node.name[:50],
|
|
260
|
+
"displayFormName": getattr(node, 'label', node.name).replace('\u00a0', ' ').strip(),
|
|
261
|
+
"valueType": value_type,
|
|
262
|
+
"domainType": "TRACKER",
|
|
263
|
+
"aggregationType": "NONE"
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# Only create optionSet for non-boolean select questions
|
|
267
|
+
if node.tricc_type in ['select_one', 'select_multiple'] and not is_boolean_question:
|
|
268
|
+
# Create optionSet for choices
|
|
269
|
+
if hasattr(node, 'options') and node.options:
|
|
270
|
+
option_set_id = self.generate_id(f"optionset_{node.name}")
|
|
271
|
+
data_element["optionSet"] = {"id": option_set_id}
|
|
272
|
+
|
|
273
|
+
# Create the actual optionSet definition
|
|
274
|
+
option_set = {
|
|
275
|
+
"id": option_set_id,
|
|
276
|
+
"name": f"{node.name} Options",
|
|
277
|
+
"shortName": f"{node.name}_opts"[:50],
|
|
278
|
+
"valueType": "TEXT",
|
|
279
|
+
"options": []
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# Add options (node.options is a dict, not a list)
|
|
283
|
+
for key, option in node.options.items():
|
|
284
|
+
if isinstance(option, TriccNodeSelectOption):
|
|
285
|
+
option_id = self.generate_id(f"option_{node.name}_{option.name}")
|
|
286
|
+
option_name = self.get_export_name(option)
|
|
287
|
+
if isinstance(option_name, str):
|
|
288
|
+
option_name = option_name.replace('\u00a0', ' ').strip()
|
|
289
|
+
elif isinstance(option_name, TriccStatic):
|
|
290
|
+
option_name = str(option_name.value)
|
|
291
|
+
# Create separate option entity
|
|
292
|
+
option_def = {
|
|
293
|
+
"id": option_id,
|
|
294
|
+
"name": option_name,
|
|
295
|
+
"shortName": option.name[:50],
|
|
296
|
+
"code": str(self.get_export_name(option))
|
|
297
|
+
}
|
|
298
|
+
self.options[option_id] = option_def
|
|
299
|
+
|
|
300
|
+
# Add option reference to optionSet (only ID)
|
|
301
|
+
option_set["options"].append({"id": option_id})
|
|
302
|
+
|
|
303
|
+
self.option_sets[option_set_id] = option_set
|
|
304
|
+
|
|
305
|
+
self.data_elements[node.name] = data_element
|
|
306
|
+
return data_element
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
def generate_calculate(self, node, processed_nodes, **kwargs):
|
|
310
|
+
if generate_calculate(node, processed_nodes, **kwargs):
|
|
311
|
+
if issubclass(node.__class__, TriccNodeCalculateBase) and node.expression:
|
|
312
|
+
# Create program rule variable for the calculate
|
|
313
|
+
var_id = self.generate_id(self.get_export_name(node))
|
|
314
|
+
expression_str = self.convert_expression_to_string(node.expression)
|
|
315
|
+
|
|
316
|
+
# Determine data type from operation
|
|
317
|
+
data_type = "TEXT" # default
|
|
318
|
+
if hasattr(node.expression, 'get_datatype'):
|
|
319
|
+
operation_datatype = node.expression.get_datatype()
|
|
320
|
+
if operation_datatype:
|
|
321
|
+
# Create a mock node with the datatype to use the mapping function
|
|
322
|
+
class MockNode:
|
|
323
|
+
def __init__(self, tricc_type):
|
|
324
|
+
self.tricc_type = tricc_type
|
|
325
|
+
mock_node = MockNode(operation_datatype)
|
|
326
|
+
data_type = self.map_tricc_type_to_dhis2_value_type(mock_node)
|
|
327
|
+
|
|
328
|
+
program_rule_variable = {
|
|
329
|
+
"id": var_id,
|
|
330
|
+
"name": self.get_export_name(node.name)[:50],
|
|
331
|
+
"programRuleVariableSourceType": "CALCULATED_VALUE",
|
|
332
|
+
"calculatedValueScript": expression_str,
|
|
333
|
+
"dataType": data_type,
|
|
334
|
+
"useCodeForOptionSet": False,
|
|
335
|
+
"program": {"id": self.program_metadata["id"]}
|
|
336
|
+
}
|
|
337
|
+
self.program_rule_variables.append(program_rule_variable)
|
|
338
|
+
# Add to concept map for potential referencing
|
|
339
|
+
self.concept_map[node.name] = var_id
|
|
340
|
+
return True
|
|
341
|
+
return False
|
|
342
|
+
|
|
343
|
+
def process_export(self, start_pages, **kwargs):
|
|
344
|
+
self.activity_export(start_pages["main"], **kwargs)
|
|
345
|
+
|
|
346
|
+
def activity_export(self, activity, processed_nodes=None, **kwargs):
|
|
347
|
+
if processed_nodes is None:
|
|
348
|
+
processed_nodes = OrderedSet()
|
|
349
|
+
stashed_nodes = OrderedSet()
|
|
350
|
+
groups = {}
|
|
351
|
+
groups[activity.id] = 0
|
|
352
|
+
path_len = 0
|
|
353
|
+
process = ["main"]
|
|
354
|
+
|
|
355
|
+
# Create program stage
|
|
356
|
+
stage_id = self.generate_id(self.get_export_name(activity))
|
|
357
|
+
program_stage = {
|
|
358
|
+
"id": stage_id,
|
|
359
|
+
"name": getattr(activity.root, 'label', 'Main Stage').replace('\u00a0', ' ').strip(),
|
|
360
|
+
"programStageDataElements": [],
|
|
361
|
+
"programStageSections": []
|
|
362
|
+
}
|
|
363
|
+
self.program_metadata["programStages"].append(program_stage)
|
|
364
|
+
|
|
365
|
+
# Start with the main section for this activity
|
|
366
|
+
self.start_section(activity, groups, processed_nodes, process, **kwargs)
|
|
367
|
+
|
|
368
|
+
walktrhough_tricc_node_processed_stached(
|
|
369
|
+
activity.root,
|
|
370
|
+
self.generate_export,
|
|
371
|
+
processed_nodes,
|
|
372
|
+
stashed_nodes,
|
|
373
|
+
path_len,
|
|
374
|
+
cur_group=activity.root.group,
|
|
375
|
+
process=process,
|
|
376
|
+
recursive=False,
|
|
377
|
+
**kwargs
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# End the main section
|
|
381
|
+
self.end_section(activity, groups, **kwargs)
|
|
382
|
+
|
|
383
|
+
# Manage stashed nodes similar to other strategies
|
|
384
|
+
prev_stashed_nodes = stashed_nodes.copy()
|
|
385
|
+
loop_count = 0
|
|
386
|
+
len_prev_processed_nodes = 0
|
|
387
|
+
while len(stashed_nodes) > 0:
|
|
388
|
+
loop_count = check_stashed_loop(
|
|
389
|
+
stashed_nodes,
|
|
390
|
+
prev_stashed_nodes,
|
|
391
|
+
processed_nodes,
|
|
392
|
+
len_prev_processed_nodes,
|
|
393
|
+
loop_count,
|
|
394
|
+
)
|
|
395
|
+
prev_stashed_nodes = stashed_nodes.copy()
|
|
396
|
+
len_prev_processed_nodes = len(processed_nodes)
|
|
397
|
+
if len(stashed_nodes) > 0:
|
|
398
|
+
s_node = stashed_nodes.pop()
|
|
399
|
+
if s_node.group is None:
|
|
400
|
+
logger.critical("ERROR group is none for node {}".format(s_node.get_name()))
|
|
401
|
+
|
|
402
|
+
# Start section for stashed node if it's a different group
|
|
403
|
+
self.start_section(s_node.group, groups, processed_nodes, process, relevance=True, **kwargs)
|
|
404
|
+
|
|
405
|
+
walktrhough_tricc_node_processed_stached(
|
|
406
|
+
s_node,
|
|
407
|
+
self.generate_export,
|
|
408
|
+
processed_nodes,
|
|
409
|
+
stashed_nodes,
|
|
410
|
+
path_len,
|
|
411
|
+
groups=groups,
|
|
412
|
+
cur_group=s_node.group,
|
|
413
|
+
recursive=False,
|
|
414
|
+
process=process,
|
|
415
|
+
**kwargs
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# End section for stashed node
|
|
419
|
+
self.end_section(s_node.group, groups, **kwargs)
|
|
420
|
+
|
|
421
|
+
return processed_nodes
|
|
422
|
+
|
|
423
|
+
def start_section(self, cur_group, groups, processed_nodes, process, relevance=False, **kwargs):
|
|
424
|
+
name = get_export_name(cur_group)
|
|
425
|
+
|
|
426
|
+
if name in groups:
|
|
427
|
+
groups[name] += 1
|
|
428
|
+
name = name + "_" + str(groups[name])
|
|
429
|
+
else:
|
|
430
|
+
groups[name] = 0
|
|
431
|
+
|
|
432
|
+
relevance_expression = (
|
|
433
|
+
cur_group.relevance if (
|
|
434
|
+
relevance and
|
|
435
|
+
cur_group.relevance is not None and
|
|
436
|
+
cur_group.relevance != ""
|
|
437
|
+
) else ""
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
if not relevance:
|
|
441
|
+
relevance_expression = ""
|
|
442
|
+
elif isinstance(relevance_expression, (TriccOperation, TriccStatic)):
|
|
443
|
+
relevance_expression = self.get_tricc_operation_expression(relevance_expression)
|
|
444
|
+
|
|
445
|
+
# Create section
|
|
446
|
+
section_id = self.generate_id(f"section_{name}")
|
|
447
|
+
section_name = name
|
|
448
|
+
if cur_group and hasattr(cur_group, 'label') and cur_group.label:
|
|
449
|
+
section_name = cur_group.label.replace('\u00a0', ' ').strip()
|
|
450
|
+
section = {
|
|
451
|
+
"id": section_id,
|
|
452
|
+
"name": section_name,
|
|
453
|
+
"sortOrder": len(self.sections),
|
|
454
|
+
"programStage": {"id": self.program_metadata["programStages"][-1]["id"]},
|
|
455
|
+
"dataElements": [],
|
|
456
|
+
"activity_ref": cur_group
|
|
457
|
+
}
|
|
458
|
+
# Add section to program stage
|
|
459
|
+
if self.program_metadata["programStages"]:
|
|
460
|
+
self.program_metadata["programStages"][-1]["programStageSections"].append({"id": section_id})
|
|
461
|
+
|
|
462
|
+
self.sections[section_id] = section
|
|
463
|
+
self.current_section = section_id
|
|
464
|
+
|
|
465
|
+
def end_section(self, cur_group, groups, **kwargs):
|
|
466
|
+
# In DHIS2, sections don't have explicit end markers like XLSForm groups
|
|
467
|
+
# The section is already created and added to the program stage
|
|
468
|
+
pass
|
|
469
|
+
|
|
470
|
+
def generate_export(self, node, processed_nodes, **kwargs):
|
|
471
|
+
if not is_ready_to_process(node, processed_nodes, strict=True):
|
|
472
|
+
return False
|
|
473
|
+
|
|
474
|
+
if not process_reference(
|
|
475
|
+
node, processed_nodes, {}, replace_reference=False, codesystems=kwargs.get("codesystems", None)
|
|
476
|
+
):
|
|
477
|
+
return False
|
|
478
|
+
|
|
479
|
+
if node not in processed_nodes:
|
|
480
|
+
# Skip creating data elements for calculate nodes - they should only be program rule variables
|
|
481
|
+
if not issubclass(node.__class__, TriccNodeCalculateBase):
|
|
482
|
+
data_element = self.generate_data_element(node)
|
|
483
|
+
if data_element:
|
|
484
|
+
# Add to program stage
|
|
485
|
+
if self.program_metadata["programStages"]:
|
|
486
|
+
psde_id = self.generate_id(f"psde_{node.name}")
|
|
487
|
+
psde = {
|
|
488
|
+
"id": psde_id,
|
|
489
|
+
"dataElement": {"id": data_element["id"]},
|
|
490
|
+
"compulsory": bool(getattr(node, 'required', False))
|
|
491
|
+
}
|
|
492
|
+
self.program_metadata["programStages"][-1]["programStageDataElements"].append(psde)
|
|
493
|
+
|
|
494
|
+
# Add data element to current section
|
|
495
|
+
if self.current_section and self.current_section in self.sections:
|
|
496
|
+
self.sections[self.current_section]["dataElements"].append({"id": data_element["id"]})
|
|
497
|
+
|
|
498
|
+
return True
|
|
499
|
+
|
|
500
|
+
def clean_section(self, program_stages_payload):
|
|
501
|
+
"""Clean sections by removing empty ones and merging sections with same activity_ref"""
|
|
502
|
+
sections_to_remove = set()
|
|
503
|
+
prev_activity_ref = None
|
|
504
|
+
prev_section_id = None
|
|
505
|
+
|
|
506
|
+
for section in sorted(self.sections.values(), key=lambda x: x["sortOrder"]):
|
|
507
|
+
section_id = section["id"]
|
|
508
|
+
activity_ref = section.get("activity_ref")
|
|
509
|
+
# Remove empty sections
|
|
510
|
+
if not section.get("dataElements"):
|
|
511
|
+
sections_to_remove.add(section_id)
|
|
512
|
+
|
|
513
|
+
# Check for sections with same activity_ref
|
|
514
|
+
elif activity_ref == prev_activity_ref:
|
|
515
|
+
# Merge this section into the existing one
|
|
516
|
+
existing_section = self.sections[prev_section_id]
|
|
517
|
+
|
|
518
|
+
# Move data elements to existing section
|
|
519
|
+
existing_section["dataElements"].extend(section["dataElements"])
|
|
520
|
+
|
|
521
|
+
# Mark this section for removal
|
|
522
|
+
sections_to_remove.add(section_id)
|
|
523
|
+
else:
|
|
524
|
+
prev_activity_ref = activity_ref
|
|
525
|
+
prev_section_id = section_id
|
|
526
|
+
|
|
527
|
+
# Remove sections that should be removed
|
|
528
|
+
for section_id in sections_to_remove:
|
|
529
|
+
if section_id in self.sections:
|
|
530
|
+
del self.sections[section_id]
|
|
531
|
+
|
|
532
|
+
# Update stage sections to remove deleted sections
|
|
533
|
+
for stage in program_stages_payload:
|
|
534
|
+
stage["programStageSections"][:] = [
|
|
535
|
+
s for s in stage["programStageSections"]
|
|
536
|
+
if s["id"] not in sections_to_remove
|
|
537
|
+
]
|
|
538
|
+
|
|
539
|
+
def export(self, start_pages, version):
|
|
540
|
+
form_id = start_pages["main"].root.form_id or "dhis2_program"
|
|
541
|
+
base_path = os.path.join(self.output_path, form_id)
|
|
542
|
+
if not os.path.exists(base_path):
|
|
543
|
+
os.makedirs(base_path)
|
|
544
|
+
|
|
545
|
+
# Prepare collections for all entities
|
|
546
|
+
program_rules_payload = []
|
|
547
|
+
program_rule_actions_payload = []
|
|
548
|
+
program_stages_payload = []
|
|
549
|
+
program_rule_variables_payload = []
|
|
550
|
+
|
|
551
|
+
if self.program_metadata["programStages"]:
|
|
552
|
+
# Extract full stage definitions
|
|
553
|
+
program_stages_payload = [
|
|
554
|
+
{
|
|
555
|
+
**stage,
|
|
556
|
+
"program": {"id": self.program_metadata["id"]}
|
|
557
|
+
}
|
|
558
|
+
for stage in self.program_metadata["programStages"]
|
|
559
|
+
]
|
|
560
|
+
# Clean sections before processing actions to ensure only valid sections are used
|
|
561
|
+
self.clean_section(program_stages_payload)
|
|
562
|
+
# In program, only keep stage ID references
|
|
563
|
+
self.program_metadata["programStages"] = [
|
|
564
|
+
{"id": stage["id"]}
|
|
565
|
+
for stage in program_stages_payload
|
|
566
|
+
]
|
|
567
|
+
else:
|
|
568
|
+
program_stages_payload = []
|
|
569
|
+
|
|
570
|
+
if self.program_rule_actions:
|
|
571
|
+
# Resolve activity references to section IDs for HIDESECTION actions
|
|
572
|
+
program_rule_actions_payload = []
|
|
573
|
+
for action in self.program_rule_actions:
|
|
574
|
+
if action.get("activity_ref"):
|
|
575
|
+
# Find all sections for this activity (after cleaning)
|
|
576
|
+
activity = action["activity_ref"]
|
|
577
|
+
matching_sections = [
|
|
578
|
+
sec_id for sec_id, section in self.sections.items()
|
|
579
|
+
if section.get("activity_ref") == activity
|
|
580
|
+
]
|
|
581
|
+
|
|
582
|
+
# Create one action per matching section
|
|
583
|
+
for i, section_id in enumerate(matching_sections):
|
|
584
|
+
action_copy = dict(action)
|
|
585
|
+
action_copy["programStageSection"] = {"id": section_id}
|
|
586
|
+
del action_copy["activity_ref"]
|
|
587
|
+
|
|
588
|
+
if i > 0:
|
|
589
|
+
# For additional sections, create new IDs for action and corresponding rule
|
|
590
|
+
original_rule_id = action["programRule"]["id"]
|
|
591
|
+
new_rule_id = self.generate_id(f"{original_rule_id}_section_{i}")
|
|
592
|
+
new_action_id = self.generate_id(f"{action['id']}_section_{i}")
|
|
593
|
+
|
|
594
|
+
action_copy["id"] = new_action_id
|
|
595
|
+
action_copy["programRule"] = {"id": new_rule_id}
|
|
596
|
+
|
|
597
|
+
# Create duplicate rule with new ID
|
|
598
|
+
original_rule = next(
|
|
599
|
+
(r for r in self.program_rules if r["id"] == original_rule_id), None
|
|
600
|
+
)
|
|
601
|
+
if original_rule:
|
|
602
|
+
new_rule = dict(original_rule)
|
|
603
|
+
new_rule["id"] = new_rule_id
|
|
604
|
+
new_rule["name"] = f"{original_rule['name']} (Section {i})"
|
|
605
|
+
new_rule["programRuleActions"] = [{"id": new_action_id}]
|
|
606
|
+
self.program_rules.append(new_rule)
|
|
607
|
+
|
|
608
|
+
program_rule_actions_payload.append(action_copy)
|
|
609
|
+
else:
|
|
610
|
+
# Non-activity actions (HIDEFIELD) can be added directly
|
|
611
|
+
program_rule_actions_payload.append(action)
|
|
612
|
+
|
|
613
|
+
if self.program_rules:
|
|
614
|
+
program_rules_payload = [
|
|
615
|
+
{
|
|
616
|
+
**rule,
|
|
617
|
+
"program": {"id": self.program_metadata["id"]}
|
|
618
|
+
}
|
|
619
|
+
for rule in self.program_rules
|
|
620
|
+
]
|
|
621
|
+
|
|
622
|
+
if self.program_rule_variables:
|
|
623
|
+
program_rule_variables_payload = self.program_rule_variables
|
|
624
|
+
|
|
625
|
+
# Build the program with references to other entities
|
|
626
|
+
program_payload = dict(self.program_metadata)
|
|
627
|
+
if program_rule_variables_payload:
|
|
628
|
+
program_payload["programRuleVariables"] = [
|
|
629
|
+
{"id": var["id"]}
|
|
630
|
+
for var in program_rule_variables_payload
|
|
631
|
+
]
|
|
632
|
+
if program_rules_payload:
|
|
633
|
+
program_payload["programRules"] = [
|
|
634
|
+
{"id": rule["id"]}
|
|
635
|
+
for rule in program_rules_payload
|
|
636
|
+
]
|
|
637
|
+
|
|
638
|
+
# Create single comprehensive payload with all entities at root level
|
|
639
|
+
full_payload = {
|
|
640
|
+
"programs": [program_payload]
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if program_stages_payload:
|
|
644
|
+
full_payload["programStages"] = program_stages_payload
|
|
645
|
+
if program_rules_payload:
|
|
646
|
+
full_payload["programRules"] = program_rules_payload
|
|
647
|
+
if program_rule_actions_payload:
|
|
648
|
+
full_payload["programRuleActions"] = program_rule_actions_payload
|
|
649
|
+
if program_rule_variables_payload:
|
|
650
|
+
full_payload["programRuleVariables"] = program_rule_variables_payload
|
|
651
|
+
if self.data_elements:
|
|
652
|
+
full_payload["dataElements"] = list(self.data_elements.values())
|
|
653
|
+
if self.options:
|
|
654
|
+
full_payload["options"] = list(self.options.values())
|
|
655
|
+
if self.option_sets:
|
|
656
|
+
full_payload["optionSets"] = list(self.option_sets.values())
|
|
657
|
+
if self.sections:
|
|
658
|
+
# Remove activity_ref from sections before serialization
|
|
659
|
+
sections_payload = []
|
|
660
|
+
for section in self.sections.values():
|
|
661
|
+
section_copy = dict(section)
|
|
662
|
+
if "activity_ref" in section_copy:
|
|
663
|
+
del section_copy["activity_ref"]
|
|
664
|
+
sections_payload.append(section_copy)
|
|
665
|
+
full_payload["programStageSections"] = sections_payload
|
|
666
|
+
|
|
667
|
+
# Export everything to a single file
|
|
668
|
+
metadata_file = os.path.join(base_path, f"{form_id}_metadata.json")
|
|
669
|
+
with open(metadata_file, 'w') as f:
|
|
670
|
+
json.dump(full_payload, f, indent=2)
|
|
671
|
+
logger.info(f"Exported complete DHIS2 metadata to {metadata_file}")
|
|
672
|
+
|
|
673
|
+
def get_tricc_operation_operand(self, r):
|
|
674
|
+
if isinstance(r, TriccOperation):
|
|
675
|
+
return self.get_tricc_operation_expression(r)
|
|
676
|
+
elif isinstance(r, TriccReference):
|
|
677
|
+
# Use DHIS2 ID from concept_map instead of name
|
|
678
|
+
node_id = self.concept_map.get(r.value.name, self.get_export_name(r.value))
|
|
679
|
+
return f"#{{{node_id}}}"
|
|
680
|
+
elif isinstance(r, TriccStatic):
|
|
681
|
+
if isinstance(r.value, bool):
|
|
682
|
+
return str(r.value).lower()
|
|
683
|
+
if isinstance(r.value, str):
|
|
684
|
+
return f"'{r.value}'"
|
|
685
|
+
else:
|
|
686
|
+
return str(r.value)
|
|
687
|
+
elif isinstance(r, bool):
|
|
688
|
+
return str(r).lower()
|
|
689
|
+
elif isinstance(r, str):
|
|
690
|
+
return f"{r}"
|
|
691
|
+
elif isinstance(r, (int, float)):
|
|
692
|
+
return str(r)
|
|
693
|
+
elif isinstance(r, TriccNodeSelectOption):
|
|
694
|
+
option = self.get_option_value(r.name)
|
|
695
|
+
if r.name in ('true', 'false'):
|
|
696
|
+
return option
|
|
697
|
+
return f"'{option}'"
|
|
698
|
+
elif issubclass(r.__class__, TriccNodeDisplayCalculateBase):
|
|
699
|
+
# Use DHIS2 ID from concept_map instead of name
|
|
700
|
+
node_id = self.concept_map.get(r.name, self.get_export_name(r))
|
|
701
|
+
return f"#{node_id}"
|
|
702
|
+
elif issubclass(r.__class__, TriccNodeInputModel):
|
|
703
|
+
# Use DHIS2 ID from concept_map instead of name
|
|
704
|
+
node_id = self.concept_map.get(r.name, self.get_export_name(r))
|
|
705
|
+
return f"#{{{node_id}}}"
|
|
706
|
+
elif issubclass(r.__class__, TriccNodeBaseModel):
|
|
707
|
+
# Use DHIS2 ID from concept_map instead of name
|
|
708
|
+
node_id = self.concept_map.get(r.name, self.get_export_name(r))
|
|
709
|
+
return f"#{{{node_id}}}"
|
|
710
|
+
else:
|
|
711
|
+
raise NotImplementedError(f"This type of node {r.__class__.__name__} is not supported within an operation")
|
|
712
|
+
|
|
713
|
+
def convert_expression_to_string(self, expression):
|
|
714
|
+
if isinstance(expression, TriccOperation):
|
|
715
|
+
return self.get_tricc_operation_expression(expression)
|
|
716
|
+
else:
|
|
717
|
+
return self.get_tricc_operation_operand(expression)
|
|
718
|
+
|
|
719
|
+
# Operation methods for DHIS2 expressions
|
|
720
|
+
def tricc_operation_equal(self, ref_expressions):
|
|
721
|
+
return f"{ref_expressions[0]} == {ref_expressions[1]}"
|
|
722
|
+
|
|
723
|
+
def tricc_operation_not_equal(self, ref_expressions):
|
|
724
|
+
return f"{ref_expressions[0]} != {ref_expressions[1]}"
|
|
725
|
+
|
|
726
|
+
def tricc_operation_and(self, ref_expressions):
|
|
727
|
+
if len(ref_expressions) == 1:
|
|
728
|
+
return ref_expressions[0]
|
|
729
|
+
if len(ref_expressions) > 1:
|
|
730
|
+
return " && ".join(ref_expressions)
|
|
731
|
+
else:
|
|
732
|
+
return "true"
|
|
733
|
+
|
|
734
|
+
def tricc_operation_or(self, ref_expressions):
|
|
735
|
+
if len(ref_expressions) == 1:
|
|
736
|
+
return ref_expressions[0]
|
|
737
|
+
if len(ref_expressions) > 1:
|
|
738
|
+
return "(" + " || ".join(ref_expressions) + ")"
|
|
739
|
+
else:
|
|
740
|
+
return "true"
|
|
741
|
+
|
|
742
|
+
def tricc_operation_not(self, ref_expressions):
|
|
743
|
+
return f"!({ref_expressions[0]})"
|
|
744
|
+
|
|
745
|
+
def tricc_operation_plus(self, ref_expressions):
|
|
746
|
+
return " + ".join(ref_expressions)
|
|
747
|
+
|
|
748
|
+
def tricc_operation_minus(self, ref_expressions):
|
|
749
|
+
if len(ref_expressions) > 1:
|
|
750
|
+
return " - ".join(map(str, ref_expressions))
|
|
751
|
+
elif len(ref_expressions) == 1:
|
|
752
|
+
return f"-{ref_expressions[0]}"
|
|
753
|
+
|
|
754
|
+
def tricc_operation_more(self, ref_expressions):
|
|
755
|
+
return f"{ref_expressions[0]} > {ref_expressions[1]}"
|
|
756
|
+
|
|
757
|
+
def tricc_operation_less(self, ref_expressions):
|
|
758
|
+
return f"{ref_expressions[0]} < {ref_expressions[1]}"
|
|
759
|
+
|
|
760
|
+
def tricc_operation_more_or_equal(self, ref_expressions):
|
|
761
|
+
return f"{ref_expressions[0]} >= {ref_expressions[1]}"
|
|
762
|
+
|
|
763
|
+
def tricc_operation_less_or_equal(self, ref_expressions):
|
|
764
|
+
return f"{ref_expressions[0]} <= {ref_expressions[1]}"
|
|
765
|
+
|
|
766
|
+
def tricc_operation_selected(self, ref_expressions):
|
|
767
|
+
# For DHIS2, check if value is selected in multi-select
|
|
768
|
+
return f"d2:hasValue({ref_expressions[0]}) && d2:contains({ref_expressions[0]}, {ref_expressions[1]})"
|
|
769
|
+
|
|
770
|
+
def tricc_operation_count(self, ref_expressions):
|
|
771
|
+
return f"d2:count({ref_expressions[0]})"
|
|
772
|
+
|
|
773
|
+
def tricc_operation_multiplied(self, ref_expressions):
|
|
774
|
+
return "*".join(ref_expressions)
|
|
775
|
+
|
|
776
|
+
def tricc_operation_divided(self, ref_expressions):
|
|
777
|
+
return f"{ref_expressions[0]} / {ref_expressions[1]}"
|
|
778
|
+
|
|
779
|
+
def tricc_operation_modulo(self, ref_expressions):
|
|
780
|
+
return f"{ref_expressions[0]} % {ref_expressions[1]}"
|
|
781
|
+
|
|
782
|
+
def tricc_operation_coalesce(self, ref_expressions):
|
|
783
|
+
return f"d2:coalesce({','.join(ref_expressions)})"
|
|
784
|
+
|
|
785
|
+
def tricc_operation_native(self, ref_expressions):
|
|
786
|
+
if len(ref_expressions) > 0:
|
|
787
|
+
return f"{ref_expressions[0]}({','.join(ref_expressions[1:])})"
|
|
788
|
+
|
|
789
|
+
def tricc_operation_istrue(self, ref_expressions):
|
|
790
|
+
return f"{ref_expressions[0]} == true"
|
|
791
|
+
|
|
792
|
+
def tricc_operation_isfalse(self, ref_expressions):
|
|
793
|
+
return f"{ref_expressions[0]} == false"
|
|
794
|
+
|
|
795
|
+
def tricc_operation_parenthesis(self, ref_expressions):
|
|
796
|
+
return f"({ref_expressions[0]})"
|
|
797
|
+
|
|
798
|
+
def tricc_operation_between(self, ref_expressions):
|
|
799
|
+
return f"{ref_expressions[0]} >= {ref_expressions[1]} && {ref_expressions[0]} < {ref_expressions[2]}"
|
|
800
|
+
|
|
801
|
+
def tricc_operation_isnull(self, ref_expressions):
|
|
802
|
+
return f"!d2:hasValue({ref_expressions[0]})"
|
|
803
|
+
|
|
804
|
+
def tricc_operation_isnotnull(self, ref_expressions):
|
|
805
|
+
return f"d2:hasValue({ref_expressions[0]})"
|
|
806
|
+
|
|
807
|
+
def tricc_operation_isnottrue(self, ref_expressions):
|
|
808
|
+
return f"{ref_expressions[0]} != true"
|
|
809
|
+
|
|
810
|
+
def tricc_operation_isnotfalse(self, ref_expressions):
|
|
811
|
+
return f"{ref_expressions[0]} != false"
|
|
812
|
+
|
|
813
|
+
def tricc_operation_notexist(self, ref_expressions):
|
|
814
|
+
return f"!d2:hasValue({ref_expressions[0]})"
|
|
815
|
+
|
|
816
|
+
def tricc_operation_case(self, ref_expressions):
|
|
817
|
+
# Simplified case handling
|
|
818
|
+
parts = []
|
|
819
|
+
for i in range(0, len(ref_expressions), 2):
|
|
820
|
+
if i + 1 < len(ref_expressions):
|
|
821
|
+
parts.append(f"if({ref_expressions[i]}, {ref_expressions[i+1]})")
|
|
822
|
+
return " || ".join(parts)
|
|
823
|
+
|
|
824
|
+
def tricc_operation_ifs(self, ref_expressions):
|
|
825
|
+
return self.tricc_operation_case(ref_expressions[1:])
|
|
826
|
+
|
|
827
|
+
def tricc_operation_if(self, ref_expressions):
|
|
828
|
+
return f"if({ref_expressions[0]}, {ref_expressions[1]}, {ref_expressions[2]})"
|
|
829
|
+
|
|
830
|
+
def tricc_operation_contains(self, ref_expressions):
|
|
831
|
+
return f"d2:contains({ref_expressions[0]}, {ref_expressions[1]})"
|
|
832
|
+
|
|
833
|
+
def tricc_operation_exists(self, ref_expressions):
|
|
834
|
+
parts = []
|
|
835
|
+
for ref in ref_expressions:
|
|
836
|
+
parts.append(f"d2:hasValue({ref})")
|
|
837
|
+
return " && ".join(parts)
|
|
838
|
+
|
|
839
|
+
def tricc_operation_cast_number(self, ref_expressions):
|
|
840
|
+
return f"d2:toNumber({ref_expressions[0]})"
|
|
841
|
+
|
|
842
|
+
def tricc_operation_cast_integer(self, ref_expressions):
|
|
843
|
+
return f"d2:toNumber({ref_expressions[0]})"
|
|
844
|
+
|
|
845
|
+
def tricc_operation_zscore(self, ref_expressions):
|
|
846
|
+
# Placeholder - would need specific implementation
|
|
847
|
+
return f"zscore({','.join(ref_expressions)})"
|
|
848
|
+
|
|
849
|
+
def tricc_operation_datetime_to_decimal(self, ref_expressions):
|
|
850
|
+
return f"d2:daysBetween({ref_expressions[0]}, '1970-01-01')"
|
|
851
|
+
|
|
852
|
+
def tricc_operation_round(self, ref_expressions):
|
|
853
|
+
return f"d2:round({ref_expressions[0]})"
|
|
854
|
+
|
|
855
|
+
def tricc_operation_izscore(self, ref_expressions):
|
|
856
|
+
return f"izscore({','.join(ref_expressions)})"
|
|
857
|
+
|
|
858
|
+
def tricc_operation_concatenate(self, ref_expressions):
|
|
859
|
+
return f"d2:concatenate({','.join(ref_expressions)})"
|
|
@@ -21,6 +21,7 @@ from tricc_oo.models.tricc import (
|
|
|
21
21
|
TriccNodeSelectOption,
|
|
22
22
|
TriccNodeInputModel,
|
|
23
23
|
TriccNodeBaseModel,
|
|
24
|
+
TriccNodeSelect,
|
|
24
25
|
TriccNodeDisplayModel,
|
|
25
26
|
)
|
|
26
27
|
|
|
@@ -69,6 +70,9 @@ class OpenMRSStrategy(BaseOutPutStrategy):
|
|
|
69
70
|
self.current_segment = None
|
|
70
71
|
self.current_activity = None
|
|
71
72
|
self.concept_map = {}
|
|
73
|
+
self.calculated_fields = [] # Store calculated fields to add to first section of each page
|
|
74
|
+
self.calculated_fields_added = set() # Track which pages have had calculated fields added
|
|
75
|
+
self.inject_version()
|
|
72
76
|
|
|
73
77
|
def get_export_name(self, r):
|
|
74
78
|
if isinstance(r, TriccNodeSelectOption):
|
|
@@ -146,6 +150,9 @@ class OpenMRSStrategy(BaseOutPutStrategy):
|
|
|
146
150
|
logger.info("generate the export format")
|
|
147
151
|
self.process_export(self.project.start_pages, pages=self.project.pages)
|
|
148
152
|
|
|
153
|
+
logger.info("create calculation page")
|
|
154
|
+
self.create_calculation_page()
|
|
155
|
+
|
|
149
156
|
logger.info("print the export")
|
|
150
157
|
self.export(self.project.start_pages, version=version)
|
|
151
158
|
|
|
@@ -160,7 +167,7 @@ class OpenMRSStrategy(BaseOutPutStrategy):
|
|
|
160
167
|
'select_multiple': 'multiCheckbox',
|
|
161
168
|
'select_yesno': 'select',
|
|
162
169
|
'not_available': 'checkbox',
|
|
163
|
-
'note': '
|
|
170
|
+
'note': 'markdown'
|
|
164
171
|
}
|
|
165
172
|
|
|
166
173
|
# if issubclass(node.__class__, TriccNodeSelectYesNo):
|
|
@@ -250,7 +257,9 @@ class OpenMRSStrategy(BaseOutPutStrategy):
|
|
|
250
257
|
}
|
|
251
258
|
}
|
|
252
259
|
}
|
|
253
|
-
|
|
260
|
+
# Collect calculated fields to add to first section of each page
|
|
261
|
+
self.calculated_fields.append(question)
|
|
262
|
+
return None # Don't return the question, it will be added to first section
|
|
254
263
|
return None
|
|
255
264
|
|
|
256
265
|
def generate_calculate(self, node, processed_nodes, **kwargs):
|
|
@@ -400,6 +409,8 @@ class OpenMRSStrategy(BaseOutPutStrategy):
|
|
|
400
409
|
return f"'{option}'"
|
|
401
410
|
elif issubclass(r.__class__, TriccNodeInputModel):
|
|
402
411
|
return self.get_export_name(r)
|
|
412
|
+
elif issubclass(r.__class__, TriccNodeSelect):
|
|
413
|
+
return "(" + self.get_export_name(r) + " ?? [])"
|
|
403
414
|
elif issubclass(r.__class__, TriccNodeBaseModel):
|
|
404
415
|
return self.get_export_name(r)
|
|
405
416
|
else:
|
|
@@ -447,7 +458,7 @@ class OpenMRSStrategy(BaseOutPutStrategy):
|
|
|
447
458
|
return f"!({ref_expressions[0]})"
|
|
448
459
|
|
|
449
460
|
def tricc_operation_plus(self, ref_expressions):
|
|
450
|
-
return " + ".join(ref_expressions)
|
|
461
|
+
return "(" + " + ".join(ref_expressions) +")"
|
|
451
462
|
|
|
452
463
|
def tricc_operation_minus(self, ref_expressions):
|
|
453
464
|
if len(ref_expressions) > 1:
|
|
@@ -472,7 +483,7 @@ class OpenMRSStrategy(BaseOutPutStrategy):
|
|
|
472
483
|
return f"({ref_expressions[0]}.includes({ref_expressions[1]}))"
|
|
473
484
|
|
|
474
485
|
def tricc_operation_count(self, ref_expressions):
|
|
475
|
-
return f"
|
|
486
|
+
return f"{ref_expressions[0]}.length"
|
|
476
487
|
|
|
477
488
|
def tricc_operation_multiplied(self, ref_expressions):
|
|
478
489
|
return "*".join(ref_expressions)
|
|
@@ -638,6 +649,7 @@ class OpenMRSStrategy(BaseOutPutStrategy):
|
|
|
638
649
|
"label": section_label,
|
|
639
650
|
"questions": []
|
|
640
651
|
})
|
|
652
|
+
|
|
641
653
|
if group_node.relevance:
|
|
642
654
|
relevance_str = self.convert_expression_to_string(not_clean(group_node.relevance))
|
|
643
655
|
if relevance_str and relevance_str != 'false':
|
|
@@ -645,3 +657,38 @@ class OpenMRSStrategy(BaseOutPutStrategy):
|
|
|
645
657
|
"hideWhenExpression": f"{relevance_str}"
|
|
646
658
|
}
|
|
647
659
|
logger.debug(f"Started section: {section_label}")
|
|
660
|
+
|
|
661
|
+
def create_calculation_page(self):
|
|
662
|
+
"""Create a dedicated page for all calculated fields"""
|
|
663
|
+
if self.calculated_fields:
|
|
664
|
+
self.clean_sections()
|
|
665
|
+
self.clean_pages()
|
|
666
|
+
page = {
|
|
667
|
+
"label": "Calculations",
|
|
668
|
+
"sections": [
|
|
669
|
+
{
|
|
670
|
+
"label": "Calculations",
|
|
671
|
+
"questions": self.calculated_fields
|
|
672
|
+
}
|
|
673
|
+
]
|
|
674
|
+
}
|
|
675
|
+
self.form_data["pages"].append(page)
|
|
676
|
+
logger.debug("Created calculation page")
|
|
677
|
+
|
|
678
|
+
def inject_version(self):
|
|
679
|
+
# Add hidden version field using version() function
|
|
680
|
+
question = {
|
|
681
|
+
"id": "version",
|
|
682
|
+
"type": "control",
|
|
683
|
+
"label": "",
|
|
684
|
+
"hide": {
|
|
685
|
+
"hideWhenExpression": "true"
|
|
686
|
+
},
|
|
687
|
+
"questionOptions": {
|
|
688
|
+
"calculate": {
|
|
689
|
+
"calculateExpression": "version()"
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
# Collect calculated fields to add to first section of each page
|
|
694
|
+
self.calculated_fields.append(question)
|
tricc_oo/visitors/tricc.py
CHANGED
|
@@ -28,6 +28,7 @@ from tricc_oo.models.calculate import (
|
|
|
28
28
|
TriccNodeActivityEnd,
|
|
29
29
|
TriccNodeActivityStart,
|
|
30
30
|
TriccNodeEnd,
|
|
31
|
+
TriccNodeDiagnosis,
|
|
31
32
|
get_node_from_id,
|
|
32
33
|
|
|
33
34
|
)
|
|
@@ -120,27 +121,13 @@ def get_last_version(name, processed_nodes, _list=None):
|
|
|
120
121
|
# node is the node to calculate
|
|
121
122
|
# processed_nodes are the list of processed nodes
|
|
122
123
|
def get_node_expressions(node, processed_nodes, process=None):
|
|
123
|
-
get_overall_exp = issubclass(node.__class__,
|
|
124
|
-
node.__class__, (TriccNodeDisplayBridge, TriccNodeBridge)
|
|
125
|
-
)
|
|
124
|
+
get_overall_exp = issubclass(node.__class__, (TriccNodeDisplayCalculateBase, TriccNodeProposedDiagnosis, TriccNodeDiagnosis)) and not isinstance(node, (TriccNodeDisplayBridge))
|
|
126
125
|
expression = None
|
|
127
126
|
# in case of recursive call processed_nodes will be None
|
|
128
127
|
if processed_nodes is None or is_ready_to_process(node, processed_nodes=processed_nodes):
|
|
129
128
|
expression = get_node_expression(
|
|
130
129
|
node, processed_nodes=processed_nodes, get_overall_exp=get_overall_exp, process=process
|
|
131
130
|
)
|
|
132
|
-
|
|
133
|
-
# if get_overall_exp:
|
|
134
|
-
# if expression and (not isinstance(expression, str) or expression != '')
|
|
135
|
-
# and expression is not TriccStatic(True) :
|
|
136
|
-
# num_expression = TriccOperation(
|
|
137
|
-
# TriccOperator.CAST_NUMBER,
|
|
138
|
-
# [expression]
|
|
139
|
-
# )
|
|
140
|
-
# elif expression is TriccStatic(True) or (not expression and get_overall_exp):
|
|
141
|
-
# expression = TriccStatic(True)
|
|
142
|
-
# else:
|
|
143
|
-
# expression = None
|
|
144
131
|
if (
|
|
145
132
|
issubclass(node.__class__, TriccNodeCalculateBase)
|
|
146
133
|
and not isinstance(expression, (TriccStatic, TriccReference, TriccOperation))
|
|
@@ -1564,7 +1551,12 @@ def set_prev_next_node(source_node, target_node, replaced_node=None, edge_only=F
|
|
|
1564
1551
|
set_next_node(source_node, target_node, replaced_node, edge_only)
|
|
1565
1552
|
|
|
1566
1553
|
if activity and not any([(e.source == source_id) and (e.target == target_id) for e in activity.edges]):
|
|
1567
|
-
|
|
1554
|
+
if issubclass(source_node.__class__, TriccNodeSelect):
|
|
1555
|
+
label = "continue"
|
|
1556
|
+
elif isinstance(source_node, TriccNodeRhombus):
|
|
1557
|
+
label = "yes"
|
|
1558
|
+
else:
|
|
1559
|
+
label = None
|
|
1568
1560
|
activity.edges.append(TriccEdge(id=generate_id(), source=source_id, target=target_id, value=label))
|
|
1569
1561
|
|
|
1570
1562
|
|
|
@@ -1848,6 +1840,16 @@ def get_node_expression(in_node, processed_nodes, get_overall_exp=False, is_prev
|
|
|
1848
1840
|
logger.critical(f"Rhombus without expression {node.get_name()}")
|
|
1849
1841
|
elif is_prev and issubclass(node.__class__, TriccNodeDisplayCalculateBase):
|
|
1850
1842
|
expression = TriccOperation(TriccOperator.ISTRUE, [node])
|
|
1843
|
+
prev_exp_overall = get_node_expression(
|
|
1844
|
+
node,
|
|
1845
|
+
processed_nodes=processed_nodes,
|
|
1846
|
+
get_overall_exp=False,
|
|
1847
|
+
is_prev=False,
|
|
1848
|
+
process=process,
|
|
1849
|
+
negate=negate
|
|
1850
|
+
)
|
|
1851
|
+
if prev_exp_overall in [TriccStatic(True), TriccStatic(False)]:
|
|
1852
|
+
expression = prev_exp_overall
|
|
1851
1853
|
elif hasattr(node, "expression_reference") and isinstance(node.expression_reference, TriccOperation):
|
|
1852
1854
|
# if issubclass(node.__class__, TriccNodeDisplayCalculateBase):
|
|
1853
1855
|
# expression = TriccOperation(
|
|
@@ -2141,7 +2143,7 @@ def get_prev_node_expression(node, processed_nodes, get_overall_exp=False, exclu
|
|
|
2141
2143
|
processed_nodes=processed_nodes,
|
|
2142
2144
|
get_overall_exp=get_overall_exp,
|
|
2143
2145
|
is_prev=True,
|
|
2144
|
-
process=
|
|
2146
|
+
process=get_overall_exp,
|
|
2145
2147
|
)
|
|
2146
2148
|
if isinstance(node, TriccNodeActivity) or get_overall_exp:
|
|
2147
2149
|
add_sub_expression(act_expression_inputs, sub)
|
|
@@ -2150,11 +2152,12 @@ def get_prev_node_expression(node, processed_nodes, get_overall_exp=False, exclu
|
|
|
2150
2152
|
|
|
2151
2153
|
if act_expression_inputs:
|
|
2152
2154
|
act_sub = or_join(act_expression_inputs)
|
|
2155
|
+
# if there is condition fallback on the calling activity condition
|
|
2153
2156
|
if act_sub == TriccStatic(True):
|
|
2154
2157
|
act_sub = get_node_expression(
|
|
2155
2158
|
prev_node.activity,
|
|
2156
2159
|
processed_nodes=processed_nodes,
|
|
2157
|
-
get_overall_exp=
|
|
2160
|
+
get_overall_exp=get_overall_exp,
|
|
2158
2161
|
is_prev=True,
|
|
2159
2162
|
negate=False,
|
|
2160
2163
|
process=process,
|
|
@@ -2241,7 +2244,7 @@ def get_count_terms_details(prev_node, processed_nodes, get_overall_exp, negate=
|
|
|
2241
2244
|
get_node_expression(
|
|
2242
2245
|
prev_node,
|
|
2243
2246
|
processed_nodes=processed_nodes,
|
|
2244
|
-
get_overall_exp=
|
|
2247
|
+
get_overall_exp=get_overall_exp,
|
|
2245
2248
|
is_prev=True,
|
|
2246
2249
|
process=process,
|
|
2247
2250
|
)
|
|
@@ -2258,7 +2261,7 @@ def get_count_terms_details(prev_node, processed_nodes, get_overall_exp, negate=
|
|
|
2258
2261
|
TriccOperator.CAST_NUMBER,
|
|
2259
2262
|
[
|
|
2260
2263
|
get_node_expression(
|
|
2261
|
-
prev_node, processed_nodes=processed_nodes, get_overall_exp=
|
|
2264
|
+
prev_node, processed_nodes=processed_nodes, get_overall_exp=get_overall_exp, is_prev=True, process=process
|
|
2262
2265
|
)
|
|
2263
2266
|
],
|
|
2264
2267
|
)
|
|
@@ -2279,7 +2282,7 @@ def get_add_terms(node, processed_nodes, get_overall_exp=False, negate=False, pr
|
|
|
2279
2282
|
get_node_expression(
|
|
2280
2283
|
prev_node,
|
|
2281
2284
|
processed_nodes=processed_nodes,
|
|
2282
|
-
get_overall_exp=
|
|
2285
|
+
get_overall_exp=get_overall_exp,
|
|
2283
2286
|
is_prev=True,
|
|
2284
2287
|
process=process,
|
|
2285
2288
|
)
|
|
@@ -2352,7 +2355,11 @@ def get_rhombus_terms(node, processed_nodes, get_overall_exp=False, negate=False
|
|
|
2352
2355
|
TriccOperator.CAST_NUMBER,
|
|
2353
2356
|
[
|
|
2354
2357
|
get_node_expression(
|
|
2355
|
-
expression,
|
|
2358
|
+
expression,
|
|
2359
|
+
processed_nodes=processed_nodes,
|
|
2360
|
+
get_overall_exp=get_overall_exp,
|
|
2361
|
+
is_prev=True,
|
|
2362
|
+
process=process
|
|
2356
2363
|
)
|
|
2357
2364
|
],
|
|
2358
2365
|
)
|
|
@@ -2390,7 +2397,7 @@ def get_calculation_terms(node, processed_nodes, get_overall_exp=False, negate=F
|
|
|
2390
2397
|
return get_count_terms(node, False, negate, process=process)
|
|
2391
2398
|
elif isinstance(node, TriccNodeRhombus):
|
|
2392
2399
|
return get_rhombus_terms(
|
|
2393
|
-
node, processed_nodes=processed_nodes, get_overall_exp=
|
|
2400
|
+
node, processed_nodes=processed_nodes, get_overall_exp=get_overall_exp, negate=negate, process=process
|
|
2394
2401
|
)
|
|
2395
2402
|
elif isinstance(node, (TriccNodeWait)):
|
|
2396
2403
|
# just use to force order of question
|
|
@@ -2407,7 +2414,6 @@ def get_calculation_terms(node, processed_nodes, get_overall_exp=False, negate=F
|
|
|
2407
2414
|
)
|
|
2408
2415
|
elif isinstance(node, (TriccNodeActivityStart, TriccNodeActivityEnd)):
|
|
2409
2416
|
# the group have the relevance for the activity, not needed to replicate it
|
|
2410
|
-
# return get_prev_node_expression(node.activity, processed_nodes, get_overall_exp=False, excluded_name=None)
|
|
2411
2417
|
expression = None
|
|
2412
2418
|
elif isinstance(node, TriccNodeExclusive):
|
|
2413
2419
|
if len(node.prev_nodes) == 1:
|
|
@@ -2420,7 +2426,7 @@ def get_calculation_terms(node, processed_nodes, get_overall_exp=False, negate=F
|
|
|
2420
2426
|
return get_node_expression(
|
|
2421
2427
|
node_to_negate,
|
|
2422
2428
|
processed_nodes=processed_nodes,
|
|
2423
|
-
get_overall_exp=
|
|
2429
|
+
get_overall_exp=get_overall_exp,
|
|
2424
2430
|
is_prev=True,
|
|
2425
2431
|
negate=True,
|
|
2426
2432
|
process=process,
|
|
@@ -2429,7 +2435,7 @@ def get_calculation_terms(node, processed_nodes, get_overall_exp=False, negate=F
|
|
|
2429
2435
|
return get_node_expression(
|
|
2430
2436
|
node_to_negate,
|
|
2431
2437
|
processed_nodes=processed_nodes,
|
|
2432
|
-
get_overall_exp=
|
|
2438
|
+
get_overall_exp=get_overall_exp,
|
|
2433
2439
|
is_prev=True,
|
|
2434
2440
|
negate=True,
|
|
2435
2441
|
process=process,
|
|
@@ -2616,13 +2622,17 @@ def generate_base(node, processed_nodes, **kwargs):
|
|
|
2616
2622
|
node.min = float(node.min)
|
|
2617
2623
|
if int(node.min) == node.min:
|
|
2618
2624
|
node.min = int(node.min)
|
|
2619
|
-
constraints.append(
|
|
2625
|
+
constraints.append(
|
|
2626
|
+
TriccOperation(TriccOperator.MORE_OR_EQUAL, ["$this", TriccStatic(node.min)])
|
|
2627
|
+
)
|
|
2620
2628
|
constraints_min = "The minimun value is {0}.".format(node.min)
|
|
2621
2629
|
if node.max is not None and node.max != "":
|
|
2622
2630
|
node.max = float(node.max)
|
|
2623
2631
|
if int(node.max) == node.max:
|
|
2624
2632
|
node.max = int(node.max)
|
|
2625
|
-
constraints.append(
|
|
2633
|
+
constraints.append(
|
|
2634
|
+
TriccOperation(TriccOperator.LESS_OR_EQUAL, ["$this", TriccStatic(node.max)])
|
|
2635
|
+
)
|
|
2626
2636
|
constraints_max = "The maximum value is {0}.".format(node.max)
|
|
2627
2637
|
if len(constraints) > 1:
|
|
2628
2638
|
node.constraint = TriccOperation(TriccOperator.AND, constraints)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
tests/build.py,sha256=
|
|
1
|
+
tests/build.py,sha256=Qbxvjkj_Wk2nQ-WjaMGiE1FIe3SRmJMRIgeoMoxqlfQ,6748
|
|
2
2
|
tests/test_cql.py,sha256=dAsLMqVaS6qxnq62fg5KqTFu6UG6pHO6Ab3NZ1c9T3Y,5248
|
|
3
3
|
tests/to_ocl.py,sha256=4e-i65K3UM6wHgdVcrZcM9AyL1bahIsXJiZTXhhHgQk,2048
|
|
4
4
|
tricc_oo/__init__.py,sha256=oWCE1ubmC_6iqaWOMgTei4eXVQgV202Ia-tXS1NnW_4,139
|
|
@@ -7,15 +7,15 @@ tricc_oo/converters/codesystem_to_ocl.py,sha256=Fh7Vk73OsxljZKu1k6H9uzYwz334tpQT
|
|
|
7
7
|
tricc_oo/converters/cql_to_operation.py,sha256=PUyV_YpUY98Ox0H_F_CN3UUf_I-BhFZVOcWWKTtwecM,14492
|
|
8
8
|
tricc_oo/converters/datadictionnary.py,sha256=T2HLCBo4Am1p0kFqSH1r0PqbD8AC2IGuWkbvMvSCru0,3658
|
|
9
9
|
tricc_oo/converters/drawio_type_map.py,sha256=UCPiGs7Lw0bigKScmZUnmOhACBz-FiDq92jHkI7RTSQ,9113
|
|
10
|
-
tricc_oo/converters/tricc_to_xls_form.py,sha256=
|
|
10
|
+
tricc_oo/converters/tricc_to_xls_form.py,sha256=HZh0tQoKfRMPshJvEBwCOGqYGA1ZJLJ67bwV79qtlwk,3486
|
|
11
11
|
tricc_oo/converters/utils.py,sha256=JZrtrvvOfXwdkw49pKauzinOcauWwsy-CVcw36TjyLo,1684
|
|
12
|
-
tricc_oo/converters/xml_to_tricc.py,sha256=
|
|
12
|
+
tricc_oo/converters/xml_to_tricc.py,sha256=YltAT2wo6bPDMLRuKitqStjWW_OmSJhMpAwv2IAscfs,39544
|
|
13
13
|
tricc_oo/converters/cql/cqlLexer.py,sha256=8HArbRphcrpnAG4uogJ2rHv4tc1WLzjN0B1uFeYILAc,49141
|
|
14
14
|
tricc_oo/converters/cql/cqlListener.py,sha256=fA7-8DcS2Q69ckwjdg57-OfFHBxjTZFdoSKrtw7Hffc,57538
|
|
15
15
|
tricc_oo/converters/cql/cqlParser.py,sha256=x3KdrwX9nwENSEJ5Ex7_l5NMnu3kWBO0uLdYu4moTq0,414745
|
|
16
16
|
tricc_oo/converters/cql/cqlVisitor.py,sha256=iHuup2S7OGSVWLEcI4H3oecRqgXztC1sKnew_1P2iGY,33880
|
|
17
17
|
tricc_oo/models/__init__.py,sha256=CgS52LLqdDIaXHvZy08hhu_VaYw80OEdfL_llM9ICBA,108
|
|
18
|
-
tricc_oo/models/base.py,sha256=
|
|
18
|
+
tricc_oo/models/base.py,sha256=AaeB69vWg3ulBttoezEniUiU8HWh_pvrw7tCHpL27g4,25926
|
|
19
19
|
tricc_oo/models/calculate.py,sha256=uNP0IDUqPQcJq9Co05H8eX5wbR_DikSxuOHxfVE5Dxg,8018
|
|
20
20
|
tricc_oo/models/lang.py,sha256=ZMRwdoPWe01wEDhOM0uRk-6rt3BkoAAZM8mZ61--s3A,2265
|
|
21
21
|
tricc_oo/models/ocl.py,sha256=MybSeB6fgCOUVJ4aektff0vrrTZsyfwZ2Gt_pPBu_FY,8728
|
|
@@ -31,20 +31,21 @@ tricc_oo/strategies/input/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
|
31
31
|
tricc_oo/strategies/input/base_input_strategy.py,sha256=BEODXS74na1QRRcJVQ4cxiD8F7uRqaLyhE3QzKpGVvk,3891
|
|
32
32
|
tricc_oo/strategies/input/drawio.py,sha256=uXAUPhXOeg0Uk_BNqlCqFBW4cWNox4VfH559bj1fhC0,12767
|
|
33
33
|
tricc_oo/strategies/output/base_output_strategy.py,sha256=M9UFR67-_CFoW681bPAeBS1OUGuFtmUbM_rltACI0hk,8798
|
|
34
|
+
tricc_oo/strategies/output/dhis2_form.py,sha256=m23EeZB7uXkNCFJr9hNGevgsA1dQqBrtX5uoy_22QRI,36500
|
|
34
35
|
tricc_oo/strategies/output/fhir_form.py,sha256=hbL921pe1Doun4IQrJuZ_Sq2fCh98G3grYie5olC4uc,15740
|
|
35
36
|
tricc_oo/strategies/output/html_form.py,sha256=qSleEZOMV_-Z04y-i-ucyd5rgAYWAyjPwMrw0IHtCRM,8604
|
|
36
|
-
tricc_oo/strategies/output/openmrs_form.py,sha256=
|
|
37
|
+
tricc_oo/strategies/output/openmrs_form.py,sha256=zAmDGMmZdIGNpil5MD-huiUvt_Dbhc2vt5qsGaCS2_k,29003
|
|
37
38
|
tricc_oo/strategies/output/spice.py,sha256=QMeoismVC3PdbvwTK0PtUjWX9jl9780fbQIXn76fMXw,10761
|
|
38
39
|
tricc_oo/strategies/output/xls_form.py,sha256=26pEea0I_owpsz9S8hoHJNzChA5b2Th8KPRIeTEMfqo,29323
|
|
39
40
|
tricc_oo/strategies/output/xlsform_cdss.py,sha256=X00Lt5MzV8TX14dR4dFI1MqllI5S1e13bKbeysWM9uA,17435
|
|
40
41
|
tricc_oo/strategies/output/xlsform_cht.py,sha256=RY_mre9j6w2vVnRFSGn5R3CuTWFjIbQyl1uWwz9Ay5E,22965
|
|
41
42
|
tricc_oo/strategies/output/xlsform_cht_hf.py,sha256=xm6SKirV3nMZvM2w54_zJcXAeAgAkq-EEqGEjnOWv6c,988
|
|
42
43
|
tricc_oo/visitors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
|
-
tricc_oo/visitors/tricc.py,sha256=
|
|
44
|
+
tricc_oo/visitors/tricc.py,sha256=cgmiT26pgkmmocqi7vVMtyo9WyvYyR1nMg-cPjmUawQ,107724
|
|
44
45
|
tricc_oo/visitors/utils.py,sha256=j83aAq5s5atXi3OC0jc_uJd54a8XrHHmizeeEbWZQJg,421
|
|
45
46
|
tricc_oo/visitors/xform_pd.py,sha256=ryAnI3V9x3eTmJ2LNsUZfvl0_yfCqo6oBgeSu-WPqaE,9613
|
|
46
|
-
tricc_oo-1.
|
|
47
|
-
tricc_oo-1.
|
|
48
|
-
tricc_oo-1.
|
|
49
|
-
tricc_oo-1.
|
|
50
|
-
tricc_oo-1.
|
|
47
|
+
tricc_oo-1.6.0.dist-info/licenses/LICENSE,sha256=Pz2eACSxkhsGfW9_iN60pgy-enjnbGTj8df8O3ebnQQ,16726
|
|
48
|
+
tricc_oo-1.6.0.dist-info/METADATA,sha256=xhuGPgAE0OqK6f_UXRhxWsV2E5PZupPkLQ7QJzGD18k,8576
|
|
49
|
+
tricc_oo-1.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
50
|
+
tricc_oo-1.6.0.dist-info/top_level.txt,sha256=NvbfMNAiy9m4b1unBsqpeOQWh4IgA1Xa33BtKA4abxk,15
|
|
51
|
+
tricc_oo-1.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|