gooddata-eval 1.71.1.dev2__py3-none-any.whl → 1.71.1.dev3__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.
- gooddata_eval/core/scoring.py +70 -20
- {gooddata_eval-1.71.1.dev2.dist-info → gooddata_eval-1.71.1.dev3.dist-info}/METADATA +2 -2
- {gooddata_eval-1.71.1.dev2.dist-info → gooddata_eval-1.71.1.dev3.dist-info}/RECORD +6 -6
- {gooddata_eval-1.71.1.dev2.dist-info → gooddata_eval-1.71.1.dev3.dist-info}/WHEEL +0 -0
- {gooddata_eval-1.71.1.dev2.dist-info → gooddata_eval-1.71.1.dev3.dist-info}/entry_points.txt +0 -0
- {gooddata_eval-1.71.1.dev2.dist-info → gooddata_eval-1.71.1.dev3.dist-info}/licenses/LICENSE.txt +0 -0
gooddata_eval/core/scoring.py
CHANGED
|
@@ -63,29 +63,46 @@ def uri_to_display_name(uri: str) -> str:
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
def validate_cross_references(viz: CreatedVisualization) -> tuple[bool, list[str]]:
|
|
66
|
-
"""Validate ranking-filter `using`/`attribute` resolve to correct URI prefixes.
|
|
66
|
+
"""Validate ranking-filter `using`/`attribute` resolve to correct URI prefixes.
|
|
67
|
+
|
|
68
|
+
Always returns `(ok, errors)` — a malformed filter produces an error entry, never an
|
|
69
|
+
exception. Anything unusable (None, empty, non-string) used to reach `.startswith()`
|
|
70
|
+
or `dict.get()` and blow up with AttributeError/TypeError mid-evaluation.
|
|
71
|
+
|
|
72
|
+
`using` is required by the AAC schema, `attribute` is optional (see
|
|
73
|
+
`_normalize_ranking_filter`), so an absent/None/empty `attribute` is accepted silently.
|
|
74
|
+
"""
|
|
67
75
|
errors: list[str] = []
|
|
68
76
|
fields = viz.query.fields
|
|
69
77
|
for filter_key, filter_dict in viz.query.filter_by.items():
|
|
70
78
|
if filter_dict.get("type") != "ranking_filter":
|
|
71
79
|
continue
|
|
72
|
-
using_val = filter_dict.get("using"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
if "attribute" in filter_dict:
|
|
82
|
-
attr_val = filter_dict["attribute"]
|
|
83
|
-
attr_uri = _resolve_alias_to_uri(attr_val, fields)
|
|
84
|
-
if not attr_uri.startswith(("label/", "attribute/")):
|
|
80
|
+
using_val = filter_dict.get("using")
|
|
81
|
+
if not isinstance(using_val, str) or not using_val:
|
|
82
|
+
errors.append(f"ranking filter '{filter_key}': using={using_val!r} — a metric/ or fact/ URI is required")
|
|
83
|
+
else:
|
|
84
|
+
using_uri = _resolve_alias_to_uri(using_val, fields)
|
|
85
|
+
field_def = fields.get(using_val)
|
|
86
|
+
is_adhoc_agg = isinstance(field_def, AacQueryField) and bool(field_def.aggregation)
|
|
87
|
+
if not using_uri.startswith(("metric/", "fact/")) and not is_adhoc_agg:
|
|
85
88
|
errors.append(
|
|
86
|
-
f"ranking filter '{filter_key}':
|
|
87
|
-
f"resolves to '{
|
|
89
|
+
f"ranking filter '{filter_key}': using='{using_val}' "
|
|
90
|
+
f"resolves to '{using_uri}' — expected a metric/ or fact/ URI"
|
|
88
91
|
)
|
|
92
|
+
attr_val = filter_dict.get("attribute")
|
|
93
|
+
if attr_val is None or attr_val == "":
|
|
94
|
+
continue
|
|
95
|
+
if not isinstance(attr_val, str):
|
|
96
|
+
errors.append(
|
|
97
|
+
f"ranking filter '{filter_key}': attribute={attr_val!r} — expected a label/ or attribute/ URI"
|
|
98
|
+
)
|
|
99
|
+
continue
|
|
100
|
+
attr_uri = _resolve_alias_to_uri(attr_val, fields)
|
|
101
|
+
if not attr_uri.startswith(("label/", "attribute/")):
|
|
102
|
+
errors.append(
|
|
103
|
+
f"ranking filter '{filter_key}': attribute='{attr_val}' "
|
|
104
|
+
f"resolves to '{attr_uri}' — expected a label/ or attribute/ URI"
|
|
105
|
+
)
|
|
89
106
|
return len(errors) == 0, errors
|
|
90
107
|
|
|
91
108
|
|
|
@@ -99,11 +116,43 @@ def _normalize_date_filter(filter_dict: dict, _fields: dict) -> dict:
|
|
|
99
116
|
}
|
|
100
117
|
|
|
101
118
|
|
|
102
|
-
def
|
|
119
|
+
def _sole_dimension_uri(viz: CreatedVisualization) -> str | None:
|
|
120
|
+
"""URI of the visualization's only dimension, or None when it has zero or several."""
|
|
121
|
+
dim_uris = get_dimension_uri_set(viz)
|
|
122
|
+
return next(iter(dim_uris)) if len(dim_uris) == 1 else None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _normalize_ranking_filter(
|
|
126
|
+
filter_dict: dict,
|
|
127
|
+
fields: dict[str, AacQueryField | str],
|
|
128
|
+
sole_dim_uri: str | None = None,
|
|
129
|
+
) -> dict:
|
|
130
|
+
"""Canonicalize a ranking filter so equivalent filters compare equal.
|
|
131
|
+
|
|
132
|
+
`attribute` is optional in the AAC schema (gen-ai models it as `NotRequired[str]` /
|
|
133
|
+
`str | None`), and when it is omitted AFM ranks over every dimension of the result. For a
|
|
134
|
+
single-dimension visualization that is exactly "rank by that one dimension", so an omitted
|
|
135
|
+
attribute is filled in with `sole_dim_uri` instead of comparing as an empty string — the
|
|
136
|
+
agent and the dataset may legitimately express the same filter either way.
|
|
137
|
+
|
|
138
|
+
The substitution is deliberately gated on there being exactly ONE dimension: with two or
|
|
139
|
+
more, omitting `attribute` ranks over the dimension *tuple*, which is a different filter,
|
|
140
|
+
so those stay strict. Callers pass the sole dimension of the visualization the filter
|
|
141
|
+
belongs to, which makes the comparison symmetric — it does not matter which side omitted it.
|
|
142
|
+
|
|
143
|
+
Missing, None and "" are all treated as "not specified"; so is a non-string, which
|
|
144
|
+
`validate_cross_references` reports separately rather than crashing the comparison.
|
|
145
|
+
"""
|
|
146
|
+
attr_val = filter_dict.get("attribute")
|
|
147
|
+
if not isinstance(attr_val, str) or not attr_val:
|
|
148
|
+
dim_uri = sole_dim_uri or ""
|
|
149
|
+
else:
|
|
150
|
+
dim_uri = _resolve_alias_to_uri(attr_val, fields)
|
|
151
|
+
using_val = filter_dict.get("using")
|
|
103
152
|
entry: dict = {
|
|
104
153
|
"type": "ranking_filter",
|
|
105
|
-
"metric_uri": _resolve_alias_to_uri(
|
|
106
|
-
"dim_uri":
|
|
154
|
+
"metric_uri": _resolve_alias_to_uri(using_val, fields) if isinstance(using_val, str) else "",
|
|
155
|
+
"dim_uri": dim_uri,
|
|
107
156
|
}
|
|
108
157
|
if "top" in filter_dict:
|
|
109
158
|
entry["top"] = filter_dict["top"]
|
|
@@ -127,12 +176,13 @@ def _split_and_normalize_filters(viz: CreatedVisualization) -> tuple[set[str], s
|
|
|
127
176
|
ranking_set: set[str] = set()
|
|
128
177
|
attr_set: set[str] = set()
|
|
129
178
|
fields = viz.query.fields
|
|
179
|
+
sole_dim_uri = _sole_dimension_uri(viz)
|
|
130
180
|
for filter_dict in viz.query.filter_by.values():
|
|
131
181
|
ft = filter_dict.get("type")
|
|
132
182
|
if ft == "date_filter":
|
|
133
183
|
date_set.add(json.dumps(_normalize_date_filter(filter_dict, fields), sort_keys=True))
|
|
134
184
|
elif ft == "ranking_filter":
|
|
135
|
-
ranking_set.add(json.dumps(_normalize_ranking_filter(filter_dict, fields), sort_keys=True))
|
|
185
|
+
ranking_set.add(json.dumps(_normalize_ranking_filter(filter_dict, fields, sole_dim_uri), sort_keys=True))
|
|
136
186
|
elif ft == "attribute_filter":
|
|
137
187
|
attr_set.add(json.dumps(_normalize_attribute_filter(filter_dict, fields), sort_keys=True))
|
|
138
188
|
return date_set, ranking_set, attr_set
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gooddata-eval
|
|
3
|
-
Version: 1.71.1.
|
|
3
|
+
Version: 1.71.1.dev3
|
|
4
4
|
Summary: Evaluate the GoodData AI agent against your own questions and models.
|
|
5
5
|
Project-URL: Source, https://github.com/gooddata/gooddata-python-sdk
|
|
6
6
|
Author-email: GoodData <support@gooddata.com>
|
|
@@ -17,7 +17,7 @@ Classifier: Topic :: Scientific/Engineering
|
|
|
17
17
|
Classifier: Topic :: Software Development
|
|
18
18
|
Classifier: Typing :: Typed
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
|
-
Requires-Dist: gooddata-sdk~=1.71.1.
|
|
20
|
+
Requires-Dist: gooddata-sdk~=1.71.1.dev3
|
|
21
21
|
Requires-Dist: httpx<1.0,>=0.27
|
|
22
22
|
Requires-Dist: orjson<4.0.0,>=3.9.15
|
|
23
23
|
Requires-Dist: pydantic<3.0,>=2.6
|
|
@@ -8,7 +8,7 @@ gooddata_eval/core/config.py,sha256=mFtztP727uAhZ6b7twM5pFvzyazzMGVdgTYMvkqZT2Y,
|
|
|
8
8
|
gooddata_eval/core/connection.py,sha256=mh-Do5-Ti0-DXXikfCIH10eTTUbKuZUAS_sLoPNDJ5I,1229
|
|
9
9
|
gooddata_eval/core/models.py,sha256=FdGGZUSp1Qcs98Ib5U3OhGplBlUZMimlIdAqGLTjFDU,4473
|
|
10
10
|
gooddata_eval/core/runner.py,sha256=sxE7kWDG6ttkSf5kUkZEJJ-B3DqWHXQOFBMEjMr4-D4,8520
|
|
11
|
-
gooddata_eval/core/scoring.py,sha256=
|
|
11
|
+
gooddata_eval/core/scoring.py,sha256=NbZnQeDuH1DF0z7RwxoeuQaTN_b4-85uu6Vm1QTjekI,8544
|
|
12
12
|
gooddata_eval/core/workspace.py,sha256=S8exiy5gpDtcRtT8-i2fIRALF34Pbo0J46xm8X_ldus,11846
|
|
13
13
|
gooddata_eval/core/agentic/__init__.py,sha256=nPjJ9sbnJVvsAlo9QMQlnPFgLNTZRzy9-0IKlnRcU8Q,2671
|
|
14
14
|
gooddata_eval/core/agentic/_catalog.py,sha256=eCaeyvLF-FCN0J3UnXSHaKtufU6HvrM2mxnmgyXsY1U,2085
|
|
@@ -44,8 +44,8 @@ gooddata_eval/core/reporting/console.py,sha256=kCHt4sCSaSRh0ZWqaSEJOl6_Jgb6uiouS
|
|
|
44
44
|
gooddata_eval/core/reporting/json_report.py,sha256=sKPG9-uxzDsf6EJyBXoaHVIfN1g-E2rZ8fA0wtyBYb4,2943
|
|
45
45
|
gooddata_eval/core/summary/__init__.py,sha256=U54lANKm34yjqm7dZL9KkGouPbwAaQWC9wiAmb5B5g4,32
|
|
46
46
|
gooddata_eval/core/summary/http_client.py,sha256=dPArJ6zoCu1W9xmYXFA1WMDNsSOkhn3gaCpYgCUp3gk,2174
|
|
47
|
-
gooddata_eval-1.71.1.
|
|
48
|
-
gooddata_eval-1.71.1.
|
|
49
|
-
gooddata_eval-1.71.1.
|
|
50
|
-
gooddata_eval-1.71.1.
|
|
51
|
-
gooddata_eval-1.71.1.
|
|
47
|
+
gooddata_eval-1.71.1.dev3.dist-info/METADATA,sha256=ylGcgPJSeYQvXqCFnhfKf46z17jI_UZ3hKcoksHvjA0,9995
|
|
48
|
+
gooddata_eval-1.71.1.dev3.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
49
|
+
gooddata_eval-1.71.1.dev3.dist-info/entry_points.txt,sha256=28nFp5Viknx4haPYgzK9QHlwPFfC6rPF6RmjC2ht8EI,56
|
|
50
|
+
gooddata_eval-1.71.1.dev3.dist-info/licenses/LICENSE.txt,sha256=LVfVlC9maU3K9lgMwdZnClQQsOIbOekdcdVKr9qzJtk,256836
|
|
51
|
+
gooddata_eval-1.71.1.dev3.dist-info/RECORD,,
|
|
File without changes
|
{gooddata_eval-1.71.1.dev2.dist-info → gooddata_eval-1.71.1.dev3.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{gooddata_eval-1.71.1.dev2.dist-info → gooddata_eval-1.71.1.dev3.dist-info}/licenses/LICENSE.txt
RENAMED
|
File without changes
|