cleanframe-engine 0.4.0__tar.gz → 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/.gitignore +3 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/CHANGELOG.md +130 -1
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/CONTRIBUTING.md +17 -1
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/PKG-INFO +124 -20
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/README.md +114 -17
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/_util.py +7 -1
- cleanframe_engine-0.5.1/cleanframe/_version.py +1 -0
- cleanframe_engine-0.5.1/cleanframe/agent_tools.py +1335 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/api.py +59 -1
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/cli.py +163 -1
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/codegen.py +25 -5
- cleanframe_engine-0.5.1/cleanframe/decisions.py +1465 -0
- cleanframe_engine-0.5.1/cleanframe/demo.py +124 -0
- cleanframe_engine-0.5.1/cleanframe/demo_data/next_month_customers.csv +5 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/categories.py +134 -38
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/currency.py +2 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/dates.py +6 -0
- cleanframe_engine-0.5.1/cleanframe/mcp_server.py +193 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/ops.py +73 -5
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/quality.py +10 -3
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/report.py +7 -3
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/result.py +8 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/README.md +7 -0
- cleanframe_engine-0.5.1/docs/agents.md +230 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/api-reference.md +15 -1
- cleanframe_engine-0.5.1/docs/benchmark.md +58 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/cli.md +40 -5
- cleanframe_engine-0.5.1/docs/decisions.md +183 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/detectors-and-ops.md +9 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/getting-started.md +12 -1
- cleanframe_engine-0.5.1/docs/github-action.md +194 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/installation.md +8 -2
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/plugins.md +1 -1
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/production.md +3 -0
- cleanframe_engine-0.5.1/examples/customer.schema.yaml +31 -0
- cleanframe_engine-0.5.1/examples/github-action/data-ci.yml +57 -0
- cleanframe_engine-0.5.1/examples/messy_customers.csv +7 -0
- cleanframe_engine-0.5.1/examples/next_month_customers.csv +5 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/pyproject.toml +22 -2
- cleanframe_engine-0.5.1/tests/test_agent_tools.py +539 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_audit_hardening.py +44 -0
- cleanframe_engine-0.5.1/tests/test_bench.py +280 -0
- cleanframe_engine-0.5.1/tests/test_decisions.py +534 -0
- cleanframe_engine-0.5.1/tests/test_demo.py +59 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_fix_numbers_and_parity.py +16 -1
- cleanframe_engine-0.5.1/tests/test_github_action.py +579 -0
- cleanframe_engine-0.5.1/tests/test_mcp_server.py +117 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_ops.py +14 -0
- cleanframe_engine-0.5.1/tests/test_playground.py +258 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_profile_detectors.py +61 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_properties.py +3 -1
- cleanframe_engine-0.4.0/cleanframe/_version.py +0 -1
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/LICENSE +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/SECURITY.md +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/__init__.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/__main__.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/_numparse.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/_textparse.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/checks.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/dataio.py +0 -0
- {cleanframe_engine-0.4.0/examples → cleanframe_engine-0.5.1/cleanframe/demo_data}/customer.schema.yaml +0 -0
- {cleanframe_engine-0.4.0/examples → cleanframe_engine-0.5.1/cleanframe/demo_data}/messy_customers.csv +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/__init__.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/base.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/contacts.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/dedup.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/nulls.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/outliers.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/schema_mapping.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/text.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/units.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/diff.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/drift.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/errors.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/executor.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/fingerprint.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/issues.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/llm.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/planner.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/plugins.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/profile.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/py.typed +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/readfix.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/recipe.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/schema.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/streaming.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/types.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/validate.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/workbook.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/architecture.md +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/concepts.md +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/faq.md +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/llm.md +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/recipe-spec.md +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/schema-spec.md +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/examples/customer.recipe.yaml +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/conftest.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_api_report_cli.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_cli_json_logging.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_cross_platform.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_docs_in_sync.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_drift_codegen.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_executor_diff.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_fix_llm_trust.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_fix_reading.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_fix_streaming.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_llm_schema.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_planner.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_plugins.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_production_safety.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_recipe.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_release_hardening.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_review_round2.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_unique_mapping.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_validate.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave1_codegen.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave1_correctness.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave1_hardening.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave1_lineage.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave1_llm.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave2_scaling.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave2_vectorization.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave3_selection.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave3_workbook.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave4_readfix.py +0 -0
- {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave5_streaming.py +0 -0
|
@@ -7,6 +7,132 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.1] — 2026-10-19
|
|
11
|
+
|
|
12
|
+
One logo everywhere, a README hero made from real output, and a correct row count in `cleanframe demo`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- The HTML profiling and diff reports show the CleanFrame mark in their header, the same mark as the
|
|
17
|
+
README, the docs and the playground. `docs/assets/Logo.svg` is now the one source of the mark.
|
|
18
|
+
- The README hero is rendered from a real `cleanframe demo` run by `scripts/render_brand_assets.py`, in a
|
|
19
|
+
light and a dark version: the messy file, the recipe excerpt, the clean output with its quarantined row,
|
|
20
|
+
and the drift stop on next month's file. It replaces an illustration whose steps were not CleanFrame ops.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- `cleanframe demo` said "1 duplicate row(s) dropped" for the row it had quarantined. Its summary now
|
|
25
|
+
reports the quarantined row on its own and names duplicates only when the recipe dropped some.
|
|
26
|
+
|
|
27
|
+
## [0.5.0] — 2026-10-18
|
|
28
|
+
|
|
29
|
+
Decision models, agents, a playground and data CI. CleanFrame can now ask a typed decision model
|
|
30
|
+
(TypeSafe's Jev, an open "System One" model, or any LLM through an adapter) the judgment calls rules
|
|
31
|
+
leave open, and freeze the confident answers into the recipe. Replay still makes zero AI calls. This
|
|
32
|
+
release also fixes two pandas 3 bugs and a rules merge that could fold one real value into another.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- **Decision models: `clean(decide=...)` / `cleanframe clean --decide SPEC`** (`cleanframe/decisions.py`).
|
|
37
|
+
CleanFrame asks four kinds of typed questions: is `BLR` another name for `Bangalore` (choice); is a typo
|
|
38
|
+
merge the rules made really the same category (yes/no, and a "no" **vetoes** the merge); does an ambiguous
|
|
39
|
+
token such as `none`/`-`/`unknown` mean missing in *this* column (yes/no); which source column holds a
|
|
40
|
+
weakly-matched schema field (choice).
|
|
41
|
+
- Backends: `typesafe/<model>` (`TYPESAFE_API_KEY`), `openrouter/typesafe/<model>` (`OPENROUTER_API_KEY`),
|
|
42
|
+
`systemone/<model>` for self-hosted Jev-compatible servers (`SYSTEMONE_BASE_URL`), `rules` (offline
|
|
43
|
+
heuristics), any LLM spec through an adapter (recorded as `calibrated: false`), or a custom object with
|
|
44
|
+
`.decide()`. The System One wire protocol is implemented with the standard library: no new dependency.
|
|
45
|
+
- Policy: an answer that changes data needs `p ≥ max(mode threshold, decision_threshold)` (0.9 by default).
|
|
46
|
+
Below that it is a recorded suggestion, never executed. Undoing a rules merge only needs `p > 0.5`.
|
|
47
|
+
Confirming every typo merge in a column lifts it to 0.9 confidence so it survives `strict`.
|
|
48
|
+
- Privacy follows `llm_exposure`: under `metadata` only schema questions (names, types, patterns) and
|
|
49
|
+
null-token questions (tokens from CleanFrame's fixed vocabulary) are asked. Category questions need
|
|
50
|
+
`sample` and are only asked about columns whose values repeat.
|
|
51
|
+
- Every answer is recorded in `recipe.meta.decisions` (model, calibrated, threshold, counts, records);
|
|
52
|
+
`CleanResult.decisions` and the CLI's `--json` summary carry the same. Questions are built from sorted
|
|
53
|
+
distinct values, so row order cannot change them. `max_decisions` caps cost, 429/5xx are retried with
|
|
54
|
+
backoff, and a failing backend falls back to the rules plan with a warning (`--no-llm-fallback` raises).
|
|
55
|
+
- New flags: `--decide`, `--decision-threshold`, `--max-decisions`.
|
|
56
|
+
- **`cleanframe demo`.** A guided run on sample data that now ships inside the wheel
|
|
57
|
+
(`cleanframe/demo_data/`): profile, clean into a recipe, watch next month's drifted file **stop**, then
|
|
58
|
+
replay the drafted patch. It works right after `pip install`, with no clone and no API key. Also
|
|
59
|
+
`cleanframe.demo.demo_file()` / `run_demo()`. Before this, the README's first command needed a clone.
|
|
60
|
+
- `examples/next_month_customers.csv`: the drifted sample the demo uses.
|
|
61
|
+
- **MCP server for AI agents**: `cleanframe-mcp`, `cleanframe mcp` or `python -m cleanframe.mcp_server`
|
|
62
|
+
(extra `[mcp]`, `mcp>=2.3`). Seven tools: `profile_data`, `suggest_recipe`, `apply_recipe`,
|
|
63
|
+
`check_drift`, `explain_recipe`, `list_capabilities`, `export_code`.
|
|
64
|
+
- Errors come back structured (`ok`/`kind`) and flagged `isError`. Output is bounded JSON, with every cap reported.
|
|
65
|
+
- The input file is never written, and drift stops a replay by default.
|
|
66
|
+
- `CLEANFRAME_AGENT_HIDE_VALUES=1` replaces quoted cell values with `"<hidden>"`.
|
|
67
|
+
- The same tools are plain functions in `cleanframe.agent_tools`, with no `mcp` dependency.
|
|
68
|
+
- **Agent Skill** `skills/cleanframe/SKILL.md`, installed with `npx skills add inboxpraveen/Cleanframe`. It
|
|
69
|
+
teaches Claude Code, Codex, Cursor and other agents the recipe workflow and its hard rules, instead of
|
|
70
|
+
throwaway pandas.
|
|
71
|
+
- **GitHub Action "CleanFrame Data CI"** (`action/`, `uses: inboxpraveen/Cleanframe/action@v0.5.0`).
|
|
72
|
+
- Replays committed recipes over data files on pull requests or a schedule, and fails the check on schema
|
|
73
|
+
drift (exit 3) or validation failure (exit 4).
|
|
74
|
+
- Writes a per-file job-summary table and annotations.
|
|
75
|
+
- Outputs: `drift`, `changed-cells`, `quarantined`, `failed-files`, `exit-code`, `results-file`.
|
|
76
|
+
- Inputs reach its scripts only through environment variables, and CleanFrame runs in an isolated virtualenv.
|
|
77
|
+
- See `docs/github-action.md` and `examples/github-action/data-ci.yml`.
|
|
78
|
+
- **In-browser playground** (`playground/`, https://inboxpraveen.github.io/Cleanframe/).
|
|
79
|
+
- Drop a CSV/TSV/XLSX and get a Mess Score (100 − quality score), the detected problems, the YAML recipe, a
|
|
80
|
+
cell-level diff, the generated pandas code and both HTML reports.
|
|
81
|
+
- A shareable score card shows counts only, never names or values.
|
|
82
|
+
- CleanFrame runs client-side in Pyodide 314.0.7 in a Web Worker. A Content-Security-Policy limits the page
|
|
83
|
+
to its own site and the pinned Pyodide CDN path, so files never leave the browser.
|
|
84
|
+
- Deployed by `.github/workflows/pages.yml` from a wheel built from `main`; `python playground/dev_server.py`
|
|
85
|
+
serves it locally.
|
|
86
|
+
- **CleanBench** (`bench/`, not shipped in the wheel or sdist): a benchmark for data-cleaning decisions.
|
|
87
|
+
- It runs `decide=` backends through CleanFrame's real pipeline on 60 hand-labelled messy tables
|
|
88
|
+
(299 questions: aliases, typo merges, null tokens, schema mapping).
|
|
89
|
+
- Gold is written as equivalence classes and checked by an offline oracle pass. Every scenario lists the
|
|
90
|
+
questions it must produce.
|
|
91
|
+
- Metrics: safe-apply precision (of the changes a system makes, how many are right), coverage@0.9, macro
|
|
92
|
+
accuracy with scenario-bootstrap CIs, ECE, Brier, cells fixed vs corrupted, latency, $ per 1k decisions.
|
|
93
|
+
- `python -m bench.cleanbench {validate,run,leaderboard,export,hash}`. `export` writes standalone System One
|
|
94
|
+
requests with gold, for other leaderboards.
|
|
95
|
+
- A private held-out split is committed only as a SHA-256.
|
|
96
|
+
- The rules baseline is on `bench/LEADERBOARD.md` with its misses visible. Docs: `docs/benchmark.md`.
|
|
97
|
+
- CI: a required **pandas 3** job (pandas 3.0 shipped in January; the existing job only tried
|
|
98
|
+
pre-releases), and an **action smoke** job that runs the action on Linux, Windows and macOS runners.
|
|
99
|
+
|
|
100
|
+
### Fixed
|
|
101
|
+
|
|
102
|
+
- **pandas 3: `dedup` with `ignore_case` silently kept case-variant duplicates.** The key builder only
|
|
103
|
+
case-folded `object` columns, and pandas 3 reads text as dtype `str`. It now folds `str`/`string`/categorical
|
|
104
|
+
columns too, in the executor and in exported code alike.
|
|
105
|
+
- **pandas 3.1: `fill_na` warns, and pandas 4 would make it fail,** when the fill value doesn't fit the
|
|
106
|
+
column: `empty` or a text value into a number or date column, `zero` into text (pandas 3's default
|
|
107
|
+
text dtype). pandas used to turn such a column into `object` silently; 3.1 deprecates that. The op and
|
|
108
|
+
exported code now cast explicitly first, so results are unchanged and nothing warns. Caught by the
|
|
109
|
+
pandas pre-release CI job on 3.1.0rc0.
|
|
110
|
+
- **A typo merge could fold one real value into another.** `Austria` (rare) was merged into `Australia`
|
|
111
|
+
in `review` and `auto` mode because the names score 0.875 similar. A typo merge now also needs the two
|
|
112
|
+
spellings to be one edit apart, so look-alikes and opposites two letters apart stay separate
|
|
113
|
+
(`Hypothyroidism`/`Hyperthyroidism`, `Prednisone`/`Prednisolone`). Real typos (`Banglore`, `Kolkatta`,
|
|
114
|
+
`Califronia`) still merge; rarer two-edit typos are left to a human or a decision model.
|
|
115
|
+
- **Categories that differ only in `+`, `-` or `#` were merged as case variants** (confidence 0.9, applied
|
|
116
|
+
in every mode): blood groups `A-` → `A+`, `O-` → `O+`; `C++`/`C#` → `C`. These marks now always count.
|
|
117
|
+
A hyphen between letters (`New-York`) is still cosmetic.
|
|
118
|
+
- **`similarity()` scored any two non-Latin strings as identical** (both reduce to an empty ASCII key), so
|
|
119
|
+
`दिल्ली` matched `मुंबई` perfectly in schema mapping and drift. Such pairs are now compared as text.
|
|
120
|
+
- Negation is detected inside phrases too (`Very dissatisfied` / `Very satisfied`).
|
|
121
|
+
- **The quality score ignored date-format and money-as-text problems.** Those issues report `unparsed: 0`
|
|
122
|
+
when every value parses, and the scorer read that as "no rows affected". So a column in three date
|
|
123
|
+
formats cost nothing. The detectors now report an explicit `affected` count, and the scorer prefers it.
|
|
124
|
+
Scores for such files drop (the bundled sample goes from 87 to 80); that is the fix, not a regression.
|
|
125
|
+
- `docs/plugins.md` showed `cleanframe --plugin X apply …`, which is a usage error; global flags go after
|
|
126
|
+
the subcommand.
|
|
127
|
+
- Two tests assumed pandas 2 string semantics and failed on pandas 3 without a library bug behind them;
|
|
128
|
+
they now accept either representation of a missing text cell.
|
|
129
|
+
|
|
130
|
+
### Changed
|
|
131
|
+
|
|
132
|
+
- `pyproject.toml` and `CITATION.cff` name the maintainer (Praveen Kumar); PyPI keywords and project
|
|
133
|
+
URLs (Playground, Funding) added.
|
|
134
|
+
- `docs/cli.md`: the `--json` summary documents `usage_error` and the `decisions` field.
|
|
135
|
+
|
|
10
136
|
## [0.4.0] — 2026-09-30
|
|
11
137
|
|
|
12
138
|
A correctness release. An empirical review (26 hard-input probes, a 1M/3M-row benchmark and a
|
|
@@ -390,7 +516,10 @@ now fail loudly, and a few transforms that quietly corrupted data no longer run.
|
|
|
390
516
|
deterministic executor, validation/quarantine, cell-level diff, schema drift, HTML reports,
|
|
391
517
|
codegen, and CLI.
|
|
392
518
|
|
|
393
|
-
[Unreleased]: https://github.com/inboxpraveen/Cleanframe/compare/v0.
|
|
519
|
+
[Unreleased]: https://github.com/inboxpraveen/Cleanframe/compare/v0.5.1...HEAD
|
|
520
|
+
[0.5.1]: https://github.com/inboxpraveen/Cleanframe/releases/tag/v0.5.1
|
|
521
|
+
[0.5.0]: https://github.com/inboxpraveen/Cleanframe/releases/tag/v0.5.0
|
|
522
|
+
[0.4.0]: https://github.com/inboxpraveen/Cleanframe/releases/tag/v0.4.0
|
|
394
523
|
[0.3.1]: https://github.com/inboxpraveen/Cleanframe/releases/tag/v0.3.1
|
|
395
524
|
[0.3.0]: https://github.com/inboxpraveen/Cleanframe/releases/tag/v0.3.0
|
|
396
525
|
[0.2.0]: https://github.com/inboxpraveen/Cleanframe/commits/main
|
|
@@ -27,6 +27,17 @@ than no change at all. The rules below exist to protect it.
|
|
|
27
27
|
raw cell values by default, and its output must go through `Recipe.from_dict`
|
|
28
28
|
(which validates it) — never straight to the executor.
|
|
29
29
|
|
|
30
|
+
The same holds for **decision models** (`decisions.py`, `clean(decide=...)`): they
|
|
31
|
+
answer typed questions while planning, and accepted answers become ordinary recipe
|
|
32
|
+
ops, so replay never calls them. Under the default `metadata` exposure a question may
|
|
33
|
+
carry column names, types, value *patterns* and — the one exception — a null token,
|
|
34
|
+
because the token always comes from CleanFrame's fixed vocabulary
|
|
35
|
+
(`ops.DEFAULT_NA_TOKENS`), never from free-form data. Category values are sent only
|
|
36
|
+
under `sample`. An answer that *changes* data needs `p ≥ max(mode threshold,
|
|
37
|
+
decision_threshold)`; below that it is a recorded suggestion, never executed.
|
|
38
|
+
Questions must be built from sorted distinct values so row order cannot change them
|
|
39
|
+
(`tests/test_decisions.py` checks this).
|
|
40
|
+
|
|
30
41
|
3. **Recipes round-trip losslessly and idempotently.** `Recipe.from_yaml(r.to_yaml())`
|
|
31
42
|
must equal `r`, and applying `to_yaml` twice must be byte-identical. If you add
|
|
32
43
|
an op with parameters, you must add a `coerce`/`compact` pair such that
|
|
@@ -47,7 +58,9 @@ than no change at all. The rules below exist to protect it.
|
|
|
47
58
|
1.5 g?) or malformed for the declared convention (`2,5` under `thousands=","`), the
|
|
48
59
|
result is a counted, warned null, never a plausible-looking number. Wrong numbers
|
|
49
60
|
are worse than missing ones. Likewise a clustering/merge rule must never fold together
|
|
50
|
-
values that differ in sign, decimal or grouping (`-300`/`300`)
|
|
61
|
+
values that differ in sign, decimal or grouping (`-300`/`300`), and a *typo* merge must
|
|
62
|
+
be a typo: one edit apart, so look-alike words such as `Austria`/`Australia` or
|
|
63
|
+
`Hypothyroidism`/`Hyperthyroidism` stay apart.
|
|
51
64
|
|
|
52
65
|
5. **Every changed cell is tracked.** The executor assigns a stable row id and
|
|
53
66
|
tracks column lineage so `CellDiff` can attribute every change. If you add a
|
|
@@ -92,6 +105,9 @@ DataFrame ─▶ profile.py ─▶ detectors/ ─▶ planner.py ─▶ Recipe (r
|
|
|
92
105
|
unknown check name at load time. It lives apart from `validate.py` so `recipe.py`
|
|
93
106
|
can use the guard without an import cycle.
|
|
94
107
|
- **`llm.py`** — optional; writes a recipe from metadata only.
|
|
108
|
+
- **`decisions.py`** — optional; asks a decision model (Jev, an open System One model,
|
|
109
|
+
or an LLM via an adapter) the judgment calls rules leave open, and turns confident
|
|
110
|
+
answers into ordinary proposals for the planner.
|
|
95
111
|
|
|
96
112
|
The dependency direction is strictly downward (detectors import ops, planner
|
|
97
113
|
imports detectors, etc.). Don't introduce an upward import — it will create a
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cleanframe-engine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: The reproducible data-cleaning engine for Python. AI writes the recipe once; the recipe runs forever — deterministic, diffable, reviewable.
|
|
5
5
|
Project-URL: Homepage, https://github.com/inboxpraveen/Cleanframe
|
|
6
6
|
Project-URL: Repository, https://github.com/inboxpraveen/Cleanframe
|
|
7
7
|
Project-URL: Documentation, https://github.com/inboxpraveen/Cleanframe/wiki
|
|
8
8
|
Project-URL: Issues, https://github.com/inboxpraveen/Cleanframe/issues
|
|
9
9
|
Project-URL: Changelog, https://github.com/inboxpraveen/Cleanframe/blob/main/CHANGELOG.md
|
|
10
|
-
|
|
10
|
+
Project-URL: Playground, https://inboxpraveen.github.io/Cleanframe/
|
|
11
|
+
Project-URL: Funding, https://github.com/sponsors/inboxpraveen
|
|
12
|
+
Author: Praveen Kumar
|
|
13
|
+
Maintainer: Praveen Kumar
|
|
11
14
|
License-Expression: Apache-2.0
|
|
12
15
|
License-File: LICENSE
|
|
13
|
-
Keywords: data-cleaning,data-quality,data-validation,etl,pandas,reproducible,schema-drift
|
|
16
|
+
Keywords: ai-agents,csv,data-cleaning,data-quality,data-validation,decision-models,etl,excel,llm,mcp,pandas,reproducible,schema-drift
|
|
14
17
|
Classifier: Development Status :: 4 - Beta
|
|
15
18
|
Classifier: Intended Audience :: Developers
|
|
16
19
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -30,6 +33,7 @@ Requires-Dist: pyyaml>=6.0
|
|
|
30
33
|
Provides-Extra: all
|
|
31
34
|
Requires-Dist: anthropic>=0.30; extra == 'all'
|
|
32
35
|
Requires-Dist: charset-normalizer>=3; extra == 'all'
|
|
36
|
+
Requires-Dist: mcp<3,>=2.3; extra == 'all'
|
|
33
37
|
Requires-Dist: openai>=1.0; extra == 'all'
|
|
34
38
|
Requires-Dist: openpyxl>=3.0; extra == 'all'
|
|
35
39
|
Requires-Dist: pyarrow>=12.0; extra == 'all'
|
|
@@ -37,6 +41,7 @@ Provides-Extra: detect
|
|
|
37
41
|
Requires-Dist: charset-normalizer>=3; extra == 'detect'
|
|
38
42
|
Provides-Extra: dev
|
|
39
43
|
Requires-Dist: hypothesis>=6.80; extra == 'dev'
|
|
44
|
+
Requires-Dist: mcp<3,>=2.3; extra == 'dev'
|
|
40
45
|
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
41
46
|
Requires-Dist: openpyxl>=3.0; extra == 'dev'
|
|
42
47
|
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
@@ -48,23 +53,33 @@ Requires-Dist: openpyxl>=3.0; extra == 'excel'
|
|
|
48
53
|
Provides-Extra: llm
|
|
49
54
|
Requires-Dist: anthropic>=0.30; extra == 'llm'
|
|
50
55
|
Requires-Dist: openai>=1.0; extra == 'llm'
|
|
56
|
+
Provides-Extra: mcp
|
|
57
|
+
Requires-Dist: mcp<3,>=2.3; extra == 'mcp'
|
|
51
58
|
Provides-Extra: parquet
|
|
52
59
|
Requires-Dist: pyarrow>=12.0; extra == 'parquet'
|
|
53
60
|
Description-Content-Type: text/markdown
|
|
54
61
|
|
|
55
62
|
<p align="center">
|
|
56
|
-
<img src="https://raw.githubusercontent.com/inboxpraveen/Cleanframe/main/docs/assets/Logo.
|
|
63
|
+
<img src="https://raw.githubusercontent.com/inboxpraveen/Cleanframe/main/docs/assets/Logo.svg" alt="CleanFrame logo" width="120" />
|
|
57
64
|
</p>
|
|
58
65
|
|
|
59
66
|
<h1 align="center">CleanFrame</h1>
|
|
60
67
|
|
|
61
68
|
<p align="center">
|
|
62
69
|
<b>The reproducible data-cleaning engine for Python.</b><br/>
|
|
63
|
-
AI
|
|
70
|
+
AI decides once. The recipe runs forever — deterministic, diffable, reviewable.
|
|
64
71
|
</p>
|
|
65
72
|
|
|
66
73
|
<p align="center">
|
|
67
|
-
<
|
|
74
|
+
<a href="https://inboxpraveen.github.io/Cleanframe/"><b>▶ Try it in your browser</b></a> (nothing to install, your file never leaves the tab)
|
|
75
|
+
· <code>pip install cleanframe-engine && cleanframe demo</code>
|
|
76
|
+
</p>
|
|
77
|
+
|
|
78
|
+
<p align="center">
|
|
79
|
+
<picture>
|
|
80
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/inboxpraveen/Cleanframe/main/docs/assets/Hero-dark.png">
|
|
81
|
+
<img src="https://raw.githubusercontent.com/inboxpraveen/Cleanframe/main/docs/assets/Hero.png" width="100%" alt="Real output of cleanframe demo: a messy customers CSV becomes clean data through a reviewable YAML recipe. Changed cells are highlighted, a row with an invalid email is held in quarantine, and next month's renamed column stops the replay."/>
|
|
82
|
+
</picture>
|
|
68
83
|
</p>
|
|
69
84
|
|
|
70
85
|
<p align="center">
|
|
@@ -74,6 +89,8 @@ Description-Content-Type: text/markdown
|
|
|
74
89
|
<img src="https://img.shields.io/badge/python-3.10%2B-blue" alt="Python"/>
|
|
75
90
|
<img src="https://img.shields.io/badge/LLM-optional-success" alt="LLM optional"/>
|
|
76
91
|
<a href="https://github.com/inboxpraveen/Cleanframe/wiki"><img src="https://img.shields.io/badge/docs-wiki-informational" alt="Wiki"/></a>
|
|
92
|
+
<a href="https://inboxpraveen.github.io/Cleanframe/"><img src="https://img.shields.io/badge/playground-in_browser-ff6f00" alt="Playground"/></a>
|
|
93
|
+
<a href="https://github.com/inboxpraveen/Cleanframe/blob/main/docs/decisions.md"><img src="https://img.shields.io/badge/decision_models-Jev--style-8a2be2" alt="Decision models"/></a>
|
|
77
94
|
</p>
|
|
78
95
|
|
|
79
96
|
---
|
|
@@ -108,26 +125,31 @@ The plan compiles to deterministic pandas operations. Same input → same output
|
|
|
108
125
|
|
|
109
126
|
## 30-second demo
|
|
110
127
|
|
|
111
|
-
No API key
|
|
128
|
+
No API key, no sample files to download. The demo data ships inside the package:
|
|
112
129
|
|
|
113
130
|
```bash
|
|
114
131
|
pip install cleanframe-engine
|
|
115
|
-
cleanframe
|
|
132
|
+
cleanframe demo
|
|
116
133
|
```
|
|
117
134
|
|
|
118
|
-
|
|
135
|
+
It profiles a messy customer file (HTML report + quality score), cleans it into a recipe, then replays
|
|
136
|
+
that recipe on *next month's* file. That file has a renamed column and a new date format, so the replay
|
|
137
|
+
**stops** and tells you why. Finally the demo drafts the patch that makes it replay cleanly. Every step
|
|
138
|
+
prints the command to run on your own data.
|
|
139
|
+
|
|
140
|
+
The same thing in Python:
|
|
119
141
|
|
|
120
142
|
```python
|
|
121
143
|
import pandas as pd
|
|
122
144
|
import cleanframe as cf
|
|
123
145
|
|
|
124
|
-
df = pd.read_csv("
|
|
146
|
+
df = pd.read_csv("cleanframe-demo/messy_customers.csv") # written by `cleanframe demo`
|
|
125
147
|
|
|
126
148
|
result = cf.clean(
|
|
127
149
|
df,
|
|
128
|
-
target_schema="
|
|
129
|
-
|
|
130
|
-
|
|
150
|
+
target_schema="cleanframe-demo/customer.schema.yaml", # or: cf.infer_schema(df)
|
|
151
|
+
mode="review", # review | auto | strict
|
|
152
|
+
# decide="typesafe/jev-latest", # optional decision model for judgment calls (TYPESAFE_API_KEY)
|
|
131
153
|
)
|
|
132
154
|
|
|
133
155
|
result.diff.show() # cell-level before/after, git-diff style
|
|
@@ -157,6 +179,32 @@ If the new file's schema drifted (a renamed column, a new currency format), Clea
|
|
|
157
179
|
Run `cleanframe suggest new_customers.csv --recipe <recipe.yaml> --update` to review a patch.
|
|
158
180
|
```
|
|
159
181
|
|
|
182
|
+
## Decision models: AI makes the judgment calls, under a 0.9 bar
|
|
183
|
+
|
|
184
|
+
Some cleaning steps are judgment calls, not parsing. Is `BLR` another name for `Bangalore`? Is `Banglore`
|
|
185
|
+
a typo, or a different place? Does `none` in an *allergies* column mean "missing" or "no allergies"? Which
|
|
186
|
+
column is the schema's `amount_inr`? Rules can't settle these safely. With `decide=`, a **decision model**
|
|
187
|
+
answers each one as a typed question (pick an option, or yes/no) with a probability. That can be
|
|
188
|
+
[Jev](https://typesafe.ai), an open "System One" model, or any LLM through an adapter.
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
cleanframe clean vendor.csv --decide typesafe/jev-latest --llm-exposure sample
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- **An answer changes data only at p ≥ 0.9** (`decision_threshold`). Below that it is recorded as a
|
|
195
|
+
suggestion in the recipe and never executed. A confident "no, these differ" vetoes a typo merge the
|
|
196
|
+
rules would have made.
|
|
197
|
+
- **Asked once, at planning time.** Accepted answers become ordinary recipe ops (`normalize_values`,
|
|
198
|
+
`to_na`, `rename_to`), and every answer is logged in `recipe.meta.decisions`. Replay makes zero AI calls.
|
|
199
|
+
- **Privacy follows `llm_exposure`.** Category values are sent only under `sample`, and only for columns
|
|
200
|
+
whose values repeat. Dates and numbers are never asked: they stay with the deterministic parsers.
|
|
201
|
+
- Backends: `typesafe/…`, `openrouter/typesafe/…`, self-hosted `systemone/…`, offline `rules`, or any
|
|
202
|
+
`--llm` spec. No SDK is needed.
|
|
203
|
+
|
|
204
|
+
How good are decision models at this? **[CleanBench](https://github.com/inboxpraveen/Cleanframe/blob/main/bench/README.md)** measures it on hand-labelled messy
|
|
205
|
+
columns full of traps, scoring *safe-apply precision*: of the answers that would change your data, how
|
|
206
|
+
many are right. See [Decision models](https://github.com/inboxpraveen/Cleanframe/blob/main/docs/decisions.md).
|
|
207
|
+
|
|
160
208
|
## Bring your own API key (or no key at all)
|
|
161
209
|
|
|
162
210
|
CleanFrame is LLM-optional and provider-agnostic.
|
|
@@ -167,6 +215,7 @@ CleanFrame is LLM-optional and provider-agnostic.
|
|
|
167
215
|
| **Metadata** | LLM sees column names, dtypes, and *value patterns* (regex sketches) — never raw values | Only metadata |
|
|
168
216
|
| **Sample** | LLM sees a small shuffled sample of *values*. Emails, phones and long strings are redacted; other short values (names, cities) are sent verbatim. Passing `llm_exposure="sample"` is the opt-in — there is no separate approval step | Sample values, per the redaction above |
|
|
169
217
|
| **Replay** | Saved recipes | Never — recipes need no LLM |
|
|
218
|
+
| **Decisions** (`decide=`) | Typed questions to a decision model while planning; same exposure rules | Metadata only by default; distinct category values under `sample` |
|
|
170
219
|
|
|
171
220
|
- Keys come from environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`,
|
|
172
221
|
`OPENROUTER_API_KEY`, `GROQ_API_KEY`, …). CleanFrame never stores or logs them, and
|
|
@@ -190,7 +239,7 @@ CleanFrame is LLM-optional and provider-agnostic.
|
|
|
190
239
|
| Column name chaos | `Cust Name`, `customer_name`, `CustomerName` → `customer_name` |
|
|
191
240
|
| Date formats | `12/01/24`, `1 Jan 2024`, `2024-01-01` → ISO dates |
|
|
192
241
|
| Currency & numbers | `₹1,20,000`, `$1,200`, `-$5`, `(1,200)`, `1.234,56`, `1200 INR` → typed floats (+ a currency column); a value whose grouping is invalid or ambiguous becomes a *counted, warned* null, never a guess |
|
|
193
|
-
| Category variants | `Bengaluru` / `bengaluru ` / `BENGALURU` and typos (`Banglore
|
|
242
|
+
| Category variants | `Bengaluru` / `bengaluru ` / `BENGALURU` and typos (`Banglore`, one edit away) → one canonical value; look-alikes such as `Austria`/`Australia` stay apart; abbreviations such as `BLR` need a seed map or a [decision model](https://github.com/inboxpraveen/Cleanframe/blob/main/docs/decisions.md) |
|
|
194
243
|
| Emails & phones | validation, normalization, `00`/`+` international prefixes, optional default country code |
|
|
195
244
|
| Duplicates | exact duplicates are dropped (tracked in the diff); fuzzy near-duplicates and key collisions are *reported* with keep/drop proposals for you to act on |
|
|
196
245
|
| Missing values | detected and explained; strategies proposed, never silently applied |
|
|
@@ -218,9 +267,44 @@ CleanFrame is built for pipelines, not just demos:
|
|
|
218
267
|
- **Strict recipes** — unknown ops/params, duplicate keys and bad regexes fail at *load*; plain YAML keys like `yes`, `010` or `12:30` stay column names
|
|
219
268
|
- **Generated code = executor** — exported pandas embeds the executor's own parsers, and property-based tests check they agree
|
|
220
269
|
- **Plugins that travel** — custom ops/detectors load from entry points, `--plugin` or `CLEANFRAME_PLUGINS`, and can opt in to code export and streaming
|
|
270
|
+
- **Gated AI decisions** — a decision model's answer changes data only at p ≥ 0.9, every answer is logged in the recipe, and replay never calls the model
|
|
271
|
+
- **pandas 1.5 → 3.x** — CI runs the minimum pins, pandas 2 and pandas 3
|
|
221
272
|
|
|
222
273
|
See the [Production Guide](https://github.com/inboxpraveen/Cleanframe/wiki/Production-Guide).
|
|
223
274
|
|
|
275
|
+
## Data CI on GitHub
|
|
276
|
+
|
|
277
|
+
Fail a pull request when a data file drifts from its committed recipe:
|
|
278
|
+
|
|
279
|
+
```yaml
|
|
280
|
+
- uses: actions/checkout@v7
|
|
281
|
+
- uses: inboxpraveen/Cleanframe/action@v0.5.1
|
|
282
|
+
with:
|
|
283
|
+
files: data/incoming/*.csv
|
|
284
|
+
recipe: recipes/customer.recipe.yaml
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Every file is replayed, the job summary gets a per-file table, and the check fails on schema drift
|
|
288
|
+
(exit 3) or a failing validation (exit 4). See [GitHub Action](https://github.com/inboxpraveen/Cleanframe/blob/main/docs/github-action.md).
|
|
289
|
+
|
|
290
|
+
## Use it from your AI agent
|
|
291
|
+
|
|
292
|
+
Coding agents love to "just fix the CSV" with throwaway pandas that nobody reviews and nothing replays.
|
|
293
|
+
Give them CleanFrame instead, and they write a **recipe**: reviewed in git, replayed deterministically,
|
|
294
|
+
stopped by drift.
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
npx skills add inboxpraveen/Cleanframe # Agent Skill: Claude Code, Codex, Cursor, …
|
|
298
|
+
pip install "cleanframe-engine[mcp]" # MCP server: profile, suggest, apply, drift, explain, export
|
|
299
|
+
claude mcp add cleanframe -- cleanframe-mcp
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
See [AI agents](https://github.com/inboxpraveen/Cleanframe/blob/main/docs/agents.md) for Claude Desktop, Cursor, VS Code and Codex configs.
|
|
303
|
+
Tools: `profile_data`, `suggest_recipe`, `apply_recipe`, `check_drift`, `explain_recipe`, `list_capabilities`,
|
|
304
|
+
`export_code`. There is no LLM inside the server, the input file is never overwritten, and drift stops the replay.
|
|
305
|
+
|
|
306
|
+
<!-- mcp-name: io.github.inboxpraveen/cleanframe -->
|
|
307
|
+
|
|
224
308
|
## The recipe: your durable artifact
|
|
225
309
|
|
|
226
310
|
```yaml
|
|
@@ -266,6 +350,7 @@ not against it.
|
|
|
266
350
|
| Works with zero LLM / offline | ✅ | ❌ | ✅ | ✅ | ✅ | ⚠️ |
|
|
267
351
|
| Cell-level diff & lineage | ✅ | ❌ | ❌ | ❌ | ⚠️ | ⚠️ |
|
|
268
352
|
| Schema-drift alerts on re-import | ✅ | ❌ | ⚠️ | ❌ | ❌ | ✅ ($6k+/yr) |
|
|
353
|
+
| AI judgment calls gated by probability, frozen for replay | ✅ | ❌ | ❌ | ❌ | ❌ | ⚠️ |
|
|
269
354
|
| Free & open source | ✅ Apache-2.0 | ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
270
355
|
|
|
271
356
|
Use PandasAI to *explore*. Use pandera/GX to *guard*. Use **CleanFrame to fix — repeatably.**
|
|
@@ -276,10 +361,11 @@ Use PandasAI to *explore*. Use pandera/GX to *guard*. Use **CleanFrame to fix
|
|
|
276
361
|
CSV / Excel / DataFrame
|
|
277
362
|
│
|
|
278
363
|
▼
|
|
279
|
-
Profiler ─→ Issue Detectors ─→ Planner ──────────→ Recipe (YAML)
|
|
280
|
-
(deterministic) (rules, or │
|
|
281
|
-
|
|
282
|
-
|
|
364
|
+
Profiler ─→ Issue Detectors ─→ [Decisions] ─→ Planner ──────────→ Recipe (YAML)
|
|
365
|
+
(deterministic) (optional: (rules, or │
|
|
366
|
+
typed Qs to LLM-assisted) ▼
|
|
367
|
+
Jev / an LLM, Executor (pure pandas)
|
|
368
|
+
gated p ≥ 0.9)
|
|
283
369
|
│
|
|
284
370
|
▼
|
|
285
371
|
Validator · Cell-diff · HTML report
|
|
@@ -307,7 +393,9 @@ pip install cleanframe-engine
|
|
|
307
393
|
pip install "cleanframe-engine[excel]" # Excel
|
|
308
394
|
pip install "cleanframe-engine[parquet]" # Parquet
|
|
309
395
|
pip install "cleanframe-engine[llm]" # Anthropic + OpenAI SDKs
|
|
396
|
+
pip install "cleanframe-engine[mcp]" # MCP server for AI agents
|
|
310
397
|
pip install "cleanframe-engine[all]"
|
|
398
|
+
# decision models (decide=) need no extra: the System One protocol uses the standard library
|
|
311
399
|
|
|
312
400
|
# straight from git
|
|
313
401
|
pip install "cleanframe-engine @ git+https://github.com/inboxpraveen/Cleanframe"
|
|
@@ -329,7 +417,10 @@ Python 3.10+. The distribution is [`cleanframe-engine`](https://pypi.org/project
|
|
|
329
417
|
- [x] Read-time format auto-correction (encoding + delimiter)
|
|
330
418
|
- [x] Plugin discovery; custom ops that export and stream
|
|
331
419
|
- [x] `--json` run summaries, library logging, property-based invariant tests
|
|
332
|
-
- [
|
|
420
|
+
- [x] Decision models (`decide=`): typed, probability-gated judgment calls frozen into recipes
|
|
421
|
+
- [x] CleanBench: a public benchmark and leaderboard for data-cleaning decisions
|
|
422
|
+
- [x] MCP server + Agent Skill; GitHub Action for data CI; in-browser playground; `cleanframe demo`
|
|
423
|
+
- [ ] Decision questions for more judgment calls (unit families, near-duplicate rows), with per-question calibration from CleanBench
|
|
333
424
|
- [ ] pandera / GX / dbt exporters
|
|
334
425
|
- [ ] Polars backend
|
|
335
426
|
- [ ] Recipe registry for teams
|
|
@@ -340,7 +431,20 @@ The detector plugin system exists so the community owns the long tail of messy-d
|
|
|
340
431
|
|
|
341
432
|
## Sponsoring
|
|
342
433
|
|
|
343
|
-
CleanFrame is free, Apache-2.0, and will stay that way
|
|
434
|
+
CleanFrame is free, Apache-2.0, and will stay that way. No feature is paywalled for a person with a laptop
|
|
435
|
+
and a messy CSV. If it saves your team recurring hours, [sponsorship](https://github.com/sponsors/inboxpraveen)
|
|
436
|
+
funds detector coverage, CleanBench runs on new models, and fast fixes for anything that costs you data.
|
|
437
|
+
|
|
438
|
+
| Tier | What you get |
|
|
439
|
+
|---|---|
|
|
440
|
+
| **$5/mo** | Your name in the sponsors list |
|
|
441
|
+
| **$25/mo** | Your name and a link in this README; issues you open are looked at first |
|
|
442
|
+
| **$100/mo**: retainer, patch on demand | I build the recipe for one recurring file (a vendor export, a settlement report, a CRM dump). You own it and run it; when the format changes, I patch it within 3 business days |
|
|
443
|
+
| **$150/mo**: company sponsor | A short section near the top of this README with your logo, a link and two or three lines you write, plus a mention in the release notes |
|
|
444
|
+
| **$200/mo**: retainer, monthly run | Patch on demand, plus I run the recipe on each new file and send the cleaned output with a cell-level diff, and one custom detector per quarter. Your data stays on my machine, is never shared or used for training, and is deleted after 30 days |
|
|
445
|
+
| **$250 one-time**: recipe build | Send one file. You get a schema and a complete recipe committed to your repo, the cleaned output with a report and a diff of every changed cell, a 30-minute handover call, and 30 days of support |
|
|
446
|
+
|
|
447
|
+
The [Sponsors page](https://github.com/sponsors/inboxpraveen) has the current tiers and terms.
|
|
344
448
|
|
|
345
449
|
---
|
|
346
450
|
|