SqueakyCleanText 0.6.1__tar.gz → 0.7.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 (60) hide show
  1. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/PKG-INFO +148 -8
  2. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/README.md +140 -2
  3. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/SqueakyCleanText.egg-info/PKG-INFO +148 -8
  4. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/SqueakyCleanText.egg-info/SOURCES.txt +28 -1
  5. squeakycleantext-0.7.1/SqueakyCleanText.egg-info/entry_points.txt +3 -0
  6. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/SqueakyCleanText.egg-info/requires.txt +7 -4
  7. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/pyproject.toml +13 -6
  8. squeakycleantext-0.7.1/sct/__init__.py +71 -0
  9. squeakycleantext-0.7.1/sct/adapters.py +224 -0
  10. squeakycleantext-0.7.1/sct/browser.py +174 -0
  11. squeakycleantext-0.7.1/sct/cli.py +101 -0
  12. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/config.py +138 -8
  13. squeakycleantext-0.7.1/sct/evaluation.py +209 -0
  14. squeakycleantext-0.7.1/sct/mcp_server.py +104 -0
  15. squeakycleantext-0.7.1/sct/model_benchmark.py +175 -0
  16. squeakycleantext-0.7.1/sct/model_candidates.py +89 -0
  17. squeakycleantext-0.7.1/sct/ocr.py +123 -0
  18. squeakycleantext-0.7.1/sct/privacy.py +173 -0
  19. squeakycleantext-0.7.1/sct/risk.py +58 -0
  20. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/sct.py +286 -32
  21. squeakycleantext-0.7.1/sct/streaming.py +87 -0
  22. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/anonymization_map.py +4 -2
  23. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/constants.py +63 -6
  24. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/contact.py +36 -15
  25. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/gliclass_adapter.py +10 -8
  26. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/gliner_adapter.py +6 -3
  27. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/ner.py +259 -58
  28. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/onnx_pipeline.py +8 -1
  29. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/process_result.py +3 -0
  30. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/stopwords.py +2 -2
  31. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/torch_pipeline.py +11 -2
  32. squeakycleantext-0.7.1/tests/test_adapters.py +149 -0
  33. squeakycleantext-0.7.1/tests/test_benchmark_release.py +67 -0
  34. squeakycleantext-0.7.1/tests/test_browser.py +37 -0
  35. squeakycleantext-0.7.1/tests/test_chunking.py +170 -0
  36. squeakycleantext-0.7.1/tests/test_cli.py +79 -0
  37. squeakycleantext-0.7.1/tests/test_config_state.py +121 -0
  38. squeakycleantext-0.7.1/tests/test_contracts.py +136 -0
  39. squeakycleantext-0.7.1/tests/test_evaluation.py +129 -0
  40. squeakycleantext-0.7.1/tests/test_mcp_server.py +145 -0
  41. squeakycleantext-0.7.1/tests/test_model_benchmarks.py +119 -0
  42. squeakycleantext-0.7.1/tests/test_ocr.py +87 -0
  43. squeakycleantext-0.7.1/tests/test_privacy.py +401 -0
  44. squeakycleantext-0.7.1/tests/test_regex_boundaries.py +334 -0
  45. squeakycleantext-0.7.1/tests/test_risk.py +21 -0
  46. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/tests/test_sct.py +3 -2
  47. squeakycleantext-0.7.1/tests/test_streaming.py +34 -0
  48. squeakycleantext-0.6.1/sct/__init__.py +0 -13
  49. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/LICENSE +0 -0
  50. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/SqueakyCleanText.egg-info/dependency_links.txt +0 -0
  51. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/SqueakyCleanText.egg-info/top_level.txt +0 -0
  52. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/__init__.py +0 -0
  53. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/datetime.py +0 -0
  54. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/normtext.py +0 -0
  55. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/resources.py +0 -0
  56. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/special.py +0 -0
  57. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/sct/utils/synthetic.py +0 -0
  58. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/setup.cfg +0 -0
  59. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/setup.py +0 -0
  60. {squeakycleantext-0.6.1 → squeakycleantext-0.7.1}/tests/test_functional.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SqueakyCleanText
3
- Version: 0.6.1
3
+ Version: 0.7.1
4
4
  Summary: Text preprocessing & PII anonymization pipeline for NLP/ML: ONNX NER ensemble, language detection, stopword removal, and configurable token replacement.
5
5
  Author: Rehan Fazal
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/rhnfzl/SqueakyCleanText
8
8
  Project-URL: Repository, https://github.com/rhnfzl/SqueakyCleanText
9
9
  Project-URL: Issues, https://github.com/rhnfzl/SqueakyCleanText/issues
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3
22
22
  Classifier: Programming Language :: Python :: 3.11
23
23
  Classifier: Programming Language :: Python :: 3.12
24
24
  Classifier: Programming Language :: Python :: 3.13
25
- Classifier: License :: OSI Approved :: MIT License
25
+ Classifier: Programming Language :: Python :: 3.14
26
26
  Classifier: Operating System :: OS Independent
27
27
  Requires-Python: >=3.11
28
28
  Description-Content-Type: text/markdown
@@ -32,7 +32,6 @@ Requires-Dist: stop-words>=2025.11.4
32
32
  Requires-Dist: emoji>=2.14
33
33
  Requires-Dist: ftfy>=6.3
34
34
  Requires-Dist: Unidecode>=1.4
35
- Requires-Dist: beautifulsoup4>=4.14
36
35
  Requires-Dist: onnxruntime>=1.24.2
37
36
  Requires-Dist: tokenizers>=0.22.2
38
37
  Requires-Dist: huggingface-hub>=1.5.0
@@ -56,8 +55,6 @@ Provides-Extra: presidio
56
55
  Requires-Dist: presidio-analyzer>=2.2.360; extra == "presidio"
57
56
  Provides-Extra: classify
