tricc-oo 1.5.13__py3-none-any.whl → 1.6.8__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 (47) hide show
  1. tests/build.py +20 -28
  2. tests/test_build.py +260 -0
  3. tests/test_cql.py +48 -109
  4. tests/to_ocl.py +15 -17
  5. tricc_oo/__init__.py +0 -6
  6. tricc_oo/converters/codesystem_to_ocl.py +51 -40
  7. tricc_oo/converters/cql/cqlLexer.py +1 -0
  8. tricc_oo/converters/cql/cqlListener.py +1 -0
  9. tricc_oo/converters/cql/cqlParser.py +1 -0
  10. tricc_oo/converters/cql/cqlVisitor.py +1 -0
  11. tricc_oo/converters/cql_to_operation.py +129 -123
  12. tricc_oo/converters/datadictionnary.py +45 -54
  13. tricc_oo/converters/drawio_type_map.py +146 -65
  14. tricc_oo/converters/tricc_to_xls_form.py +58 -28
  15. tricc_oo/converters/utils.py +4 -4
  16. tricc_oo/converters/xml_to_tricc.py +296 -235
  17. tricc_oo/models/__init__.py +2 -1
  18. tricc_oo/models/base.py +333 -305
  19. tricc_oo/models/calculate.py +66 -51
  20. tricc_oo/models/lang.py +26 -27
  21. tricc_oo/models/ocl.py +146 -161
  22. tricc_oo/models/ordered_set.py +15 -19
  23. tricc_oo/models/tricc.py +149 -89
  24. tricc_oo/parsers/xml.py +15 -30
  25. tricc_oo/serializers/planuml.py +4 -6
  26. tricc_oo/serializers/xls_form.py +110 -153
  27. tricc_oo/strategies/input/base_input_strategy.py +28 -32
  28. tricc_oo/strategies/input/drawio.py +59 -71
  29. tricc_oo/strategies/output/base_output_strategy.py +151 -65
  30. tricc_oo/strategies/output/dhis2_form.py +908 -0
  31. tricc_oo/strategies/output/fhir_form.py +377 -0
  32. tricc_oo/strategies/output/html_form.py +224 -0
  33. tricc_oo/strategies/output/openmrs_form.py +694 -0
  34. tricc_oo/strategies/output/spice.py +106 -127
  35. tricc_oo/strategies/output/xls_form.py +322 -244
  36. tricc_oo/strategies/output/xlsform_cdss.py +627 -142
  37. tricc_oo/strategies/output/xlsform_cht.py +252 -125
  38. tricc_oo/strategies/output/xlsform_cht_hf.py +13 -24
  39. tricc_oo/visitors/tricc.py +1424 -1033
  40. tricc_oo/visitors/utils.py +16 -16
  41. tricc_oo/visitors/xform_pd.py +91 -89
  42. {tricc_oo-1.5.13.dist-info → tricc_oo-1.6.8.dist-info}/METADATA +128 -84
  43. tricc_oo-1.6.8.dist-info/RECORD +52 -0
  44. tricc_oo-1.6.8.dist-info/licenses/LICENSE +373 -0
  45. {tricc_oo-1.5.13.dist-info → tricc_oo-1.6.8.dist-info}/top_level.txt +0 -0
  46. tricc_oo-1.5.13.dist-info/RECORD +0 -46
  47. {tricc_oo-1.5.13.dist-info → tricc_oo-1.6.8.dist-info}/WHEEL +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
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,29 +91,30 @@ 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"))
63
- root = create_root_node(diagram)
64
- name = diagram.attrib.get("name")
103
+ id = get_id(external_id, diagram.attrib.get("id"))
104
+ root, name = create_root_node(diagram)
105
+ label = diagram.attrib.get("name")
65
106
  form_id = diagram.attrib.get("name", None)
66
107
  if root is not None:
