Skip to content

Commit beef340

Browse files
authored
docs: align all documentation with the 2.4.0 code (#138)
1 parent 3fef9a4 commit beef340

11 files changed

Lines changed: 182 additions & 77 deletions

‎ARCHITECTURE.md‎

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ It supports two privacy export modes:
2525

2626
| 📄 File | ✨ Responsibility |
2727
| ------ | ----------------- |
28-
| `NullifyPDF.py` | Main PySide6 UI, PDF rendering, AI scan orchestration, redaction, export, reconstruction, OCR integration |
28+
| `NullifyPDF.py` | Main PySide6 UI, PDF rendering, AI scan orchestration (including the Italian-model consent step), redaction, export, reconstruction, OCR integration, Info window |
2929
| `update_check.py` | Qt-free update logic: version comparison (SemVer, stable/beta channels), GitHub release lookup, per-OS asset selection, trusted-host download, startup-check preference (`~/.nullifypdf/update_check.json`). Only runs on user click or if the user enabled the startup check |
3030
| `pii_detection.py` | Qt-free AI detection helpers: word-box text index (neighbour-aware rectangle clamping, ligatures, letter-spaced and hyphenated words), offset-to-rect mapping and partial-match policy, per-type thresholds, pattern validation, evidence-based PERSON/LOCATION scoring, accepted/suggested states, allowlist by word identity, whole-word propagation, entity selection persistence |
3131
| `privacy_core.py` | Privacy mode enum, placeholder registry (random / sequential / generic / keyed styles), encrypted restore-map payloads (version 3, optional per-occurrence text style; scrypt, PBKDF2 maps still readable), password strength check, restore-map parsing |
@@ -34,7 +34,9 @@ It supports two privacy export modes:
3434
| `export_engine.py` | Qt-free export pipeline (`export_document`), neutral file names, private temp handling, audit record, local-data wipe |
3535
| `review_dialog.py` | Qt dialog to accept or reject suggested detections |
3636
| `model_manager.py` | Pinned Hugging Face model catalog, SHA-256 verification, local model store and hardware recommendation |
37-
| `model_dialog.py` | Italian model selection, consent and progress-aware downloads |
37+
| `model_dialog.py` | Local ONNX model dialog: catalog, details, consent and a threaded download whose progress fills the download button (second click cancels; list and Close are locked meanwhile) |
38+
| `spacy_models.py` | The spaCy Italian pipeline (CC BY-NC-SA 3.0) downloaded on demand: consent text, pinned size and SHA-256, safe unpacking into `~/.nullifypdf/spacy_models/`, blank-pipeline fallback |
39+
| `licences_info.py` | Licence of NullifyPDF and of every component and model with official links, used by the Info window and the README table |
3840
| `onnx_ner.py` | CPU ONNX token-classification adapter with character-offset mapping and label mapping |
3941
| `word_import.py` | Safe DOCX OOXML text/table extraction into an internal PyMuPDF document; no macro execution |
4042
| `help_guide.py` | In-app guide (Italian, F1) and the tooltip table applied to every widget |
@@ -45,7 +47,7 @@ It supports two privacy export modes:
4547
| `PDF_Checker.py` | Heuristic post-export inspection helper |
4648
| `build_local.py` | PyInstaller build script |
4749
| `scripts/download_ocr_data.py` | Downloads EN/IT Tesseract `tessdata_fast` files bundled into every build |
48-
| `tests/` | Unit and smoke tests for validation, privacy primitives, OCR config, and build configuration |
50+
| `tests/` | Unit, integration and offscreen GUI tests (see [Tests](#-tests)) |
4951

5052
---
5153

@@ -107,8 +109,8 @@ Export pipeline writes a cleaned PDF copy
107109
2. The language is always Italian (`IT`); the user optionally enables:
108110
- `OCR PDF scansionati`
109111
- `Oscura Immagini`
110-
3. `cmd_auto_ai()` checks OCR configuration if OCR is enabled.
111-
4. `AIWorker.run_scan()` processes pages in a background thread.
112+
3. `cmd_auto_ai()` calls `_prepare_italian_model()`: if the spaCy Italian pipeline is neither installed as a package nor in `~/.nullifypdf/spacy_models/`, a dialog shows its licence and asks for consent (download, continue with rules only, or cancel); then it checks OCR configuration if OCR is enabled.
113+
4. `AIWorker.run_scan()` processes pages in a background thread; Presidio loads only the Italian pipeline (`AIWorker.italian_model`), and installed local ONNX models are loaded once per scan and add suggestions only.
112114
5. For each page:
113115
- the analysis text is built by `pii_detection.build_page_index()` from `page.get_text("words")`, recording each word's rectangle so character offsets map back to exact boxes. Each rectangle is trimmed against the words of neighbouring lines (a word box spans ascender to descender and would otherwise overlap them); ligatures are expanded, letter-spaced runs become one word and a word hyphenated across two lines is joined
114116
- if the page looks scanned, OCR is run with `page.get_textpage_ocr()` and the same index is built from the OCR words; on text pages with embedded images only the images are OCR'd and merged
@@ -229,7 +231,18 @@ Runtime behavior:
229231
4. OCR text is used for Presidio detection.
230232
5. OCR search rectangles are used to place redactions on scanned pages.
231233

232-
Every release bundles EN/IT OCR data.
234+
Every release bundles EN/IT OCR data. The documents analysed are Italian, so `ita` is the OCR language; `eng` is added only to read English words inside Italian pages.
235+
236+
---
237+
238+
## 🧠 Models
239+
240+
| Model | Where it comes from | Role |
241+
| ----- | ------------------- | ---- |
242+
| spaCy `it_core_news_md` 3.8.0 | Downloaded on first use after consent (`spacy_models.py`); never bundled, because its licence (CC BY-NC-SA 3.0) is non-commercial | Statistical recognition of names and places through Presidio |
243+
| ONNX models (MiniLMv2 EN/IT, PiiScan, OpenMed Italian FastClinical) | Optional, downloaded from pinned Hugging Face revisions (`model_manager.py`), every file checked against size and SHA-256 | Extra suggestions only; never applied automatically |
244+
245+
The development environment installs the Italian spaCy package so the tests can run; the PyInstaller specs exclude it, together with `pymupdf4llm` and `pymupdf.layout`.
233246

234247
---
235248

@@ -277,6 +290,8 @@ Beta releases (`v*-beta.N` tags) and stable releases (`vX.Y.Z` tags) are each co
277290

278291
**Linux packages** are built by the reusable workflow `build-linux.yml`, shared by the stable, beta and test builds. The PyInstaller binary and the `.rpm` are built once inside a `rockylinux:9` container (glibc 2.34, rpm 4.16), the `.deb` is wrapped from the same binary on `ubuntu-24.04`, and each package is then installed and started under Xvfb in clean Ubuntu 24.04, Ubuntu 26.04, Debian 13, Rocky 9, Rocky 10 and Fedora containers. A release is published only if this smoke test passes. Runners are pinned, so the move of `ubuntu-latest` to a new Ubuntu release does not change the build. When a distribution approaches end of support, update the matrix in `build-linux.yml` (and the list in the README).
279292

293+
Pull requests also run the unit tests against the latest PyMuPDF, a guard that only `develop` may be merged into `main`, project automation, and GitHub code scanning (CodeQL on pull requests to `main`).
294+
280295
Expected artifact names include:
281296

282297
```text
@@ -292,14 +307,13 @@ NullifyPDF_vX.Y.Z_Fedora.rpm
292307

293308
Current tests cover:
294309

295-
- list persistence
296-
- resource path resolution
297-
- OCR configuration helpers
298-
- privacy placeholder registry
299-
- encrypted restore-map round trip
300-
- reconstruction of a pseudonymized PDF from its restore map
301-
- build configuration
302-
- OCR data inclusion behavior
310+
- detection pipeline, geometry (rectangle clamping, glyph-deletion rule), scoring and the Italian lexicon (`test_pii_detection.py`, `test_detection_pipeline.py`, `test_geometry.py`)
311+
- redaction engine, export sanitiser and export verification (`test_redaction_engine.py`, `test_export_sanitize.py`, `test_privacy_hardening.py`)
312+
- placeholders, encrypted restore maps and reconstruction (`test_privacy_core.py`, `test_reconstruct.py`)
313+
- encrypted dictionaries and the key store (`test_secure_store.py`)
314+
- offscreen GUI flows: review, export, reconstruction, guide and tooltips, Info window, model dialog (`test_gui_flow.py`, `test_gui_help.py`, `test_model_dialog.py`)
315+
- model catalog, on-demand Italian model download, DOCX import, update check (`test_model_manager.py`, `test_spacy_models.py`, `test_word_import.py`, `test_update_check.py`)
316+
- licence consistency (no non-commercial component in the installer) and build configuration (`test_licences.py`, `test_build_config.py`, `test_validation.py`)
303317

304318
Run:
305319

@@ -313,6 +327,7 @@ pytest tests/ -v
313327

314328
- Detection is probabilistic. Presidio, spaCy, and OCR can miss data or create false positives. Heuristics that reject label-like single-word names trade a little recall (for example a lone first name that starts a line) for precision; such values can be marked manually.
315329
- OCR depends on Tesseract language data and scan quality.
330+
- Only Italian documents are supported. Without the Italian spaCy model (download declined) only rules and dictionaries run and names are found less often.
316331
- Handwriting is not reliably supported.
317332
- Complex PDFs may contain structures not covered by automated cleanup.
318333
- Pseudonymized PDFs are still personal-data processing when the restore map exists.

‎CONTRIBUTING.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ git remote add upstream https://github.com/overwrite00/NullifyPDF.git
2424
python setup_env.py
2525
```
2626

27-
This creates isolated venv, installs dependencies, and downloads NLP models.
27+
This creates an isolated venv, installs dependencies and installs the Italian spaCy model used by the tests (the released program downloads it on demand).
2828

2929
Then enable the repo's git hooks (one-time per clone) so the `Last updated` footer in docs stays in sync automatically:
3030

@@ -219,7 +219,7 @@ thread when scanning large PDFs concurrently."
219219
### Before Opening PR
220220

221221
1. ✅ Run tests: `pytest tests/ -v`
222-
2. ✅ Type check (blocking in CI): `mypy NullifyPDF.py pii_detection.py privacy_core.py update_check.py PDF_Checker.py`
222+
2. ✅ Type check (blocking in CI): `mypy NullifyPDF.py pii_detection.py privacy_core.py update_check.py PDF_Checker.py export_engine.py pdf_sanitize.py review_dialog.py lexicon_it.py secure_store.py word_lists.py dictionary_dialog.py help_guide.py spacy_models.py licences_info.py`
223223
3. ✅ Build locally: `python build_local.py`
224224
4. ✅ Update CHANGELOG.md with your changes
225225
5. ✅ Self-review your code (would you understand this in 6 months?)

‎DEVELOPMENT.md‎

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -105,32 +105,44 @@ If the GUI opens → **You're ready to develop!** 🎉
105105
```
106106
NullifyPDF/
107107
|-- NullifyPDF.py # Main PySide6 application
108-
|-- model_manager.py # Pinned local model catalog and integrity checks
109-
|-- model_dialog.py # Model consent and download UI
110-
|-- onnx_ner.py # ONNX token-classification adapter
111-
|-- word_import.py # Safe DOCX text/table importer
112-
|-- privacy_core.py # Privacy modes and encrypted restore maps
108+
|-- pii_detection.py # Qt-free detection: word index, geometry, scoring, partial-match policy
109+
|-- lexicon_it.py # Italian word lists; lexicon_data_it.py is generated (ISTAT, Wikidata)
110+
|-- privacy_core.py # Privacy modes, placeholders and encrypted restore maps
111+
|-- export_engine.py # Qt-free export pipeline, audit record, local-data wipe
112+
|-- pdf_sanitize.py # Export sanitiser and output verification
113+
|-- review_dialog.py # Review of suggested detections
114+
|-- secure_store.py # Master key protection (DPAPI / keyring / key file), AES-GCM sealing
115+
|-- word_lists.py # Encrypted blocklist/allowlist with expiry
116+
|-- dictionary_dialog.py # Dictionary editor
117+
|-- help_guide.py # In-app guide (Italian) and tooltips
118+
|-- licences_info.py # Licences and links shown in the Info window and README
119+
|-- spacy_models.py # Italian spaCy pipeline downloaded on demand after consent
120+
|-- model_manager.py # Pinned local ONNX model catalog and integrity checks
121+
|-- model_dialog.py # Local model dialog with in-button download progress
122+
|-- onnx_ner.py # ONNX token-classification adapter
123+
|-- word_import.py # Safe DOCX text/table importer
113124
|-- update_check.py # Qt-free update check: versions, GitHub releases, asset choice, download
114-
|-- pii_detection.py # Qt-free AI detection helpers (offsets, filters, whole-word matching)
115125
|-- PDF_Checker.py # Heuristic verification utility
116126
|-- setup_env.py # Environment setup script
117127
|-- build_local.py # PyInstaller build script
118128
|-- requirements.txt # Python dependencies
129+
|-- LICENSE, THIRD_PARTY_NOTICES.md
119130
|-- scripts/
120-
| `-- download_ocr_data.py # EN/IT OCR data downloader
121-
|-- tests/
122-
| |-- test_validation.py
123-
| |-- test_privacy_core.py
124-
| |-- test_pii_detection.py
125-
| |-- test_reconstruct.py
126-
| `-- test_build_config.py
131+
| |-- download_ocr_data.py # EN/IT OCR data downloader
132+
| |-- build_lexicon_data.py # Generates lexicon_data_it.py (development only)
133+
| |-- extract_release_notes.py
134+
| `-- update_docs_dates.py
135+
|-- tests/ # pytest suite (unit, integration and offscreen GUI tests)
127136
|-- images/
128137
| `-- NullifyPDF.png
129138
`-- .github/
130139
`-- workflows/
131-
|-- test_build.yml
132-
|-- beta-release.yml
133-
`-- release.yml
140+
|-- test_build.yml # unit tests and full build matrix
141+
|-- beta-release.yml # v*-beta.N tags
142+
|-- release.yml # vX.Y.Z tags
143+
|-- build-linux.yml # reusable: .deb/.rpm build and smoke tests
144+
|-- guard-main-source.yml
145+
`-- project-automation.yml
134146
```
135147

136148
---
@@ -145,25 +157,32 @@ NullifyPDF/
145157
| **PyMuPDF** | 2.1 | PDF manipulation and OCR bridge |
146158
| **presidio-analyzer** | 2.2.366 | PII detection |
147159
| **spaCy** | 3.8.18 | NLP for entity recognition |
148-
| **cryptography** | 50.0.2 | Encrypted restore maps |
160+
| **cryptography** | 50.0.2 | Encrypted restore maps and dictionaries |
161+
| **keyring** | 25.7.0 | OS key store (macOS and Linux only) |
149162
| **onnxruntime** | 1.31.1 | CPU inference for optional local PII models |
150163
| **tokenizers** | 0.23.3 | Fast local tokenizer with character offsets |
151164
| **python-docx** | — | Not required; DOCX import uses the standard-library OOXML reader |
152165
| **pytest** | 9.2.0 | Testing framework |
166+
| **mypy** | 1.20.2 | Type checking (blocking in CI) |
167+
| **PyInstaller** | 6.22.3 | Builds the executables |
153168

154169
### Language Models
155170

156171
| Model | Size | Purpose | Licence | In the installer |
157172
| ----------------- | ----- | ----------- | ------- | ---------------- |
158173
| `it_core_news_md` | 45 MB | Italian NER | CC BY-NC-SA 3.0 | **no**, downloaded by the app on demand (`spacy_models.py`) |
159174

160-
`setup_env.py` installs both in the development environment (the Italian tests
175+
`setup_env.py` installs it in the development environment (the Italian tests
161176
need it). The PyInstaller specs exclude `it_core_news_md`, `pymupdf4llm` and
162177
`pymupdf.layout`. At run time the app asks for consent, downloads the Italian
163178
pipeline from the publisher's release, checks its size and SHA-256 and stores it
164179
under `~/.nullifypdf/spacy_models/`. If the user declines, a blank Italian
165180
pipeline is used and only rule-based detection is available.
166181

182+
The optional ONNX models (MiniLMv2 EN/IT, PiiScan, OpenMed Italian FastClinical)
183+
are described in `model_manager.py`; they are downloaded by the user from pinned
184+
Hugging Face revisions into `~/.nullifypdf/models/` and add suggestions only.
185+
167186
---
168187

169188
## 🔍 Understanding the Code
@@ -242,20 +261,23 @@ Opens `htmlcov/index.html` in browser.
242261
CI runs `mypy` as a blocking step. Run the same check locally before opening a PR:
243262

244263
```bash
245-
mypy NullifyPDF.py pii_detection.py privacy_core.py update_check.py PDF_Checker.py
264+
mypy NullifyPDF.py pii_detection.py privacy_core.py update_check.py PDF_Checker.py export_engine.py pdf_sanitize.py review_dialog.py lexicon_it.py secure_store.py word_lists.py dictionary_dialog.py help_guide.py spacy_models.py licences_info.py
246265
```
247266

248267
PyMuPDF is imported as `pymupdf` (aliased to `fitz`) so it is really type-checked; its dynamically defined constants are read once near the top of `NullifyPDF.py`.
249268

250269
### What's Tested
251270

252-
- ✅ **PDFListManager** — File I/O, persistence
253-
- ✅ **OCR Config** — Tesseract language selection and tessdata discovery
254-
- ✅ **Privacy Core** — Placeholder mapping and encrypted restore maps (v1/v2/v3)
255-
- ✅ **AI Detection Helpers** — Offset-to-rectangle mapping, thresholds, label/heading filtering, whole-word matching
256-
- ✅ **Reconstruction** — Placeholder restoration, original font/size/colour on native PDFs
257-
- ✅ **Build Config** — OCR data bundling behavior
258-
- ✅ **Resource Paths** — PyInstaller compatibility
271+
- ✅ **Detection** — word index, rectangle clamping and the glyph-deletion rule, scoring, partial-match policy, Italian lexicon
272+
- ✅ **Redaction and export** — redaction engine, sanitiser, export verification (survivor and leak checks)
273+
- ✅ **Privacy Core** — placeholders and encrypted restore maps (v1 to v4 formats, Argon2id envelope, recovery key), reconstruction with the original font
274+
- ✅ **Local data** — encrypted dictionaries with expiry and the secure key store
275+
- ✅ **GUI flows** — offscreen tests of review, export, reconstruction, guide and tooltips, Info window, model dialog and Italian-model consent
276+
- ✅ **Models and import** — model catalog and integrity checks, on-demand Italian model download, DOCX import, update check
277+
- ✅ **Licences and build** — licence consistency, OCR data bundling, PyInstaller configuration, resource paths
278+
279+
> [!TIP]
280+
> GUI tests run offscreen (`QT_QPA_PLATFORM=offscreen`) and never touch the OS keyring (`NULLIFYPDF_DISABLE_KEYRING=1`).
259281
260282
---
261283

@@ -469,6 +491,10 @@ isort NullifyPDF.py
469491
| `pdf_sanitize.py` | Export sanitiser and output verification |
470492
| `export_engine.py` | Qt-free export pipeline, audit record, local-data hygiene |
471493
| `review_dialog.py` | Review dialog for suggested detections |
494+
| `spacy_models.py` | Italian spaCy pipeline downloaded on demand (consent, SHA-256, fallback) |
495+
| `licences_info.py` | Licences and official links shown in the Info window and README (a test keeps README in step) |
496+
| `model_manager.py` / `model_dialog.py` / `onnx_ner.py` | Optional ONNX models: catalog, verified download, adapter |
497+
| `word_import.py` | DOCX import |
472498
| `secure_store.py` | Master key protection (DPAPI / keyring / key file), AES-GCM sealing |
473499
| `word_lists.py` | Encrypted blocklist/allowlist with expiry |
474500
| `dictionary_dialog.py` | Dictionary editor dialog |
@@ -595,6 +621,13 @@ Ready to contribute? See [CONTRIBUTING.md](./CONTRIBUTING.md) for:
595621
- 💬 **GitHub Discussions:** [Discussions](https://github.com/overwrite00/NullifyPDF/discussions)
596622
- 🐛 **GitHub Issues:** [Issues](https://github.com/overwrite00/NullifyPDF/issues)
597623

624+
### Release procedure
625+
626+
- **Beta**: work lands on `develop` through squash-merged pull requests; an annotated tag `vX.Y.Z-beta.N` on `develop` starts `beta-release.yml`. Set `__version_prerelease__` in `NullifyPDF.py` to the beta suffix.
627+
- **Stable**: set `__version_prerelease__` to `""`, move the `[Unreleased]` changelog entries under `## [X.Y.Z] - date` (leave `[Unreleased]` empty), merge `develop` into `main` with a merge commit through a pull request (one approving review is required), then tag `vX.Y.Z` on `main`. `release.yml` checks the changelog and the version before building.
628+
- Documentation-only changes do not need a release.
629+
- When dependencies change, update `THIRD_PARTY_NOTICES.md` and `licences_info.py`; `tests/test_licences.py` fails if a non-commercial component would be bundled.
630+
598631
### Release notes and the update check
599632

600633
The release workflows fill each GitHub release body with `scripts/extract_release_notes.py`: the `## [X.Y.Z]` section for stable tags, `## [Unreleased]` for beta tags. The in-app update check displays that body, so keep the changelog sections accurate before tagging.

0 commit comments

Comments
 (0)