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.
Files changed (66) hide show
  1. tests/build.py +213 -0
  2. tests/test_cql.py +197 -0
  3. tests/to_ocl.py +51 -0
  4. {tricc → tricc_oo}/__init__.py +3 -1
  5. tricc_oo/converters/codesystem_to_ocl.py +169 -0
  6. tricc_oo/converters/cql/cqlLexer.py +822 -0
  7. tricc_oo/converters/cql/cqlListener.py +1632 -0
  8. tricc_oo/converters/cql/cqlParser.py +11204 -0
  9. tricc_oo/converters/cql/cqlVisitor.py +913 -0
  10. tricc_oo/converters/cql_to_operation.py +402 -0
  11. tricc_oo/converters/datadictionnary.py +115 -0
  12. tricc_oo/converters/drawio_type_map.py +222 -0
  13. tricc_oo/converters/tricc_to_xls_form.py +61 -0
  14. tricc_oo/converters/utils.py +65 -0
  15. tricc_oo/converters/xml_to_tricc.py +1003 -0
  16. tricc_oo/models/__init__.py +4 -0
  17. tricc_oo/models/base.py +732 -0
  18. tricc_oo/models/calculate.py +216 -0
  19. tricc_oo/models/ocl.py +281 -0
  20. tricc_oo/models/ordered_set.py +125 -0
  21. tricc_oo/models/tricc.py +418 -0
  22. tricc_oo/parsers/xml.py +138 -0
  23. tricc_oo/serializers/__init__.py +0 -0
  24. tricc_oo/serializers/xls_form.py +745 -0
  25. tricc_oo/strategies/__init__.py +0 -0
  26. tricc_oo/strategies/input/__init__.py +0 -0
  27. tricc_oo/strategies/input/base_input_strategy.py +111 -0
  28. tricc_oo/strategies/input/drawio.py +317 -0
  29. tricc_oo/strategies/output/base_output_strategy.py +148 -0
  30. tricc_oo/strategies/output/spice.py +365 -0
  31. tricc_oo/strategies/output/xls_form.py +697 -0
  32. tricc_oo/strategies/output/xlsform_cdss.py +189 -0
  33. tricc_oo/strategies/output/xlsform_cht.py +200 -0
  34. tricc_oo/strategies/output/xlsform_cht_hf.py +334 -0
  35. tricc_oo/visitors/__init__.py +0 -0
  36. tricc_oo/visitors/tricc.py +2198 -0
  37. tricc_oo/visitors/utils.py +17 -0
  38. tricc_oo/visitors/xform_pd.py +264 -0
  39. tricc_oo-1.4.15.dist-info/METADATA +219 -0
  40. tricc_oo-1.4.15.dist-info/RECORD +46 -0
  41. {tricc_oo-1.0.1.dist-info → tricc_oo-1.4.15.dist-info}/WHEEL +1 -1
  42. tricc_oo-1.4.15.dist-info/top_level.txt +2 -0
  43. tricc/converters/mc_to_tricc.py +0 -542
  44. tricc/converters/tricc_to_xls_form.py +0 -553
  45. tricc/converters/utils.py +0 -44
  46. tricc/converters/xml_to_tricc.py +0 -740
  47. tricc/models/tricc.py +0 -1093
  48. tricc/parsers/xml.py +0 -81
  49. tricc/serializers/xls_form.py +0 -364
  50. tricc/strategies/input/base_input_strategy.py +0 -80
  51. tricc/strategies/input/drawio.py +0 -246
  52. tricc/strategies/input/medalcreator.py +0 -168
  53. tricc/strategies/output/base_output_strategy.py +0 -92
  54. tricc/strategies/output/xls_form.py +0 -194
  55. tricc/strategies/output/xlsform_cdss.py +0 -46
  56. tricc/strategies/output/xlsform_cht.py +0 -106
  57. tricc/visitors/tricc.py +0 -375
  58. tricc_oo-1.0.1.dist-info/LICENSE +0 -78
  59. tricc_oo-1.0.1.dist-info/METADATA +0 -229
  60. tricc_oo-1.0.1.dist-info/RECORD +0 -26
  61. tricc_oo-1.0.1.dist-info/top_level.txt +0 -2
  62. venv/bin/vba_extract.py +0 -78
  63. {tricc → tricc_oo}/converters/__init__.py +0 -0
  64. {tricc → tricc_oo}/models/lang.py +0 -0
  65. {tricc/serializers → tricc_oo/parsers}/__init__.py +0 -0
  66. {tricc → tricc_oo}/serializers/planuml.py +0 -0
