tricc-oo 1.5.26__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.
tests/build.py CHANGED
@@ -6,6 +6,7 @@ from tricc_oo.strategies.output.xls_form import XLSFormStrategy # noqa: F401
6
6
  from tricc_oo.strategies.output.openmrs_form import OpenMRSStrategy # noqa: F401
7
7
  from tricc_oo.strategies.output.fhir_form import FHIRStrategy # noqa: F401
8
8
  from tricc_oo.strategies.output.html_form import HTMLStrategy # noqa: F401
9
+ from tricc_oo.strategies.output.dhis2_form import DHIS2Strategy # noqa: F401
9
10
  from tricc_oo.strategies.input.drawio import DrawioStrategy # noqa: F401
10
11
  import getopt
11
12
  import logging
tests/test_build.py ADDED
@@ -0,0 +1,260 @@
1
+ import unittest
2
+ import subprocess
3
+ import sys
4
+ import os
5
+ import tempfile
6
+ import shutil
7
+ from pathlib import Path
8
+ import pandas as pd
9
+ from pyxform import create_survey_from_xls
10
+
11
+
12
+ class TestBuildScript(unittest.TestCase):
13
+ """Test cases for the build.py script with different argument combinations."""
14
+
15
+ def setUp(self):
16
+ """Set up test fixtures."""
17
+ self.test_data_dir = Path(__file__).parent / "data"
18
+ self.test_output_dir = Path(__file__).parent / "output"
19
+ self.demo_file = self.test_data_dir / "demo.drawio"
20
+
21
+ # Ensure test data exists
22
+ self.assertTrue(self.demo_file.exists(), f"Test data file {self.demo_file} does not exist")
23
+
24
+ def run_build_script(self, args):
25
+ """Helper method to run build.py with given arguments."""
26
+ cmd = [sys.executable, str(Path(__file__).parent / "build.py")] + args
27
+ result = subprocess.run(cmd, capture_output=True, text=True, cwd=Path(__file__).parent.parent)
28
+ return result
29
+
30
+ def test_basic_build_with_demo_file(self):
31
+ """Test basic build with demo.drawio file."""
32
+ with tempfile.TemporaryDirectory() as temp_dir:
33
+ args = [
34
+ "-i", str(self.demo_file),
35
+ "-o", temp_dir,
36
+ "-l", "i"
37
+ ]
38
+ result = self.run_build_script(args)
39
+ self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
40
+
41
+ def test_build_with_directory_input(self):
42
+ """Test build with directory containing drawio files."""
43
+ with tempfile.TemporaryDirectory() as temp_dir:
44
+ args = [
45
+ "-i", str(self.test_data_dir),
46
+ "-o", temp_dir,
47
+ "-l", "i"
48
+ ]
49
+ result = self.run_build_script(args)
50
+ self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
51
+
52
+ def test_build_with_xlsform_strategy(self):
53
+ """Test build with XLSFormStrategy (default)."""
54
+ with tempfile.TemporaryDirectory() as temp_dir:
55
+ args = [
56
+ "-i", str(self.demo_file),
57
+ "-o", temp_dir,
58
+ "-O", "XLSFormStrategy",
59
+ "-l", "i"
60
+ ]
61
+ result = self.run_build_script(args)
62
+ self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
63
+
64
+ def test_build_with_html_strategy(self):
65
+ """Test build with HTMLStrategy."""
66
+ with tempfile.TemporaryDirectory() as temp_dir:
67
+ args = [
68
+ "-i", str(self.demo_file),
69
+ "-o", temp_dir,
70
+ "-O", "HTMLStrategy",
71
+ "-l", "i"
72
+ ]
73
+ result = self.run_build_script(args)
74
+ self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
75
+
76
+ def test_build_with_fhir_strategy(self):
77
+ """Test build with FHIRStrategy."""
78
+ with tempfile.TemporaryDirectory() as temp_dir:
79
+ args = [
80
+ "-i", str(self.demo_file),
81
+ "-o", temp_dir,
82
+ "-O", "FHIRStrategy",
83
+ "-l", "i"
84
+ ]
85
+ result = self.run_build_script(args)
86
+ self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
87
+
88
+ def test_build_with_dhis2_strategy(self):
89
+ """Test build with DHIS2Strategy."""
90
+ with tempfile.TemporaryDirectory() as temp_dir:
91
+ args = [
92
+ "-i", str(self.demo_file),
93
+ "-o", temp_dir,
94
+ "-O", "DHIS2Strategy",
95
+ "-l", "i"
96
+ ]
97
+ result = self.run_build_script(args)
98
+ self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
99
+
100
+ def test_build_with_openmrs_strategy(self):
101
+ """Test build with OpenMRSStrategy."""
102
+ with tempfile.TemporaryDirectory() as temp_dir:
103
+ args = [
104
+ "-i", str(self.demo_file),
105
+ "-o", temp_dir,
106
+ "-O", "OpenMRSStrategy",
107
+ "-l", "i"
108
+ ]
109
+ result = self.run_build_script(args)
110
+ self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
111
+
112
+ def test_build_with_cht_strategy(self):
113
+ """Test build with XLSFormCHTStrategy."""
114
+ with tempfile.TemporaryDirectory() as temp_dir:
115
+ args = [
116
+ "-i", str(self.demo_file),
117
+ "-o", temp_dir,
118
+ "-O", "XLSFormCHTStrategy",
119
+ "-l", "i"
120
+ ]
121
+ result = self.run_build_script(args)
122
+ self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
123
+
124
+ def test_build_with_cht_hf_strategy(self):
125
+ """Test build with XLSFormCHTHFStrategy."""
126
+ with tempfile.TemporaryDirectory() as temp_dir:
127
+ args = [
128
+ "-i", str(self.demo_file),
129
+ "-o", temp_dir,
130
+ "-O", "XLSFormCHTHFStrategy",
131
+ "-l", "i"
132
+ ]
133
+ result = self.run_build_script(args)
134
+ self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
135
+
136
+ def test_build_with_cdss_strategy(self):
137
+ """Test build with XLSFormCDSSStrategy."""
138
+ with tempfile.TemporaryDirectory() as temp_dir:
139
+ args = [
140
+ "-i", str(self.demo_file),
141
+ "-o", temp_dir,
142
+ "-O", "XLSFormCDSSStrategy",
143
+ "-l", "i"
144
+ ]
145
+ result = self.run_build_script(args)
146
+ self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
147
+
148
+ # def test_build_with_spice_strategy(self):
149
+ # """Test build with SpiceStrategy."""
150
+ # with tempfile.TemporaryDirectory() as temp_dir:
151
+ # args = [
152
+ # "-i", str(self.demo_file),
153
+ # "-o", temp_dir,
154
+ # "-O", "SpiceStrategy",
155
+ # "-l", "i"
156
+ # ]
157
+ # result = self.run_build_script(args)
158
+ # self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
159
+
160
+ def test_build_with_debug_level(self):
161
+ """Test build with debug logging level."""
162
+ with tempfile.TemporaryDirectory() as temp_dir:
163
+ args = [
164
+ "-i", str(self.demo_file),
165
+ "-o", temp_dir,
166
+ "-l", "d"
167
+ ]
168
+ result = self.run_build_script(args)
169
+ self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
170
+
171
+ def test_build_with_trad_option(self):
172
+ """Test build with translation option."""
173
+ with tempfile.TemporaryDirectory() as temp_dir:
174
+ args = [
175
+ "-i", str(self.demo_file),
176
+ "-o", temp_dir,
177
+ "-t",
178
+ "-l", "i"
179
+ ]
180
+ result = self.run_build_script(args)
181
+ self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
182
+
183
+ def test_build_with_form_id(self):
184
+ """Test build with custom form ID."""
185
+ with tempfile.TemporaryDirectory() as temp_dir:
186
+ args = [
187
+ "-i", str(self.demo_file),
188
+ "-o", temp_dir,
189
+ "-d", "test_form_123",
190
+ "-l", "i"
191
+ ]
192
+ result = self.run_build_script(args)
193
+ self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
194
+
195
+ def test_build_missing_input(self):
196
+ """Test build with missing input file."""
197
+ args = ["-o", "/tmp/test_output"]
198
+ result = self.run_build_script(args)
199
+ self.assertNotEqual(result.returncode, 0, "Build should fail with missing input")
200
+
201
+ def test_build_invalid_input_file(self):
202
+ """Test build with invalid input file."""
203
+ with tempfile.TemporaryDirectory() as temp_dir:
204
+ args = [
205
+ "-i", "/nonexistent/file.drawio",
206
+ "-o", temp_dir,
207
+ "-l", "i"
208
+ ]
209
+ result = self.run_build_script(args)
210
+ self.assertNotEqual(result.returncode, 0, "Build should fail with invalid input file")
211
+
212
+ def validate_xls_form(self, xls_path):
213
+ """Helper method to validate XLS form using ODK libraries."""
214
+ try:
215
+ # Convert XLS to XML using pyxform
216
+ survey = create_survey_from_xls(xls_path)
217
+ xml_output = survey.to_xml()
218
+
219
+ # Basic validation - check if XML was generated successfully
220
+ # In a real scenario, you might want to use odk_validate command line tool
221
+ if xml_output and len(xml_output.strip()) > 0:
222
+ return True, "Validation successful"
223
+ else:
224
+ return False, "Empty XML output"
225
+ except Exception as e:
226
+ return False, str(e)
227
+
228
+ def test_xlsform_strategy_validation(self):
229
+ """Test XLSFormStrategy output validation with ODK libraries."""
230
+ with tempfile.TemporaryDirectory() as temp_dir:
231
+ xls_output = Path(temp_dir) / "demo_tricc.xlsx"
232
+ args = [
233
+ "-i", str(self.demo_file),
234
+ "-o", temp_dir,
235
+ "-O", "XLSFormStrategy",
236
+ "-l", "i"
237
+ ]
238
+ result = self.run_build_script(args)
239
+ self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
240
+
241
+ # Check if XLS file was created
242
+ self.assertTrue(xls_output.exists(), f"XLS file {xls_output} was not created")
243
+
244
+ def test_xlsform_cdss_strategy_validation(self):
245
+ """Test XLSFormCDSSStrategy output validation with ODK libraries."""
246
+ with tempfile.TemporaryDirectory() as temp_dir:
247
+ xls_output = Path(temp_dir) / "demo_tricc.xlsx"
248
+ args = [
249
+ "-i", str(self.demo_file),
250
+ "-o", temp_dir,
251
+ "-O", "XLSFormCDSSStrategy",
252
+ "-l", "i"
253
+ ]
254
+ result = self.run_build_script(args)
255
+ self.assertEqual(result.returncode, 0, f"Build failed: {result.stderr}")
256
+
257
+
258
+
259
+ if __name__ == "__main__":
260
+ unittest.main()
@@ -1,6 +1,6 @@
1
1
  import logging
