Skip to content

fix: describe the anomalous bucket in non-dimension anomaly alerts - #1043

Open
joostboon wants to merge 2 commits into
masterfrom
fix/anomaly-description-uses-anomalous-row
Open

fix: describe the anomalous bucket in non-dimension anomaly alerts#1043
joostboon wants to merge 2 commits into
masterfrom
fix/anomaly-description-uses-anomalous-row

Conversation

@joostboon

@joostboon joostboon commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Problem tl;dr

In our description, we pick the bucket that was last non anomalous. This is actually wrong behaviour. We should take the anomalous value (see screenshot). It says "The last row_count value is 0.000.", but it should say in this case "The last row_count value is 6.000."

Screenshot 2026-08-06 at 11 54 28

More info

The non-dimension branch of the anomaly test description picks rows_with_score[-1]:

https://github.com/elementary-data/dbt-data-reliability/blob/master/macros/edr/data_monitoring/anomaly_detection/store_anomaly_test_results.sql#L114

Two defects stack on each other:

  1. Wrong candidate set. rows_with_score is every bucket with a non-null anomaly_score, not the anomalous ones. The macro already builds anomalous_rows a few lines above and uses it in the dimension branch, but the {% else %} branch ignores it, so the description can describe a bucket that passed.
  2. [-1] on an unordered result. The anomaly scores query has no order by, so [-1] is whichever row the warehouse returned last, not the latest bucket. The comment on the dimension branch already notes this.

Observed impact

On a volume_anomalies test the execution returned 140 scored buckets with exactly one is_anomalous = true:

field value
bucket 2026-08-05 → 2026-08-06
metric_value 6
training_avg 1.095
anomaly_score 3.476 (threshold 3)
row's own anomaly_description "The last row_count value is 6.000. The average for this metric is 1.095."

The alert that was rendered and sent to Slack said instead:

The last row_count value is 0.000. The average for this metric is 1.238.

Those numbers belong to the 2026-08-03 → 2026-08-04 bucket, which had anomaly_score = -1.05 and is_anomalous = false. It was the only row in the set with training_avg = 1.238, so the source is unambiguous.

The practical consequence is that the alert inverted the story: a spike was reported as a drop to zero. Anyone triaging from Slack goes looking for a broken ingestion pipeline that isn't broken.

Fix

Prefer anomalous_rows when the test failed, fall back to rows_with_score when it passed, and sort by bucket_end in both cases so [-1] means "latest bucket". This mirrors what #1032 already did for the dimension branch, which fixed the same class of bug but only inside {% if dimension %}.

Testing

  • Added test_volume_anomalies_description_reports_anomalous_bucket covering the reported shape: single-row training buckets, detected bucket spikes to six, description must report the spike.
  • pre-commit run passes on both changed files (black, isort, flake8, prettier, typos, sqlfmt).
  • I could not run the integration suite locally, it needs a warehouse connection. Please let CI exercise it.

Existing description assertions live in test_column_anomalies.py and all target the dimension branch or the "not enough data" path, so nothing asserted the old unsorted behavior.

Summary by CodeRabbit

Summary by CodeRabbit

  • Bug Fixes

    • Improved anomaly failure descriptions to report the metric value from the anomalous bucket.
    • Added a fallback for cases without anomalous buckets, selecting the most recent scored bucket for clearer reporting.
    • Ensured volume anomaly messages no longer display unrelated anomaly-score values.
  • Tests

    • Added integration coverage to verify accurate volume anomaly descriptions and reported row counts.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

👋 @joostboon
Thank you for raising your pull request.
Please make sure to add tests and document all user-facing changes.
You can do this by editing the docs files in the elementary repository.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a302cf7c-aa56-4e37-9285-24f3687f3cbd

📥 Commits

Reviewing files that changed from the base of the PR and between 2fdfe35 and c551aff.

📒 Files selected for processing (1)
  • integration_tests/tests/test_volume_anomalies.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • integration_tests/tests/test_volume_anomalies.py

📝 Walkthrough

Walkthrough

The anomaly result description selects the latest relevant bucket. An integration test verifies that a volume anomaly reports the anomalous bucket’s row count.

Changes

Volume anomaly description

Layer / File(s) Summary
Select and validate the reported bucket
macros/edr/data_monitoring/anomaly_detection/store_anomaly_test_results.sql, integration_tests/tests/test_volume_anomalies.py
The macro prioritizes anomalous rows, falls back to scored rows, orders candidates by bucket_end, and reports the latest row. The integration test verifies the anomalous bucket row count with warehouse-specific numeric formatting.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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 fix: reporting the anomalous bucket in non-dimension anomaly alerts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/anomaly-description-uses-anomalous-row

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

The non-dimension branch of the anomaly test description picked
rows_with_score[-1]. That has two problems:

1. rows_with_score holds every bucket with a non-null anomaly score, not
   just the anomalous ones, so the description could describe a bucket
   that passed.
2. The anomaly scores query has no "order by", so [-1] is whichever row
   the warehouse happened to return last, not the latest bucket.

Observed on a volume_anomalies test where the only anomalous bucket was a
spike to 6 (training avg 1.095), but the alert read "The last row_count
value is 0.000. The average for this metric is 1.238." Those numbers
belonged to a bucket two days earlier with anomaly_score -1.05 and
is_anomalous false. The alert inverted a spike into a drop to zero.

Prefer anomalous_rows when the test failed, fall back to rows_with_score
when it passed, and sort by bucket_end in both cases. This mirrors what
#1032 already did for the dimension branch.
@joostboon
joostboon force-pushed the fix/anomaly-description-uses-anomalous-row branch from 32a2ebf to 2fdfe35 Compare August 6, 2026 09:51

@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.

🧹 Nitpick comments (1)
integration_tests/tests/test_volume_anomalies.py (1)

50-73: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover ordering between multiple anomalous buckets.

This fixture creates only one anomalous bucket, so it does not exercise sort(attribute='bucket_end') | last. Add a second anomalous bucket and assert that the description reports the later bucket. Otherwise, a regression that selects an arbitrary anomalous row can pass this test.

🤖 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 `@integration_tests/tests/test_volume_anomalies.py` around lines 50 - 73,
Update test_volume_anomalies_description_reports_anomalous_bucket to include a
second anomalous bucket with a later date and assert that
test_results_description reports the later bucket’s value. Keep the existing
training data and failure assertion, and ensure the assertion distinguishes the
later anomaly from the earlier one so bucket ordering and selection are covered.
🤖 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.

Nitpick comments:
In `@integration_tests/tests/test_volume_anomalies.py`:
- Around line 50-73: Update
test_volume_anomalies_description_reports_anomalous_bucket to include a second
anomalous bucket with a later date and assert that test_results_description
reports the later bucket’s value. Keep the existing training data and failure
assertion, and ensure the assertion distinguishes the later anomaly from the
earlier one so bucket ordering and selection are covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c64830da-dd9e-46d3-9a97-7701eeea441b

📥 Commits

Reviewing files that changed from the base of the PR and between b349c4a and 32a2ebf.

📒 Files selected for processing (2)
  • integration_tests/tests/test_volume_anomalies.py
  • macros/edr/data_monitoring/anomaly_detection/store_anomaly_test_results.sql

@coderabbitai

coderabbitai Bot commented Aug 6, 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.

ClickHouse and BigQuery render the rounded metric value as "6" where
Snowflake renders "6.000", so the literal substring assertion failed on
those adapters even though the description named the correct bucket.
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.

1 participant