67
108
  activity = TriccNodeActivity(
68
109
  root=root,
69
- name=get_rand_name(f"a{id}"),
110
+ name=name, # start node 'name' is saved in label
70
111
  id=id,
71
112
  external_id=external_id,
72
- label=name,
113
+ label=label,
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_") # FIXME
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,22 +443,19 @@ 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"))
447
+ name = generate_id("start"+external_id)
392
448
  node = TriccNodeActivityStart(
393
449
  id=id,
394
450
  external_id=external_id,
395
- #parent=elm.attrib.get("parent"),
396
- name="ma" + id,
451
+ # parent=elm.attrib.get("parent"),
452
+ name=name,
397
453
  label=diagram.attrib.get("name"),
398
454
  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
- ),
455
+ instance=int(elm.attrib.get("instance") if elm.attrib.get("instance") is not None else 1),
404
456
  )
405
457
  load_expressions(node)
406
- return node
458
+ return node, _get_name(elm.attrib.get("name", "act_"), external_id, diagram.attrib.get("id")) if node else None
407
459
 
408
460
 
409
461
  # converter XML item to object
@@ -425,6 +477,39 @@ def set_additional_attributes(attribute_names, elm, node):
425
477
  setattr(node, attributename, attribute)
426
478
 
427
479
 
480
+ def get_context_type(node):
481
+ context_type = getattr(node, "context_type", None)
482
+ if context_type:
483
+ return context_type
484
+ if isinstance(node, TriccNodeSelectMultiple):
485
+ return "Question"
486
+ elif isinstance(node, TriccNodeSelectOption):
487
+ if isinstance(node.select, TriccNodeSelectMultiple):
488
+ return "Symptom-Finding"
489
+ else:
490
+ return "Value"
491
+ elif isinstance(node, TriccNodeNote):
492
+ return "InteractSet"
493
+ elif isinstance(
494
+ node,
495
+ (
496
+ TriccNodeDecimal,
497
+ TriccNodeInteger,
498
+ TriccNodeText,
499
+ TriccNodeDate,
500
+ TriccNodeSelectOne,
501
+ TriccNodeSelectYesNo,
502
+ TriccNodeSelectNotAvailable,
503
+ TriccNodeInput,
504
+ ),
505
+ ):
506
+ return "Symptom-Finding"
507
+ elif isinstance(node, (TriccNodeProposedDiagnosis, TriccNodeDiagnosis)):
508
+ return "Diagnosis"
509
+ elif issubclass(node.__class__, TriccNodeCalculateBase):
510
+ return "Calculation"
511
+ else:
512
+ return "Misc"
428
513
 
429
514
 
430
515
  def get_select_options(diagram, select_node, nodes):
@@ -435,16 +520,12 @@ def get_select_options(diagram, select_node, nodes):
435
520
  for elm in list:
436
521
  name = elm.attrib.get("name")
437
522
  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
- )
523
+ logger.critical("Select question {0} have twice the option name {1}".format(select_node.get_name(), name))
443
524
  else:
444
525
  options_name_list.append(name)
445
-
526
+
446
527
  external_id = elm.attrib.get("id")
447
- id = get_id(external_id, diagram.attrib.get('id'))
528
+ id = get_id(external_id, diagram.attrib.get("id"))
448
529
  option = TriccNodeSelectOption(
449
530
  id=id,
450
531
  label=elm.attrib.get("label"),
@@ -453,8 +534,8 @@ def get_select_options(diagram, select_node, nodes):
453
534
  list_name=select_node.list_name,
454
535
  activity=select_node.activity,
455
536
  group=select_node.group,
456
- )
457
- set_additional_attributes(["save", "relevance"], elm, option)
537
+ )
538
+ set_additional_attributes(["save", "relevance", "context_type"], elm, option)
458
539
  load_expressions(option)
459
540
  options[i] = option
460
541
  nodes[id] = option
@@ -464,7 +545,7 @@ def get_select_options(diagram, select_node, nodes):
464
545
  else:
465
546
  return options
466
547
 
467
- ##TBR START
548
+ # TBR START
468
549
 
469
550
 
470
551
  def get_max_version(dict):
