pytrilogy 0.0.3.37__py3-none-any.whl → 0.0.3.39__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.
Potentially problematic release.
This version of pytrilogy might be problematic. Click here for more details.
- {pytrilogy-0.0.3.37.dist-info → pytrilogy-0.0.3.39.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.3.37.dist-info → pytrilogy-0.0.3.39.dist-info}/RECORD +25 -25
- {pytrilogy-0.0.3.37.dist-info → pytrilogy-0.0.3.39.dist-info}/WHEEL +1 -1
- trilogy/__init__.py +1 -1
- trilogy/core/environment_helpers.py +59 -42
- trilogy/core/functions.py +6 -4
- trilogy/core/models/author.py +8 -5
- trilogy/core/models/build.py +54 -5
- trilogy/core/models/environment.py +11 -4
- trilogy/core/processing/concept_strategies_v3.py +23 -13
- trilogy/core/processing/node_generators/filter_node.py +3 -2
- trilogy/core/processing/node_generators/group_to_node.py +3 -0
- trilogy/core/processing/node_generators/union_node.py +44 -28
- trilogy/core/processing/nodes/__init__.py +19 -0
- trilogy/core/processing/nodes/merge_node.py +3 -0
- trilogy/core/processing/utility.py +30 -6
- trilogy/core/query_processor.py +3 -0
- trilogy/core/statements/author.py +2 -0
- trilogy/dialect/base.py +1 -0
- trilogy/parsing/common.py +84 -5
- trilogy/parsing/parse_engine.py +32 -3
- trilogy/parsing/trilogy.lark +41 -19
- {pytrilogy-0.0.3.37.dist-info → pytrilogy-0.0.3.39.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.37.dist-info → pytrilogy-0.0.3.39.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.37.dist-info → pytrilogy-0.0.3.39.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
pytrilogy-0.0.3.
|
|
2
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
pytrilogy-0.0.3.39.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
2
|
+
trilogy/__init__.py,sha256=sSwrSvubMxHHrZZ2-OXo7jwbQqXFn0_nVeSAW4j8vo4,303
|
|
3
3
|
trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
trilogy/constants.py,sha256=5eQxk1A0pv-TQk3CCvgZCFA9_K-6nxrOm7E5Lxd7KIY,1652
|
|
5
5
|
trilogy/engine.py,sha256=OK2RuqCIUId6yZ5hfF8J1nxGP0AJqHRZiafcowmW0xc,1728
|
|
@@ -13,21 +13,21 @@ trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
13
13
|
trilogy/core/constants.py,sha256=7XaCpZn5mQmjTobbeBn56SzPWq9eMNDfzfsRU-fP0VE,171
|
|
14
14
|
trilogy/core/enums.py,sha256=JwbWyAHOC2xRTZe2SeEvlIGPvmC1KjcJ4uh1Po5USzQ,7380
|
|
15
15
|
trilogy/core/env_processor.py,sha256=pFsxnluKIusGKx1z7tTnfsd_xZcPy9pZDungkjkyvI0,3170
|
|
16
|
-
trilogy/core/environment_helpers.py,sha256=
|
|
16
|
+
trilogy/core/environment_helpers.py,sha256=drsaJvwmwZ1xvyEf1mAcIekQS2k3B9AKMhMzw_X2Hbs,9741
|
|
17
17
|
trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
|
|
18
18
|
trilogy/core/exceptions.py,sha256=JPYyBcit3T_pRtlHdtKSeVJkIyWUTozW2aaut25A2xI,673
|
|
19
|
-
trilogy/core/functions.py,sha256=
|
|
19
|
+
trilogy/core/functions.py,sha256=4fEOGgXWDvgrJtCg_5m2Y9iWnHfLbvLQ82RkIMl_1K0,27722
|
|
20
20
|
trilogy/core/graph_models.py,sha256=z17EoO8oky2QOuO6E2aMWoVNKEVJFhLdsQZOhC4fNLU,2079
|
|
21
21
|
trilogy/core/internal.py,sha256=iicDBlC6nM8d7e7jqzf_ZOmpUsW8yrr2AA8AqEiLx-s,1577
|
|
22
22
|
trilogy/core/optimization.py,sha256=aihzx4-2-mSjx5td1TDTYGvc7e9Zvy-_xEyhPqLS-Ig,8314
|
|
23
|
-
trilogy/core/query_processor.py,sha256=
|
|
23
|
+
trilogy/core/query_processor.py,sha256=Vl-u0F0rbqI2liv82yJgiZCB255Kx_KiuzZVHL6aeTM,19459
|
|
24
24
|
trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
-
trilogy/core/models/author.py,sha256=
|
|
26
|
-
trilogy/core/models/build.py,sha256=
|
|
25
|
+
trilogy/core/models/author.py,sha256=hS1caD8y7XWRBlHfwgZOrBcz3TisDPba8joFaiEXxX0,77072
|
|
26
|
+
trilogy/core/models/build.py,sha256=flDLLFCR0XJ0fpUhkGg-bpDOkH915tDdXaKmrAzNIcg,61828
|
|
27
27
|
trilogy/core/models/build_environment.py,sha256=s_C9xAHuD3yZ26T15pWVBvoqvlp2LdZ8yjsv2_HdXLk,5363
|
|
28
28
|
trilogy/core/models/core.py,sha256=wx6hJcFECMG-Ij972ADNkr-3nFXkYESr82ObPiC46_U,10875
|
|
29
29
|
trilogy/core/models/datasource.py,sha256=6RjJUd2u4nYmEwFBpJlM9LbHVYDv8iHJxqiBMZqUrwI,9422
|
|
30
|
-
trilogy/core/models/environment.py,sha256=
|
|
30
|
+
trilogy/core/models/environment.py,sha256=FXQ8hOJuwF4Ul5LxNPFe1r94fN8UtI9Gx3MJwXOCiO0,27222
|
|
31
31
|
trilogy/core/models/execute.py,sha256=mQm5Gydo2Ph0W7w9wm5dQEarS04PC-IKAgNVsdqOZsQ,34524
|
|
32
32
|
trilogy/core/optimizations/__init__.py,sha256=EBanqTXEzf1ZEYjAneIWoIcxtMDite5-n2dQ5xcfUtg,356
|
|
33
33
|
trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
|
|
@@ -35,42 +35,42 @@ trilogy/core/optimizations/inline_constant.py,sha256=lvNTIXaLNkw3HseJyXyDNk5R52d
|
|
|
35
35
|
trilogy/core/optimizations/inline_datasource.py,sha256=AHuTGh2x0GQ8usOe0NiFncfTFQ_KogdgDl4uucmhIbI,4241
|
|
36
36
|
trilogy/core/optimizations/predicate_pushdown.py,sha256=g4AYE8Aw_iMlAh68TjNXGP754NTurrDduFECkUjoBnc,9399
|
|
37
37
|
trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
-
trilogy/core/processing/concept_strategies_v3.py,sha256=
|
|
38
|
+
trilogy/core/processing/concept_strategies_v3.py,sha256=reXqgktFKy5qhm4k6abrs4J16Sovxd4naHrsEcNarEk,43957
|
|
39
39
|
trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
|
|
40
|
-
trilogy/core/processing/utility.py,sha256=
|
|
40
|
+
trilogy/core/processing/utility.py,sha256=rfzdgl-vWkCyhLzXNNuWgPLK59eiYypQb6TdZKymUqk,21469
|
|
41
41
|
trilogy/core/processing/node_generators/__init__.py,sha256=o8rOFHPSo-s_59hREwXMW6gjUJCsiXumdbJNozHUf-Y,800
|
|
42
42
|
trilogy/core/processing/node_generators/basic_node.py,sha256=UVsXMn6jTjm_ofVFt218jAS11s4RV4zD781vP4im-GI,3371
|
|
43
43
|
trilogy/core/processing/node_generators/common.py,sha256=ZsDzThjm_mAtdQpKAg8QIJiPVZ4KuUkKyilj4eOhSDs,9439
|
|
44
|
-
trilogy/core/processing/node_generators/filter_node.py,sha256=
|
|
44
|
+
trilogy/core/processing/node_generators/filter_node.py,sha256=lT167yBgy3P9sDBM1Cjj0PKSXro8dvGtBmc8nwsUjig,8366
|
|
45
45
|
trilogy/core/processing/node_generators/group_node.py,sha256=kO-ersxIL04rZwX5-vFIFQQnp357PFo_7ZKXoGq3wyc,5989
|
|
46
|
-
trilogy/core/processing/node_generators/group_to_node.py,sha256=
|
|
46
|
+
trilogy/core/processing/node_generators/group_to_node.py,sha256=v2Xl5VjNO2hKJAdmqCbmAOqaRPvxRUbtfhhQXFMifWk,3197
|
|
47
47
|
trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
|
|
48
48
|
trilogy/core/processing/node_generators/node_merge_node.py,sha256=sv55oynfqgpHEpo1OEtVDri-5fywzPhDlR85qaWikvY,16195
|
|
49
49
|
trilogy/core/processing/node_generators/rowset_node.py,sha256=YmBs6ZQ7azLXRFEmeoecpGjK4pMHsUCovuBxfb3UKZI,6848
|
|
50
50
|
trilogy/core/processing/node_generators/select_merge_node.py,sha256=lxXhMhDKGbu67QFNbbAT-BO8gbWppIvjn_hAXpLEPe0,19953
|
|
51
51
|
trilogy/core/processing/node_generators/select_node.py,sha256=Y-zO0AFkTrpi2LyebjpyHU7WWANr7nKZSS9rY7DH4Wo,1888
|
|
52
52
|
trilogy/core/processing/node_generators/synonym_node.py,sha256=9LHK2XHDjbyTLjmDQieskG8fqbiSpRnFOkfrutDnOTE,2258
|
|
53
|
-
trilogy/core/processing/node_generators/union_node.py,sha256=
|
|
53
|
+
trilogy/core/processing/node_generators/union_node.py,sha256=VNo6Oey4p8etU9xrOh2oTT2lIOTvY6PULUPRvVa2uxU,2877
|
|
54
54
|
trilogy/core/processing/node_generators/unnest_node.py,sha256=cOEKnMRzXUW3bwmiOlgn3E1-B38osng0dh2pDykwITY,2410
|
|
55
55
|
trilogy/core/processing/node_generators/window_node.py,sha256=MjLmFKUiS-_p-Ak_9mr3becGde9eu5frxmqI7plIETY,5808
|
|
56
56
|
trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
57
|
trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=GMW07bb6hXurhF0hZLYoMAKSIS65tat5hwBjvqqPeSA,6516
|
|
58
|
-
trilogy/core/processing/nodes/__init__.py,sha256=
|
|
58
|
+
trilogy/core/processing/nodes/__init__.py,sha256=Lxr3rs_bqOAtMtn3DHIkY058ZzjyLM5mSfGMKW2z0NY,5555
|
|
59
59
|
trilogy/core/processing/nodes/base_node.py,sha256=FHrY8GsTKPuMJklOjILbhGqCt5s1nmlj62Z-molARDA,16835
|
|
60
60
|
trilogy/core/processing/nodes/filter_node.py,sha256=5VtRfKbCORx0dV-vQfgy3gOEkmmscL9f31ExvlODwvY,2461
|
|
61
61
|
trilogy/core/processing/nodes/group_node.py,sha256=MUvcOg9U5J6TnWBel8eht9PdI9BfAKjUxmfjP_ZXx9o,10484
|
|
62
|
-
trilogy/core/processing/nodes/merge_node.py,sha256=
|
|
62
|
+
trilogy/core/processing/nodes/merge_node.py,sha256=02oWRca0ba41U6PSAB14jwnWWxoyrvxRPLwkli259SY,15865
|
|
63
63
|
trilogy/core/processing/nodes/select_node_v2.py,sha256=Xyfq8lU7rP7JTAd8VV0ATDNal64n4xIBgWQsOuMe_Ak,8824
|
|
64
64
|
trilogy/core/processing/nodes/union_node.py,sha256=fDFzLAUh5876X6_NM7nkhoMvHEdGJ_LpvPokpZKOhx4,1425
|
|
65
65
|
trilogy/core/processing/nodes/unnest_node.py,sha256=oLKMMNMx6PLDPlt2V5neFMFrFWxET8r6XZElAhSNkO0,2181
|
|
66
66
|
trilogy/core/processing/nodes/window_node.py,sha256=JXJ0iVRlSEM2IBr1TANym2RaUf_p5E_l2sNykRzXWDo,1710
|
|
67
67
|
trilogy/core/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
68
|
-
trilogy/core/statements/author.py,sha256=
|
|
68
|
+
trilogy/core/statements/author.py,sha256=rYDf9rCQ4YKEO9br1OWmOZd-51AiaDkaWYegvteJa8M,14728
|
|
69
69
|
trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
70
70
|
trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWhnK1g,759
|
|
71
71
|
trilogy/core/statements/execute.py,sha256=cSlvpHFOqpiZ89pPZ5GDp9Hu6j6uj-5_h21FWm_L-KM,1248
|
|
72
72
|
trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
73
|
-
trilogy/dialect/base.py,sha256=
|
|
73
|
+
trilogy/dialect/base.py,sha256=vp6_9fUkblAWVpCXGBIcoAx6N7vof9M7s9t6-b_waUY,41409
|
|
74
74
|
trilogy/dialect/bigquery.py,sha256=j5PQvwMUMcLHaxZgbqe6P-v-pwhHDQ38z8uK6ecxzR0,3359
|
|
75
75
|
trilogy/dialect/common.py,sha256=XjHkP8Dqezjkd2JU5xoAlMRS_6HNyXQCF4CykLK3C8o,5011
|
|
76
76
|
trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
|
|
@@ -87,13 +87,13 @@ trilogy/hooks/graph_hook.py,sha256=c-vC-IXoJ_jDmKQjxQyIxyXPOuUcLIURB573gCsAfzQ,2
|
|
|
87
87
|
trilogy/hooks/query_debugger.py,sha256=1npRjww94sPV5RRBBlLqMJRaFkH9vhEY6o828MeoEcw,5583
|
|
88
88
|
trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
89
89
|
trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
90
|
-
trilogy/parsing/common.py,sha256=
|
|
90
|
+
trilogy/parsing/common.py,sha256=GXGDnlHKTzcNx8NmcUNnfyUVNOcSgajrlz4YXdOTRSM,25931
|
|
91
91
|
trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
|
|
92
92
|
trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
|
|
93
93
|
trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
94
|
-
trilogy/parsing/parse_engine.py,sha256=
|
|
94
|
+
trilogy/parsing/parse_engine.py,sha256=Tagt3zPBViM-GZYuqHb1SnbEIwa8fx12kF07JMgmyV4,65312
|
|
95
95
|
trilogy/parsing/render.py,sha256=hI4y-xjXrEXvHslY2l2TQ8ic0zAOpN41ADH37J2_FZY,19047
|
|
96
|
-
trilogy/parsing/trilogy.lark,sha256=
|
|
96
|
+
trilogy/parsing/trilogy.lark,sha256=2Noe58vGYteilKd6w-np3fb4lzWI-G9Gt0AMyOMVw3k,13735
|
|
97
97
|
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
98
98
|
trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
|
|
99
99
|
trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -102,8 +102,8 @@ trilogy/std/display.preql,sha256=2BbhvqR4rcltyAbOXAUo7SZ_yGFYZgFnurglHMbjW2g,40
|
|
|
102
102
|
trilogy/std/geography.preql,sha256=-fqAGnBL6tR-UtT8DbSek3iMFg66ECR_B_41pODxv-k,504
|
|
103
103
|
trilogy/std/money.preql,sha256=ZHW-csTX-kYbOLmKSO-TcGGgQ-_DMrUXy0BjfuJSFxM,80
|
|
104
104
|
trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
|
|
105
|
-
pytrilogy-0.0.3.
|
|
106
|
-
pytrilogy-0.0.3.
|
|
107
|
-
pytrilogy-0.0.3.
|
|
108
|
-
pytrilogy-0.0.3.
|
|
109
|
-
pytrilogy-0.0.3.
|
|
105
|
+
pytrilogy-0.0.3.39.dist-info/METADATA,sha256=4lI1j2Kk43ajM4gujEmJDlu0vXWN2tMPqQfudt4ZKaQ,9100
|
|
106
|
+
pytrilogy-0.0.3.39.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
|
107
|
+
pytrilogy-0.0.3.39.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
|
|
108
|
+
pytrilogy-0.0.3.39.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
109
|
+
pytrilogy-0.0.3.39.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from trilogy.constants import DEFAULT_NAMESPACE
|
|
2
2
|
from trilogy.core.enums import ConceptSource, DatePart, FunctionType, Purpose
|
|
3
|
-
from trilogy.core.functions import AttrAccess
|
|
4
|
-
from trilogy.core.models.author import Concept, Function, Metadata
|
|
3
|
+
from trilogy.core.functions import AttrAccess
|
|
4
|
+
from trilogy.core.models.author import Concept, Function, Metadata, TraitDataType
|
|
5
5
|
from trilogy.core.models.core import DataType, StructType, arg_to_datatype
|
|
6
6
|
from trilogy.core.models.environment import Environment
|
|
7
|
-
from trilogy.parsing.common import Meta
|
|
7
|
+
from trilogy.parsing.common import Meta
|
|
8
8
|
|
|
9
9
|
FUNCTION_DESCRIPTION_MAPS = {
|
|
10
10
|
FunctionType.DATE: "The date part of a timestamp/date. Integer, 0-31 depending on month.",
|
|
@@ -19,7 +19,6 @@ FUNCTION_DESCRIPTION_MAPS = {
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def generate_date_concepts(concept: Concept, environment: Environment):
|
|
22
|
-
factory = FunctionFactory(environment=environment)
|
|
23
22
|
if concept.metadata and concept.metadata.description:
|
|
24
23
|
base_description = concept.metadata.description
|
|
25
24
|
else:
|
|
@@ -28,24 +27,36 @@ def generate_date_concepts(concept: Concept, environment: Environment):
|
|
|
28
27
|
base_line_number = concept.metadata.line_number
|
|
29
28
|
else:
|
|
30
29
|
base_line_number = None
|
|
31
|
-
|
|
32
|
-
FunctionType.MONTH,
|
|
33
|
-
FunctionType.YEAR,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
arg_tuples: list[tuple[FunctionType, TraitDataType]] = [
|
|
31
|
+
(FunctionType.MONTH, TraitDataType(type=DataType.INTEGER, traits=["month"])),
|
|
32
|
+
(FunctionType.YEAR, TraitDataType(type=DataType.INTEGER, traits=["year"])),
|
|
33
|
+
(
|
|
34
|
+
FunctionType.QUARTER,
|
|
35
|
+
TraitDataType(type=DataType.INTEGER, traits=["quarter"]),
|
|
36
|
+
),
|
|
37
|
+
(FunctionType.DAY, TraitDataType(type=DataType.INTEGER, traits=["day"])),
|
|
38
|
+
(
|
|
39
|
+
FunctionType.DAY_OF_WEEK,
|
|
40
|
+
TraitDataType(type=DataType.INTEGER, traits=["day_of_week"]),
|
|
41
|
+
),
|
|
42
|
+
]
|
|
43
|
+
for ftype, dtype in arg_tuples:
|
|
38
44
|
fname = ftype.name.lower()
|
|
45
|
+
address = concept.address + f".{fname}"
|
|
46
|
+
if address in environment.concepts:
|
|
47
|
+
continue
|
|
39
48
|
default_type = (
|
|
40
49
|
Purpose.CONSTANT
|
|
41
50
|
if concept.purpose == Purpose.CONSTANT
|
|
42
51
|
else Purpose.PROPERTY
|
|
43
52
|
)
|
|
44
|
-
function =
|
|
53
|
+
function = Function.model_construct(
|
|
45
54
|
operator=ftype,
|
|
46
|
-
|
|
55
|
+
arguments=[concept.reference],
|
|
56
|
+
output_datatype=dtype,
|
|
57
|
+
output_purpose=default_type,
|
|
47
58
|
)
|
|
48
|
-
new_concept = Concept(
|
|
59
|
+
new_concept = Concept.model_construct(
|
|
49
60
|
name=f"{concept.name}.{fname}",
|
|
50
61
|
datatype=function.output_datatype,
|
|
51
62
|
purpose=default_type,
|
|
@@ -61,15 +72,19 @@ def generate_date_concepts(concept: Concept, environment: Environment):
|
|
|
61
72
|
concept_source=ConceptSource.AUTO_DERIVED,
|
|
62
73
|
),
|
|
63
74
|
)
|
|
64
|
-
if new_concept.name in environment.concepts:
|
|
65
|
-
continue
|
|
66
75
|
environment.add_concept(new_concept, add_derived=False)
|
|
67
76
|
for grain in [DatePart.MONTH, DatePart.YEAR]:
|
|
68
|
-
|
|
77
|
+
address = concept.address + f".{grain.value}_start"
|
|
78
|
+
if address in environment.concepts:
|
|
79
|
+
continue
|
|
80
|
+
function = Function.model_construct(
|
|
69
81
|
operator=FunctionType.DATE_TRUNCATE,
|
|
70
|
-
|
|
82
|
+
arguments=[concept.reference, grain],
|
|
83
|
+
output_datatype=DataType.DATE,
|
|
84
|
+
output_purpose=default_type,
|
|
85
|
+
arg_count=2,
|
|
71
86
|
)
|
|
72
|
-
new_concept = Concept(
|
|
87
|
+
new_concept = Concept.model_construct(
|
|
73
88
|
name=f"{concept.name}.{grain.value}_start",
|
|
74
89
|
datatype=DataType.DATE,
|
|
75
90
|
purpose=Purpose.PROPERTY,
|
|
@@ -85,13 +100,11 @@ def generate_date_concepts(concept: Concept, environment: Environment):
|
|
|
85
100
|
concept_source=ConceptSource.AUTO_DERIVED,
|
|
86
101
|
),
|
|
87
102
|
)
|
|
88
|
-
|
|
89
|
-
continue
|
|
103
|
+
|
|
90
104
|
environment.add_concept(new_concept, add_derived=False)
|
|
91
105
|
|
|
92
106
|
|
|
93
107
|
def generate_datetime_concepts(concept: Concept, environment: Environment):
|
|
94
|
-
factory = FunctionFactory(environment=environment)
|
|
95
108
|
if concept.metadata and concept.metadata.description:
|
|
96
109
|
base_description = concept.metadata.description
|
|
97
110
|
else:
|
|
@@ -100,24 +113,29 @@ def generate_datetime_concepts(concept: Concept, environment: Environment):
|
|
|
100
113
|
base_line_number = concept.metadata.line_number
|
|
101
114
|
else:
|
|
102
115
|
base_line_number = None
|
|
103
|
-
|
|
104
|
-
FunctionType.DATE,
|
|
105
|
-
FunctionType.HOUR,
|
|
106
|
-
FunctionType.MINUTE,
|
|
107
|
-
FunctionType.SECOND,
|
|
108
|
-
]
|
|
116
|
+
setup_tuples: list[tuple[FunctionType, DataType | TraitDataType]] = [
|
|
117
|
+
(FunctionType.DATE, DataType.DATE),
|
|
118
|
+
(FunctionType.HOUR, TraitDataType(type=DataType.INTEGER, traits=["hour"])),
|
|
119
|
+
(FunctionType.MINUTE, TraitDataType(type=DataType.INTEGER, traits=["minute"])),
|
|
120
|
+
(FunctionType.SECOND, TraitDataType(type=DataType.INTEGER, traits=["second"])),
|
|
121
|
+
]
|
|
122
|
+
for ftype, datatype in setup_tuples:
|
|
109
123
|
fname = ftype.name.lower()
|
|
124
|
+
address = concept.address + f".{fname}"
|
|
125
|
+
if address in environment.concepts:
|
|
126
|
+
continue
|
|
110
127
|
default_type = (
|
|
111
128
|
Purpose.CONSTANT
|
|
112
129
|
if concept.purpose == Purpose.CONSTANT
|
|
113
130
|
else Purpose.PROPERTY
|
|
114
131
|
)
|
|
115
|
-
|
|
116
|
-
const_function = factory.create_function(
|
|
132
|
+
const_function = Function.model_construct(
|
|
117
133
|
operator=ftype,
|
|
118
|
-
|
|
134
|
+
arguments=[concept.reference],
|
|
135
|
+
output_datatype=datatype,
|
|
136
|
+
output_purpose=default_type,
|
|
119
137
|
)
|
|
120
|
-
new_concept = Concept(
|
|
138
|
+
new_concept = Concept.model_construct(
|
|
121
139
|
name=f"{concept.name}.{fname}",
|
|
122
140
|
datatype=const_function.output_datatype,
|
|
123
141
|
purpose=default_type,
|
|
@@ -148,15 +166,18 @@ def generate_key_concepts(concept: Concept, environment: Environment):
|
|
|
148
166
|
else:
|
|
149
167
|
base_line_number = None
|
|
150
168
|
for ftype in [FunctionType.COUNT]:
|
|
169
|
+
address = concept.address + f".{ftype.name.lower()}"
|
|
170
|
+
if address in environment.concepts:
|
|
171
|
+
continue
|
|
151
172
|
fname = ftype.name.lower()
|
|
152
173
|
default_type = Purpose.METRIC
|
|
153
|
-
const_function: Function = Function(
|
|
174
|
+
const_function: Function = Function.model_construct(
|
|
154
175
|
operator=ftype,
|
|
155
176
|
output_datatype=DataType.INTEGER,
|
|
156
177
|
output_purpose=default_type,
|
|
157
|
-
arguments=[concept],
|
|
178
|
+
arguments=[concept.reference],
|
|
158
179
|
)
|
|
159
|
-
new_concept = Concept(
|
|
180
|
+
new_concept = Concept.model_construct(
|
|
160
181
|
name=f"{concept.name}.{fname}",
|
|
161
182
|
datatype=DataType.INTEGER,
|
|
162
183
|
purpose=default_type,
|
|
@@ -172,8 +193,6 @@ def generate_key_concepts(concept: Concept, environment: Environment):
|
|
|
172
193
|
concept_source=ConceptSource.AUTO_DERIVED,
|
|
173
194
|
),
|
|
174
195
|
)
|
|
175
|
-
if new_concept.name in environment.concepts:
|
|
176
|
-
continue
|
|
177
196
|
environment.add_concept(new_concept, add_derived=False)
|
|
178
197
|
|
|
179
198
|
|
|
@@ -199,10 +218,7 @@ def generate_related_concepts(
|
|
|
199
218
|
|
|
200
219
|
if isinstance(concept.datatype, StructType):
|
|
201
220
|
for key, value in concept.datatype.fields_map.items():
|
|
202
|
-
|
|
203
|
-
[concept, key], meta=meta, environment=environment
|
|
204
|
-
)
|
|
205
|
-
auto = Concept(
|
|
221
|
+
auto = Concept.model_construct(
|
|
206
222
|
name=key,
|
|
207
223
|
datatype=arg_to_datatype(value),
|
|
208
224
|
purpose=Purpose.PROPERTY,
|
|
@@ -212,7 +228,8 @@ def generate_related_concepts(
|
|
|
212
228
|
and environment.namespace != DEFAULT_NAMESPACE
|
|
213
229
|
else concept.name
|
|
214
230
|
),
|
|
215
|
-
lineage=AttrAccess(
|
|
231
|
+
lineage=AttrAccess([concept.reference, key], environment=environment),
|
|
232
|
+
grain=concept.grain,
|
|
216
233
|
)
|
|
217
234
|
environment.add_concept(auto, meta=meta)
|
|
218
235
|
if isinstance(value, Concept):
|
trilogy/core/functions.py
CHANGED
|
@@ -17,6 +17,7 @@ from trilogy.core.exceptions import InvalidSyntaxException
|
|
|
17
17
|
from trilogy.core.models.author import (
|
|
18
18
|
AggregateWrapper,
|
|
19
19
|
Concept,
|
|
20
|
+
ConceptRef,
|
|
20
21
|
Function,
|
|
21
22
|
Parenthetical,
|
|
22
23
|
UndefinedConcept,
|
|
@@ -35,7 +36,7 @@ from trilogy.core.models.core import (
|
|
|
35
36
|
)
|
|
36
37
|
from trilogy.core.models.environment import Environment
|
|
37
38
|
|
|
38
|
-
GENERIC_ARGS = Concept | Function | str | int | float | date | datetime
|
|
39
|
+
GENERIC_ARGS = Concept | ConceptRef | Function | str | int | float | date | datetime
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
@dataclass
|
|
@@ -191,6 +192,7 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
191
192
|
),
|
|
192
193
|
FunctionType.GROUP: FunctionConfig(
|
|
193
194
|
arg_count=-1,
|
|
195
|
+
output_type_function=lambda args: get_output_type_at_index(args, 0),
|
|
194
196
|
),
|
|
195
197
|
FunctionType.COUNT: FunctionConfig(
|
|
196
198
|
output_purpose=Purpose.METRIC,
|
|
@@ -772,7 +774,7 @@ class FunctionFactory:
|
|
|
772
774
|
output_purpose = Purpose.PROPERTY
|
|
773
775
|
return Function(
|
|
774
776
|
operator=operator,
|
|
775
|
-
arguments=full_args,
|
|
777
|
+
arguments=full_args, # type: ignore
|
|
776
778
|
output_datatype=final_output_type,
|
|
777
779
|
output_purpose=output_purpose,
|
|
778
780
|
valid_inputs=valid_inputs,
|
|
@@ -803,7 +805,7 @@ def create_function_derived_concept(
|
|
|
803
805
|
purpose=purpose,
|
|
804
806
|
lineage=Function(
|
|
805
807
|
operator=operator,
|
|
806
|
-
arguments=arguments,
|
|
808
|
+
arguments=[x.reference for x in arguments],
|
|
807
809
|
output_datatype=output_type,
|
|
808
810
|
output_purpose=purpose,
|
|
809
811
|
arg_count=len(arguments),
|
|
@@ -895,7 +897,7 @@ def Split(args: list[Concept], environment: Environment) -> Function:
|
|
|
895
897
|
)
|
|
896
898
|
|
|
897
899
|
|
|
898
|
-
def AttrAccess(args: list[
|
|
900
|
+
def AttrAccess(args: list[ConceptRef | str | int], environment: Environment):
|
|
899
901
|
return FunctionFactory(environment).create_function(
|
|
900
902
|
args=args, operator=FunctionType.ATTR_ACCESS
|
|
901
903
|
)
|
trilogy/core/models/author.py
CHANGED
|
@@ -180,8 +180,14 @@ class UndefinedConcept(ConceptRef):
|
|
|
180
180
|
|
|
181
181
|
def address_with_namespace(address: str, namespace: str) -> str:
|
|
182
182
|
existing_ns = address.split(".", 1)[0]
|
|
183
|
+
if "." in address:
|
|
184
|
+
existing_name = address.split(".", 1)[1]
|
|
185
|
+
else:
|
|
186
|
+
existing_name = address
|
|
187
|
+
if existing_name == ALL_ROWS_CONCEPT:
|
|
188
|
+
return address
|
|
183
189
|
if existing_ns == DEFAULT_NAMESPACE:
|
|
184
|
-
return f"{namespace}.{
|
|
190
|
+
return f"{namespace}.{existing_name}"
|
|
185
191
|
return f"{namespace}.{address}"
|
|
186
192
|
|
|
187
193
|
|
|
@@ -1129,7 +1135,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
|
|
|
1129
1135
|
return self.lineage.concept_arguments if self.lineage else []
|
|
1130
1136
|
|
|
1131
1137
|
@classmethod
|
|
1132
|
-
def calculate_derivation(self, lineage, purpose):
|
|
1138
|
+
def calculate_derivation(self, lineage, purpose: Purpose) -> Derivation:
|
|
1133
1139
|
from trilogy.core.models.build import (
|
|
1134
1140
|
BuildAggregateWrapper,
|
|
1135
1141
|
BuildFilterItem,
|
|
@@ -1598,9 +1604,6 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1598
1604
|
] = None
|
|
1599
1605
|
arguments: Sequence[FuncArgs]
|
|
1600
1606
|
|
|
1601
|
-
def __init__(self, **kwargs):
|
|
1602
|
-
super().__init__(**kwargs)
|
|
1603
|
-
|
|
1604
1607
|
def __repr__(self):
|
|
1605
1608
|
return f'{self.operator.value}({",".join([str(a) for a in self.arguments])})'
|
|
1606
1609
|
|
trilogy/core/models/build.py
CHANGED
|
@@ -65,6 +65,7 @@ from trilogy.core.models.author import (
|
|
|
65
65
|
RowsetLineage,
|
|
66
66
|
SelectLineage,
|
|
67
67
|
SubselectComparison,
|
|
68
|
+
UndefinedConcept,
|
|
68
69
|
WhereClause,
|
|
69
70
|
WindowItem,
|
|
70
71
|
)
|
|
@@ -800,9 +801,6 @@ class BuildConcept(Addressable, BuildConceptArgs, DataTyped, BaseModel):
|
|
|
800
801
|
modifiers: List[Modifier] = Field(default_factory=list) # type: ignore
|
|
801
802
|
pseudonyms: set[str] = Field(default_factory=set)
|
|
802
803
|
|
|
803
|
-
def with_select_context(self, *args, **kwargs):
|
|
804
|
-
return self
|
|
805
|
-
|
|
806
804
|
@property
|
|
807
805
|
def is_aggregate(self) -> bool:
|
|
808
806
|
return self.build_is_aggregate
|
|
@@ -1503,7 +1501,7 @@ class Factory:
|
|
|
1503
1501
|
return base
|
|
1504
1502
|
|
|
1505
1503
|
@build.register
|
|
1506
|
-
def _(self, base: Function) -> BuildFunction:
|
|
1504
|
+
def _(self, base: Function) -> BuildFunction | BuildAggregateWrapper:
|
|
1507
1505
|
from trilogy.parsing.common import arbitrary_to_concept
|
|
1508
1506
|
|
|
1509
1507
|
raw_args: list[Concept | FuncArgs] = []
|
|
@@ -1517,6 +1515,54 @@ class Factory:
|
|
|
1517
1515
|
raw_args.append(narg)
|
|
1518
1516
|
else:
|
|
1519
1517
|
raw_args.append(arg)
|
|
1518
|
+
if base.operator == FunctionType.GROUP:
|
|
1519
|
+
group_base = raw_args[0]
|
|
1520
|
+
final_args: List[Concept | ConceptRef] = []
|
|
1521
|
+
if isinstance(group_base, ConceptRef):
|
|
1522
|
+
if group_base.address in self.environment.concepts and not isinstance(
|
|
1523
|
+
self.environment.concepts[group_base.address], UndefinedConcept
|
|
1524
|
+
):
|
|
1525
|
+
group_base = self.environment.concepts[group_base.address]
|
|
1526
|
+
if (
|
|
1527
|
+
isinstance(group_base, Concept)
|
|
1528
|
+
and isinstance(group_base.lineage, AggregateWrapper)
|
|
1529
|
+
and not group_base.lineage.by
|
|
1530
|
+
):
|
|
1531
|
+
arguments = raw_args[1:]
|
|
1532
|
+
for x in arguments:
|
|
1533
|
+
if isinstance(x, (ConceptRef, Concept)):
|
|
1534
|
+
final_args.append(x)
|
|
1535
|
+
elif isinstance(x, (AggregateWrapper, FilterItem, WindowItem)):
|
|
1536
|
+
newx = arbitrary_to_concept(
|
|
1537
|
+
x,
|
|
1538
|
+
environment=self.environment,
|
|
1539
|
+
)
|
|
1540
|
+
final_args.append(newx)
|
|
1541
|
+
else:
|
|
1542
|
+
# constants, etc, can be ignored for group
|
|
1543
|
+
continue
|
|
1544
|
+
group_base = group_base.model_copy(
|
|
1545
|
+
deep=True,
|
|
1546
|
+
update={
|
|
1547
|
+
"lineage": AggregateWrapper(
|
|
1548
|
+
function=group_base.lineage.function,
|
|
1549
|
+
by=final_args,
|
|
1550
|
+
)
|
|
1551
|
+
},
|
|
1552
|
+
)
|
|
1553
|
+
group_base = group_base.with_grain(
|
|
1554
|
+
Grain.from_concepts(final_args, environment=self.environment)
|
|
1555
|
+
)
|
|
1556
|
+
rval = self.build(group_base)
|
|
1557
|
+
return BuildFunction.model_construct(
|
|
1558
|
+
operator=base.operator,
|
|
1559
|
+
arguments=[rval, *[self.build(c) for c in raw_args[1:]]],
|
|
1560
|
+
output_datatype=base.output_datatype,
|
|
1561
|
+
output_purpose=base.output_purpose,
|
|
1562
|
+
valid_inputs=base.valid_inputs,
|
|
1563
|
+
arg_count=base.arg_count,
|
|
1564
|
+
)
|
|
1565
|
+
|
|
1520
1566
|
new = BuildFunction.model_construct(
|
|
1521
1567
|
operator=base.operator,
|
|
1522
1568
|
arguments=[self.build(c) for c in raw_args],
|
|
@@ -1568,7 +1614,10 @@ class Factory:
|
|
|
1568
1614
|
new_lineage, final_grain, _ = base.get_select_grain_and_keys(
|
|
1569
1615
|
self.grain, self.environment
|
|
1570
1616
|
)
|
|
1571
|
-
|
|
1617
|
+
if new_lineage:
|
|
1618
|
+
build_lineage = self.build(new_lineage)
|
|
1619
|
+
else:
|
|
1620
|
+
build_lineage = None
|
|
1572
1621
|
derivation = Concept.calculate_derivation(build_lineage, base.purpose)
|
|
1573
1622
|
granularity = Concept.calculate_granularity(
|
|
1574
1623
|
derivation, final_grain, build_lineage
|
|
@@ -129,6 +129,9 @@ class EnvironmentConceptDict(dict):
|
|
|
129
129
|
def __getitem__(
|
|
130
130
|
self, key: str, line_no: int | None = None, file: Path | None = None
|
|
131
131
|
) -> Concept | UndefinedConceptFull:
|
|
132
|
+
# fast access path
|
|
133
|
+
if key in self.keys():
|
|
134
|
+
return super(EnvironmentConceptDict, self).__getitem__(key)
|
|
132
135
|
if isinstance(key, ConceptRef):
|
|
133
136
|
return self.__getitem__(key.address, line_no=line_no, file=file)
|
|
134
137
|
try:
|
|
@@ -311,11 +314,15 @@ class Environment(BaseModel):
|
|
|
311
314
|
f.write(self.model_dump_json())
|
|
312
315
|
return ppath
|
|
313
316
|
|
|
314
|
-
def validate_concept(
|
|
317
|
+
def validate_concept(
|
|
318
|
+
self, new_concept: Concept, meta: Meta | None = None
|
|
319
|
+
) -> Concept | None:
|
|
315
320
|
lookup = new_concept.address
|
|
321
|
+
if lookup not in self.concepts:
|
|
322
|
+
return None
|
|
316
323
|
existing: Concept = self.concepts.get(lookup) # type: ignore
|
|
317
|
-
if
|
|
318
|
-
return
|
|
324
|
+
if isinstance(existing, UndefinedConcept):
|
|
325
|
+
return None
|
|
319
326
|
|
|
320
327
|
def handle_persist():
|
|
321
328
|
deriv_lookup = (
|
|
@@ -361,7 +368,7 @@ class Environment(BaseModel):
|
|
|
361
368
|
if existing and self.config.allow_duplicate_declaration:
|
|
362
369
|
if existing.metadata.concept_source == ConceptSource.PERSIST_STATEMENT:
|
|
363
370
|
return handle_persist()
|
|
364
|
-
return
|
|
371
|
+
return None
|
|
365
372
|
elif existing.metadata:
|
|
366
373
|
if existing.metadata.concept_source == ConceptSource.PERSIST_STATEMENT:
|
|
367
374
|
return handle_persist()
|
|
@@ -110,11 +110,14 @@ def get_priority_concept(
|
|
|
110
110
|
depth: int,
|
|
111
111
|
) -> BuildConcept:
|
|
112
112
|
# optimized search for missing concepts
|
|
113
|
-
pass_one =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
pass_one = sorted(
|
|
114
|
+
[
|
|
115
|
+
c
|
|
116
|
+
for c in all_concepts
|
|
117
|
+
if c.address not in attempted_addresses and c.address not in found_concepts
|
|
118
|
+
],
|
|
119
|
+
key=lambda x: x.address,
|
|
120
|
+
)
|
|
118
121
|
# sometimes we need to scan intermediate concepts to get merge keys or filter keys,
|
|
119
122
|
# so do an exhaustive search
|
|
120
123
|
# pass_two = [c for c in all_concepts+filter_only if c.address not in attempted_addresses]
|
|
@@ -388,6 +391,9 @@ def generate_node(
|
|
|
388
391
|
# conditions=conditions,
|
|
389
392
|
)
|
|
390
393
|
else:
|
|
394
|
+
logger.info(
|
|
395
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} skipping search, already in a recursion fot these concepts"
|
|
396
|
+
)
|
|
391
397
|
return None
|
|
392
398
|
return ConstantNode(
|
|
393
399
|
input_concepts=[],
|
|
@@ -453,9 +459,10 @@ def generate_node(
|
|
|
453
459
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} including any filters, there are non-root concepts we should expand first: {non_root}. Recursing with all of these as mandatory"
|
|
454
460
|
)
|
|
455
461
|
|
|
456
|
-
if not history.check_started(
|
|
457
|
-
|
|
458
|
-
):
|
|
462
|
+
# if not history.check_started(
|
|
463
|
+
# root_targets, accept_partial=accept_partial, conditions=conditions
|
|
464
|
+
# ) or 1==1:
|
|
465
|
+
if True:
|
|
459
466
|
history.log_start(
|
|
460
467
|
root_targets, accept_partial=accept_partial, conditions=conditions
|
|
461
468
|
)
|
|
@@ -470,7 +477,10 @@ def generate_node(
|
|
|
470
477
|
# which we do whenever we hit a root node
|
|
471
478
|
# conditions=conditions,
|
|
472
479
|
)
|
|
473
|
-
|
|
480
|
+
else:
|
|
481
|
+
logger.info(
|
|
482
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} skipping root search, already in a recursion for these concepts"
|
|
483
|
+
)
|
|
474
484
|
check = history.gen_select_node(
|
|
475
485
|
concept,
|
|
476
486
|
local_optional,
|
|
@@ -546,7 +556,10 @@ def generate_node(
|
|
|
546
556
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} resolved concepts through synonyms"
|
|
547
557
|
)
|
|
548
558
|
return resolved
|
|
549
|
-
|
|
559
|
+
else:
|
|
560
|
+
logger.info(
|
|
561
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} skipping synonym search, already in a recursion for these concepts"
|
|
562
|
+
)
|
|
550
563
|
return None
|
|
551
564
|
else:
|
|
552
565
|
raise ValueError(f"Unknown derivation {concept.derivation} on {concept}")
|
|
@@ -1041,7 +1054,6 @@ def _search_concepts(
|
|
|
1041
1054
|
environment=environment,
|
|
1042
1055
|
depth=depth,
|
|
1043
1056
|
)
|
|
1044
|
-
logger.info(f"gcheck result is {result}")
|
|
1045
1057
|
if result.required:
|
|
1046
1058
|
logger.info(
|
|
1047
1059
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Adding group node"
|
|
@@ -1104,7 +1116,6 @@ def source_query_concepts(
|
|
|
1104
1116
|
history=history,
|
|
1105
1117
|
conditions=conditions,
|
|
1106
1118
|
)
|
|
1107
|
-
|
|
1108
1119
|
if not root:
|
|
1109
1120
|
error_strings = [
|
|
1110
1121
|
f"{c.address}<{c.purpose}>{c.derivation}>" for c in output_concepts
|
|
@@ -1130,5 +1141,4 @@ def source_query_concepts(
|
|
|
1130
1141
|
)
|
|
1131
1142
|
else:
|
|
1132
1143
|
candidate = root
|
|
1133
|
-
|
|
1134
1144
|
return candidate
|