Skip to content

Commit be219d5

Browse files
committed
test: correct agentic alert parsing and ranking-filter cross-ref
- alert-skill: read expected fields case-insensitively, drop the prose "Time window/Filters" fallback - scoring: accept a ranking-filter `using` that resolves to an ad-hoc aggregation field (COUNT(attribute)), not only metric/ or fact/ URIs. risk: nonprod
1 parent 3d2a457 commit be219d5

2 files changed

Lines changed: 34 additions & 15 deletions

File tree

packages/gooddata-eval/src/gooddata_eval/core/agentic/alert_skill.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ def _check_threshold(expected: CatalogMetricAlert, actual_args: dict) -> bool:
7777

7878

7979
def _check_trigger(expected: CatalogMetricAlert, actual_args: dict) -> bool:
80+
if expected.operator == "ANOMALY":
81+
return True
8082
exp_trigger = expected.trigger
8183
act_trigger = actual_args.get("trigger", actual_args.get("triggerMode", "ALWAYS"))
8284
if exp_trigger in _ALWAYS_TRIGGER_VALUES:
@@ -235,27 +237,42 @@ class AgenticAlertSummary:
235237
best: AlertRunResult
236238

237239

240+
def _case_insensitive_get(d: dict, *keys: str) -> Any:
241+
"""Look up a value by key, preferring an exact match then a case-insensitive one."""
242+
for k in keys:
243+
if k in d:
244+
return d[k]
245+
lowered = {str(k).lower(): v for k, v in d.items()}
246+
for k in keys:
247+
if k.lower() in lowered:
248+
return lowered[k.lower()]
249+
return None
250+
251+
238252
def _normalize_expected_output(expected: dict) -> CatalogMetricAlert:
239253
"""Parse expected_output dict into CatalogMetricAlert, accepting display-format or internal-format keys."""
240-
operator = expected.get("operator") or expected.get("Operator") or "GREATER_THAN"
241-
threshold = expected.get("threshold") or expected.get("Threshold")
242-
threshold_from = expected.get("threshold_from")
243-
threshold_to = expected.get("threshold_to")
244-
trigger = expected.get("trigger") or expected.get("Trigger") or "not specified"
245-
246-
metric_id = expected.get("metric_id")
247-
if not metric_id and "Metric" in expected:
248-
m = re.search(r"\(([^)]+)\)\s*$", str(expected["Metric"]))
249-
if m:
250-
metric_id = m.group(1).strip()
251-
252-
raw_recip = expected.get("recipients") or expected.get("Recipient(s)") or []
254+
operator = _case_insensitive_get(expected, "operator") or "GREATER_THAN"
255+
threshold = _case_insensitive_get(expected, "threshold")
256+
threshold_from = _case_insensitive_get(expected, "threshold_from")
257+
threshold_to = _case_insensitive_get(expected, "threshold_to")
258+
259+
trigger = _case_insensitive_get(expected, "trigger") or "not specified"
260+
trigger = _TRIGGER_DISPLAY_TO_API.get(trigger, trigger)
261+
262+
metric_id = _case_insensitive_get(expected, "metric_id")
263+
if not metric_id:
264+
metric_disp = _case_insensitive_get(expected, "metric")
265+
if metric_disp:
266+
m = re.search(r"\(([^)]+)\)\s*$", str(metric_disp))
267+
metric_id = m.group(1).strip() if m else None
268+
269+
raw_recip = _case_insensitive_get(expected, "recipients", "recipient(s)") or []
253270
if isinstance(raw_recip, str):
254271
recipients = [r.strip() for r in raw_recip.replace(";", ",").split(",") if r.strip()]
255272
else:
256273
recipients = list(raw_recip)
257274

258-
filters = expected.get("filters") or expected.get("Time window/Filters")
275+
filters = _case_insensitive_get(expected, "filters")
259276
if isinstance(filters, str) and any(kw in filters for kw in ("None", "All time")):
260277
filters = None
261278

packages/gooddata-eval/src/gooddata_eval/core/scoring.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ def validate_cross_references(viz: CreatedVisualization) -> tuple[bool, list[str
7171
continue
7272
using_val = filter_dict.get("using", "")
7373
using_uri = _resolve_alias_to_uri(using_val, fields)
74-
if not using_uri.startswith(("metric/", "fact/")):
74+
field_def = fields.get(using_val)
75+
is_adhoc_agg = isinstance(field_def, AacQueryField) and bool(field_def.aggregation)
76+
if not using_uri.startswith(("metric/", "fact/")) and not is_adhoc_agg:
7577
errors.append(
7678
f"ranking filter '{filter_key}': using='{using_val}' "
7779
f"resolves to '{using_uri}' — expected a metric/ or fact/ URI"

0 commit comments

Comments
 (0)