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.
Files changed (126) hide show
  1. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/.gitignore +3 -0
  2. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/CHANGELOG.md +130 -1
  3. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/CONTRIBUTING.md +17 -1
  4. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/PKG-INFO +124 -20
  5. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/README.md +114 -17
  6. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/_util.py +7 -1
  7. cleanframe_engine-0.5.1/cleanframe/_version.py +1 -0
  8. cleanframe_engine-0.5.1/cleanframe/agent_tools.py +1335 -0
  9. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/api.py +59 -1
  10. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/cli.py +163 -1
  11. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/codegen.py +25 -5
  12. cleanframe_engine-0.5.1/cleanframe/decisions.py +1465 -0
  13. cleanframe_engine-0.5.1/cleanframe/demo.py +124 -0
  14. cleanframe_engine-0.5.1/cleanframe/demo_data/next_month_customers.csv +5 -0
  15. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/categories.py +134 -38
  16. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/currency.py +2 -0
  17. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/dates.py +6 -0
  18. cleanframe_engine-0.5.1/cleanframe/mcp_server.py +193 -0
  19. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/ops.py +73 -5
  20. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/quality.py +10 -3
  21. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/report.py +7 -3
  22. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/result.py +8 -0
  23. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/README.md +7 -0
  24. cleanframe_engine-0.5.1/docs/agents.md +230 -0
  25. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/api-reference.md +15 -1
  26. cleanframe_engine-0.5.1/docs/benchmark.md +58 -0
  27. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/cli.md +40 -5
  28. cleanframe_engine-0.5.1/docs/decisions.md +183 -0
  29. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/detectors-and-ops.md +9 -0
  30. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/getting-started.md +12 -1
  31. cleanframe_engine-0.5.1/docs/github-action.md +194 -0
  32. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/installation.md +8 -2
  33. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/plugins.md +1 -1
  34. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/production.md +3 -0
  35. cleanframe_engine-0.5.1/examples/customer.schema.yaml +31 -0
  36. cleanframe_engine-0.5.1/examples/github-action/data-ci.yml +57 -0
  37. cleanframe_engine-0.5.1/examples/messy_customers.csv +7 -0
  38. cleanframe_engine-0.5.1/examples/next_month_customers.csv +5 -0
  39. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/pyproject.toml +22 -2
  40. cleanframe_engine-0.5.1/tests/test_agent_tools.py +539 -0
  41. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_audit_hardening.py +44 -0
  42. cleanframe_engine-0.5.1/tests/test_bench.py +280 -0
  43. cleanframe_engine-0.5.1/tests/test_decisions.py +534 -0
  44. cleanframe_engine-0.5.1/tests/test_demo.py +59 -0
  45. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_fix_numbers_and_parity.py +16 -1
  46. cleanframe_engine-0.5.1/tests/test_github_action.py +579 -0
  47. cleanframe_engine-0.5.1/tests/test_mcp_server.py +117 -0
  48. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_ops.py +14 -0
  49. cleanframe_engine-0.5.1/tests/test_playground.py +258 -0
  50. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_profile_detectors.py +61 -0
  51. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_properties.py +3 -1
  52. cleanframe_engine-0.4.0/cleanframe/_version.py +0 -1
  53. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/LICENSE +0 -0
  54. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/SECURITY.md +0 -0
  55. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/__init__.py +0 -0
  56. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/__main__.py +0 -0
  57. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/_numparse.py +0 -0
  58. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/_textparse.py +0 -0
  59. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/checks.py +0 -0
  60. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/dataio.py +0 -0
  61. {cleanframe_engine-0.4.0/examples → cleanframe_engine-0.5.1/cleanframe/demo_data}/customer.schema.yaml +0 -0
  62. {cleanframe_engine-0.4.0/examples → cleanframe_engine-0.5.1/cleanframe/demo_data}/messy_customers.csv +0 -0
  63. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/__init__.py +0 -0
  64. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/base.py +0 -0
  65. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/contacts.py +0 -0
  66. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/dedup.py +0 -0
  67. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/nulls.py +0 -0
  68. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/outliers.py +0 -0
  69. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/schema_mapping.py +0 -0
  70. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/text.py +0 -0
  71. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/units.py +0 -0
  72. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/diff.py +0 -0
  73. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/drift.py +0 -0
  74. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/errors.py +0 -0
  75. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/executor.py +0 -0
  76. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/fingerprint.py +0 -0
  77. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/issues.py +0 -0
  78. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/llm.py +0 -0
  79. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/planner.py +0 -0
  80. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/plugins.py +0 -0
  81. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/profile.py +0 -0
  82. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/py.typed +0 -0
  83. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/readfix.py +0 -0
  84. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/recipe.py +0 -0
  85. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/schema.py +0 -0
  86. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/streaming.py +0 -0
  87. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/types.py +0 -0
  88. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/validate.py +0 -0
  89. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/cleanframe/workbook.py +0 -0
  90. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/architecture.md +0 -0
  91. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/concepts.md +0 -0
  92. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/faq.md +0 -0
  93. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/llm.md +0 -0
  94. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/recipe-spec.md +0 -0
  95. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/docs/schema-spec.md +0 -0
  96. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/examples/customer.recipe.yaml +0 -0
  97. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/conftest.py +0 -0
  98. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_api_report_cli.py +0 -0
  99. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_cli_json_logging.py +0 -0
  100. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_cross_platform.py +0 -0
  101. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_docs_in_sync.py +0 -0
  102. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_drift_codegen.py +0 -0
  103. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_executor_diff.py +0 -0
  104. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_fix_llm_trust.py +0 -0
  105. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_fix_reading.py +0 -0
  106. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_fix_streaming.py +0 -0
  107. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_llm_schema.py +0 -0
  108. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_planner.py +0 -0
  109. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_plugins.py +0 -0
  110. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_production_safety.py +0 -0
  111. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_recipe.py +0 -0
  112. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_release_hardening.py +0 -0
  113. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_review_round2.py +0 -0
  114. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_unique_mapping.py +0 -0
  115. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_validate.py +0 -0
  116. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave1_codegen.py +0 -0
  117. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave1_correctness.py +0 -0
  118. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave1_hardening.py +0 -0
  119. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave1_lineage.py +0 -0
  120. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave1_llm.py +0 -0
  121. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave2_scaling.py +0 -0
  122. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave2_vectorization.py +0 -0
  123. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave3_selection.py +0 -0
  124. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave3_workbook.py +0 -0
  125. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave4_readfix.py +0 -0
  126. {cleanframe_engine-0.4.0 → cleanframe_engine-0.5.1}/tests/test_wave5_streaming.py +0 -0
