Validation Gauntlet: gold-labelled disposition benchmark + six defect fixes#137
Merged
Conversation
…aphy, close fieldcheck gaps
Six defects surfaced by the Validation Gauntlet, each pinned by a failing
regression test first (tests/test_gauntlet_regressions.py):
- fix_dtypes coercion casualties ('apple' in a 99%-numeric price column)
were nulled and then silently imputed by the auto engine — fabricated
medians with no per-cell trace. They are now quarantined: left missing,
excluded from auto-imputation, originals preserved in the new
CleanReport.coerced_cells, decision recorded as a human_review action.
Sentinels and true NaN keep the documented auto-impute behaviour and an
explicit impute= still fills everything.
- formatted-number stragglers ("$1,234.56") in a mostly-plain numeric
column are now parsed by the existing locale-aware rescue instead of
coerced to missing (the rescue only ran when the plain parse failed).
- validate_fields consensus now honours the same contamination boundary as
the fix_dtypes warning that points users at it (>=60% share with a
handful of stragglers); the documented handoff frame no longer passes
silently.
- explicitly allowed values beat null-marker heuristics ('NA' with
allowed_values containing 'NA' is Namibia, not missing).
- case variants of allowed values ('ACTIVE' vs 'active') get a
warning-severity issue with the canonical suggestion instead of silent
casefold acceptance.
- FieldSpec.min_value/max_value accept date bounds for date fields, so a
future dob or an 1875 admission date is flagged.
- free_text/text/entity fields keep their typography: the punctuation→ASCII
mapping (curly quotes, em-dash, prime marks) is withheld for
content-bearing types in config_for_field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…chmark Five deterministic fixtures (finance, healthcare, crm, ecommerce, adversarial text) label every injected defect with the disposition FreshData should choose — preserve / repair / flag / review — including the flagship 'apple' case: quarantined in a price column, preserved as a company name and ticker, routed to review as a lowercase ticker. The runner drives public surfaces only (fd.clean defaults, validate_fields, clean_text safe + explicit opt-in config, semantic auto mode, lint_text_encoding, detect_pii, domain packs) and the metrics score detection P/R/F1, repair accuracy per source, review routing, preservation, corruption, escapes, FPR, audit completeness, determinism, trust monotonicity, runtime and peak memory. python -m benchmarks.gauntlet run writes JSON + Markdown with a failure catalogue; --check gates against absolute thresholds and the stored baseline.json. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- gauntlet.yml: PR-triggered lightweight run (300 rows) with absolute gates and no-regression checks vs the committed baseline.json, Markdown job summary, uploaded artifacts; heavier sizes via workflow_dispatch. - docs/validation-gauntlet.md (+ mkdocs nav, benchmarks/README pointer). - CHANGELOG: gauntlet, coerced-cell quarantine, formatted-number rescue, fieldcheck consensus/vocabulary/case/date-bounds fixes, typography protection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
|
Strix is installed on this repository, but we couldn't run this PR security review because this workspace's trial has ended. Add a card to resume code reviews here. |
Contributor
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (20)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
FreshData benchmark report —
|
| fixture | n_rows | n_cols | p50 s | p95 s | peak MB | repair % | false-repair % | preserve % | trust | monotonic | export % |
|---|---|---|---|---|---|---|---|---|---|---|---|
| crm | 10,200 | 40 | 1.406 | 1.411 | 13.9 | 100.0 | 0.0 | 100.0 | 93.84 | ✅ | 100.0 |
| event_log | 10,000 | 25 | 0.568 | 0.569 | 5.1 | 100.0 | 0.0 | 100.0 | 99.677 | ✅ | 100.0 |
| finance | 10,200 | 60 | 1.809 | 1.815 | 18.3 | 100.0 | 0.0 | 100.0 | 99.499 | ✅ | 100.0 |
| gold | 10,200 | 7 | 0.214 | 0.215 | 3.7 | 100.0 | 0.0 | 100.0 | 98.3 | ✅ | 100.0 |
| provenance | 10,000 | 18 | 0.467 | 0.471 | 7.9 | 100.0 | 0.0 | 100.0 | 99.765 | ✅ | 100.0 |
| wide_schema | 10,000 | 100 | 3.446 | 3.474 | 13.7 | 100.0 | 0.0 | 100.0 | 96.042 | ✅ | 100.0 |
Authored-code reduction (Metric 6)
- FreshData: 3 lines
- pandas baseline: 25 lines (88.0% reduction)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A release-ready Validation Gauntlet: an objective, gold-labelled benchmark for FreshData's semantic validation, domain validation and text-cleaning surfaces — plus fixes for the six highest-impact defects it exposed, each pinned by a failing-first regression test.
The benchmark (
benchmarks/gauntlet/)Five deterministic fixtures (finance, healthcare, crm, ecommerce, adversarial text; 300 rows, seeded) label every injected defect with the disposition FreshData should choose: preserve / repair / flag / review. Automatic removal is never the default correct answer — mutating a flag/review/preserve cell counts as a corruption, the most severe verdict.
Flagship case:
applein a price column is quarantined (original kept inreport.coerced_cells),Apple(company) andAAPL(ticker) are preserved, lowercase tickerappleis routed to review with suggestionAAPL.The runner drives public API only:
fd.cleandefaults,fd.validate_fields,fd.clean_text(safe + explicit opt-in config, graded separately), semantic auto mode,lint_text_encoding,detect_pii, domain packs. Metrics: detection P/R/F1, repair accuracy (per surface), review routing, preservation rate, corruption count, escape rate, FPR, audit completeness, determinism, trust monotonicity, runtime + peak memory.python -m benchmarks.gauntlet runemits JSON + Markdown with a failure catalogue;--checkgates against absolute thresholds and the committedbaseline.json.gauntlet.ymlruns it on every PR with a job summary and artifacts.Defects fixed (failing regression test first —
tests/test_gauntlet_regressions.py)fix_dtypesnulled coercion casualties and the auto engine imputed the median:'apple'price →425.17,'forty'age →53,'37,2'°C →37.6. Now quarantined: left missing, excluded from auto-imputation, originals in the newCleanReport.coerced_cells,human_reviewaudit action. Sentinels/true-NaN keep documented auto-impute; explicitimpute=still fills everything."$1,234.56"rescue only ran when the plain parse failed its threshold;'$3,400.50'in a mostly-plain column became NaN. Now rescued into correct numbers.validate_fieldshandoff found nothing:fd.clean's contamination warning fires from a 60% parse share, but the consensus gate needed 80% — the README frame the warning names passed silently. Consensus now honours the same straggler boundary.'NA'withallowed_values={'US','DE','NA'}was treated as missing. Membership now outranks null markers (Namibia wins).'ACTIVE'vs{'active'}now yields a warning-severity issue with the canonical suggestion (accept_with_warning).FieldSpec.min_value/max_valueaccept date strings for date fields; a future dob or an 1875 admission date is now flagged.12″into12"and em-dashes into hyphens inside free text and entity names. Content-bearing types now keep their typography; untyped columns keep existing behaviour.Results (300 rows/fixture, seed 42)
Before the fixes the same gauntlet measured 11+ corruptions across fixtures (median-fabricated prices,
'NA'→'Unknown'countries, mangled product names) and repair accuracy as low as 0.14 on finance.Known accepted gap (documented in the failure catalogue and docs): a hostile SQL-injection payload inside a
person_namefield escapes detection — any plausibility check tight enough to catch it false-positives on legally real names (X Æ A-12).Verification
pytest -m "not online and not large": 3490 passed, 0 failed, coverage 93.68% (gate 93) — baseline before changes: 3463 passedruff check src tests benchmarks/gauntlet: cleanmypy src/freshdata: clean (199 files)python -m benchmarks.gauntlet run --check: all gates passbenchmarks/bench.py runfixtures: 100% fidelity / 0% false repair / 100% preservation (unchanged)Backward compatibility
impute="median"(explicit) restores the old outcome.clean_text(df, field_types={...: "free_text"})no longer normalizes typographic punctuation for content types; untyped columns unchanged.report.to_dict()gains acoerced_cellskey only when non-empty.Built with AI tooling; all changes reviewed and verified by test runs above.
🤖 Generated with Claude Code