All notable changes to this project are documented here. The format follows Keep a Changelog, and the project adheres to Semantic Versioning.
- Added a runnable PyJanitor interoperability example that demonstrates both tool orderings while keeping PyJanitor optional.
- A dependency-optional Great Expectations recipe demonstrating the repair-then-validate workflow with an in-memory checkpoint.
Remediation of the July 2026 v1.2.0 production-readiness audit: the unsafe defaults it confirmed are now safe-by-default, with every old behavior still available as an explicit opt-in. These default changes are breaking, hence the major version. v1.2.0 was tagged but never published, so PyPI users upgrade directly from 1.1.1 and should read the [1.2.0] section below as part of this release.
drop_duplicatesnow defaults toFalseunder every strategy. Exact duplicate rows are detected and reported but never removed until you opt in withdrop_duplicates=True. A duplicate ratio aboveduplicate_thresholdstill raises the strong report warning; the newduplicate_ratio_action="error"escalates it toDuplicateRatioErrorfor pipelines that must stop on a suspicious join.outlier_action="auto"now flags under every strategy, including"aggressive", and never rewrites values. Winsorizing requires an explicitoutlier_action="cap"; explicitly-requested capping is now skew-aware (log-space Tukey fences for strongly skewed positive data) so legitimate heavy tails are not flattened.dayfirst="auto"never infers a column-wide day/month order. Values whose day-first and month-first readings are both valid are quarantined intoreport.coerced_cells(originals preserved, audit action recorded) unlessdayfirst=True/Falseis set explicitly; a single unambiguous sibling value no longer flips the interpretation of a whole column.- Column-name outlier heuristics are opt-in. The
_DOMAIN_SENSITIVEname match (fraud/amount/risk/…) no longer changes outlier decisions by default; identically-distributed data gets identical treatment regardless of column name. Opt back in withdomain_sensitive_names=True. fd.anonymize()fails closed. Called with norulesand nodetection_configit now raisesValueErrorinstead of returning the data unchanged with a warning.- CSV outputs neutralize spreadsheet formula injection by default
(
fd.clean_csv,freshdata clean/apply-planCLI CSV output, the streaming CLI including its quarantine export, and the HTML-report ledger download). Cells and labels starting with= + - @ <tab> <cr>— including behind leading whitespace — are prefixed with'. Usesanitize_formulas=False/--no-sanitize-formulaswhere byte-exact round-trips matter. - PyPI Development Status classifier downgraded to
4 - Betauntil the project's own absolute release gates (CleanBench T5 runtime gate included) hold on release infrastructure.
- PII detection no longer misreports dates, UUID fragments, licence IDs,
Aadhaar-style 4-4-4 digit groups, semver strings, or ordinary numbers as
PHONE, and no longer misreports IBANs asCREDIT_CARD. Regex matches for PHONE / CREDIT_CARD / IBAN must now pass post-match validators: structural plausibility and boundary checks for phones, Luhn (13–19 digits) for cards, ISO 13616 mod-97 for IBANs (spaced and compact). Checksum-verified findings reportsource="checksum"with score ≥ 0.95. - Restored the nightly perf-regression workflow's CleanBench T5 gate, which
a temporary profiling probe (PRs #152/#153) had left neutered with
|| true— the gate and its failure-alert issue automation are enforcing again. - Refreshed the committed TruthBench
baseline.json, which still recorded the four pre-release-audit KNOWN-RED gates (cleaning:raw_pii_leakage,cleaning:review_routing,cleaning:exact_repair,generated_code_sandbox) even though the release-audit fixes shipped in v1.2.0 made them pass; a regression on any of them now fails the PR ratchet.
Note: v1.2.0 was tagged (
v1.2.0) but never published to PyPI or GitHub Releases — the release was stopped on a benchmark runtime gate. Everything below first shipped to users in 2.0.0.
- TruthBench release runner (
benchmarks/truthbench/): the semantic red-team foundation gained its missing production pieces — an end-to-end runner, normalized decision hashing with fail-closed repeat verification (determinism.py), full generated-code verification (parse → strict AST allowlist → compile → isolatedpython -Iexecution with module poisoning, timeout, input contract, protected-cell and PII-canary checks), a deterministic failure minimizer, atomic schema-validated result artifacts, and a CLI.make truthbench-release(equivalentlyPYTHONPATH=src python -m benchmarks.truthbench run --profile release --backends pandas,polars,duckdb --require-backends --repeats 2 --check) now gates PR CI and the release workflow; every decision/sink surface maps to a concrete behavioral adapter (a contract test rejects placeholders). - Adversarial regression suites for the twelve release-risk hypotheses
(
tests/test_release_hypotheses.py) and the TruthBench components. - Validation Gauntlet (
benchmarks/gauntlet/,docs/validation-gauntlet.md): a gold-labelled disposition benchmark for the validation, domain and text-cleaning surfaces. Five deterministic fixtures (finance, healthcare, CRM, e-commerce, adversarial text) label every injected defect with the disposition FreshData should choose (preserve / repair / flag / review) and the harness scores detection P/R/F1, repair accuracy, review routing, preservation, corruption, escapes, false positives, audit completeness, determinism, trust monotonicity and runtime/memory. Runs on every PR (gauntlet.yml) with absolute gates plus no-regression checks against the storedbaseline.json. CleanReport.coerced_cells: per-cell record ({column: {row: original}}) of values thatfix_dtypesnulled because they did not parse as the column's inferred type — the recovery source for quarantined cells, also included inreport.to_dict().- Date-field range validation in
fd.validate_fields:FieldSpec.min_value/max_valuenow accept a date string or timestamp fordate/datetimefields, so a future date of birth or an 1875 admission date is flagged as adomain_mismatch(gauntlet finding). - Case-variant vocabulary suggestions in
fd.validate_fields: a value that matches anallowed_valuesentry except for case (ACTIVEvsactive) is no longer silently accepted — it gets a warning-severity issue with the canonical form assuggestionand actionaccept_with_warning(gauntlet finding). docs/trust-claims.md(claim-to-evidence map for every trust-relevant README/docs claim, superset of the machine-enforcedCLAIM_REGISTRY) anddocs/threat-model.md(trust boundaries, per-privacy-mode guarantees, ranked residual risks), both linked in the docs nav.benchmarks/bench_outofcore.py: subprocess-isolated peak-RSS evidence for the four engine/output-format combinations on a generated parquet fixture (per-scenarioru_maxrss, wall time, and thematerializedflag).- AI Copilot (experimental) —
freshdata.experimental.ai_copilot.analyze_dataset: deterministic, fully offline dataset analysis that returns a ranked problem list (PII, policy violations, duplicates, missing values, mixed date formats, near-duplicate category spellings), a PII warning, an ordered explainable cleaning plan, and copy-ready freshdata code generated for the analyzed dataset. Privacy-first: raw string values never enter the report'smodel_context(every string-like sample column is hash-masked, numeric values pass through as-is, or samples are omitted entirely withprivacy="schema_only"); the payload is SHA-256 fingerprinted in the audit. An optionalproviderhook (plainCallable[[str], str]) allows plugging in an LLM later — no built-in provider ships, no API key is needed, and provider failures never break the deterministic report. - Flagship demo:
examples/freshdata_ai_copilot_demo.pyplus the bundledexamples/data/messy_customers.csv— the full messy-to-audit-ready story (analyze → mask → clean under a compiled policy → merge category variants → re-score trust), and a new docs guide (docs/ai-copilot.md). CITATION.cffso the project can be cited from GitHub's "Cite this repository" button, and a documentation issue template alongside the existing bug/feature templates.
- Lint now covers the whole repository (
ruff check .in CI, closing #54): benchmark and notebook lint debt fixed, dead code removed (harness_metricsunused gold-labels block), and the ASV-managedfreshdata-benchmarks/sub-project excluded as tool-generated. No runtime behavior changes. - All CI workflows now declare least-privilege
GITHUB_TOKENpermissions (read-only by default; the nightly-alert and coverage-badge jobs keep their scoped write grants). - Contributor docs (
CONTRIBUTING.md,README.md,QUALITY_OPS.md) now quote the exact commands CI runs; the pre-commit config no longer ships aruff-formathook the codebase and CI never enforced.
- Dead packaging/CI config:
MANIFEST.in(ignored by the hatchling build backend — the sdist is shaped bypyproject.toml) andfreshdata-benchmarks/.github/workflows/(nested workflow directories are never executed by GitHub Actions). - Committed AI-assistant working artifacts (
.superpowers/, now git-ignored) and internal planning notes that were being published to the documentation site (docs/superpowers/).
-
Default-path slowdown from the scientific-notation segfault guard (release blocker, nightly issue #147): the guard that masks huge-exponent tokens (
"1e999") beforepd.to_numeric— protection against a pandas 2.3.x segfault — screened text columns cell by cell through a Python predicate, roughly doublingfix_dtypestime on 50k-row frames in CI. Each column is now screened with a single C-level joined-blob regex scan and the per-cell predicate runs only on columns that screen positive. Masking semantics are unchanged; the CleanBench T5 runtime gate is back within its ±20 % baseline envelope. -
dir(freshdata)no longer listsActiontwice: the privacy-policy engine'sActionenum was listed in the lazy enterprise exports but was unreachable there —fd.Actionis (and remains) the audit action fromfreshdata.report. Usefreshdata.enterprise.Actionfor the privacy enum. -
Sensitive-column masking across all report surfaces (
fd.clean(sensitive_columns=...),fd.validate_fields(sensitive_columns=...),analyze_dataset(sensitive_columns=...)): values from declared-sensitive columns never appear verbatim in report warnings, coerced-cell payloads, action rationales/metadata/evidence,normalized_cells, or Copilot-recommended pipelines (which now always mask declared columns before printing report summaries). A deterministic[SENSITIVE:xxxxxxxx]digest token keeps records correlatable without disclosure. -
Ambiguous and partial dates are quarantined, never interpreted: a short-form date whose day/month order cannot be resolved (no explicit
dayfirst, no disambiguating sibling) and partial ISO dates ("2025-01") now coerce to missing with originals preserved incoerced_cellsfor review instead of being silently resolved month-first / to a fabricated day; time-range strings ("09:00-17:00") no longer parse to bogus offset-bearing timestamps. -
Corroboration-gated semantic mutations: unit strips auto-apply only with a declared column unit (inferred units demote to suggestions; unit-mismatched values become high-risk review items), and a new dataset-level
semantic_context["currencies"]declaration routes out-of-policy currency values to review instead of silently dropping the denomination. -
CSV formula injection in
write_exception_table: observed values such as=HYPERLINK(...)were written verbatim to the exception-table CSV and would execute when opened in a spreadsheet. The CSV path now routes through the samesanitize_csv_formulasguard every other spreadsheet export uses. -
Trust-score monotonicity:
compute_trust_scorerated corrupted frames higher than pristine ones because constant columns counted as structural inconsistency (corrupting one made it vary, clearing the flag). Constant columns are now surfaced as per-column issues instead of lowering the consistency dimension. -
Semantic overconfidence: an isolated unit value (
"10 lb"among plain numbers) is no longer auto-stripped at 0.97 confidence — inferred unit consistency now requires majority support and demotes to a suggestion otherwise; already-canonical ISO dates are no longer rewritten to timestamps when unparseable values keep the column as text. -
Calibration (nightly issue #139): the CleanBench full-suite ECE gate failed at 0.0384 > 0.03 from systematic underconfidence of the measured deterministic canonicalization families.
calib-default-2maps their raw 0.96–0.97 scores to measured rule-of-three lower bounds (email_format 148/148, phone_format 168/168, reference_value 128/128 across seeds 0–9) while staying identity below the 0.95 auto threshold, so no apply/suggest/review decision changes. ECE is now 0.0217. -
Default-path performance regression (nightly issues #139/#140): the pandas-segfault exponent guard and the relative-date guard in
fix_dtypesscanned whole columns per value; both are now vectorized (candidate prefilter / unique-first) restoring the T5 runtime gate (slowdown 0.27 → 0.03 vs the v1.0 baseline) with unchanged semantics. -
Nightly online/large lane (issue #138): the lane inherited the repo-wide
--cov-fail-under=93while deliberately selecting ~21 tests, so it could never pass; it now runs with--no-cov(coverage stays enforced on the full fast lane). -
Unparseable values are quarantined, never fabricated (gauntlet finding, the
'apple'-in-a-price-column case): whenfix_dtypesconverts a mostly-numeric (or datetime) text column, cells that fail to parse used to becomeNaNand then be silently imputed by the auto engine — turning junk into a fabricated median. They now stay missing, are excluded from auto-imputation, keep their originals inreport.coerced_cells, and the decision is ahuman_reviewaction in the audit trail. Genuine missing values (trueNaN, sentinels like"N/A") keep the documented auto-impute behaviour, and an explicitimpute=request still fills everything. -
Formatted-number stragglers (
"$1,234.56","1,200,500.00") in a mostly-plain numeric column are now parsed by the existing locale-aware rescue instead of being coerced to missing — the rescue previously only engaged when the plain parse failed the threshold entirely (gauntlet finding). -
fd.validate_fieldsconsensus inference now honours the same contamination boundary as thefix_dtypeswarning that points users at it (dominant share ≥ 60% with at most a handful of stragglers). Previously the warning fired from a 60% parse share but the consensus gate required 80%, so the exact frame the warning named sailed throughvalidate_fieldssilently (gauntlet finding). -
Explicitly allowed values are no longer swallowed by null-marker heuristics in
fd.validate_fields: withFieldSpec(allowed_values={"US", "DE", "NA"}),"NA"is Namibia, not a missing value (gauntlet finding). -
clean_text/validate_fieldstext normalization no longer rewrites typography in content-bearing fields: forfree_text,textand entity name types, the punctuation→ASCII mapping (curly quotes, em-dashes, prime marks —12″became12") is withheld, matching the field-aware safety contract. Untyped columns keep the existing behaviour (gauntlet finding). -
anonymize()called with norulesand nodetection_confignow emits aUserWarninginstead of silently returning the data unchanged — a privacy call that does nothing must say so. Behavior is otherwise unchanged; pass an empty rule set intentionally by suppressing the warning (found by the installed-wheel matrix audit). -
pip install "freshdata-cleaner[polars]"now actually enables the advertised polars round-trip: the extra was missingpyarrow, whichfd.clean(polars_df)needs for the polars→pandas interchange, so the natural install crashed with polars' internal ModuleNotFoundError. The extra now ships pyarrow, and the adapter raises an actionable message naming the fix when pyarrow is absent (found by the installed-wheel matrix audit). -
explain_cleancell-change reporting: when cleaning removed rows (for example duplicate removal), every shared column previously reported the whole surviving row count as "cells changed". Frames are now aligned on their shared index labels and only genuinely differing cells are counted; cells missing on both sides are unchanged, value↔missing transitions count, and a dtype conversion alone no longer marks untouched values as changed. The elementwise fallback also no longer uses a Python-3.10-onlyzip(strict=...)argument, which crashed on Python 3.9 when reached (#30). -
memory_bytessampled estimation (frames above 200k rows) no longer counts the index payload once per string-like column; a string-heavy index is now measured once, matching the exact path used for smaller frames (#35). -
Integer finalization now checks the exact int64 range in integer space instead of a float magnitude threshold:
-2**63and2**63 - 1024(the largest float64 below2**63) convert to int64/Int64 exactly instead of being demoted to float64, and values at or above2**63can never be admitted by float rounding (#34). -
AI Copilot privacy hardening: sample rows in
model_contextnow hash-mask every string-like column, not only declared/regex-detected PII columns — names, addresses, free text, and obfuscated identifiers in undeclared columns no longer leave the machine raw. A new explicitallow_unmasked_columnsopt-out exists but never exempts declared or detected PII.category_noiseproblem details are stripped of raw value previews before enteringmodel_contextin all privacy modes (includingschema_only); the localreport.problemskeeps the rich previews. -
Out-of-core docs now match measured behavior: keeping a native handle requires
fix_dtypes=Falsein addition tostrategy="conservative"(dtype fixing runs sampled pandas heuristics and forces the recorded fallback), andoutput_format="polars-lazy"defers only the final materialization — pipeline stages currently collect intermediates eagerly, so peak memory during cleaning matches eager output. The DuckDB handle path is the measured lower-peak-memory route (#52, #53). -
CSV formula-injection protection (OWASP):
export_review_queuenow neutralizes spreadsheet formula payloads in CSV exports by default (string cells and column labels starting with= + - @ <tab> <cr>get a leading'; opt out withsanitize_formulas=False) — review queues are built to be opened by humans in spreadsheets.fd.clean_csvand the streaming CLI keep byte-exact output by default and gain an explicit opt-in (sanitize_formulas=True/--sanitize-formulas) covering the cleaned output and the quarantine export. JSONL/Parquet are never altered. -
SECURITY.mdsupported-versions table updated to the current 1.1.x line. -
Source distribution now contains exactly the documented file set: the hatchling
includepatterns are anchored to the repo root, so unanchored names no longer pull in stray matches at any depth (docs/examples/*.html, nestedREADME.mds). -
freshdata-benchmarks/README.mdno longer claims CI execution or published results the repository never produced; it now documents the ASV suite as a locally-run comparative benchmark, separate from the CleanBench CI workflow.
- README rendering on PyPI: the logo and several links (
LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,examples/*) used paths relative to the repository, which resolve on GitHub but not on the PyPI project page (rendered with no repository context). All now point to absolutegithub.com/raw.githubusercontent.comURLs. No code changes.
- Interactive output layer (
freshdata.render, lazy-imported):to_html()/_repr_html_()/.show()onCleanReport(collapsible action timeline + filterable audit ledger),Profile(inline quality cockpit),CleanPlan(decision cards / strategy diff grid),ExplainReport(before/after diff explorer), and thecompare_plans/compare_clean/infer_rolesframes (via a transparentReportFrameDataFrame subclass). Self-contained HTML needs no optional deps; new[viz]/[notebook]extras (itables, plotly, great-tables, anywidget) only upgrade the output.Actiongainsstatus/reversible/memory_influenced/human_reviewmetadata. - Cleaning memory:
fd.learn_cleaning_memory/fd.load_cleaning_memory/CleaningMemory(JSON + server-free SQLite storage,to_dict/to_json/diff/summary) andfd.clean(df, memory=...)replay — applies accepted decisions when the dataset signature matches and blocks + explains unsafe replay when the data drifts too far. - Baseline drift convenience:
fd.compare_to_baselinenow accepts a raw DataFrame baseline pluskey=/event_time=for key-level change counts;DriftReportgainswhat_likely_matters()and an interactive view. - Quality-debt ledger:
fd.evaluate_quality_debtscores nine debt dimensions, persists history to SQLite, and escalates warn→fail on repeated/worsening issues. - Dirty-join assistant:
fd.suggest_join_keysproposes exact + fuzzy join keys with confidence, blocking, per-field explanations, and an ambiguous/review section — never auto-joining low-confidence matches. - Text/encoding lint:
fd.lint_text_encodingdetects mixed scripts, mojibake, NFC/NFD inconsistency, RTL/LTR risk, locale-ambiguous dates/numbers, and replacement/control characters (diagnostic-only, with safe-repair flags). - Stakeholder summaries:
fd.stakeholder_summaryexports business-language Markdown / HTML. - Honest out-of-core handles: new
output_format="duckdb"/"polars-lazy"return an un-materialized DuckDB relation / PolarsLazyFrame;CleanReport.materializedflags it. Streaming Polars dedup is now streaming-safe (no forcedmaintain_order) and discloses the order trade-off. - Benchmarks:
benchmarks/bench_report.py(100MB CSV ingest, 1M-row profile, 10M-row null-fill, import-time, memory; balanced vs aggressive) with reproducible commands and honest "not yet measured" placeholders in the docs. - New CDC / event-time quality gate
fd.cdc_profile(df, event_time=..., key=...)(modulefreshdata.cdc, also exportingCDCReport/CDCDefect): classifies change-data-capture defects that are not nulls — stale, late (past-watermark), out-of-order, duplicate-key, invalid-operation, missing-event-time, and replay-risk batches — with per-key ordering, an explicit-watermark mode, and freshness/ordering/CDC trust penalties (each0..1). Read-only; never imputes.CDCReportsupports.summary()/.to_dict()/.to_json()/.to_frame()/.passed/.trust_penalties/.freshness_seconds. - New provenance-aware cleaning for document/OCR-extracted tables (module
freshdata.provenance):fd.clean(df, source_provenance=..., return_report=True)andclean_enterprise(..., source_provenance=...)preserve per-columnsource_file/page/region/parser_confidence/extracted_atand warn when a low-confidence field is coerced or repaired (provenance_confidence_threshold, default0.7). The summary lands atCleanReport.source_provenanceand in.to_dict(). FreshData is the post-extraction normalization/audit layer, not a PDF parser. - New baseline-free contract schema diff (
fd.diff_schema(df, contract=...), exposed lazily fromfreshdata.enterprise.contracts): explains structural schema drift before any repair runs, with no persisted baseline required. Reports added/unexpected, removed, renamed, dtype, nullability, and semantic-domain drift, returning aDriftReportwith a structuredcontract_resultscategorization and.summary()/.to_dict()/.to_json()/.to_frame()exports. Policieson_unexpected(fail|warn|preserve) andon_missing(fail|warn|ignore) control the gate. Rename detection is evidence-based (matching semantic type or high name similarity over a dtype-compatible pair), so unrelated same-dtype columns are never reported as renames.fd.profile(df, contract=...)attaches the same diff atprofile.schema_diff.DriftReportalso gains a.to_frame()exporter shared withmonitor_contract/compare_to_baseline. Read-only; never mutates input. - Contract gate in
fd.cleanandfd.suggest_plan(contract=,on_unexpected=,on_missing=): runsdiff_schemaon the input before repair. A failing gate (errors in the diff) raisesContractViolation(carrying theDriftReportat.report); otherwise the diff is attached to theCleanReportas a JSON-friendlycontract_violationssection that surfaces in.summary()and.to_dict().fd.suggest_plan(df, contract=...)exposes the same diff atplan.schema_diff. In-memory pandas engine only; never auto-renames or drops on the basis of a diff.CleanReportgains acontract_violationsfield. - New wide-schema / large-frame perf controls on
fd.profile:profile_sample=Nprofiles a deterministic N-row sample (stats become estimates),max_columns=Mcaps profiling to the first M columns, andlazy_report=Trueskips the expensive full-frame duplicate-row scan. When any is used theProfiledescribes the profiled subset and records the totals atprofile.materialization(also in.to_dict()).build_profilegains matchingsample=/max_columns=/lazy=keyword-only parameters; default behaviour is unchanged. - New two-frame entity-resolution wrapper
fd.link(left, right, keys=..., strategy="exact"|"fuzzy"|"external")(alsofreshdata.enterprise.link): the ergonomic front door overlink_entities. Builds the resolution config fromkeys+strategy, returns anEntityResolutionReportwith candidate pairs, confidence scores, per-field explanations, and a steward-reviewable structure.strategy="external"formats an adapter callable's pairs (e.g. Dedupe) without re-implementing it. Defaults to the pandas backend (no optional deps); supports ablocking=override andreturn_linked=. - Privacy/regulated-pipeline hardening on
MaskingRule:strategy="token"is now accepted as an alias for the reversibletokenizestrategy, and rules gainretention_days,policy_id, andpolicy_reasonfields.MaskReport(frommask_dataframe) now surfaces per-columnretentionand an auditablepolicy_provenancelist (which rule masked each column, with what strategy, under which policy id, and why), both exported via.to_dict(). FreshData records the declared retention policy for audit; it does not enforce deletion and makes no automatic compliance claims. - New compliance-grade privacy policy engine (
freshdata.enterprise.privacy_policy, exposed asfd.PrivacyPolicy/fd.PrivacyRule/fd.CompliancePack/fd.Jurisdiction/fd.apply_privacy_policy/fd.load_privacy_policy/fd.load_compliance_pack): turns the masking primitives into a declarative, jurisdiction-aware (US / EU / UK / India / Global) policy with actionsclassify/tokenize/pseudonymize/redact/drop/minimize/quarantine/preserve_with_reason. Ships built-in HIPAA, FERPA, PCI and GDPR rule packs (YAML underfreshdata/compliance/packs) combining column-name, value-regex, context and entity/domain-pack classifiers; PCI card numbers are gated by a Luhn check. Policies load from YAML/JSON. Reversible tokenisation uses pluggable vault backends (memory/json/sqlite, viafd.make_vault) and requires an explicit vault and key;detokenize_seriesreverses only with both. The returnedPrivacyReportgains a Data-Trust privacy dimension (sensitive_fields_detected/_touched,unprotected_sensitive_fields,policy_violations, 0–100 score), per-column audit fields (rule_id,action,legal_basis_or_reason,jurisdiction,compliance_pack), plusto_frame()/to_json(). Reports redact previews and never expose vault secrets by default. The legacydetect_pii/anonymize/check_k_anonymity/MaskingRule/PrivacyReportAPI is unchanged. - New schema-drift & data-contract monitoring (
freshdata.enterprise.contracts, exposed asfd.build_baseline/fd.save_baseline/fd.load_baseline/fd.compare_to_baseline/fd.monitor_contract): record a versioned, PII-safeDatasetBaseline(schema + numeric/categorical/datetime statistics) for a trusted dataset, persist it as JSON ("schema_version": "freshdata-baseline-v1"), then detect schema drift, distribution drift (dependency-free KS statistic and PSI over baseline quantile/frequency bins),DataContractviolations (dtype/nullable/unique/ allowed-values/min-max/regex/cardinality), and a trust-score quality gate. Baselines never store raw sample values unlessinclude_samples=True; category labels are hashed by default. Configured viaDriftConfig. Findings are JSON-serialisable and the input frame is never mutated. - New stronger PII detection + reversible / format-preserving anonymization
(
freshdata.enterprise.privacy, exposed asfd.detect_pii/fd.anonymize/fd.check_k_anonymity): a Presidio-style but dependency-free detector (regex + context keywords, optional Presidio NER behind the[privacy]extra) across 15+ entity types with HIPAA/GDPR context boosting; reversible tokenization with an in-memory or JSONTokenVault(tokenize_value/detokenize_value); surrogate/fpeformat-preserving anonymization (clearly flagged as not cryptographic FPE unlesspyffxis installed); HIPAA/GDPR-taggedMaskingEventaudit records that redact raw previews by default (audit_include_pii=Trueto include them); and acheck_k_anonymityre-identification report.MaskingRulegainstokenize/fpe/surrogatestrategies plusentity_types/reversible/key/key_env/token_vault_path/preserve_format/hipaa_tags/gdpr_tags; all existing strategies keep working unchanged. - New probabilistic entity resolution at scale (
freshdata.enterprise.entity_resolution, exposed asfd.resolve_entities/fd.link_entities): a Splink-style, DuckDB-backed record-linkage backend (with a pandas fallback) that blocks candidate pairs via SQL predicates, scores them with weighted comparisons (exact / Jaro–Winkler / Levenshtein / numeric & date distance / phonetic Soundex / custom SQL — all pure-Python primitives), and builds entity clusters via connected components with a completeness-based canonical record. A hardmax_pairsgate prevents cartesian explosions. Configured viaEntityResolutionConfig/BlockingRule/ComparisonLevel. Documented as rule-weighted probabilistic linkage (not full EM-trained Splink parity). EnterpriseConfiggainsdrift/privacy/anonymization/k_anonymity/entity_resolutionsub-configs andenable_contracts/enable_privacy_detection/enable_entity_resolutiontoggles;clean_enterpriseacceptsbaseline=/contract=andEnterpriseResultnow carriesdrift_report/privacy_report/k_anonymity_report/entity_resolution_report. New optional extras[privacy]and[entity-resolution], plus examplesschema_drift_monitoring.py,privacy_anonymization.py, andentity_resolution_duckdb.py.- New FHIR R4 JSON parser (
fd.parse_domain(source, format="fhir")): flattens a Bundle, a single resource, a list of resources, a JSON string, or a file path intopatient/observation/encounter/condition/medication_requestframes whose columns line up with the healthcare validators. The healthcare pack now validates Condition and MedicationRequest (FHIR R4 clinical-status / status / intent value sets, ICD-10 codes against a documented common sample, ISO-8601 dates), adds UCUM unit validation on Observations via the reference layer, and auto-detects all five resources. Resource IDs are never imputed;patient_idstays PHI-masked unlessaudit_include_phi=True; unsupported resource types are recorded as warnings, not dropped. The HL7 v2 parser now also parses theOBRsegment (anorderframe, with eachOBXlinked to its order). - New format parsers (
freshdata.parsers) andfd.parse_domain/fd.clean_domain_file: structural readers that turn HL7 v2 ER7 (MSH/PID/PV1/OBX → patient/encounter/observation, with LOINC/SNOMED/ICD-10 code-system URIs), GPX (waypoints/routes/tracks), SDMX-ML (audit-only observations), and UN/EDIFACT (segments/elements, honoringUNAdelimiters + the release character) into DataFrames. Parsers register via afreshdata.parsersplugin registry; malformed input is recorded inParseResult.warningsrather than raising. - New centralized reference-data layer (
freshdata.domains.reference): one cached, normalizer-awareload_reference(...)over the bundled code sets (ISO-4217, ISO-3166, UN/CEFACT units, plus new UCUM and UN/LOCODE samples), each with a_metaversion/disclaimer block. Supports case-sensitive/insensitive matching, synonym coercion, and aninvalid_maskfor validators. - New finance tick mode (
fd.clean(df, domain="finance", finance_mode="tick")): validates market tick/trade data — ISO-8601 non-future timestamps, positive price/size, ISO-4217 currency (via the reference layer), non-crossed quotes (bid <= ask), duplicate-tick detection, and BCBS-239 / SOX-style completeness controls. Symbol and exchange are IDs and are never imputed; the defaultfinance_mode="ledger"is unchanged. - New energy (SCADA / Modbus) domain pack:
fd.clean(df, domain="energy")validates point-level telemetry — one row per(asset_id, register_address, timestamp)reading — against common Modbus/SCADA conventions: the 16-bit register-address range (0–65535), the public Modbus function codes (1, 2, 3, 4, 5, 6, 15, 16), OPC/SCADA point quality (good/bad/uncertain/stale/null, with synonym coercion), engineering units, and non-future ISO-8601 timestamps. Asset IDs are never imputed; bad/stale/uncertain readings and function/register-class mismatches are flagged for audit rather than dropped. Bundled reference data ships with_metaversion/disclaimer notes documenting that these are common public conventions, not exhaustive vendor specifications. The validator is stateless per frame, so it composes with micro-batch streaming. - New
freshdata.streamingsubpackage andfd.StreamingCleanerfor streaming / micro-batch cleaning of datasets larger than memory. It consumes pandas (and, when installed, PyArrowTable/RecordBatchand polarsDataFrame/LazyFrame) batches, keeps bounded running statistics across them — Welford mean/variance, reservoir-sampled medians, Space-Saving top-k categories — and emits the same explainableCleanReportper micro-batch, now carrying astreamingblock withbatch_id, rows seen, and per-batch / rolling / cumulative trust scores plus a schema-drift flag. Imputation runs in a warmup phase (collect stats, defer and audit) then a stable phase (impute from running stats), preserving every leakage-aware safety gate (ID/target/free-text). Optional source connectors (clean_kafka,clean_arrow_flight) sit behind newfreshdata[kafka|flight]extras and raise a clearImportErrorwhen absent. New CLI subcommandsfreshdata stream,stream-kafka, andbenchmark-streamprocess CSV/Parquet batch-by-batch with per-batch + summary reports and a trust-gate exit code, andbenchmarks/bench_streaming.pyproves stable memory across a lazily-generated 100M-row stream.CleanReportserialization stays backward compatible (nostreamingkey for normal in-memory cleans). - New
freshdata.executionsubpackage: a pluggable, out-of-core / Arrow-native execution engine.fd.clean()gains keyword-onlyengine("pandas"|"polars"|"duckdb"|"auto"),output_format("pandas"|"polars"|"arrow"), andengine_config(EngineConfig) arguments — all backward compatible; default callers are unchanged. The Polars backend cleansLazyFrame/Parquet sources with projection/predicate pushdown and streaming collection; the DuckDB backend cleans via staged SQL with spill-to-disk under a configurablememory_limit. Both reproduce the deterministic representation-repair + structural-reduction + full-row-dedup subset natively (identicalCleanReportto the pandas pipeline) and transparently fall back to pandas for the accuracy-first decision engine, dtype heuristics, and opt-in impute/outliers.engine="auto"picks a backend from the source type and row count, andfd.clean("data.parquet")now also reads a file path directly. New optional extras:freshdata[polars|duckdb|pyarrow|outofcore|bench]. - New
freshdata.benchmarksharness (python -m freshdata.benchmarks.run_benchmarks) that generates synthetic Parquet at a target row count without materialising it, then timesfd.cleanacross the pandas/polars/duckdb backends (wall time, peak resident memory, throughput, Data Trust Score). Seesrc/freshdata/benchmarks/RESULTS.mdfor a 10k–10M reference run. - New
freshdata.integrationssubpackage with first-class orchestration hooks for Dagster (freshdata_asset_check,FreshDataResource), Airflow (FreshDataCleanOperator), and dbt (FreshDataDbtTransform, thedbt-gateCLI, and afreshdata_trust_gatemacro). A framework-agnostic core,evaluate_trust_gate(df, ...) -> (DataFrame, TrustGateResult), cleans a frame and gates it on the 0-100 Data Trust Score, reacting to a low score with warn / fail / skip. Each adapter is an opt-in extra (freshdata[dagster|airflow|dbt|integrations]) and imports cleanly without its framework; a compliance bundle is attached to the gate report whenfreshdata.complianceis available. - New
freshdata.compliancesubpackage that maps aCleanReportonto regulatory control frameworks and emits standards-grade audit artifacts viagenerate_compliance_report(report, frameworks=[...]) -> ComplianceBundle. Five frameworks ship:21cfr_11(21 CFR §11.10(e) audit trail),gdpr_30(Article 30 + 17),alcoa_plus(ALCOA+ data integrity),sox_404(transformation controls), andhipaa_safe_harbor(18-identifier coverage). Reports are purely additive and report-only (never mutate the input). Optionaldataframe=recovers column roles/missing ratios viainfer_roles, andenterprise_result=folds in the Data Trust Score, PII-masking events, and clustering lineage.ComplianceConfig.strict_cfr_normalization(defaultFalse) toggles whether lossless normalising rewrites count as obscuring for the 21 CFR gate. - Four new domain validator packs:
healthcare(FHIR/US Core —Patient,Observation,Encounterwithfhir_resource=/auto-detection),education(Ed-Fi),agriculture(ADAPT, with area/yield unit coercion), andmedia(EIDR/DDEX viamedia_type=/auto-detection, with tested EIDR Mod 37,2 and ICPN GS1 mod-10 check digits). Healthcare/education redact PHI in the audit trail as[PHI]unlessaudit_include_phi=True.fd.cleangains optionalfhir_resource,media_type, andaudit_include_phikeyword arguments. - P1 repair-layer primitives for validator bridges, schema drift harmonization, duplicate/replay defense, and human review queues.
- Top-level bridge adapters:
freshdata.from_gx,freshdata.from_dbt_failures,freshdata.from_pandera_errors,freshdata.emit_gx_expectations, andfreshdata.emit_dbt_tests.
- Packaging: the distribution is
freshdata-cleaneragain. A recent commit renamed the project back tofreshdata, a name PyPI rejects as too similar to the existingfresh-dataproject (the exact collision that forced the original rename).pyproject.toml, every in-source install hint, the docs, and the packaging tests now agree onpip install freshdata-cleaner(import name unchanged:import freshdata). - MissForest:
<col>_was_missingindicators are no longer all-False. The indicator was computed after the column had been imputed, so it never marked any row (andmissforest_add_indicators="auto"never fired at all). Indicators now come from the pre-fill missing mask and the pre-computed column context, matching the standard imputation engine. - Outliers: an explicit
outlier_actionis now honored. Under the defaultstrategy="balanced",outlier_action="cap"(and"remove") was silently downgraded to"flag", so capping never happened despite being the documented default — extreme values were returned unchanged. Explicit"cap"/"remove"/"flag"are now applied to every eligible numeric column. - Small frames no longer skip outlier handling. The engine's minimum non-null threshold dropped from 10 to 4 (the floor at which IQR / z-score fences are defined), so outliers in small DataFrames are detected and handled.
- The default
outlier_actionis now"auto"(context-aware: flags underbalanced, caps underaggressive, flags heavy-tailed >15%-outlying columns). The default behavior underbalancedis unchanged (still flags); only the explicit-directive path changed. An explicitcap/removeon a heavy-tailed column now caps / removes and emits a warning instead of silently flagging.
First stable release. The public API is now considered stable under Semantic Versioning — breaking changes will require a 2.0.
- Promoted the package to Production/Stable (
Development Status :: 5).
- No behavioral changes versus 0.5.0. The stable public surface is
fd.clean,fd.profile,fd.suggest_plan,fd.compare_plans,fd.compare_clean,fd.explain_clean,fd.infer_roles,fd.Cleaner,fd.CleanConfig,fd.CleanReport/fd.Action,fd.Profile, and the lazily importedfreshdata.enterpriselayer. - Install:
pip install freshdata-cleaner; import:import freshdata as fd.
- Documentation site built with MkDocs Material and deployed to GitHub
Pages (https://freshcode-org.github.io/freshdata/): installation,
quickstart, cleaning-engine, profiling, feature overview, benchmarks,
auto-generated API reference (mkdocstrings), FAQ, and contributing guides,
with search, dark/light mode, OpenGraph metadata,
sitemap.xml, androbots.txtfor SEO/AI discoverability. examples/— 8 runnable scripts (missing values, outliers, normalization, profiling, ML pipeline, large datasets, pandas integration, CSV automation) andnotebooks/— 3 reproducible Jupyter walkthroughs.- Packaging governance:
MANIFEST.in,SECURITY.md,RELEASE.md,.pre-commit-config.yaml, a tag-triggered PyPI release workflow (release.yml) using Trusted Publishing, a docs-deploy workflow (docs.yml), and an issue-template chooser config. - Expanded PyPI keywords and classifiers and a
Documentationproject URL for better search ranking and discoverability.
clean_enterprise(df)and the reusableFreshDataEnterprisepipeline: core cleaning → fuzzy value clustering → semantic validation → PII masking, returning anEnterpriseResult(cleaned frame + trust scores + quality report + lineage). Accepts and returns pandas or polars — Polars-native on the hot path when installed, with a vectorized pandas fallback otherwise.- Data Trust Score (
compute_trust_score,TrustScore): a 0–100 score from completeness, validity, uniqueness, and structural consistency, with per-column detail and a JSON/MarkdownQualityReport(build_quality_report). - Value clustering (
merge_clusters,cluster_column): OpenRefine-style fingerprint key-collision and n-gram merging of variants/typos, built from native Polars string expressions (pandas fallback), withmost_frequent/longest/shortest/firstcanonicalisation. - PII masking (
mask_dataframe,MaskingRule): salted SHA-256hash,redact,partial,regex_scrub(built-in email/phone/SSN/credit-card/IP/IBAN patterns), anddrop; null-preserving and frame-type-preserving. - Semantic validation (
SemanticValidator+ReferenceSetValidator/RegexValidator/CallableValidator/APISemanticValidator,run_semantic_validation), including a built-in ISO-3166iso_country_validator. - Lineage (
LineageTracker,schema_of): records who/when/input-schema/output-schema/ rule per step and exports OpenLineage-compatibleSTART/COMPLETERunEvents (schema + column-lineage facets) with no hard dependency on the OpenLineage client. - Optional Cleanlab wrappers (
detect_label_issues,detect_outliers) with a clear install-hint error when cleanlab is absent. - CLI (
freshdata):clean/profile/trustsubcommands reading CSV/Parquet/JSON, emitting JSON quality + OpenLineage reports, with a non-zero exit code on trust-gate failure — suitable as an Airflow/Prefect batch step. Config via JSON/YAML files. - New optional-dependency extras:
pyarrow,semantic,cli,cleanlab, aggregateenterprise, andall. Polars/PyArrow/requests/cleanlab are imported lazily, so plainimport freshdatastays dependency-light.
- Default strategy is now
"balanced"— accuracy-first cleaning that preserves high-missing columns, flags outliers instead of capping, and skips KNN imputation. Usestrategy="aggressive"for v0.2-style scrubbing (KNN, column drops, winsorization). strategy="auto"is deprecated (alias for"aggressive"; emitsDeprecationWarningonce per process).
fd.suggest_plan(df)andfd.compare_plans(df)— dry-run previews of engine model choices per column, with ranked alternatives.- Model selection router (
engine/model_select.py) scoring imputation and outlier actions;Action.model_idrecords the chosen model. - Expanded target/label heuristics (
aqi,*_bucket,score, …) and domain-sensitive outlier preservation (pollutants, prices, latency, …). profile(df, include_plan=True)attaches aCleanPlanatprofile.plan.src/freshdata/py.typedmarker for PEP 561 typing support.- Multi-dataset regression suite (
tests/fixtures/,test_regressions.py,test_realworld.py,test_model_select.py,test_plan.py). - Golden report snapshots (
tests/fixtures/golden/,pytest --update-golden). - Benchmark tests (
test_benchmark.py) andbenchmarks/bench.py --fixtures. - CI enforces ≥93% coverage and treats
freshdatawarnings as errors. - README migration guide for 0.2 → 0.3.
- KNN imputation: collinearity pruning, row-count gate (10k), warning suppression, index alignment on fill.
- Re-cleaning idempotency for outlier flag columns.
fd.compare_clean()— side-by-side quality + efficiency metrics per strategy.- Four new scenario fixtures:
large_panel(3k rows),duplicate_heavy,locale_numbers,mixed_roles. - Performance baselines (
tests/fixtures/perf/baselines.json) with 25% regression gate. @pytest.mark.largeoptional full AQI.csv benchmark (FRESHDATA_AQI_PATH).- Engine perf: one-pass
EngineCache(contexts + correlation matrix), lazy informative-missing checks, sampled skew on large columns. benchmarks/bench.py --comparetable output.
fd.clean(df) now performs real, context-aware automatic cleaning by
default, driven by a rule-based decision engine.
- Decision engine (
strategy="auto", the new default): profiles every column (missing ratio, dtype, skewness, cardinality, inferred role, informative missingness) and applies threshold rules for missing values and outliers. Every action — including deliberately preserving a column — is logged with a rationale, risk level, and confidence score. - Missing-value bands with configurable thresholds
(
missing_threshold_low/medium/high, defaults 0.05/0.30/0.60): contextual mean/median/mode/sentinel/ffill imputation, KNN imputation for correlated numeric features (scikit-learn optional), column drops for high/extreme missingness with logged reasons,<col>_was_missingindicator columns when missingness is informative. - Column-role inference: targets are never modified, IDs are never imputed, free text is never force-filled, datetimes use time-aware fills.
- Outlier engine:
outlier_action="cap"(default) /"remove"/"flag"/None;outlier_method="auto"(z-score for ~normal, IQR for skewed) and"isolation_forest"; heavy-tail protection (flag instead of cap); domain-sensitive columns (fraud/anomaly/risk) keep their extremes. - Duplicate rules:
duplicate_keep="first"/"last"/"drop"/"aggregate",duplicate_thresholddata-quality warning, time-indexed frames protected unlessallow_timeseries_duplicates=True; count and percentage reported. - New
clean()parameters:strategy, the threshold options,outlier_action,preserve_original,return_report,verbose,preserve_columns,target_column,id_columns,advanced_imputation,missing_indicators. - Report upgrades: per-action
rationale/risk/confidence, missing cells before/after, duplicates removed, outliers handled, columns dropped/imputed/preserved,warnings,recommendations, and a compactbrief()used byverbose=True. - Optional extra:
pip install "freshdata-cleaner[ml]"for scikit-learn.
- Default behavior: statistical cleaning now runs by default. Pass
strategy="conservative"for the 0.1.x representation-only behavior; explicitimpute=/outliers=still override the engine. report.to_frame()gainedrationale,risk, andconfidencecolumns.verbose=True(default) prints a one-line summary per clean.
Initial release.
freshdata.clean()— automatic, audited cleaning: column-name normalization, whitespace stripping, sentinel-string normalization, empty row/column pruning, validated dtype inference (numeric incl. currency/thousands separators, datetime, boolean), and exact duplicate removal.- Opt-in steps: imputation (
auto/mean/median/mode), outlier clipping/flagging (IQR or z-score), constant-column dropping, memory optimization (numeric downcasting + category conversion), index reset. freshdata.profile()— read-only profiling whose dtype suggestions are produced by the same inference codecleanuses.freshdata.Cleaner— reusable configured pipeline withreport_.freshdata.CleanConfig— frozen, self-validating configuration; unknown options raise with a "did you mean" suggestion.freshdata.CleanReport/freshdata.Action— structured audit trail withsummary(),to_dict(),to_frame().- Type hints throughout (
py.typed), zero dependencies beyond pandas/numpy, support for Python 3.9–3.13.