@@ -0,0 +1,2198 @@
1
+ import re
2
+ import logging
3
+
4
+ from tricc_oo.converters.utils import *
5
+ from tricc_oo.models import *
6
+ from tricc_oo.visitors.tricc import *
7
+ from tricc_oo.visitors.utils import PROCESSES
8
+ from tricc_oo.converters.datadictionnary import lookup_codesystems_code
9
+
10
+ logger = logging.getLogger("default")
11
+ ONE_QUESTION_AT_A_TIME = False
12
+
13
+ TRICC_TRUE_VALUE = 'true'
14
+ TRICC_FALSE_VALUE = 'false'
15
+
16
+ def merge_node(from_node,to_node):
17
+ if from_node.activity != to_node.activity:
18
+ logger.critical("Cannot merge nodes from different activities")
19
+ elif issubclass(from_node.__class__, TriccNodeCalculateBase) and issubclass(to_node.__class__, TriccNodeCalculateBase):
20
+ for e in to_node.activity.edges:
21
+ if e.target == from_node.id:
22
+ e.target = to_node.id
23
+ else:
24
+ logger.critical("Cannot merge not calculate nodes ")
25
+
26
+
27
+ def get_max_version(dict):
28
+ max_version = None
29
+ for id, sim_node in dict.items():
30
+ if max_version is None or max_version.version < sim_node.version :
31
+ max_version = sim_node
32
+ return max_version
33
+
34
+ def get_versions(name, iterable):
35
+ return [n for n in iterable if version_filter(name)(n)]
36
+
37
+ def version_filter(name):
38
+ return lambda item: hasattr(item, 'name') and ( (isinstance(item, TriccNodeEnd) and name == item.get_reference()) or item.name == name ) and not isinstance(item, TriccNodeSelectOption)
39
+
40
+ def get_last_version(name, processed_nodes, _list=None):
41
+ max_version = None
42
+ if isinstance(_list, dict):
43
+ _list = _list[name].values() if name in _list else []
44
+ if _list is None:
45
+ if isinstance(processed_nodes, OrderedSet):
46
+ return processed_nodes.find_last(version_filter(name))
47
+ else:
48
+ _list = get_versions(name, processed_nodes)
49
+ if _list:
50
+ for sim_node in _list:
51
+ # get the max version while not taking a node that have a next node before next calc
52
+ if ((max_version is None
53
+ or max_version.activity.path_len < sim_node.activity.path_len
54
+ or max_version.path_len < sim_node.path_len
55
+ or (max_version.path_len == sim_node.path_len and hash(max_version.id) < hash(sim_node.id))
56
+ ) ):
57
+ max_version = sim_node
58
+ if not max_version:
59
+ already_processed = list(filter(lambda p_node: hasattr(p_node, 'name') and p_node.name == name , _list))
60
+ if already_processed:
61
+ max_version = sorted(filtered, key=lambda x: x.path_len, reverse=False)[0]
62
+
63
+ return max_version
64
+
65
+
66
+ # main function to retrieve the expression from the tree
67
+ # node is the node to calculate
68
+ # processed_nodes are the list of processed nodes
69
+ def get_node_expressions(node, processed_nodes, process=None):
70
+ is_calculate = issubclass(node.__class__, TriccNodeCalculateBase)
71
+ expression = None
72
+ # in case of recursive call processed_nodes will be None
73
+ if processed_nodes is None or is_ready_to_process(node, processed_nodes=processed_nodes):
74
+ expression = get_node_expression(node, processed_nodes=processed_nodes, is_calculate=is_calculate, process=process)
75
+
76
+ if is_calculate:
77
+ if expression and (not isinstance(expression, str) or expression != '') and expression is not True :
78
+ num_expression = TriccOperation(
79
+ TriccOperator.CAST_NUMBER,
80
+ [expression]
81
+ )
82
+ elif expression is True or (not expression and is_calculate):
83
+ expression = TriccStatic(1)
84
+ else:
85
+ expression = ''
86
+ if (
87
+ issubclass(node.__class__, TriccNodeCalculateBase)
88
+ and not isinstance(expression, (TriccStatic, TriccReference, TriccOperation))
89
+ and str(expression) != ''
90
+ and not isinstance(node, (TriccNodeWait, TriccNodeActivityEnd, TriccNodeActivityStart, TriccNodeEnd))
91
+ ):
92
+ logger.warning("Calculate {0} returning no calculations".format(node.get_name()))
93
+ expression = TriccStatic(True)
94
+ return expression
95
+
96
+
97
+ def process_calculate(node,processed_nodes, stashed_nodes, calculates, used_calculates,
98
+ warn = False, process=None, **kwargs ):
99
+ # used_calculates dict[name, Dict[id, node]]
100
+ # processed_nodes Dict[id, node]
101
+ # calculates dict[name, Dict[id, node]]
102
+
103
+
104
+ if node not in processed_nodes:
105
+ # generate condition
106
+ if (
107
+ is_ready_to_process(node, processed_nodes,True)
108
+ and process_reference(
109
+ node,
110
+ processed_nodes=processed_nodes,
111
+ calculates=calculates,
112
+ used_calculates=used_calculates,
113
+ replace_reference=False,
114
+ warn = warn,
115
+ codesystems= kwargs.get('codesystems', None)
116
+ )
117
+ ):
118
+ if kwargs.get('warn', True):
119
+ logger.debug('Processing relevance for node {0}'.format(node.get_name()))
120
+ node_name = node.name if not isinstance(node, TriccNodeEnd) else node.get_reference()
121
+ last_version = get_last_version(node_name, processed_nodes) if issubclass(node.__class__, (TriccNodeDisplayModel, TriccNodeDisplayCalculateBase, TriccNodeEnd)) and not isinstance(node, TriccNodeSelectOption) else None
122
+ #last_version = processed_nodes.find_prev(node, lambda item: hasattr(item, 'name') and item.name == node.name)
123
+ if last_version:
124
+ # 0-100 for manually specified instance. 100-200 for auto instance
125
+ node.version = last_version.version + 1
126
+ last_version.last = False
127
+ node.path_len = max(node.path_len, last_version.path_len + 1)
128
+ # FIXME this is for XLS form where only calculate are evaluated for a activity that is not triggered
129
+ if not issubclass(node.__class__, (TriccNodeInputModel)):
130
+ node.last = True
131
+ if (
132
+ issubclass(node.__class__, (TriccNodeDisplayCalculateBase, TriccNodeEnd)) and node.name is not None
133
+ ):
134
+ #logger.debug("set last to false for node {} and add its link it to next one".format(last_used_calc.get_name()))
135
+ if node.prev_nodes:
136
+ set_prev_next_node(last_version, node)
137
+ else:
138
+ expression = node.expression or node.expression_reference or node.relevance
139
+ datatype = expression.get_datatype()
140
+ if datatype == 'boolean':
141
+ expression_reference = TriccOperation(
142
+ TriccOperator.OR,
143
+ [TriccOperation(TriccOperator.ISTRUE, [last_version]), expression]
144
+ )
145
+
146
+ elif datatype == 'number':
147
+ expression = TriccOperation(
148
+ TriccOperator.PLUS,
149
+ [last_version, expression]
150
+ )
151
+ else:
152
+ expression = TriccOperation(
153
+ TriccOperator.COALESCE,
154
+ [last_version, expression]
155
+ )
156
+ if node.expression:
157
+ node.expression = expression
158
+ elif node.expression_reference:
159
+ node.expression_reference = expression
160
+ elif node.relevance:
161
+ node.relevance = expression
162
+ else:
163
+ node.last = False
164
+
165
+
166
+ calc = TriccNodeCalculate(
167
+ id=generate_id(f"save{node.id}"),
168
+ name=node.name,
169
+ path_len=node.path_len+1,
170
+ version=last_version.version + 2,
171
+ expression=TriccOperation(
172
+ TriccOperator.COALESCE,
173
+ [node, last_version, TriccStatic("''")]
174
+ ),
175
+ last=True,
176
+ activity=node.activity,
177
+ group=node.group
178
+ )
179
+ node.activity.nodes[calc.id]=calc
180
+ node.activity.calculates.append(calc)
181
+ if issubclass(node.__class__, TriccNodeInputModel):
182
+ node.expression = TriccOperation(
183
+ TriccOperator.COALESCE,
184
+ [
185
+ '$this',
186
+ last_version
187
+ ]
188
+ )
189
+
190
+
191
+
192
+ # if has prev, create condition
193
+ if hasattr(node, 'relevance') and (node.relevance is None or isinstance(node.relevance, TriccOperation)):
194
+ node.relevance = get_node_expressions(node, processed_nodes=processed_nodes, process=process)
195
+ # manage not Available
196
+ if isinstance(node, TriccNodeSelectNotAvailable):
197
+ # update the checkbox
198
+ if node.parent:
199
+ if len(node.prev_nodes) == 1:
200
+ prev = list(node.prev_nodes)[0]
201
+ if isinstance(prev, TriccNodeMoreInfo) and prev.parent.name == node.name:
202
+ prev.parent = node
203
+
204
+
205
+ # managing more info on NotAvaialbee
206
+ parent_empty = TriccOperation(TriccOperator.ISNULL, [node.parent])
207
+ node.relevance = and_join([node.parent.relevance, parent_empty])
208
+ node.required = parent_empty
209
+ node.constraint = parent_empty
210
+ node.constraint_message = "Cannot be selected with a value entered above"
211
+ # update the check box parent : create loop error
212
+ node.parent.required = None # "${{{0}}}=''".format(node.name)
213
+ else:
214
+ logger.warning("not available node {} does't have a single parent".format(node.get_name()))
215
+ elif isinstance(node.relevance, TriccOperation):
216
+ relevance_reference = list(node.relevance.get_references())
217
+ for r in relevance_reference:
218
+ if issubclass(r.__class__, (TriccNodeDisplayCalculateBase )):
219
+ add_used_calculate(node, r, calculates, used_calculates, processed_nodes)
220
+
221
+ generate_calculates(node,calculates, used_calculates,processed_nodes=processed_nodes)
222
+ if last_version and hasattr(node, 'relevance'):
223
+ if isinstance(node, TriccNodeInputModel):
224
+ version_relevance = TriccOperation(
225
+ TriccOperator.ISNULL,
226
+ [last_version]
227
+ )
228
+ elif last_version.relevance:
229
+ version_relevance = not_clean(
230
+ last_version.relevance
231
+ )
232
+ elif last_version.activity.relevance:
233
+ version_relevance = not_clean(
234
+ last_version.activity.relevance,
235
+ )
236
+ else:
237
+ version_relevance = None
238
+
239
+ if version_relevance:
240
+ if getattr(node, 'relevance', None):
241
+ node.relevance = and_join(
242
+ [
243
+ version_relevance,
244
+ node.relevance
245
+ ])
246
+
247
+ elif hasattr(node, 'relevance'):
248
+ node.relevance = version_relevance
249
+
250
+ #update_calc_version(calculates,node_name)
251
+ #if hasattr(node, 'next_nodes'):
252
+ #node.next_nodes=reorder_node_list(node.next_nodes, node.group)
253
+ process_reference(
254
+ node,
255
+ processed_nodes=processed_nodes,
256
+ calculates=calculates,
257
+ used_calculates=used_calculates,
258
+ replace_reference=True,
259
+ warn = warn,
260
+ codesystems= kwargs.get('codesystems', None)
261
+ )
262
+ if isinstance(node, (TriccNodeMainStart, TriccNodeActivityStart)):
263
+ process_reference(
264
+ node.activity,
265
+ processed_nodes=processed_nodes,
266
+ calculates=calculates,
267
+ used_calculates=used_calculates,
268
+ replace_reference=True,
269
+ warn = warn,
270
+ codesystems= kwargs.get('codesystems', None)
271
+ )
272
+
273
+ return True
274
+ # not ready to process or already processed
275
+
276
+ return False
277
+
278
+ def update_calc_version(calculates,name):
279
+ if name in calculates and len(calculates[name])>1:
280
+ ordered_list = sorted(list(calculates[name].values()), key=lambda x:x.path_len)
281
+ i = 1
282
+ len_max=len(calculates[name])
283
+ for elm in ordered_list:
284
+ elm.version=i
285
+ elm.last= (i == len_max)
286
+ i+=1
287
+
288
+
289
+ def get_max_named_version(calculates,name):
290
+ max = 0
291
+ if name in calculates:
292
+ for node in calculates[name].values():
293
+ if node.version > max:
294
+ max = node.version
295
+ return max
296
+
297
+ def get_count_node(node):
298
+ count_id = generate_id(f"count{node.id}")
299
+ count_name = "cnt_"+count_id
300
+ return TriccNodeCount(
301
+ id = count_id,
302
+ group = node.group,
303
+ activity = node.activity,
304
+ label = "count: "+node.get_name(),
305
+ name = count_name,
306
+ path_len=node.path_len
307
+ )
308
+
309
+ ### Function that inject a wait after path that will wait for the nodes
310
+ def get_activity_wait(prev_nodes, nodes_to_wait, next_nodes, replaced_node = None, edge_only = False, activity = None):
311
+
312
+ if issubclass(nodes_to_wait.__class__,TriccBaseModel):
313
+ nodes_to_wait = [nodes_to_wait]
314
+ if issubclass(prev_nodes.__class__,TriccBaseModel):
315
+ prev_nodes = set([prev_nodes])
316
+ elif isinstance(prev_nodes, list):
317
+ prev_nodes = set(prev_nodes)
318
+
319
+ iterator = iter(prev_nodes)
320
+ prev_node = next(iterator)
321
+ path = prev_node if len(prev_nodes) == 1 else get_bridge_path(prev_nodes, activity)
322
+
323
+ activity = activity or prev_node.activity
324
+ calc_node = TriccNodeWait(
325
+ id = generate_id(f"ar{''.join([x.id for x in nodes_to_wait])}{activity.id}"),
326
+ reference = nodes_to_wait,
327
+ activity = activity,
328
+ group = activity,
329
+ path = path
330
+ )
331
+
332
+ #start the wait and the next_nodes from the prev_nodes
333
+ #add the wait as dependency of the next_nodes
334
+
335
+ # add edge between rhombus and node
336
+
337
+ set_prev_next_node(path,calc_node, edge_only=edge_only, activity=activity )
338
+ for next_node in next_nodes:
339
+ #if prev != replaced_node and next_node != replaced_node :
340
+ # set_prev_next_node(prev,next_node,replaced_node)
341
+ #if first:
342
+ #first = False
343
+ set_prev_next_node(calc_node,next_node, edge_only=edge_only,activity=activity)
344
+
345
+
346
+ return calc_node
347
+
348
+ def get_bridge_path(prev_nodes, node=None,edge_only=False):
349
+ iterator = iter(prev_nodes)
350
+ p_p_node = next(iterator)
351
+ if node is None:
352
+ node = p_p_node
353
+ calc_id = generate_id(f"br{''.join([x.id for x in prev_nodes])}{node.id}")
354
+ calc_name = "path_"+calc_id
355
+ data = {
356
+ 'id': calc_id,
357
+ 'group': node.group,
358
+ 'activity': node.activity,
359
+ 'label': "path: " + ( node.get_name()),
360
+ 'name': calc_name,
361
+ 'path_len': node.path_len + 1 * (node == p_p_node)
362
+ }
363
+
364
+ if sum([0 if issubclass(n.__class__, (TriccNodeDisplayCalculateBase, TriccNodeRhombus)) else 1 for n in prev_nodes])>0 : #and len(node.prev_nodes)>1:
365
+ calc= TriccNodeDisplayBridge( **data)
366
+ else:
367
+ calc = TriccNodeBridge( **data)
368
+ return calc
369
+
370
+ def inject_bridge_path(node, nodes):
371
+
372
+ prev_nodes = [nodes[n.source] for n in list(filter(lambda x: (x.target == node.id or x.target == node) and x.source in list(nodes.keys()), node.activity.edges))]
373
+ if prev_nodes:
374
+ calc = get_bridge_path(prev_nodes, node,edge_only=True)
375
+
376
+ for e in node.activity.edges:
377
+ if e.target == node.id:
378
+ # if e.source in node.activity.nodes and len(node.activity.nodes[e.source].next_nodes):
379
+ # set_prev_next_node(node.activity[e.source], node, edge_only=True, replaced_node=node)
380
+ # else:
381
+ e.target = calc.id
382
+
383
+ # add edge between bridge and node
384
+ set_prev_next_node(calc,node,edge_only=True, activity=node.activity)
385
+ node.path_len += 1
386
+ return calc
387
+
388
+
389
+ def inject_node_before(before, node, activity):
390
+ before.group = activity
391
+ before.activity = activity
392
+ activity.nodes[before.id] = before
393
+ nodes = activity.nodes
394
+ prev_nodes = node.prev_nodes.union(set(nodes[n.source] for n in list(filter(lambda x: (x.target == node.id or x.target == node) and x.source in nodes, node.activity.edges))))
395
+ edge_processed = False
396
+ before.path_len = node.path_len
397
+ for e in node.activity.edges:
398
+ if e.target == node.id:
399
+ e.target = before.id
400
+ for p in prev_nodes:
401
+ prev_processed = len(node.next_nodes) > 0
402
+ if node in p.next_nodes:
403
+ p.next_nodes.remove(node)
404
+ p.next_nodes.append(before)
405
+
406
+ # add edge between bridge and node
407
+ set_prev_next_node(before,node,edge_only=not edge_processed, activity=node.activity)
408
+ node.path_len += 1
409
+
410
+
411
+
412
+ def generate_calculates(node,calculates, used_calculates,processed_nodes):
413
+ list_calc = []
414
+ count_node = None
415
+ ## add select calcualte
416
+ if issubclass(node.__class__, TriccNodeCalculateBase):
417
+ if isinstance(node, TriccNodeRhombus):
418
+ if (
419
+ (node.expression_reference is None or isinstance(node.expression_reference, TriccOperation))
420
+ and isinstance(node.reference, list)
421
+ and len(node.reference)==1
422
+ and issubclass(node.reference[0].__class__, TriccNodeSelect)
423
+ ):
424
+
425
+ count_node = get_count_node(node)
426
+ list_calc.append(count_node)
427
+ set_prev_next_node(node.reference[0],count_node)
428
+ node.path_len+=1
429
+
430
+ if isinstance(node.expression_reference, TriccOperation):
431
+ node.expression_reference.replace_node(node.reference, count_node)
432
+ node.reference[0] = count_node
433
+ # elif isinstance(node.reference, TriccOperation):
434
+ # references = node.reference.get_references()
435
+ # if len(references) == 1 and issubclass(node.reference[0].__class__, TriccNodeSelect):
436
+ # count_node = get_count_node(node)
437
+ # list_calc.append(count_node)
438
+ # set_prev_next_node(references[0],count_node)
439
+ # node.path_len+=1
440
+ # node.reference.replace_node(references[0], count_node)
441
+ if count_node:
442
+ processed_nodes.add(count_node)
443
+ add_calculate(calculates, count_node)
444
+ add_used_calculate(
445
+ node,
446
+ count_node,
447
+ calculates=calculates,
448
+ used_calculates=used_calculates,
449
+ processed_nodes=processed_nodes
450
+ )
451
+
452
+
453
+ # if a prev node is a calculate then it must be added in used_calc
454
+ for prev in node.prev_nodes:
455
+ add_used_calculate(
456
+ node,
457
+ prev,
458
+ calculates=calculates,
459
+ used_calculates=used_calculates,
460
+ processed_nodes=processed_nodes
461
+ )
462
+ #if the node have a save
463
+ if hasattr(node, 'save') and node.save is not None and node.save != '':
464
+ # get fragments type.name.icdcode
465
+ calculate_name=node.save
466
+ if not isinstance(node, TriccNodeSelectYesNo) and issubclass(node.__class__, (TriccNodeSelect)):
467
+ calc_node = get_count_node(node)
468
+ calc_node.path_len += 1
469
+ calc_node.name=calculate_name
470
+ calc_node.label = "save select: " +node.get_name()
471
+ else:
472
+ calc_id = generate_id(f"autosave{node.id}")
473
+ calc_node = TriccNodeCalculate(
474
+ name=calculate_name,
475
+ id = calc_id,
476
+ group = node.group,
477
+ activity = node.activity,
478
+ label = "save: " +node.get_name(),
479
+ path_len=node.path_len+ 1
480
+ )
481
+ logger.debug("generate_save_calculate:{}:{} as {}".format(calc_node.tricc_type, node.name if hasattr(node,'name') else node.id, calculate_name))
482
+ if isinstance(node, TriccNodeSelectYesNo):
483
+ yesNode = node.options[0]
484
+ set_prev_next_node(yesNode,calc_node)
485
+ else:
486
+ set_prev_next_node(node,calc_node)
487
+ list_calc.append(calc_node)
488
+ #add_save_calculate(calc_node, calculates, used_calculates,processed_nodes)
489
+ for calc in list_calc:
490
+ node.activity.nodes[calc.id] = calc
491
+ return list_calc
492
+
493
+
494
+
495
+ def add_calculate(calculates, calc_node):
496
+ if issubclass(calc_node.__class__, TriccNodeDisplayCalculateBase):
497
+ if calc_node.name not in calculates:
498
+ calculates[calc_node.name]= {}
499
+ calculates[calc_node.name][calc_node.id] = calc_node
500
+
501
+ def get_option_code_from_label(node, option_label):
502
+ if hasattr(node, 'options'):
503
+ for i in node.options:
504
+ if node.options[i].label.strip() == option_label.strip():
505
+ return node.options[i].name
506
+ logger.critical(f"option with label {option_label} not found in {node.get_name()}")
507
+ else:
508
+ logger.critical(f"node {node.get_name()} has no options")
509
+
510
+
511
+ def process_reference(node, processed_nodes, calculates, used_calculates=None, replace_reference=False,warn=False, codesystems=None):
512
+ if getattr(node, 'expression_reference', None):
513
+ modified_expression = process_operation_reference(
514
+ node.expression_reference,
515
+ node,
516
+ processed_nodes=processed_nodes,
517
+ calculates=calculates,
518
+ used_calculates=used_calculates,
519
+ replace_reference=replace_reference,
520
+ warn=warn,
521
+ codesystems=codesystems
522
+ )
523
+ if modified_expression is False:
524
+ return False
525
+ elif modified_expression and replace_reference:
526
+ node.reference = list(modified_expression.get_references())
527
+ node.expression_reference = modified_expression
528
+ elif getattr(node, 'reference', None):
529
+ if isinstance(node.reference, list):
530
+ if len(node.reference) == 1 :
531
+ operation = node.reference[0]
532
+ else:
533
+ operation = and_join(
534
+ node.reference
535
+ )
536
+ modified_expression = process_operation_reference(
537
+ operation,
538
+ node,
539
+ processed_nodes=processed_nodes,
540
+ calculates=calculates,
541
+ used_calculates=used_calculates,
542
+ replace_reference=replace_reference,
543
+ warn=warn,
544
+ codesystems=codesystems
545
+ )
546
+ if modified_expression is False:
547
+ return False
548
+ elif modified_expression:
549
+ node.reference = list(modified_expression.get_references())
550
+ if not isinstance(node, TriccNodeWait):
551
+ node.expression_reference = modified_expression
552
+ elif isinstance(node.reference, (TriccOperation, TriccReference)):
553
+ modified_expression = process_operation_reference(
554
+ node.reference,
555
+ node,
556
+ processed_nodes=processed_nodes,
557
+ calculates=calculates,
558
+ used_calculates=used_calculates,
559
+ replace_reference=replace_reference,
560
+ warn=warn,
561
+ codesystems=codesystems
562
+ )
563
+ if modified_expression is False:
564
+ return False
565
+ elif modified_expression and replace_reference:
566
+ node.reference = list(modified_expression.get_references())
567
+ node.expression_reference = modified_expression
568
+
569
+ if isinstance(getattr(node, 'relevance', None), (TriccOperation, TriccReference)):
570
+ modified_expression = process_operation_reference(
571
+ node.relevance,
572
+ node,
573
+ processed_nodes=processed_nodes,
574
+ calculates=calculates,
575
+ used_calculates=used_calculates,
576
+ replace_reference=replace_reference,
577
+ warn=warn,
578
+ codesystems=codesystems
579
+ )
580
+ if modified_expression is False:
581
+ return False
582
+ elif modified_expression and replace_reference:
583
+ node.relevance = modified_expression
584
+
585
+ if isinstance(getattr(node, 'default', None), (TriccOperation, TriccReference)):
586
+ modified_expression = process_operation_reference(
587
+ node.default,
588
+ node,
589
+ processed_nodes=processed_nodes,
590
+ calculates=calculates,
591
+ used_calculates=used_calculates,
592
+ replace_reference=replace_reference,
593
+ warn=warn,
594
+ codesystems=codesystems
595
+ )
596
+ if modified_expression is False:
597
+ return False
598
+ elif modified_expression and replace_reference:
599
+ node.relevance = modified_expression
600
+
601
+ if isinstance(getattr(node, 'expression', None), (TriccOperation, TriccReference)):
602
+ modified_expression = process_operation_reference(
603
+ node.expression,
604
+ node,
605
+ processed_nodes=processed_nodes,
606
+ calculates=calculates,
607
+ used_calculates=used_calculates,
608
+ replace_reference=replace_reference,
609
+ warn=warn,
610
+ codesystems=codesystems
611
+ )
612
+ if modified_expression is False:
613
+ return False
614
+ elif modified_expression and replace_reference:
615
+ node.expression = modified_expression
616
+
617
+ if isinstance(getattr(node, 'applicability', None), (TriccOperation, TriccReference)):
618
+ modified_expression = process_operation_reference(
619
+ node.applicability,
620
+ node,
621
+ processed_nodes=processed_nodes,
622
+ calculates=calculates,
623
+ used_calculates=used_calculates,
624
+ replace_reference=replace_reference,
625
+ warn=warn,
626
+ codesystems=codesystems
627
+ )
628
+ if modified_expression is False:
629
+ return False
630
+ elif modified_expression and replace_reference:
631
+ node.applicability = modified_expression
632
+ return True
633
+
634
+ def process_operation_reference(operation, node, processed_nodes, calculates, used_calculates=None, replace_reference=False,warn=False, codesystems=None):
635
+ modified_operation = None
636
+ node_reference = []
637
+ reference = []
638
+ option_label = None
639
+ ref_list = [r.value for r in operation.get_references() if isinstance(r, TriccReference)]
640
+ real_ref_list = [r for r in operation.get_references() if issubclass(r.__class__, TriccNodeBaseModel)]
641
+ for ref in ref_list:
642
+ if ref.endswith(']'):
643
+ terms = ref[:-1].split('[')
644
+ option_label = terms[1]
645
+ ref = terms[0]
646
+ else:
647
+ option_label = None
648
+ node_in_act = [n for n in node.activity.nodes.values() if n.name == ref and n != node]
649
+ if node_in_act:
650
+ if any(n not in processed_nodes for n in node_in_act):
651
+ return False
652
+ else:
653
+ last_found = node_in_act[0]
654
+ else:
655
+ last_found = get_last_version(name=ref, processed_nodes=processed_nodes)
656
+ if last_found is None:
657
+ if codesystems:
658
+ concept = lookup_codesystems_code(codesystems, ref)
659
+ if not concept:
660
+ logger.critical(f"reference {ref} not found in the project")
661
+ exit(1)
662
+ else:
663
+ if warn:
664
+ logger.debug(f"reference {ref}::{concept.display} not yet processed {node.get_name()}")
665
+
666
+ elif warn:
667
+ logger.debug(f"reference {ref} not found for a calculate {node.get_name()}")
668
+ return False
669
+ else:
670
+ node_reference.append(last_found)
671
+ reference.append(TriccReference(ref))
672
+ if replace_reference:
673
+ if isinstance(operation, (TriccOperation)):
674
+ if modified_operation is None:
675
+ modified_operation = operation.copy(keep_node=True)
676
+ modified_operation.replace_node(TriccReference(ref), last_found)
677
+ elif operation == TriccReference(ref):
678
+ modified_operation = last_found
679
+ if option_label:
680
+ # Resolve human-readable label
681
+ option_code = get_option_code_from_label(last_found, option_label)
682
+ if option_code:
683
+ modified_operation = replace_code_reference(operation, old=f"{ref}[{option_label}]", new=option_code )
684
+ else:
685
+ if warn:
686
+ logger.warning(f"Could not resolve label '{option_label}' for reference {ref}")
687
+ return False
688
+
689
+
690
+ node.path_len = max(node.path_len, last_found.path_len)
691
+ for ref in real_ref_list:
692
+ if is_prev_processed(ref, node, processed_nodes=processed_nodes, local=False) is False:
693
+ return False
694
+
695
+ if used_calculates is not None:
696
+ for ref_nodes in node_reference:
697
+ if issubclass(ref_nodes.__class__, TriccNodeCalculateBase):
698
+ add_used_calculate(node, ref_nodes, calculates, used_calculates, processed_nodes=processed_nodes)
699
+ return modified_operation
700
+
701
+ def replace_code_reference(expression, old, new):
702
+ if isinstance(expression, str):
703
+ return expression_reference.replace(old, f"'{new}'")
704
+ if isinstance(expression, TriccOperation):
705
+ expression.replace_node(TriccReference(old), TriccStatic(new))
706
+ return expression
707
+ #add_used_calculate(node, calc_node, calculates, used_calculates, processed_nodes)
708
+
709
+ def add_used_calculate(node, prev_node, calculates, used_calculates, processed_nodes):
710
+ if issubclass(prev_node.__class__, TriccNodeDisplayCalculateBase):
711
+ if prev_node in processed_nodes:
712
+ # if not a verison, index will equal -1
713
+ if prev_node.name not in calculates :
714
+ logger.debug("node {} refered before being processed".format(node.get_name()))
715
+ return False
716
+ max_version = prev_node#get_max_version(calculates[node_clean_name])
717
+ if prev_node.name not in used_calculates:
718
+ used_calculates[prev_node.name] = {}
719
+ #save the max version only once
720
+ if max_version.id not in used_calculates[prev_node.name]:
721
+ used_calculates[prev_node.name][max_version.id] = max_version
722
+ else:
723
+ logger.debug("process_calculate_version_requirement: failed for {0} , prev Node {1} ".format(node.get_name(), prev_node.get_name()))
724
+
725
+
726
+ def get_select_not_available_options(node,group,label):
727
+ return {0:TriccNodeSelectOption(
728
+ id = generate_id(f"notavaialble{node.id}"),
729
+ name="1",
730
+ label=label,
731
+ select = node,
732
+ group = group,
733
+ list_name = node.list_name
734
+ )}
735
+
736
+ def get_select_yes_no_options(node, group):
737
+ yes = TriccNodeSelectOption(
738
+ id = generate_id(f'yes{node.id}'),
739
+ name=f"{TRICC_TRUE_VALUE}",
740
+ label="Yes",
741
+ select = node,
742
+ group = group,
743
+ list_name = node.list_name
744
+ )
745
+ no = TriccNodeSelectOption(
746
+ id = generate_id(f'no{node.id}'),
747
+ name=f"{TRICC_FALSE_VALUE}",
748
+ label="No",
749
+ select = node,
750
+ group = group,
751
+ list_name = node.list_name
752
+ )
753
+ return {0:yes, 1:no }
754
+
755
+ # walkthough all node in an iterative way, the same node might be parsed 2 times
756
+ # therefore to avoid double processing the nodes variable saves the node already processed
757
+ # there 2 strategies : process it the first time or the last time (wait that all the previuous node are processed)
758
+
759
+ def walktrhough_tricc_node_processed_stached(node, callback, processed_nodes, stashed_nodes, path_len, recursive=False, warn = False,
760
+ node_path = [], process=None, **kwargs):
761
+ ended_activity = False
762
+ # logger.debug("walkthrough::{}::{}".format(callback.__name__, node.get_name()))
763
+
764
+ path_len = max(node.activity.path_len, *[0,*[getattr(n,'path_len',0) + 1 for n in node.activity.prev_nodes]]) + 1
765
+ if hasattr(node, 'prev_nodes'):
766
+ path_len = max(path_len, *[0,*[getattr(n,'path_len',0)+ 1 for n in node.prev_nodes]])
767
+ if hasattr(node, 'get_references'):
768
+ references = node.get_references()
769
+ if references:
770
+ path_len = max(path_len, *[0,*[getattr(n,'path_len',0) + 1 for n in references]])
771
+ node.path_len = max(node.path_len, path_len)
772
+ if (
773
+ callback(
774
+ node,
775
+ processed_nodes=processed_nodes,
776
+ stashed_nodes=stashed_nodes,
777
+ warn = warn,
778
+ node_path=node_path,
779
+ process=process,
780
+ **kwargs
781
+ )
782
+ ):
783
+ node_path.append(node)
784
+ # node processing succeed
785
+ if not isinstance(node, TriccNodeActivity) and node not in processed_nodes:
786
+ processed_nodes.add(node)
787
+ if warn:
788
+ logger.debug("{}::{}: processed ({})".format(callback.__name__, node.get_name(), len(processed_nodes)))
789
+ if isinstance(node, (TriccNodeEnd, TriccNodeActivityEnd)) and node.activity not in processed_nodes:
790
+ end_nodes = node.activity.get_end_nodes()
791
+ if all([e in processed_nodes for e in end_nodes]):
792
+ processed_nodes.add(node.activity)
793
+ ended_activity = True
794
+ if warn:
795
+ logger.debug("{}::{}: processed ({})".format(callback.__name__, node.activity.get_name(), len(processed_nodes)))
796
+ elif node in stashed_nodes:
797
+ stashed_nodes.remove(node)
798
+ # logger.debug("{}::{}: unstashed ({})".format(callback.__name__, node.get_name(), len(stashed_nodes)))
799
+ # put the stached node from that group first
800
+ # if has next, walkthrough them (support options)
801
+ # if len(stashed_nodes)>1:
802
+ if not recursive:
803
+ reorder_node_list(stashed_nodes, node.group, processed_nodes)
804
+ if isinstance(node, (TriccNodeActivityStart, TriccNodeMainStart)):
805
+ if getattr(node, 'process', None):
806
+ if process is None:
807
+ process = [node.process]
808
+ else:
809
+ process[0] = node.process
810
+ if recursive:
811
+ for gp in node.activity.groups.values():
812
+ walktrhough_tricc_node_processed_stached(
813
+ gp,
814
+ callback,
815
+ processed_nodes=processed_nodes,
816
+ stashed_nodes=stashed_nodes,
817
+ path_len=path_len,
818
+ recursive=recursive,
819
+ warn = warn,
820
+ node_path = node_path.copy(),
821
+ **kwargs
822
+ )
823
+ for c in node.activity.calculates:
824
+ if len(c.prev_nodes)== 0:
825
+ walktrhough_tricc_node_processed_stached(
826
+ c,
827
+ callback,
828
+ processed_nodes=processed_nodes,
829
+ stashed_nodes=stashed_nodes,
830
+ path_len=path_len,
831
+ recursive=recursive,
832
+ warn = warn,
833
+ node_path = node_path.copy(),
834
+ **kwargs
835
+ )
836
+ else:
837
+ stashed_nodes += [c for c in node.activity.calculates if len(c.prev_nodes)== 0]
838
+ stashed_nodes += node.activity.groups.values()
839
+ elif issubclass(node.__class__, TriccNodeSelect):
840
+ for option in node.options.values():
841
+ option.path_len = max(path_len, option.path_len)
842
+ callback(option, processed_nodes=processed_nodes, stashed_nodes=stashed_nodes, warn = warn, node_path=node_path,**kwargs)
843
+ if option not in processed_nodes:
844
+ processed_nodes.add(option)
845
+ if warn:
846
+ logger.debug(
847
+ "{}::{}: processed ({})".format(callback.__name__, option.get_name(), len(processed_nodes)))
848
+ walkthrough_tricc_option(node, callback, processed_nodes, stashed_nodes, path_len + 1, recursive,
849
+ warn = warn,node_path = node_path, **kwargs)
850
+ if isinstance(node, TriccNodeActivity):
851
+ if node.root not in processed_nodes:
852
+ if node.root is not None:
853
+ node.root.path_len = max(path_len, node.root.path_len)
854
+ if getattr(node.root, 'process', None):
855
+ if process is None:
856
+ process = [node.root.process]
857
+ else:
858
+ process[0] = node.root.process
859
+ if recursive:
860
+ walktrhough_tricc_node_processed_stached(node.root, callback, processed_nodes, stashed_nodes, path_len,
861
+ recursive, warn = warn,node_path = node_path.copy(),**kwargs)
862
+ # for gp in node.groups:
863
+ # walktrhough_tricc_node_processed_stached(gp, callback, processed_nodes, stashed_nodes, path_len,
864
+ # recursive, warn = warn,**kwargs)
865
+ # if node.calculates:
866
+ # for c in node.calculates:
867
+ # walktrhough_tricc_node_processed_stached(c, callback, processed_nodes, stashed_nodes, path_len,
868
+ # recursive, warn = warn,**kwargs)
869
+ elif node.root not in stashed_nodes:
870
+ #stashed_nodes.insert(0,node.root)
871
+ stashed_nodes.insert_at_top(node.root)
872
+ # if node.calculates:
873
+ # stashed_nodes += node.calculates
874
+ # for gp in node.groups:
875
+ # stashed_nodes.add(gp)
876
+ # # stashed_nodes.insert(0,gp)
877
+ return
878
+ elif ended_activity:
879
+ for next_node in node.next_nodes:
880
+ if next_node not in stashed_nodes:
881
+ #stashed_nodes.insert(0,next_node)
882
+ if recursive:
883
+ walktrhough_tricc_node_processed_stached(next_node, callback, processed_nodes, stashed_nodes, path_len,
884
+ recursive, warn = warn,node_path = node_path.copy(),**kwargs)
885
+ else:
886
+ stashed_nodes.insert_at_top(next_node)
887
+
888
+
889
+ elif hasattr(node, 'next_nodes') and len(node.next_nodes) > 0 and not isinstance(node, TriccNodeActivity):
890
+ if recursive:
891
+ walkthrough_tricc_next_nodes(node, callback, processed_nodes, stashed_nodes, path_len + 1, recursive,
892
+ warn = warn,node_path = node_path,**kwargs)
893
+ else:
894
+ for nn in node.next_nodes:
895
+ if nn not in stashed_nodes:
896
+ stashed_nodes.insert_at_top(nn)
897
+
898
+
899
+ else:
900
+ if node not in processed_nodes and node not in stashed_nodes:
901
+ if node not in stashed_nodes:
902
+ stashed_nodes.insert_at_bottom(node)
903
+ if warn:
904
+ logger.debug("{}::{}: stashed({})".format(callback.__name__, node.get_name(), len(stashed_nodes)))
905
+
906
+
907
+ def walkthrough_tricc_next_nodes(node, callback, processed_nodes, stashed_nodes, path_len, recursive, warn = False, node_path = [], **kwargs):
908
+
909
+ if not recursive:
910
+ for next_node in node.next_nodes:
911
+ if next_node not in stashed_nodes:
912
+ stashed_nodes.insert_at_top(next_node)
913
+ else:
914
+ list_next = set(node.next_nodes)
915
+ for next_node in list_next:
916
+ if not isinstance(node, (TriccNodeActivityEnd, TriccNodeEnd)):
917
+ if next_node not in processed_nodes:
918
+ walktrhough_tricc_node_processed_stached(next_node, callback, processed_nodes, stashed_nodes,
919
+ path_len + 1,recursive, warn = warn,node_path = node_path.copy(), **kwargs)
920
+ else:
921
+ logger.critical(
922
+ "{}::end node of {} has a next node".format(callback.__name__, node.activity.get_name()))
923
+ exit(1)
924
+
925
+
926
+ def walkthrough_tricc_option(node, callback, processed_nodes, stashed_nodes, path_len, recursive, warn = False,node_path = [], **kwargs):
927
+ if not recursive:
928
+ for option in node.options.values():
929
+ if hasattr(option, 'next_nodes') and len(option.next_nodes) > 0:
930
+ for next_node in option.next_nodes:
931
+ if next_node not in stashed_nodes:
932
+ stashed_nodes.insert_at_top(next_node)
933
+ #stashed_nodes.insert(0,next_node)
934
+ else:
935
+ list_option = []
936
+ while not all(elem in list_option for elem in list(node.options.values())):
937
+ for option in node.options.values():
938
+ if option not in list_option:
939
+ list_option.append(option)
940
+ # then walk the options
941
+ if hasattr(option, 'next_nodes') and len(option.next_nodes) > 0:
942
+ list_next = set(option.next_nodes)
943
+ for next_node in list_next:
944
+ if next_node not in processed_nodes:
945
+ walktrhough_tricc_node_processed_stached(next_node, callback, processed_nodes,
946
+ stashed_nodes, path_len + 1, recursive,
947
+ warn = warn,
948
+ node_path = node_path.copy(), **kwargs)
949
+
950
+
951
+ def get_data_for_log(node):
952
+ return "{}:{}|{} {}:{}".format(
953
+ node.group.get_name() if node.group is not None else node.activity.get_name(),
954
+ node.group.instance if node.group is not None else node.activity.instance ,
955
+ node.__class__,
956
+ node.get_name(),
957
+ node.instance)
958
+
959
+ def stashed_node_func(node, callback, recursive=False, **kwargs):
960
+ processed_nodes = kwargs.pop('processed_nodes', OrderedSet())
961
+ stashed_nodes = kwargs.pop('stashed_nodes', OrderedSet())
962
+ process = kwargs.pop('process', ['main'])
963
+ path_len = 0
964
+
965
+ walktrhough_tricc_node_processed_stached(
966
+ node,
967
+ callback,
968
+ processed_nodes,
969
+ stashed_nodes,
970
+ path_len,
971
+ recursive,
972
+ process=process,
973
+ **kwargs)
974
+ # callback( node, **kwargs)
975
+ ## MANAGE STASHED NODES
976
+ prev_stashed_nodes = stashed_nodes.copy()
977
+ loop_count = 0
978
+ len_prev_processed_nodes = 0
979
+ while len(stashed_nodes) > 0:
980
+ loop_count = check_stashed_loop(stashed_nodes, prev_stashed_nodes, processed_nodes, len_prev_processed_nodes,
981
+ loop_count)
982
+ prev_stashed_nodes = stashed_nodes.copy()
983
+ len_prev_processed_nodes = len(processed_nodes)
984
+ if len(stashed_nodes) > 0:
985
+ s_node = stashed_nodes.pop()
986
+ # remove duplicates
987
+ if s_node in stashed_nodes:
988
+ stashed_nodes.remove(s_node)
989
+ if kwargs.get('warn', True):
990
+ logger.debug("{}:: {}: unstashed for processing ({})".format(callback.__name__, s_node.__class__,
991
+ get_data_for_log(s_node),
992
+ len(stashed_nodes)))
993
+ warn = loop_count >= (9 * len(stashed_nodes )+1)
994
+ walktrhough_tricc_node_processed_stached(
995
+ s_node,
996
+ callback,
997
+ processed_nodes,
998
+ stashed_nodes,
999
+ path_len,
1000
+ recursive,
1001
+ warn=warn,
1002
+ process=process,
1003
+ **kwargs)
1004
+
1005
+
1006
+ # check if the all the prev nodes are processed
1007
+ def is_ready_to_process(in_node, processed_nodes, strict=True, local=False):
1008
+ if isinstance(in_node, TriccNodeSelectOption):
1009
+ node = in_node.select
1010
+ elif (
1011
+ isinstance(in_node, (TriccNodeActivityStart, TriccNodeMainStart)) ):
1012
+ # check before
1013
+ return True
1014
+ else:
1015
+ node = in_node
1016
+ if hasattr(node, 'prev_nodes'):
1017
+ # ensure the previous node of the select are processed, not the option prev nodes
1018
+ for prev_node in node.prev_nodes:
1019
+ if is_prev_processed(prev_node, node, processed_nodes, local) is False:
1020
+ return False
1021
+ return True
1022
+
1023
+ def is_prev_processed(prev_node, node, processed_nodes, local):
1024
+ if hasattr(prev_node, 'select'):
1025
+ return is_prev_processed(prev_node.select, node, processed_nodes, local)
1026
+ if prev_node not in processed_nodes and (not local):
1027
+ if isinstance(prev_node, TriccNodeExclusive):
1028
+ iterator = iter(prev_node.prev_nodes)
1029
+ p_n_node = next(iterator)
1030
+ logger.debug("is_ready_to_process:failed:via_excl: {} - {} > {} {}:{}".format(
1031
+ get_data_for_log(p_n_node),
1032
+ prev_node.get_name(),
1033
+ node.__class__, node.get_name(), node.instance))
1034
+
1035
+ else:
1036
+ logger.debug("is_ready_to_process:failed: {} -> {} {}:{}".format(
1037
+ get_data_for_log(prev_node),
1038
+ node.__class__, node.get_name(), node.instance))
1039
+
1040
+ logger.debug("prev node node {}:{} for node {} not in processed".format(prev_node.__class__,
1041
+ prev_node.get_name(),
1042
+ node.get_name()))
1043
+ return False
1044
+ return True
1045
+
1046
+
1047
+
1048
+ def print_trace(node, prev_node, processed_nodes, stashed_nodes, history = []):
1049
+
1050
+ if node != prev_node:
1051
+ if node in processed_nodes:
1052
+ logger.warning("print trace :: node {} was the last not processed ({})".format(
1053
+ get_data_for_log(prev_node), node.id, ">".join(history)))
1054
+ #processed_nodes.add(prev_node)
1055
+ return False
1056
+ elif node in history:
1057
+ logger.critical("print trace :: CYCLE node {} found in history ({})".format(
1058
+ get_data_for_log(prev_node), ">".join(history)))
1059
+ exit(1)
1060
+ elif node in stashed_nodes:
1061
+ # logger.debug("print trace :: node {}::{} in stashed".format(node.__class__,node.get_name()))
1062
+ return False
1063
+ # else:
1064
+ # logger.debug("print trace :: node {} not processed/stashed".format(node.get_name()))
1065
+ return True
1066
+
1067
+
1068
+ def reverse_walkthrough(in_node, next_node, callback, processed_nodes, stashed_nodes, history = []):
1069
+ # transform dead-end nodes
1070
+ if next_node == in_node and next_node not in stashed_nodes:
1071
+ # workaround fir loop
1072
+ return False
1073
+
1074
+
1075
+ if isinstance(in_node, TriccNodeSelectOption):
1076
+ node = in_node.select
1077
+ elif isinstance(in_node, TriccNodeActivityStart):
1078
+ node = in_node.activity
1079
+ else:
1080
+ node = in_node
1081
+ if callback(node, next_node, processed_nodes=processed_nodes, stashed_nodes=stashed_nodes):
1082
+ history.append(node)
1083
+ if isinstance(in_node, TriccNodeActivity):
1084
+ prev_nodes = set(in_node.get_end_nodes())
1085
+ for prev in prev_nodes:
1086
+ reverse_walkthrough(prev, next_node, callback, processed_nodes=processed_nodes, stashed_nodes=stashed_nodes, history=history)
1087
+ if hasattr(node, 'prev_nodes'):
1088
+ if node.prev_nodes:
1089
+ for prev in node.prev_nodes:
1090
+ reverse_walkthrough(prev, node, callback, processed_nodes=processed_nodes, stashed_nodes=stashed_nodes, history=history)
1091
+ elif node in node.activity.calculates:
1092
+ reverse_walkthrough(prev, node.activity.root, callback, processed_nodes=processed_nodes, stashed_nodes=stashed_nodes, history=history)
1093
+
1094
+ if issubclass(node.__class__, TriccRhombusMixIn):
1095
+ if isinstance(node.reference, list):
1096
+ for ref in node.reference:
1097
+ reverse_walkthrough(ref, node, callback, processed_nodes=processed_nodes, stashed_nodes=stashed_nodes, history= history)
1098
+
1099
+
1100
+
1101
+
1102
+ def get_prev_node_by_name(processed_nodes, name, node):
1103
+ # look for the node in the same activity
1104
+ last_calc = get_last_version(
1105
+ name,
1106
+ processed_nodes
1107
+ )
1108
+ if last_calc:
1109
+ return last_calc
1110
+
1111
+ filtered = list(
1112
+ filter(lambda p_node:
1113
+ hasattr(p_node,'name')
1114
+ and p_node.name == name
1115
+ and p_node.instance == node.instance
1116
+ and p_node.path_len <= node.path_len, processed_nodes
1117
+ ))
1118
+ if len(filtered) == 0:
1119
+ filtered = list(filter(lambda p_node: hasattr(p_node, 'name') and p_node.name == name , processed_nodes))
1120
+ if len(filtered) > 0:
1121
+ return sorted(filtered, key=lambda x: x.path_len, reverse=False)[0]
1122
+
1123
+ MIN_LOOP_COUNT = 10
1124
+
1125
+ def check_stashed_loop(stashed_nodes, prev_stashed_nodes, processed_nodes, len_prev_processed_nodes, loop_count):
1126
+ loop_out = {}
1127
+
1128
+ if len(stashed_nodes) == len(prev_stashed_nodes):
1129
+ # to avoid checking the details
1130
+ if loop_count<=0:
1131
+ if loop_count < -MIN_LOOP_COUNT:
1132
+ loop_count = MIN_LOOP_COUNT+1
1133
+ else:
1134
+ loop_count -= 1
1135
+ if loop_count > MIN_LOOP_COUNT:
1136
+ if set(stashed_nodes) == set(prev_stashed_nodes) and len(processed_nodes) == len_prev_processed_nodes:
1137
+ loop_count += 1
1138
+ if loop_count > max(MIN_LOOP_COUNT, 11 * len(prev_stashed_nodes) + 1):
1139
+ logger.critical("Stashed node list was unchanged: loop likely or unresolved dependence")
1140
+ waited, looped = get_all_dependant(stashed_nodes, stashed_nodes, processed_nodes)
1141
+ logger.debug(f"{len(looped)} nodes waiting stashed nodes")
1142
+ logger.info("unresolved reference")
1143
+ for es_node in [n for n in stashed_nodes if isinstance(n, TriccReference)]:
1144
+ logger.info("Stashed node {}:{}|{} {}".format(
1145
+ es_node.activity.get_name() if hasattr(es_node,'activity') else '' ,
1146
+ es_node.activity.instance if hasattr(es_node,'activity') else '',
1147
+ es_node.__class__,
1148
+ es_node.get_name()))
1149
+ logger.info("looped nodes")
1150
+ for dep_list in looped:
1151
+ for d in looped[dep_list]:
1152
+ if d.get_name() == dep_list:
1153
+ logger.critical("[{}] depends on itself".format(
1154
+ dep_list,
1155
+ ))
1156
+ logger.error("[{}] depends on [{}]".format(
1157
+ dep_list, str(d)
1158
+ ))
1159
+ if dep_list in waited:
1160
+ for d in waited[dep_list]:
1161
+ logger.warning("[{}] depends on [{}]".format(
1162
+ dep_list, str(d)
1163
+ ))
1164
+
1165
+ #reverse_walkthrough(es_node, es_node, print_trace, processed_nodes, stashed_nodes)
1166
+ logger.info("waited nodes")
1167
+ for dep_list in waited:
1168
+ if dep_list not in looped:
1169
+ for d in waited[dep_list]:
1170
+ logger.warning("[{}] depends on [{}]".format(
1171
+ dep_list, d.get_name()
1172
+ ))
1173
+
1174
+ if len(stashed_nodes) == len(prev_stashed_nodes):
1175
+ exit(1)
1176
+ else:
1177
+ loop_count = 0
1178
+ else:
1179
+ loop_count = 0
1180
+ return loop_count
1181
+
1182
+
1183
+ def add_to_tree(tree, n, d):
1184
+ n_str = str(n)
1185
+ if n_str not in tree:
1186
+ tree[n_str] = []
1187
+ if d not in tree[n_str]:
1188
+ tree[n_str].append(d)
1189
+ return tree
1190
+
1191
+
1192
+ def get_all_dependant(loop, stashed_nodes, processed_nodes, depth=0, waited=None , looped=None):
1193
+ if looped is None:
1194
+ looped = {}
1195
+ if waited is None:
1196
+ waited = {}
1197
+ for n in loop:
1198
+ dependant = OrderedSet()
1199
+ i=0
1200
+ #logger.critical(f"{i}: {n.__class__}::{n.get_name()}::{getattr(n,'instance','')}::{process_reference(n, processed_nodes, [])}")
1201
+ i += 1
1202
+ if hasattr(n, 'prev_nodes') and n.prev_nodes:
1203
+ dependant = dependant | n.prev_nodes
1204
+ if hasattr(n, 'get_references'):
1205
+ dependant = dependant | (n.get_references() or OrderedSet())
1206
+ if not isinstance(dependant, list):
1207
+ pass
1208
+ for d in dependant:
1209
+ if isinstance(d, TriccNodeSelectOption):
1210
+ d = d.select
1211
+ if d not in waited and d not in looped:
1212
+ if isinstance(d, TriccReference):
1213
+ if not any(n.name == d.value for n in processed_nodes):
1214
+ if not any(n.name == d.value for n in stashed_nodes):
1215
+ waited = add_to_tree(waited, n, d)
1216
+ else :
1217
+ looped = add_to_tree(looped, n, d)
1218
+
1219
+ elif d not in processed_nodes:
1220
+ if d in stashed_nodes:
1221
+ looped = add_to_tree(looped, n, d)
1222
+ else :
1223
+ waited = add_to_tree(waited, n, d)
1224
+ if depth < MAX_DRILL:
1225
+ return get_all_dependant(waited, stashed_nodes, processed_nodes, depth+1, waited , looped)
1226
+
1227
+ return waited, looped
1228
+
1229
+
1230
+ MAX_DRILL = 1
1231
+
1232
+ def get_last_end_node(processed_nodes, process=None):
1233
+ end_name = 'tricc_end_'
1234
+ if process:
1235
+ end_name += process
1236
+ return get_last_version(end_name, processed_nodes)
1237
+
1238
+ # Set the source next node to target and clean next nodes of replace node
1239
+ def set_prev_next_node(source_node, target_node, replaced_node=None, edge_only = False, activity=None):
1240
+ activity = activity or source_node.activity
1241
+ source_id, source_node = get_node_from_id(activity, source_node, edge_only)
1242
+ target_id, target_node = get_node_from_id(activity, target_node, edge_only)
1243
+ # if it is end node, attached it to the activity/page
1244
+ if not edge_only:
1245
+ set_prev_node(source_node, target_node, replaced_node, edge_only)
1246
+ set_next_node(source_node, target_node, replaced_node, edge_only)
1247
+
1248
+ if activity and not any([(e.source == source_id) and ( e.target == target_id) for e in activity.edges]):
1249
+ label = "continue" if issubclass(source_node.__class__, TriccNodeSelectYesNo) else None
1250
+ activity.edges.append(TriccEdge(id = generate_id(), source = source_id, target = target_id, value = label))
1251
+
1252
+ def remove_prev_next(prev_node, next_node, activity=None):
1253
+ activity = activity or prev_node.activity
1254
+ if hasattr(prev_node, 'next_nodes') and next_node in prev_node.next_nodes:
1255
+ prev_node.next_nodes.remove(next_node)
1256
+ if hasattr(next_node, 'prev_nodes') and prev_node in next_node.prev_nodes:
1257
+ next_node.prev_nodes.remove(prev_node)
1258
+
1259
+ for e in list(activity.edges):
1260
+ if (e.target == getattr(next_node, 'id', next_node) and e.source == getattr(prev_node, 'id', prev_node)):
1261
+ activity.edges.remove(e)
1262
+
1263
+
1264
+
1265
+ def set_next_node(source_node, target_node, replaced_node=None, edge_only = False, activity=None):
1266
+ activity = activity or source_node.activity
1267
+ replace_target = None
1268
+ if not edge_only:
1269
+ if replaced_node is not None and hasattr(source_node, 'path') and replaced_node == source_node.path:
1270
+ source_node.path = target_node
1271
+ elif replaced_node is not None and hasattr(source_node, 'next_nodes') and replaced_node in source_node.next_nodes:
1272
+ replace_target = True
1273
+ source_node.next_nodes.remove(replaced_node)
1274
+ if hasattr(replaced_node, 'prev_nodes') and source_node in replaced_node.prev_nodes:
1275
+ replaced_node.prev_nodes.remove(source_node)
1276
+ #if replaced_node is not None and hasattr(target_node, 'next_nodes') and replaced_node in target_node.next_nodes:
1277
+ # target_node.next_nodes.remove(replaced_node)
1278
+ if target_node not in source_node.next_nodes:
1279
+ source_node.next_nodes.add(target_node)
1280
+ # if rhombus in next_node of prev node and next node as ref
1281
+ if replaced_node is not None:
1282
+ rhombus_list = list(filter(lambda x: issubclass(x.__class__, TriccRhombusMixIn), source_node.next_nodes))
1283
+ for rhm in rhombus_list:
1284
+ if isinstance(rhm.reference, list):
1285
+ if replaced_node in rhm.reference:
1286
+ rhm.reference.remove(replaced_node)
1287
+ rhm.reference.append(target_node)
1288
+ if target_node.id not in activity.nodes:
1289
+ activity.nodes[target_node.id] = target_node
1290
+ if replaced_node and replaced_node in replaced_node.activity.calculates:
1291
+ replaced_node.activity.calculates.remove(replaced_node)
1292
+ if replaced_node and replace_target:
1293
+ if replaced_node.id in replaced_node.activity.nodes:
1294
+ del replaced_node.activity.nodes[replaced_node.id]
1295
+ next_edges = set([
1296
+ e for e in replaced_node.activity.edges if (e.target == replaced_node.id or e.target == replaced_node)
1297
+ ] + [
1298
+ e for e in activity.edges if (e.target == replaced_node.id or e.target == replaced_node)
1299
+ ])
1300
+ if len(next_edges)==0:
1301
+ for e in next_edges:
1302
+ e.target = target_node.id
1303
+
1304
+
1305
+
1306
+ # Set the target_node prev node to source and clean prev nodes of replace_node
1307
+ def set_prev_node(source_node, target_node, replaced_node=None, edge_only = False, activity=None):
1308
+ activity = activity or source_node.activity
1309
+ replace_source = False
1310
+ # update the prev node of the target not if not an end node
1311
+ # update directly the prev node of the target
1312
+ if replaced_node is not None and hasattr(target_node, 'path') and replaced_node == target_node.path:
1313
+ target_node.path = source_node
1314
+ if replaced_node is not None and hasattr(target_node, 'prev_nodes') and replaced_node in target_node.prev_nodes:
1315
+ replace_source = True
1316
+ target_node.prev_nodes.remove(replaced_node)
1317
+ if hasattr(replaced_node, 'next_nodes') and source_node in replaced_node.next_nodes:
1318
+ replaced_node.next_nodes.remove(source_node)
1319
+ #if replaced_node is not None and hasattr(source_node, 'prev_nodes') and replaced_node in source_node.prev_nodes:
1320
+ # source_node.prev_nodes.remove(replaced_node)
1321
+ if source_node not in target_node.prev_nodes:
1322
+ target_node.prev_nodes.add(source_node)
1323
+ if source_node.id not in activity.nodes:
1324
+ activity.nodes[source_node.id] = source_node
1325
+ if replaced_node and replace_source:
1326
+ if replaced_node.id in replaced_node.activity.nodes:
1327
+ del replaced_node.activity.nodes[replaced_node.id]
1328
+ next_edges = set([
1329
+ e for e in replaced_node.activity.edges if (e.source == replaced_node.id or e.source == replaced_node)
1330
+ ] + [
1331
+ e for e in activity.edges if (e.source == replaced_node.id or e.source == replaced_node)
1332
+ ])
1333
+ if len(next_edges)==0:
1334
+ for e in next_edges:
1335
+ e.target = target_node.id
1336
+
1337
+
1338
+ def replace_node(old, new, page = None):
1339
+ if page is None:
1340
+ page = old.activity
1341
+ logger.debug("replacing node {} with node {} from page {}".format(old.get_name(), new.get_name(), page.get_name()))
1342
+ # list_node used to avoid updating a list in the loop
1343
+ list_nodes = []
1344
+ for prev_node in old.prev_nodes:
1345
+ list_nodes.append(prev_node)
1346
+ for prev_node in list_nodes:
1347
+ set_prev_next_node(prev_node, new, old)
1348
+ old.prev_nodes = set()
1349
+ list_nodes = []
1350
+ for next_node in old.next_nodes:
1351
+ list_nodes.append(next_node)
1352
+ for next_node in list_nodes:
1353
+ set_prev_next_node(new, next_node, old)
1354
+ old.next_nodes = set()
1355
+ if old in page.nodes:
1356
+ del page.nodes[old.id]
1357
+ page.nodes[new.id] = new
1358
+
1359
+ for edge in page.edges:
1360
+ if edge.source == old.id:
1361
+ edge.source = new.id
1362
+ if edge.target == old.id:
1363
+ edge.target = new.id
1364
+
1365
+ def replace_prev_next_node(prev_node, next_node, old_node, force = False):
1366
+ replace_prev_node(prev_node, next_node, old_node)
1367
+ replace_next_node(prev_node, next_node, old_node)
1368
+
1369
+ def replace_prev_node(prev_node, next_node, old_node, force = False):
1370
+ #create a copy pf the list
1371
+ list_nodes = list(next_node.prev_nodes)
1372
+ # replace in case old node is found
1373
+ for p_n_node in list_nodes:
1374
+ if p_n_node == old_node or force:
1375
+ set_prev_next_node(prev_node, next_node, old_node)
1376
+
1377
+
1378
+ def replace_next_node(prev_node,next_node,old_node):
1379
+ list_nodes = list(prev_node.next_nodes)
1380
+ for n_p_node in list_nodes:
1381
+ if n_p_node == old_node :
1382
+ set_prev_next_node(prev_node, next_node, old_node)
1383
+
1384
+ def reorder_node_list(list_node, group, processed_nodes):
1385
+ active_activities = set(n.activity for n in processed_nodes)
1386
+
1387
+ # Define a lambda to assign numeric priorities
1388
+ def filter_logic(l_node):
1389
+
1390
+ if (
1391
+ isinstance(l_node, TriccNodeWait)
1392
+ and any(isinstance(rn, TriccNodeActivity) and any(sn.activity == rn for sn in list_node) for rn in l_node.reference)
1393
+ ):
1394
+ return 7
1395
+ elif group is not None and hasattr(l_node, 'group') and l_node.group and l_node.group.id == group.id:
1396
+ return 0 # Highest priority: Same group
1397
+ elif issubclass(l_node.__class__, TriccRhombusMixIn) :
1398
+ return 6
1399
+ elif hasattr(group, 'group') and group.group and l_node.group and l_node.group.id == group.group.id:
1400
+ return 1 # Second priority: Parent group
1401
+ elif not isinstance(l_node.activity.root, TriccNodeActivityStart) and l_node.activity in active_activities:
1402
+ return 2 # Third priority: Active activities
1403
+ elif not isinstance(l_node.activity.root, TriccNodeActivityStart):
1404
+ return 3 # Third priority: Active activities
1405
+ elif l_node.activity in active_activities:
1406
+ return 4 # Third priority: Active activities
1407
+
1408
+
1409
+ else:
1410
+ return 5 # Lowest priority: Others
1411
+
1412
+ # Sort list_node in place using filter_logic as the key
1413
+ list_node.sort(key=filter_logic, reverse=False)
1414
+ return None
1415
+
1416
+ def loop_info(loop, **kwargs):
1417
+ logger.critical("dependency details")
1418
+ for n in loop:
1419
+ i=0
1420
+ logger.critical(f"{i}: {n.__class__}::{n.get_name()}")
1421
+ i += 1
1422
+
1423
+
1424
+ def has_loop(node, processed_nodes, stashed_nodes, warn , node_path=[], action_on_loop=loop_info,action_on_other=None, **kwargs):
1425
+ next_nodes = get_extended_next_nodes(node)
1426
+ for next_node in next_nodes:
1427
+ if next_node in node_path:
1428
+ loop_start_key = node_path.index(next_node)
1429
+ loop = node_path[loop_start_key:]
1430
+ loop.append(node)
1431
+ loop.append(next_node)
1432
+ action_on_loop(loop, **kwargs)
1433
+ return False
1434
+ if callable(action_on_other):
1435
+ action_on_other(next_node, **kwargs)
1436
+ return True
1437
+
1438
+
1439
+
1440
+ def get_extended_next_nodes(node):
1441
+
1442
+ nodes = node.next_nodes if hasattr(node,'next_nodes') else set()
1443
+ if issubclass(node.__class__, TriccNodeSelect ):
1444
+ for o in node.options.values():
1445
+ nodes = nodes | o.next_nodes
1446
+ if isinstance(node, ( TriccNodeActivity) ):
1447
+ nodes = nodes | node.root.next_nodes
1448
+ return nodes
1449
+
1450
+
1451
+ # calculate or retrieve a node expression
1452
+ def get_node_expression( in_node, processed_nodes, is_calculate=False, is_prev=False, negate=False, process=None):
1453
+ # in case of calculate we only use the select multiple if none is not selected
1454
+ expression = None
1455
+ negate_expression = None
1456
+ node = in_node
1457
+ if isinstance(node, (TriccNodeActivityStart,TriccNodeMainStart, TriccNodeActivityEnd)):
1458
+ if is_prev and is_calculate:
1459
+ expression = get_node_expression(
1460
+ node.activity,
1461
+ processed_nodes=processed_nodes,
1462
+ is_calculate=is_calculate,
1463
+ is_prev=is_prev,
1464
+ negate=negate,
1465
+ process=process
1466
+ )
1467
+ elif isinstance(node, (TriccNodeActivityStart)):
1468
+ return None
1469
+
1470
+ elif isinstance(node, TriccNodeWait):
1471
+ if is_prev:
1472
+ # the wait don't do any calculation with the reference it is only use to wait until the reference are valid
1473
+ return get_node_expression(
1474
+ node.path,
1475
+ processed_nodes=processed_nodes,
1476
+ is_calculate=is_calculate,
1477
+ is_prev=True,
1478
+ process=process
1479
+ )
1480
+ else:
1481
+ #it is a empty calculate
1482
+ return None
1483
+ elif isinstance(node, TriccNodeRhombus):
1484
+ # if is_prev:
1485
+ # expression = TriccOperation(
1486
+ # TriccOperator.ISTRUE,
1487
+ # [node]
1488
+ # )
1489
+ # else:
1490
+ expression = get_rhombus_terms(node, processed_nodes, process=process) # if issubclass(node.__class__, TricNodeDisplayCalulate) else TRICC_CALC_EXPRESSION.format(get_export_name(node)) #
1491
+ negate_expression = not_clean(expression)
1492
+ if node.path is None :
1493
+ if len(node.prev_nodes) == 1:
1494
+ node.path = list(node.prev_nodes)[0]
1495
+ elif len(node.prev_nodes) > 1:
1496
+ logger.critical(f"missing path for Rhombus {node.get_name()}")
1497
+ exit(1)
1498
+ prev_exp = get_node_expression(
1499
+ node.path,
1500
+ processed_nodes=processed_nodes,
1501
+ is_calculate=is_calculate,
1502
+ is_prev=True,
1503
+ process=process)
1504
+ if prev_exp and expression:
1505
+ expression = and_join([prev_exp, expression])
1506
+ negate_expression = and_join([
1507
+ prev_exp,
1508
+ negate_expression
1509
+ ])
1510
+
1511
+ elif prev_exp:
1512
+
1513
+ logger.error(f"useless rhombus {node.get_name()}")
1514
+ expression = prev_exp
1515
+ negate_expression = prev_exp
1516
+ logger.critical(f"Rhombus without expression {node.get_name()}")
1517
+ elif is_prev and issubclass(node.__class__, TriccNodeDisplayCalculateBase):
1518
+ expression = TriccOperation(TriccOperator.ISTRUE, [node])
1519
+ elif hasattr(node, 'expression_reference') and isinstance(node.expression_reference, TriccOperation):
1520
+ # if issubclass(node.__class__, TriccNodeDisplayCalculateBase):
1521
+ # expression = TriccOperation(
1522
+ # TriccOperator.CAST_NUMBER,
1523
+ # [node.expression_reference])
1524
+ # else:
1525
+ expression = node.expression_reference
1526
+ elif not is_prev and hasattr(node, 'relevance') and isinstance(node.relevance, TriccOperation):
1527
+ expression = node.relevance
1528
+ elif is_prev and isinstance(node, TriccNodeSelectOption):
1529
+ if negate:
1530
+ negate_expression = get_selected_option_expression(node, negate)
1531
+ else:
1532
+ expression = get_selected_option_expression(node, negate)
1533
+ #TODO remove that and manage it on the "Save" part
1534
+ elif is_prev and isinstance(node, TriccNodeSelectNotAvailable):
1535
+ expression = TriccOperation(
1536
+ TriccOperator.SELECTED,
1537
+ [
1538
+ node,
1539
+ TriccStatic(1)
1540
+ ]
1541
+ )
1542
+ elif issubclass(node.__class__, TriccNodeCalculateBase):
1543
+ if negate:
1544
+ negate_expression = get_calculation_terms(node, processed_nodes=processed_nodes, is_calculate=is_calculate, negate=True, process=process)
1545
+ else:
1546
+ expression = get_calculation_terms(node, processed_nodes=processed_nodes, is_calculate=is_calculate, process=process)
1547
+ elif ONE_QUESTION_AT_A_TIME and is_prev and not is_calculate and hasattr(node, 'required') and node.required:
1548
+ expression = get_required_node_expression(node)
1549
+
1550
+ if expression is None:
1551
+ expression = get_prev_node_expression(node, processed_nodes=processed_nodes, is_calculate=is_calculate, process=process)
1552
+ # in_node not in processed_nodes is need for calculates that can but run after the end of the activity
1553
+
1554
+
1555
+ if isinstance(node, TriccNodeActivity):
1556
+
1557
+
1558
+ if node.base_instance is not None and not is_prev:
1559
+ activity = node
1560
+ expression_inputs = []
1561
+ past_instances = [
1562
+ n for n in processed_nodes if getattr(n.base_instance, 'id', None) == node.base_instance.id
1563
+ ]
1564
+ for past_instance in past_instances:
1565
+ add_sub_expression(
1566
+ expression_inputs,
1567
+ get_node_expression(
1568
+ past_instance,
1569
+ processed_nodes=processed_nodes,
1570
+ is_calculate=False,
1571
+ is_prev=True,
1572
+ process=process
1573
+ )
1574
+ )
1575
+
1576
+ if isinstance(node.applicability,(TriccStatic,TriccOperation, TriccReference)):
1577
+ if expression:
1578
+ expression = and_join([node.applicability, expression])
1579
+ else:
1580
+ expression = node.applicability
1581
+ if expression and expression_inputs:
1582
+ add_sub_expression(expression_inputs, expression)
1583
+ expression = nand_join(expression, or_join(expression_inputs))
1584
+ elif expression_inputs:
1585
+ expression = negate_term(or_join(expression_inputs))
1586
+ if not is_prev:
1587
+ end_expressions = []
1588
+ f_end_expression = get_end_expression(processed_nodes)
1589
+ if f_end_expression:
1590
+ end_expressions.append(f_end_expression)
1591
+ if process[0] in PROCESSES:
1592
+ for p in PROCESSES[PROCESSES.index(process[0])+1:]:
1593
+ p_end_expression = get_end_expression(processed_nodes, p)
1594
+ if p_end_expression:
1595
+ end_expressions.append(p_end_expression)
1596
+ if node.applicability:
1597
+ end_expressions.append(node.applicability)
1598
+ if end_expressions:
1599
+ if expression:
1600
+ end_expressions.append(expression)
1601
+ if len(end_expressions) == 1:
1602
+ expression = end_expressions[0]
1603
+ else:
1604
+ expression = and_join(end_expressions)
1605
+
1606
+
1607
+
1608
+ if negate:
1609
+ if negate_expression is not None:
1610
+ return negate_expression
1611
+ elif expression is not None:
1612
+ return negate_term(expression)
1613
+ else:
1614
+ logger.critical("exclusive can not negate None from {}".format(node.get_name()))
1615
+ # exit(1)
1616
+ else:
1617
+ return expression
1618
+
1619
+ def get_end_expression(processed_nodes, process=None):
1620
+ end_node = get_last_end_node(processed_nodes, process)
1621
+ if end_node:
1622
+ return TriccOperation(
1623
+ TriccOperator.ISNOTTRUE,
1624
+ [end_node]
1625
+ )
1626
+
1627
+
1628
+
1629
+
1630
+ def export_proposed_diags(activity, diags=None, **kwargs):
1631
+ if diags is None:
1632
+ diags = []
1633
+ for node in activity.nodes.values():
1634
+ if isinstance(node, TriccNodeActivity):
1635
+ diags = export_proposed_diags(node, diags, **kwargs)
1636
+ if isinstance(node, TriccNodeProposedDiagnosis):
1637
+ if node.last is not False\
1638
+ and not any([diag.name == node.name for diag in diags]):
1639
+ diags.append(node)
1640
+ return diags
1641
+
1642
+
1643
+ def get_accept_diagnostic_node(code, display, severity, activity):
1644
+ node = TriccNodeAcceptDiagnostic(
1645
+ id=generate_id("pre_final." + code),
1646
+ name="pre_final." + code,
1647
+ label=display,
1648
+ list_name="acc_rej",
1649
+ activity=activity,
1650
+ group=activity,
1651
+ severity=severity
1652
+ )
1653
+ node.options = get_select_accept_reject_options(node, node.activity)
1654
+ return node
1655
+
1656
+ def get_diagnostic_node(code, display, severity, activity):
1657
+ node = TriccNodeAcceptDiagnostic(
1658
+ id=generate_id("final." + code),
1659
+ name="final." + code,
1660
+ label=display,
1661
+ list_name="acc_rej",
1662
+ activity=activity,
1663
+ group=activity,
1664
+ severity=severity
1665
+ )
1666
+ node.options = get_select_accept_reject_options(node, node.activity)
1667
+ return node
1668
+
1669
+ def get_select_accept_reject_options(node, group):
1670
+ yes = TriccNodeSelectOption(
1671
+ id = generate_id(f'accept{node.id}'),
1672
+ name=f"{TRICC_TRUE_VALUE}",
1673
+ label="Accept",
1674
+ select = node,
1675
+ group = group,
1676
+ list_name = node.list_name
1677
+ )
1678
+ no = TriccNodeSelectOption(
1679
+ id = generate_id(f'reject{node.id}'),
1680
+ name=f"{TRICC_FALSE_VALUE}",
1681
+ label="Reject",
1682
+ select = node,
1683
+ group = group,
1684
+ list_name = node.list_name
1685
+ )
1686
+ return {0:yes, 1:no }
1687
+
1688
+ def create_determine_diagnosis_activity(diags):
1689
+ start = TriccNodeActivityStart(
1690
+ id=generate_id('start.determine-diagnosis'),
1691
+ name="start.determine-diagnosis"
1692
+ )
1693
+
1694
+
1695
+ activity = TriccNodeActivity(
1696
+ id=generate_id('activity-determine-diagnosis'),
1697
+ name='determine-diagnosis',
1698
+ label='Diagnosis',
1699
+ root=start,
1700
+ )
1701
+
1702
+
1703
+ start.activity = activity
1704
+ start.group = activity
1705
+ diags_conf = []
1706
+ r_diags_conf = []
1707
+ end = TriccNodeActivityEnd(
1708
+ id=generate_id("end.determine-diagnosis"),
1709
+ name="end.determine-diagnosis",
1710
+ activity=activity,
1711
+ group=activity,
1712
+ )
1713
+ activity.nodes[end.id]=end
1714
+ for proposed in diags:
1715
+ d = get_diagnostic_node(proposed.name, proposed.label, proposed.severity, activity)
1716
+ diags_conf.append(d)
1717
+ r = TriccNodeRhombus(
1718
+ id=generate_id(f"proposed-rhombus{proposed.id}"),
1719
+ expression_reference=TriccOperation(
1720
+ TriccOperator.ISTRUE,
1721
+ [TriccReference(proposed.name)]
1722
+ ),
1723
+ reference=[TriccReference(proposed.name)],
1724
+ activity=activity,
1725
+ group=activity )
1726
+ r_diags_conf.append(r)
1727
+ set_prev_next_node(start, r, edge_only=False)
1728
+ set_prev_next_node(r, d, edge_only=False)
1729
+ set_prev_next_node(d, end, edge_only=False)
1730
+ activity.nodes[d.options[0].id] = d.options[0]
1731
+ activity.nodes[d.options[1].id] = d.options[1]
1732
+ activity.nodes[d.id]=d
1733
+ activity.nodes[r.id]=r
1734
+ # fallback
1735
+ f = TriccNodeSelectMultiple(
1736
+ name="tricc.manual.diag",
1737
+ label="Add a diagnostic",
1738
+ list_name='manual_diag',
1739
+ id=generate_id("tricc.manual.diag"),
1740
+ activity=activity,
1741
+ group=activity,
1742
+ required=TriccStatic(False),
1743
+
1744
+ )
1745
+ options = [
1746
+ TriccNodeSelectOption(
1747
+ id=generate_id(d.name),
1748
+ name=d.name,
1749
+ label=d.label,
1750
+ list_name=f.list_name,
1751
+ relevance=d.activity.applicability,
1752
+ select=f
1753
+ ) for d in diags
1754
+ ]
1755
+ f.options=dict(zip(range(0, len(options)), options))
1756
+ wait2 = get_activity_wait([activity.root], diags_conf, [f], edge_only=False)
1757
+ activity.nodes[wait2.id]=wait2
1758
+ activity.nodes[f.id]=f
1759
+
1760
+
1761
+ return activity
1762
+
1763
+ def get_prev_node_expression( node, processed_nodes, is_calculate=False, excluded_name=None, process=None):
1764
+ expression = None
1765
+ if node is None:
1766
+ pass
1767
+ # when getting the prev node, we calculate the
1768
+ if hasattr(node, 'expression_inputs') and len(node.expression_inputs) > 0:
1769
+ expression_inputs = node.expression_inputs
1770
+ expression_inputs = clean_or_list(expression_inputs)
1771
+ else:
1772
+ expression_inputs = []
1773
+ for prev_node in node.prev_nodes:
1774
+ if excluded_name is None or prev_node != excluded_name or (
1775
+ isinstance(excluded_name, str) and hasattr(prev_node, 'name') and prev_node.name != excluded_name): # or isinstance(prev_node, TriccNodeActivityEnd):
1776
+ # the rhombus should calculate only reference
1777
+ add_sub_expression(expression_inputs, get_node_expression(
1778
+ prev_node,
1779
+ processed_nodes=processed_nodes,
1780
+ is_calculate=is_calculate,
1781
+ is_prev=True,
1782
+ process=process))
1783
+ # avoid void is there is not conditions to avoid looping too much itme
1784
+ # expression_inputs = clean_or_list(
1785
+ # [
1786
+ # get_tricc_operation_operand(e)
1787
+ # if isinstance(expression, TriccOperation)
1788
+ # else e
1789
+ # for e in expression_inputs])
1790
+
1791
+ expression = None
1792
+ if len(expression_inputs) == 1:
1793
+ expression = expression_inputs[0]
1794
+
1795
+ elif expression_inputs:
1796
+ expression = TriccOperation(
1797
+ TriccOperator.OR,
1798
+ expression_inputs
1799
+ )
1800
+ # if isinstance(node, TriccNodeExclusive):
1801
+ # expression = TRICC_NEGATE.format(expression)
1802
+ # only used for activityStart
1803
+ else:
1804
+ expression = TriccStatic(True)
1805
+ return expression
1806
+
1807
+ def get_activity_end_terms( node, processed_nodes, process=None):
1808
+ end_nodes = node.get_end_nodes()
1809
+ expression_inputs = []
1810
+ for end_node in end_nodes:
1811
+ add_sub_expression(
1812
+ expression_inputs,
1813
+ get_node_expression(
1814
+ end_node,
1815
+ processed_nodes=processed_nodes,
1816
+ is_calculate=False,
1817
+ is_prev=True,
1818
+ process=process))
1819
+
1820
+ return or_join(expression_inputs)
1821
+
1822
+ def get_count_terms( node, processed_nodes, is_calculate, negate=False, process=None):
1823
+ terms = []
1824
+ for prev_node in node.prev_nodes:
1825
+ operation_none = TriccOperation(
1826
+ TriccOperator.SELECTED,
1827
+ [
1828
+ prev_node,
1829
+ TriccStatic('opt_none')
1830
+ ]
1831
+ )
1832
+ if isinstance(prev_node, TriccNodeSelectMultiple):
1833
+ if negate:
1834
+ terms.append()
1835
+ #terms.append(TRICC_SELECT_MULTIPLE_CALC_NONE_EXPRESSION.format(get_export_name(prev_node)))
1836
+ else:
1837
+ terms.append(TriccOperation(
1838
+ TriccOperator.MINUS,
1839
+ [
1840
+ TriccOperation(
1841
+ TriccOperator.NATIVE,
1842
+ [
1843
+ 'count-selected',
1844
+ prev_node
1845
+ ]
1846
+ ),TriccOperation(
1847
+ TriccOperator.CAST_NUMBER,
1848
+ [
1849
+ operation_none
1850
+ ]
1851
+ )
1852
+ ]))
1853
+ #terms.append(TRICC_SELECT_MULTIPLE_CALC_EXPRESSION.format(get_export_name(prev_node)))
1854
+ elif isinstance(prev_node, (TriccNodeSelectYesNo, TriccNodeSelectNotAvailable)):
1855
+ terms.append(TriccOperation(
1856
+ TriccOperator.SELECTED,
1857
+ [
1858
+ prev_node,
1859
+ TriccStatic('1')
1860
+ ]
1861
+ ))
1862
+ #terms.append(TRICC_SELECTED_EXPRESSION.format(get_export_name(prev_node), '1'))
1863
+ elif isinstance(prev_node, TriccNodeSelectOption):
1864
+ terms.append(get_selected_option_expression(prev_node, negate))
1865
+ else:
1866
+ if negate:
1867
+ terms.append(
1868
+ TriccOperation(
1869
+ TriccOperator.CAST_NUMBER,
1870
+ [
1871
+ TriccOperation(
1872
+ TriccOperator.NATIVE,
1873
+ [
1874
+ TriccOperation(
1875
+ TriccOperator.CAST_NUMBER,
1876
+ [
1877
+ get_node_expression(
1878
+ prev_node,
1879
+ processed_nodes=processed_nodes,
1880
+ is_calculate=False,
1881
+ is_prev=True,
1882
+ process=process)
1883
+ ]),
1884
+ TriccStatic('0')
1885
+ ]
1886
+ )
1887
+ ]
1888
+ )
1889
+ )
1890
+ else:
1891
+ terms.append(
1892
+ TriccOperation(
1893
+ TriccOperator.CAST_NUMBER,
1894
+ [
1895
+ get_node_expression(
1896
+ prev_node,
1897
+ processed_nodes=processed_nodes,
1898
+ is_calculate=False,
1899
+ is_prev=True,
1900
+ process=process)
1901
+ ]
1902
+ ))
1903
+ if len(terms) == 1:
1904
+ return TriccOperation(
1905
+ TriccOperator.CAST_NUMBER,
1906
+ [terms[0]]
1907
+ )
1908
+ elif len(terms) > 0:
1909
+ return TriccOperation(
1910
+ TriccOperator.PLUS,
1911
+ [
1912
+ TriccOperation(
1913
+ TriccOperator.CAST_NUMBER,
1914
+ [term]
1915
+ ) for term in terms
1916
+ ]
1917
+ )
1918
+
1919
+
1920
+ def get_add_terms( node, processed_nodes, is_calculate=False, negate=False, process=None):
1921
+ if negate:
1922
+ logger.warning("negate not supported for Add node {}".format(node.get_name()))
1923
+ terms = []
1924
+ for prev_node in node.prev_nodes:
1925
+ if issubclass(prev_node, TriccNodeNumber) or isinstance(node, TriccNodeCount):
1926
+ terms.append(
1927
+ TriccOperation(
1928
+ TriccOperator.COALESCE,
1929
+ [
1930
+ prev_node,
1931
+ TriccStatic(0)
1932
+ ]
1933
+ )
1934
+ )
1935
+ else:
1936
+ terms.append(
1937
+ TriccOperation(
1938
+ TriccOperator.CAST_NUMBER,
1939
+ [
1940
+ get_node_expression(
1941
+ prev_node,
1942
+ processed_nodes=processed_nodes,
1943
+ is_calculate=False,
1944
+ is_prev=True,
1945
+ process=process)
1946
+ ]
1947
+ )
1948
+ )
1949
+ if len(terms) > 0:
1950
+ operation = terms[0]
1951
+ if len(terms) > 1:
1952
+ for term in terms[1:]:
1953
+ operation = TriccOperation(
1954
+ TriccOperator.ADD,
1955
+ [
1956
+ operation,
1957
+ term
1958
+ ]
1959
+ )
1960
+ return operation
1961
+
1962
+ def get_rhombus_terms( node, processed_nodes, is_calculate=False, negate=False, process=None):
1963
+ expression = None
1964
+ left_term = None
1965
+ operator = None
1966
+ if node.reference is not None:
1967
+ if isinstance(node.reference, set):
1968
+ node.reference = list(node.reference)
1969
+ # calcualte the expression only for select muzltiple and fake calculate
1970
+ if issubclass(node.reference.__class__, (list,OrderedSet)):
1971
+ if node.expression_reference is None and len(node.reference) == 1:
1972
+ ref = node.reference[0]
1973
+ if issubclass(ref.__class__, TriccNodeBaseModel):
1974
+ if isinstance(ref, TriccNodeActivity):
1975
+ expression = get_activity_end_terms(ref, processed_nodes, process=process)
1976
+ elif issubclass(ref.__class__, TriccNodeFakeCalculateBase):
1977
+ expression = get_node_expression(
1978
+ ref,
1979
+ processed_nodes=processed_nodes,
1980
+ is_calculate=True,
1981
+ is_prev=True,
1982
+ process=process
1983
+ )
1984
+ else:
1985
+ expression = ref
1986
+ elif issubclass(ref.__class__, TriccReference):
1987
+ expression = ref
1988
+ else:
1989
+ logger.critical('reference {0} was not found in the previous nodes of node {1}'.format(node.reference,
1990
+ node.get_name()))
1991
+ exit(1)
1992
+ elif node.expression_reference is not None and node.expression_reference != '':
1993
+ if isinstance(node.expression_reference, TriccOperation):
1994
+ return node.expression_reference
1995
+ elif isinstance(node.expression_reference, str):
1996
+ expression = node.expression_reference.format(*get_list_names(node.reference))
1997
+ else:
1998
+ logger.critical('expression_reference {0} unsuported type {1}'.format(node.expression_reference, node.expression_reference.__class__.__name__))
1999
+ exit(1)
2000
+
2001
+ else:
2002
+ logger.warning("missing expression for node {}".format(node.get_name()))
2003
+ else:
2004
+ logger.critical('reference {0} is not a list {1}'.format(node.reference, node.get_name()))
2005
+ exit(1)
2006
+ else:
2007
+ logger.critical('reference empty for Rhombis {}'.format( node.get_name()))
2008
+ exit(1)
2009
+
2010
+ if expression is not None:
2011
+ if isinstance(expression, TriccOperation):
2012
+ return expression
2013
+ elif issubclass(expression.__class__ , TriccNodeCalculateBase):
2014
+ return TriccOperation(
2015
+ TriccOperator.CAST_NUMBER,
2016
+ [
2017
+ get_node_expression(
2018
+ expression,
2019
+ processed_nodes=processed_nodes,
2020
+ is_calculate=True,
2021
+ is_prev=True,
2022
+ process=process
2023
+ )
2024
+ ])
2025
+ elif issubclass(expression.__class__ , (TriccOperation) ):
2026
+ return expression
2027
+ elif issubclass(expression.__class__ , (TriccNodeDisplayModel, TriccReference)):
2028
+ return TriccOperation(
2029
+ TriccOperator.ISTRUE,
2030
+ [
2031
+ expression
2032
+ ]
2033
+ )
2034
+ else:
2035
+ if left_term is not None and re.search(" (\+)|(\-)|(or)|(and) ", expression):
2036
+ expression = "({0}){1}".format(expression, left_term)
2037
+ else:
2038
+ expression = "{0}{1}".format(expression, left_term)
2039
+ else:
2040
+ logger.critical("Rhombus reference was not found for node {}, reference {}".format(
2041
+ node.get_name(),
2042
+ node.reference
2043
+ ))
2044
+ exit(1)
2045
+
2046
+ return expression
2047
+ # function that generate the calculation terms return by calculate node
2048
+ # @param node calculate node to assess
2049
+ # @param processed_nodes list of node already processed, importnat because only processed node could be use
2050
+ # @param is_calculate used when this funciton is called in the evaluation of another calculate
2051
+ # @param negate use to retriece the negation of a calculation
2052
+ def get_calculation_terms( node, processed_nodes, is_calculate=False, negate=False, process=None):
2053
+ # returns something directly only if the negate is managed
2054
+ expression = None
2055
+ if isinstance(node, TriccNodeAdd):
2056
+ return get_add_terms(node, False, negate, process=process)
2057
+ elif isinstance(node, TriccNodeCount):
2058
+ return get_count_terms(node, False, negate, process=process)
2059
+ elif isinstance(node, TriccNodeRhombus):
2060
+ return get_rhombus_terms(
2061
+ node,
2062
+ processed_nodes=processed_nodes,
2063
+ is_calculate=False,
2064
+ negate=negate,
2065
+ process=process
2066
+ )
2067
+ elif isinstance(node, ( TriccNodeWait)):
2068
+ # just use to force order of question
2069
+ expression = None
2070
+ # in case of calulate expression evaluation, we need to get the relevance of the activity
2071
+ # because calculate are not the the activity group
2072
+ elif isinstance(node, (TriccNodeActivityStart)) and is_calculate:
2073
+ expression = get_prev_node_expression(node.activity, processed_nodes=processed_nodes, is_calculate=is_calculate, negate=negate, process=process)
2074
+ elif isinstance(node, (TriccNodeActivityStart, TriccNodeActivityEnd)):
2075
+ # the group have the relevance for the activity, not needed to replicate it
2076
+ expression = None #return get_prev_node_expression(node.activity, processed_nodes, is_calculate=False, excluded_name=None)
2077
+ elif isinstance(node, TriccNodeExclusive):
2078
+ if len(node.prev_nodes) == 1:
2079
+ iterator = iter(node.prev_nodes)
2080
+ node_to_negate = next(iterator)
2081
+ if isinstance(node_to_negate, TriccNodeExclusive):
2082
+ logger.critical("2 exclusives cannot be on a row")
2083
+ exit(1)
2084
+ elif issubclass(node_to_negate.__class__, TriccNodeCalculateBase):
2085
+ return get_node_expression(
2086
+ node_to_negate,
2087
+ processed_nodes=processed_nodes,
2088
+ is_prev=True,
2089
+ negate=True,
2090
+ process=process
2091
+ )
2092
+ elif isinstance(node_to_negate, TriccNodeActivity):
2093
+ return get_node_expression(
2094
+ node_to_negate,
2095
+ processed_nodes=processed_nodes,
2096
+ is_calculate=False,
2097
+ is_prev=True,
2098
+ negate=True,
2099
+ process=process
2100
+ )
2101
+ else:
2102
+ logger.critical(f"exclusive node {node.get_name()}\
2103
+ does not depend of a calculate but on\
2104
+ {node_to_negate.__class__}::{node_to_negate.get_name()}")
2105
+
2106
+ else:
2107
+ logger.critical("exclusive node {} has no ou too much parent".format(node.get_name()))
2108
+
2109
+ if isinstance(node.expression_reference, (TriccOperation, TriccStatic)):
2110
+ expression = node.expression_reference
2111
+ elif node.reference is not None and node.expression_reference is not None :
2112
+ expression = get_prev_node_expression(node, processed_nodes=processed_nodes, is_calculate=is_calculate, process=process)
2113
+ ref_expression = node.expression_reference.format(*[get_export_name(ref) for ref in node.reference])
2114
+ if expression is not None and expression != '':
2115
+ expression = and_join([expression,ref_expression])
2116
+ else:
2117
+ expression = ref_expression
2118
+ elif expression is None:
2119
+ expression = get_prev_node_expression(node, processed_nodes=processed_nodes, is_calculate=is_calculate, process=process)
2120
+
2121
+ # manage the generic negation
2122
+ if negate:
2123
+
2124
+ return negate_term(expression)
2125
+ else:
2126
+ return expression
2127
+
2128
+ # Function that add element to array is not None or ''
2129
+ def add_sub_expression(array, sub):
2130
+ if isinstance(sub, TriccOperation) or sub:
2131
+ not_sub = negate_term(sub)
2132
+ if not_sub in array:
2133
+ # avoid having 2 conditions that are complete opposites
2134
+ array.remove(not_sub)
2135
+ array.append(TriccStatic(True))
2136
+ else:
2137
+ array.append(sub)
2138
+ # elif sub is None:
2139
+ # array.append(TriccStatic(True))
2140
+
2141
+
2142
+
2143
+ # function that negate terms
2144
+ # @param expression to negate
2145
+ def negate_term(expression):
2146
+
2147
+ return not_clean(expression)
2148
+
2149
+
2150
+
2151
+
2152
+ # if the node is "required" then we can take the fact that it has value for the next elements
2153
+ def get_required_node_expression(node):
2154
+ return TriccOperation(
2155
+ operator=TriccOperator.EXISTS,
2156
+ reference=[
2157
+ node
2158
+ ]
2159
+ )
2160
+
2161
+
2162
+ # Get a selected option
2163
+ def get_selected_option_expression(option_node, negate):
2164
+
2165
+ selected = TriccOperation(
2166
+ TriccOperator.SELECTED,
2167
+ [
2168
+ option_node.select,
2169
+ TriccStatic(option_node.name)
2170
+ ]
2171
+ )
2172
+
2173
+ if negate:
2174
+ return TriccOperation(
2175
+ operator=TriccOperator.AND,
2176
+ resource=[
2177
+ TriccOperation(
2178
+ operator=TriccOperator.NOT,
2179
+ resource=[
2180
+ selected
2181
+ ]
2182
+ ),TriccOperation(
2183
+ operator=TriccOperator.NATIVE,
2184
+ resource=[
2185
+ 'count-selected',
2186
+ option_node.select
2187
+ ]
2188
+ )
2189
+ ])
2190
+
2191
+ else:
2192
+ return selected
2193
+
2194
+
2195
+
2196
+
2197
+
2198
+