@@ -99,3 +99,6 @@ examples/messy_customers.recipe.yaml
99
99
  examples/messy_customers.schema.yaml
100
100
  !examples/customer.recipe.yaml
101
101
  !examples/customer.schema.yaml
102
+
103
+ # `cleanframe demo` output when run inside the repository
104
+ /cleanframe-demo/
@@ -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.3.1...HEAD
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.4.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
- Author: CleanFrame contributors
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.png" alt="CleanFrame logo" width="180" />
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 writes the cleaning recipe once. The recipe runs forever — deterministic, diffable, reviewable.
70
+ AI decides once. The recipe runs forever — deterministic, diffable, reviewable.
64
71
  </p>
65
72
 
66
73
  <p align="center">
67
- <img src="https://raw.githubusercontent.com/inboxpraveen/Cleanframe/main/docs/assets/Hero.png" width="auto" alt="CleanFrame"/>
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
+ &nbsp;·&nbsp; <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 needed for this:
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 report examples/messy_customers.csv
132
+ cleanframe demo
116
133
  ```
117
134
 
118
- You get an HTML report: detected issues, quality score, column-by-column diagnosis. Then fix it:
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("examples/messy_customers.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="examples/customer.schema.yaml", # or: cf.infer_schema(df)
129
- llm="anthropic/claude-sonnet-4-6", # optional — omit for rules-only
130
- mode="review", # review | auto | strict
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`) → one canonical value; abbreviations such as `BLR` need a seed map or an LLM |
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
- LLM-assisted) ▼
282
- Executor (pure pandas)
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
- - [ ] `MessyData-100` public benchmark + leaderboard
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 for individuals — no feature is paywalled for a person with a laptop and a messy CSV. If it saves your team recurring hours, [sponsorship](https://github.com/sponsors/inboxpraveen) funds detector coverage, the benchmark, and long-term maintenance.
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