tricc-oo 1.0.1__py3-none-any.whl → 1.4.15__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 +213 -0
- tests/test_cql.py +197 -0
- tests/to_ocl.py +51 -0
- {tricc → tricc_oo}/__init__.py +3 -1
- tricc_oo/converters/codesystem_to_ocl.py +169 -0
- tricc_oo/converters/cql/cqlLexer.py +822 -0
- tricc_oo/converters/cql/cqlListener.py +1632 -0
- tricc_oo/converters/cql/cqlParser.py +11204 -0
- tricc_oo/converters/cql/cqlVisitor.py +913 -0
- tricc_oo/converters/cql_to_operation.py +402 -0
- tricc_oo/converters/datadictionnary.py +115 -0
- tricc_oo/converters/drawio_type_map.py +222 -0
- tricc_oo/converters/tricc_to_xls_form.py +61 -0
- tricc_oo/converters/utils.py +65 -0
- tricc_oo/converters/xml_to_tricc.py +1003 -0
- tricc_oo/models/__init__.py +4 -0
- tricc_oo/models/base.py +732 -0
- tricc_oo/models/calculate.py +216 -0
- tricc_oo/models/ocl.py +281 -0
- tricc_oo/models/ordered_set.py +125 -0
- tricc_oo/models/tricc.py +418 -0
- tricc_oo/parsers/xml.py +138 -0
- tricc_oo/serializers/__init__.py +0 -0
- tricc_oo/serializers/xls_form.py +745 -0
- tricc_oo/strategies/__init__.py +0 -0
- tricc_oo/strategies/input/__init__.py +0 -0
- tricc_oo/strategies/input/base_input_strategy.py +111 -0
- tricc_oo/strategies/input/drawio.py +317 -0
- tricc_oo/strategies/output/base_output_strategy.py +148 -0
- tricc_oo/strategies/output/spice.py +365 -0
- tricc_oo/strategies/output/xls_form.py +697 -0
- tricc_oo/strategies/output/xlsform_cdss.py +189 -0
- tricc_oo/strategies/output/xlsform_cht.py +200 -0
- tricc_oo/strategies/output/xlsform_cht_hf.py +334 -0
- tricc_oo/visitors/__init__.py +0 -0
- tricc_oo/visitors/tricc.py +2198 -0
- tricc_oo/visitors/utils.py +17 -0
- tricc_oo/visitors/xform_pd.py +264 -0
- tricc_oo-1.4.15.dist-info/METADATA +219 -0
- tricc_oo-1.4.15.dist-info/RECORD +46 -0
- {tricc_oo-1.0.1.dist-info → tricc_oo-1.4.15.dist-info}/WHEEL +1 -1
- tricc_oo-1.4.15.dist-info/top_level.txt +2 -0
- tricc/converters/mc_to_tricc.py +0 -542
- tricc/converters/tricc_to_xls_form.py +0 -553
- tricc/converters/utils.py +0 -44
- tricc/converters/xml_to_tricc.py +0 -740
- tricc/models/tricc.py +0 -1093
- tricc/parsers/xml.py +0 -81
- tricc/serializers/xls_form.py +0 -364
- tricc/strategies/input/base_input_strategy.py +0 -80
- tricc/strategies/input/drawio.py +0 -246
- tricc/strategies/input/medalcreator.py +0 -168
- tricc/strategies/output/base_output_strategy.py +0 -92
- tricc/strategies/output/xls_form.py +0 -194
- tricc/strategies/output/xlsform_cdss.py +0 -46
- tricc/strategies/output/xlsform_cht.py +0 -106
- tricc/visitors/tricc.py +0 -375
- tricc_oo-1.0.1.dist-info/LICENSE +0 -78
- tricc_oo-1.0.1.dist-info/METADATA +0 -229
- tricc_oo-1.0.1.dist-info/RECORD +0 -26
- tricc_oo-1.0.1.dist-info/top_level.txt +0 -2
- venv/bin/vba_extract.py +0 -78
- {tricc → tricc_oo}/converters/__init__.py +0 -0
- {tricc → tricc_oo}/models/lang.py +0 -0
- {tricc/serializers → tricc_oo/parsers}/__init__.py +0 -0
- {tricc → tricc_oo}/serializers/planuml.py +0 -0
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Strategy to build the skyp logic following the XLSForm way
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import datetime
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
from tricc_oo.converters.tricc_to_xls_form import *
|
|
13
|
+
|
|
14
|
+
from tricc_oo.models import (
|
|
15
|
+
TriccNodeActivity,
|
|
16
|
+
TriccGroup,
|
|
17
|
+
TriccOperation,
|
|
18
|
+
TriccOperator
|
|
19
|
+
)
|
|
20
|
+
from tricc_oo.models.lang import SingletonLangClass
|
|
21
|
+
|
|
22
|
+
from tricc_oo.visitors.tricc import (
|
|
23
|
+
check_stashed_loop,
|
|
24
|
+
walktrhough_tricc_node_processed_stached,
|
|
25
|
+
is_ready_to_process,
|
|
26
|
+
process_reference,
|
|
27
|
+
get_node_expressions,
|
|
28
|
+
TRICC_TRUE_VALUE,
|
|
29
|
+
TRICC_FALSE_VALUE
|
|
30
|
+
)
|
|
31
|
+
from tricc_oo.serializers.xls_form import (
|
|
32
|
+
CHOICE_MAP,
|
|
33
|
+
SURVEY_MAP,
|
|
34
|
+
end_group,
|
|
35
|
+
generate_xls_form_export,
|
|
36
|
+
start_group,
|
|
37
|
+
BOOLEAN_MAP
|
|
38
|
+
)
|
|
39
|
+
from tricc_oo.strategies.output.base_output_strategy import BaseOutPutStrategy
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger("default")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
The XLSForm strategy is a strategy that will generate the XLSForm logic
|
|
47
|
+
The XLSForm logic is a logic that is based on the XLSForm format
|
|
48
|
+
The XLSForm format is a format that is used by the ODK Collect application
|
|
49
|
+
The ODK Collect application is an application that is used to collect data on mobile devices
|
|
50
|
+
|
|
51
|
+
document below function
|
|
52
|
+
|
|
53
|
+
generate_xls_form_condition
|
|
54
|
+
generate_xls_form_relevance
|
|
55
|
+
generate_xls_form_calculate
|
|
56
|
+
generate_xls_form_export
|
|
57
|
+
start_group
|
|
58
|
+
end_group
|
|
59
|
+
walktrhough_tricc_node_processed_stached
|
|
60
|
+
check_stashed_loop
|
|
61
|
+
generate_xls_form_export
|
|
62
|
+
generate_xls_form_export
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
langs = SingletonLangClass()
|
|
66
|
+
|
|
67
|
+
OPERATOR_COALESCE_FALLBACK = {
|
|
68
|
+
TriccOperator.ISTRUE: 0,
|
|
69
|
+
TriccOperator.ISFALSE: 1,
|
|
70
|
+
TriccOperator.ISNOTTRUE: 0,
|
|
71
|
+
TriccOperator.ISNOTFALSE: 1,
|
|
72
|
+
TriccOperator.MORE: -2147483648,
|
|
73
|
+
TriccOperator.MORE_OR_EQUAL: -2147483648,
|
|
74
|
+
TriccOperator.LESS: 2147483647,
|
|
75
|
+
TriccOperator.LESS_OR_EQUAL: 2147483647
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class XLSFormStrategy(BaseOutPutStrategy):
|
|
79
|
+
pd.set_option('display.max_colwidth', None)
|
|
80
|
+
df_survey = pd.DataFrame(columns=SURVEY_MAP.keys())
|
|
81
|
+
df_calculate = pd.DataFrame(columns=SURVEY_MAP.keys())
|
|
82
|
+
df_choice = pd.DataFrame(columns=CHOICE_MAP.keys())
|
|
83
|
+
calculates = {}
|
|
84
|
+
# add save nodes and merge nodes
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def clean_coalesce(self, expression):
|
|
88
|
+
if re.match(r"^coalesce\(\${[^}]+},''\)$", expression):
|
|
89
|
+
return expression[9:-4]
|
|
90
|
+
return expression
|
|
91
|
+
|
|
92
|
+
def generate_base(self, node, **kwargs):
|
|
93
|
+
return self.generate_xls_form_condition(node, **kwargs)
|
|
94
|
+
|
|
95
|
+
def generate_relevance(self, node, **kwargs):
|
|
96
|
+
return self.generate_xls_form_relevance(node, **kwargs)
|
|
97
|
+
|
|
98
|
+
def generate_calculate(self, node, **kwargs):
|
|
99
|
+
return self.generate_xls_form_calculate(node, **kwargs)
|
|
100
|
+
|
|
101
|
+
def __init__(self, project, output_path):
|
|
102
|
+
super().__init__(project, output_path)
|
|
103
|
+
self.do_clean()
|
|
104
|
+
|
|
105
|
+
def do_clean(self, **kwargs):
|
|
106
|
+
self.calculates = {}
|
|
107
|
+
self.used_calculates = {}
|
|
108
|
+
|
|
109
|
+
def get_kwargs(self):
|
|
110
|
+
return {
|
|
111
|
+
"df_survey": self.df_survey,
|
|
112
|
+
"df_choice": self.df_choice,
|
|
113
|
+
"df_calculate": self.df_calculate,
|
|
114
|
+
"calculates": self.calculates
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
def generate_export(self, node, **kwargs):
|
|
118
|
+
return generate_xls_form_export(self, node, **kwargs)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def export(self, start_pages, version):
|
|
122
|
+
if start_pages["main"].root.form_id is not None:
|
|
123
|
+
form_id = str(start_pages["main"].root.form_id)
|
|
124
|
+
else:
|
|
125
|
+
logger.critical("form id required in the first start node")
|
|
126
|
+
exit(1)
|
|
127
|
+
title = start_pages["main"].root.label
|
|
128
|
+
file_name = form_id + ".xlsx"
|
|
129
|
+
# make a 'settings' tab
|
|
130
|
+
now = datetime.datetime.now()
|
|
131
|
+
indx = [[1]]
|
|
132
|
+
|
|
133
|
+
settings = {
|
|
134
|
+
"form_title": title,
|
|
135
|
+
"form_id": form_id,
|
|
136
|
+
"version": version,
|
|
137
|
+
"default_language": "English (en)",
|
|
138
|
+
"style": "pages",
|
|
139
|
+
}
|
|
140
|
+
df_settings = pd.DataFrame(settings, index=indx)
|
|
141
|
+
df_settings.head()
|
|
142
|
+
|
|
143
|
+
newpath = os.path.join(self.output_path, file_name)
|
|
144
|
+
# create newpath if it not exists
|
|
145
|
+
if not os.path.exists(self.output_path):
|
|
146
|
+
os.makedirs(self.output_path)
|
|
147
|
+
|
|
148
|
+
# create a Pandas Excel writer using XlsxWriter as the engine
|
|
149
|
+
writer = pd.ExcelWriter(newpath, engine="xlsxwriter")
|
|
150
|
+
if len(self.df_survey[self.df_survey['name'] == 'version'] ):
|
|
151
|
+
self.df_survey.loc[ self.df_survey['name'] == 'version', 'label'] = f"v{version}"
|
|
152
|
+
self.df_survey.to_excel(writer, sheet_name="survey", index=False)
|
|
153
|
+
self.df_choice.to_excel(writer, sheet_name="choices", index=False)
|
|
154
|
+
df_settings.to_excel(writer, sheet_name="settings", index=False)
|
|
155
|
+
|
|
156
|
+
# close the Pandas Excel writer and output the Excel file
|
|
157
|
+
# writer.save()
|
|
158
|
+
|
|
159
|
+
# run this on a windows python instance because if not then the generated xlsx file remains open
|
|
160
|
+
writer.close()
|
|
161
|
+
# writer.handles = None
|
|
162
|
+
|
|
163
|
+
def process_export(self, start_pages, **kwargs):
|
|
164
|
+
self.activity_export(start_pages["main"], **kwargs)
|
|
165
|
+
|
|
166
|
+
def activity_export(self, activity, processed_nodes=None, **kwargs):
|
|
167
|
+
if processed_nodes is None:
|
|
168
|
+
processed_nodes = OrderedSet()
|
|
169
|
+
stashed_nodes = OrderedSet()
|
|
170
|
+
# The stashed node are all the node that have all their prevnode processed but not from the same group
|
|
171
|
+
# This logic works only because the prev node are ordered by group/parent ..
|
|
172
|
+
skip_header = 0
|
|
173
|
+
groups = {}
|
|
174
|
+
cur_group = activity
|
|
175
|
+
groups[activity.id] = 0
|
|
176
|
+
path_len = 0
|
|
177
|
+
# keep the vesrions on the group id, max version
|
|
178
|
+
start_group(self, cur_group=cur_group, groups=groups, **self.get_kwargs())
|
|
179
|
+
walktrhough_tricc_node_processed_stached(
|
|
180
|
+
activity.root,
|
|
181
|
+
self.generate_export,
|
|
182
|
+
processed_nodes,
|
|
183
|
+
stashed_nodes,
|
|
184
|
+
path_len,
|
|
185
|
+
cur_group=activity.root.group,
|
|
186
|
+
recursive=False,
|
|
187
|
+
**self.get_kwargs()
|
|
188
|
+
)
|
|
189
|
+
end_group(self, cur_group=activity, groups=groups, **self.get_kwargs())
|
|
190
|
+
# we save the survey data frame
|
|
191
|
+
df_survey_final = pd.DataFrame(columns=SURVEY_MAP.keys())
|
|
192
|
+
if len(self.df_survey) > (2 + skip_header):
|
|
193
|
+
df_survey_final = self.df_survey
|
|
194
|
+
## MANAGE STASHED NODES
|
|
195
|
+
prev_stashed_nodes = stashed_nodes.copy()
|
|
196
|
+
loop_count = 0
|
|
197
|
+
len_prev_processed_nodes = 0
|
|
198
|
+
while len(stashed_nodes) > 0:
|
|
199
|
+
self.df_survey = pd.DataFrame(columns=SURVEY_MAP.keys())
|
|
200
|
+
loop_count = check_stashed_loop(
|
|
201
|
+
stashed_nodes,
|
|
202
|
+
prev_stashed_nodes,
|
|
203
|
+
processed_nodes,
|
|
204
|
+
len_prev_processed_nodes,
|
|
205
|
+
loop_count,
|
|
206
|
+
)
|
|
207
|
+
prev_stashed_nodes = stashed_nodes.copy()
|
|
208
|
+
len_prev_processed_nodes = len(processed_nodes)
|
|
209
|
+
if len(stashed_nodes) > 0:
|
|
210
|
+
s_node = stashed_nodes.pop()
|
|
211
|
+
# while len(stashed_nodes)>0 and isinstance(s_node,TriccGroup):
|
|
212
|
+
# s_node = stashed_nodes.pop()
|
|
213
|
+
if s_node.group is None:
|
|
214
|
+
logger.critical(
|
|
215
|
+
"ERROR group is none for node {}".format(s_node.get_name())
|
|
216
|
+
)
|
|
217
|
+
start_group(
|
|
218
|
+
self,
|
|
219
|
+
cur_group=s_node.group,
|
|
220
|
+
groups=groups,
|
|
221
|
+
relevance=True,
|
|
222
|
+
**self.get_kwargs()
|
|
223
|
+
)
|
|
224
|
+
# arrange empty group
|
|
225
|
+
|
|
226
|
+
walktrhough_tricc_node_processed_stached(
|
|
227
|
+
s_node,
|
|
228
|
+
self.generate_export,
|
|
229
|
+
processed_nodes,
|
|
230
|
+
stashed_nodes,
|
|
231
|
+
path_len,
|
|
232
|
+
groups=groups,
|
|
233
|
+
cur_group=s_node.group,
|
|
234
|
+
recursive=False,
|
|
235
|
+
**self.get_kwargs()
|
|
236
|
+
)
|
|
237
|
+
# add end group if new node where added OR if the previous end group was removed
|
|
238
|
+
end_group(self, cur_group=s_node.group, groups=groups, **self.get_kwargs())
|
|
239
|
+
# if two line then empty grou
|
|
240
|
+
if len(self.df_survey) > (2 + skip_header):
|
|
241
|
+
if cur_group == s_node.group:
|
|
242
|
+
# drop the end group (to merge)
|
|
243
|
+
logger.debug(
|
|
244
|
+
"printing same group {}::{}::{}::{}".format(
|
|
245
|
+
s_node.group.__class__,
|
|
246
|
+
s_node.group.get_name(),
|
|
247
|
+
s_node.id,
|
|
248
|
+
s_node.group.instance,
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
if len(df_survey_final):
|
|
252
|
+
df_survey_final.drop(
|
|
253
|
+
index=df_survey_final.index[-1], axis=0, inplace=True
|
|
254
|
+
)
|
|
255
|
+
self.df_survey = self.df_survey[(1 + skip_header) :]
|
|
256
|
+
df_survey_final = pd.concat(
|
|
257
|
+
[df_survey_final, self.df_survey], ignore_index=True
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
else:
|
|
261
|
+
logger.debug(
|
|
262
|
+
"printing group {}::{}::{}::{}".format(
|
|
263
|
+
s_node.group.__class__,
|
|
264
|
+
s_node.group.get_name(),
|
|
265
|
+
s_node.id,
|
|
266
|
+
s_node.group.instance,
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
df_survey_final = pd.concat(
|
|
270
|
+
[df_survey_final, self.df_survey], ignore_index=True
|
|
271
|
+
)
|
|
272
|
+
cur_group = s_node.group
|
|
273
|
+
|
|
274
|
+
# add the calulate
|
|
275
|
+
self.df_calculate = self.df_calculate.dropna(axis=0, subset=["calculation"])
|
|
276
|
+
df_empty_calc = self.df_calculate[self.df_calculate["calculation"] == ""]
|
|
277
|
+
self.df_survey.reset_index(drop=True, inplace=True)
|
|
278
|
+
self.df_calculate.reset_index(drop=True, inplace=True)
|
|
279
|
+
self.df_calculate = self.df_calculate.drop(df_empty_calc.index)
|
|
280
|
+
self.df_survey = pd.concat(
|
|
281
|
+
[df_survey_final, self.df_calculate], ignore_index=True
|
|
282
|
+
)
|
|
283
|
+
df_duplicate = self.df_calculate[
|
|
284
|
+
self.df_calculate.duplicated(subset=["calculation"], keep="first")
|
|
285
|
+
]
|
|
286
|
+
# self.df_survey=self.df_survey.drop_duplicates(subset=['name'])
|
|
287
|
+
for index, drop_calc in df_duplicate.iterrows():
|
|
288
|
+
# remove the duplicate
|
|
289
|
+
replace_name = False
|
|
290
|
+
# find the actual calcualte
|
|
291
|
+
similar_calc = self.df_survey[
|
|
292
|
+
(drop_calc["calculation"] == self.df_survey["calculation"])
|
|
293
|
+
& (self.df_survey["type"] == "calculate")
|
|
294
|
+
]
|
|
295
|
+
same_calc = self.df_survey[self.df_survey["name"] == drop_calc["name"]]
|
|
296
|
+
if len(same_calc) > 1:
|
|
297
|
+
# check if all calc have the same name
|
|
298
|
+
if len(same_calc) == len(similar_calc):
|
|
299
|
+
# drop all but one
|
|
300
|
+
self.df_survey.drop(same_calc.index[1:])
|
|
301
|
+
elif len(same_calc) < len(similar_calc):
|
|
302
|
+
self.df_survey.drop(same_calc.index)
|
|
303
|
+
replace_name = True
|
|
304
|
+
elif len(same_calc) == 1:
|
|
305
|
+
self.df_survey.drop(similar_calc.index)
|
|
306
|
+
replace_name = True
|
|
307
|
+
|
|
308
|
+
if replace_name:
|
|
309
|
+
save_calc = self.df_survey[
|
|
310
|
+
(drop_calc["calculation"] == self.df_survey["calculation"])
|
|
311
|
+
& (self.df_survey["type"] == "calculate")
|
|
312
|
+
]
|
|
313
|
+
if len(save_calc) >= 1:
|
|
314
|
+
save_calc = save_calc.iloc[0]
|
|
315
|
+
if save_calc["name"] != drop_calc["name"]:
|
|
316
|
+
self.df_survey.replace(
|
|
317
|
+
"\$\{" + drop_calc["name"] + "\}",
|
|
318
|
+
"\$\{" + save_calc["name"] + "\}",
|
|
319
|
+
regex=True,
|
|
320
|
+
)
|
|
321
|
+
else:
|
|
322
|
+
logger.critical(
|
|
323
|
+
"duplicate reference not found for calculation: {}".format(
|
|
324
|
+
drop_calc["calculation"]
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
for index, empty_calc in df_empty_calc.iterrows():
|
|
328
|
+
self.df_survey.replace("\$\{" + empty_calc["name"] + "\}", "1", regex=True)
|
|
329
|
+
|
|
330
|
+
# TODO try to reinject calc to reduce complexity
|
|
331
|
+
for i, c in self.df_calculate[
|
|
332
|
+
~self.df_calculate["name"].isin(self.df_survey["name"])
|
|
333
|
+
].iterrows():
|
|
334
|
+
real_calc = re.find(r"^number\((.+)\)$", c["calculation"])
|
|
335
|
+
if real_calc is not None and real_calc != "":
|
|
336
|
+
self.df_survey[~self.df_survey["name"] == c["name"]].replace(
|
|
337
|
+
real_calc, "\$\{" + c["name"] + "\}"
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
df_duplicate = self.df_survey[
|
|
341
|
+
self.df_survey.duplicated(subset=["name"], keep="first")
|
|
342
|
+
]
|
|
343
|
+
for index, duplicate in df_duplicate.iterrows():
|
|
344
|
+
logger.critical(f"duplicate survey name: {duplicate['name']}")
|
|
345
|
+
self.df_survey.reset_index(drop=True, inplace=True)
|
|
346
|
+
return processed_nodes
|
|
347
|
+
|
|
348
|
+
def get_tricc_operation_expression(self, operation):
|
|
349
|
+
ref_expressions = []
|
|
350
|
+
if not hasattr(operation, 'reference'):
|
|
351
|
+
return self.get_tricc_operation_operand(operation)
|
|
352
|
+
|
|
353
|
+
operator = getattr(operation, 'operator', '')
|
|
354
|
+
coalesce_fallback = OPERATOR_COALESCE_FALLBACK[operator] if operator in OPERATOR_COALESCE_FALLBACK else "''"
|
|
355
|
+
for r in operation.reference:
|
|
356
|
+
if isinstance(r, list):
|
|
357
|
+
r_expr = [
|
|
358
|
+
self.get_tricc_operation_expression(sr) if isinstance(sr, TriccOperation)
|
|
359
|
+
else self.get_tricc_operation_operand(sr,coalesce_fallback)
|
|
360
|
+
for sr in r
|
|
361
|
+
]
|
|
362
|
+
elif isinstance(r, TriccOperation):
|
|
363
|
+
r_expr = self.get_tricc_operation_expression(r)
|
|
364
|
+
else:
|
|
365
|
+
r_expr = self.get_tricc_operation_operand(r,coalesce_fallback)
|
|
366
|
+
if isinstance(r_expr, TriccReference):
|
|
367
|
+
r_expr = self.get_tricc_operation_operand(r_expr,coalesce_fallback)
|
|
368
|
+
ref_expressions.append(r_expr)
|
|
369
|
+
|
|
370
|
+
# build lower level
|
|
371
|
+
if hasattr(self,f"tricc_operation_{operation.operator}"):
|
|
372
|
+
callable = getattr(self,f"tricc_operation_{operation.operator}")
|
|
373
|
+
return callable(ref_expressions)
|
|
374
|
+
else:
|
|
375
|
+
raise NotImplementedError(f"This type of opreation '{operation.operator}' is not supported in this strategy")
|
|
376
|
+
|
|
377
|
+
def tricc_operation_multiplied(self, ref_expressions):
|
|
378
|
+
return '*'.join(ref_expressions)
|
|
379
|
+
def tricc_operation_divided(self, ref_expressions):
|
|
380
|
+
return f"{ref_expressions[0]} div {ref_expressions[1]}"
|
|
381
|
+
def tricc_operation_modulo(self, ref_expressions):
|
|
382
|
+
return f"{ref_expressions[0]} mod {ref_expressions[1]}"
|
|
383
|
+
def tricc_operation_coalesce(self, ref_expressions):
|
|
384
|
+
return f"coalesce({','.join(map(self.clean_coalesce, ref_expressions))})"
|
|
385
|
+
def tricc_operation_module(self, ref_expressions):
|
|
386
|
+
return f"{ref_expressions[0]} mod {ref_expressions[1]}"
|
|
387
|
+
def tricc_operation_minus(self, ref_expressions):
|
|
388
|
+
if len(ref_expressions)>1:
|
|
389
|
+
return ' - '.join(ref_expressions)
|
|
390
|
+
elif len(ref_expressions)==1:
|
|
391
|
+
return f'-{ref_expressions[0]}'
|
|
392
|
+
def tricc_operation_plus(self, ref_expressions):
|
|
393
|
+
return ' + '.join(ref_expressions)
|
|
394
|
+
def tricc_operation_not(self, ref_expressions):
|
|
395
|
+
return f"not({ref_expressions[0]})"
|
|
396
|
+
def tricc_operation_and(self, ref_expressions):
|
|
397
|
+
if len(ref_expressions) == 1:
|
|
398
|
+
return ref_expressions[0]
|
|
399
|
+
if len(ref_expressions)>1:
|
|
400
|
+
ref_expressions = [f"({r})" if isinstance(r, str) and any(op in r for op in [' or ',' + ',' - '])else r for r in ref_expressions]
|
|
401
|
+
return ' and '.join(map(str, ref_expressions))
|
|
402
|
+
else:
|
|
403
|
+
return '1'
|
|
404
|
+
|
|
405
|
+
def tricc_operation_or(self, ref_expressions):
|
|
406
|
+
if len(ref_expressions) == 1:
|
|
407
|
+
return ref_expressions[0]
|
|
408
|
+
if len(ref_expressions)>1:
|
|
409
|
+
ref_expressions = [f"({r})" if isinstance(r, str) and any(op in r for op in [' and ',' + ',' - ']) else r for r in ref_expressions]
|
|
410
|
+
return ' or '.join(map(str, ref_expressions))
|
|
411
|
+
else:
|
|
412
|
+
return '1'
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def tricc_operation_native(self, ref_expressions):
|
|
417
|
+
if len(ref_expressions)>0:
|
|
418
|
+
if ref_expressions[0] =='GetChoiceName':
|
|
419
|
+
return f"jr:choice-name({self.clean_coalesce(ref_expressions[1])}, '{self.clean_coalesce(ref_expressions[2])}')"
|
|
420
|
+
elif ref_expressions[0] =='GetFacilityParam':
|
|
421
|
+
return '0'
|
|
422
|
+
#return f"jr:choice-name({','.join(ref_expressions[1:])})"
|
|
423
|
+
else:
|
|
424
|
+
return f"{ref_expressions[0]}({','.join(ref_expressions[1:])})"
|
|
425
|
+
|
|
426
|
+
def tricc_operation_istrue(self, ref_expressions):
|
|
427
|
+
if str(BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]).isnumeric():
|
|
428
|
+
return f"{ref_expressions[0]}>={BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
|
|
429
|
+
else:
|
|
430
|
+
return f"{ref_expressions[0]}={BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
|
|
431
|
+
def tricc_operation_isfalse(self, ref_expressions):
|
|
432
|
+
if str(BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]).isnumeric():
|
|
433
|
+
return f"{ref_expressions[0]}={BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
|
|
434
|
+
else:
|
|
435
|
+
return f"{ref_expressions[0]}={BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
|
|
436
|
+
def tricc_operation_parenthesis(self, ref_expressions):
|
|
437
|
+
return f"({ref_expressions[0]})"
|
|
438
|
+
def tricc_operation_selected(self, ref_expressions):
|
|
439
|
+
parts = []
|
|
440
|
+
for s in ref_expressions[1:]:
|
|
441
|
+
# for option with numeric value
|
|
442
|
+
cleaned_s = s if isinstance(s, str) else '\''+str(s)+'\''
|
|
443
|
+
parts.append(f"selected({self.clean_coalesce(ref_expressions[0])}, {cleaned_s})")
|
|
444
|
+
if len(parts) == 1:
|
|
445
|
+
return parts[0]
|
|
446
|
+
else:
|
|
447
|
+
return self.tricc_operation_or(parts)
|
|
448
|
+
def tricc_operation_more_or_equal(self, ref_expressions):
|
|
449
|
+
return f"{ref_expressions[0]}>={ref_expressions[1]}"
|
|
450
|
+
def tricc_operation_less_or_equal(self, ref_expressions):
|
|
451
|
+
return f"{ref_expressions[0]}<={ref_expressions[1]}"
|
|
452
|
+
def tricc_operation_more(self, ref_expressions):
|
|
453
|
+
return f"{ref_expressions[0]}>{ref_expressions[1]}"
|
|
454
|
+
def tricc_operation_less(self, ref_expressions):
|
|
455
|
+
return f"{ref_expressions[0]}<{ref_expressions[1]}"
|
|
456
|
+
def tricc_operation_between(self, ref_expressions):
|
|
457
|
+
return f"{ref_expressions[0]}>={ref_expressions[1]} and {ref_expressions[0]} < {ref_expressions[2]}"
|
|
458
|
+
def tricc_operation_equal(self, ref_expressions):
|
|
459
|
+
return f"{ref_expressions[0]}={ref_expressions[1]}"
|
|
460
|
+
def tricc_operation_not_equal(self, ref_expressions):
|
|
461
|
+
return f"{ref_expressions[0]}!={ref_expressions[1]}"
|
|
462
|
+
def tricc_operation_isnull(self, ref_expressions):
|
|
463
|
+
return f"{ref_expressions[0]}=''"
|
|
464
|
+
def tricc_operation_isnotnull(self, ref_expressions):
|
|
465
|
+
return f"{ref_expressions[0]}!=''"
|
|
466
|
+
|
|
467
|
+
def tricc_operation_isnottrue(self, ref_expressions):
|
|
468
|
+
if str(BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]).isnumeric():
|
|
469
|
+
return f"{ref_expressions[0]}<{BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
|
|
470
|
+
else:
|
|
471
|
+
return f"{ref_expressions[0]}!={BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
|
|
472
|
+
def tricc_operation_isnotfalse(self, ref_expressions):
|
|
473
|
+
if str(BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]).isnumeric():
|
|
474
|
+
return f"{ref_expressions[0]}>{BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
|
|
475
|
+
else:
|
|
476
|
+
return f"{ref_expressions[0]}!={BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
|
|
477
|
+
def tricc_operation_notexist(self, ref_expressions):
|
|
478
|
+
return f"{ref_expressions[0]}=''"
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def tricc_operation_case(self, ref_expressions):
|
|
483
|
+
ifs = 0
|
|
484
|
+
parts = []
|
|
485
|
+
else_found = False
|
|
486
|
+
if not isinstance(ref_expressions[0], list):
|
|
487
|
+
return self.tricc_operation_ifs(ref_expressions)
|
|
488
|
+
for i in range(int(len(ref_expressions))):
|
|
489
|
+
if isinstance(ref_expressions[i], list):
|
|
490
|
+
parts.append(f"if({ref_expressions[i][0]},{ref_expressions[i][1]}")
|
|
491
|
+
ifs += 1
|
|
492
|
+
else:
|
|
493
|
+
else_found = True
|
|
494
|
+
parts.append(ref_expressions[i])
|
|
495
|
+
#join the if
|
|
496
|
+
exp = ','.join(map(str,parts))
|
|
497
|
+
# in case there is no default put ''
|
|
498
|
+
if not else_found:
|
|
499
|
+
exp += ",''"
|
|
500
|
+
#add the closing )
|
|
501
|
+
for i in range(ifs):
|
|
502
|
+
exp += ")"
|
|
503
|
+
return exp
|
|
504
|
+
|
|
505
|
+
def tricc_operation_ifs(self, ref_expressions):
|
|
506
|
+
ifs = 0
|
|
507
|
+
parts = []
|
|
508
|
+
else_found = False
|
|
509
|
+
for i in range(int(len(ref_expressions[1:]))):
|
|
510
|
+
if isinstance(ref_expressions[i+1], list):
|
|
511
|
+
parts.append(f"if({ref_expressions[0]}={ref_expressions[i+1][0]},{ref_expressions[i+1][1]}")
|
|
512
|
+
ifs += 1
|
|
513
|
+
else:
|
|
514
|
+
else_found = True
|
|
515
|
+
parts.append(ref_expressions[i+1])
|
|
516
|
+
#join the if
|
|
517
|
+
exp = ','.join(parts)
|
|
518
|
+
# in case there is no default put ''
|
|
519
|
+
if not else_found:
|
|
520
|
+
exp += ",''"
|
|
521
|
+
#add the closing )
|
|
522
|
+
for i in range(ifs):
|
|
523
|
+
exp += ")"
|
|
524
|
+
return exp
|
|
525
|
+
|
|
526
|
+
def tricc_operation_if(self, ref_expressions):
|
|
527
|
+
return f"if({ref_expressions[0]},{ref_expressions[1]},{ref_expressions[2]})"
|
|
528
|
+
|
|
529
|
+
def tricc_operation_contains(self, ref_expressions):
|
|
530
|
+
return f"contains('{self.clean_coalesce(ref_expressions[0])}', '{self.clean_coalesce(ref_expressions[1])}')"
|
|
531
|
+
|
|
532
|
+
def tricc_operation_exists(self, ref_expressions):
|
|
533
|
+
parts = []
|
|
534
|
+
for ref in ref_expressions:
|
|
535
|
+
parts.append(self.tricc_operation_not_equal([self.tricc_operation_coalesce([ref, "''"]), "''"]))
|
|
536
|
+
return self.tricc_operation_and(parts)
|
|
537
|
+
|
|
538
|
+
def tricc_operation_cast_number(self, ref_expressions):
|
|
539
|
+
if isinstance(ref_expressions[0], (int, float,)):
|
|
540
|
+
return f"{ref_expressions[0]}"
|
|
541
|
+
elif not ref_expressions or ref_expressions[0] == '':
|
|
542
|
+
logger.warning("empty cast number")
|
|
543
|
+
return '0'
|
|
544
|
+
elif ref_expressions[0] == 'true' or ref_expressions[0] is True or ref_expressions[0] == BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]:
|
|
545
|
+
return 1
|
|
546
|
+
elif ref_expressions[0] == 'false' or ref_expressions[0] is False or ref_expressions[0] == BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]:
|
|
547
|
+
return 0
|
|
548
|
+
else:
|
|
549
|
+
return f"number({ref_expressions[0]})"
|
|
550
|
+
|
|
551
|
+
def tricc_operation_cast_integer(self, ref_expressions):
|
|
552
|
+
if isinstance(ref_expressions[0], (int, float,)):
|
|
553
|
+
return f"{ref_expressions[0]}"
|
|
554
|
+
elif not ref_expressions or ref_expressions[0] == '':
|
|
555
|
+
logger.warning("empty cast number")
|
|
556
|
+
return '0'
|
|
557
|
+
elif ref_expressions[0] == 'true' or ref_expressions[0] is True or ref_expressions[0] == BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]:
|
|
558
|
+
return 1
|
|
559
|
+
elif ref_expressions[0] == 'false' or ref_expressions[0] is False or ref_expressions[0] == BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]:
|
|
560
|
+
return 0
|
|
561
|
+
else:
|
|
562
|
+
return f"int({ref_expressions[0]})"
|
|
563
|
+
def tricc_operation_zscore(self, ref_expressions):
|
|
564
|
+
y, ll, m, s = self.get_zscore_params(ref_expressions)
|
|
565
|
+
# return ((Math.pow((y / m), l) - 1) / (s * l));
|
|
566
|
+
return f"(pow({y} div ({m}), {ll}) -1) div (({s}) div ({ll}))"
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def tricc_operation_izscore(self, ref_expressions):
|
|
570
|
+
z, ll, m, s = self.get_zscore_params(ref_expressions)
|
|
571
|
+
# return (m * (z*s*l-1)^(1/l));
|
|
572
|
+
return f"pow({m} * ({z} * {s} * {ll} -1), 1 div {ll})"
|
|
573
|
+
|
|
574
|
+
def get_zscore_params(self, ref_expressions):
|
|
575
|
+
table = ref_expressions[0]
|
|
576
|
+
sex = clean_name(ref_expressions[1])
|
|
577
|
+
x = clean_name(ref_expressions[2])
|
|
578
|
+
yz = clean_name(ref_expressions[3])
|
|
579
|
+
ll = (
|
|
580
|
+
f"number(instance({table})/root/item[sex={sex} and x_max>"
|
|
581
|
+
+ x
|
|
582
|
+
+ " and x_min<="
|
|
583
|
+
+ x
|
|
584
|
+
+ "]/l)"
|
|
585
|
+
)
|
|
586
|
+
m = (
|
|
587
|
+
f"number(instance({table})/root/item[sex={sex} and x_max>"
|
|
588
|
+
+ x
|
|
589
|
+
+ " and x_min<="
|
|
590
|
+
+ x
|
|
591
|
+
+ "]/m)"
|
|
592
|
+
)
|
|
593
|
+
s = (
|
|
594
|
+
f"number(instance({table})/root/item[sex={sex} and x_max>"
|
|
595
|
+
+ x
|
|
596
|
+
+ " and x_min<="
|
|
597
|
+
+ x
|
|
598
|
+
+ "]/s)"
|
|
599
|
+
)
|
|
600
|
+
return yz, ll, m, s
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
# function update the calcualte in the XLSFORM format
|
|
605
|
+
# @param left part
|
|
606
|
+
# @param right part
|
|
607
|
+
def generate_xls_form_calculate(self, node, processed_nodes, stashed_nodes, **kwargs):
|
|
608
|
+
if is_ready_to_process(node, processed_nodes):
|
|
609
|
+
if node not in processed_nodes:
|
|
610
|
+
if kwargs.get('warn', True):
|
|
611
|
+
logger.debug("generation of calculate for node {}".format(node.get_name()))
|
|
612
|
+
if hasattr(node, 'expression') and (node.expression is None) and issubclass(node.__class__,TriccNodeCalculateBase):
|
|
613
|
+
node.expression = get_node_expressions(node, processed_nodes, process=kwargs.get('process', 'main '))
|
|
614
|
+
# continue walk
|
|
615
|
+
return True
|
|
616
|
+
return False
|
|
617
|
+
|
|
618
|
+
# function update the select node in the XLSFORM format
|
|
619
|
+
# @param left part
|
|
620
|
+
# @param right part
|
|
621
|
+
def generate_xls_form_condition(self, node, processed_nodes, stashed_nodes, calculates, **kwargs):
|
|
622
|
+
if is_ready_to_process(node, processed_nodes, strict=False) and process_reference(node, processed_nodes, calculates, replace_reference=False, codesystems= kwargs.get('codesystems', None)):
|
|
623
|
+
if node not in processed_nodes:
|
|
624
|
+
if issubclass(node.__class__, TriccRhombusMixIn) and isinstance(node.reference, str):
|
|
625
|
+
logger.warning("node {} still using the reference string".format(node.get_name()))
|
|
626
|
+
if issubclass(node.__class__, TriccNodeInputModel):
|
|
627
|
+
# we don't overright if define in the diagram
|
|
628
|
+
if node.constraint is None:
|
|
629
|
+
if isinstance(node, TriccNodeSelectMultiple):
|
|
630
|
+
node.constraint = TriccOperation(
|
|
631
|
+
TriccOperator.OR,
|
|
632
|
+
[
|
|
633
|
+
TriccOperation(TriccOperator.EQUAL, ['$this', TriccStatic('opt_none')]),
|
|
634
|
+
TriccOperation(TriccOperator.NOT, [
|
|
635
|
+
TriccOperation(TriccOperator.SELECTED,
|
|
636
|
+
[
|
|
637
|
+
'$this', TriccStatic('opt_none')
|
|
638
|
+
])
|
|
639
|
+
])
|
|
640
|
+
]
|
|
641
|
+
)#'.=\'opt_none\' or not(selected(.,\'opt_none\'))'
|
|
642
|
+
node.constraint_message = '**None** cannot be selected together with choice.'
|
|
643
|
+
elif node.tricc_type in (TriccNodeType.integer, TriccNodeType.decimal):
|
|
644
|
+
constraints = []
|
|
645
|
+
constraints_min = ''
|
|
646
|
+
constraints_max = ''
|
|
647
|
+
if node.min is not None and node.min != '':
|
|
648
|
+
constraints.append(TriccOperation(TriccOperator.MORE_OR_EQUAL, ['$this', node.min]))
|
|
649
|
+
constraints_min= "The minimun value is {0}.".format(node.min)
|
|
650
|
+
if node.max is not None and node.max != '':
|
|
651
|
+
constraints.append(TriccOperation(TriccOperator.LESS_OR_EQUAL, ['$this', node.max]))
|
|
652
|
+
constraints_max="The maximum value is {0}.".format(node.max)
|
|
653
|
+
if len(constraints) > 1:
|
|
654
|
+
node.constraint = TriccOperation(TriccOperator.AND, constraints)
|
|
655
|
+
node.constraint_message = (constraints_min + " " + constraints_max).strip()
|
|
656
|
+
elif len(constraints) == 1:
|
|
657
|
+
node.constraint = constraints[0]
|
|
658
|
+
node.constraint_message = (constraints_min + " " + constraints_max).strip()
|
|
659
|
+
# continue walk
|
|
660
|
+
return True
|
|
661
|
+
return False
|
|
662
|
+
|
|
663
|
+
# function transform an object to XLSFORM value
|
|
664
|
+
# @param r reference to be translated
|
|
665
|
+
def get_tricc_operation_operand(self,r, coalesce_fallback="''"):
|
|
666
|
+
if isinstance(r, TriccOperation):
|
|
667
|
+
return self.get_tricc_operation_expression(r)
|
|
668
|
+
elif isinstance(r, TriccReference):
|
|
669
|
+
logger.warning(f"reference still used in the calculate {r.value}")
|
|
670
|
+
return f"${{{get_export_name(r.value)}}}"
|
|
671
|
+
elif isinstance(r, TriccStatic):
|
|
672
|
+
if isinstance(r.value, bool) :#or r.value in ('true', 'false')
|
|
673
|
+
return BOOLEAN_MAP[str(TRICC_TRUE_VALUE)] if r.value else BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]
|
|
674
|
+
if r.value == TRICC_TRUE_VALUE:
|
|
675
|
+
return BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]
|
|
676
|
+
if r.value == TRICC_FALSE_VALUE:
|
|
677
|
+
return BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]
|
|
678
|
+
if isinstance(r.value, str):
|
|
679
|
+
return f"'{r.value}'"
|
|
680
|
+
else:
|
|
681
|
+
return str(r.value)
|
|
682
|
+
elif isinstance(r, str):
|
|
683
|
+
return f"{r}"
|
|
684
|
+
elif isinstance(r, (int, float)):
|
|
685
|
+
return str(r)
|
|
686
|
+
elif isinstance(r, TriccNodeSelectOption):
|
|
687
|
+
logger.warning(f"select option {r.get_name()} from {r.select.get_name()} was used as a reference")
|
|
688
|
+
return f"'{r.name}'"
|
|
689
|
+
elif issubclass(r.__class__, TriccNodeInputModel):
|
|
690
|
+
return f"coalesce(${{{get_export_name(r)}}},{coalesce_fallback})"
|
|
691
|
+
elif issubclass(r.__class__, TriccNodeBaseModel):
|
|
692
|
+
return f"${{{get_export_name(r)}}}"
|
|
693
|
+
else:
|
|
694
|
+
raise NotImplementedError(f"This type of node {r.__class__} is not supported within an operation")
|
|
695
|
+
|
|
696
|
+
def tricc_operation_concatenate(self, ref_expressions):
|
|
697
|
+
return f"concat({','.join(ref_expressions)})"
|