From 0d34e378a44caaeec37f5e1df0baee1bcd269e2f Mon Sep 17 00:00:00 2001 From: Kevin Costner <120246174+kevincostner17@users.noreply.github.com> Date: Tue, 15 Sep 2026 22:20:41 +0530 Subject: [PATCH 1/3] docs: regenerate HTML examples for current default output The committed samples predated the report-only duplicate default, the outlier flag default and the not-assessed quality-debt dimensions (#420): rows now read 205 -> 205, revenue shows outliers -> flag (7 flagged), and the quality_debt total is 0.34 with schema_drift/category_churn unknown. Regenerated with scripts/generate_html_examples.py. The action timeline's duration and the quality-debt run_at timestamp are run-dependent and no renderer option omits them, so those lines change on every regeneration. --- docs/examples/action_timeline.html | 8 ++++---- docs/examples/baseline_drift.html | 2 +- docs/examples/compare_plans_grid.html | 2 +- docs/examples/explain_diff.html | 10 +++++----- docs/examples/quality_debt.html | 8 ++++---- docs/examples/suggest_plan_cards.html | 4 ++-- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/examples/action_timeline.html b/docs/examples/action_timeline.html index fb787a7d..3f4c8f6f 100644 --- a/docs/examples/action_timeline.html +++ b/docs/examples/action_timeline.html @@ -29,7 +29,7 @@ .fd-del-pos{color:#1a7f37}.fd-del-neg{color:#cf222e} .fd-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.8rem} -

freshdata clean report

backend=pandas · 0.015s
205 → 200
rows
5 → 5
columns
76 → 23
missing cells
5
duplicates removed
0
outliers handled
5
actions
-

Action timeline

fix_dtypes signup_date — converted to datetime64[ns] (205 affected) low automatic
confidence
100%
drop_duplicates — dropped 5 duplicate row(s) (2.4% of rows, keep='first') (5 affected) low automatic
confidence
100%
missing email — preserved 23 missing value(s) (0 affected) medium automatic
why: identifier-like column — imputing keys would fabricate identities
confidence
100%
missing tier — filled 51 missing value(s) with sentinel "Missing" ('Missing') (51 affected) medium automatic
why: medium missingness without a dominant category; an explicit sentinel keeps the gap visible
confidence
70%
outliers revenue — preserved 5 outlier(s), 2.5% of values (method=iqr, factor=1.5) (0 affected) low automatic
why: protected column role or domain
confidence
90%
-

Audit ledger

⬇ JSON⬇ CSV
columnactionriskconfidencecountdescription
signup_datefix_dtypeslow100%205converted to datetime64[ns]
drop_duplicateslow100%5dropped 5 duplicate row(s) (2.4% of rows, keep='first')
emailmissingmedium100%0preserved 23 missing value(s)
tiermissingmedium70%51filled 51 missing value(s) with sentinel "Missing" ('Missing')
revenueoutlierslow90%0preserved 5 outlier(s), 2.5% of values (method=iqr, factor=1.5)
-

Needs review

\ No newline at end of file +

freshdata clean report

backend=pandas · 0.021s
205 → 205
rows
5 → 6
columns
76 → 24
missing cells
0
duplicates removed
7
outliers handled
5
actions
+

Action timeline

fix_dtypes signup_date — converted to datetime64[ns] (205 affected) low automatic
confidence
100%
drop_duplicates — detected 5 duplicate row(s) (2.4%), none removed (0 affected) low automatic
why: drop_duplicates=False (default): duplicate rows are reported, never removed; pass drop_duplicates=True to remove them
confidence
100%
missing email — preserved 24 missing value(s) (0 affected) medium automatic
why: identifier-like column — imputing keys would fabricate identities
confidence
100%
missing tier — filled 52 missing value(s) with sentinel "Missing" ('Missing') (52 affected) medium automatic
why: medium missingness without a dominant category; an explicit sentinel keeps the gap visible
confidence
70%
outliers revenue — flagged 7 outlier(s), 3.4% of values (method=iqr, factor=1.5) in new column 'revenue_outlier' (7 affected) low automatic
why: flagging records the detection without altering any value
confidence
70%
+

Audit ledger

⬇ JSON⬇ CSV
columnactionriskconfidencecountdescription
signup_datefix_dtypeslow100%205converted to datetime64[ns]
drop_duplicateslow100%0detected 5 duplicate row(s) (2.4%), none removed
emailmissingmedium100%0preserved 24 missing value(s)
tiermissingmedium70%52filled 52 missing value(s) with sentinel "Missing" ('Missing')
revenueoutlierslow70%7flagged 7 outlier(s), 3.4% of values (method=iqr, factor=1.5) in new column 'revenue_outlier'
+

Needs review

\ No newline at end of file diff --git a/docs/examples/baseline_drift.html b/docs/examples/baseline_drift.html index 312a3f7c..ca575e34 100644 --- a/docs/examples/baseline_drift.html +++ b/docs/examples/baseline_drift.html @@ -32,4 +32,4 @@

freshdata drift report — baseline

FAIL · 1 error(s), 1 warning(s) · baseline v1.0.0
FAIL
verdict
1
errors
1
warnings

What likely matters

Findings

checkcolumnstatusmessage
drift.kscustomer_idmediumKS 0.244 >= warn threshold 0.1
drift.psicustomer_idhighPSI 0.877 >= fail threshold 0.25
-⬇ JSON
\ No newline at end of file +⬇ JSON \ No newline at end of file diff --git a/docs/examples/compare_plans_grid.html b/docs/examples/compare_plans_grid.html index c41e10b2..b871166a 100644 --- a/docs/examples/compare_plans_grid.html +++ b/docs/examples/compare_plans_grid.html @@ -29,4 +29,4 @@ .fd-del-pos{color:#1a7f37}.fd-del-neg{color:#cf222e} .fd-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.8rem} -

freshdata strategy comparison

strategy diff grid
columnstrategymissing_modeloutlier_actionn_outliers
emailbalancedpreserve0
revenuebalanced5
tierbalancedsentinel0
emailaggressivepreserve0
revenueaggressive5
tieraggressivesentinel0
\ No newline at end of file +

freshdata strategy comparison

strategy diff grid
columnstrategymissing_modeloutlier_actionn_outliers
emailbalancedpreserve0
revenuebalancedflag7
tierbalancedsentinel0
emailaggressivepreserve0
revenueaggressiveflag7
tieraggressivesentinel0
\ No newline at end of file diff --git a/docs/examples/explain_diff.html b/docs/examples/explain_diff.html index ed05fd9c..e5788493 100644 --- a/docs/examples/explain_diff.html +++ b/docs/examples/explain_diff.html @@ -29,8 +29,8 @@ .fd-del-pos{color:#1a7f37}.fd-del-neg{color:#cf222e} .fd-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.8rem} -

freshdata explain — balanced

before/after diff explorer
205×5 → 200×5
shape
76 → 23
missing
5
steps
1000
changed cells
-

Decisions

-

Safe changes

none
-

Risky changes (review)

columnbefore dtypeafter dtypechanged cells
customer_idint64int64200
emailobjectobject200
revenuefloat64float64200
signup_dateobjectdatetime64[ns]200
tierobjectobject200
-⬇ JSON
\ No newline at end of file +

freshdata explain — balanced

before/after diff explorer
205×5 → 205×6
shape
76 → 24
missing
5
steps
257
changed cells
+

Decisions

+

Safe changes

columnbefore dtypeafter dtypechanged cells
tierobjectobject52
customer_idint64int640
emailobjectobject0
revenuefloat64float640
+

Risky changes (review)

columnbefore dtypeafter dtypechanged cells
revenue_outlierbool205
signup_dateobjectdatetime64[ns]0
+⬇ JSON
\ No newline at end of file diff --git a/docs/examples/quality_debt.html b/docs/examples/quality_debt.html index 400abde9..491cefa2 100644 --- a/docs/examples/quality_debt.html +++ b/docs/examples/quality_debt.html @@ -29,7 +29,7 @@ .fd-del-pos{color:#1a7f37}.fd-del-neg{color:#cf222e} .fd-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.8rem} -

freshdata quality-debt gate

WARN · policy warn_then_fail · total debt 1.25
WARN
-
WARN
status
1.25
total debt
2
over threshold
97.6
trust
-
dimensionscorethresholdseveritytrenddetail
pii_risk1.000.00medium177 potential PII column(s)
human_review_backlog0.200.00medium2 item(s) awaiting review
duplicates0.020.10low5 duplicate row(s) removed
missingness0.020.10low23 missing cell(s) remain
schema_drift0.000.00lowno baseline supplied
type_instability0.000.10low0 column(s) with unstable types
outlier_spikes0.000.10low0 outlier(s) flagged
category_churn0.000.10lowno baseline supplied
failed_repairs0.000.00low0 high-risk action(s)
-⬇ JSON
\ No newline at end of file +

freshdata quality-debt gate

WARN · policy warn_then_fail · total debt 0.34
WARN
+
WARN
status
0.34
total debt
2
over threshold
97.5
trust
+
dimensionscorethresholdseveritytrenddetail
pii_risk0.170.00medium1 potential PII column(s)
human_review_backlog0.100.00medium1 item(s) awaiting review
outlier_spikes0.030.10low7 outlier(s) flagged
duplicates0.020.10low5 duplicate row(s) detected (0 removed)
missingness0.020.10low24 missing cell(s) remain
schema_driftn/a0.00unknownno baseline supplied
type_instability0.000.10low0 column(s) with unstable types
category_churnn/a0.10unknownno baseline supplied
failed_repairs0.000.00low0 high-risk action(s)
+⬇ JSON
\ No newline at end of file diff --git a/docs/examples/suggest_plan_cards.html b/docs/examples/suggest_plan_cards.html index c9117b20..d2b198a2 100644 --- a/docs/examples/suggest_plan_cards.html +++ b/docs/examples/suggest_plan_cards.html @@ -29,5 +29,5 @@ .fd-del-pos{color:#1a7f37}.fd-del-neg{color:#cf222e} .fd-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.8rem} -

freshdata clean plan — balanced

per-column decision cards
email
missing → preserve
confidence 95%
why: identifier column
revenue
no engine action
tier
missing → sentinel
confidence 80%
why: no dominant category — explicit sentinel
alternatives: mode
-⬇ JSON
\ No newline at end of file +

freshdata clean plan — balanced

per-column decision cards
email
missing → preserve
confidence 95%
why: identifier column
revenue
outliers → flag (7 flagged)
tier
missing → sentinel
confidence 80%
why: no dominant category — explicit sentinel
alternatives: mode
+⬇ JSON
\ No newline at end of file From f8f64147deca7831a8e60082ab44a57af35edc2d Mon Sep 17 00:00:00 2001 From: Kevin Costner <120246174+kevincostner17@users.noreply.github.com> Date: Tue, 15 Sep 2026 22:20:59 +0530 Subject: [PATCH 2/3] test: skip benchmark tests when benchmarks/ is absent The sdist ships tests/ but not benchmarks/. conftest.py only filters module-level imports, so tests that read benchmarks/ inside the test body failed or errored from an unpacked sdist. The bench_streaming fixture now uses pytest.importorskip (as the cleanbench suites do), and the entity resolution smoke test and benchmark-stream CLI test skip when the benchmark file is missing. The CLI test also runs from the repo root, since the command resolves benchmarks/ against the working directory. --- tests/test_enterprise_entity_resolution_dedupe.py | 7 ++++--- tests/test_streaming_cli.py | 12 ++++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/test_enterprise_entity_resolution_dedupe.py b/tests/test_enterprise_entity_resolution_dedupe.py index f36af65f..bd7601ca 100644 --- a/tests/test_enterprise_entity_resolution_dedupe.py +++ b/tests/test_enterprise_entity_resolution_dedupe.py @@ -494,9 +494,10 @@ def test_retail_template_and_overrides(): def test_benchmark_smoke(): root = pathlib.Path(__file__).resolve().parents[1] - spec = importlib.util.spec_from_file_location( - "bench_er", root / "benchmarks" / "bench_entity_resolution.py" - ) + bench_path = root / "benchmarks" / "bench_entity_resolution.py" + if not bench_path.is_file(): + pytest.skip("benchmarks/ directory not present (e.g. running from the sdist)") + spec = importlib.util.spec_from_file_location("bench_er", bench_path) assert spec and spec.loader mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) diff --git a/tests/test_streaming_cli.py b/tests/test_streaming_cli.py index 008d832a..b9826c88 100644 --- a/tests/test_streaming_cli.py +++ b/tests/test_streaming_cli.py @@ -120,9 +120,8 @@ def test_cli_stream_parquet_roundtrip(tmp_path): def bench_module(): sys.path.insert(0, str(BENCH_DIR)) try: - import bench_streaming # type: ignore[import-not-found] - - yield bench_streaming + # benchmarks/ is repo-only (not in the sdist): skip rather than error there. + yield pytest.importorskip("bench_streaming") finally: sys.path.remove(str(BENCH_DIR)) sys.modules.pop("bench_streaming", None) @@ -149,7 +148,12 @@ def test_benchmark_generator_emits_exact_row_count(bench_module): # -- benchmark-stream CLI command --------------------------------------------------- -def test_cli_benchmark_stream_writes_json(tmp_path): +def test_cli_benchmark_stream_writes_json(tmp_path, monkeypatch): + # The command imports bench_streaming from ./benchmarks, which only a git + # checkout has; the sdist ships tests/ without it. + if not (BENCH_DIR / "bench_streaming.py").is_file(): + pytest.skip("benchmarks/ directory not present (e.g. running from the sdist)") + monkeypatch.chdir(BENCH_DIR.parent) report = tmp_path / "bench.json" rc = main(["benchmark-stream", "--rows", "2000", "--batch-size", "1000", "--cols", "6", "--report", str(report)]) From f149d84371012db53959219647b2f156ef30ff19 Mon Sep 17 00:00:00 2001 From: Kevin Costner <120246174+kevincostner17@users.noreply.github.com> Date: Tue, 15 Sep 2026 22:21:13 +0530 Subject: [PATCH 3/3] docs: note Python 3.9 on Linux aarch64 builds the privacy stack from source thinc 8.3.4 and blis 1.2.0 publish no cp39 Linux aarch64 wheel, and spacy 3.8.7 (the py3.9 cap) requires thinc>=8.3.4,<8.4, so no pin avoids the source build. Document it in the install guide and next to the py3.9 caps in pyproject.toml. Dependency pins are unchanged. --- docs/installation.md | 8 ++++++++ pyproject.toml | 3 +++ 2 files changed, 11 insertions(+) diff --git a/docs/installation.md b/docs/installation.md index 0d0494a7..07b827ee 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -59,6 +59,14 @@ Install only what you need: Pass a Polars DataFrame to `fd.clean` and get a Polars DataFrame back. +!!! note "Python 3.9 on Linux aarch64: the `privacy` extra builds from source" + `freshdata-cleaner[privacy]` (and `[all]`) pulls in spaCy through Presidio. + On Python 3.9 spaCy is capped at 3.8.7, which requires `thinc>=8.3.4,<8.4`, + and neither thinc 8.3.4 nor blis 1.2.0 publishes a cp39 Linux aarch64 wheel. + pip therefore compiles thinc and blis from source there, which needs a C/C++ + toolchain and takes several minutes. Python 3.10+ or x86-64 installs use + prebuilt wheels. + ## Verify the installation ```bash diff --git a/pyproject.toml b/pyproject.toml index 72e52101..bebadcd0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -168,6 +168,9 @@ excel = [ # Newer ones still declare Requires-Python >=3.9 but publish only an sdist for # 3.9, and spacy >=3.8.13 cannot even build there (it needs thinc>=8.3.12, which # is Python >=3.10). Keep these caps in sync with the `all` extra. +# Even at these caps, thinc 8.3.4 and blis 1.2.0 have no cp39 Linux aarch64 +# wheel, so that platform compiles them from source (see docs/installation.md). +# No pin avoids it: spacy 3.8.7 requires thinc>=8.3.4,<8.4. privacy = [ "presidio-analyzer>=2.2", "presidio-anonymizer>=2.2",