Skip to content

fix(gooddata-eval): make ranking attribute optional on 1-dim viz - #1707

Merged
henrynguyengooddata merged 1 commit into
masterfrom
fix-test
Aug 5, 2026
Merged

fix(gooddata-eval): make ranking attribute optional on 1-dim viz#1707
henrynguyengooddata merged 1 commit into
masterfrom
fix-test

Conversation

@myhoai

@myhoai myhoai commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

JIRA: QA-28615
risk: nonprod

Summary by CodeRabbit

  • Bug Fixes

    • Ranking filters now report invalid values as validation errors instead of causing exceptions.
    • Omitted, empty, or None ranking attributes are accepted for single-dimension visualizations.
    • Ranking filters correctly infer the sole visualization dimension, including when aliases differ.
    • Validation continues to detect incorrect ranking limits and invalid configurations, including multi-dimension cases.
  • Tests

    • Added coverage for ranking-filter validation, aliases, missing fields, malformed inputs, and cross-reference checks.

@myhoai
myhoai requested review from hkad98, lupko and pcerny as code owners August 4, 2026 11:36
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Ranking-filter validation now reports malformed values as errors. Normalization accepts unspecified attributes for single-dimension visualizations and uses the sole dimension. Tests cover validation, comparison, cross-reference handling, and visualization evaluation.

Changes

Ranking Filter Validation

Layer / File(s) Summary
Scoring validation and normalization
packages/gooddata-eval/src/gooddata_eval/core/scoring.py
Ranking-filter validation handles malformed using and attribute values without exceptions. Normalization uses the visualization’s sole dimension when the attribute is unspecified.
Ranking filter regression coverage
packages/gooddata-eval/tests/test_scoring.py, packages/gooddata-eval/tests/test_visualization_evaluator.py
Tests cover omitted, empty, None, malformed, mismatched, and single-dimension ranking-filter attributes. Visualization evaluation tests cover alias-equivalent dimensions.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: hkad98, lupko, pcerny

Poem

A rabbit checks each ranked score,
No malformed value hops out the door.
One lone dimension fills the chart,
Tests guard every tricky part.
“Hop approved!” says Bunny with delight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: making the ranking attribute optional for one-dimensional visualizations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.40%. Comparing base (acfcc1a) to head (c265cc7).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
...es/gooddata-eval/src/gooddata_eval/core/scoring.py 88.88% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1707      +/-   ##
==========================================
+ Coverage   78.30%   78.40%   +0.09%     
==========================================
  Files         271      271              
  Lines       18689    18741      +52     
==========================================
+ Hits        14634    14693      +59     
+ Misses       4055     4048       -7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/gooddata-eval/tests/test_scoring.py`:
- Around line 134-147: Update
test_validate_cross_references_never_raises_on_empty_or_none_uris so each
malformed case includes its expected validation result, marking invalid
attribute and using values as errors. Assert ok matches that expected result and
retain the existing errors-list consistency check, ensuring cases such as
attribute=[] and using=None cannot pass as valid.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4d7617e8-da56-4ff1-9bc2-f51be8b2e785

📥 Commits

Reviewing files that changed from the base of the PR and between acfcc1a and 483f4b4.

📒 Files selected for processing (3)
  • packages/gooddata-eval/src/gooddata_eval/core/scoring.py
  • packages/gooddata-eval/tests/test_scoring.py
  • packages/gooddata-eval/tests/test_visualization_evaluator.py

Comment thread packages/gooddata-eval/tests/test_scoring.py Outdated
The visualization comparator required a ranking filter's `attribute` to match
exactly, but `attribute` is optional in the AAC schema -- gen-ai models it as
`NotRequired[str]` / `str | None` in all three of its ranking-filter types, and
when it is absent AFM ranks over every dimension of the result. On a chart with
exactly one dimension that is the same filter, so the comparator was stricter
than the product contract and failed correct answers.

Both Anthropic models consistently omit `attribute` while getting the metric and
top/bottom-N right, which made this the largest visualization failure cluster:
14 of 49 viz failures in run 30850362312 (opus48 9, sonnet46 3, bedrock 2). No
GPT combo is affected.

`_normalize_ranking_filter` now fills an omitted attribute in with the
visualization's sole dimension URI instead of comparing it as an empty string.
The substitution is gated on there being exactly ONE distinct dimension: with
two or more, omitting `attribute` ranks over the dimension tuple, which is a
genuinely different filter, so those stay strict.

It is applied to expected and actual alike, because datasets omit `attribute`
too -- without symmetry an agent that supplies the more precise filter would
fail against a fixture that omits it. Missing, None and "" now normalize
identically, so `attribute: null` no longer differs from an absent key.

Also make `validate_cross_references` return a score instead of raising. None,
"" and non-string values reached `.startswith()` / `dict.get()` and blew up with
AttributeError / TypeError mid-evaluation. This affected the `using` branch as
well as `attribute`. Its test asserts the expected verdict per malformed case
rather than comparing `ok` against the returned error-list length, which was a
tautology against an implementation that returns exactly `len(errors) == 0`;
confirmed non-vacuous by mutation.

Verified by re-scoring all 48 expected/actual pairs lifted from run 30850362312
with the patched module: 14 flip FAIL -> PASS, 0 checks that CI reported as True
became False. Note this raises opus48's pass rate ~4.5pp for comparator reasons,
not model ones.

JIRA: QA-28615
risk: nonprod

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/gooddata-eval/src/gooddata_eval/core/scoring.py`:
- Around line 143-155: Reject malformed ranking filters, including non-string
values such as attribute=[], before normalization in both the visualization
evaluator and conversation.py paths. Add the validation gate before
check_filters so invalid filters cannot become equivalent to omitted attributes,
and ensure conversation.py cannot set output_correct=True without
cross-reference validation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 830a4168-f2a0-4c49-8c77-4b4ae0758662

