cleanframe-engine 0.5.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.5.0 → cleanframe_engine-0.5.1}/CHANGELOG.md +21 -1
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/PKG-INFO +16 -10
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/README.md +15 -9
- cleanframe_engine-0.5.1/cleanframe/_version.py +1 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/cli.py +7 -5
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/demo.py +9 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/report.py +7 -3
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/README.md +2 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/github-action.md +2 -2
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/examples/github-action/data-ci.yml +1 -1
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_demo.py +5 -0
- cleanframe_engine-0.5.0/cleanframe/_version.py +0 -1
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/.gitignore +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/CONTRIBUTING.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/LICENSE +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/SECURITY.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/__init__.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/__main__.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/_numparse.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/_textparse.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/_util.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/agent_tools.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/api.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/checks.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/codegen.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/dataio.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/decisions.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/demo_data/customer.schema.yaml +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/demo_data/messy_customers.csv +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/demo_data/next_month_customers.csv +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/__init__.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/base.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/categories.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/contacts.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/currency.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/dates.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/dedup.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/nulls.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/outliers.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/schema_mapping.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/text.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/units.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/diff.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/drift.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/errors.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/executor.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/fingerprint.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/issues.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/llm.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/mcp_server.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/ops.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/planner.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/plugins.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/profile.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/py.typed +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/quality.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/readfix.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/recipe.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/result.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/schema.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/streaming.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/types.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/validate.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/workbook.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/agents.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/api-reference.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/architecture.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/benchmark.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/cli.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/concepts.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/decisions.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/detectors-and-ops.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/faq.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/getting-started.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/installation.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/llm.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/plugins.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/production.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/recipe-spec.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/schema-spec.md +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/examples/customer.recipe.yaml +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/examples/customer.schema.yaml +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/examples/messy_customers.csv +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/examples/next_month_customers.csv +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/pyproject.toml +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/conftest.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_agent_tools.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_api_report_cli.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_audit_hardening.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_bench.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_cli_json_logging.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_cross_platform.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_decisions.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_docs_in_sync.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_drift_codegen.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_executor_diff.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_fix_llm_trust.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_fix_numbers_and_parity.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_fix_reading.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_fix_streaming.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_github_action.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_llm_schema.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_mcp_server.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_ops.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_planner.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_playground.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_plugins.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_production_safety.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_profile_detectors.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_properties.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_recipe.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_release_hardening.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_review_round2.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_unique_mapping.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_validate.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave1_codegen.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave1_correctness.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave1_hardening.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave1_lineage.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave1_llm.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave2_scaling.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave2_vectorization.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave3_selection.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave3_workbook.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave4_readfix.py +0 -0
- {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave5_streaming.py +0 -0
|
@@ -7,6 +7,23 @@ 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
|
+
|
|
10
27
|
## [0.5.0] — 2026-10-18
|
|
11
28
|
|
|
12
29
|
Decision models, agents, a playground and data CI. CleanFrame can now ask a typed decision model
|
|
@@ -499,7 +516,10 @@ now fail loudly, and a few transforms that quietly corrupted data no longer run.
|
|
|
499
516
|
deterministic executor, validation/quarantine, cell-level diff, schema drift, HTML reports,
|
|
500
517
|
codegen, and CLI.
|
|
501
518
|
|
|
502
|
-
[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
|
|
503
523
|
[0.3.1]: https://github.com/inboxpraveen/Cleanframe/releases/tag/v0.3.1
|
|
504
524
|
[0.3.0]: https://github.com/inboxpraveen/Cleanframe/releases/tag/v0.3.0
|
|
505
525
|
[0.2.0]: https://github.com/inboxpraveen/Cleanframe/commits/main
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cleanframe-engine
|
|
3
|
-
Version: 0.5.
|
|
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
|
|
@@ -60,7 +60,7 @@ Requires-Dist: pyarrow>=12.0; extra == 'parquet'
|
|
|
60
60
|
Description-Content-Type: text/markdown
|
|
61
61
|
|
|
62
62
|
<p align="center">
|
|
63
|
-
<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" />
|
|
64
64
|
</p>
|
|
65
65
|
|
|
66
66
|
<h1 align="center">CleanFrame</h1>
|
|
@@ -76,7 +76,10 @@ Description-Content-Type: text/markdown
|
|
|
76
76
|
</p>
|
|
77
77
|
|
|
78
78
|
<p align="center">
|
|
79
|
-
<
|
|
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>
|
|
80
83
|
</p>
|
|
81
84
|
|
|
82
85
|
<p align="center">
|
|
@@ -275,7 +278,7 @@ Fail a pull request when a data file drifts from its committed recipe:
|
|
|
275
278
|
|
|
276
279
|
```yaml
|
|
277
280
|
- uses: actions/checkout@v7
|
|
278
|
-
- uses: inboxpraveen/Cleanframe/action@v0.5.
|
|
281
|
+
- uses: inboxpraveen/Cleanframe/action@v0.5.1
|
|
279
282
|
with:
|
|
280
283
|
files: data/incoming/*.csv
|
|
281
284
|
recipe: recipes/customer.recipe.yaml
|
|
@@ -432,13 +435,16 @@ CleanFrame is free, Apache-2.0, and will stay that way. No feature is paywalled
|
|
|
432
435
|
and a messy CSV. If it saves your team recurring hours, [sponsorship](https://github.com/sponsors/inboxpraveen)
|
|
433
436
|
funds detector coverage, CleanBench runs on new models, and fast fixes for anything that costs you data.
|
|
434
437
|
|
|
435
|
-
| Tier | What
|
|
438
|
+
| Tier | What you get |
|
|
436
439
|
|---|---|
|
|
437
|
-
|
|
|
438
|
-
|
|
|
439
|
-
|
|
|
440
|
-
|
|
|
441
|
-
|
|
|
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.
|
|
442
448
|
|
|
443
449
|
---
|
|
444
450
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/inboxpraveen/Cleanframe/main/docs/assets/Logo.
|
|
2
|
+
<img src="https://raw.githubusercontent.com/inboxpraveen/Cleanframe/main/docs/assets/Logo.svg" alt="CleanFrame logo" width="120" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">CleanFrame</h1>
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
</p>
|
|
16
16
|
|
|
17
17
|
<p align="center">
|
|
18
|
-
<
|
|
18
|
+
<picture>
|
|
19
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/inboxpraveen/Cleanframe/main/docs/assets/Hero-dark.png">
|
|
20
|
+
<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."/>
|
|
21
|
+
</picture>
|
|
19
22
|
</p>
|
|
20
23
|
|
|
21
24
|
<p align="center">
|
|
@@ -214,7 +217,7 @@ Fail a pull request when a data file drifts from its committed recipe:
|
|
|
214
217
|
|
|
215
218
|
```yaml
|
|
216
219
|
- uses: actions/checkout@v7
|
|
217
|
-
- uses: inboxpraveen/Cleanframe/action@v0.5.
|
|
220
|
+
- uses: inboxpraveen/Cleanframe/action@v0.5.1
|
|
218
221
|
with:
|
|
219
222
|
files: data/incoming/*.csv
|
|
220
223
|
recipe: recipes/customer.recipe.yaml
|
|
@@ -371,13 +374,16 @@ CleanFrame is free, Apache-2.0, and will stay that way. No feature is paywalled
|
|
|
371
374
|
and a messy CSV. If it saves your team recurring hours, [sponsorship](https://github.com/sponsors/inboxpraveen)
|
|
372
375
|
funds detector coverage, CleanBench runs on new models, and fast fixes for anything that costs you data.
|
|
373
376
|
|
|
374
|
-
| Tier | What
|
|
377
|
+
| Tier | What you get |
|
|
375
378
|
|---|---|
|
|
376
|
-
|
|
|
377
|
-
|
|
|
378
|
-
|
|
|
379
|
-
|
|
|
380
|
-
|
|
|
379
|
+
| **$5/mo** | Your name in the sponsors list |
|
|
380
|
+
| **$25/mo** | Your name and a link in this README; issues you open are looked at first |
|
|
381
|
+
| **$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 |
|
|
382
|
+
| **$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 |
|
|
383
|
+
| **$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 |
|
|
384
|
+
| **$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 |
|
|
385
|
+
|
|
386
|
+
The [Sponsors page](https://github.com/sponsors/inboxpraveen) has the current tiers and terms.
|
|
381
387
|
|
|
382
388
|
---
|
|
383
389
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.1"
|
|
@@ -348,11 +348,13 @@ def _cmd_demo(args: argparse.Namespace) -> int:
|
|
|
348
348
|
f"--recipe {run.files['recipe']} --out {run.files['clean']} "
|
|
349
349
|
f"--code {run.files['code']} --report {run.files['diff']}"
|
|
350
350
|
)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
f"{
|
|
354
|
-
|
|
355
|
-
|
|
351
|
+
parts = [f"{s.get('changed_cells', 0)} cell(s) changed in {s.get('changed_columns', 0)} column(s)"]
|
|
352
|
+
if run.duplicates:
|
|
353
|
+
parts.append(f"{run.duplicates} duplicate row(s) dropped")
|
|
354
|
+
if run.dropped:
|
|
355
|
+
parts.append(f"{run.dropped} other row(s) dropped")
|
|
356
|
+
parts.append(f"{run.quarantined} row(s) quarantined (held with a reason, never deleted)")
|
|
357
|
+
_emit(" " + ", ".join(parts))
|
|
356
358
|
_note(diff=s, rows_quarantined=run.quarantined)
|
|
357
359
|
for key in ("recipe", "clean", "code", "diff"):
|
|
358
360
|
_emit(f" ✓ {run.files[key]}")
|
|
@@ -42,6 +42,10 @@ class DemoRun:
|
|
|
42
42
|
issues: list = field(default_factory=list)
|
|
43
43
|
clean_summary: dict = field(default_factory=dict)
|
|
44
44
|
quarantined: int = 0
|
|
45
|
+
#: Exact duplicate rows the recipe dropped (quarantined rows are not counted here).
|
|
46
|
+
duplicates: int = 0
|
|
47
|
+
#: Rows dropped for any other reason: a ``drop`` validation rule or a plugin frame op.
|
|
48
|
+
dropped: int = 0
|
|
45
49
|
drift_text: str = ""
|
|
46
50
|
patch_notes: list[str] = field(default_factory=list)
|
|
47
51
|
replay_rows: int = 0
|
|
@@ -98,6 +102,11 @@ def run_demo(directory: str | Path, *, overwrite: bool = False) -> DemoRun:
|
|
|
98
102
|
result.report(run.files["diff"])
|
|
99
103
|
run.clean_summary = result.diff.summary()
|
|
100
104
|
run.quarantined = int(len(result.quarantine))
|
|
105
|
+
# diff.dropped_rows lists every row missing from the clean output, the
|
|
106
|
+
# quarantined ones included, so split it before naming any count.
|
|
107
|
+
removed = result.diff.dropped_rows
|
|
108
|
+
run.duplicates = sum(1 for _, reason in removed if reason == "dedup")
|
|
109
|
+
run.dropped = len(removed) - run.quarantined - run.duplicates
|
|
101
110
|
|
|
102
111
|
next_month = run.files["next_month_customers.csv"]
|
|
103
112
|
try:
|
|
@@ -54,9 +54,13 @@ body {
|
|
|
54
54
|
font: 15px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
55
55
|
}
|
|
56
56
|
.wrap { max-width: 1040px; margin: 0 auto; padding: 32px 20px 80px; }
|
|
57
|
-
header.masthead { display: flex; align-items:
|
|
57
|
+
header.masthead { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
|
|
58
58
|
border-bottom: 1px solid var(--border); padding-bottom: 16px; margin-bottom: 24px; }
|
|
59
|
-
header.masthead h1 { font-size: 22px; margin: 0; letter-spacing: -0.02em;
|
|
59
|
+
header.masthead h1 { font-size: 22px; margin: 0; letter-spacing: -0.02em;
|
|
60
|
+
display: flex; align-items: center; gap: 10px; }
|
|
61
|
+
header.masthead .mark { width: 26px; height: 26px; flex: none; }
|
|
62
|
+
.mark .c { fill: #06b6d4; } .mark .s { fill: #5a6780; }
|
|
63
|
+
@media (prefers-color-scheme: dark) { .mark .c { fill: #22d3ee; } .mark .s { fill: #64748b; } }
|
|
60
64
|
header.masthead .sub { color: var(--muted); font-size: 14px; }
|
|
61
65
|
.grid { display: grid; gap: 16px; }
|
|
62
66
|
.stats { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); margin-bottom: 28px; }
|
|
@@ -109,7 +113,7 @@ _BASE = """<!doctype html>
|
|
|
109
113
|
</head>
|
|
110
114
|
<body><div class="wrap">
|
|
111
115
|
<header class="masthead">
|
|
112
|
-
<h1>CleanFrame</h1>
|
|
116
|
+
<h1><svg class="mark" viewBox="0 0 64 64" aria-hidden="true"><defs><linearGradient id="cf-mark-frame" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#06b6d4"/><stop offset=".5" stop-color="#3b82f6"/><stop offset="1" stop-color="#8b5cf6"/></linearGradient></defs><path d="M5 21V5h16M43 5h16v16M59 43v16H43M21 59H5V43" fill="none" stroke="url(#cf-mark-frame)" stroke-width="6"/><g class="c"><rect x="15" y="15" width="10" height="10"/><rect x="39" y="15" width="10" height="10"/><rect x="27" y="27" width="10" height="10"/><rect x="15" y="39" width="10" height="10"/><rect x="39" y="39" width="10" height="10"/></g><g class="s"><rect x="27" y="15" width="10" height="10"/><rect x="15" y="27" width="10" height="10"/><rect x="39" y="27" width="10" height="10"/><rect x="27" y="39" width="10" height="10"/></g></svg>CleanFrame</h1>
|
|
113
117
|
<span class="sub">{{ subtitle }}</span>
|
|
114
118
|
</header>
|
|
115
119
|
{{ body|safe }}
|
|
@@ -6,7 +6,7 @@ drifted from its recipe or a validation fails, and writes a per-file table to th
|
|
|
6
6
|
summary.
|
|
7
7
|
|
|
8
8
|
```yaml
|
|
9
|
-
- uses: inboxpraveen/Cleanframe/action@v0.5.
|
|
9
|
+
- uses: inboxpraveen/Cleanframe/action@v0.5.1
|
|
10
10
|
with:
|
|
11
11
|
files: data/incoming/*.csv
|
|
12
12
|
recipe: recipes/customer.recipe.yaml
|
|
@@ -151,7 +151,7 @@ Full YAML for each setup: [`action/README.md`](../action/README.md#recipes).
|
|
|
151
151
|
|
|
152
152
|
| `uses:` | `cleanframe-version` empty installs |
|
|
153
153
|
|---------|-------------------------------------|
|
|
154
|
-
| `inboxpraveen/Cleanframe/action@v0.5.
|
|
154
|
+
| `inboxpraveen/Cleanframe/action@v0.5.1` | CleanFrame **from the same checkout** as the action, so action and library are one release |
|
|
155
155
|
| `inboxpraveen/cleanframe-action@v1` (Marketplace) | The CleanFrame release pinned by that action release |
|
|
156
156
|
|
|
157
157
|
For a fully reproducible run, pin a tag or commit SHA in `uses:` and set
|
|
@@ -33,6 +33,9 @@ def test_demo_runs_report_clean_drift_and_patch(tmp_path):
|
|
|
33
33
|
assert Path(run.files[key]).is_file(), key
|
|
34
34
|
assert run.quality is not None and 0 <= run.quality.score <= 100
|
|
35
35
|
assert run.quarantined == 1 # the one invalid email is held back, not deleted
|
|
36
|
+
# the quarantined row is the only one missing from the output: it is no duplicate
|
|
37
|
+
assert (run.duplicates, run.dropped) == (0, 0)
|
|
38
|
+
assert run.clean_summary["rows_dropped"] == run.quarantined
|
|
36
39
|
assert "Amt (INR)" in run.drift_text
|
|
37
40
|
assert any("Amt (INR)" in note for note in run.patch_notes)
|
|
38
41
|
assert run.replay_rows == 4
|
|
@@ -52,3 +55,5 @@ def test_demo_cli_json_summary(tmp_path):
|
|
|
52
55
|
assert summary["status"] == "ok" and summary["command"] == "demo"
|
|
53
56
|
assert Path(summary["outputs"]["recipe"]).is_file()
|
|
54
57
|
assert "Schema drift detected" in out.stderr
|
|
58
|
+
assert "1 row(s) quarantined" in out.stderr
|
|
59
|
+
assert "duplicate row(s) dropped" not in out.stderr
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.5.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/demo_data/customer.schema.yaml
RENAMED
|
File without changes
|
{cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/demo_data/messy_customers.csv
RENAMED
|
File without changes
|
{cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/demo_data/next_month_customers.csv
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|