complydoc 0.3.0__py3-none-any.whl

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 (100) hide show
  1. complydoc/CHANGELOG.md +347 -0
  2. complydoc/__init__.py +6 -0
  3. complydoc/audit.py +467 -0
  4. complydoc/cli.py +1148 -0
  5. complydoc/config/__init__.py +0 -0
  6. complydoc/config/loader.py +335 -0
  7. complydoc/config/model_prices.json +1628 -0
  8. complydoc/config/pricing.yaml +264 -0
  9. complydoc/config/readiness.yaml +230 -0
  10. complydoc/config/schema.py +406 -0
  11. complydoc/config/sensitive.yaml +371 -0
  12. complydoc/cost/__init__.py +0 -0
  13. complydoc/cost/estimator.py +387 -0
  14. complydoc/cost/price_table.py +192 -0
  15. complydoc/cost/pricing_import.py +241 -0
  16. complydoc/cost/tokenizer.py +89 -0
  17. complydoc/cost/vision.py +106 -0
  18. complydoc/discovery.py +68 -0
  19. complydoc/geometry.py +40 -0
  20. complydoc/ingest/__init__.py +0 -0
  21. complydoc/ingest/base.py +290 -0
  22. complydoc/ingest/docx.py +146 -0
  23. complydoc/ingest/engines/__init__.py +0 -0
  24. complydoc/ingest/engines/base.py +46 -0
  25. complydoc/ingest/engines/rapidocr.py +104 -0
  26. complydoc/ingest/engines/registry.py +53 -0
  27. complydoc/ingest/engines/tesseract.py +75 -0
  28. complydoc/ingest/extractors/__init__.py +0 -0
  29. complydoc/ingest/extractors/base.py +110 -0
  30. complydoc/ingest/extractors/pdfium.py +65 -0
  31. complydoc/ingest/extractors/plumber.py +53 -0
  32. complydoc/ingest/extractors/pypdfreader.py +47 -0
  33. complydoc/ingest/extractors/registry.py +58 -0
  34. complydoc/ingest/image.py +92 -0
  35. complydoc/ingest/ocr.py +126 -0
  36. complydoc/ingest/pdf.py +697 -0
  37. complydoc/ingest/registry.py +55 -0
  38. complydoc/ingest/xlsx.py +90 -0
  39. complydoc/offline.py +97 -0
  40. complydoc/overall.py +254 -0
  41. complydoc/quickwins.py +299 -0
  42. complydoc/readiness/__init__.py +0 -0
  43. complydoc/readiness/analyser.py +116 -0
  44. complydoc/readiness/base.py +89 -0
  45. complydoc/readiness/registry.py +52 -0
  46. complydoc/readiness/scoring.py +120 -0
  47. complydoc/readiness/signals/__init__.py +0 -0
  48. complydoc/readiness/signals/acroform.py +23 -0
  49. complydoc/readiness/signals/columns.py +75 -0
  50. complydoc/readiness/signals/date_formats.py +60 -0
  51. complydoc/readiness/signals/encryption.py +31 -0
  52. complydoc/readiness/signals/fonts.py +59 -0
  53. complydoc/readiness/signals/garbled.py +75 -0
  54. complydoc/readiness/signals/image_area.py +38 -0
  55. complydoc/readiness/signals/language.py +73 -0
  56. complydoc/readiness/signals/ocr_confidence.py +38 -0
  57. complydoc/readiness/signals/page_size.py +32 -0
  58. complydoc/readiness/signals/rotation_skew.py +198 -0
  59. complydoc/readiness/signals/scan_dpi.py +50 -0
  60. complydoc/readiness/signals/tables.py +124 -0
  61. complydoc/readiness/signals/text_coverage.py +38 -0
  62. complydoc/readiness/signals/text_layer.py +35 -0
  63. complydoc/report/__init__.py +0 -0
  64. complydoc/report/charts.py +339 -0
  65. complydoc/report/diffing.py +108 -0
  66. complydoc/report/html_writer.py +394 -0
  67. complydoc/report/json_writer.py +24 -0
  68. complydoc/report/limitations.py +385 -0
  69. complydoc/report/models.py +400 -0
  70. complydoc/report/preview.py +379 -0
  71. complydoc/report/templates/report.html.j2 +1732 -0
  72. complydoc/report/text_writer.py +69 -0
  73. complydoc/sample/employee-record.pdf +0 -0
  74. complydoc/sample/financial-summary.pdf +0 -0
  75. complydoc/sample/invoice-scan.pdf +79 -0
  76. complydoc/sample/supplier-list.docx +0 -0
  77. complydoc/sample/supplier-onboarding.pdf +0 -0
  78. complydoc/sample/terms-and-conditions.pdf +0 -0
  79. complydoc/sampling.py +78 -0
  80. complydoc/sensitive/__init__.py +0 -0
  81. complydoc/sensitive/base.py +125 -0
  82. complydoc/sensitive/detectors/__init__.py +0 -0
  83. complydoc/sensitive/detectors/ner.py +113 -0
  84. complydoc/sensitive/detectors/regex_detector.py +68 -0
  85. complydoc/sensitive/masking.py +60 -0
  86. complydoc/sensitive/registry.py +59 -0
  87. complydoc/sensitive/scanner.py +224 -0
  88. complydoc/sensitive/validators.py +313 -0
  89. complydoc/skill/SKILL.md +122 -0
  90. complydoc/skill/__init__.py +0 -0
  91. complydoc/text.py +96 -0
  92. complydoc/vendor/__init__.py +14 -0
  93. complydoc/vendor/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4 +100256 -0
  94. complydoc/vendor/tiktoken_cache/fb374d419588a4632f3f557e76b4b70aebbca790 +199998 -0
  95. complydoc/warm.py +44 -0
  96. complydoc-0.3.0.dist-info/METADATA +362 -0
  97. complydoc-0.3.0.dist-info/RECORD +100 -0
  98. complydoc-0.3.0.dist-info/WHEEL +4 -0
  99. complydoc-0.3.0.dist-info/entry_points.txt +2 -0
  100. complydoc-0.3.0.dist-info/licenses/LICENSE +21 -0