@@ -484,11 +565,10 @@ def get_max_named_version(calculates, name):
484
565
  return max
485
566
 
486
567
 
487
-
488
568
  def inject_bridge_path(node, nodes):
489
569
  calc_name = "p" + node.id
490
570
  calc_id = generate_id(calc_name)
491
-
571
+
492
572
  data = {
493
573
  "id": calc_id,
494
574
  "group": node.group,
@@ -501,22 +581,16 @@ def inject_bridge_path(node, nodes):
501
581
  nodes[n.source]
502
582
  for n in list(
503
583
  filter(
504
- lambda x: (x.target == node.id or x.target == node)
505
- and x.source in nodes,
584
+ lambda x: (x.target == node.id or x.target == node) and x.source in nodes,
506
585
  node.activity.edges,
507
586
  )
508
587
  )
509
588
  ]
510
- if ( len(prev_nodes)>1 and
511
- sum(
589
+ if (
590
+ len(prev_nodes) > 1
591
+ and sum(
512
592
  [
513
- (
514
- 0
515
- if issubclass(
516
- n.__class__, (TriccNodeDisplayCalculateBase, TriccNodeRhombus)
517
- )
518
- else 1
519
- )
593
+ (0 if issubclass(n.__class__, (TriccNodeDisplayCalculateBase, TriccNodeRhombus)) else 1)
520
594
  for n in prev_nodes
521
595
  ]
522
596
  )
@@ -545,7 +619,7 @@ def enrich_node(diagram, media_path, edge, node, activity, help_before=False):
545
619
  # get node and process type
546
620
  type, message = get_message(diagram, edge.source_external_id)
547
621
  if type is not None:
548
- if type == 'help':
622
+ if type == "help":
549
623
  help = TriccNodeMoreInfo(
550
624
  id=generate_id(),
551
625
  name=f"{node.name}.more_info",
@@ -553,7 +627,7 @@ def enrich_node(diagram, media_path, edge, node, activity, help_before=False):
553
627
  parent=node,
554
628
  required=None,
555
629
  )
556
- #node.help = message
630
+ # node.help = message
557
631
  if help_before:
558
632
  inject_node_before(help, node, activity)
559
633
  else:
@@ -585,34 +659,41 @@ def enrich_node(diagram, media_path, edge, node, activity, help_before=False):
585
659
  return None, None
586
660
  else:
587
661
  logger.warning(f"edge from an unsuported node {edge.source_external_id}")
588
-
662
+
589
663
  return None, None
590
664
 
591
665
 
592
- get_style_dict = lambda style: dict(item.split('=', 1) for item in style.split(';') if '=' in item)
666
+ def get_style_dict(style):
667
+ return dict(item.split("=", 1) for item in style.split(";") if "=" in item)
593
668
 
594
669
 
595
- def severity_from_color(color):
596
- if color == '#fff2cc':
597
- return 'moderate'
598
- elif color == '#f8cecc':
599
- return 'severe'
670
+ def severity_from_color(color):
671
+ if color == "#fff2cc":
672
+ return "moderate"
673
+ elif color == "#f8cecc":
674
+ return "severe"
600
675
  else:
601
- return 'light'
602
-
676
+ return "light"
603
677
 
604
678
 
605
679
  def add_tricc_base_node(
606
- diagram, nodes, type, list, group, attributes=[], mandatory_attributes=[], has_options=None
680
+ diagram,
681
+ nodes,
682
+ type,
683
+ list,
684
+ group,
685
+ attributes=[],
686
+ mandatory_attributes=[],
687
+ has_options=None,
607
688
  ):
608
689
  for elm in list:
609
690
  external_id = elm.attrib.get("id")
610
- id = get_id( external_id, diagram.attrib.get("id"))
611
- parent = elm.attrib.get("parent")
691
+ id = get_id(external_id, diagram.attrib.get("id"))
692
+ elm.attrib.get("parent")
612
693
  node = type(
613
694
  external_id=external_id,
614
695
  id=id,
615
- #parent=parent,
696
+ # parent=parent,
616
697
  group=group,
617
698
  activity=group,
618
699
  **set_mandatory_attribute(elm, mandatory_attributes, diagram),
@@ -630,102 +711,99 @@ def add_tricc_base_node(
630
711
  node.options = get_select_yes_no_options(node, group)
631
712
  nodes[node.options[0].id] = node.options[0]
632
713
  nodes[node.options[1].id] = node.options[1]
633
- elif type == TriccNodeProposedDiagnosis and getattr(node, 'severity', '') == None:
714
+ elif type == TriccNodeProposedDiagnosis and getattr(node, "severity", "") is None:
634
715
  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
-
716
+ styles = get_style_dict(mxcell.attrib.get("style", ""))
717
+ if "fillColor" in styles and styles["fillColor"] != "none":
718
+ node.severity = severity_from_color(styles["fillColor"])
719
+
641
720
  set_additional_attributes(attributes, elm, node)
642
721
  load_expressions(node)
643
722
  nodes[id] = node
644
723
 
645
724
 
646
725
  def load_expressions(node):
647
- if getattr(node, 'expression', None):
648
- node.expression = parse_expression('', node.expression)
649
- if getattr(node, 'relevance', None):
650
- node.relevance = parse_expression('', node.relevance)
651
- if getattr(node, 'default', None):
652
- node.default = parse_expression('', node.default)
653
- if getattr(node, 'reference', None):
726
+ if getattr(node, "constraint", None):
727
+ node.constraint = parse_expression("", node.constraint)
728
+ if getattr(node, "expression", None):
729
+ node.expression = parse_expression("", node.expression)
730
+ if getattr(node, "relevance", None):
731
+ node.relevance = parse_expression("", node.relevance)
732
+ if getattr(node, "trigger", None):
733
+ node.trigger = parse_expression("", node.trigger)
734
+ if getattr(node, "default", None):
735
+ node.default = parse_expression("", node.default)
736
+ if getattr(node, "reference", None):
654
737
  if isinstance(node, TriccNodeRhombus):
655
738
  node.label = remove_html(node.label)
656
739
  node.expression_reference = parse_expression(node.label, node.reference)
657
740
  else:
658
- node.expression_reference = parse_expression('', node.reference)
659
-
741
+ node.expression_reference = parse_expression("", node.reference)
742
+
660
743
  node.reference = node.expression_reference.get_references()
661
-
662
-
663
-
744
+
664
745
 
665
746
  def parse_expression(label=None, expression=None):
666
747
  if expression:
667
- ref_pattern = r'(\$\{[^\}]+\})'
668
- # only if simple ref
748
+ ref_pattern = r"(\$\{[^\}]+\})"
749
+ # only if simple ref
669
750
  if not re.search(ref_pattern, expression):
670
751
  operation = transform_cql_to_operation(expression, label)
671
752
  if isinstance(operation, TriccReference):
672
753
  if label:
673
- if label[0] == '[' and label[-1] == ']':
754
+ if label[0] == "[" and label[-1] == "]":
674
755
  operation = TriccOperation(
675
756
  operator=TriccOperator.SELECTED,
676
757
  reference=[
677
758
  operation,
678
- TriccReference(operation.value + label)
679
- ]
759
+ TriccReference(operation.value + label),
760
+ ],
680
761
  )
681
762
  else:
682
763
  for operator in OPERATION_LIST:
683
764
  if operator in label:
684
- if operator == '==':
685
- operator = '='
765
+ if operator == "==":
766
+ operator = "="
686
767
  terms = label.split(operator)
687
768
  operation = transform_cql_to_operation(
688
769
  f"{expression} {operator} {terms[1].replace('?', '').strip()}",
689
- label
770
+ label,
690
771
  )
691
772
  break
692
773
  # implied is true
693
774
  if isinstance(operation, TriccReference):
694
- operation = TriccOperation(
695
- operator=TriccOperator.ISTRUE,
696
- reference=[
697
- operation,
698
- ]
699
- )
700
-
775
+ operation = TriccOperation(
776
+ operator=TriccOperator.ISTRUE,
777
+ reference=[
778
+ operation,
779
+ ],
780
+ )
781
+
701
782
  else:
702
783
  pass
703
784
 
704
785
  else:
705
- operation = transform_cql_to_operation(
706
- expression.replace('${', '"').replace('}', '"'),
707
- label
708
- )
709
-
786
+ operation = transform_cql_to_operation(expression.replace("${", '"').replace("}", '"'), label)
787
+
710
788
  if operation is None:
711
789
  logger.warning(f"unable to parse: {expression} ")
712
790
  return expression
713
791
  return operation
714
-
792
+
715
793
 
716
794
  def set_mandatory_attribute(elm, mandatory_attributes, diagram=None):
717
795
  param = {}
718
- diagram_id = diagram.attrib.get('id')
796
+ diagram_id = diagram.attrib.get("id")
719
797
  for attributes in mandatory_attributes:
720
- if attributes == 'name':
798
+ if attributes == "name":
721
799
  name = elm.attrib.get("name")
722
800
  id = elm.attrib.get("id")
723
801
  attribute_value = _get_name(name, id, diagram_id)
724
- elif attributes == 'list_name':
802
+ elif attributes == "list_name":
725
803
  name = elm.attrib.get("name")
726
804
  id = elm.attrib.get("id")
727
- attribute_value = TRICC_LIST_NAME.format(clean_str(_get_name(name, id, diagram_id), replace_dots= True))
728
- else:
805
+ attribute_value = TRICC_LIST_NAME.format(clean_str(_get_name(name, id, diagram_id), replace_dots=True))
806
+ else:
729
807
  attribute_value = elm.attrib.get(attributes)
730
808
  if attribute_value is None:
731
809
  if elm.attrib.get("label") is not None:
@@ -734,21 +812,19 @@ def set_mandatory_attribute(elm, mandatory_attributes, diagram=None):
734
812
  display_name = elm.attrib.get("name")
735
813
  else:
736
814
  display_name = elm.attrib.get("id")
737
-
815
+
738
816
  if attributes == "source":
739
817
  if elm.attrib.get("target") is not None:
740
- logger.critical(
741
- "the attibute target is {}".format(elm.attrib.get("target"))
742
- )
818
+ logger.critical("the attibute target is {}".format(elm.attrib.get("target")))
743
819
  elif attributes == "target":
744
820
  if elm.attrib.get("source") is not None:
745
- logger.critical(
746
- "the attibute target is {}".format(elm.attrib.get("source"))
747
- )
748
-
821
+ logger.critical("the attibute target is {}".format(elm.attrib.get("source")))
822
+
749
823
  logger.critical(
750
824
  "the attibute {} is mandatory but not found in {} within group {}".format(
751
- attributes, display_name, diagram.attrib.get('name') if diagram is not None else ""
825
+ attributes,
826
+ display_name,
827
+ diagram.attrib.get("name") if diagram is not None else "",
752
828
  )
753
829
  )
754
830
  exit(1)
@@ -770,15 +846,15 @@ def clean_link(link):
770
846
  return link_parts[1]
771
847
 
772
848
 
773
- def get_groups(diagram, nodes, parent_group):
849
+ def get_groups(diagram, nodes, activity):
774
850
  groups = {}
775
851
  list = get_tricc_type_list(diagram, "object", TriccNodeType.page)
776
852
  for elm in list:
777
- add_group(elm, diagram, nodes, groups, parent_group)
853
+ add_group(elm, diagram, nodes, groups, activity, activity)
778
854
  return groups
779
855
 
780
856
 
781
- def add_group(elm, diagram, nodes, groups, parent_group):
857
+ def add_group(elm, diagram, nodes, groups, parent_group, activity):
782
858
  external_id = elm.attrib.get("id")
783
859
  id = get_id(external_id, diagram.attrib.get("id"))
784
860
  if id not in groups:
@@ -788,11 +864,10 @@ def add_group(elm, diagram, nodes, groups, parent_group):
788
864
  id=id,
789
865
  external_id=external_id,
790
866
  group=parent_group,
867
+ activity=activity,
791
868
  )
792
869
  # get elememt witn parent = id and tricc_type defiend
793
- list_child = get_tricc_type_list(
794
- diagram, ["object", "UserObject"], tricc_type=None, parent_id=id
795
- )
870
+ list_child = get_tricc_type_list(diagram, ["object", "UserObject"], tricc_type=None, parent_id=id)
796
871
  add_group_to_child(group, diagram, list_child, nodes, groups, parent_group)
797
872
  if group is not None:
798
873
  groups[group.id] = group
@@ -808,12 +883,10 @@ def add_group_to_child(group, diagram, list_child, nodes, groups, parent_group):
808
883
  tricc_type=None,
809
884
  parent_id=child_elm.attrib.get("id"),
810
885
  )
811
- add_group_to_child(
812
- group, diagram, list_sub_child, nodes, groups, parent_group
813
- )
886
+ add_group_to_child(group, diagram, list_sub_child, nodes, groups, parent_group)
814
887
  elif child_elm.attrib.get("tricc_type") == TriccNodeType.page:
815
888
  child_group_id = child_elm.attrib.get("id")
816
- if not child_group_id in groups:
889
+ if child_group_id not in groups:
817
890
  child_group = add_group(child_elm, diagram, nodes, groups, group)
818
891
  else:
819
892
  child_group = groups[child_group_id]
@@ -838,16 +911,14 @@ def add_image_from_style(style, path):
838
911
  image_attrib = None
839
912
  if style is not None and "image=data:image/" in style:
840
913
  image_attrib = style.split("image=data:image/")
841
- if image_attrib is not None and len(image_attrib)== 2:
914
+ if image_attrib is not None and len(image_attrib) == 2:
842
915
  image_parts = image_attrib[1].split(",")
843
916
  if len(image_parts) == 2:
844
917
  payload = image_parts[1][:-1]
845
- image_name = hashlib.md5(payload.encode('utf-8')).hexdigest()
918
+ image_name = hashlib.md5(payload.encode("utf-8")).hexdigest()
846
919
  path = os.path.join(path, "images")
847
920
  file_name = os.path.join(path, image_name + "." + image_parts[0])
848
- if not (
849
- os.path.isdir(path)
850
- ): # check if it exists, because if it does, error will be raised
921
+ if not (os.path.isdir(path)): # check if it exists, because if it does, error will be raised
851
922
  # (later change to make folder complaint to CHT)
852
923
  os.makedirs(path, exist_ok=True)
853
924
  with open(file_name, "wb") as fh:
@@ -857,13 +928,6 @@ def add_image_from_style(style, path):
857
928
  return None, None
858
929
 
859
930
 
860
- def get_contained_main_node(diagram, id):
861
- list = get_mxcell_parent_list(diagram, id, media_nodes)
862
- if isinstance(list, List) and len(list) > 0:
863
- # use only the first one
864
- return list[0]
865
-
866
-
867
931
  def get_message(diagram, id):
868
932
  elm = get_elm(diagram, id)
869
933
  if elm is not None:
@@ -884,14 +948,12 @@ def get_edges(diagram):
884
948
  id = get_id(external_id, diagram.attrib.get("id"))
885
949
  edge = TriccEdge(
886
950
  id=id,
887
- **set_mandatory_attribute(
888
- elm, ["source", "parent", "target"], diagram
889
- ),
951
+ **set_mandatory_attribute(elm, ["source", "parent", "target"], diagram),
890
952
  )
891
953
  edge.source_external_id = edge.source
892
954
  edge.target_external_id = edge.target
893
- edge.source = get_id(edge.source, diagram.attrib.get("id"))
894
- edge.target = get_id(edge.target, diagram.attrib.get("id"))
955
+ edge.source = get_id(edge.source, diagram.attrib.get("id"))
956
+ edge.target = get_id(edge.target, diagram.attrib.get("id"))
895
957
  set_additional_attributes(["value"], elm, edge)
896
958
  if edge.value is not None:
897
959
  edge.value = remove_html(edge.value)
@@ -899,7 +961,7 @@ def get_edges(diagram):
899
961
  return edges
900
962
 
901
963
 
902
- ## Process edges
964
+ # Process edges
903
965
 
904
966
 
905
967
  def process_factor_edge(edge, nodes):
@@ -908,7 +970,10 @@ def process_factor_edge(edge, nodes):
908
970
  source = nodes[edge.source]
909
971
  return TriccNodeCalculate(
910
972
  id=edge.id,
911
- expression_reference=TriccOperation(TriccOperator.MULTIPLIED, [TriccReference(nodes[edge.source].name),TriccStatic(float(factor))]),
973
+ expression_reference=TriccOperation(
974
+ TriccOperator.MULTIPLIED,
975
+ [TriccReference(nodes[edge.source].name), TriccStatic(float(factor))],
976
+ ),
912
977
  reference=[TriccReference(source.name)],
913
978
  activity=source.activity,
914
979
  group=source.group,
@@ -917,15 +982,20 @@ def process_factor_edge(edge, nodes):
917
982
  return None
918
983
 
919
984
 
920
- def process_condition_edge(edge, nodes):
921
- label = edge.value.strip()
922
- node = nodes[edge.source]
923
- node_ref = f'"{nodes[edge.source].name}"'
924
- if '$this' in label:
925
- operation = parse_expression('', expression=label.replace('$this', node_ref ))
985
+ def process_condition_edge(edge, label, nodes):
986
+ source = nodes[edge.source]
987
+ node_ref = f'"{source.name}"'
988
+ if "$this" in label:
989
+ operation = parse_expression("", expression=label.replace("$this", node_ref))
926
990
  else:
927
991
  operation = parse_expression(label, expression=node_ref)
992
+
928
993
  if operation and isinstance(operation, TriccOperation):
994
+ # special management for simple operation
995
+ if issubclass(source.__class__, TriccNodeSelect) and "$this" not in label:
996
+ operation.replace_node(
997
+ TriccReference(source.name),
998
+ get_count_terms_details(source, None, False))
929
999
  # insert rhombus
930
1000
  return TriccNodeRhombus(
931
1001
  id=edge.id,
@@ -934,13 +1004,10 @@ def process_condition_edge(edge, nodes):
934
1004
  path=nodes[edge.source],
935
1005
  activity=nodes[edge.source].activity,
936
1006
  group=nodes[edge.source].group,
937
- label=label
1007
+ label=label,
938
1008
  )
939
1009
 
940
1010
 
941
-
942
-
943
-
944
1011
  def process_exclusive_edge(edge, nodes):
945
1012
  error = None
946
1013
  if issubclass(nodes[edge.source].__class__, TriccNodeCalculateBase):
@@ -971,9 +1038,7 @@ def process_exclusive_edge(edge, nodes):
971
1038
 
972
1039
  def process_yesno_edge(edge, nodes):
973
1040
  if not edge.value:
974
- logger.critical(
975
- "yesNo {} node with labelless edges".format(nodes[edge.source].get_name())
976
- )
1041
+ logger.critical("yesNo {} node with labelless edges".format(nodes[edge.source].get_name()))
977
1042
  exit(1)
978
1043
  label = edge.value.strip().lower()
979
1044
  yes_option = None
@@ -992,8 +1057,4 @@ def process_yesno_edge(edge, nodes):
992
1057
  edge.source = no_option.id
993
1058
  edge.source_external_id = None
994
1059
  else:
995
- logger.warning(
996
- "edge {0} is coming from select {1}".format(
997
- edge.id, nodes[edge.source].get_name()
998
- )
999
- )
1060
+ logger.warning("edge {0} is coming from select {1}".format(edge.id, nodes[edge.source].get_name()))