📥 Commits

Reviewing files that changed from the base of the PR and between 78904a0 and c265cc7.

📒 Files selected for processing (3)
  • packages/gooddata-eval/src/gooddata_eval/core/scoring.py
  • packages/gooddata-eval/tests/test_scoring.py
  • packages/gooddata-eval/tests/test_visualization_evaluator.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/gooddata-eval/tests/test_visualization_evaluator.py
  • packages/gooddata-eval/tests/test_scoring.py

Comment on lines +143 to +155
Missing, None and "" are all treated as "not specified"; so is a non-string, which
`validate_cross_references` reports separately rather than crashing the comparison.
"""
attr_val = filter_dict.get("attribute")
if not isinstance(attr_val, str) or not attr_val:
dim_uri = sole_dim_uri or ""
else:
dim_uri = _resolve_alias_to_uri(attr_val, fields)
using_val = filter_dict.get("using")
entry: dict = {
"type": "ranking_filter",
"metric_uri": _resolve_alias_to_uri(filter_dict.get("using", ""), fields),
"dim_uri": _resolve_alias_to_uri(filter_dict.get("attribute", ""), fields),
"metric_uri": _resolve_alias_to_uri(using_val, fields) if isinstance(using_val, str) else "",
"dim_uri": dim_uri,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map scoring entry points before inspecting callers.
ast-grep outline packages/gooddata-eval/src/gooddata_eval/core/scoring.py --items all --view expanded

# Inspect validation and normalization call sites.
rg -n -C 12 --glob '*.py' \
  '\bvalidate_cross_references\s*\(|\b_split_and_normalize_filters\s*\(' \
  packages/gooddata-eval/src packages/gooddata-eval/tests

Repository: gooddata/gooddata-python-sdk

Length of output: 16517


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- scoring.py ---'
sed -n '1,230p' packages/gooddata-eval/src/gooddata_eval/core/scoring.py

printf '%s\n' '--- visualization evaluator ---'
sed -n '1,180p' packages/gooddata-eval/src/gooddata_eval/core/evaluators/visualization.py

printf '%s\n' '--- evaluator and scoring usages ---'
rg -n -C 8 --glob '*.py' \
  'evaluate_visualization|check_filters|cross_ref_valid|filters_correct|filter_ranking_score|EvaluationResult' \
  packages/gooddata-eval/src packages/gooddata-eval/tests

Repository: gooddata/gooddata-python-sdk

Length of output: 50384


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import ast

path = Path("packages/gooddata-eval/src/gooddata_eval/core/scoring.py")
tree = ast.parse(path.read_text())
wanted = {
    "validate_cross_references",
    "_sole_dimension_uri",
    "_normalize_ranking_filter",
    "_split_and_normalize_filters",
    "check_filters",
}
for node in tree.body:
    if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name in wanted:
        print(f"--- {node.name} lines {node.lineno}-{node.end_lineno} ---")
        print("\n".join(path.read_text().splitlines()[node.lineno-1:node.end_lineno]))
PY

printf '%s\n' '--- direct evaluator control flow ---'
python3 - <<'PY'
from pathlib import Path
p = Path("packages/gooddata-eval/src/gooddata_eval/core/evaluators/visualization.py")
lines = p.read_text().splitlines()
for i, line in enumerate(lines, 1):
    if any(x in line for x in ("validate_cross_references", "check_filters", "return EvaluationResult")):
        lo, hi = max(1, i-8), min(len(lines), i+18)
        print(f"--- lines {lo}-{hi} ---")
        print("\n".join(f"{n}: {lines[n-1]}" for n in range(lo, hi+1)))
PY

Repository: gooddata/gooddata-python-sdk

Length of output: 12853


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- conversation scoring path ---'
sed -n '130,190p' packages/gooddata-eval/src/gooddata_eval/core/agentic/conversation.py
rg -n -C 10 --glob '*.py' \
  'validate_cross_references|_evaluate_visualization|_evaluate_against_candidates|_evaluate_expected' \
  packages/gooddata-eval/src packages/gooddata-eval/tests

printf '%s\n' '--- validation/scoring tests ---'
sed -n '120,175p' packages/gooddata-eval/tests/test_scoring.py
sed -n '90,145p' packages/gooddata-eval/tests/test_visualization_evaluator.py

Repository: gooddata/gooddata-python-sdk

Length of output: 36482


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json

def normalize_ranking_filter(filter_dict, fields, sole_dim_uri):
    attr_val = filter_dict.get("attribute")
    if not isinstance(attr_val, str) or not attr_val:
        dim_uri = sole_dim_uri or ""
    else:
        dim_uri = fields.get(attr_val, attr_val)
    using_val = filter_dict.get("using")
    entry = {
        "type": "ranking_filter",
        "metric_uri": fields.get(using_val, using_val) if isinstance(using_val, str) else "",
        "dim_uri": dim_uri,
    }
    if "top" in filter_dict:
        entry["top"] = filter_dict["top"]
    if "bottom" in filter_dict:
        entry["bottom"] = filter_dict["bottom"]
    return json.dumps(entry, sort_keys=True)

fields = {
    "m_sales": "metric/net_sales",
    "d_product": "label/product_id",
}
expected = {"type": "ranking_filter", "using": "m_sales", "attribute": "d_product", "top": 5}
malformed = {"type": "ranking_filter", "using": "m_sales", "attribute": [], "top": 5}
omitted = {"type": "ranking_filter", "using": "m_sales", "top": 5}

for name, value in (("expected", expected), ("malformed", malformed), ("omitted", omitted)):
    print(name, normalize_ranking_filter(value, fields, "label/product_id"))

print("malformed_matches_expected:",
      normalize_ranking_filter(malformed, fields, "label/product_id")
      == normalize_ranking_filter(expected, fields, "label/product_id"))
print("malformed_matches_omitted:",
      normalize_ranking_filter(malformed, fields, "label/product_id")
      == normalize_ranking_filter(omitted, fields, "label/product_id"))
PY

Repository: gooddata/gooddata-python-sdk

Length of output: 564


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 12 --glob '*.py' \
  '_check_output_correct\s*\(' \
  packages/gooddata-eval/src packages/gooddata-eval/tests

sed -n '1,135p' packages/gooddata-eval/src/gooddata_eval/core/agentic/conversation.py

Repository: gooddata/gooddata-python-sdk

Length of output: 10780


Reject malformed filters before normalization.

attribute=[] normalizes to the same ranking filter as a valid or omitted attribute. The visualization evaluator reports a false-positive ranking score, and conversation.py can set output_correct=True without cross-reference validation. Gate both paths before check_filters.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/gooddata-eval/src/gooddata_eval/core/scoring.py` around lines 143 -
155, Reject malformed ranking filters, including non-string values such as
attribute=[], before normalization in both the visualization evaluator and
conversation.py paths. Add the validation gate before check_filters so invalid
filters cannot become equivalent to omitted attributes, and ensure
conversation.py cannot set output_correct=True without cross-reference
validation.

@henrynguyengooddata
henrynguyengooddata merged commit 17bcb5c into master Aug 5, 2026
15 checks passed
@henrynguyengooddata
henrynguyengooddata deleted the fix-test branch August 5, 2026 02:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants