Skip to content

Fixes #33230: [DQ Threshold W1.2] Deviation-from-statistic evaluation + migrate 12 statistical tests - #33444

Open
TeddyCr wants to merge 2 commits into
mainfrom
agent/issue-33230
Open

TeddyCr wants to merge 2 commits into
mainfrom
agent/issue-33230

Conversation

@TeddyCr

@TeddyCr TeddyCr commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

This pull request was opened by an olivaw agent. It is an input to review, not an approved change — read the diff, not this description.

Fixes #33230

What olivaw checked (machine-observed, §8.2/A.1)

  • diff: non-empty
  • build: not_run
  • tests: not_run
  • base: 42d153541b0679aa2e8ba4ab206ceb88b2a2873d
  • head: 8cff28cb3cb530eb9613d560f4b5f9979674280f

No build or test command is configured for this workspace, so only the diff's existence was checked. Set run_config.build_cmd/test_cmd to have olivaw verify its own work.

What the agent says it did (unverified)

Committed on agent/issue-33230.

What I did

Core evaluation (ingestion/src/metadata/data_quality/validations/thresholds.py, new)

  • apply_bound_tolerance(min, max, threshold, unit) — widens a resolved range. ABSOLUTE subtracts/adds the raw threshold; PERCENTAGE uses |bound| * threshold / 100 so a negative bound widens outward (-100 at 5% → -105).
  • within_deviation(observed, expected, threshold, unit)|observed - expected| <= threshold (ABSOLUTE) or <= |expected| * threshold / 100 (PERCENTAGE).

D6 — applied downstream of bound resolution. Three helpers on BaseTestValidator (base_test_handler.py:709+): get_failure_threshold(), get_bounds(min_param, max_param) (calls get_min_bound/get_max_bound, then widens), apply_bound_tolerance(min, max) for validators that resolve bounds themselves, and matches_expected(observed, expected). Nothing was changed inside the getters, so a Collate AnomalyDetectionMixin override still feeds its predicted bounds through the widening. There's a test pinning exactly that (test_get_bounds_widens_dynamically_resolved_bounds).

12 tests migrated — the 6 columnValue{Min,Max,Mean,Median,StdDev}/SumToBeBetween via _get_test_parameters, tableRowCountToBeBetween, tableColumnCountToBeBetween, tableRowInsertedCountToBeBetween, plus tableRowCountToEqual, tableColumnCountToEqual, columnValuesMissingCountToBeEqual.

Degenerate cases: a 0 bound/expected under PERCENTAGE stays strict and logs a warning naming the side — no floor substituted. An unset bound stays ∓inf (guarded, since inf * 0 is nan). threshold = 0 short-circuits to the original bounds / strict equality.

Tests: ingestion/tests/unit/observability/data_quality/validations/test_failure_thresholds.py, covering positive/negative/zero/infinite bounds, zero expected value, and PERCENTAGE > 100.

Assumptions worth flagging

  1. [DQ Threshold W0.1] Seed definitions + data migration for threshold / thresholdUnit #33227 has not landed — no failureThreshold/thresholdUnit exists anywhere in the repo. Rather than build an inert feature, I added the field: thresholdUnit enum (ABSOLUTE/PERCENTAGE) in tests/basic.json, failureThreshold (number, minimum: 0, default 0) + thresholdUnit on testCase.json and createTestCase.json, wired through TestCaseMapper, PATCH_FIELDS and the updater in TestCaseRepository, and mirrored into the committed generated TS. This will conflict with [DQ Threshold W0.1] Seed definitions + data migration for threshold / thresholdUnit #33227 if that PR lands with different names.
  2. Widened bounds are what gets reported, not the configured ones — test_params carries the effective bounds, so the verdict, the result message, minBound/maxBound on the result and the BetweenBoundsChecker used for dimensional/SQL evaluation all agree.
  3. No maximum on failureThreshold, so PERCENTAGE > 100 is accepted here, per the acceptance criteria.

Not done

  • The UI form for configuring the threshold (separate sub-task) — the generated types carry the field but no form input uses it.
  • CSV import/export of the new fields in TestCaseRepository, which would change the documented CSV header.
  • openmetadata-collate#6635 (W2.1) lives in another repo.

Verification limits

No Python environment in this clone (no pydantic), so I could not execute the pytest suite. I verified all of thresholds.py by executing it against stubbed generated-schema imports — all 39 bound-widening and deviation cases pass, including the four zero-reference warning paths. The validator-level tests, the Java changes, and the schema codegen are unrun here; they compile-check clean (py_compile, JSON parse) but need CI.

Greptile Summary

This PR adds configurable absolute or percentage failure tolerances to data-quality test cases and migrates twelve statistical validators to apply them.

  • Adds shared Python helpers for widening bounds and comparing observed values against expected values.
  • Extends the test-case schemas, generated TypeScript models, Java mapper, repository patch handling, and change tracking with threshold fields.
  • Adds unit coverage for absolute, percentage, zero, negative, infinite, and dynamically resolved bounds.
  • The changes since the previous review only relocate generated TypeScript enum declarations and do not address the three existing review findings.
Confidence Score: 2/5

The PR does not appear safe to merge because three previously reported threshold contract and persistence defects remain unresolved.

The current code still does not preserve threshold fields through partial PUT updates, does not include them in test-case CSV export/import, and accepts thresholds for validators that do not apply them. The changes since the previous review only relocate generated TypeScript enum declarations, leaving all three existing findings outstanding.

Important Files Changed
Filename Overview
ingestion/src/metadata/data_quality/validations/thresholds.py Implements the shared absolute and percentage deviation calculations, including zero and unbounded cases.
ingestion/src/metadata/data_quality/validations/base_test_handler.py Exposes threshold-aware bound resolution and expected-value comparison to validators.
ingestion/tests/unit/observability/data_quality/validations/test_failure_thresholds.py Covers the core tolerance calculations and dynamically resolved bound behavior.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java Tracks threshold patch changes, but the previous PUT-preservation and CSV-round-trip concerns remain outstanding.
openmetadata-spec/src/main/resources/json/schema/api/tests/createTestCase.json Adds threshold configuration to every test case without restricting it to validators that implement threshold evaluation.
openmetadata-spec/src/main/resources/json/schema/tests/testCase.json Adds persisted threshold fields, while the previously identified CSV serialization gap remains outstanding.
openmetadata-ui/src/main/resources/ui/src/generated/api/tests/createTestCase.ts Relocates the generated threshold enum declaration without changing the exported API.
openmetadata-ui/src/main/resources/ui/src/generated/tests/testCase.ts Relocates the generated threshold enum declaration without changing the exported entity type.
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Test-case schema] --> B[Generated Java and Python models]
  A --> C[Generated TypeScript models]
  B --> D[TestCase repository]
  D --> E[Persisted threshold configuration]
  E --> F[BaseTestValidator]
  F --> G[Resolve expected value or bounds]
  G --> H[Apply absolute or percentage tolerance]
  H --> I[Data-quality verdict and result]
Loading

Reviews (2): Last reviewed commit: "Update generated TypeScript types and de..." | Re-trigger Greptile

Context used (3)

…stical tests

Adds the second threshold semantic: a tolerance on an aggregate statistic
rather than on rows.

Range tests widen their bounds by the threshold — ABSOLUTE subtracts/adds the
raw value, PERCENTAGE a share of |bound| so a negative bound widens outward
(-100 at 5% gives -105, not -95). Exact-value tests tolerate a delta around the
expected value on the same two units.

The widening happens in `BaseTestValidator.get_bounds()`/`apply_bound_tolerance()`,
downstream of `get_min_bound`/`get_max_bound`, so it also holds for validators
that resolve their bounds dynamically by overriding those getters — widening
inside the getters would silently no-op for every dynamic-assertion test case.

Degenerate cases are handled explicitly rather than papered over: a bound or
expected value of 0 has no percentage, so the test case stays strict on that
side and a warning says why; an unset bound stays ∓inf and the tolerance is a
no-op there. A threshold of 0 reproduces today's verdict exactly.

The `failureThreshold`/`thresholdUnit` fields this builds on are added here too,
since #33227 has not landed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@TeddyCr
TeddyCr requested review from a team as code owners September 16, 2026 22:58
@TeddyCr
TeddyCr requested review from ulixius9 and a lite review from Copilot and removed request for Copilot September 16, 2026 22:58
@github-actions github-actions Bot added Ingestion safe to test Add this label to run secure Github workflows on PRs labels Sep 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

The Python checkstyle failed.

Please run make py_format and py_format_check in the root of your repository and commit the changes to this PR.
You can also use pre-commit to automate the Python code formatting.

You can install the pre-commit hooks with make install_test precommit_install.

Comment on lines 124 to +127
private static final String UPDATE_FIELDS =
"owners,entityLink,testSuite,testSuites,testDefinition,dimensionColumns,topDimensions";
private static final String PATCH_FIELDS =
"owners,entityLink,testSuite,testSuites,testDefinition,computePassedFailedRowCount,useDynamicAssertion,dimensionColumns,topDimensions";
"owners,entityLink,testSuite,testSuites,testDefinition,computePassedFailedRowCount,useDynamicAssertion,failureThreshold,thresholdUnit,dimensionColumns,topDimensions";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 PUT Clears Thresholds

These fields are included in PATCH_FIELDS but not UPDATE_FIELDS. When an older or partial PUT client omits failureThreshold or thresholdUnit while changing another field, the repository does not hydrate their stored values and then persists the request object with them set to null. This silently removes the configured tolerance, so later data-quality runs revert to strict evaluation.

Suggested change
private static final String UPDATE_FIELDS =
"owners,entityLink,testSuite,testSuites,testDefinition,dimensionColumns,topDimensions";
private static final String PATCH_FIELDS =
"owners,entityLink,testSuite,testSuites,testDefinition,computePassedFailedRowCount,useDynamicAssertion,dimensionColumns,topDimensions";
"owners,entityLink,testSuite,testSuites,testDefinition,computePassedFailedRowCount,useDynamicAssertion,failureThreshold,thresholdUnit,dimensionColumns,topDimensions";
private static final String UPDATE_FIELDS =
"owners,entityLink,testSuite,testSuites,testDefinition,failureThreshold,thresholdUnit,dimensionColumns,topDimensions";
private static final String PATCH_FIELDS =
"owners,entityLink,testSuite,testSuites,testDefinition,computePassedFailedRowCount,useDynamicAssertion,failureThreshold,thresholdUnit,dimensionColumns,topDimensions";

Knowledge Base Used: Metadata entity lifecycle

