tricc-oo 1.5.22__py3-none-any.whl → 1.5.24__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 (45) hide show
  1. tests/build.py +17 -23
  2. tests/test_cql.py +37 -108
  3. tests/to_ocl.py +15 -17
  4. tricc_oo/__init__.py +0 -6
  5. tricc_oo/converters/codesystem_to_ocl.py +51 -40
  6. tricc_oo/converters/cql/cqlLexer.py +1 -0
  7. tricc_oo/converters/cql/cqlListener.py +1 -0
  8. tricc_oo/converters/cql/cqlParser.py +1 -0
  9. tricc_oo/converters/cql/cqlVisitor.py +1 -0
  10. tricc_oo/converters/cql_to_operation.py +125 -123
  11. tricc_oo/converters/datadictionnary.py +45 -54
  12. tricc_oo/converters/drawio_type_map.py +143 -61
  13. tricc_oo/converters/tricc_to_xls_form.py +14 -24
  14. tricc_oo/converters/utils.py +3 -3
  15. tricc_oo/converters/xml_to_tricc.py +286 -231
  16. tricc_oo/models/__init__.py +2 -1
  17. tricc_oo/models/base.py +300 -308
  18. tricc_oo/models/calculate.py +63 -49
  19. tricc_oo/models/lang.py +26 -27
  20. tricc_oo/models/ocl.py +146 -161
  21. tricc_oo/models/ordered_set.py +15 -19
  22. tricc_oo/models/tricc.py +145 -89
  23. tricc_oo/parsers/xml.py +15 -30
  24. tricc_oo/serializers/planuml.py +4 -6
  25. tricc_oo/serializers/xls_form.py +81 -135
  26. tricc_oo/strategies/input/base_input_strategy.py +28 -32
  27. tricc_oo/strategies/input/drawio.py +59 -71
  28. tricc_oo/strategies/output/base_output_strategy.py +142 -67
  29. tricc_oo/strategies/output/fhir_form.py +377 -0
  30. tricc_oo/strategies/output/html_form.py +224 -0
  31. tricc_oo/strategies/output/openmrs_form.py +647 -0
  32. tricc_oo/strategies/output/spice.py +106 -127
  33. tricc_oo/strategies/output/xls_form.py +263 -222
  34. tricc_oo/strategies/output/xlsform_cdss.py +623 -142
  35. tricc_oo/strategies/output/xlsform_cht.py +108 -115
  36. tricc_oo/strategies/output/xlsform_cht_hf.py +13 -24
  37. tricc_oo/visitors/tricc.py +1297 -1016
  38. tricc_oo/visitors/utils.py +16 -16
  39. tricc_oo/visitors/xform_pd.py +91 -89
  40. {tricc_oo-1.5.22.dist-info → tricc_oo-1.5.24.dist-info}/METADATA +127 -84
  41. tricc_oo-1.5.24.dist-info/RECORD +50 -0
  42. tricc_oo-1.5.24.dist-info/licenses/LICENSE +373 -0
  43. tricc_oo-1.5.22.dist-info/RECORD +0 -46
  44. {tricc_oo-1.5.22.dist-info → tricc_oo-1.5.24.dist-info}/WHEEL +0 -0
  45. {tricc_oo-1.5.22.dist-info → tricc_oo-1.5.24.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,56 @@
1
+ import html2text
2
+ import logging
1
3
  import base64
2
4
  import os
3
5
  import re
4
- from curses.ascii import isalnum, isalpha, isdigit
5
6
 
6
- from numpy import isnan
7
7
 
8
- from tricc_oo.converters.utils import clean_name, remove_html
8
+ from tricc_oo.converters.utils import remove_html, clean_str
9
9
  from tricc_oo.converters.cql_to_operation import transform_cql_to_operation
10
- from tricc_oo.models.tricc import *
11
- from tricc_oo.models.base import TriccNodeType, OPERATION_LIST
10
+ from tricc_oo.converters.utils import generate_id, get_rand_name
11
+ from tricc_oo.models.base import (
12
+ TriccOperator, TriccOperation,
13
+ TriccStatic, TriccReference, TriccNodeType, TriccEdge, OPERATION_LIST
14
+ )
15
+ from tricc_oo.models.calculate import (
16
+ TriccNodeDisplayBridge,
17
+ TriccNodeBridge,
18
+ TriccNodeActivityEnd,
19
+ TriccNodeActivityStart,
20
+ TriccNodeEnd,
21
+ TriccNodeCalculate,
22
+ TriccNodeRhombus,
23
+ TriccNodeDisplayCalculateBase,
24
+ TriccNodeExclusive,
25
+ TriccNodeProposedDiagnosis,
26
+ TriccNodeDiagnosis,
27
+ TriccRhombusMixIn,
28
+ TriccNodeInput,
29
+
30
+ )
31
+ from tricc_oo.models.tricc import (
32
+ TriccNodeCalculateBase,
33
+ TriccNodeMainStart,
34
+ TriccNodeActivity,
35
+ TriccGroup,
36
+ TriccNodeSelect,
37
+ TriccNodeGoTo,
38
+ TriccNodeSelectMultiple,
39
+ TriccNodeInputModel,
40
+ TriccNodeSelectNotAvailable,
41
+ TriccNodeSelectOption,
42
+ TriccNodeDisplayModel,
43
+ TriccNodeMoreInfo,
44
+ TriccNodeText,
45
+ TriccNodeDecimal,
46
+ TriccNodeInteger,
47
+ TriccNodeDate,
48
+ TriccNodeSelectOne,
49
+ TriccNodeSelectYesNo,
50
+ TriccNodeNote,
51
+ )
52
+
53
+
12
54
  from tricc_oo.models.ocl import get_data_type
13
55
  from tricc_oo.converters.drawio_type_map import TYPE_MAP
14
56
  from tricc_oo.parsers.xml import (
@@ -20,7 +62,11 @@ from tricc_oo.parsers.xml import (
20
62
  get_tricc_type_list,
21
63
  )
22
64
  import hashlib
23
- from tricc_oo.visitors.tricc import *
65
+ from tricc_oo.visitors.tricc import (
66
+ get_select_yes_no_options, get_select_not_available_options,
67
+ set_prev_next_node, inject_node_before,
68
+ merge_node, remove_prev_next, get_activity_wait, get_count_terms_details
69
+ )
24
70
  from tricc_oo.converters.datadictionnary import add_concept
25
71
 
26
72
  TRICC_YES_LABEL = ["yes", "oui"]
@@ -28,15 +74,9 @@ TRICC_NO_LABEL = ["no", "non"]
28
74
  TRICC_FOLLOW_LABEL = ["follow", "suivre", "continue"]
29
75
  NO_LABEL = "NO_LABEL"
30
76
  TRICC_LIST_NAME = "list_{0}"
31
- import logging
32
- DISPLAY_ATTRIBUTES = [
33
- 'label',
34
- 'hint',
35
- 'help'
36
- ]
37
- logger = logging.getLogger("default")
38
- import html2text
39
77
 
78
+ DISPLAY_ATTRIBUTES = ["label", "hint", "help"]
79
+ logger = logging.getLogger("default")
40
80
 
41
81
 
42
82
  def get_all_nodes(diagram, activity, nodes):
@@ -51,15 +91,16 @@ def get_all_nodes(diagram, activity, nodes):
51
91
  activity,
52
92
  attributes=TYPE_MAP[tricc_type]["attributes"],
53
93
  mandatory_attributes=TYPE_MAP[tricc_type]["mandatory_attributes"],
54
- has_options=TYPE_MAP[tricc_type].get('has_options', None)
94
+ has_options=TYPE_MAP[tricc_type].get("has_options", None),
55
95
  )
56
96
 
57
97
  return nodes
58
98
 
99
+
59
100
  def create_activity(diagram, media_path, project):
60
101
 
61
102
  external_id = diagram.attrib.get("id")
62
- id = get_id( external_id, diagram.attrib.get("id"))
103
+ id = get_id(external_id, diagram.attrib.get("id"))
63
104
  root = create_root_node(diagram)
64
105
  name = diagram.attrib.get("name")
65
106
  form_id = diagram.attrib.get("name", None)
@@ -73,7 +114,7 @@ def create_activity(diagram, media_path, project):
73
114
  form_id=form_id,
74
115
  )
