Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
104ff35
Fix exclude_detection_period_from_training for large time buckets
devin-ai-integration[bot] Feb 10, 2026
0b7d60e
Add weekly bucket tests for exclude_detection_period_from_training
devin-ai-integration[bot] Feb 10, 2026
3deca9d
Skip weekly bucket exclusion tests on Dremio due to bucket boundary d…
devin-ai-integration[bot] Feb 10, 2026
788de34
Add comment explaining why Dremio is skipped in weekly bucket tests
devin-ai-integration[bot] Feb 10, 2026
f72d239
Remove 'The fix itself is not Dremio-specific' from skip comments
devin-ai-integration[bot] Feb 10, 2026
55961a0
Redesign monthly bucket tests: month-aligned data, no detection_perio…
devin-ai-integration[bot] Feb 10, 2026
7a8a3bc
Increase sensitivity to 5 for cross-database compatibility
devin-ai-integration[bot] Feb 10, 2026
5f645bb
Monthly bucket tests: reduce anomaly magnitude and raise sensitivity …
devin-ai-integration[bot] Feb 10, 2026
f6e304d
Skip monthly bucket exclusion tests on Redshift due to z-score variance
devin-ai-integration[bot] Feb 10, 2026
83b7d6a
Skip monthly bucket exclusion tests on Dremio due to z-score variance
devin-ai-integration[bot] Feb 10, 2026
c91199f
Move backfill_days fix to config load time: set backfill_days = max(b…
devin-ai-integration[bot] Feb 10, 2026
2056309
Revert get_anomaly_scores_query.sql to original: fix is now only in g…
devin-ai-integration[bot] Feb 10, 2026
3958f32
Add log when backfill_days is increased to match bucket size; revert …
devin-ai-integration[bot] Feb 10, 2026
948f2b6
Address PR review: fix month arithmetic, add detailed skip comments f…
devin-ai-integration[bot] Feb 10, 2026
44262e3
Fix normal_month_start: use .replace(year=year-1) directly instead of…
devin-ai-integration[bot] Feb 10, 2026
91cc841
Make anomaly 5x normal to widen z-score margin; remove Redshift/Dremi…
devin-ai-integration[bot] Feb 12, 2026
33cdfe0
Re-add Redshift/Dremio to skip_targets: 5x anomaly still fails on the…
devin-ai-integration[bot] Feb 12, 2026
94294de
Fix post-rebase lint issues
cursoragent May 18, 2026
fc0b36a
Run monthly anomaly tests on all targets
cursoragent May 18, 2026
372cae3
Use seven-day anomaly exclusion tests
cursoragent May 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions integration_tests/tests/test_column_anomalies.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,3 +605,109 @@ def test_col_anom_excl_detect_train(test_id: str, dbt_project: DbtProject):
"Expected FAIL when exclude_detection_period_from_training=True "
"(detection data excluded from training baseline, anomaly detected)"
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Devin - please check CodeRabbit comments

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed both CodeRabbit comments in commit 2cc884f:

  1. Fixed anomaly_month_start calculation: (current_month_1st - timedelta(days=31)).replace(day=1)(current_month_1st - timedelta(days=1)).replace(day=1) to avoid skipping months (e.g. March → January instead of February).
  2. Fixed normal_month_start calculation similarly, using .replace(year=anomaly_month_start.year - 1) to avoid leap year issues with timedelta(days=365).



def test_col_excl_detect_train_seven_day_bucket(test_id: str, dbt_project: DbtProject):
"""
Test exclude_detection_period_from_training with 7-day buckets for column anomalies.

This tests the fix where the detection period is set to the bucket size
when the bucket period exceeds backfill_days. With 7-day buckets
and default backfill_days (2), without the fix the 2-day exclusion window
cannot contain any 7-day bucket_end, making exclusion ineffective.

detection_period is intentionally NOT set so that backfill_days stays at
its default (2), which is smaller than the 7-day bucket.
Setting detection_period would override backfill_days and mask the bug.

Scenario:
- 12 normal 7-day buckets with low null count
- 1 anomalous 7-day bucket with high null count
- time_bucket: 7 days (7 days >> default backfill_days of 2)
- Without exclusion: anomaly absorbed into training → test passes
- With exclusion + fix: anomaly excluded from training → test fails
"""
utc_now = datetime.utcnow().date()
anomaly_bucket_start = utc_now - timedelta(days=7)
normal_bucket_start = anomaly_bucket_start - timedelta(days=12 * 7)

normal_data: List[Dict[str, Any]] = []
day = normal_bucket_start
day_idx = 0
while day < anomaly_bucket_start:
null_count = 1 + (day_idx % 3)
normal_data.extend(
[
{TIMESTAMP_COLUMN: day.strftime(DATE_FORMAT), "superhero": superhero}
for superhero in ["Superman", "Batman", "Wonder Woman", "Flash"]
]
)
normal_data.extend(
[
{TIMESTAMP_COLUMN: day.strftime(DATE_FORMAT), "superhero": None}
for _ in range(null_count)
]
)
day += timedelta(days=1)
day_idx += 1

anomalous_data: List[Dict[str, Any]] = []
day = anomaly_bucket_start
while day < utc_now:
anomalous_data.extend(
[
{TIMESTAMP_COLUMN: day.strftime(DATE_FORMAT), "superhero": superhero}
for superhero in ["Superman", "Batman", "Wonder Woman", "Flash"]
]
)
anomalous_data.extend(
[
{TIMESTAMP_COLUMN: day.strftime(DATE_FORMAT), "superhero": None}
for _ in range(10)
]
)
day += timedelta(days=1)

all_data = normal_data + anomalous_data

test_args_without_exclusion = {
"timestamp_column": TIMESTAMP_COLUMN,
"column_anomalies": ["null_count"],
"time_bucket": {"period": "day", "count": 7},
"training_period": {"period": "day", "count": 91},
"min_training_set_size": 5,
"anomaly_sensitivity": 10,
"anomaly_direction": "spike",
"exclude_detection_period_from_training": False,
}

test_result_without = dbt_project.test(
test_id + "_f",
DBT_TEST_NAME,
test_args_without_exclusion,
data=all_data,
test_column="superhero",
test_vars={"force_metrics_backfill": True},
)
assert test_result_without["status"] == "pass", (
"Expected PASS when exclude_detection_period_from_training=False "
"(detection data included in training baseline)"
)

test_args_with_exclusion = {
**test_args_without_exclusion,
"exclude_detection_period_from_training": True,
}

test_result_with = dbt_project.test(
test_id + "_t",
DBT_TEST_NAME,
test_args_with_exclusion,
data=all_data,
test_column="superhero",
test_vars={"force_metrics_backfill": True},
)
assert test_result_with["status"] == "fail", (
"Expected FAIL when exclude_detection_period_from_training=True "
"(large bucket fix: detection period set to bucket size)"
)
81 changes: 81 additions & 0 deletions integration_tests/tests/test_volume_anomalies.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,3 +583,84 @@ def test_exclude_detection_from_training(test_id: str, dbt_project: DbtProject):
assert (
test_result_with_exclusion["status"] == "fail"
), "Test should fail when anomaly is excluded from training"


def test_excl_detect_train_seven_day_bucket(test_id: str, dbt_project: DbtProject):
"""
Test exclude_detection_period_from_training with 7-day buckets.

This tests the fix where the detection period is set to the bucket size
when the bucket period exceeds backfill_days. With 7-day buckets
and default backfill_days (2), without the fix the 2-day exclusion window
cannot contain any 7-day bucket_end, making exclusion ineffective.

detection_period is intentionally NOT set so that backfill_days stays at
its default (2), which is smaller than the 7-day bucket.
Setting detection_period would override backfill_days and mask the bug.

Scenario:
- 12 normal 7-day buckets
- 1 anomalous 7-day bucket
- time_bucket: 7 days (7 days >> default backfill_days of 2)
- Without exclusion: anomaly absorbed into training → test passes
- With exclusion + fix: anomaly excluded from training → test fails
"""
utc_now = datetime.utcnow()
utc_today = utc_now.date()
anomaly_bucket_start = utc_today - timedelta(days=7)
normal_bucket_start = anomaly_bucket_start - timedelta(days=12 * 7)

normal_data = []
day = normal_bucket_start
day_idx = 0
while day < anomaly_bucket_start:
rows_per_day = 8 + (day_idx % 3)
normal_data.extend(
[{TIMESTAMP_COLUMN: day.strftime(DATE_FORMAT)} for _ in range(rows_per_day)]
)
day += timedelta(days=1)
day_idx += 1

anomalous_data = []
day = anomaly_bucket_start
while day < utc_today:
anomalous_data.extend(
[{TIMESTAMP_COLUMN: day.strftime(DATE_FORMAT)} for _ in range(50)]
)
day += timedelta(days=1)

all_data = normal_data + anomalous_data

test_args_without_exclusion = {
**DBT_TEST_ARGS,
"training_period": {"period": "day", "count": 91},
"time_bucket": {"period": "day", "count": 7},
"sensitivity": 10,
}

test_result_without = dbt_project.test(
test_id + "_without",
DBT_TEST_NAME,
test_args_without_exclusion,
data=all_data,
test_vars={"force_metrics_backfill": True},
)
assert (
test_result_without["status"] == "pass"
), "Test should pass when anomaly is included in training"

test_args_with_exclusion = {
**test_args_without_exclusion,
"exclude_detection_period_from_training": True,
}

test_result_with = dbt_project.test(
test_id + "_with",
DBT_TEST_NAME,
test_args_with_exclusion,
data=all_data,
test_vars={"force_metrics_backfill": True},
)
assert (
test_result_with["status"] == "fail"
), "Test should fail when anomaly is excluded from training (large bucket fix)"
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@
{%- set backfill_days = elementary.detection_period_to_backfill_days(
detection_period, backfill_days, model_graph_node
) -%}
{%- if metric_props.time_bucket %}
{%- set bucket_in_days = elementary.convert_period(
metric_props.time_bucket, "day"
).count %}
{%- if bucket_in_days > backfill_days %}
{%- do elementary.edr_log(
"backfill_days increased from "
~ backfill_days
~ " to "
~ bucket_in_days
~ " to match time bucket size."
) %}
{%- set backfill_days = bucket_in_days %}
{%- endif %}
{%- endif %}
{%- set fail_on_zero = elementary.get_test_argument(
"fail_on_zero", fail_on_zero, model_graph_node
) %}
Expand Down
Loading