58
57
  Requires-Dist: gliclass>=0.1.16; extra == "classify"
59
- Provides-Extra: classify-onnx
60
- Requires-Dist: onnxruntime>=1.24.2; extra == "classify-onnx"
61
58
  Provides-Extra: all-ner
62
59
  Requires-Dist: torch>=2.6.0; extra == "all-ner"
63
60
  Requires-Dist: transformers>=4.48; extra == "all-ner"
@@ -73,6 +70,11 @@ Requires-Dist: rapidfuzz>=3.12; extra == "dev"
73
70
  Provides-Extra: test
74
71
  Requires-Dist: coverage>=7.8; extra == "test"
75
72
  Requires-Dist: pytest-cov>=6.0; extra == "test"
73
+ Provides-Extra: mcp
74
+ Requires-Dist: fastmcp>=4.0.5; extra == "mcp"
75
+ Provides-Extra: ocr
76
+ Requires-Dist: pillow>=12.3.0; extra == "ocr"
77
+ Requires-Dist: pytesseract>=0.3.13; extra == "ocr"
76
78
  Dynamic: license-file
77
79
 
78
80
  <div align="center">
@@ -82,7 +84,7 @@ Dynamic: license-file
82
84
  [![PyPI](https://img.shields.io/pypi/v/squeakycleantext.svg)](https://pypi.org/project/squeakycleantext/)
83
85
  [![PyPI - Downloads](https://img.shields.io/pypi/dm/squeakycleantext)](https://pypistats.org/packages/squeakycleantext)
84
86
  [![Python package](https://github.com/rhnfzl/SqueakyCleanText/actions/workflows/python-package.yml/badge.svg)](https://github.com/rhnfzl/SqueakyCleanText/actions/workflows/python-package.yml)
85
- [![Python Versions](https://img.shields.io/badge/Python-3.11%20|%203.12%20|%203.13-blue)](https://pypi.org/project/squeakycleantext/)
87
+ [![Python Versions](https://img.shields.io/badge/Python-3.11%20|%203.12%20|%203.13%20|%203.14-blue)](https://pypi.org/project/squeakycleantext/)
86
88
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
87
89
 
88
90
  A comprehensive text cleaning and preprocessing pipeline for machine learning and NLP tasks.
@@ -179,6 +181,8 @@ The base install uses **ONNX Runtime** for NER inference - no PyTorch or Transfo
179
181
  | Synthetic | `pip install SqueakyCleanText[synthetic]` | Faker-based synthetic replacement (realistic fake values instead of `<TAG>` tokens) |
180
182
  | Presidio | `pip install SqueakyCleanText[presidio]` | Presidio-analyzer for `presidio_gliner` backend |
181
183
  | Classify | `pip install SqueakyCleanText[classify]` | GLiClass document-level pre-classification |
184
+ | MCP | `pip install SqueakyCleanText[mcp]` | FastMCP server with text and JSON cleaning tools |
185
+ | OCR | `pip install SqueakyCleanText[ocr]` | Pillow image redaction and Tesseract OCR adapter |
182
186
  | All NER | `pip install SqueakyCleanText[all-ner]` | All NER backends combined |
183
187
  | Development | `pip install SqueakyCleanText[dev]` | Testing and linting tools |
184
188
 
@@ -304,7 +308,7 @@ lm_text, stat_text, lang = cleaner.process(
304
308
  # Entities are anonymized: names, SSNs, emails, dates of birth, and 50+ more PII types
305
309
  ```
306
310
 
307
- PII mode auto-configures: `ner_backend='gliner'`, uses [`knowledgator/gliner-pii-base-v1.0`](https://huggingface.co/knowledgator/gliner-pii-base-v1.0), sets threshold to 0.3 (recall-focused), and expands positional tags. User-provided values always take priority.
311
+ PII mode auto-configures: `ner_backend='gliner'`, uses [`knowledgator/gliner-pii-base-v1.0`](https://huggingface.co/knowledgator/gliner-pii-base-v1.0), sets `gliner_threshold` and the final `ner_confidence_threshold` to 0.3 (recall-focused), and expands positional tags. User-provided values always take priority.
308
312
 
309
313
  **Alternative PII models** (pass as `gliner_model`):
310
314
 
@@ -588,11 +592,14 @@ new_cfg = dataclasses.replace(cfg, check_ner_process=False)
588
592
  | `check_replace_html` | `True` | Strip/replace HTML tags |
589
593
  | `check_replace_urls` | `True` | Replace URLs with token |
590
594
  | `check_replace_emails` | `True` | Replace emails with token |
595
+ | `check_replace_ips` | `True` | Replace IPv4/IPv6 addresses (runs before dates, phones, years) |
596
+ | `replace_ips_public_only` | `False` | Keep private, loopback and reserved IPs; replace only public ones |
591
597
  | `check_replace_years` | `True` | Replace years (1900-2099) |
592
598
  | `check_replace_dates` | `False` | Replace full dates (ISO 8601, month names) |
593
599
  | `check_fuzzy_replace_dates` | `False` | Fuzzy match misspelled months (requires `[fuzzy]`) |
594
600
  | `check_replace_phone_numbers` | `True` | Replace phone numbers |
595
601
  | `check_replace_numbers` | `True` | Replace standalone numbers |
602
+ | `replace_numbers_in_words` | `False` | Also replace digits inside Latin/Greek/Cyrillic words (`GPT4` to `GPT<NUMBER>`) |
596
603
  | `check_replace_currency_symbols` | `True` | Replace currency symbols |
597
604
  | `check_ner_process` | `True` | Run NER entity recognition |
598
605
  | `check_remove_isolated_letters` | `True` | Remove single letters |
@@ -615,6 +622,7 @@ new_cfg = dataclasses.replace(cfg, check_ner_process=False)
615
622
  | `replace_with_url` | `"<URL>"` |
616
623
  | `replace_with_html` | `"<HTML>"` |
617
624
  | `replace_with_email` | `"<EMAIL>"` |
625
+ | `replace_with_ips` | `"<IP>"` |
618
626
  | `replace_with_years` | `"<YEAR>"` |
619
627
  | `replace_with_dates` | `"<DATE>"` |
620
628
  | `replace_with_phone_numbers` | `"<PHONE>"` |
@@ -653,6 +661,121 @@ new_cfg = dataclasses.replace(cfg, check_ner_process=False)
653
661
 
654
662
  </details>
655
663
 
664
+ ## Privacy Engine
665
+
666
+ Applications can apply versioned policy rules and inspect each decision.
667
+ This makes privacy behavior testable across services, datasets, and model upgrades.
668
+
669
+ ```python
670
+ from sct import EntityRule, PrivacyPolicy, TextCleaner, TextCleanerConfig
671
+
672
+ policy = PrivacyPolicy(
673
+ name="support-export",
674
+ version="1",
675
+ rules={
676
+ "PERSON": EntityRule(action="redact", min_score=0.8),
677
+ "ORGANISATION": EntityRule(action="keep"),
678
+ },
679
+ allowlist=frozenset({"Jane Public"}),
680
+ )
681
+
682
+ cleaner = TextCleaner(
683
+ cfg=TextCleanerConfig(ner_mode="pii"),
684
+ privacy_policy=policy,
685
+ include_entities=True,
686
+ )
687
+ result = cleaner.process("Contact Maria Chen for help.")
688
+
689
+ print(result.metadata["findings"])
690
+ print(result.metadata["detector"])
691
+ ```
692
+
693
+ Finding offsets (`start`, `end`) and NER entries in the reversible map are
694
+ document-relative positions in the post-regex text: the text after the regex
695
+ stages replaced URLs, emails, phones and the like. They are not offsets into the
696
+ raw input.
697
+
698
+ Reversible output is pseudonymized personal data. Anyone with the token map can
699
+ restore the source values. Keep token maps outside logs and cleaned datasets.
700
+
701
+ Use a `TokenStore` implementation to keep maps behind opaque references:
702
+
703
+ ```python
704
+ from sct import InMemoryTokenStore, TextCleaner, TextCleanerConfig
705
+
706
+ store = InMemoryTokenStore()
707
+ cleaner = TextCleaner(
708
+ cfg=TextCleanerConfig(replacement_mode="reversible"),
709
+ token_store=store,
710
+ )
711
+ result = cleaner.process("Maria lives in Utrecht.")
712
+ reference = result.metadata["anon_map_reference"]
713
+ ```
714
+
715
+ `InMemoryTokenStore` supports tests and short sessions. Production systems
716
+ should implement `TokenStore` with encryption, access control, expiry, and audit logs.
717
+
718
+ ### Boundary adapters
719
+
720
+ The base package includes JSON, DataFrame, retrieval, command-line, and streaming adapters:
721
+
722
+ ```python
723
+ from sct import RAGChunk, process_json, process_rag_chunks
724
+
725
+ cleaned_json = process_json(cleaner, {"message": "Email a@example.com"})
726
+ cleaned_chunks = process_rag_chunks(
727
+ cleaner,
728
+ [RAGChunk(id="1", text="Call +1-202-555-0147", metadata={})],
729
+ )
730
+ ```
731
+
732
+ ```sh
733
+ printf 'Email maria@example.com' | sct-clean --no-ner
734
+ printf 'Email maria@example.com' | sct-clean --no-ner \
735
+ --replacement-mode reversible --token-map tokens.json
736
+ pip install 'SqueakyCleanText[mcp]'
737
+ sct-mcp
738
+ ```
739
+
740
+ `BufferedStreamingRedactor` holds a full document for cross-chunk NER safety.
741
+ `WindowedStreamingRedactor` provides bounded latency for contact tokens.
742
+
743
+ The OCR adapter accepts any `OCRProvider`. The included Tesseract provider also
744
+ needs a local Tesseract executable. Browser export writes an ONNX Runtime Web
745
+ manifest, but it does not copy model files or Python language detection.
746
+
747
+ Corpus owners can call `assess_reidentification_risk` to measure k-anonymity
748
+ for selected quasi-identifiers. Reports contain counts only, not source values.
749
+
750
+ ## PII Evaluation
751
+
752
+ Model changes need ground-truth privacy evaluation, not only backend agreement.
753
+ The evaluation harness reports exact entity metrics by label, language, domain,
754
+ and named slice.
755
+
756
+ ```sh
757
+ python -m scripts.evaluate_pii \
758
+ --dataset evaluation/pii_starter.jsonl \
759
+ --predictions path/to/model-predictions.jsonl \
760
+ --model model-name-and-revision \
761
+ --output evaluation/results/model-name.json
762
+ ```
763
+
764
+ The included starter corpus is synthetic and only checks the evaluation path.
765
+ See [`evaluation/README.md`](evaluation/README.md) for schemas and release rules.
766
+
767
+ Pinned candidates live in `sct.model_candidates`. Run one candidate with:
768
+
769
+ ```sh
770
+ python -m scripts.benchmark_models \
771
+ --dataset evaluation/pii_starter.jsonl \
772
+ --candidate gliner2-pii \
773
+ --output evaluation/results/gliner2-pii.json
774
+ ```
775
+
776
+ Published third-party results are reference data only. `assess_candidate`
777
+ requires local recall, exact-span F1, latency, and ONNX parity before adoption.
778
+
656
779
  ## Architecture
657
780
 
658
781
  SqueakyCleanText processes text through a configurable pipeline of sequential steps:
@@ -690,6 +813,23 @@ Each step is toggled by a `TextCleanerConfig` field. The pipeline is built once
690
813
 
691
814
  ## What's New
692
815
 
816
+ **v0.7.0**
817
+ - **Privacy policies**: Apply versioned entity rules, allowlists, thresholds, and redaction actions.
818
+ - **Structured audit findings**: Inspect entity decisions, detector backends, and pinned model revisions.
819
+ - **Safer reversible processing**: Restore contact values and keep token maps behind an optional `TokenStore`.
820
+ - **Boundary adapters**: Clean JSON, DataFrame-like objects, RAG chunks, standard input, and MCP tool requests.
821
+ - **Privacy evaluation**: Score exact PII spans by label, language, domain, and custom slice.
822
+ - **Model adoption gates**: Compare pinned candidates using recall, F1, latency, and ONNX parity.
823
+ - **Extended media support**: Redact OCR regions, process bounded streams, and export browser deployment manifests.
824
+ - **Corpus risk indicators**: Measure k-anonymity for user-selected quasi-identifiers.
825
+ - **Supply-chain improvements**: Pin model revisions, publish through PyPI Trusted Publishing, and ship standard lockfiles.
826
+ - **Python 3.14**: Added CI coverage alongside Python 3.11 through 3.13.
827
+
828
+ Migration notes:
829
+ - `gliclass_onnx=True` now raises an error because the upstream package has no native ONNX loader.
830
+ - HTML processing now honors `replace_with_html` instead of silently stripping tags.
831
+ - Structured adapters reject reversible mode unless each token map can be retained safely.
832
+
693
833
  **v0.6.0**
694
834
  - **PII detection mode** (`ner_mode='pii'`): auto-configures GLiNER with 60+ PII entity labels (personal, financial, healthcare, identity, digital)
695
835
  - **Synthetic replacement** (`replacement_mode='synthetic'`): Faker-generated realistic values instead of `<TAG>` placeholders, with per-document consistency
@@ -5,7 +5,7 @@
5
5
  [![PyPI](https://img.shields.io/pypi/v/squeakycleantext.svg)](https://pypi.org/project/squeakycleantext/)
6
6
  [![PyPI - Downloads](https://img.shields.io/pypi/dm/squeakycleantext)](https://pypistats.org/packages/squeakycleantext)
7
7
  [![Python package](https://github.com/rhnfzl/SqueakyCleanText/actions/workflows/python-package.yml/badge.svg)](https://github.com/rhnfzl/SqueakyCleanText/actions/workflows/python-package.yml)
8
- [![Python Versions](https://img.shields.io/badge/Python-3.11%20|%203.12%20|%203.13-blue)](https://pypi.org/project/squeakycleantext/)
8
+ [![Python Versions](https://img.shields.io/badge/Python-3.11%20|%203.12%20|%203.13%20|%203.14-blue)](https://pypi.org/project/squeakycleantext/)
9
9
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
10
10
 
11
11
  A comprehensive text cleaning and preprocessing pipeline for machine learning and NLP tasks.
@@ -102,6 +102,8 @@ The base install uses **ONNX Runtime** for NER inference - no PyTorch or Transfo
102
102
  | Synthetic | `pip install SqueakyCleanText[synthetic]` | Faker-based synthetic replacement (realistic fake values instead of `<TAG>` tokens) |
103
103
  | Presidio | `pip install SqueakyCleanText[presidio]` | Presidio-analyzer for `presidio_gliner` backend |
104
104
  | Classify | `pip install SqueakyCleanText[classify]` | GLiClass document-level pre-classification |
105
+ | MCP | `pip install SqueakyCleanText[mcp]` | FastMCP server with text and JSON cleaning tools |
106
+ | OCR | `pip install SqueakyCleanText[ocr]` | Pillow image redaction and Tesseract OCR adapter |
105
107
  | All NER | `pip install SqueakyCleanText[all-ner]` | All NER backends combined |
106
108
  | Development | `pip install SqueakyCleanText[dev]` | Testing and linting tools |
107
109
 
@@ -227,7 +229,7 @@ lm_text, stat_text, lang = cleaner.process(
227
229
  # Entities are anonymized: names, SSNs, emails, dates of birth, and 50+ more PII types
228
230
  ```
229
231
 
230
- PII mode auto-configures: `ner_backend='gliner'`, uses [`knowledgator/gliner-pii-base-v1.0`](https://huggingface.co/knowledgator/gliner-pii-base-v1.0), sets threshold to 0.3 (recall-focused), and expands positional tags. User-provided values always take priority.
232
+ PII mode auto-configures: `ner_backend='gliner'`, uses [`knowledgator/gliner-pii-base-v1.0`](https://huggingface.co/knowledgator/gliner-pii-base-v1.0), sets `gliner_threshold` and the final `ner_confidence_threshold` to 0.3 (recall-focused), and expands positional tags. User-provided values always take priority.
231
233
 
232
234
  **Alternative PII models** (pass as `gliner_model`):
233
235
 
@@ -511,11 +513,14 @@ new_cfg = dataclasses.replace(cfg, check_ner_process=False)
511
513
  | `check_replace_html` | `True` | Strip/replace HTML tags |
512
514
  | `check_replace_urls` | `True` | Replace URLs with token |
513
515
  | `check_replace_emails` | `True` | Replace emails with token |
516
+ | `check_replace_ips` | `True` | Replace IPv4/IPv6 addresses (runs before dates, phones, years) |
517
+ | `replace_ips_public_only` | `False` | Keep private, loopback and reserved IPs; replace only public ones |
514
518
  | `check_replace_years` | `True` | Replace years (1900-2099) |
515
519
  | `check_replace_dates` | `False` | Replace full dates (ISO 8601, month names) |
516
520
  | `check_fuzzy_replace_dates` | `False` | Fuzzy match misspelled months (requires `[fuzzy]`) |
517
521
  | `check_replace_phone_numbers` | `True` | Replace phone numbers |
518
522
  | `check_replace_numbers` | `True` | Replace standalone numbers |
523
+ | `replace_numbers_in_words` | `False` | Also replace digits inside Latin/Greek/Cyrillic words (`GPT4` to `GPT<NUMBER>`) |
519
524
  | `check_replace_currency_symbols` | `True` | Replace currency symbols |
520
525
  | `check_ner_process` | `True` | Run NER entity recognition |
521
526
  | `check_remove_isolated_letters` | `True` | Remove single letters |
@@ -538,6 +543,7 @@ new_cfg = dataclasses.replace(cfg, check_ner_process=False)
538
543
  | `replace_with_url` | `"<URL>"` |
539
544
  | `replace_with_html` | `"<HTML>"` |
540
545
  | `replace_with_email` | `"<EMAIL>"` |
546
+ | `replace_with_ips` | `"<IP>"` |
541
547
  | `replace_with_years` | `"<YEAR>"` |
542
548
  | `replace_with_dates` | `"<DATE>"` |
543
549
  | `replace_with_phone_numbers` | `"<PHONE>"` |
@@ -576,6 +582,121 @@ new_cfg = dataclasses.replace(cfg, check_ner_process=False)
576
582
 
577
583
  </details>
578
584
 
585
+ ## Privacy Engine
586
+
587
+ Applications can apply versioned policy rules and inspect each decision.
588
+ This makes privacy behavior testable across services, datasets, and model upgrades.
589
+
590
+ ```python
591
+ from sct import EntityRule, PrivacyPolicy, TextCleaner, TextCleanerConfig
592
+
593
+ policy = PrivacyPolicy(
594
+ name="support-export",
595
+ version="1",
596
+ rules={
597
+ "PERSON": EntityRule(action="redact", min_score=0.8),
598
+ "ORGANISATION": EntityRule(action="keep"),
599
+ },
600
+ allowlist=frozenset({"Jane Public"}),
601
+ )
602
+
603
+ cleaner = TextCleaner(
604
+ cfg=TextCleanerConfig(ner_mode="pii"),
605
+ privacy_policy=policy,
606
+ include_entities=True,
607
+ )
608
+ result = cleaner.process("Contact Maria Chen for help.")
609
+
610
+ print(result.metadata["findings"])
611
+ print(result.metadata["detector"])
612
+ ```
613
+
614
+ Finding offsets (`start`, `end`) and NER entries in the reversible map are
615
+ document-relative positions in the post-regex text: the text after the regex
616
+ stages replaced URLs, emails, phones and the like. They are not offsets into the
617
+ raw input.
618
+
619
+ Reversible output is pseudonymized personal data. Anyone with the token map can
620
+ restore the source values. Keep token maps outside logs and cleaned datasets.
621
+
622
+ Use a `TokenStore` implementation to keep maps behind opaque references:
623
+
624
+ ```python
625
+ from sct import InMemoryTokenStore, TextCleaner, TextCleanerConfig
626
+
627
+ store = InMemoryTokenStore()
628
+ cleaner = TextCleaner(
629
+ cfg=TextCleanerConfig(replacement_mode="reversible"),
630
+ token_store=store,
631
+ )
632
+ result = cleaner.process("Maria lives in Utrecht.")
633
+ reference = result.metadata["anon_map_reference"]
634
+ ```
635
+
636
+ `InMemoryTokenStore` supports tests and short sessions. Production systems
637
+ should implement `TokenStore` with encryption, access control, expiry, and audit logs.
638
+
639
+ ### Boundary adapters
640
+
641
+ The base package includes JSON, DataFrame, retrieval, command-line, and streaming adapters:
642
+
643
+ ```python
644
+ from sct import RAGChunk, process_json, process_rag_chunks
645
+
646
+ cleaned_json = process_json(cleaner, {"message": "Email a@example.com"})
647
+ cleaned_chunks = process_rag_chunks(
648
+ cleaner,
649
+ [RAGChunk(id="1", text="Call +1-202-555-0147", metadata={})],
650
+ )
651
+ ```
652
+
653
+ ```sh
654
+ printf 'Email maria@example.com' | sct-clean --no-ner
655
+ printf 'Email maria@example.com' | sct-clean --no-ner \
656
+ --replacement-mode reversible --token-map tokens.json
657
+ pip install 'SqueakyCleanText[mcp]'
658
+ sct-mcp
659
+ ```
660
+
661
+ `BufferedStreamingRedactor` holds a full document for cross-chunk NER safety.
662
+ `WindowedStreamingRedactor` provides bounded latency for contact tokens.
663
+
664
+ The OCR adapter accepts any `OCRProvider`. The included Tesseract provider also
665
+ needs a local Tesseract executable. Browser export writes an ONNX Runtime Web
666
+ manifest, but it does not copy model files or Python language detection.
667
+
668
+ Corpus owners can call `assess_reidentification_risk` to measure k-anonymity
669
+ for selected quasi-identifiers. Reports contain counts only, not source values.
670
+
671
+ ## PII Evaluation
672
+
673
+ Model changes need ground-truth privacy evaluation, not only backend agreement.
674
+ The evaluation harness reports exact entity metrics by label, language, domain,
675
+ and named slice.
676
+
677
+ ```sh
678
+ python -m scripts.evaluate_pii \
679
+ --dataset evaluation/pii_starter.jsonl \
680
+ --predictions path/to/model-predictions.jsonl \
681
+ --model model-name-and-revision \
682
+ --output evaluation/results/model-name.json
683
+ ```
684
+
685
+ The included starter corpus is synthetic and only checks the evaluation path.
686
+ See [`evaluation/README.md`](evaluation/README.md) for schemas and release rules.
687
+
688
+ Pinned candidates live in `sct.model_candidates`. Run one candidate with:
689
+
690
+ ```sh
691
+ python -m scripts.benchmark_models \
692
+ --dataset evaluation/pii_starter.jsonl \
693
+ --candidate gliner2-pii \
694
+ --output evaluation/results/gliner2-pii.json
695
+ ```
696
+
697
+ Published third-party results are reference data only. `assess_candidate`
698
+ requires local recall, exact-span F1, latency, and ONNX parity before adoption.
699
+
579
700
  ## Architecture
580
701
 
581
702
  SqueakyCleanText processes text through a configurable pipeline of sequential steps:
@@ -613,6 +734,23 @@ Each step is toggled by a `TextCleanerConfig` field. The pipeline is built once
613
734
 
614
735
  ## What's New
615
736
 
737
+ **v0.7.0**
738
+ - **Privacy policies**: Apply versioned entity rules, allowlists, thresholds, and redaction actions.
739
+ - **Structured audit findings**: Inspect entity decisions, detector backends, and pinned model revisions.
740
+ - **Safer reversible processing**: Restore contact values and keep token maps behind an optional `TokenStore`.
741
+ - **Boundary adapters**: Clean JSON, DataFrame-like objects, RAG chunks, standard input, and MCP tool requests.
742
+ - **Privacy evaluation**: Score exact PII spans by label, language, domain, and custom slice.
743
+ - **Model adoption gates**: Compare pinned candidates using recall, F1, latency, and ONNX parity.
744
+ - **Extended media support**: Redact OCR regions, process bounded streams, and export browser deployment manifests.
745
+ - **Corpus risk indicators**: Measure k-anonymity for user-selected quasi-identifiers.
746
+ - **Supply-chain improvements**: Pin model revisions, publish through PyPI Trusted Publishing, and ship standard lockfiles.
747
+ - **Python 3.14**: Added CI coverage alongside Python 3.11 through 3.13.
748
+
749
+ Migration notes:
750
+ - `gliclass_onnx=True` now raises an error because the upstream package has no native ONNX loader.
751
+ - HTML processing now honors `replace_with_html` instead of silently stripping tags.
752
+ - Structured adapters reject reversible mode unless each token map can be retained safely.
753
+
616
754
  **v0.6.0**
617
755
  - **PII detection mode** (`ner_mode='pii'`): auto-configures GLiNER with 60+ PII entity labels (personal, financial, healthcare, identity, digital)
618
756
  - **Synthetic replacement** (`replacement_mode='synthetic'`): Faker-generated realistic values instead of `<TAG>` placeholders, with per-document consistency
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SqueakyCleanText
3
- Version: 0.6.1
3
+ Version: 0.7.1
4
4
  Summary: Text preprocessing & PII anonymization pipeline for NLP/ML: ONNX NER ensemble, language detection, stopword removal, and configurable token replacement.
5
5
  Author: Rehan Fazal
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/rhnfzl/SqueakyCleanText
8
8
  Project-URL: Repository, https://github.com/rhnfzl/SqueakyCleanText
9
9
  Project-URL: Issues, https://github.com/rhnfzl/SqueakyCleanText/issues
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3
22
22
  Classifier: Programming Language :: Python :: 3.11
23
23
  Classifier: Programming Language :: Python :: 3.12
24
24
  Classifier: Programming Language :: Python :: 3.13
25
- Classifier: License :: OSI Approved :: MIT License
25
+ Classifier: Programming Language :: Python :: 3.14
26
26
  Classifier: Operating System :: OS Independent
27
27
  Requires-Python: >=3.11
28
28
  Description-Content-Type: text/markdown
@@ -32,7 +32,6 @@ Requires-Dist: stop-words>=2025.11.4
32
32
  Requires-Dist: emoji>=2.14
33
33
  Requires-Dist: ftfy>=6.3
34
34
  Requires-Dist: Unidecode>=1.4
35
- Requires-Dist: beautifulsoup4>=4.14
36
35
  Requires-Dist: onnxruntime>=1.24.2
37
36
  Requires-Dist: tokenizers>=0.22.2
38
37
  Requires-Dist: huggingface-hub>=1.5.0
@@ -56,8 +55,6 @@ Provides-Extra: presidio
56
55
  Requires-Dist: presidio-analyzer>=2.2.360; extra == "presidio"
57
56
  Provides-Extra: classify
58
57
  Requires-Dist: gliclass>=0.1.16; extra == "classify"
59
- Provides-Extra: classify-onnx
60
- Requires-Dist: onnxruntime>=1.24.2; extra == "classify-onnx"
61
58
  Provides-Extra: all-ner
62
59
  Requires-Dist: torch>=2.6.0; extra == "all-ner"
63
60
  Requires-Dist: transformers>=4.48; extra == "all-ner"
@@ -73,6 +70,11 @@ Requires-Dist: rapidfuzz>=3.12; extra == "dev"
73
70
  Provides-Extra: test
74
71
  Requires-Dist: coverage>=7.8; extra == "test"
75
72
  Requires-Dist: pytest-cov>=6.0; extra == "test"
73
+ Provides-Extra: mcp
74
+ Requires-Dist: fastmcp>=4.0.5; extra == "mcp"
75
+ Provides-Extra: ocr
76
+ Requires-Dist: pillow>=12.3.0; extra == "ocr"
77
+ Requires-Dist: pytesseract>=0.3.13; extra == "ocr"
76
78
  Dynamic: license-file
77
79
 
78
80
  <div align="center">
@@ -82,7 +84,7 @@ Dynamic: license-file
82
84
  [![PyPI](https://img.shields.io/pypi/v/squeakycleantext.svg)](https://pypi.org/project/squeakycleantext/)
83
85
  [![PyPI - Downloads](https://img.shields.io/pypi/dm/squeakycleantext)](https://pypistats.org/packages/squeakycleantext)
84
86
  [![Python package](https://github.com/rhnfzl/SqueakyCleanText/actions/workflows/python-package.yml/badge.svg)](https://github.com/rhnfzl/SqueakyCleanText/actions/workflows/python-package.yml)
85
- [![Python Versions](https://img.shields.io/badge/Python-3.11%20|%203.12%20|%203.13-blue)](https://pypi.org/project/squeakycleantext/)
87
+ [![Python Versions](https://img.shields.io/badge/Python-3.11%20|%203.12%20|%203.13%20|%203.14-blue)](https://pypi.org/project/squeakycleantext/)
86
88
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
87
89
 
88
90
  A comprehensive text cleaning and preprocessing pipeline for machine learning and NLP tasks.
@@ -179,6 +181,8 @@ The base install uses **ONNX Runtime** for NER inference - no PyTorch or Transfo
179
181
  | Synthetic | `pip install SqueakyCleanText[synthetic]` | Faker-based synthetic replacement (realistic fake values instead of `<TAG>` tokens) |
180
182
  | Presidio | `pip install SqueakyCleanText[presidio]` | Presidio-analyzer for `presidio_gliner` backend |
181
183
  | Classify | `pip install SqueakyCleanText[classify]` | GLiClass document-level pre-classification |
184
+ | MCP | `pip install SqueakyCleanText[mcp]` | FastMCP server with text and JSON cleaning tools |
185
+ | OCR | `pip install SqueakyCleanText[ocr]` | Pillow image redaction and Tesseract OCR adapter |
182
186
  | All NER | `pip install SqueakyCleanText[all-ner]` | All NER backends combined |
183
187
  | Development | `pip install SqueakyCleanText[dev]` | Testing and linting tools |
184
188
 
@@ -304,7 +308,7 @@ lm_text, stat_text, lang = cleaner.process(
304
308
  # Entities are anonymized: names, SSNs, emails, dates of birth, and 50+ more PII types
305
309
  ```
306
310
 
307
- PII mode auto-configures: `ner_backend='gliner'`, uses [`knowledgator/gliner-pii-base-v1.0`](https://huggingface.co/knowledgator/gliner-pii-base-v1.0), sets threshold to 0.3 (recall-focused), and expands positional tags. User-provided values always take priority.
311
+ PII mode auto-configures: `ner_backend='gliner'`, uses [`knowledgator/gliner-pii-base-v1.0`](https://huggingface.co/knowledgator/gliner-pii-base-v1.0), sets `gliner_threshold` and the final `ner_confidence_threshold` to 0.3 (recall-focused), and expands positional tags. User-provided values always take priority.
308
312
 
309
313
  **Alternative PII models** (pass as `gliner_model`):
310
314
 
@@ -588,11 +592,14 @@ new_cfg = dataclasses.replace(cfg, check_ner_process=False)
588
592
  | `check_replace_html` | `True` | Strip/replace HTML tags |
589
593
  | `check_replace_urls` | `True` | Replace URLs with token |
590
594
  | `check_replace_emails` | `True` | Replace emails with token |
595
+ | `check_replace_ips` | `True` | Replace IPv4/IPv6 addresses (runs before dates, phones, years) |
596
+ | `replace_ips_public_only` | `False` | Keep private, loopback and reserved IPs; replace only public ones |
591
597
  | `check_replace_years` | `True` | Replace years (1900-2099) |
592
598
  | `check_replace_dates` | `False` | Replace full dates (ISO 8601, month names) |
593
599
  | `check_fuzzy_replace_dates` | `False` | Fuzzy match misspelled months (requires `[fuzzy]`) |
594
600
  | `check_replace_phone_numbers` | `True` | Replace phone numbers |
595
601
  | `check_replace_numbers` | `True` | Replace standalone numbers |
602
+ | `replace_numbers_in_words` | `False` | Also replace digits inside Latin/Greek/Cyrillic words (`GPT4` to `GPT<NUMBER>`) |
596
603
  | `check_replace_currency_symbols` | `True` | Replace currency symbols |
597
604
  | `check_ner_process` | `True` | Run NER entity recognition |
598
605
  | `check_remove_isolated_letters` | `True` | Remove single letters |
@@ -615,6 +622,7 @@ new_cfg = dataclasses.replace(cfg, check_ner_process=False)
615
622
  | `replace_with_url` | `"<URL>"` |
616
623
  | `replace_with_html` | `"<HTML>"` |
617
624
  | `replace_with_email` | `"<EMAIL>"` |
625
+ | `replace_with_ips` | `"<IP>"` |
618
626
  | `replace_with_years` | `"<YEAR>"` |
619
627
  | `replace_with_dates` | `"<DATE>"` |
620
628
  | `replace_with_phone_numbers` | `"<PHONE>"` |
@@ -653,6 +661,121 @@ new_cfg = dataclasses.replace(cfg, check_ner_process=False)
653
661
 
654
662
  </details>
655
663
 
664
+ ## Privacy Engine
665
+
666
+ Applications can apply versioned policy rules and inspect each decision.
667
+ This makes privacy behavior testable across services, datasets, and model upgrades.
668
+
669
+ ```python
670
+ from sct import EntityRule, PrivacyPolicy, TextCleaner, TextCleanerConfig
671
+
672
+ policy = PrivacyPolicy(
673
+ name="support-export",
674
+ version="1",
675
+ rules={
676
+ "PERSON": EntityRule(action="redact", min_score=0.8),
677
+ "ORGANISATION": EntityRule(action="keep"),
678
+ },
679
+ allowlist=frozenset({"Jane Public"}),
680
+ )
681
+
682
+ cleaner = TextCleaner(
683
+ cfg=TextCleanerConfig(ner_mode="pii"),
684
+ privacy_policy=policy,
685
+ include_entities=True,
686
+ )
687
+ result = cleaner.process("Contact Maria Chen for help.")
688
+
689
+ print(result.metadata["findings"])
690
+ print(result.metadata["detector"])
691
+ ```
692
+
693
+ Finding offsets (`start`, `end`) and NER entries in the reversible map are
694
+ document-relative positions in the post-regex text: the text after the regex
695
+ stages replaced URLs, emails, phones and the like. They are not offsets into the
696
+ raw input.
697
+
698
+ Reversible output is pseudonymized personal data. Anyone with the token map can
699
+ restore the source values. Keep token maps outside logs and cleaned datasets.
700
+
701
+ Use a `TokenStore` implementation to keep maps behind opaque references:
702
+
703
+ ```python
704
+ from sct import InMemoryTokenStore, TextCleaner, TextCleanerConfig
705
+
706
+ store = InMemoryTokenStore()
707
+ cleaner = TextCleaner(
708
+ cfg=TextCleanerConfig(replacement_mode="reversible"),
709
+ token_store=store,
710
+ )
711
+ result = cleaner.process("Maria lives in Utrecht.")
712
+ reference = result.metadata["anon_map_reference"]
713
+ ```
714
+
715
+ `InMemoryTokenStore` supports tests and short sessions. Production systems
716
+ should implement `TokenStore` with encryption, access control, expiry, and audit logs.
717
+
718
+ ### Boundary adapters
719
+
720
+ The base package includes JSON, DataFrame, retrieval, command-line, and streaming adapters:
721
+
722
+ ```python
723
+ from sct import RAGChunk, process_json, process_rag_chunks
724
+
725
+ cleaned_json = process_json(cleaner, {"message": "Email a@example.com"})
726
+ cleaned_chunks = process_rag_chunks(
727
+ cleaner,
728
+ [RAGChunk(id="1", text="Call +1-202-555-0147", metadata={})],
729
+ )
730
+ ```
731
+
732
+ ```sh
733
+ printf 'Email maria@example.com' | sct-clean --no-ner
734
+ printf 'Email maria@example.com' | sct-clean --no-ner \
735
+ --replacement-mode reversible --token-map tokens.json
736
+ pip install 'SqueakyCleanText[mcp]'
737
+ sct-mcp
738
+ ```
739
+
740
+ `BufferedStreamingRedactor` holds a full document for cross-chunk NER safety.
741
+ `WindowedStreamingRedactor` provides bounded latency for contact tokens.
742
+
743
+ The OCR adapter accepts any `OCRProvider`. The included Tesseract provider also
744
+ needs a local Tesseract executable. Browser export writes an ONNX Runtime Web
745
+ manifest, but it does not copy model files or Python language detection.
746
+
747
+ Corpus owners can call `assess_reidentification_risk` to measure k-anonymity
748
+ for selected quasi-identifiers. Reports contain counts only, not source values.
749
+
750
+ ## PII Evaluation
751
+
752
+ Model changes need ground-truth privacy evaluation, not only backend agreement.
753
+ The evaluation harness reports exact entity metrics by label, language, domain,
754
+ and named slice.
755
+
756
+ ```sh
757
+ python -m scripts.evaluate_pii \
758
+ --dataset evaluation/pii_starter.jsonl \
759
+ --predictions path/to/model-predictions.jsonl \
760
+ --model model-name-and-revision \
761
+ --output evaluation/results/model-name.json
762
+ ```
763
+
764
+ The included starter corpus is synthetic and only checks the evaluation path.
765
+ See [`evaluation/README.md`](evaluation/README.md) for schemas and release rules.
766
+
767
+ Pinned candidates live in `sct.model_candidates`. Run one candidate with:
768
+
769
+ ```sh
770
+ python -m scripts.benchmark_models \
771
+ --dataset evaluation/pii_starter.jsonl \
772
+ --candidate gliner2-pii \
773
+ --output evaluation/results/gliner2-pii.json
774
+ ```
775
+
776
+ Published third-party results are reference data only. `assess_candidate`
777
+ requires local recall, exact-span F1, latency, and ONNX parity before adoption.
778
+
656
779
  ## Architecture
657
780
 
658
781
  SqueakyCleanText processes text through a configurable pipeline of sequential steps:
@@ -690,6 +813,23 @@ Each step is toggled by a `TextCleanerConfig` field. The pipeline is built once
690
813
 
691
814
  ## What's New
692
815
 
816
+ **v0.7.0**
817
+ - **Privacy policies**: Apply versioned entity rules, allowlists, thresholds, and redaction actions.
818
+ - **Structured audit findings**: Inspect entity decisions, detector backends, and pinned model revisions.
819
+ - **Safer reversible processing**: Restore contact values and keep token maps behind an optional `TokenStore`.
820
+ - **Boundary adapters**: Clean JSON, DataFrame-like objects, RAG chunks, standard input, and MCP tool requests.
821
+ - **Privacy evaluation**: Score exact PII spans by label, language, domain, and custom slice.
822
+ - **Model adoption gates**: Compare pinned candidates using recall, F1, latency, and ONNX parity.
823
+ - **Extended media support**: Redact OCR regions, process bounded streams, and export browser deployment manifests.
824
+ - **Corpus risk indicators**: Measure k-anonymity for user-selected quasi-identifiers.
825
+ - **Supply-chain improvements**: Pin model revisions, publish through PyPI Trusted Publishing, and ship standard lockfiles.
826
+ - **Python 3.14**: Added CI coverage alongside Python 3.11 through 3.13.
827
+
828
+ Migration notes:
829
+ - `gliclass_onnx=True` now raises an error because the upstream package has no native ONNX loader.
830
+ - HTML processing now honors `replace_with_html` instead of silently stripping tags.
831
+ - Structured adapters reject reversible mode unless each token map can be retained safely.
832
+
693
833
  **v0.6.0**
694
834
  - **PII detection mode** (`ner_mode='pii'`): auto-configures GLiNER with 60+ PII entity labels (personal, financial, healthcare, identity, digital)
695
835
  - **Synthetic replacement** (`replacement_mode='synthetic'`): Faker-generated realistic values instead of `<TAG>` placeholders, with per-document consistency