Comment on lines 165 to +175
"type": "boolean",
"default": false
},
"failureThreshold": {
"description": "Tolerance applied when evaluating this test case. A deviation within the threshold is still reported as a success. Defaults to 0, which evaluates the test case strictly.",
"type": "number",
"minimum": 0,
"default": 0
},
"thresholdUnit": {
"description": "Unit `failureThreshold` is expressed in.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 CSV Drops Thresholds

The new threshold settings are persisted on a test case, but the TestCase CSV exporter and importer do not write or read either field. Exporting and re-importing a test with a nonzero tolerance therefore omits its failureThreshold and thresholdUnit; the threshold defaults to zero, making the restored test strict and potentially turning previously successful executions into failures.

Knowledge Base Used:

Comment on lines 54 to +64
"type": "boolean",
"default": false
},
"failureThreshold": {
"description": "Tolerance applied when evaluating this test case. A deviation within the threshold is still reported as a success. Defaults to 0, which evaluates the test case strictly.",
"type": "number",
"minimum": 0,
"default": 0
},
"thresholdUnit": {
"description": "Unit `failureThreshold` is expressed in.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Unsupported Thresholds Are Accepted

CreateTestCase accepts a failure threshold for every test definition, but only the twelve migrated validators call the new threshold-aware helpers. An API or SDK caller can therefore save a nonzero threshold on another test type without receiving a validation error, while that validator continues evaluating strictly and silently ignores the saved configuration.

Knowledge Base Used:

@github-actions

Copy link
Copy Markdown
Contributor

✅ Generated Sources Auto-Updated

The generated TypeScript types (src/generated/) and dereferenced JSON
schemas (src/jsons/, public/jsons/) have been automatically updated
based on JSON schema changes in this PR.

Copilot AI lite review requested due to automatic review settings September 16, 2026 23:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@gitar-bot

gitar-bot Bot commented Sep 16, 2026

Copy link
Copy Markdown
Code Review ✅ Approved

Implements deviation-from-statistic evaluation with configurable failure thresholds and migrates 12 statistical tests to use the new apply_bound_tolerance() and within_deviation() helpers. Supports both ABSOLUTE and PERCENTAGE tolerance units, with proper handling of degenerate cases (zero bounds, infinite bounds, zero threshold). No issues found.

Review coverage

Rules No rules evaluated

Functional validation Not enabled · Set up

Auto-approval Not enabled · Set up

Options

Display: compact → Counting what did not apply, without listing it.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source

@github-actions

Copy link
Copy Markdown
Contributor

The Python checkstyle failed.

Please run make py_format and py_format_check in the root of your repository and commit the changes to this PR.
You can also use pre-commit to automate the Python code formatting.

You can install the pre-commit hooks with make install_test precommit_install.

@github-actions

Copy link
Copy Markdown
Contributor

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 72%
72.9% (102662/140816) 57.76% (62372/107976) 58.92% (20425/34661)

@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

✅ Playwright Results — workflow succeeded

Validated commit d9546031292b7af1507f0699606f2a53c7ae45de in Playwright run 35160717232, attempt 1.

✅ 876 passed · ❌ 0 failed · 🟡 0 flaky · ⏭️ 0 skipped · 🧰 0 lifecycle flaky

Performance

Blocking targets: ✅ met · Optimization targets: 🟡 in progress

Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting.

🕒 Full workflow signal wall (to summary) 54m 15s

⏱️ Max setup 4m 53s · max shard execution 22m 58s · max shard-job elapsed before upload 26m 51s · reporting 7s

🌐 230.41 requests/attempt · 2.15 app boots/UI scenario · 50.51% common-shard skew

Optimization targets still in progress:

  • Common shard skew was 50.51% (convergence target: at most 15%).
  • Browser traffic was 230.41 requests per attempt (convergence target: fewer than 200).
  • Application boot ratio was 2.15 per UI scenario (2002 boots / 930 scenarios; convergence target: at most 1).
Shard Passed Failed Flaky Skipped Lifecycle failed Lifecycle flaky
✅ Shard chromium-01 146 0 0 0 0 0
✅ Shard chromium-02 152 0 0 0 0 0
✅ Shard chromium-03 149 0 0 0 0 0
✅ Shard chromium-04 175 0 0 0 0 0
✅ Shard chromium-05 162 0 0 0 0 0
✅ Shard import-export-01 26 0 0 0 0 0
✅ Shard ingestion-01 38 0 0 0 0 0
✅ Shard ingestion-02 25 0 0 0 0 0
✅ Shard reindex-01 3 0 0 0 0 0

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ingestion safe to test Add this label to run secure Github workflows on PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DQ Threshold W1.2] Deviation-from-statistic evaluation + migrate 12 statistical tests

3 participants