complydoc/CHANGELOG.md ADDED
@@ -0,0 +1,347 @@
1
+ # Changelog
2
+
3
+ Notable changes, newest first. Versions follow [semantic versioning](https://semver.org):
4
+ the major number changes when a report's JSON shape or a config file's meaning breaks,
5
+ the minor number when something is added, the patch number when a measurement is
6
+ corrected. `schema_version` in the JSON is versioned separately and is the field to
7
+ branch on when reading reports programmatically.
8
+
9
+ ## [0.3.0] — 2026-09-10
10
+
11
+ ### Added
12
+
13
+ - The release can publish to PyPI. Publishing the drafted release uploads the
14
+ wheel and sdist already attached to it — the artefacts that were built and
15
+ attested, not a rebuild — over Trusted Publishing, so there is no token
16
+ stored anywhere. The tag deliberately does not do it: a version on PyPI
17
+ cannot be replaced or reused, so the last step before it is a person.
18
+ - PyPI metadata: classifiers, and Homepage, Changelog and Issues links. The
19
+ README's images are absolute URLs now, because PyPI renders it with no
20
+ repository context and relative paths arrive broken.
21
+ - `complydoc demo` audits six synthetic sample documents that ship with the
22
+ tool and opens the report. Evaluating this used to mean finding a folder
23
+ first. The samples carry the problems the tool exists to find — a scan with
24
+ no text layer, a two-column page the readers disagree about, a whitespace
25
+ table, and identifiers of several kinds — and no real person is described in
26
+ any of them.
27
+ - The README shows the report. The product is the page it produces, and the
28
+ repository did not show it.
29
+ - Global readiness, on the front page as a ring. AI readiness asks whether the
30
+ text can be got off the page; this asks whether the folder can be put through
31
+ a pipeline at all, combining content with the cost path it forces and what it
32
+ carries that should not leave. Weights are in `readiness.yaml` and printed
33
+ beside the score. A factor the run did not measure is dropped and the rest
34
+ renormalised — never counted as nought — and the report says how many of the
35
+ three it was built from.
36
+ - The ring shows the composition rather than the score, because the mean hides
37
+ the tail: a folder averaging 71 can still hold two documents nothing can be
38
+ read from, and those two are the ones somebody has to deal with.
39
+ - Quick wins: what to do next, ranked by how much of the folder each touches.
40
+ Every entry names the documents it applies to, says whether complydoc can do
41
+ it or a person has to, and where the consequence follows from prices already
42
+ in the report it is computed — the OCR entry quotes what those documents cost
43
+ today on the image path. None of them predicts a score, because signals
44
+ interact and the only honest way to know is to fix the documents and run the
45
+ audit again.
46
+ - Every sensitive finding carries an evidence tier: `confirmed` when a checksum
47
+ passed, `corroborated` when a label sits beside it, `pattern` for a shape
48
+ alone, and `model` for a statistical guess. The security table shows it and
49
+ breaks a severity tie on it, so a confirmed card number sorts above a name a
50
+ model thought it saw.
51
+
52
+ ### Changed
53
+
54
+ - Cost is off the front page. The tile and the per-1,000 chart move to the Cost
55
+ tab, which is what that tab is for, and the chart leads it. An audit's front
56
+ page should answer whether these documents can be used, not what the pipeline
57
+ would bill — the price only matters once the answer to the first question is
58
+ yes. The only figure left is what a quick win would save, which is the reason
59
+ to act on it rather than a cost breakdown.
60
+ - The model detector reports no confidence rather than 1.0. The small English
61
+ pipeline exposes no per-entity score, so recording one put a guess level with
62
+ a passed checksum — the one place this tool was reporting a number that meant
63
+ something other than what it said. `confidence` is now null there, and the
64
+ `min_confidence: 0.5` configured for names and organisations is gone: it
65
+ filtered nothing and implied a threshold that was never applied.
66
+ - `schema_version` is 3. The report carries `overall` and `quick_wins`, every
67
+ sensitive match carries `evidence`, and `confidence` on a match may be null.
68
+ - The summary quotes Claude Sonnet 5 rather than whichever model happened to be
69
+ cheapest. The cheapest was a moving target — it changed with a catalogue
70
+ refresh rather than with the folder — and it flattered the estimate with a
71
+ model few people would actually run. `compare.headline_model` in
72
+ `pricing.yaml` sets it, and the report falls back to the cheapest priced model
73
+ and still names it when that one is not in the comparison.
74
+ - A model that carried its own id as its name now borrows the catalogue's, so
75
+ the chart no longer reads `zai/glm-5.3-flash` beside `Claude Sonnet 5`. The
76
+ seven curated entries that were written that way are fixed as well.
77
+ - How many documents each architecture reaches is stated once per architecture
78
+ in the legend, instead of beside every bar. On a dozen models that was ninety
79
+ copies of three numbers, crowding out the figures that differ. It stays on
80
+ each bar's own hover.
81
+ - The explanation of a sensitive mark follows the mark instead of appearing
82
+ under the page, and there is one of it. The mark used to carry an SVG
83
+ `<title>` as well, which is the browser's own tooltip, so pointing at a mark
84
+ drew the same words twice in two different boxes. The mark now carries an
85
+ `aria-label`, which says the same thing to a screen reader without drawing
86
+ anything.
87
+ - A mark says what was found, masked exactly as the findings table masks it —
88
+ the last few characters, with the separators kept so the shape stays legible.
89
+ A rectangle and a category left the reader hunting for which item it was.
90
+ `--reveal` puts the whole value there as it does everywhere else.
91
+ - The summary tiles say one thing each. The heading above the first chart no
92
+ longer repeats the tile directly above it.
93
+
94
+ ### Fixed
95
+
96
+ - `.complydoc`, where a run writes when nobody passes `--out`, is git-ignored.
97
+ Those reports carry the text read off each page and a picture of every page,
98
+ so a default run inside a repository was leaving document content untracked
99
+ in the working tree.
100
+
101
+ ### Performance
102
+
103
+ - A 392-page book took 2.1 GB of memory and now takes 0.99 GB. pdfplumber
104
+ caches every object it parses off a page, and nothing released them, so the
105
+ whole document's characters — 680,000 of them — stayed in memory until the
106
+ run ended. They are dropped as soon as each page has been read. It matters
107
+ most where it was worst: a process pool holds one document per worker, so
108
+ the old figure was multiplied by the number of workers.
109
+ - The coverage helper is fourteen times faster. It called `np.clip` four times
110
+ per rectangle, and numpy's per-call overhead on a single Python float dwarfs
111
+ the arithmetic — on a dense page that was a tenth of the whole run. Plain
112
+ `min`/`max` gives the identical answer.
113
+
114
+ ### Internal
115
+
116
+ - Type suppressions are down from twenty to four, and each was removed by
117
+ fixing what caused it rather than by widening the annotation. The largest
118
+ group came from one helper that returned `object` where it had a `Config` in
119
+ hand, which cost eight `type: ignore` comments in commands downstream. The
120
+ four that remain are in the network guard, where assigning to a standard
121
+ library method cannot be expressed in the type system.
122
+ - One severity table instead of two. The report and the readiness score each
123
+ had their own copy, so a fourth severity would have had to be remembered in
124
+ both; the band label is public now rather than reached for as a private name
125
+ from another module.
126
+ - Dead code removed: an unused page property, and a price helper left behind
127
+ when model selection moved from price spread to product line.
128
+
129
+ ## [0.2.0] — 2026-09-10
130
+
131
+ ### Added
132
+
133
+ - `complydoc compare <path>` runs an audit with every reader and every OCR
134
+ engine installed, so comparing does not mean naming each one by hand. It says
135
+ which it is using before it starts, and says so plainly when there is nothing
136
+ installed to compare against.
137
+ - Extractors are pluggable, and more than one can run in a single pass.
138
+ `--extractor` picks which library reads the text layer; `--compare-extractor`
139
+ reads every page with a second one as well and reports where the two differ.
140
+ Only the first reaches a finding — the rest are measured, never adopted.
141
+ Comparison lives inside a run, so it is the same page on the same machine at the
142
+ same moment rather than two runs that would differ for reasons of their own.
143
+ `complydoc extractors` lists them. With `--extracted-text` on, what each reader made
144
+ of a page is kept, so the Documents page can switch between them and show the text
145
+ itself rather than only how much of it there was.
146
+ - pdfium as a second extractor: measured against pdfplumber on a real 392-page book
147
+ the two agree on the text within one to two per cent, and pdfium reads it about
148
+ thirteen times faster. It provides no table structure and a box per line rather
149
+ than per word, so the signals that need those report that they could not measure
150
+ rather than returning a number that means something else.
151
+ - A third reader for a PDF's text layer, `pypdf`. It is already a dependency,
152
+ so it costs no install and no measurable time, and it shares no code with
153
+ either of the others — which is the only reason a third reading is worth
154
+ having. It returns text and no geometry, so it reports coverage as not
155
+ measured rather than as nought per cent, and the findings that need boxes say
156
+ the same.
157
+ - OCR engines are pluggable the same way, with `--ocr-engine`,
158
+ `--compare-ocr-engine` and `complydoc engines`. Tesseract is included for anyone
159
+ who already has it; it is not a dependency, because it needs a system binary.
160
+ - Where two readers parted company is now shown, not just measured. Each other
161
+ reader's pane carries its own text with the words only it found underlined
162
+ and the words only the kept reader found struck through, so the difference is
163
+ read in place rather than by flipping between two panes and holding both in
164
+ your head. The page bar gains a control that jumps to the next page the
165
+ readers read differently, which on a long document is a handful of pages
166
+ among hundreds.
167
+ - The report tells a reader that walked a page in the wrong order apart from
168
+ one that read different words. They look the same to any similarity score and
169
+ they call for different things: the first scrambled a page it could read, the
170
+ second could not read part of it.
171
+ - A live bar, count and clock while a folder is read. A run over a few hundred
172
+ documents takes minutes, and a terminal that says nothing for minutes is
173
+ indistinguishable from one that has hung. A pipe still gets one line per
174
+ document, and `--quiet` still gets nothing.
175
+ - A vendored model catalogue from models.dev: every current model from the eight
176
+ first-party providers, so `--model` reaches one without anyone having hand-written an
177
+ entry for it. `complydoc models --new N` lists the most recently released, because an
178
+ alphabetical dump sorts a two-year-old model above this month's. It is data on disk —
179
+ a run still reaches no network — and `make prices` refreshes it. Imported prices are
180
+ marked as imported, kept apart from the handful someone verified against a provider's
181
+ page, and a report that prices against one says so in its limitations.
182
+ Batch prices come from litellm, the only one of the two sources that publishes them.
183
+ - The report compares three models per provider rather than whichever ten had been
184
+ written down, so every provider is represented and two of them are no longer missing
185
+ altogether. Each provider is topped up from the catalogue with its most recently
186
+ released models that take images; `compare.per_provider` in `pricing.yaml` sets the
187
+ number. A refreshed catalogue brings a refreshed comparison.
188
+ - Batch pricing, where the provider publishes one. The cost page shows what the same
189
+ tokens cost through a batch endpoint beside the interactive price. Never inferred from
190
+ the customary half price: a discount nobody can check does not belong in a budget.
191
+ - `--save-text <dir>` keeps the text complydoc read, one file per document. Reading a
192
+ scanned folder is the slow part of an audit and it was being thrown away, so the next
193
+ tool to want the text ran OCR over the same pages again.
194
+ - The summary says how long the work took by stage — reading, OCR, signals, identifier
195
+ scan — and what the measured rate means for 100, 1,000, 10,000 and 100,000 documents.
196
+
197
+ ### Changed
198
+
199
+ - The difficulty component is called readiness. A high score always meant a document
200
+ that was easy to process, which read backwards under a name promising the opposite.
201
+ The command is `complydoc readiness`, the config file is `readiness.yaml`, the JSON
202
+ carries `readiness` where it carried `difficulty`, and `schema_version` is 2. The
203
+ score bands read the same way round as the number now: ready, workable, needs work,
204
+ not ready. Signal directions are `higher_is_better` and `lower_is_better`.
205
+ - The Documents page is a page viewer rather than a grid of the first twelve pages.
206
+ Every page of a document is reachable, by stepping or by typing a page number, and
207
+ the page sits beside the text that was read off it instead of in a separate tab.
208
+ The two halves are one row of equal height and each scrolls inside its own frame.
209
+ - The Documents page is the file list and the two panels, and nothing else. The
210
+ folder-wide table of readiness signals, the per-document summary line and the list
211
+ of poorly rated signals moved to that document's own Signals tab, where they answer
212
+ a question the reader has actually asked.
213
+ - The text read off each page is in the report by default. Reading a page beside what
214
+ was extracted from it is the point of the tool, and it was behind a flag. The report
215
+ says on its security page that the masking covers the findings table and not the file,
216
+ since the file now reproduces the pages those values were read off.
217
+ `--no-extracted-text` restores a report with no document content.
218
+ - Sensitive marks on the page layout explain themselves on hover: what was found, why it
219
+ was reported, and why that matters. The value itself is never in the explanation.
220
+ - Readings are compared by word rather than by character, and spacing is no
221
+ longer a difference. Every reader breaks lines somewhere slightly different,
222
+ and counting that marked every page of every document. One measure now backs
223
+ both the number in the table and the marks on the page.
224
+ - The report is laid out to the width of the window rather than a 60rem reading
225
+ column, so the page and its text get the room.
226
+ - Findings on the security page arrive ordered by severity, and every column there
227
+ can be sorted.
228
+ - The page heading repeating the folder path, the timestamp and the version is gone.
229
+ All of it is recorded once, in the footer.
230
+ - The panels are one fixed frame, identical on every document and every page. Their
231
+ height used to follow whichever page image was loaded, so the workspace resized
232
+ every time you stepped a page or picked another file. A document nobody could open
233
+ now draws the same workspace with the reason inside it, rather than a different
234
+ block that resized the page on arrival.
235
+ - The file list sits at the height of the panels, not the column that holds them.
236
+ - Every page starts the same distance below the tab bar, whether or not it opens on
237
+ a heading.
238
+ - `complydoc pricing-import` reads the vendored table, so it works without litellm
239
+ installed, and the entry it generates is marked `price_source: imported` rather than
240
+ being stamped with a `last_verified` date nobody earned.
241
+
242
+ ### Performance
243
+
244
+ Measured on this machine: a folder of 102 documents 17.6s to 10.9s with OCR and
245
+ 7.4s to 2.9s without, a 392-page book 56.9s to 31.0s.
246
+
247
+ - Skew was measured by rotating the whole page once per candidate angle. The same
248
+ measurement falls out of projecting the ink pixels, which are a tenth of the page,
249
+ and a coarse pass now finds the degree before the fine pass refines it.
250
+ - The entity model loaded a tagger, a dependency parser and a lemmatiser that nothing
251
+ reads, and parsed every page once per category rather than once.
252
+ - The whitespace table pass pulled the text out of every candidate before applying the
253
+ geometric test that rejects almost all of them. The cheap test runs first now, and it
254
+ reuses the words the page has already been asked for instead of clustering its
255
+ characters into words a second time.
256
+ - Worker processes fork from a server that has loaded the models, instead of each
257
+ loading its own copy, and the number of them is chosen from the size of the folder.
258
+
259
+ ### Fixed
260
+
261
+ - Two extractors that read a page in a different order are now reported as
262
+ disagreeing. The comparison used to be a character count, which cannot see the
263
+ case it most needs to: on a two-column page, one library reads down the columns
264
+ and another straight across, interleaving every sentence, and both return the
265
+ same number of characters. Readings are now compared in order, and the report
266
+ names the kind of difference rather than only that there was one.
267
+ - Pointing at a sensitive mark on the page layout does something. The marks are drawn
268
+ as outlines, and an SVG shape with no fill answers the pointer only along its stroke —
269
+ on a mark six pixels tall that is two hairlines, so hovering the middle of one hit
270
+ nothing. The explanation also appears under the page at once rather than waiting for
271
+ the browser's own tooltip, and the marks can be tabbed to.
272
+ - DOCX merged cells were counted by object identity, which made the count depend on
273
+ memory reuse and differ between processes reading the same file. They are read from
274
+ the markup now.
275
+ - The OCR engine registers its own shutdown cleanup, at the point it creates the
276
+ native threads, instead of relying on another module importing it during
277
+ interpreter teardown — when the import machinery may already be gone, and a run
278
+ that had already succeeded aborts with a mutex error.
279
+ - The file list sat in the right place only some of the time. `.spread:not([hidden])` also
280
+ matched a panel hidden along with the whole Pages view, because the attribute sits on the
281
+ container, so the list was being centred on a zero-height ghost whenever the signals tab
282
+ was showing. It also now re-aligns when a page is first shown, which it could not do while
283
+ it was hidden.
284
+ - An imported price is no longer reported as a verification that went stale. It was
285
+ never claimed to be verified, and warning once per model buried the run's real
286
+ limitations under a dozen copies of what the provenance entry says once.
287
+
288
+ ### Documentation
289
+
290
+ - The README is written for someone running the tool: what it does, how to run it, what the
291
+ flags mean. Architecture, adding a signal, fixtures and the release process moved to
292
+ CONTRIBUTING.md, and this changelog now ships inside the package, so an installed copy can
293
+ say what changed in the version you have.
294
+ - The README says how to keep an installed copy up to date, and why `--reinstall`
295
+ matters as much as `--force`.
296
+
297
+ ## [0.1.0]
298
+
299
+ First release.
300
+
301
+ ### Audit
302
+
303
+ - Three independent components — cost, extraction readiness, sensitive data — run
304
+ together or one at a time. The report states which of them ran.
305
+ - Cost estimated from measured page geometry and a real tokenizer, across text, OCR and
306
+ vision paths for every priced model. Vision formulas and prices live in
307
+ `pricing.yaml` with a `last_verified` date; a price older than 90 days is reported as
308
+ stale rather than quoted plainly.
309
+ - Nineteen readiness signals, each contributing a measured value, a rating and one
310
+ sentence saying why. Weights and thresholds are configuration, not code, and are
311
+ printed alongside any score. A new signal is one new file plus a registration.
312
+ - Sensitive data scan covering national identifiers for the UK, US, IE, NL, PT, ES, FR
313
+ and DE, EU VAT numbers, IBANs, payment cards and local NER for names and
314
+ organisations, each with its own checksum or validator.
315
+ - Wall-clock time measured per document and projected to a backlog, including the
316
+ observed OCR rate on the machine that ran it.
317
+
318
+ ### Safety
319
+
320
+ - The network guard replaces the socket module's outbound entry points before any
321
+ document is opened, in every process, and the test suite asserts a full audit
322
+ completes with it armed. `run.offline_guard` records this in every report.
323
+ - Matched values are masked to the last four characters at most. `--reveal` prints them
324
+ in full, and the report says on its face that it was used. Categories marked
325
+ `never_reveal` stay masked regardless.
326
+
327
+ ### Reports
328
+
329
+ - One self-contained HTML file — no network, no bundler, no server — with summary, cost,
330
+ security and per-document pages, and a document browser showing each page beside what
331
+ was extracted from it and what OCR read.
332
+ - A JSON report of the same run, and `--print-json` for piping into another tool.
333
+ - A limitations section generated from the run's own facts: what could not be opened,
334
+ which pages held no readable text, which detectors were unavailable, which prices are
335
+ unverified.
336
+
337
+ ### Running it
338
+
339
+ - `complydoc` on its own audits the current directory.
340
+ - `--jobs` spreads documents over a process pool, `--sample` reads a deterministic
341
+ type-proportional subset of a large folder, `--password` opens encrypted PDFs.
342
+ - A packaged agent skill, installed with the tool, so an agent can be told to use it.
343
+
344
+ [Unreleased]: https://github.com/duartecaldascardoso/complydoc/compare/v0.3.0...HEAD
345
+ [0.3.0]: https://github.com/duartecaldascardoso/complydoc/compare/v0.2.0...v0.3.0
346
+ [0.2.0]: https://github.com/duartecaldascardoso/complydoc/compare/v0.1.0...v0.2.0
347
+ [0.1.0]: https://github.com/duartecaldascardoso/complydoc/releases/tag/v0.1.0
complydoc/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ """complydoc — an offline pre-purchase diagnostic for document automation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.3.0"
6
+ __all__ = ["__version__"]