75
116
  if root.relevance is not None:
76
- activity.applicability=root.relevance
117
+ activity.applicability = root.relevance
77
118
  # activity definition is never instanciated
78
119
  if isinstance(root, TriccNodeActivityStart):
79
120
  activity.instance = 0
@@ -85,21 +126,45 @@ def create_activity(diagram, media_path, project):
85
126
  activity.edges = edges
86
127
  nodes = get_nodes(diagram, activity)
87
128
  for n in nodes.values():
88
-
129
+
89
130
  if (
90
- issubclass(n.__class__, (TriccNodeDisplayModel, TriccNodeDisplayCalculateBase))
131
+ issubclass(n.__class__, (TriccNodeDisplayModel, TriccNodeDisplayCalculateBase))
91
132
  and not isinstance(n, (TriccRhombusMixIn, TriccNodeRhombus, TriccNodeDisplayBridge))
92
- and not n.name.startswith('label_')
133
+ and not n.name.startswith("label_")
93
134
  ):
94
- system = n.name.split('.')[0] if '.' in n.name else 'tricc'
135
+ system = n.name.split(".")[0] if "." in n.name else "tricc"
95
136
  if isinstance(n, TriccNodeSelectOption) and isinstance(n.select, TriccNodeSelectNotAvailable):
96
- add_concept(project.code_systems, system, n.select.name, n.label, {"datatype": 'Boolean'})
137
+ add_concept(
138
+ project.code_systems,
139
+ system,
140
+ n.select.name,
141
+ n.label,
142
+ {"datatype": "Boolean", "contextType": get_context_type(n)},
143
+ )
97
144
  elif not isinstance(n, TriccNodeSelectNotAvailable):
98
- add_concept(project.code_systems, system, n.name, n.label, {"datatype": get_data_type(n.tricc_type)})
99
- if getattr(n,'save', None):
100
- system = n.save.split('.')[0] if '.' in n.save else 'tricc'
101
- add_concept(project.code_systems, system, n.save, n.label, {"datatype": get_data_type(n.tricc_type)})
102
-
145
+ add_concept(
146
+ project.code_systems,
147
+ system,
148
+ n.name,
149
+ n.label,
150
+ {
151
+ "datatype": get_data_type(n.tricc_type),
152
+ "contextType": get_context_type(n),
153
+ },
154
+ )
155
+ if getattr(n, "save", None):
156
+ system = n.save.split(".")[0] if "." in n.save else "tricc"
157
+ add_concept(
158
+ project.code_systems,
159
+ system,
160
+ n.save,
161
+ n.label,
162
+ {
163
+ "datatype": get_data_type(n.tricc_type),
164
+ "contextType": get_context_type(n),
165
+ },
166
+ )
167
+
103
168
  groups = get_groups(diagram, nodes, activity)
104
169
  if groups and len(groups) > 0:
105
170
  activity.groups = groups
@@ -110,7 +175,7 @@ def create_activity(diagram, media_path, project):
110
175
  # link back the activity
111
176
  activity.root.activity = activity
112
177
  manage_dangling_calculate(activity)
113
-
178
+
114
179
  if activity is not None:
115
180
  if activity.root is not None:
116
181
  project.pages[activity.id] = activity
@@ -119,7 +184,7 @@ def create_activity(diagram, media_path, project):
119
184
  activity.root.process == "main" or activity.root.process is None
