tricc-oo 1.5.23__py3-none-any.whl → 1.5.25__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 +12 -11
- tests/test_cql.py +4 -4
- tricc_oo/converters/datadictionnary.py +6 -1
- tricc_oo/converters/tricc_to_xls_form.py +39 -8
- tricc_oo/converters/utils.py +1 -1
- tricc_oo/models/tricc.py +3 -1
- tricc_oo/serializers/xls_form.py +1 -9
- tricc_oo/strategies/input/drawio.py +2 -2
- tricc_oo/strategies/output/base_output_strategy.py +34 -0
- tricc_oo/strategies/output/fhir_form.py +377 -0
- tricc_oo/strategies/output/html_form.py +224 -0
- tricc_oo/strategies/output/openmrs_form.py +647 -0
- tricc_oo/strategies/output/xls_form.py +47 -84
- tricc_oo/strategies/output/xlsform_cht.py +2 -2
- tricc_oo/visitors/tricc.py +118 -8
- {tricc_oo-1.5.23.dist-info → tricc_oo-1.5.25.dist-info}/METADATA +125 -84
- {tricc_oo-1.5.23.dist-info → tricc_oo-1.5.25.dist-info}/RECORD +20 -17
- {tricc_oo-1.5.23.dist-info → tricc_oo-1.5.25.dist-info}/WHEEL +0 -0
- {tricc_oo-1.5.23.dist-info → tricc_oo-1.5.25.dist-info}/licenses/LICENSE +0 -0
- {tricc_oo-1.5.23.dist-info → tricc_oo-1.5.25.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import uuid
|
|
5
|
+
from tricc_oo.visitors.tricc import (
|
|
6
|
+
is_ready_to_process,
|
|
7
|
+
process_reference,
|
|
8
|
+
generate_base,
|
|
9
|
+
generate_calculate,
|
|
10
|
+
walktrhough_tricc_node_processed_stached,
|
|
11
|
+
check_stashed_loop,
|
|
12
|
+
)
|
|
13
|
+
from tricc_oo.converters.tricc_to_xls_form import get_export_name
|
|
14
|
+
import datetime
|
|
15
|
+
from tricc_oo.strategies.output.base_output_strategy import BaseOutPutStrategy
|
|
16
|
+
from tricc_oo.models.base import (
|
|
17
|
+
not_clean, TriccOperation,
|
|
18
|
+
TriccStatic, TriccReference
|
|
19
|
+
)
|
|
20
|
+
from tricc_oo.models.tricc import (
|
|
21
|
+
TriccNodeSelectOption,
|
|
22
|
+
TriccNodeInputModel,
|
|
23
|
+
TriccNodeBaseModel,
|
|
24
|
+
TriccNodeDisplayModel,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from tricc_oo.models.calculate import TriccNodeDisplayCalculateBase, TriccNodeDiagnosis
|
|
28
|
+
from tricc_oo.models.ordered_set import OrderedSet
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger("default")
|
|
31
|
+
|
|
32
|
+
# Namespace for deterministic UUIDs
|
|
33
|
+
UUID_NAMESPACE = uuid.UUID('12345678-1234-5678-9abc-def012345678')
|
|
34
|
+
|
|
35
|
+
CIEL_YES = "1065AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
|
36
|
+
CIEL_NO = "1066AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class OpenMRSStrategy(BaseOutPutStrategy):
|
|
40
|
+
processes = ["main"]
|
|
41
|
+
project = None
|
|
42
|
+
output_path = None
|
|
43
|
+
|
|
44
|
+
def __init__(self, project, output_path):
|
|
45
|
+
super().__init__(project, output_path)
|
|
46
|
+
form_id = getattr(self.project.start_pages["main"], 'form_id', 'openmrs_form')
|
|
47
|
+
self.form_data = {
|
|
48
|
+
"$schema": "http://json.openmrs.org/form.schema.json",
|
|
49
|
+
"name": form_id,
|
|
50
|
+
"uuid": str(uuid.uuid5(UUID_NAMESPACE, form_id)),
|
|
51
|
+
"encounterType": str(uuid.uuid5(UUID_NAMESPACE, f"{form_id}_encounter_type")),
|
|
52
|
+
"processor": "EncounterFormProcessor",
|
|
53
|
+
"published": False,
|
|
54
|
+
"retired": False,
|
|
55
|
+
"version": "1.0",
|
|
56
|
+
"availableIntents": [
|
|
57
|
+
{
|
|
58
|
+
"intent": "*",
|
|
59
|
+
"display": form_id
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"referencedForms": [],
|
|
63
|
+
"encounter": form_id,
|
|
64
|
+
"pages": []
|
|
65
|
+
}
|
|
66
|
+
self.field_counter = 1
|
|
67
|
+
self.questions_temp = [] # Temporary storage for questions with ordering info
|
|
68
|
+
self.processing_order = 0 # Counter to track processing order
|
|
69
|
+
self.current_segment = None
|
|
70
|
+
self.current_activity = None
|
|
71
|
+
self.concept_map = {}
|
|
72
|
+
|
|
73
|
+
def get_export_name(self, r):
|
|
74
|
+
if isinstance(r, TriccNodeSelectOption):
|
|
75
|
+
return self.get_option_value(r.name)
|
|
76
|
+
elif isinstance(r, str):
|
|
77
|
+
return self.get_option_value(r)
|
|
78
|
+
elif isinstance(r, TriccStatic):
|
|
79
|
+
if isinstance(r.value, str):
|
|
80
|
+
return self.get_option_value(r.value)
|
|
81
|
+
elif isinstance(r.value, bool):
|
|
82
|
+
return str(r.value).lower()
|
|
83
|
+
else:
|
|
84
|
+
return r.value
|
|
85
|
+
else:
|
|
86
|
+
return get_export_name(r) # Assuming r is a node
|
|
87
|
+
|
|
88
|
+
def generate_id(self, name):
|
|
89
|
+
return str(uuid.uuid5(UUID_NAMESPACE, name))
|
|
90
|
+
|
|
91
|
+
def get_option_value(self, option_name):
|
|
92
|
+
if option_name == 'true':
|
|
93
|
+
return TriccStatic(True)
|
|
94
|
+
elif option_name == 'false':
|
|
95
|
+
return TriccStatic(False)
|
|
96
|
+
return self.concept_map.get(option_name, option_name)
|
|
97
|
+
|
|
98
|
+
def get_tricc_operation_expression(self, operation):
|
|
99
|
+
# Similar to HTML, but for JSON, perhaps convert to string expressions
|
|
100
|
+
ref_expressions = []
|
|
101
|
+
if not hasattr(operation, "reference"):
|
|
102
|
+
return self.get_tricc_operation_operand(operation)
|
|
103
|
+
for r in operation.reference:
|
|
104
|
+
if isinstance(r, list):
|
|
105
|
+
r_expr = [
|
|
106
|
+
(
|
|
107
|
+
self.get_tricc_operation_expression(sr)
|
|
108
|
+
if isinstance(sr, TriccOperation)
|
|
109
|
+
else self.get_tricc_operation_operand(sr)
|
|
110
|
+
)
|
|
111
|
+
for sr in r
|
|
112
|
+
]
|
|
113
|
+
elif isinstance(r, TriccOperation):
|
|
114
|
+
r_expr = self.get_tricc_operation_expression(r)
|
|
115
|
+
else:
|
|
116
|
+
r_expr = self.get_tricc_operation_operand(r)
|
|
117
|
+
if isinstance(r_expr, TriccReference):
|
|
118
|
+
r_expr = self.get_tricc_operation_operand(r_expr)
|
|
119
|
+
elif isinstance(r_expr, TriccStatic) and isinstance(r_expr.value, bool):
|
|
120
|
+
r_expr = str(r_expr.value).lower()
|
|
121
|
+
ref_expressions.append(r_expr)
|
|
122
|
+
|
|
123
|
+
# build lower level
|
|
124
|
+
if hasattr(self, f"tricc_operation_{operation.operator}"):
|
|
125
|
+
callable = getattr(self, f"tricc_operation_{operation.operator}")
|
|
126
|
+
return callable(ref_expressions)
|
|
127
|
+
else:
|
|
128
|
+
raise NotImplementedError(
|
|
129
|
+
f"This type of operation '{operation.operator}' is not supported in this strategy"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def execute(self):
|
|
133
|
+
version = datetime.datetime.now().strftime("%Y%m%d%H%M")
|
|
134
|
+
logger.info(f"build version: {version}")
|
|
135
|
+
if "main" in self.project.start_pages:
|
|
136
|
+
self.process_base(self.project.start_pages, pages=self.project.pages, version=version)
|
|
137
|
+
else:
|
|
138
|
+
logger.critical("Main process required")
|
|
139
|
+
|
|
140
|
+
logger.info("generate the relevance based on edges")
|
|
141
|
+
# self.process_relevance(self.project.start_pages, pages=self.project.pages)
|
|
142
|
+
|
|
143
|
+
logger.info("generate the calculate based on edges")
|
|
144
|
+
self.process_calculate(self.project.start_pages, pages=self.project.pages)
|
|
145
|
+
|
|
146
|
+
logger.info("generate the export format")
|
|
147
|
+
self.process_export(self.project.start_pages, pages=self.project.pages)
|
|
148
|
+
|
|
149
|
+
logger.info("print the export")
|
|
150
|
+
self.export(self.project.start_pages, version=version)
|
|
151
|
+
|
|
152
|
+
def map_tricc_type_to_rendering(self, node):
|
|
153
|
+
mapping = {
|
|
154
|
+
'text': 'text',
|
|
155
|
+
'integer': 'number',
|
|
156
|
+
'decimal': 'number',
|
|
157
|
+
'date': 'date',
|
|
158
|
+
'datetime': 'datetime',
|
|
159
|
+
'select_one': 'select',
|
|
160
|
+
'select_multiple': 'multiCheckbox',
|
|
161
|
+
'select_yesno': 'select',
|
|
162
|
+
'not_available': 'checkbox',
|
|
163
|
+
'note': 'text'
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# if issubclass(node.__class__, TriccNodeSelectYesNo):
|
|
167
|
+
# return 'select'
|
|
168
|
+
return mapping.get(node.tricc_type, 'text')
|
|
169
|
+
|
|
170
|
+
def generate_base(self, node, processed_nodes, **kwargs):
|
|
171
|
+
if generate_base(node, processed_nodes, **kwargs):
|
|
172
|
+
if getattr(node, 'name', '') not in ('true', 'false'):
|
|
173
|
+
self.concept_map[node.name] = self.generate_id(self.get_export_name(node))
|
|
174
|
+
return True
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
def generate_question(self, node):
|
|
178
|
+
if issubclass(node.__class__, TriccNodeDisplayModel) and not isinstance(node, TriccNodeSelectOption):
|
|
179
|
+
question = {
|
|
180
|
+
"label": getattr(node, 'label', '').replace('\u00a0', ' ').strip(),
|
|
181
|
+
"type": "obs" if issubclass(node.__class__, TriccNodeInputModel) else 'control',
|
|
182
|
+
"questionOptions": {
|
|
183
|
+
"rendering": self.map_tricc_type_to_rendering(node),
|
|
184
|
+
"concept": self.generate_id(self.get_export_name(node))
|
|
185
|
+
},
|
|
186
|
+
"required": bool(getattr(node, 'required', False)),
|
|
187
|
+
"unspecified": False,
|
|
188
|
+
"id": self.get_export_name(node),
|
|
189
|
+
"uuid": self.generate_id(self.get_export_name(node))
|
|
190
|
+
}
|
|
191
|
+
if node.image:
|
|
192
|
+
question['questionOptions']["imageUrl"] = node.image
|
|
193
|
+
if node.hint:
|
|
194
|
+
question["questionInfo"] = node.hint
|
|
195
|
+
if node.tricc_type in ['select_one', 'select_multiple']:
|
|
196
|
+
# labelTrue = None
|
|
197
|
+
# labelFalse = None
|
|
198
|
+
# Add answers if options
|
|
199
|
+
if hasattr(node, 'options'):
|
|
200
|
+
answers = []
|
|
201
|
+
for opt in node.options.values():
|
|
202
|
+
display = getattr(opt, 'label', opt.name)
|
|
203
|
+
# All options now use UUIDs
|
|
204
|
+
concept_val = self.get_option_value(opt.name)
|
|
205
|
+
if concept_val == TriccStatic(False):
|
|
206
|
+
concept_val = CIEL_NO
|
|
207
|
+
# labelFalse = display
|
|
208
|
+
if concept_val == TriccStatic(True):
|
|
209
|
+
concept_val = CIEL_YES
|
|
210
|
+
# labelTrue = display
|
|
211
|
+
answers.append({
|
|
212
|
+
"label": display,
|
|
213
|
+
"concept": concept_val,
|
|
214
|
+
})
|
|
215
|
+
question["questionOptions"]["answers"] = answers
|
|
216
|
+
else:
|
|
217
|
+
question["questionOptions"]["answers"] = []
|
|
218
|
+
# Set concept for the question itself if it's a coded question
|
|
219
|
+
# if issubclass(node.__class__, TriccNodeSelectYesNo):
|
|
220
|
+
# question["questionOptions"]["toggleOptions"] = {
|
|
221
|
+
# "labelTrue": labelTrue,
|
|
222
|
+
# "labelFalse": labelFalse
|
|
223
|
+
# }
|
|
224
|
+
|
|
225
|
+
relevance = None
|
|
226
|
+
if hasattr(node, 'relevance') and node.relevance:
|
|
227
|
+
relevance = node.relevance
|
|
228
|
+
if hasattr(node, 'expression') and node.expression:
|
|
229
|
+
relevance = node.expression
|
|
230
|
+
if relevance:
|
|
231
|
+
relevance_str = self.convert_expression_to_string(not_clean(relevance))
|
|
232
|
+
if relevance_str and relevance_str != 'false':
|
|
233
|
+
question["hide"] = {
|
|
234
|
+
"hideWhenExpression": f"{relevance_str}"
|
|
235
|
+
}
|
|
236
|
+
return question
|
|
237
|
+
elif issubclass(node.__class__, TriccNodeDisplayCalculateBase):
|
|
238
|
+
expression = getattr(node, 'expression', None)
|
|
239
|
+
if expression:
|
|
240
|
+
question = {
|
|
241
|
+
"id": self.get_export_name(node),
|
|
242
|
+
"type": "obs" if isinstance(node, TriccNodeDiagnosis) else "control",
|
|
243
|
+
"label": getattr(node, 'label', '').replace('\u00a0', ' ').strip(),
|
|
244
|
+
"hide": {
|
|
245
|
+
"hideWhenExpression": "true"
|
|
246
|
+
},
|
|
247
|
+
"questionOptions": {
|
|
248
|
+
"calculate": {
|
|
249
|
+
"calculateExpression": self.convert_expression_to_string(expression)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return question
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
def generate_calculate(self, node, processed_nodes, **kwargs):
|
|
257
|
+
return generate_calculate(node, processed_nodes, **kwargs)
|
|
258
|
+
|
|
259
|
+
def process_export(self, start_pages, **kwargs):
|
|
260
|
+
self.activity_export(start_pages["main"], **kwargs)
|
|
261
|
+
|
|
262
|
+
def activity_export(self, activity, processed_nodes=None, **kwargs):
|
|
263
|
+
if processed_nodes is None:
|
|
264
|
+
processed_nodes = OrderedSet()
|
|
265
|
+
stashed_nodes = OrderedSet()
|
|
266
|
+
# The stashed node are all the node that have all their prevnode processed but not from the same group
|
|
267
|
+
# This logic works only because the prev node are ordered by group/parent ..
|
|
268
|
+
groups = {}
|
|
269
|
+
cur_group = activity
|
|
270
|
+
groups[activity.id] = 0
|
|
271
|
+
path_len = 0
|
|
272
|
+
process = ["main"]
|
|
273
|
+
# keep the versions on the group id, max version
|
|
274
|
+
self.start_page(cur_group)
|
|
275
|
+
self.start_section(cur_group)
|
|
276
|
+
walktrhough_tricc_node_processed_stached(
|
|
277
|
+
activity.root,
|
|
278
|
+
self.generate_export,
|
|
279
|
+
processed_nodes,
|
|
280
|
+
stashed_nodes,
|
|
281
|
+
path_len,
|
|
282
|
+
cur_group=activity.root.group,
|
|
283
|
+
process=process,
|
|
284
|
+
recursive=False,
|
|
285
|
+
**kwargs
|
|
286
|
+
)
|
|
287
|
+
# we save the survey data frame
|
|
288
|
+
# MANAGE STASHED NODES
|
|
289
|
+
prev_stashed_nodes = stashed_nodes.copy()
|
|
290
|
+
loop_count = 0
|
|
291
|
+
len_prev_processed_nodes = 0
|
|
292
|
+
while len(stashed_nodes) > 0:
|
|
293
|
+
self.questions_temp = [] # Reset for new section
|
|
294
|
+
loop_count = check_stashed_loop(
|
|
295
|
+
stashed_nodes,
|
|
296
|
+
prev_stashed_nodes,
|
|
297
|
+
processed_nodes,
|
|
298
|
+
len_prev_processed_nodes,
|
|
299
|
+
loop_count,
|
|
300
|
+
)
|
|
301
|
+
prev_stashed_nodes = stashed_nodes.copy()
|
|
302
|
+
len_prev_processed_nodes = len(processed_nodes)
|
|
303
|
+
if len(stashed_nodes) > 0:
|
|
304
|
+
s_node = stashed_nodes.pop()
|
|
305
|
+
# while len(stashed_nodes)>0 and isinstance(s_node,TriccGroup):
|
|
306
|
+
# s_node = stashed_nodes.pop()
|
|
307
|
+
if s_node.group is None:
|
|
308
|
+
logger.critical("ERROR group is none for node {}".format(s_node.get_name()))
|
|
309
|
+
# arrange empty group
|
|
310
|
+
walktrhough_tricc_node_processed_stached(
|
|
311
|
+
s_node,
|
|
312
|
+
self.generate_export,
|
|
313
|
+
processed_nodes,
|
|
314
|
+
stashed_nodes,
|
|
315
|
+
path_len,
|
|
316
|
+
groups=groups,
|
|
317
|
+
cur_group=s_node.group,
|
|
318
|
+
recursive=False,
|
|
319
|
+
process=process,
|
|
320
|
+
**kwargs
|
|
321
|
+
)
|
|
322
|
+
# add end group if new node where added OR if the previous end group was removed
|
|
323
|
+
# if two line then empty group
|
|
324
|
+
if len(self.questions_temp) > 0:
|
|
325
|
+
# Add questions to current section
|
|
326
|
+
for q_item in sorted(self.questions_temp, key=lambda x: x['processing_order']):
|
|
327
|
+
if self.current_section:
|
|
328
|
+
self.current_section["questions"].append(q_item['question'])
|
|
329
|
+
cur_group = s_node.group
|
|
330
|
+
|
|
331
|
+
return processed_nodes
|
|
332
|
+
|
|
333
|
+
def generate_export(self, node, processed_nodes, **kwargs):
|
|
334
|
+
if not is_ready_to_process(node, processed_nodes, strict=False):
|
|
335
|
+
return False
|
|
336
|
+
|
|
337
|
+
# Process references to ensure dependencies are handled
|
|
338
|
+
if not process_reference(
|
|
339
|
+
node, processed_nodes, {}, replace_reference=False, codesystems=kwargs.get("codesystems", None)
|
|
340
|
+
):
|
|
341
|
+
return False
|
|
342
|
+
|
|
343
|
+
if node not in processed_nodes:
|
|
344
|
+
if self.current_segment != getattr(node.activity.root, 'process', self.current_segment):
|
|
345
|
+
self.start_page(node.activity)
|
|
346
|
+
if self.current_activity != node.group:
|
|
347
|
+
self.start_section(node.activity)
|
|
348
|
+
question = self.generate_question(node)
|
|
349
|
+
if question:
|
|
350
|
+
# Store question with processing order
|
|
351
|
+
# self.questions_temp.append({
|
|
352
|
+
# 'question': question,
|
|
353
|
+
# 'processing_order': self.processing_order,
|
|
354
|
+
# 'node_id': getattr(node, 'id', '')
|
|
355
|
+
# })
|
|
356
|
+
self.processing_order += 1
|
|
357
|
+
self.field_counter += 1
|
|
358
|
+
self.form_data['pages'][-1]['sections'][-1]['questions'].append(question)
|
|
359
|
+
|
|
360
|
+
# Set form name from the start page label if available
|
|
361
|
+
if hasattr(self.project.start_pages["main"], 'label') and self.project.start_pages["main"].label:
|
|
362
|
+
self.form_data["name"] = self.project.start_pages["main"].label.strip()
|
|
363
|
+
elif hasattr(node, 'label') and node.label:
|
|
364
|
+
self.form_data["name"] = node.label.strip()
|
|
365
|
+
return True
|
|
366
|
+
|
|
367
|
+
def export(self, start_pages, version):
|
|
368
|
+
form_id = start_pages["main"].root.form_id or "openmrs_form"
|
|
369
|
+
file_name = f"{form_id}.json"
|
|
370
|
+
newpath = os.path.join(self.output_path, file_name)
|
|
371
|
+
if not os.path.exists(self.output_path):
|
|
372
|
+
os.makedirs(self.output_path)
|
|
373
|
+
|
|
374
|
+
with open(newpath, 'w') as f:
|
|
375
|
+
json.dump(self.form_data, f, indent=2)
|
|
376
|
+
logger.info(f"Exported OpenMRS form to {newpath}")
|
|
377
|
+
|
|
378
|
+
def get_tricc_operation_operand(self, r):
|
|
379
|
+
if isinstance(r, TriccOperation):
|
|
380
|
+
return self.get_tricc_operation_expression(r)
|
|
381
|
+
elif isinstance(r, TriccReference):
|
|
382
|
+
return self.get_export_name(r.value)
|
|
383
|
+
elif isinstance(r, TriccStatic):
|
|
384
|
+
if isinstance(r.value, bool):
|
|
385
|
+
return str(r.value).lower()
|
|
386
|
+
if isinstance(r.value, str):
|
|
387
|
+
return f"'{r.value}'"
|
|
388
|
+
else:
|
|
389
|
+
return str(r.value)
|
|
390
|
+
elif isinstance(r, bool):
|
|
391
|
+
return str(r).lower()
|
|
392
|
+
elif isinstance(r, str):
|
|
393
|
+
return f"{r}"
|
|
394
|
+
elif isinstance(r, (int, float)):
|
|
395
|
+
return str(r).lower()
|
|
396
|
+
elif isinstance(r, TriccNodeSelectOption):
|
|
397
|
+
option = self.get_option_value(r.name)
|
|
398
|
+
if r.name in ('true', 'false'):
|
|
399
|
+
return option
|
|
400
|
+
return f"'{option}'"
|
|
401
|
+
elif issubclass(r.__class__, TriccNodeInputModel):
|
|
402
|
+
return self.get_export_name(r)
|
|
403
|
+
elif issubclass(r.__class__, TriccNodeBaseModel):
|
|
404
|
+
return self.get_export_name(r)
|
|
405
|
+
else:
|
|
406
|
+
raise NotImplementedError(f"This type of node {r.__class__} is not supported within an operation")
|
|
407
|
+
|
|
408
|
+
def convert_expression_to_string(self, expression):
|
|
409
|
+
# Convert to string expression for JSON
|
|
410
|
+
if isinstance(expression, TriccOperation):
|
|
411
|
+
return self.get_tricc_operation_expression(expression)
|
|
412
|
+
else:
|
|
413
|
+
return self.get_tricc_operation_operand(expression)
|
|
414
|
+
|
|
415
|
+
# Operation methods similar, but for string expressions
|
|
416
|
+
def tricc_operation_equal(self, ref_expressions):
|
|
417
|
+
if ref_expressions[1] == TriccStatic(True) or ref_expressions[1] is True or ref_expressions[1] == 'true':
|
|
418
|
+
return f"{self._boolean(ref_expressions, '===', CIEL_YES, 'true')}"
|
|
419
|
+
elif ref_expressions[1] == TriccStatic(False) or ref_expressions[1] is False or ref_expressions[1] == 'false':
|
|
420
|
+
return f"{self._boolean(ref_expressions, '===', CIEL_NO, 'false')}"
|
|
421
|
+
return f"{ref_expressions[0]} === {ref_expressions[1]}"
|
|
422
|
+
|
|
423
|
+
def tricc_operation_not_equal(self, ref_expressions):
|
|
424
|
+
if ref_expressions[1] == TriccStatic(True) or ref_expressions[1] is True or ref_expressions[1] == 'true':
|
|
425
|
+
return f"!{self._boolean(ref_expressions, '===', CIEL_YES, 'true')}"
|
|
426
|
+
elif ref_expressions[1] == TriccStatic(False) or ref_expressions[1] is False or ref_expressions[1] == 'false':
|
|
427
|
+
return f"!{self._boolean(ref_expressions, '===', CIEL_NO, 'false')}"
|
|
428
|
+
return f"{ref_expressions[0]} !== {ref_expressions[1]}"
|
|
429
|
+
|
|
430
|
+
def tricc_operation_and(self, ref_expressions):
|
|
431
|
+
if len(ref_expressions) == 1:
|
|
432
|
+
return ref_expressions[0]
|
|
433
|
+
if len(ref_expressions) > 1:
|
|
434
|
+
return " && ".join(ref_expressions)
|
|
435
|
+
else:
|
|
436
|
+
return "true"
|
|
437
|
+
|
|
438
|
+
def tricc_operation_or(self, ref_expressions):
|
|
439
|
+
if len(ref_expressions) == 1:
|
|
440
|
+
return ref_expressions[0]
|
|
441
|
+
if len(ref_expressions) > 1:
|
|
442
|
+
return " || ".join(ref_expressions)
|
|
443
|
+
else:
|
|
444
|
+
return "true"
|
|
445
|
+
|
|
446
|
+
def tricc_operation_not(self, ref_expressions):
|
|
447
|
+
return f"!({ref_expressions[0]})"
|
|
448
|
+
|
|
449
|
+
def tricc_operation_plus(self, ref_expressions):
|
|
450
|
+
return " + ".join(ref_expressions)
|
|
451
|
+
|
|
452
|
+
def tricc_operation_minus(self, ref_expressions):
|
|
453
|
+
if len(ref_expressions) > 1:
|
|
454
|
+
return " - ".join(map(str, ref_expressions))
|
|
455
|
+
elif len(ref_expressions) == 1:
|
|
456
|
+
return f"-{ref_expressions[0]}"
|
|
457
|
+
|
|
458
|
+
def tricc_operation_more(self, ref_expressions):
|
|
459
|
+
return f"{ref_expressions[0]} > {ref_expressions[1]}"
|
|
460
|
+
|
|
461
|
+
def tricc_operation_less(self, ref_expressions):
|
|
462
|
+
return f"{ref_expressions[0]} < {ref_expressions[1]}"
|
|
463
|
+
|
|
464
|
+
def tricc_operation_more_or_equal(self, ref_expressions):
|
|
465
|
+
return f"{ref_expressions[0]} >= {ref_expressions[1]}"
|
|
466
|
+
|
|
467
|
+
def tricc_operation_less_or_equal(self, ref_expressions):
|
|
468
|
+
return f"{ref_expressions[0]} <= {ref_expressions[1]}"
|
|
469
|
+
|
|
470
|
+
def tricc_operation_selected(self, ref_expressions):
|
|
471
|
+
# For choice questions, returns true if the second reference (value) is included in the first (field)
|
|
472
|
+
return f"({ref_expressions[0]}.includes({ref_expressions[1]}))"
|
|
473
|
+
|
|
474
|
+
def tricc_operation_count(self, ref_expressions):
|
|
475
|
+
return f"({ref_expressions[0]}.length)"
|
|
476
|
+
|
|
477
|
+
def tricc_operation_multiplied(self, ref_expressions):
|
|
478
|
+
return "*".join(ref_expressions)
|
|
479
|
+
|
|
480
|
+
def tricc_operation_divided(self, ref_expressions):
|
|
481
|
+
return f"{ref_expressions[0]} / {ref_expressions[1]}"
|
|
482
|
+
|
|
483
|
+
def tricc_operation_modulo(self, ref_expressions):
|
|
484
|
+
return f"{ref_expressions[0]} % {ref_expressions[1]}"
|
|
485
|
+
|
|
486
|
+
def tricc_operation_coalesce(self, ref_expressions):
|
|
487
|
+
return f"coalesce({','.join(ref_expressions)})"
|
|
488
|
+
|
|
489
|
+
def tricc_operation_module(self, ref_expressions):
|
|
490
|
+
return f"{ref_expressions[0]} % {ref_expressions[1]}"
|
|
491
|
+
|
|
492
|
+
def tricc_operation_native(self, ref_expressions):
|
|
493
|
+
if len(ref_expressions) > 0:
|
|
494
|
+
return f"{ref_expressions[0]}({','.join(ref_expressions[1:])})"
|
|
495
|
+
|
|
496
|
+
def tricc_operation_istrue(self, ref_expressions):
|
|
497
|
+
# return f"{ref_expressions[0]} === true"
|
|
498
|
+
return f"{self._boolean(ref_expressions, '===', CIEL_YES, 'true')}"
|
|
499
|
+
|
|
500
|
+
def tricc_operation_isfalse(self, ref_expressions):
|
|
501
|
+
# return f"{ref_expressions[0]} === false"
|
|
502
|
+
return f"{self._boolean(ref_expressions, '===', CIEL_NO, 'false')}"
|
|
503
|
+
|
|
504
|
+
def tricc_operation_parenthesis(self, ref_expressions):
|
|
505
|
+
return f"({ref_expressions[0]})"
|
|
506
|
+
|
|
507
|
+
def tricc_operation_between(self, ref_expressions):
|
|
508
|
+
return f"{ref_expressions[0]} >= {ref_expressions[1]} && {ref_expressions[0]} < {ref_expressions[2]}"
|
|
509
|
+
|
|
510
|
+
def tricc_operation_isnull(self, ref_expressions):
|
|
511
|
+
return f"isEmpty({ref_expressions[0]})"
|
|
512
|
+
|
|
513
|
+
def tricc_operation_isnotnull(self, ref_expressions):
|
|
514
|
+
return f"!isEmpty{ref_expressions[0]})"
|
|
515
|
+
|
|
516
|
+
def tricc_operation_isnottrue(self, ref_expressions):
|
|
517
|
+
# return f"{ref_expressions[0]} !== true"
|
|
518
|
+
return f"!{self._boolean(ref_expressions, '===', CIEL_YES, 'true')}"
|
|
519
|
+
|
|
520
|
+
def tricc_operation_isnotfalse(self, ref_expressions):
|
|
521
|
+
# return f"{ref_expressions[0]} !== false"
|
|
522
|
+
return f"!{self._boolean(ref_expressions, '===', CIEL_NO, 'false')}"
|
|
523
|
+
|
|
524
|
+
def _boolean(self, ref_expressions, operator, answer_uuid, bool_val='false'):
|
|
525
|
+
return f"({ref_expressions[0]} {operator} {bool_val} || {ref_expressions[0]} {operator} '{answer_uuid}')"
|
|
526
|
+
|
|
527
|
+
def tricc_operation_notexist(self, ref_expressions):
|
|
528
|
+
return f"typeof {ref_expressions[0]} === 'undefined'"
|
|
529
|
+
|
|
530
|
+
def tricc_operation_case(self, ref_expressions):
|
|
531
|
+
# Simplified, assuming list of conditions
|
|
532
|
+
parts = []
|
|
533
|
+
for i in range(0, len(ref_expressions), 2):
|
|
534
|
+
if i + 1 < len(ref_expressions):
|
|
535
|
+
parts.append(f"if({ref_expressions[i]}, {ref_expressions[i+1]})")
|
|
536
|
+
return " || ".join(parts) # Simplified
|
|
537
|
+
|
|
538
|
+
def tricc_operation_ifs(self, ref_expressions):
|
|
539
|
+
# Similar to case
|
|
540
|
+
return self.tricc_operation_case(ref_expressions[1:])
|
|
541
|
+
|
|
542
|
+
def tricc_operation_if(self, ref_expressions):
|
|
543
|
+
return f"if({ref_expressions[0]}, {ref_expressions[1]}, {ref_expressions[2]})"
|
|
544
|
+
|
|
545
|
+
def tricc_operation_contains(self, ref_expressions):
|
|
546
|
+
return f"contains({ref_expressions[0]}, {ref_expressions[1]})"
|
|
547
|
+
|
|
548
|
+
def tricc_operation_exists(self, ref_expressions):
|
|
549
|
+
parts = []
|
|
550
|
+
for ref in ref_expressions:
|
|
551
|
+
parts.append(f"!isEmpty{ref})")
|
|
552
|
+
return " && ".join(parts)
|
|
553
|
+
|
|
554
|
+
def tricc_operation_cast_number(self, ref_expressions):
|
|
555
|
+
return f"Number({ref_expressions[0]})"
|
|
556
|
+
|
|
557
|
+
def tricc_operation_cast_integer(self, ref_expressions):
|
|
558
|
+
return f"Number({ref_expressions[0]})"
|
|
559
|
+
|
|
560
|
+
def tricc_operation_zscore(self, ref_expressions):
|
|
561
|
+
# Simplified, assuming params
|
|
562
|
+
return f"zscore({','.join(ref_expressions)})"
|
|
563
|
+
|
|
564
|
+
def tricc_operation_datetime_to_decimal(self, ref_expressions):
|
|
565
|
+
return f"decimal-date-time({ref_expressions[0]})"
|
|
566
|
+
|
|
567
|
+
def tricc_operation_round(self, ref_expressions):
|
|
568
|
+
return f"round({ref_expressions[0]})"
|
|
569
|
+
|
|
570
|
+
def tricc_operation_izscore(self, ref_expressions):
|
|
571
|
+
return f"izscore({','.join(ref_expressions)})"
|
|
572
|
+
|
|
573
|
+
def tricc_operation_concatenate(self, ref_expressions):
|
|
574
|
+
return f"concat({','.join(ref_expressions)})"
|
|
575
|
+
|
|
576
|
+
def clean_sections(self):
|
|
577
|
+
if (
|
|
578
|
+
self.form_data['pages']
|
|
579
|
+
and self.form_data['pages'][-1]
|
|
580
|
+
and self.form_data['pages'][-1]['sections']
|
|
581
|
+
and self.form_data['pages'][-1]['sections'][-1]
|
|
582
|
+
):
|
|
583
|
+
if len(self.form_data['pages'][-1]['sections'][-1]['questions']) == 0:
|
|
584
|
+
self.form_data['pages'][-1]['sections'].pop()
|
|
585
|
+
|
|
586
|
+
def clean_pages(self):
|
|
587
|
+
if self.form_data['pages'] and self.form_data['pages'][-1]:
|
|
588
|
+
if len(self.form_data['pages'][-1]['sections']) == 0:
|
|
589
|
+
self.form_data['pages'].pop()
|
|
590
|
+
|
|
591
|
+
def start_page(self, activity_node):
|
|
592
|
+
# Add more operations as needed...
|
|
593
|
+
"""Start a new page for an activity"""
|
|
594
|
+
self.clean_sections()
|
|
595
|
+
self.clean_pages()
|
|
596
|
+
page_label = getattr(activity_node.root, 'process', None)
|
|
597
|
+
self.current_segment = page_label
|
|
598
|
+
# Set process from id if not set
|
|
599
|
+
default_label = f"Page {len(self.form_data['pages']) + 1}"
|
|
600
|
+
|
|
601
|
+
if page_label is None:
|
|
602
|
+
label = getattr(activity_node, 'label', None)
|
|
603
|
+
if label is None:
|
|
604
|
+
page_label = default_label
|
|
605
|
+
else:
|
|
606
|
+
page_label = label
|
|
607
|
+
page_label = page_label.replace('\u00a0', ' ').strip()
|
|
608
|
+
self.form_data["pages"].append({
|
|
609
|
+
"label": page_label,
|
|
610
|
+
"sections": []
|
|
611
|
+
})
|
|
612
|
+
if activity_node.relevance:
|
|
613
|
+
relevance_str = self.convert_expression_to_string(not_clean(activity_node.relevance))
|
|
614
|
+
if relevance_str and relevance_str != 'false':
|
|
615
|
+
self.form_data["pages"][-1]["hide"] = {
|
|
616
|
+
"hideWhenExpression": f"{relevance_str}"
|
|
617
|
+
}
|
|
618
|
+
logger.debug(f"Started page: {page_label}")
|
|
619
|
+
|
|
620
|
+
def start_section(self, group_node):
|
|
621
|
+
"""Start a new section for a group"""
|
|
622
|
+
self.clean_sections()
|
|
623
|
+
self.current_activity = group_node
|
|
624
|
+
# Set process from id if not set
|
|
625
|
+
default_label = f"Section {len(self.form_data['pages'][-1]['sections']) + 1}"
|
|
626
|
+
if hasattr(group_node, 'root'):
|
|
627
|
+
section_label = getattr(group_node.root, 'label', None)
|
|
628
|
+
else:
|
|
629
|
+
section_label = getattr(group_node, 'label', None)
|
|
630
|
+
if section_label is None:
|
|
631
|
+
label = getattr(group_node, 'label', None)
|
|
632
|
+
if label is None:
|
|
633
|
+
section_label = default_label
|
|
634
|
+
else:
|
|
635
|
+
section_label = label
|
|
636
|
+
section_label = section_label.replace('\u00a0', ' ').strip()
|
|
637
|
+
self.form_data['pages'][-1]['sections'].append({
|
|
638
|
+
"label": section_label,
|
|
639
|
+
"questions": []
|
|
640
|
+
})
|
|
641
|
+
if group_node.relevance:
|
|
642
|
+
relevance_str = self.convert_expression_to_string(not_clean(group_node.relevance))
|
|
643
|
+
if relevance_str and relevance_str != 'false':
|
|
644
|
+
self.form_data["pages"][-1]['sections'][-1]["hide"] = {
|
|
645
|
+
"hideWhenExpression": f"{relevance_str}"
|
|
646
|
+
}
|
|
647
|
+
logger.debug(f"Started section: {section_label}")
|