2
- from tricc_oo.converters.utils import clean_name, clean_str
3
- from tricc_oo.models.tricc import TriccNodeSelectOption, TRICC_TRUE_VALUE, TRICC_FALSE_VALUE
2
+ from tricc_oo.converters.utils import clean_name
3
+ from tricc_oo.models.tricc import TriccNodeSelectOption, TRICC_TRUE_VALUE, TRICC_FALSE_VALUE, TriccNodeActivity
4
4
  from tricc_oo.models.calculate import TriccNodeInput
5
5
  from tricc_oo.models.base import TriccNodeBaseModel, TriccStatic, TriccReference
6
6
 
@@ -43,6 +43,8 @@ def get_export_name(node, replace_dots=True):
43
43
  value = node.name
44
44
  elif isinstance(node, TriccStatic):
45
45
  value = node.value
46
+ if isinstance(value, TriccNodeSelectOption):
47
+ value = value.name
46
48
  else:
47
49
  value = node
48
50
  if isinstance(value, bool): # or r.value in ('true', 'false')
@@ -51,10 +53,12 @@ def get_export_name(node, replace_dots=True):
51
53
  export_name = BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]
52
54
  elif value == TRICC_FALSE_VALUE:
53
55
  export_name = BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]
54
- elif isinstance(value, str):
55
- export_name = f"'{clean_str(value, replace_dots=replace_dots)}'"
56
+ elif value == '$this':
57
+ export_name = '.'
58
+ elif isinstance(value, str) and not isinstance(node, str):
59
+ export_name = f"'{value}'"
56
60
  else:
57
- export_name = str(value)
61
+ export_name = value
58
62
  if hasattr(node, 'export_name'):
59
63
  node.export_name = export_name
60
64
  return export_name
@@ -62,7 +66,12 @@ def get_export_name(node, replace_dots=True):
62
66
  return node
63
67
  else:
64
68
  node.gen_name()
65
- if isinstance(node, TriccNodeSelectOption):
69
+ if isinstance(node, TriccNodeActivity) and getattr(node, 'instance', 1) > 1:
70
+ node.export_name = clean_name(
71
+ node.name + INSTANCE_SEPARATOR + str(node.instance),
72
+ replace_dots=replace_dots,
73
+ )
74
+ elif isinstance(node, TriccNodeSelectOption):
66
75
  node.export_name = node.name
67
76
  elif node.last is False:
68
77
  node.export_name = clean_name(
@@ -7,7 +7,7 @@ import re
7
7
 
8
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.converters.utils import generate_id, get_rand_name
10
+ from tricc_oo.converters.utils import generate_id
11
11
  from tricc_oo.models.base import (
12
12
  TriccOperator, TriccOperation,
13
13
  TriccStatic, TriccReference, TriccNodeType, TriccEdge, OPERATION_LIST
@@ -101,16 +101,16 @@ def create_activity(diagram, media_path, project):
101
101
 
102
102
  external_id = diagram.attrib.get("id")
103
103
  id = get_id(external_id, diagram.attrib.get("id"))
104
- root = create_root_node(diagram)
105
- name = diagram.attrib.get("name")
104
+ root, name = create_root_node(diagram)
105
+ label = diagram.attrib.get("name")
106
106
  form_id = diagram.attrib.get("name", None)
107
107
  if root is not None:
108
108
  activity = TriccNodeActivity(
109
109
  root=root,
110
- name=get_rand_name(f"a{id}"),
110
+ name=name, # start node 'name' is saved in label
111
111
  id=id,
112
112
  external_id=external_id,
113
- label=name,
113
+ label=label,
114
114
  form_id=form_id,
115
115
  )
116
116
  if root.relevance is not None:
@@ -130,7 +130,7 @@ def create_activity(diagram, media_path, project):
130
130
  if (
131
131
  issubclass(n.__class__, (TriccNodeDisplayModel, TriccNodeDisplayCalculateBase))
132
132
  and not isinstance(n, (TriccRhombusMixIn, TriccNodeRhombus, TriccNodeDisplayBridge))
133
- and not n.name.startswith("label_")
133
+ and not n.name.startswith("label_") # FIXME
134
134
  ):
135
135
  system = n.name.split(".")[0] if "." in n.name else "tricc"
136
136
  if isinstance(n, TriccNodeSelectOption) and isinstance(n.select, TriccNodeSelectNotAvailable):
@@ -444,17 +444,18 @@ def create_root_node(diagram):
444
444
  if elm is not None:
445
445
  external_id = elm.attrib.get("id")
446
446
  id = get_id(external_id, diagram.attrib.get("id"))
447
+ name = generate_id("start"+external_id)
447
448
  node = TriccNodeActivityStart(
448
449
  id=id,
449
450
  external_id=external_id,
450
451
  # parent=elm.attrib.get("parent"),
451
- name="ma" + id,
452
+ name=name,
452
453
  label=diagram.attrib.get("name"),
453
454
  relevance=elm.attrib.get("relevance"),
454
455
  instance=int(elm.attrib.get("instance") if elm.attrib.get("instance") is not None else 1),
455
456
  )
456
457
  load_expressions(node)
457
- return node
458
+ return node, _get_name(elm.attrib.get("name", "act_"), external_id, diagram.attrib.get("id")) if node else None
458
459
 
459
460
 
460
461
  # converter XML item to object
tricc_oo/models/base.py CHANGED
@@ -11,7 +11,7 @@ from tricc_oo.models.ordered_set import OrderedSet
11
11
 
12
12
  logger = logging.getLogger("default")
13
13
 
14
- Expression = Annotated[str, StringConstraints(pattern=r"^[^\\/\:]+$")]
14
+ Expression = Annotated[str, StringConstraints(pattern=r".+")]
15
15
 
16
16
  triccId = Annotated[str, StringConstraints(pattern=r"^[^\\/\: ]+$")]
17
17
 
@@ -274,7 +274,7 @@ class TriccNodeBaseModel(TriccBaseModel):
274
274
 
275
275
 
276
276
  class TriccStatic(BaseModel):
277
- value: Union[str, float, int, bool]
277
+ value: Union[str, float, int, bool, TriccNodeBaseModel]
278
278
 
279
279
  def __init__(self, value):
280
280
  super().__init__(value=value)
@@ -494,6 +494,8 @@ class TriccOperation(BaseModel):
494
494
  return "mixed"
495
495
  else:
496
496
  return rtype.pop()
497
+ else:
498
+ return self.get_reference_datatype(self.reference)
497
499
 
498
500
  def get_reference_datatype(self, references):
499
501
  rtype = set()
@@ -6,16 +6,17 @@ from tricc_oo.converters.tricc_to_xls_form import (
6
6
  get_export_name, BOOLEAN_MAP
7
7
  )
8
8
  from tricc_oo.models.lang import SingletonLangClass
9
- from tricc_oo.converters.utils import clean_name, remove_html
9
+ from tricc_oo.converters.utils import clean_name, remove_html, generate_id
10
10
  from tricc_oo.models.base import (
11
11
  TriccOperator,
12
12
  TriccOperation, TriccStatic, TriccReference, and_join, TriccNodeType
13
13
  )
14
14
  from tricc_oo.models.calculate import (
15
15
  TriccNodeDisplayCalculateBase,
16
+ TriccNodeCalculate
16
17
  )
17
18
  from tricc_oo.models.tricc import (
18
- TriccNodeActivity, TriccNodeBaseModel, TriccNodeSelectMultiple, TriccNodeSelectOption,
19
+ TriccNodeBaseModel, TriccNodeSelectMultiple, TriccNodeSelectOption,
19
20
  TriccNodeSelectOne,
20
21
  TriccNodeSelect,
21
22
  TriccNodeMoreInfo,
@@ -39,6 +40,9 @@ langs = SingletonLangClass()
39
40
  TRICC_CALC_EXPRESSION = "${{{0}}}>0"
40
41
 
41
42
 
43
+ def get_export_group_name(in_node): return f"gcalc_{get_export_name(in_node)}"
44
+
45
+
42
46
  def start_group(
43
47
  strategy,
44
48
  cur_group,
@@ -58,10 +62,27 @@ def start_group(
58
62
 
59
63
  else:
60
64
  groups[name] = 0
61
- is_activity = isinstance(cur_group, TriccNodeActivity)
62
65
  relevance = relevance and cur_group.relevance is not None and cur_group.relevance != ""
63
-
64
- group_calc_required = False and relevance and not is_activity and len(relevance) > 100
66
+ past_instances = len(getattr(cur_group.base_instance, "instances", []))
67
+ group_calc_required = relevance is not None and (len(str(relevance)) > 100 or past_instances > 1)
68
+ calc = None
69
+ if group_calc_required and getattr(cur_group.relevance, 'operator', None) != TriccOperator.ISTRUE:
70
+
71
+ calc = TriccNodeCalculate(
72
+ id=generate_id(get_export_group_name(name)),
73
+ group=cur_group,
74
+ activity=cur_group.activity,
75
+ name=get_export_group_name(name),
76
+ expression=cur_group.relevance
77
+ )
78
+ if calc not in cur_group.calculates:
79
+ cur_group.calculates.append(calc)
80
+ processed_nodes.add(calc)
81
+
82
+ cur_group.relevance = TriccOperation(
83
+ TriccOperator.ISTRUE,
84
+ [calc]
85
+ )
65
86
 
66
87
  relevance_expression = cur_group.relevance
67
88
  relevance_expression = get_applicability_expression(cur_group, processed_nodes, process, relevance_expression)
@@ -69,14 +90,9 @@ def start_group(
69
90
  relevance_expression = get_process_skip_expression(cur_group, processed_nodes, process, relevance_expression)
70
91
 
71
92
  if not relevance:
72
- relevance_expression = ""
93
+ relevance_expression_str = ""
73
94
  elif isinstance(relevance_expression, (TriccOperation, TriccStatic)):
74
- relevance_expression = strategy.get_tricc_operation_expression(relevance_expression)
75
-
76
- # elif is_activity:
77
- # relevance_expression = TRICC_CALC_EXPRESSION.format(get_export_name(cur_group.root))
78
- elif group_calc_required:
79
- relevance_expression = TRICC_CALC_EXPRESSION.format("gcalc_" + name)
95
+ relevance_expression_str = strategy.get_tricc_operation_expression(relevance_expression)
80
96
 
81
97
  # group
82
98
  values = []
@@ -91,23 +107,23 @@ def start_group(
91
107
  if relevance_expression is True:
92
108
  values.append("")
93
109
  else:
94
- values.append(relevance_expression)
110
+ values.append(relevance_expression_str)
95
111
 
96
112
  else:
97
113
  values.append(get_xfrom_trad(strategy, cur_group, column, SURVEY_MAP))
98
114
  df_survey.loc[len(df_survey)] = values
99
115
 
100
116
  # calc
101
- if group_calc_required and len(df_calculate[df_calculate["name"] == "gcalc_" + name]) == 0:
117
+ if calc and len(df_calculate[df_calculate["name"] == get_export_group_name(name)]) == 0:
102
118
  calc_values = []
103
119
  for column in SURVEY_MAP:
104
120
  if column == "type":
105
121
  calc_values.append("calculate")
106
122
  elif column == "name":
107
- value = "gcalc_" + name
123
+ value = get_export_name(calc)
108
124
  calc_values.append(value)
109
125
  elif column == "calculation":
110
- calc_values.append(get_attr_if_exists(strategy, cur_group, "relevance", SURVEY_MAP))
126
+ calc_values.append(f"number({strategy.get_tricc_operation_expression(calc.expression)}")
111
127
  elif column == "relevance":
112
128
  calc_values.append("")
113
129
  else:
@@ -376,7 +392,7 @@ def get_attr_if_exists(strategy, node, column, map_array):
376
392
 
377
393
  elif isinstance(value, (TriccOperation, TriccStatic, TriccReference)):
378
394
  expression = strategy.get_tricc_operation_expression(value)
379
- return expression.replace("$this", ".") if isinstance(expression, str) else expression
395
+ return expression
380
396
  elif value is not None:
381
397
  return str(value) if not isinstance(value, dict) else value
382
398
  else:
@@ -399,7 +415,7 @@ def get_more_info_select(strategy, node):
399
415
  if column == "type":
400
416
  values.append("select_one more_info")
401
417
  elif column == "label":
402
- values.append("NO_LABEL")
418
+ values.append(strategy.get_empty_label())
403
419
  elif column == "name":
404
420
  values.append(get_export_name(node) + "_optin")
405
421
  elif column == "hint":
@@ -43,6 +43,9 @@ class BaseOutPutStrategy:
43
43
 
44
44
  self.export(self.project.start_pages, version=version)
45
45
 
46
+ logger.info("validate the output")
47
+ self.validate()
48
+
46
49
  # walking function
47
50
  def process_base(self, start_pages, **kwargs):
48
51
  # for each node, check if condition is required issubclass(TriccNodeDisplayModel)
@@ -106,6 +109,10 @@ class BaseOutPutStrategy:
106
109
  def export(self, **kwargs):
107
110
  pass
108
111
 
112
+ @abc.abstractmethod
113
+ def validate(self):
114
+ pass
115
+
109
116
  def tricc_operation_equal(self, ref_expressions):
110
117
  # r[0] = r[1]
111
118
  raise NotImplementedError("This type of opreration is not supported in this strategy")