120
185
  ):
121
186
  project.start_pages["main"] = activity
122
- activity.root.process = 'main'
187
+ activity.root.process = "main"
123
188
  elif activity.root.process is not None:
124
189
  if activity.root.process not in project.start_pages:
125
190
  project.start_pages[activity.root.process] = []
@@ -127,24 +192,27 @@ def create_activity(diagram, media_path, project):
127
192
  else:
128
193
  logger.warning(
129
194
  "Page {0} has a start node but there is already a start node in page {1}".format(
130
- activity.label, start_page.label
195
+ activity.label, project.start_pages[activity.root.process][0]
131
196
  )
132
197
  )
133
198
  if images:
134
199
  project.images += images
135
- for node in list(filter(lambda p_node: isinstance(p_node, TriccNodeSelectNotAvailable),list(activity.nodes.values()))):
200
+ for node in list(
201
+ filter(
202
+ lambda p_node: isinstance(p_node, TriccNodeSelectNotAvailable),
203
+ list(activity.nodes.values()),
204
+ )
205
+ ):
136
206
  prev_node = None
137
- prev_edges = list(filter(lambda p_e: p_e.target == node.id,list(activity.edges)))
207
+ prev_edges = list(filter(lambda p_e: p_e.target == node.id, list(activity.edges)))
138
208
  if len(prev_edges):
139
209
  prev_node = [n for n in activity.nodes.values() if n.id in [p_e.source for p_e in prev_edges]]
140
210
  if prev_node:
141
211
  node.parent = prev_node[0]
142
- if not node.parent :
212
+ if not node.parent:
143
213
  logger.critical(f"unable to find the parent of the NotApplicable node {node.get_name()}")
144
214
  exit(1)
145
215
 
146
-
147
-
148
216
  else:
149
217
  return None, None
150
218
  logger.warning("root not found for page {0}".format(name))
@@ -158,10 +226,7 @@ def manage_dangling_calculate(activity):
158
226
  for n in list(
159
227
  filter(
160
228
  lambda x: (x.target == node.id or x.target == node)
161
- and (
162
- x.source in activity.nodes
163
- or x.source in activity.nodes.values()
164
- ),
229
+ and (x.source in activity.nodes or x.source in activity.nodes.values()),
165
230
  activity.edges,
166
231
  )
167
232
  )
@@ -188,7 +253,9 @@ def process_edges(diagram, media_path, activity, nodes):
188
253
  if image is not None:
189
254
  images.append({"file_path": enriched, "image_content": image})
190
255
 
191
- elif isinstance(nodes[edge.target], (TriccNodeActivityEnd)) or (isinstance(nodes[edge.target], (TriccNodeEnd)) and isinstance(activity.root, TriccNodeMainStart )):
256
+ elif isinstance(nodes[edge.target], (TriccNodeActivityEnd)) or (
257
+ isinstance(nodes[edge.target], (TriccNodeEnd)) and isinstance(activity.root, TriccNodeMainStart)
258
+ ):
192
259
  end_found = True
193
260
  if (
194
261
  edge.target in nodes
@@ -197,19 +264,16 @@ def process_edges(diagram, media_path, activity, nodes):
197
264
  ):
198
265
  edge.target = nodes[edge.target].path.id
199
266
  # modify edge for selectyesNo
200
- if edge.source in nodes and isinstance(
201
- nodes[edge.source], TriccNodeSelectYesNo
202
- ):
267
+ if edge.source in nodes and isinstance(nodes[edge.source], TriccNodeSelectYesNo):
203
268
  process_yesno_edge(edge, nodes)
204
269
 
205
270
  # create calculate based on edges label
206
271
  elif edge.value is not None:
207
272
  label = edge.value.strip()
273
+ label_html_free = html2text.html2text(label)
208
274
  processed = False
209
275
  calc = None
210
- if (
211
- label.lower() in TRICC_FOLLOW_LABEL
212
- ):
276
+ if label.lower() in TRICC_FOLLOW_LABEL:
213
277
  if isinstance(nodes[edge.source], TriccNodeRhombus):
214
278
  edge.source = nodes[edge.source].path.id
215
279
  edge.source_external_id = None
@@ -221,9 +285,12 @@ def process_edges(diagram, media_path, activity, nodes):
221
285
  calc = process_factor_edge(edge, nodes)
222
286
  elif label.lower() in TRICC_NO_LABEL:
223
287
  calc = process_exclusive_edge(edge, nodes)
224
- elif any(reserved in html2text.html2text(label.lower()) for reserved in ([str(o) for o in list(TriccOperator)] + list(OPERATION_LIST.keys()) + ['$this'])):
288
+ elif any(
289
+ reserved in label_html_free
290
+ for reserved in ([str(o) for o in list(TriccOperator)] + list(OPERATION_LIST.keys()) + ["$this"])
291
+ ):
225
292
  # manage comment
226
- calc = process_condition_edge(edge, nodes)
293
+ calc = process_condition_edge(edge, label_html_free, nodes)
227
294
  else:
228
295
  logger.warning(f"unsupported edge label {label} in {diagram.attrib.get('name', diagram.attrib['id'])}")
229
296
  processed = True
@@ -242,25 +309,23 @@ def process_edges(diagram, media_path, activity, nodes):
242
309
  "not management found",
243
310
  )
244
311
  )
245
- elif edge.source in nodes and isinstance(nodes[edge.source], TriccNodeRhombus):
246
- logger.critical(
247
- "rhombus {} node with labelless edges".format(nodes[edge.source].get_name())
248
- )
312
+ elif edge.source in nodes and isinstance(nodes[edge.source], TriccNodeRhombus):
313
+ logger.critical("rhombus {} node with labelless edges".format(nodes[edge.source].get_name()))
249
314
  if not end_found:
250
315
  fake_end = TriccNodeActivityEnd(id=generate_id(f"e{activity.name}"), activity=activity, group=activity)
251
316
  last_nodes = [
252
- n for n in list(activity.nodes.values())
317
+ n
318
+ for n in list(activity.nodes.values())
253
319
  if (
254
320
  issubclass(
255
- n.__class__,
321
+ n.__class__,
256
322
  (
257
- TriccNodeInputModel,
258
- TriccNodeText,
323
+ TriccNodeInputModel,
324
+ TriccNodeText,
259
325
  TriccNodeNote,
260
- )
261
- ) and (
262
- not any([n.id == e.source for e in activity.edges])
326
+ ),
263
327
  )
328
+ and (not any([n.id == e.source for e in activity.edges]))
264
329
  )
265
330
  ]
266
331
  if last_nodes:
@@ -271,10 +336,7 @@ def process_edges(diagram, media_path, activity, nodes):
271
336
  else:
272
337
  logger.warning(f"Activity {activity.label} have no end, calculated might be included in the end definition")
273
338
  last_nodes = [
274
- n for n in list(activity.nodes.values())
275
- if (
276
- not any([n.id == e.source for e in activity.edges])
277
- )
339
+ n for n in list(activity.nodes.values()) if (not any([n.id == e.source for e in activity.edges]))
278
340
  ]
279
341
  if last_nodes:
280
342
  for n in last_nodes:
@@ -283,20 +345,19 @@ def process_edges(diagram, media_path, activity, nodes):
283
345
  else:
284
346
  logger.critical(f"cannot guess end for {activity.get_name()}")
285
347
  exit(1)
286
-
348
+
287
349
  return images
288
350
 
351
+
289
352
  def get_id(elm_id, activity_id):
290
- return str(elm_id) if len(elm_id)>8 else str(activity_id) + str(elm_id)
353
+ return str(elm_id) if len(elm_id) > 8 else str(activity_id) + str(elm_id)
354
+
291
355
 
292
356
  def _get_name(name, id, act_id):
293
- if (
294
- name is not None
295
- and (name.endswith(("_", ".")))
296
- ):
357
+ if name is not None and (name.endswith(("_", "."))):
297
358
  return name + get_id(id, act_id)
298
359
  return name
299
-
360
+
300
361
 
301
362
  def get_nodes(diagram, activity):
302
363
  nodes = {activity.root.id: activity.root}
@@ -304,12 +365,9 @@ def get_nodes(diagram, activity):
304
365
  new_nodes = {}
305
366
  node_to_remove = []
306
367
  activity_end_node = None
307
- end_node = None
308
368
  for node in nodes.values():
309
369
  # clean name
310
- if (
311
- hasattr(node, "name")
312
- ):
370
+ if hasattr(node, "name"):
313
371
  node.name = _get_name(node.name, node.id, activity.id)
314
372
  if issubclass(node.__class__, TriccRhombusMixIn) and node.path is None:
315
373
  # generate rhombuse path
@@ -331,12 +389,10 @@ def get_nodes(diagram, activity):
331
389
  next_nodes_id = [e.target for e in activity.edges if e.source == node.id]
332
390
  if len(next_nodes_id) > 0:
333
391
 
334
- calc = get_activity_wait(
335
- path, [node], next_nodes_id, node, edge_only=True
336
- )
392
+ calc = get_activity_wait(path, [node], next_nodes_id, node, edge_only=True)
337
393
  new_nodes[calc.id] = calc
338
394
  for goto_next_node in next_nodes_id:
339
- remove_prev_next(node, goto_next_node, activity)
395
+ remove_prev_next(node, goto_next_node, activity)
340
396
  elif isinstance(node, TriccNodeActivityEnd):
341
397
  if not activity_end_node:
342
398
  activity_end_node = node
@@ -345,12 +401,11 @@ def get_nodes(diagram, activity):
345
401
  node_to_remove.append(node.id)
346
402
  # add activity relevance to calculate
347
403
  elif (
348
- issubclass(node.__class__, TriccNodeDisplayCalculateBase)
349
- and not getattr(node, 'relevance', None)
404
+ issubclass(node.__class__, TriccNodeDisplayCalculateBase)
405
+ and not getattr(node, "relevance", None)
350
406
  and node.activity.applicability
351
407
  ):
352
408
  node.applicability = node.activity.applicability
353
-
354
409
 
355
410
  nodes.update(new_nodes)
356
411
 
@@ -371,7 +426,7 @@ def create_root_node(diagram):
371
426
  elm = get_tricc_type(diagram, "UserObject", TriccNodeType.start)
372
427
  if elm is not None:
373
428
  external_id = elm.attrib.get("id")
374
- id = get_id( external_id, diagram.attrib.get("id"))
429
+ id = get_id(external_id, diagram.attrib.get("id"))
375
430
  node = TriccNodeMainStart(
376
431
  id=id,
377
432
  external_id=external_id,
@@ -380,7 +435,7 @@ def create_root_node(diagram):
380
435
  label=elm.attrib.get("label"),
381
436
  form_id=elm.attrib.get("form_id"),
382
437
  relevance=elm.attrib.get("relevance"),
383
- process=elm.attrib.get('process', 'main'),
438
+ process=elm.attrib.get("process", "registration"),
384
439
  )
385
440
  else:
386
441
  elm = get_tricc_type(diagram, "object", TriccNodeType.activity_start)
@@ -388,19 +443,15 @@ def create_root_node(diagram):
388
443
  elm = get_tricc_type(diagram, "UserObject", TriccNodeType.activity_start)
389
444
  if elm is not None:
390
445
  external_id = elm.attrib.get("id")
