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.
Files changed (126) hide show
  1. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/CHANGELOG.md +21 -1
  2. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/PKG-INFO +16 -10
  3. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/README.md +15 -9
  4. cleanframe_engine-0.5.1/cleanframe/_version.py +1 -0
  5. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/cli.py +7 -5
  6. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/demo.py +9 -0
  7. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/report.py +7 -3
  8. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/README.md +2 -0
  9. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/github-action.md +2 -2
  10. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/examples/github-action/data-ci.yml +1 -1
  11. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_demo.py +5 -0
  12. cleanframe_engine-0.5.0/cleanframe/_version.py +0 -1
  13. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/.gitignore +0 -0
  14. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/CONTRIBUTING.md +0 -0
  15. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/LICENSE +0 -0
  16. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/SECURITY.md +0 -0
  17. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/__init__.py +0 -0
  18. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/__main__.py +0 -0
  19. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/_numparse.py +0 -0
  20. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/_textparse.py +0 -0
  21. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/_util.py +0 -0
  22. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/agent_tools.py +0 -0
  23. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/api.py +0 -0
  24. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/checks.py +0 -0
  25. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/codegen.py +0 -0
  26. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/dataio.py +0 -0
  27. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/decisions.py +0 -0
  28. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/demo_data/customer.schema.yaml +0 -0
  29. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/demo_data/messy_customers.csv +0 -0
  30. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/demo_data/next_month_customers.csv +0 -0
  31. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/__init__.py +0 -0
  32. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/base.py +0 -0
  33. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/categories.py +0 -0
  34. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/contacts.py +0 -0
  35. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/currency.py +0 -0
  36. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/dates.py +0 -0
  37. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/dedup.py +0 -0
  38. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/nulls.py +0 -0
  39. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/outliers.py +0 -0
  40. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/schema_mapping.py +0 -0
  41. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/text.py +0 -0
  42. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/detectors/units.py +0 -0
  43. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/diff.py +0 -0
  44. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/drift.py +0 -0
  45. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/errors.py +0 -0
  46. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/executor.py +0 -0
  47. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/fingerprint.py +0 -0
  48. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/issues.py +0 -0
  49. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/llm.py +0 -0
  50. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/mcp_server.py +0 -0
  51. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/ops.py +0 -0
  52. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/planner.py +0 -0
  53. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/plugins.py +0 -0
  54. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/profile.py +0 -0
  55. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/py.typed +0 -0
  56. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/quality.py +0 -0
  57. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/readfix.py +0 -0
  58. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/recipe.py +0 -0
  59. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/result.py +0 -0
  60. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/schema.py +0 -0
  61. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/streaming.py +0 -0
  62. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/types.py +0 -0
  63. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/validate.py +0 -0
  64. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/cleanframe/workbook.py +0 -0
  65. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/agents.md +0 -0
  66. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/api-reference.md +0 -0
  67. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/architecture.md +0 -0
  68. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/benchmark.md +0 -0
  69. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/cli.md +0 -0
  70. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/concepts.md +0 -0
  71. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/decisions.md +0 -0
  72. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/detectors-and-ops.md +0 -0
  73. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/faq.md +0 -0
  74. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/getting-started.md +0 -0
  75. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/installation.md +0 -0
  76. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/llm.md +0 -0
  77. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/plugins.md +0 -0
  78. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/production.md +0 -0
  79. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/recipe-spec.md +0 -0
  80. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/docs/schema-spec.md +0 -0
  81. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/examples/customer.recipe.yaml +0 -0
  82. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/examples/customer.schema.yaml +0 -0
  83. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/examples/messy_customers.csv +0 -0
  84. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/examples/next_month_customers.csv +0 -0
  85. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/pyproject.toml +0 -0
  86. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/conftest.py +0 -0
  87. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_agent_tools.py +0 -0
  88. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_api_report_cli.py +0 -0
  89. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_audit_hardening.py +0 -0
  90. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_bench.py +0 -0
  91. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_cli_json_logging.py +0 -0
  92. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_cross_platform.py +0 -0
  93. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_decisions.py +0 -0
  94. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_docs_in_sync.py +0 -0
  95. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_drift_codegen.py +0 -0
  96. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_executor_diff.py +0 -0
  97. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_fix_llm_trust.py +0 -0
  98. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_fix_numbers_and_parity.py +0 -0
  99. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_fix_reading.py +0 -0
  100. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_fix_streaming.py +0 -0
  101. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_github_action.py +0 -0
  102. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_llm_schema.py +0 -0
  103. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_mcp_server.py +0 -0
  104. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_ops.py +0 -0
  105. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_planner.py +0 -0
  106. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_playground.py +0 -0
  107. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_plugins.py +0 -0
  108. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_production_safety.py +0 -0
  109. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_profile_detectors.py +0 -0
  110. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_properties.py +0 -0
  111. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_recipe.py +0 -0
  112. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_release_hardening.py +0 -0
  113. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_review_round2.py +0 -0
  114. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_unique_mapping.py +0 -0
  115. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_validate.py +0 -0
  116. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave1_codegen.py +0 -0
  117. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave1_correctness.py +0 -0
  118. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave1_hardening.py +0 -0
  119. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave1_lineage.py +0 -0
  120. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave1_llm.py +0 -0
  121. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave2_scaling.py +0 -0
  122. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave2_vectorization.py +0 -0
  123. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave3_selection.py +0 -0
  124. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave3_workbook.py +0 -0
  125. {cleanframe_engine-0.5.0 → cleanframe_engine-0.5.1}/tests/test_wave4_readfix.py +0 -0
  126. {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.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
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.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
@@ -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.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" />
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
- <img src="https://raw.githubusercontent.com/inboxpraveen/Cleanframe/main/docs/assets/Hero.png" width="auto" alt="CleanFrame"/>
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.0
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 it funds, and what you get |
438
+ | Tier | What you get |
436
439
  |---|---|
437
- | **Supporter** ($5/mo) | Your name in the release notes |
438
- | **Cleaner** ($25/mo) | Your detector request jumps the queue |
439
- | **Team** ($100/mo) | Logo here, priority bug triage, a vote on the roadmap |
440
- | **Production** ($500/mo) | A quarterly recipe/pipeline review call and private CleanBench runs on your anonymised data |
441
- | **One-time** | A regional detector pack (India GST/PAN/lakh–crore, EU IBAN/VAT, …) built in the open, credited to you |
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.png" alt="CleanFrame logo" width="180" />
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
- <img src="https://raw.githubusercontent.com/inboxpraveen/Cleanframe/main/docs/assets/Hero.png" width="auto" alt="CleanFrame"/>
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.0
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 it funds, and what you get |
377
+ | Tier | What you get |
375
378
  |---|---|
376
- | **Supporter** ($5/mo) | Your name in the release notes |
377
- | **Cleaner** ($25/mo) | Your detector request jumps the queue |
378
- | **Team** ($100/mo) | Logo here, priority bug triage, a vote on the roadmap |
379
- | **Production** ($500/mo) | A quarterly recipe/pipeline review call and private CleanBench runs on your anonymised data |
380
- | **One-time** | A regional detector pack (India GST/PAN/lakh–crore, EU IBAN/VAT, …) built in the open, credited to you |
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
- _emit(
352
- f" {s.get('changed_cells', 0)} cell(s) changed in {s.get('changed_columns', 0)} column(s), "
353
- f"{s.get('rows_dropped', 0)} duplicate row(s) dropped, {run.quarantined} row(s) quarantined "
354
- "(held with a reason, never deleted)"
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: baseline; gap: 12px; flex-wrap: wrap;
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 }}
@@ -1,3 +1,5 @@
1
+ <img src="assets/Logo.svg" alt="CleanFrame logo" width="72" />
2
+
1
3
  # CleanFrame documentation
2
4
 
3
5
  Welcome. This folder is the **in-repo** documentation for CleanFrame.
@@ -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.0
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.0` | CleanFrame **from the same checkout** as the action, so action and library are one release |
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
@@ -36,7 +36,7 @@ jobs:
36
36
 
37
37
  - name: Replay committed recipe
38
38
  id: cleanframe
39
- uses: inboxpraveen/Cleanframe/action@v0.5.0
39
+ uses: inboxpraveen/Cleanframe/action@v0.5.1
40
40
  with:
41
41
  files: | # <- adjust: one path or glob per line
42
42
  data/incoming/*.csv
@@ -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"