tricc-oo 1.0.1__py3-none-any.whl → 1.4.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tests/build.py +213 -0
- tests/test_cql.py +197 -0
- tests/to_ocl.py +51 -0
- {tricc → tricc_oo}/__init__.py +3 -1
- tricc_oo/converters/codesystem_to_ocl.py +169 -0
- tricc_oo/converters/cql/cqlLexer.py +822 -0
- tricc_oo/converters/cql/cqlListener.py +1632 -0
- tricc_oo/converters/cql/cqlParser.py +11204 -0
- tricc_oo/converters/cql/cqlVisitor.py +913 -0
- tricc_oo/converters/cql_to_operation.py +402 -0
- tricc_oo/converters/datadictionnary.py +115 -0
- tricc_oo/converters/drawio_type_map.py +222 -0
- tricc_oo/converters/tricc_to_xls_form.py +61 -0
- tricc_oo/converters/utils.py +65 -0
- tricc_oo/converters/xml_to_tricc.py +1003 -0
- tricc_oo/models/__init__.py +4 -0
- tricc_oo/models/base.py +732 -0
- tricc_oo/models/calculate.py +216 -0
- tricc_oo/models/ocl.py +281 -0
- tricc_oo/models/ordered_set.py +125 -0
- tricc_oo/models/tricc.py +418 -0
- tricc_oo/parsers/xml.py +138 -0
- tricc_oo/serializers/__init__.py +0 -0
- tricc_oo/serializers/xls_form.py +745 -0
- tricc_oo/strategies/__init__.py +0 -0
- tricc_oo/strategies/input/__init__.py +0 -0
- tricc_oo/strategies/input/base_input_strategy.py +111 -0
- tricc_oo/strategies/input/drawio.py +317 -0
- tricc_oo/strategies/output/base_output_strategy.py +148 -0
- tricc_oo/strategies/output/spice.py +365 -0
- tricc_oo/strategies/output/xls_form.py +697 -0
- tricc_oo/strategies/output/xlsform_cdss.py +189 -0
- tricc_oo/strategies/output/xlsform_cht.py +200 -0
- tricc_oo/strategies/output/xlsform_cht_hf.py +334 -0
- tricc_oo/visitors/__init__.py +0 -0
- tricc_oo/visitors/tricc.py +2198 -0
- tricc_oo/visitors/utils.py +17 -0
- tricc_oo/visitors/xform_pd.py +264 -0
- tricc_oo-1.4.15.dist-info/METADATA +219 -0
- tricc_oo-1.4.15.dist-info/RECORD +46 -0
- {tricc_oo-1.0.1.dist-info → tricc_oo-1.4.15.dist-info}/WHEEL +1 -1
- tricc_oo-1.4.15.dist-info/top_level.txt +2 -0
- tricc/converters/mc_to_tricc.py +0 -542
- tricc/converters/tricc_to_xls_form.py +0 -553
- tricc/converters/utils.py +0 -44
- tricc/converters/xml_to_tricc.py +0 -740
- tricc/models/tricc.py +0 -1093
- tricc/parsers/xml.py +0 -81
- tricc/serializers/xls_form.py +0 -364
- tricc/strategies/input/base_input_strategy.py +0 -80
- tricc/strategies/input/drawio.py +0 -246
- tricc/strategies/input/medalcreator.py +0 -168
- tricc/strategies/output/base_output_strategy.py +0 -92
- tricc/strategies/output/xls_form.py +0 -194
- tricc/strategies/output/xlsform_cdss.py +0 -46
- tricc/strategies/output/xlsform_cht.py +0 -106
- tricc/visitors/tricc.py +0 -375
- tricc_oo-1.0.1.dist-info/LICENSE +0 -78
- tricc_oo-1.0.1.dist-info/METADATA +0 -229
- tricc_oo-1.0.1.dist-info/RECORD +0 -26
- tricc_oo-1.0.1.dist-info/top_level.txt +0 -2
- venv/bin/vba_extract.py +0 -78
- {tricc → tricc_oo}/converters/__init__.py +0 -0
- {tricc → tricc_oo}/models/lang.py +0 -0
- {tricc/serializers → tricc_oo/parsers}/__init__.py +0 -0
- {tricc → tricc_oo}/serializers/planuml.py +0 -0
tricc_oo/models/base.py
ADDED
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import random
|
|
5
|
+
import string
|
|
6
|
+
from enum import Enum, auto
|
|
7
|
+
from typing import Dict, ForwardRef, List, Optional, Union, Set, Annotated
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, StringConstraints
|
|
10
|
+
from strenum import StrEnum
|
|
11
|
+
|
|
12
|
+
from tricc_oo.converters.utils import generate_id, get_rand_name
|
|
13
|
+
from tricc_oo.models.ordered_set import OrderedSet
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("default")
|
|
16
|
+
|
|
17
|
+
Expression = Annotated[
|
|
18
|
+
str,
|
|
19
|
+
StringConstraints(pattern=r'^[^\\/\:]+$')
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
triccId = Annotated[
|
|
23
|
+
str,
|
|
24
|
+
StringConstraints(pattern=r'^[^\\/\: ]+$')
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
triccName = Annotated[
|
|
28
|
+
str,
|
|
29
|
+
StringConstraints(pattern=r'^[^\s]+( [^\s]+)*$')
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
b64 = Annotated[
|
|
33
|
+
str,
|
|
34
|
+
StringConstraints(pattern=r'^[^-A-Za-z0-9+/=]|=[^=]|={3,}$')
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
TriccEdge = ForwardRef('TriccEdge')
|
|
39
|
+
# data:page/id,UkO_xCL5ZjyshJO9Bexg
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
ACTIVITY_END_NODE_FORMAT = "aend_{}"
|
|
43
|
+
END_NODE_FORMAT = "end_{}"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TriccNodeType(StrEnum):
|
|
47
|
+
#replace with auto ?
|
|
48
|
+
note = 'note'
|
|
49
|
+
calculate = 'calculate',
|
|
50
|
+
output = 'output',
|
|
51
|
+
select_multiple = 'select_multiple'
|
|
52
|
+
select_one = 'select_one'
|
|
53
|
+
select_yesno = 'select_one yesno'
|
|
54
|
+
select_option = 'select_option'
|
|
55
|
+
decimal = 'decimal'
|
|
56
|
+
integer = 'integer'
|
|
57
|
+
text = 'text'
|
|
58
|
+
date = 'date'
|
|
59
|
+
rhombus = 'rhombus' # fetch data
|
|
60
|
+
goto = 'goto' #: start the linked activity within the target activity
|
|
61
|
+
start = 'start' #: main start of the algo
|
|
62
|
+
activity_start = 'activity_start' #: start of an activity (link in)
|
|
63
|
+
link_in = 'link_in'
|
|
64
|
+
link_out = 'link_out'
|
|
65
|
+
count = 'count' #: count the number of valid input
|
|
66
|
+
add = 'add' # add counts
|
|
67
|
+
container_hint_media = 'container_hint_media' # DEPRECATED
|
|
68
|
+
activity = 'activity'
|
|
69
|
+
help = 'help-message'
|
|
70
|
+
hint = 'hint-message'
|
|
71
|
+
exclusive = 'not'
|
|
72
|
+
end = 'end'
|
|
73
|
+
activity_end = 'activity_end'
|
|
74
|
+
edge = 'edge'
|
|
75
|
+
page = 'container_page'
|
|
76
|
+
not_available = 'not_available'
|
|
77
|
+
quantity = 'quantity'
|
|
78
|
+
bridge = 'bridge'
|
|
79
|
+
wait = 'wait'
|
|
80
|
+
operation = 'operation'
|
|
81
|
+
context = 'context'
|
|
82
|
+
diagnosis = 'diagnosis'
|
|
83
|
+
proposed_diagnosis = 'proposed_diagnosis'
|
|
84
|
+
input = 'input'
|
|
85
|
+
|
|
86
|
+
def __iter__(self):
|
|
87
|
+
return iter(self.__members__.values())
|
|
88
|
+
|
|
89
|
+
def __next__(self):
|
|
90
|
+
return next(iter(self))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
media_nodes = [
|
|
95
|
+
TriccNodeType.note,
|
|
96
|
+
TriccNodeType.select_multiple,
|
|
97
|
+
TriccNodeType.select_one,
|
|
98
|
+
TriccNodeType.decimal,
|
|
99
|
+
TriccNodeType.integer,
|
|
100
|
+
TriccNodeType.text,
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TriccBaseModel(BaseModel):
|
|
105
|
+
id: triccId
|
|
106
|
+
external_id: triccId = None
|
|
107
|
+
tricc_type: TriccNodeType
|
|
108
|
+
datatype: str = None
|
|
109
|
+
instance: int = 1
|
|
110
|
+
base_instance: Optional[TriccBaseModel] = None
|
|
111
|
+
last: bool = None
|
|
112
|
+
version: int = 1
|
|
113
|
+
def get_datatype(self):
|
|
114
|
+
return self.datatype or self.tricc_type
|
|
115
|
+
|
|
116
|
+
def to_dict(self):
|
|
117
|
+
return {key: value for key, value in vars(self).items() if not key.startswith('_')}
|
|
118
|
+
|
|
119
|
+
def make_instance(self, nb_instance, **kwargs):
|
|
120
|
+
instance = self.copy()
|
|
121
|
+
# change the id to avoid collision of name
|
|
122
|
+
instance.id = generate_id(f"{self.id}{nb_instance}")
|
|
123
|
+
instance.instance = int(nb_instance)
|
|
124
|
+
instance.base_instance = self
|
|
125
|
+
attr_dict = self.to_dict()
|
|
126
|
+
for attr, value in attr_dict.items():
|
|
127
|
+
if not attr.startswith('_') and value is not None:
|
|
128
|
+
try:
|
|
129
|
+
if hasattr(value, 'copy'):
|
|
130
|
+
setattr(instance, attr, value.copy())
|
|
131
|
+
else:
|
|
132
|
+
setattr(instance, attr, value)
|
|
133
|
+
except Exception as e:
|
|
134
|
+
logger.warning(f"Warning: Could not copy attribute {attr}: {e}")
|
|
135
|
+
# assign the defualt group
|
|
136
|
+
# if activity is not None and self.group == activity.base_instance:
|
|
137
|
+
# instance.group = instance
|
|
138
|
+
return instance
|
|
139
|
+
|
|
140
|
+
def __eq__(self, other):
|
|
141
|
+
if isinstance(other, self.__class__):
|
|
142
|
+
return self.id == other.id
|
|
143
|
+
else:
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
def __ne__(self, other):
|
|
147
|
+
return not self.__eq__(other)
|
|
148
|
+
|
|
149
|
+
def __hash__(self):
|
|
150
|
+
hash_value = hash(self.id)
|
|
151
|
+
return hash_value
|
|
152
|
+
|
|
153
|
+
def get_name(self):
|
|
154
|
+
return self.id
|
|
155
|
+
|
|
156
|
+
def __str__(self):
|
|
157
|
+
return self.get_name()
|
|
158
|
+
|
|
159
|
+
def __repr__(self):
|
|
160
|
+
return f"{self.tricc_type}:{self.get_name()}({self.id})"
|
|
161
|
+
|
|
162
|
+
def __init__(self, **data):
|
|
163
|
+
if 'id' not in data:
|
|
164
|
+
data['id'] = generate_id(str(data))
|
|
165
|
+
super().__init__(**data)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class TriccEdge(TriccBaseModel):
|
|
169
|
+
tricc_type: TriccNodeType = TriccNodeType.edge
|
|
170
|
+
source: Union[triccId, TriccNodeBaseModel]
|
|
171
|
+
source_external_id: triccId = None
|
|
172
|
+
target: Union[triccId, TriccNodeBaseModel]
|
|
173
|
+
target_external_id: triccId = None
|
|
174
|
+
value: Optional[str] = None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class TriccGroup(TriccBaseModel):
|
|
179
|
+
tricc_type: TriccNodeType = TriccNodeType.page
|
|
180
|
+
group: Optional[TriccBaseModel] = None
|
|
181
|
+
name: Optional[str] = None
|
|
182
|
+
export_name:Optional[str] = None
|
|
183
|
+
label: Optional[Union[str, Dict[str,str]]] = None
|
|
184
|
+
relevance: Optional[Union[Expression, TriccOperation]] = None
|
|
185
|
+
path_len: int = 0
|
|
186
|
+
prev_nodes: OrderedSet[TriccBaseModel] = OrderedSet()
|
|
187
|
+
def __init__(self, **data):
|
|
188
|
+
super().__init__(**data)
|
|
189
|
+
if self.name is None:
|
|
190
|
+
self.name = generate_id(str(data))
|
|
191
|
+
|
|
192
|
+
def get_name(self):
|
|
193
|
+
result = str(super().get_name())
|
|
194
|
+
name = getattr(self, 'name', None)
|
|
195
|
+
label = getattr(self, 'label', None)
|
|
196
|
+
|
|
197
|
+
if name:
|
|
198
|
+
result = result + "::" + name
|
|
199
|
+
if label:
|
|
200
|
+
result = result + "::" + (
|
|
201
|
+
next(iter(self.label.values())) if isinstance(self.label, Dict) else self.label
|
|
202
|
+
)
|
|
203
|
+
if len(name) < 50:
|
|
204
|
+
return result
|
|
205
|
+
else:
|
|
206
|
+
return result[:50]
|
|
207
|
+
|
|
208
|
+
FwTriccNodeBaseModel = ForwardRef('TriccNodeBaseModel')
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class TriccNodeBaseModel(TriccBaseModel):
|
|
212
|
+
path_len: int = 0
|
|
213
|
+
group: Optional[Union[TriccGroup, FwTriccNodeBaseModel]] = None
|
|
214
|
+
name: Optional[str] = None
|
|
215
|
+
export_name: Optional[str] = None
|
|
216
|
+
label: Optional[Union[str, Dict[str,str]]] = None
|
|
217
|
+
next_nodes: OrderedSet[TriccNodeBaseModel] = OrderedSet()
|
|
218
|
+
prev_nodes: OrderedSet[TriccNodeBaseModel] = OrderedSet()
|
|
219
|
+
expression: Optional[Union[Expression, TriccOperation]] = None # will be generated based on the input
|
|
220
|
+
expression_inputs: List[Expression] = []
|
|
221
|
+
activity: Optional[FwTriccNodeBaseModel] = None
|
|
222
|
+
ref_def: Optional[Union[int,str]] = None# for medal creator
|
|
223
|
+
|
|
224
|
+
class Config:
|
|
225
|
+
use_enum_values = True # <--
|
|
226
|
+
|
|
227
|
+
def __hash__(self):
|
|
228
|
+
return hash(self.id )
|
|
229
|
+
|
|
230
|
+
# to be updated while processing because final expression will be possible to build$
|
|
231
|
+
# #only the last time the script will go through the node (all prev node expression would be created
|
|
232
|
+
def get_name(self):
|
|
233
|
+
result = str(super().get_name())
|
|
234
|
+
name = getattr(self, 'name', None)
|
|
235
|
+
label = getattr(self, 'label', None)
|
|
236
|
+
|
|
237
|
+
if name:
|
|
238
|
+
result = result + "::" + name
|
|
239
|
+
if label:
|
|
240
|
+
result = result + "::" + (
|
|
241
|
+
next(iter(self.label.values())) if isinstance(self.label, Dict) else self.label
|
|
242
|
+
)
|
|
243
|
+
if len(result) < 50:
|
|
244
|
+
return result
|
|
245
|
+
else:
|
|
246
|
+
return result[:50]
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def make_instance(self, instance_nb, activity=None):
|
|
251
|
+
instance = super().make_instance(instance_nb)
|
|
252
|
+
instance.group = activity
|
|
253
|
+
if hasattr(self, 'activity') and activity is not None:
|
|
254
|
+
instance.activity = activity
|
|
255
|
+
next_nodes = OrderedSet()
|
|
256
|
+
instance.next_nodes = next_nodes
|
|
257
|
+
prev_nodes = OrderedSet()
|
|
258
|
+
instance.prev_nodes = prev_nodes
|
|
259
|
+
expression_inputs = []
|
|
260
|
+
instance.expression_inputs = expression_inputs
|
|
261
|
+
|
|
262
|
+
for attr in ['expression', 'relevance', 'default', 'reference', 'expression_reference']:
|
|
263
|
+
if getattr(self, attr, None):
|
|
264
|
+
setattr(instance, attr, getattr(self, attr).copy())
|
|
265
|
+
|
|
266
|
+
return instance
|
|
267
|
+
|
|
268
|
+
def gen_name(self):
|
|
269
|
+
if self.name is None:
|
|
270
|
+
self.name = get_rand_name(self.id)
|
|
271
|
+
def get_references(self):
|
|
272
|
+
return OrderedSet([self])
|
|
273
|
+
|
|
274
|
+
class TriccStatic(BaseModel):
|
|
275
|
+
value: Union[str, float, int, bool]
|
|
276
|
+
def __init__(self,value):
|
|
277
|
+
super().__init__(value = value)
|
|
278
|
+
|
|
279
|
+
def get_datatype(self):
|
|
280
|
+
if str(type(self.value)) == 'bool':
|
|
281
|
+
return 'boolean'
|
|
282
|
+
elif str(self.value).isnumeric():
|
|
283
|
+
return 'number'
|
|
284
|
+
else:
|
|
285
|
+
return str(type(self.value))
|
|
286
|
+
def __eq__(self, other):
|
|
287
|
+
if isinstance(other, self.__class__):
|
|
288
|
+
return self.value == other.value
|
|
289
|
+
else:
|
|
290
|
+
return False
|
|
291
|
+
|
|
292
|
+
def __ne__(self, other):
|
|
293
|
+
return not self.__eq__(other)
|
|
294
|
+
|
|
295
|
+
def __hash__(self):
|
|
296
|
+
hash_value = hash(self.value)
|
|
297
|
+
return hash_value
|
|
298
|
+
def get_name(self):
|
|
299
|
+
return self.value
|
|
300
|
+
|
|
301
|
+
def __str__(self):
|
|
302
|
+
return str(self.value)
|
|
303
|
+
|
|
304
|
+
def __repr__(self):
|
|
305
|
+
return "TriccStatic:"+str(type(self.value))+':' +str(self.value)
|
|
306
|
+
|
|
307
|
+
def get_references(self):
|
|
308
|
+
return OrderedSet()
|
|
309
|
+
|
|
310
|
+
class TriccReference(TriccStatic):
|
|
311
|
+
value: str
|
|
312
|
+
def __copy__(self):
|
|
313
|
+
return type(self)(self.value)
|
|
314
|
+
|
|
315
|
+
def copy(self):
|
|
316
|
+
return self.__copy__()
|
|
317
|
+
|
|
318
|
+
def get_references(self):
|
|
319
|
+
return OrderedSet([self])
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
class TriccOperator(StrEnum):
|
|
323
|
+
AND = 'and' # and between left and rights
|
|
324
|
+
ADD_OR = 'and_or' # left and one of the righs
|
|
325
|
+
OR = 'or' # or between left and rights
|
|
326
|
+
NATIVE = 'native' #default left is native expression
|
|
327
|
+
ISTRUE = 'istrue' # left is right
|
|
328
|
+
ISNOTTRUE = 'isnottrue'
|
|
329
|
+
ISFALSE = 'isfalse' # left is false
|
|
330
|
+
ISNOTFALSE = 'isnotfalse' # left is false
|
|
331
|
+
SELECTED = 'selected' # right must be la select and one or several options
|
|
332
|
+
MORE_OR_EQUAL = 'more_or_equal'
|
|
333
|
+
LESS_OR_EQUAL = 'less_or_equal'
|
|
334
|
+
EQUAL = 'equal'
|
|
335
|
+
MORE = 'more'
|
|
336
|
+
NOTEQUAL = 'not_equal'
|
|
337
|
+
BETWEEN = 'between'
|
|
338
|
+
LESS = 'less'
|
|
339
|
+
CONTAINS = 'contains' # ref, txt Does CONTAINS make sense, like Select with wildcard
|
|
340
|
+
NOTEXISTS = 'notexists'
|
|
341
|
+
EXISTS = 'exists'
|
|
342
|
+
NOT = 'not'
|
|
343
|
+
ISNULL = 'isnull'
|
|
344
|
+
ISNOTNULL= 'isnotnull'
|
|
345
|
+
|
|
346
|
+
CASE = 'case' # ref (equal value, res), (equal value,res)
|
|
347
|
+
IFS = 'ifs' #(cond, res), (cond,res)
|
|
348
|
+
IF = 'if' # cond val_true, val_false
|
|
349
|
+
|
|
350
|
+
# CDSS Specific
|
|
351
|
+
HAS_QUALIFIER = 'has_qualifier'
|
|
352
|
+
|
|
353
|
+
ZSCORE = 'zscore' # left table_name, right Y, gender give Z
|
|
354
|
+
IZSCORE = 'izscore' #left table_name, right Z, gender give Y
|
|
355
|
+
AGE_DAY = 'age_day' # age from dob
|
|
356
|
+
AGE_MONTH = 'age_month' # age from dob
|
|
357
|
+
AGE_YEAR = 'age_year' # age from dob
|
|
358
|
+
DIVIDED = 'divided'
|
|
359
|
+
MULTIPLIED = 'multiplied'
|
|
360
|
+
PLUS = 'plus'
|
|
361
|
+
MINUS = 'minus'
|
|
362
|
+
MODULO = 'modulo'
|
|
363
|
+
COUNT = 'count'
|
|
364
|
+
CAST_NUMBER = 'cast_number'
|
|
365
|
+
CAST_INTEGER = 'cast_integer'
|
|
366
|
+
DRUG_DOSAGE = 'drug_dosage' # drug name, *param1 (ex: weight, age)
|
|
367
|
+
COALESCE = 'coalesce'
|
|
368
|
+
CAST_DATE = 'cast_date'
|
|
369
|
+
PARENTHESIS = 'parenthesis'
|
|
370
|
+
CONCATENATE = 'concatenate'
|
|
371
|
+
|
|
372
|
+
RETURNS_BOOLEAN =[
|
|
373
|
+
TriccOperator.ADD_OR,
|
|
374
|
+
TriccOperator.AND,
|
|
375
|
+
TriccOperator.OR,
|
|
376
|
+
TriccOperator.BETWEEN,
|
|
377
|
+
TriccOperator.CONTAINS,
|
|
378
|
+
TriccOperator.EXISTS,
|
|
379
|
+
TriccOperator.NOTEXISTS,
|
|
380
|
+
TriccOperator.ISFALSE,
|
|
381
|
+
TriccOperator.ISNOTFALSE,
|
|
382
|
+
TriccOperator.ISNOTNULL,
|
|
383
|
+
TriccOperator.ISTRUE,
|
|
384
|
+
TriccOperator.ISNOTTRUE,
|
|
385
|
+
TriccOperator.SELECTED,
|
|
386
|
+
TriccOperator.HAS_QUALIFIER,
|
|
387
|
+
TriccOperator.NOT,
|
|
388
|
+
TriccOperator.NOTEQUAL,
|
|
389
|
+
TriccOperator.MORE_OR_EQUAL,
|
|
390
|
+
TriccOperator.LESS_OR_EQUAL,
|
|
391
|
+
TriccOperator.EQUAL,
|
|
392
|
+
TriccOperator.MORE,
|
|
393
|
+
TriccOperator.LESS
|
|
394
|
+
]
|
|
395
|
+
|
|
396
|
+
RETURNS_NUMBER = [
|
|
397
|
+
TriccOperator.AGE_DAY,
|
|
398
|
+
TriccOperator.AGE_MONTH,
|
|
399
|
+
TriccOperator.AGE_YEAR,
|
|
400
|
+
TriccOperator.ZSCORE,
|
|
401
|
+
TriccOperator.IZSCORE,
|
|
402
|
+
TriccOperator.PLUS,
|
|
403
|
+
TriccOperator.MINUS,
|
|
404
|
+
TriccOperator.DIVIDED,
|
|
405
|
+
TriccOperator.MULTIPLIED,
|
|
406
|
+
TriccOperator.COUNT,
|
|
407
|
+
TriccOperator.MODULO,
|
|
408
|
+
TriccOperator.CAST_NUMBER,
|
|
409
|
+
TriccOperator.CAST_INTEGER
|
|
410
|
+
]
|
|
411
|
+
|
|
412
|
+
RETURNS_DATE =[
|
|
413
|
+
TriccOperator.CAST_DATE
|
|
414
|
+
]
|
|
415
|
+
|
|
416
|
+
OPERATION_LIST = {
|
|
417
|
+
'>=': TriccOperator.MORE_OR_EQUAL,
|
|
418
|
+
'<=': TriccOperator.LESS_OR_EQUAL,
|
|
419
|
+
'==': TriccOperator.EQUAL,
|
|
420
|
+
'!=': TriccOperator.NOTEQUAL,
|
|
421
|
+
'=': TriccOperator.EQUAL,
|
|
422
|
+
'>': TriccOperator.MORE,
|
|
423
|
+
'<': TriccOperator.LESS
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
class TriccOperation(BaseModel):
|
|
427
|
+
tricc_type: TriccNodeType = TriccNodeType.operation
|
|
428
|
+
operator: TriccOperator = TriccOperator.NATIVE
|
|
429
|
+
reference: OrderedSet[
|
|
430
|
+
Union[
|
|
431
|
+
TriccStatic, TriccNodeBaseModel, TriccOperation, TriccReference, Expression,
|
|
432
|
+
List[Union[TriccStatic, TriccNodeBaseModel, TriccOperation, TriccReference, Expression]]
|
|
433
|
+
]
|
|
434
|
+
] = []
|
|
435
|
+
|
|
436
|
+
def __str__(self):
|
|
437
|
+
str_ref = map(str, self.reference)
|
|
438
|
+
return f"{self.operator}({', '.join(map(str, str_ref))})"
|
|
439
|
+
|
|
440
|
+
def __hash__(self):
|
|
441
|
+
return hash(self.__repr__())
|
|
442
|
+
|
|
443
|
+
def __repr__(self):
|
|
444
|
+
return "TriccOperation:"+self.__str__()
|
|
445
|
+
|
|
446
|
+
def __eq__(self, other):
|
|
447
|
+
return self.__str__() == str(other)
|
|
448
|
+
|
|
449
|
+
def __init__(self, operator, reference=[]):
|
|
450
|
+
super().__init__(operator=operator, reference=reference)
|
|
451
|
+
|
|
452
|
+
def get_datatype(self):
|
|
453
|
+
if self.operator in RETURNS_BOOLEAN:
|
|
454
|
+
return 'boolean'
|
|
455
|
+
elif self.operator in RETURNS_NUMBER:
|
|
456
|
+
return 'number'
|
|
457
|
+
elif self.operator in RETURNS_DATE:
|
|
458
|
+
return 'date'
|
|
459
|
+
elif self.operator == TriccOperator.CONCATENATE:
|
|
460
|
+
return 'string'
|
|
461
|
+
elif self.operator == TriccOperator.PARENTHESIS:
|
|
462
|
+
return self.get_reference_datatype(self.reference)
|
|
463
|
+
elif self.operator == TriccOperator.IF:
|
|
464
|
+
return self.get_reference_datatype(self.reference[1:])
|
|
465
|
+
elif self.operator in ( TriccOperator.IFS, TriccOperator.CASE):
|
|
466
|
+
rtype = set()
|
|
467
|
+
for rl in self.reference:
|
|
468
|
+
rtype.add(self.get_reference_datatype(self.reference[-2:]))
|
|
469
|
+
if len(rtype)>1:
|
|
470
|
+
return 'mixed'
|
|
471
|
+
else:
|
|
472
|
+
return rtype.pop()
|
|
473
|
+
|
|
474
|
+
def get_reference_datatype(self, references):
|
|
475
|
+
rtype = set()
|
|
476
|
+
for r in references:
|
|
477
|
+
if hasattr(r, 'get_datatype'):
|
|
478
|
+
rtype.add(r.get_datatype())
|
|
479
|
+
elif hasattr(r, 'value'):
|
|
480
|
+
return str(type(r.value))
|
|
481
|
+
else:
|
|
482
|
+
return str(type(r))
|
|
483
|
+
|
|
484
|
+
if len(rtype)>1:
|
|
485
|
+
return 'mixed'
|
|
486
|
+
else:
|
|
487
|
+
return rtype.pop()
|
|
488
|
+
|
|
489
|
+
def get_references(self):
|
|
490
|
+
predecessor = OrderedSet()
|
|
491
|
+
if isinstance(self.reference, list):
|
|
492
|
+
for reference in self.reference:
|
|
493
|
+
self._process_reference(reference, predecessor)
|
|
494
|
+
else:
|
|
495
|
+
raise NotImplementedError("cannot find predecessor of a str")
|
|
496
|
+
return predecessor
|
|
497
|
+
|
|
498
|
+
def _process_reference(self, reference, predecessor):
|
|
499
|
+
if isinstance(reference, list):
|
|
500
|
+
for e in reference:
|
|
501
|
+
self._process_reference(e, predecessor)
|
|
502
|
+
elif isinstance(reference, TriccOperation):
|
|
503
|
+
subs = reference.get_references()
|
|
504
|
+
for s in subs:
|
|
505
|
+
predecessor.add(s)
|
|
506
|
+
elif issubclass(reference.__class__, (TriccNodeBaseModel, TriccReference)):
|
|
507
|
+
predecessor.add(reference)
|
|
508
|
+
|
|
509
|
+
def append(self, value):
|
|
510
|
+
self.reference.append(value)
|
|
511
|
+
def replace_node(self, old_node ,new_node):
|
|
512
|
+
if isinstance(self.reference, list):
|
|
513
|
+
for key in [i for i, x in enumerate(self.reference)]:
|
|
514
|
+
self.reference[key] = self._replace_reference(self.reference[key], new_node, old_node)
|
|
515
|
+
elif self.reference is not None:
|
|
516
|
+
raise NotImplementedError(f"cannot manage {self.reference.__class__}")
|
|
517
|
+
|
|
518
|
+
def _replace_reference(self, reference, new_node, old_node):
|
|
519
|
+
if isinstance(reference, list):
|
|
520
|
+
for key in [i for i, x in enumerate(reference)]:
|
|
521
|
+
reference[key] = self._replace_reference(reference[key], new_node, old_node)
|
|
522
|
+
if isinstance(reference, TriccOperation):
|
|
523
|
+
reference.replace_node(old_node ,new_node)
|
|
524
|
+
elif issubclass(reference.__class__, (TriccNodeBaseModel, TriccReference)) and reference == old_node:
|
|
525
|
+
reference = new_node
|
|
526
|
+
# to cover the options
|
|
527
|
+
if hasattr(reference, 'select') and hasattr(new_node, 'select') and issubclass(reference.select.__class__, TriccNodeBaseModel ) :
|
|
528
|
+
self.replace_node(reference.select ,new_node.select)
|
|
529
|
+
return reference
|
|
530
|
+
|
|
531
|
+
def __copy__(self, keep_node=False):
|
|
532
|
+
# Create a new instance
|
|
533
|
+
if keep_node:
|
|
534
|
+
reference = [e for e in self.reference]
|
|
535
|
+
else:
|
|
536
|
+
reference = [e.copy() if isinstance(e, (TriccReference, TriccOperation)) else (TriccReference(e.name) if hasattr(e, 'name') else e) for e in self.reference]
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
new_instance = type(self)(
|
|
540
|
+
self.operator,
|
|
541
|
+
reference
|
|
542
|
+
)
|
|
543
|
+
# Copy attributes (shallow copy for mutable attributes)
|
|
544
|
+
|
|
545
|
+
return new_instance
|
|
546
|
+
|
|
547
|
+
def copy(self, keep_node=False):
|
|
548
|
+
return self.__copy__(keep_node)
|
|
549
|
+
|
|
550
|
+
# function that make multipat and
|
|
551
|
+
# @param argv list of expression to join with and
|
|
552
|
+
def clean_and_list(argv):
|
|
553
|
+
for a in list(argv):
|
|
554
|
+
if isinstance(a, TriccOperation) and a.operator == TriccOperator.AND:
|
|
555
|
+
argv.remove(a)
|
|
556
|
+
return clean_and_list([*argv,*a.reference])
|
|
557
|
+
elif a == TriccStatic(True) or a == True:
|
|
558
|
+
argv.remove(a)
|
|
559
|
+
elif a == TriccStatic(False) :
|
|
560
|
+
return [TriccStatic(False)]
|
|
561
|
+
|
|
562
|
+
internal = list(set(argv))
|
|
563
|
+
for a in internal:
|
|
564
|
+
for b in internal[internal.index(a)+1:]:
|
|
565
|
+
if not_clean(b) == a:
|
|
566
|
+
return [TriccStatic(False)]
|
|
567
|
+
return sorted(list(set(argv)), key=str)
|
|
568
|
+
|
|
569
|
+
def not_clean(a):
|
|
570
|
+
new_operator = None
|
|
571
|
+
if a is None or isinstance(a, str) and a == '':
|
|
572
|
+
return TriccStatic(False)
|
|
573
|
+
elif isinstance(a, TriccStatic) and a == TriccStatic(False):
|
|
574
|
+
return TriccStatic(True)
|
|
575
|
+
elif isinstance(a, TriccStatic) and a == TriccStatic(True):
|
|
576
|
+
return TriccStatic(False)
|
|
577
|
+
elif isinstance(a, TriccOperation) and a.operator == TriccOperator.ISTRUE:
|
|
578
|
+
new_operator = TriccOperator.ISNOTTRUE
|
|
579
|
+
elif isinstance(a, TriccOperation) and a.operator == TriccOperator.ISNOTTRUE:
|
|
580
|
+
new_operator = TriccOperator.ISTRUE
|
|
581
|
+
elif isinstance(a, TriccOperation) and a.operator == TriccOperator.ISFALSE:
|
|
582
|
+
new_operator = TriccOperator.ISNOTFALSE
|
|
583
|
+
elif isinstance(a, TriccOperation) and a.operator == TriccOperator.ISNOTFALSE:
|
|
584
|
+
new_operator = TriccOperator.ISFALSE
|
|
585
|
+
elif isinstance(a, TriccOperation) and a.operator == TriccOperator.ISNULL:
|
|
586
|
+
new_operator = TriccOperator.ISNOTNULL
|
|
587
|
+
elif isinstance(a, TriccOperation) and a.operator == TriccOperator.ISNOTNULL:
|
|
588
|
+
new_operator = TriccOperator.ISNULL
|
|
589
|
+
elif isinstance(a, TriccOperation) and a.operator == TriccOperator.LESS:
|
|
590
|
+
new_operator = TriccOperator.MORE_OR_EQUAL
|
|
591
|
+
elif isinstance(a, TriccOperation) and a.operator == TriccOperator.MORE:
|
|
592
|
+
new_operator = TriccOperator.LESS_OR_EQUAL
|
|
593
|
+
elif isinstance(a, TriccOperation) and a.operator == TriccOperator.LESS_OR_EQUAL:
|
|
594
|
+
new_operator = TriccOperator.MORE
|
|
595
|
+
elif isinstance(a, TriccOperation) and a.operator == TriccOperator.MORE_OR_EQUAL:
|
|
596
|
+
new_operator = TriccOperator.LESS
|
|
597
|
+
elif isinstance(a, TriccOperation) and a.operator == TriccOperator.NOT:
|
|
598
|
+
return a.reference[0]
|
|
599
|
+
|
|
600
|
+
if new_operator:
|
|
601
|
+
return TriccOperation(
|
|
602
|
+
new_operator,
|
|
603
|
+
a.reference
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
elif not isinstance(a, TriccOperation) and issubclass(a.__class__, object):
|
|
607
|
+
return TriccOperation(
|
|
608
|
+
operator=TriccOperator.NOTEXISTS,
|
|
609
|
+
reference=[a]
|
|
610
|
+
)
|
|
611
|
+
else:
|
|
612
|
+
return TriccOperation(
|
|
613
|
+
TriccOperator.NOT,
|
|
614
|
+
[a]
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
# function that generate remove unsure condition
|
|
620
|
+
# @param list_or
|
|
621
|
+
# @param and elm use upstream
|
|
622
|
+
def clean_or_list(list_or, elm_and=None):
|
|
623
|
+
for a in list(list_or):
|
|
624
|
+
if isinstance(a, TriccOperation) and a.operator == TriccOperator.OR:
|
|
625
|
+
list_or.remove(a)
|
|
626
|
+
return clean_or_list([*list_or,*a.reference])
|
|
627
|
+
elif a == TriccStatic(False) or a == False or a == 0:
|
|
628
|
+
list_or.remove(a)
|
|
629
|
+
elif a == TriccStatic(True) or a == True or a == 1 or (elm_and is not None and not_clean(a) in list_or):
|
|
630
|
+
return [TriccStatic(True)]
|
|
631
|
+
# if there is x and not(X) in an OR list them the list is always true
|
|
632
|
+
elif elm_and is not None and (not_clean(a) == elm_and or a == elm_and ):
|
|
633
|
+
list_or.remove(a)
|
|
634
|
+
internal = list(list_or)
|
|
635
|
+
for a in internal:
|
|
636
|
+
for b in internal[internal.index(a)+1:]:
|
|
637
|
+
if not_clean(b) == a:
|
|
638
|
+
return [TriccStatic(True)]
|
|
639
|
+
if len(list_or) == 0:
|
|
640
|
+
return []
|
|
641
|
+
|
|
642
|
+
return sorted(list(set(list_or)), key=str)
|
|
643
|
+
|
|
644
|
+
def and_join(argv):
|
|
645
|
+
argv=clean_and_list(argv)
|
|
646
|
+
if len(argv) == 0:
|
|
647
|
+
return ''
|
|
648
|
+
elif len(argv) == 1:
|
|
649
|
+
return argv[0]
|
|
650
|
+
else:
|
|
651
|
+
return TriccOperation(
|
|
652
|
+
TriccOperator.AND,
|
|
653
|
+
argv
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
# function that make a 2 part and
|
|
657
|
+
# @param left part
|
|
658
|
+
# @param right part
|
|
659
|
+
def simple_and_join(left, right):
|
|
660
|
+
expression = None
|
|
661
|
+
# no term is considered as True
|
|
662
|
+
left_issue = left is None or left == ''
|
|
663
|
+
right_issue = right is None or right == ''
|
|
664
|
+
left_neg = not_clean(left)
|
|
665
|
+
right_neg = not_clean(right)
|
|
666
|
+
if left_issue and right_issue:
|
|
667
|
+
logger.critical("and with both terms empty")
|
|
668
|
+
elif left_neg == right or right_neg == left:
|
|
669
|
+
return TriccStatic(False)
|
|
670
|
+
elif left_issue:
|
|
671
|
+
logger.debug('and with empty left term')
|
|
672
|
+
return right
|
|
673
|
+
elif left == '1' or left == 1 or left == TriccStatic(True) or left is True:
|
|
674
|
+
return right
|
|
675
|
+
elif right_issue:
|
|
676
|
+
logger.debug('and with empty right term')
|
|
677
|
+
return left
|
|
678
|
+
elif right == '1' or right == 1 or right == TriccStatic(True) or right is True:
|
|
679
|
+
return left
|
|
680
|
+
else:
|
|
681
|
+
return TriccOperation(
|
|
682
|
+
TriccOperator.AND,
|
|
683
|
+
[left, right]
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
def or_join(list_or, elm_and=None):
|
|
687
|
+
cleaned_list = clean_or_list(list_or, elm_and)
|
|
688
|
+
if len(cleaned_list) == 1:
|
|
689
|
+
return cleaned_list[0]
|
|
690
|
+
elif len(cleaned_list)>1:
|
|
691
|
+
return TriccOperation(
|
|
692
|
+
TriccOperator.OR,
|
|
693
|
+
cleaned_list
|
|
694
|
+
)
|
|
695
|
+
else:
|
|
696
|
+
logger.error("empty or list")
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
# function that make a 2 part NAND
|
|
701
|
+
# @param left part
|
|
702
|
+
# @param right part
|
|
703
|
+
def nand_join(left, right):
|
|
704
|
+
# no term is considered as True
|
|
705
|
+
left_issue = left is None or left == ''
|
|
706
|
+
right_issue = right is None or right == ''
|
|
707
|
+
left_neg = left == False or left == 0 or left == '0' or left == TriccStatic(False)
|
|
708
|
+
right_neg = right == False or right == 0 or right == '0' or right == TriccStatic(False)
|
|
709
|
+
if issubclass(left.__class__, TriccNodeBaseModel):
|
|
710
|
+
left = get_export_name(left)
|
|
711
|
+
if issubclass(right.__class__, TriccNodeBaseModel):
|
|
712
|
+
right = get_export_name(right)
|
|
713
|
+
if left_issue and right_issue:
|
|
714
|
+
logger.critical("and with both terms empty")
|
|
715
|
+
elif left_issue:
|
|
716
|
+
logger.debug('and with empty left term')
|
|
717
|
+
return negate_term(right)
|
|
718
|
+
elif left == '1' or left == 1 or left == TriccStatic(True):
|
|
719
|
+
return negate_term(right)
|
|
720
|
+
elif right_issue :
|
|
721
|
+
logger.debug('and with empty right term')
|
|
722
|
+
return TriccStatic(False)
|
|
723
|
+
elif right == '1' or right == 1 or left_neg or right == TriccStatic(True):
|
|
724
|
+
return TriccStatic(False)
|
|
725
|
+
elif right_neg:
|
|
726
|
+
return left
|
|
727
|
+
else:
|
|
728
|
+
return and_join([left, negate_term(right)])
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
TriccGroup.update_forward_refs()
|
|
732
|
+
TriccEdge.update_forward_refs()
|