391
- id = get_id( external_id, diagram.attrib.get("id"))
446
+ id = get_id(external_id, diagram.attrib.get("id"))
392
447
  node = TriccNodeActivityStart(
393
448
  id=id,
394
449
  external_id=external_id,
395
- #parent=elm.attrib.get("parent"),
450
+ # parent=elm.attrib.get("parent"),
396
451
  name="ma" + id,
397
452
  label=diagram.attrib.get("name"),
398
453
  relevance=elm.attrib.get("relevance"),
399
- instance=int(
400
- elm.attrib.get("instance")
401
- if elm.attrib.get("instance") is not None
402
- else 1
403
- ),
454
+ instance=int(elm.attrib.get("instance") if elm.attrib.get("instance") is not None else 1),
404
455
  )
405
456
  load_expressions(node)
406
457
  return node
@@ -425,6 +476,39 @@ def set_additional_attributes(attribute_names, elm, node):
425
476
  setattr(node, attributename, attribute)
426
477
 
427
478
 
479
+ def get_context_type(node):
480
+ context_type = getattr(node, "context_type", None)
481
+ if context_type:
482
+ return context_type
483
+ if isinstance(node, TriccNodeSelectMultiple):
484
+ return "Question"
485
+ elif isinstance(node, TriccNodeSelectOption):
486
+ if isinstance(node.select, TriccNodeSelectMultiple):
487
+ return "Symptom-Finding"
488
+ else:
489
+ return "Value"
490
+ elif isinstance(node, TriccNodeNote):
491
+ return "InteractSet"
492
+ elif isinstance(
493
+ node,
494
+ (
495
+ TriccNodeDecimal,
496
+ TriccNodeInteger,
497
+ TriccNodeText,
498
+ TriccNodeDate,
499
+ TriccNodeSelectOne,
500
+ TriccNodeSelectYesNo,
501
+ TriccNodeSelectNotAvailable,
502
+ TriccNodeInput,
503
+ ),
504
+ ):
505
+ return "Symptom-Finding"
506
+ elif isinstance(node, (TriccNodeProposedDiagnosis, TriccNodeDiagnosis)):
507
+ return "Diagnosis"
508
+ elif issubclass(node.__class__, TriccNodeCalculateBase):
509
+ return "Calculation"
510
+ else:
511
+ return "Misc"
428
512
 
429
513
 
430
514
  def get_select_options(diagram, select_node, nodes):
@@ -435,16 +519,12 @@ def get_select_options(diagram, select_node, nodes):
435
519
  for elm in list:
436
520
  name = elm.attrib.get("name")
437
521
  if name in options_name_list and not name.endswith("_"):
438
- logger.critical(
439
- "Select question {0} have twice the option name {1}".format(
440
- select_node.get_name(), name
441
- )
442
- )
522
+ logger.critical("Select question {0} have twice the option name {1}".format(select_node.get_name(), name))
443
523
  else:
444
524
  options_name_list.append(name)
445
-
525
+
446
526
  external_id = elm.attrib.get("id")
447
- id = get_id(external_id, diagram.attrib.get('id'))
527
+ id = get_id(external_id, diagram.attrib.get("id"))
448
528
  option = TriccNodeSelectOption(
449
529
  id=id,
450
530
  label=elm.attrib.get("label"),
@@ -453,8 +533,8 @@ def get_select_options(diagram, select_node, nodes):
453
533
  list_name=select_node.list_name,
454
534
  activity=select_node.activity,
455
535
  group=select_node.group,
456
- )
457
- set_additional_attributes(["save", "relevance"], elm, option)
536
+ )
537
+ set_additional_attributes(["save", "relevance", "context_type"], elm, option)
458
538
  load_expressions(option)
459
539
  options[i] = option
460
540
  nodes[id] = option
@@ -464,7 +544,7 @@ def get_select_options(diagram, select_node, nodes):
464
544
  else:
465
545
  return options
466
546
 
467
- ##TBR START
547
+ # TBR START
468
548
 
469
549
 
470
550
  def get_max_version(dict):
@@ -484,11 +564,10 @@ def get_max_named_version(calculates, name):
484
564
  return max
485
565
 
486
566
 
487
-
488
567
  def inject_bridge_path(node, nodes):
489
568
  calc_name = "p" + node.id
490
569
  calc_id = generate_id(calc_name)
491
-
570
+
492
571
  data = {
493
572
  "id": calc_id,
494
573
  "group": node.group,
@@ -501,22 +580,16 @@ def inject_bridge_path(node, nodes):
501
580
  nodes[n.source]
502
581
  for n in list(
503
582
  filter(
504
- lambda x: (x.target == node.id or x.target == node)
505
- and x.source in nodes,
583
+ lambda x: (x.target == node.id or x.target == node) and x.source in nodes,
506
584
  node.activity.edges,
507
585
  )
508
586
  )
509
587
  ]
510
- if ( len(prev_nodes)>1 and
511
- sum(
588
+ if (
589
+ len(prev_nodes) > 1
590
+ and sum(
512
591
  [
513
- (
514
- 0
515
- if issubclass(
516
- n.__class__, (TriccNodeDisplayCalculateBase, TriccNodeRhombus)
517
- )
518
- else 1
519
- )
592
+ (0 if issubclass(n.__class__, (TriccNodeDisplayCalculateBase, TriccNodeRhombus)) else 1)
520
593
  for n in prev_nodes
521
594
  ]
522
595
  )
@@ -545,7 +618,7 @@ def enrich_node(diagram, media_path, edge, node, activity, help_before=False):
545
618
  # get node and process type
546
619
  type, message = get_message(diagram, edge.source_external_id)
547
620
  if type is not None:
548
- if type == 'help':
621
+ if type == "help":
549
622
  help = TriccNodeMoreInfo(
550
623
  id=generate_id(),
551
624
  name=f"{node.name}.more_info",
@@ -553,7 +626,7 @@ def enrich_node(diagram, media_path, edge, node, activity, help_before=False):
553
626
  parent=node,
554
627
  required=None,
555
628
  )
556
- #node.help = message
629
+ # node.help = message
557
630
  if help_before:
558
631
  inject_node_before(help, node, activity)
559
632
  else:
@@ -585,34 +658,41 @@ def enrich_node(diagram, media_path, edge, node, activity, help_before=False):
585
658
  return None, None
586
659
  else:
587
660
  logger.warning(f"edge from an unsuported node {edge.source_external_id}")
588
-
661
+
589
662
  return None, None
590
663
 
591
664
 
592
- get_style_dict = lambda style: dict(item.split('=', 1) for item in style.split(';') if '=' in item)
665
+ def get_style_dict(style):
666
+ return dict(item.split("=", 1) for item in style.split(";") if "=" in item)
593
667
 
594
668
 
595
- def severity_from_color(color):
596
- if color == '#fff2cc':
597
- return 'moderate'
598
- elif color == '#f8cecc':
599
- return 'severe'
669
+ def severity_from_color(color):
670
+ if color == "#fff2cc":
671
+ return "moderate"
672
+ elif color == "#f8cecc":
673
+ return "severe"
600
674
  else:
601
- return 'light'
602
-
675
+ return "light"
603
676
 
604
677
 
605
678
  def add_tricc_base_node(
606
- diagram, nodes, type, list, group, attributes=[], mandatory_attributes=[], has_options=None
679
+ diagram,
680
+ nodes,
681
+ type,
682
+ list,
683
+ group,
684
+ attributes=[],
685
+ mandatory_attributes=[],
686
+ has_options=None,
607
687
  ):
608
688
  for elm in list:
609
689
  external_id = elm.attrib.get("id")
610
- id = get_id( external_id, diagram.attrib.get("id"))
611
- parent = elm.attrib.get("parent")
690
+ id = get_id(external_id, diagram.attrib.get("id"))
691
+ elm.attrib.get("parent")
612
692
  node = type(
613
693
  external_id=external_id,
614
694
  id=id,
615
- #parent=parent,
695
+ # parent=parent,
616
696
  group=group,
617
697
  activity=group,
618
698
  **set_mandatory_attribute(elm, mandatory_attributes, diagram),
@@ -630,106 +710,99 @@ def add_tricc_base_node(
630
710
  node.options = get_select_yes_no_options(node, group)
631
711
  nodes[node.options[0].id] = node.options[0]
632
712
  nodes[node.options[1].id] = node.options[1]
633
- elif type == TriccNodeProposedDiagnosis and getattr(node, 'severity', '') == None:
713
+ elif type == TriccNodeProposedDiagnosis and getattr(node, "severity", "") is None:
634
714
  mxcell = get_mxcell(diagram, external_id)
635
- styles = get_style_dict(mxcell.attrib.get('style',''))
636
- if 'fillColor' in styles and styles['fillColor'] != 'none':
637
- node.severity = severity_from_color(styles['fillColor'])
638
-
639
-
640
-
715
+ styles = get_style_dict(mxcell.attrib.get("style", ""))
716
+ if "fillColor" in styles and styles["fillColor"] != "none":
717
+ node.severity = severity_from_color(styles["fillColor"])
718
+
641
719
  set_additional_attributes(attributes, elm, node)
642
720
  load_expressions(node)
643
721
  nodes[id] = node
644
722
 
645
723
 
646
724
  def load_expressions(node):
647
- if getattr(node, 'constraint', None):
648
- node.constraint = parse_expression('', node.constraint)
649
- if getattr(node, 'expression', None):
650
- node.expression = parse_expression('', node.expression)
651
- if getattr(node, 'relevance', None):
652
- node.relevance = parse_expression('', node.relevance)
653
- if getattr(node, 'trigger', None):
654
- node.trigger = parse_expression('', node.trigger)
655
- if getattr(node, 'default', None):
656
- node.default = parse_expression('', node.default)
657
- if getattr(node, 'reference', None):
725
+ if getattr(node, "constraint", None):
726
+ node.constraint = parse_expression("", node.constraint)
727
+ if getattr(node, "expression", None):
728
+ node.expression = parse_expression("", node.expression)
729
+ if getattr(node, "relevance", None):
730
+ node.relevance = parse_expression("", node.relevance)
731
+ if getattr(node, "trigger", None):
732
+ node.trigger = parse_expression("", node.trigger)
733
+ if getattr(node, "default", None):
734
+ node.default = parse_expression("", node.default)
735
+ if getattr(node, "reference", None):
658
736
  if isinstance(node, TriccNodeRhombus):
659
737
  node.label = remove_html(node.label)
660
738
  node.expression_reference = parse_expression(node.label, node.reference)
661
739
  else:
662
- node.expression_reference = parse_expression('', node.reference)
663
-
740
+ node.expression_reference = parse_expression("", node.reference)
741
+
664
742
  node.reference = node.expression_reference.get_references()
665
-
666
-
667
-
743
+
668
744
 
669
745
  def parse_expression(label=None, expression=None):
670
746
  if expression:
671
- ref_pattern = r'(\$\{[^\}]+\})'
672
- # only if simple ref
747
+ ref_pattern = r"(\$\{[^\}]+\})"
748
+ # only if simple ref
673
749
  if not re.search(ref_pattern, expression):
674
750
  operation = transform_cql_to_operation(expression, label)
675
751
  if isinstance(operation, TriccReference):
676
752
  if label:
677
- if label[0] == '[' and label[-1] == ']':
753
+ if label[0] == "[" and label[-1] == "]":
678
754
  operation = TriccOperation(
679
755
  operator=TriccOperator.SELECTED,
680
756
  reference=[
681
757
  operation,
682
- TriccReference(operation.value + label)
683
- ]
758
+ TriccReference(operation.value + label),
759
+ ],
684
760
  )
685
761
  else:
686
762
  for operator in OPERATION_LIST:
687
763
  if operator in label:
688
- if operator == '==':
689
- operator = '='
764
+ if operator == "==":
765
+ operator = "="
690
766
  terms = label.split(operator)
691
767
  operation = transform_cql_to_operation(
692
768
  f"{expression} {operator} {terms[1].replace('?', '').strip()}",
693
- label
769
+ label,
694
770
  )
695
771
  break
696
772
  # implied is true
697
773
  if isinstance(operation, TriccReference):
698
- operation = TriccOperation(
699
- operator=TriccOperator.ISTRUE,
700
- reference=[
701
- operation,
702
- ]
703
- )
704
-
774
+ operation = TriccOperation(
775
+ operator=TriccOperator.ISTRUE,
776
+ reference=[
777
+ operation,
778
+ ],
779
+ )
780
+
705
781
  else:
706
782
  pass
707
783
 
708
784
  else:
709
- operation = transform_cql_to_operation(
710
- expression.replace('${', '"').replace('}', '"'),
711
- label
712
- )
713
-
785
+ operation = transform_cql_to_operation(expression.replace("${", '"').replace("}", '"'), label)
786
+
714
787
  if operation is None:
715
788
  logger.warning(f"unable to parse: {expression} ")
716
789
  return expression
717
790
  return operation
718
-
791
+
719
792
 
720
793
  def set_mandatory_attribute(elm, mandatory_attributes, diagram=None):
721
794
  param = {}
722
- diagram_id = diagram.attrib.get('id')
795
+ diagram_id = diagram.attrib.get("id")
723
796
  for attributes in mandatory_attributes:
724
- if attributes == 'name':
797
+ if attributes == "name":
725
798
  name = elm.attrib.get("name")
726
799
  id = elm.attrib.get("id")
727
800
  attribute_value = _get_name(name, id, diagram_id)
728
- elif attributes == 'list_name':
801
+ elif attributes == "list_name":
729
802
  name = elm.attrib.get("name")
730
803
  id = elm.attrib.get("id")
731
- attribute_value = TRICC_LIST_NAME.format(clean_str(_get_name(name, id, diagram_id), replace_dots= True))
732
- else:
804
+ attribute_value = TRICC_LIST_NAME.format(clean_str(_get_name(name, id, diagram_id), replace_dots=True))
805
+ else:
733
806
  attribute_value = elm.attrib.get(attributes)
734
807
  if attribute_value is None:
735
808
  if elm.attrib.get("label") is not None:
@@ -738,21 +811,19 @@ def set_mandatory_attribute(elm, mandatory_attributes, diagram=None):
738
811
  display_name = elm.attrib.get("name")
739
812
  else:
740
813
  display_name = elm.attrib.get("id")
741
-
814
+
742
815
  if attributes == "source":
743
816
  if elm.attrib.get("target") is not None:
744
- logger.critical(
745
- "the attibute target is {}".format(elm.attrib.get("target"))
746
- )
817
+ logger.critical("the attibute target is {}".format(elm.attrib.get("target")))
747
818
  elif attributes == "target":
748
819
  if elm.attrib.get("source") is not None:
749
- logger.critical(
750
- "the attibute target is {}".format(elm.attrib.get("source"))
751
- )
752
-
820
+ logger.critical("the attibute target is {}".format(elm.attrib.get("source")))
821
+
753
822
  logger.critical(
754
823
  "the attibute {} is mandatory but not found in {} within group {}".format(
755
- attributes, display_name, diagram.attrib.get('name') if diagram is not None else ""
824
+ attributes,
825
+ display_name,
826
+ diagram.attrib.get("name") if diagram is not None else "",
756
827
  )
757
828
  )
758
829
  exit(1)
@@ -792,12 +863,10 @@ def add_group(elm, diagram, nodes, groups, parent_group, activity):
792
863
  id=id,
793
864
  external_id=external_id,
794
865
  group=parent_group,
795
- activity=activity
866
+ activity=activity,
796
867
  )
797
868
  # get elememt witn parent = id and tricc_type defiend
798
- list_child = get_tricc_type_list(
799
- diagram, ["object", "UserObject"], tricc_type=None, parent_id=id
800
- )
869
+ list_child = get_tricc_type_list(diagram, ["object", "UserObject"], tricc_type=None, parent_id=id)
801
870
  add_group_to_child(group, diagram, list_child, nodes, groups, parent_group)
802
871
  if group is not None:
803
872
  groups[group.id] = group
@@ -813,12 +882,10 @@ def add_group_to_child(group, diagram, list_child, nodes, groups, parent_group):
813
882
  tricc_type=None,
814
883
  parent_id=child_elm.attrib.get("id"),
815
884
  )
816
- add_group_to_child(
817
- group, diagram, list_sub_child, nodes, groups, parent_group
818
- )
885
+ add_group_to_child(group, diagram, list_sub_child, nodes, groups, parent_group)
819
886
  elif child_elm.attrib.get("tricc_type") == TriccNodeType.page:
820
887
  child_group_id = child_elm.attrib.get("id")
821
- if not child_group_id in groups:
888
+ if child_group_id not in groups:
822
889
  child_group = add_group(child_elm, diagram, nodes, groups, group)
823
890
  else:
824
891
  child_group = groups[child_group_id]
@@ -843,16 +910,14 @@ def add_image_from_style(style, path):
843
910
  image_attrib = None
844
911
  if style is not None and "image=data:image/" in style:
845
912
  image_attrib = style.split("image=data:image/")
846
- if image_attrib is not None and len(image_attrib)== 2:
913
+ if image_attrib is not None and len(image_attrib) == 2:
847
914
  image_parts = image_attrib[1].split(",")
848
915
  if len(image_parts) == 2:
849
916
  payload = image_parts[1][:-1]
850
- image_name = hashlib.md5(payload.encode('utf-8')).hexdigest()
917
+ image_name = hashlib.md5(payload.encode("utf-8")).hexdigest()
851
918
  path = os.path.join(path, "images")
852
919
  file_name = os.path.join(path, image_name + "." + image_parts[0])
853
- if not (
854
- os.path.isdir(path)
855
- ): # check if it exists, because if it does, error will be raised
920
+ if not (os.path.isdir(path)): # check if it exists, because if it does, error will be raised
856
921
  # (later change to make folder complaint to CHT)
857
922
  os.makedirs(path, exist_ok=True)
858
923
  with open(file_name, "wb") as fh:
@@ -862,13 +927,6 @@ def add_image_from_style(style, path):
862
927
  return None, None
863
928
 
864
929
 
865
- def get_contained_main_node(diagram, id):
866
- list = get_mxcell_parent_list(diagram, id, media_nodes)
867
- if isinstance(list, List) and len(list) > 0:
868
- # use only the first one
869
- return list[0]
870
-
871
-
872
930
  def get_message(diagram, id):
873
931
  elm = get_elm(diagram, id)
874
932
  if elm is not None:
@@ -889,14 +947,12 @@ def get_edges(diagram):
889
947
  id = get_id(external_id, diagram.attrib.get("id"))
890
948
  edge = TriccEdge(
891
949
  id=id,
892
- **set_mandatory_attribute(
893
- elm, ["source", "parent", "target"], diagram
894
- ),
950
+ **set_mandatory_attribute(elm, ["source", "parent", "target"], diagram),
895
951
  )
896
952
  edge.source_external_id = edge.source
897
953
  edge.target_external_id = edge.target
898
- edge.source = get_id(edge.source, diagram.attrib.get("id"))
899
- edge.target = get_id(edge.target, diagram.attrib.get("id"))
954
+ edge.source = get_id(edge.source, diagram.attrib.get("id"))
955
+ edge.target = get_id(edge.target, diagram.attrib.get("id"))
900
956
  set_additional_attributes(["value"], elm, edge)
901
957
  if edge.value is not None:
902
958
  edge.value = remove_html(edge.value)
@@ -904,7 +960,7 @@ def get_edges(diagram):
904
960
  return edges
905
961
 
906
962
 
907
- ## Process edges
963
+ # Process edges
908
964
 
909
965
 
910
966
  def process_factor_edge(edge, nodes):
@@ -913,7 +969,10 @@ def process_factor_edge(edge, nodes):
913
969
  source = nodes[edge.source]
914
970
  return TriccNodeCalculate(
915
971
  id=edge.id,
916
- expression_reference=TriccOperation(TriccOperator.MULTIPLIED, [TriccReference(nodes[edge.source].name),TriccStatic(float(factor))]),
972
+ expression_reference=TriccOperation(
973
+ TriccOperator.MULTIPLIED,
974
+ [TriccReference(nodes[edge.source].name), TriccStatic(float(factor))],
975
+ ),
917
976
  reference=[TriccReference(source.name)],
918
977
  activity=source.activity,
919
978
  group=source.group,
@@ -922,15 +981,20 @@ def process_factor_edge(edge, nodes):
922
981
  return None
923
982
 
924
983
 
925
- def process_condition_edge(edge, nodes):
926
- label = edge.value.strip()
927
- node = nodes[edge.source]
928
- node_ref = f'"{nodes[edge.source].name}"'
929
- if '$this' in label:
930
- operation = parse_expression('', expression=label.replace('$this', node_ref ))
984
+ def process_condition_edge(edge, label, nodes):
985
+ source = nodes[edge.source]
986
+ node_ref = f'"{source.name}"'
987
+ if "$this" in label:
988
+ operation = parse_expression("", expression=label.replace("$this", node_ref))
931
989
  else:
932
990
  operation = parse_expression(label, expression=node_ref)
991
+
933
992
  if operation and isinstance(operation, TriccOperation):
993
+ # special management for simple operation
994
+ if issubclass(source.__class__, TriccNodeSelect) and "$this" not in label:
995
+ operation.replace_node(
996
+ TriccReference(source.name),
997
+ get_count_terms_details(source, None, False))
934
998
  # insert rhombus
935
999
  return TriccNodeRhombus(
936
1000
  id=edge.id,
@@ -939,13 +1003,10 @@ def process_condition_edge(edge, nodes):
939
1003
  path=nodes[edge.source],
940
1004
  activity=nodes[edge.source].activity,
941
1005
  group=nodes[edge.source].group,
942
- label=label
1006
+ label=label,
943
1007
  )
944
1008
 
945
1009
 
946
-
947
-
948
-
949
1010
  def process_exclusive_edge(edge, nodes):
950
1011
  error = None
951
1012
  if issubclass(nodes[edge.source].__class__, TriccNodeCalculateBase):
@@ -976,9 +1037,7 @@ def process_exclusive_edge(edge, nodes):
976
1037
 
977
1038
  def process_yesno_edge(edge, nodes):
978
1039
  if not edge.value:
979
- logger.critical(
980
- "yesNo {} node with labelless edges".format(nodes[edge.source].get_name())
981
- )
1040
+ logger.critical("yesNo {} node with labelless edges".format(nodes[edge.source].get_name()))
982
1041
  exit(1)
983
1042
  label = edge.value.strip().lower()
984
1043
  yes_option = None
@@ -997,8 +1056,4 @@ def process_yesno_edge(edge, nodes):
997
1056
  edge.source = no_option.id
998
1057
  edge.source_external_id = None
999
1058
  else:
1000
- logger.warning(
1001
- "edge {0} is coming from select {1}".format(
1002
- edge.id, nodes[edge.source].get_name()
1003
- )
1004
- )
1059
+ logger.warning("edge {0} is coming from select {1}".format(edge.id, nodes[edge.source].get_name()))