docintel-platform 1.0.2__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.
- docintel/__init__.py +6 -0
- docintel/app.py +45 -0
- docintel/auth/__init__.py +12 -0
- docintel/auth/api_keys.py +48 -0
- docintel/auth/limiter.py +41 -0
- docintel/auth/middleware.py +34 -0
- docintel/auth/oidc.py +45 -0
- docintel/cli.py +21 -0
- docintel/client.py +193 -0
- docintel/config.py +20 -0
- docintel/jobs/__init__.py +16 -0
- docintel/jobs/helpers.py +38 -0
- docintel/jobs/models.py +78 -0
- docintel/jobs/queue.py +75 -0
- docintel/jobs/store.py +82 -0
- docintel/jobs/tasks.py +173 -0
- docintel/jobs/webhooks.py +32 -0
- docintel/openapi/__init__.py +1 -0
- docintel/openapi/openapi.yaml +380 -0
- docintel/ops/__init__.py +1 -0
- docintel/ops/logging.py +40 -0
- docintel/ops/metrics.py +57 -0
- docintel/ops/middleware.py +40 -0
- docintel/routes/__init__.py +1 -0
- docintel/routes/jobs.py +26 -0
- docintel/routes/match.py +43 -0
- docintel/routes/openapi_docs.py +57 -0
- docintel/routes/ops.py +22 -0
- docintel/routes/pdf.py +420 -0
- docintel/routes/text.py +41 -0
- docintel/services/__init__.py +1 -0
- docintel/services/matching/__init__.py +6 -0
- docintel/services/matching/models.py +19 -0
- docintel/services/matching/scorer.py +64 -0
- docintel/services/pdf/__init__.py +26 -0
- docintel/services/pdf/annotator.py +188 -0
- docintel/services/pdf/models.py +104 -0
- docintel/services/pdf/ocr.py +130 -0
- docintel/services/pdf/pii.py +105 -0
- docintel/services/pdf/presets.py +26 -0
- docintel/services/pdf/search.py +29 -0
- docintel/services/pdf/sensitive.py +212 -0
- docintel/services/pdf/structure.py +118 -0
- docintel/services/pdf/structure_llm.py +136 -0
- docintel/services/pdf/structure_render.py +136 -0
- docintel/services/pdf/structure_schema.py +99 -0
- docintel/services/summary/__init__.py +6 -0
- docintel/services/summary/models.py +21 -0
- docintel/services/summary/textrank.py +57 -0
- docintel/ui.py +347 -0
- docintel/wsgi.py +5 -0
- docintel_platform-1.0.2.dist-info/METADATA +607 -0
- docintel_platform-1.0.2.dist-info/RECORD +56 -0
- docintel_platform-1.0.2.dist-info/WHEEL +5 -0
- docintel_platform-1.0.2.dist-info/entry_points.txt +3 -0
- docintel_platform-1.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: docintel-platform
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: Document intelligence API and Python client for PDF OCR, PII detection, LLM structuring, matching, and summarization.
|
|
5
|
+
Author: Babandeep Singh
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/baban9/document-intelligence-platform
|
|
8
|
+
Project-URL: Repository, https://github.com/baban9/document-intelligence-platform
|
|
9
|
+
Project-URL: Documentation, https://github.com/baban9/document-intelligence-platform#readme
|
|
10
|
+
Project-URL: Issues, https://github.com/baban9/document-intelligence-platform/issues
|
|
11
|
+
Keywords: nlp,pdf,flask,document-ai,resume-matching,ocr,pii,presidio,openapi,document-intelligence
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Framework :: Flask
|
|
20
|
+
Classifier: Topic :: Text Processing
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: flask>=3.0.3
|
|
26
|
+
Requires-Dist: werkzeug>=3.0.3
|
|
27
|
+
Requires-Dist: pymupdf>=1.24.10
|
|
28
|
+
Requires-Dist: scikit-learn>=1.5.2
|
|
29
|
+
Requires-Dist: networkx>=3.2.1
|
|
30
|
+
Requires-Dist: numpy>=1.26.4
|
|
31
|
+
Requires-Dist: gunicorn>=23.0.0
|
|
32
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
33
|
+
Requires-Dist: requests>=2.32.3
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=8.3.3; extra == "dev"
|
|
36
|
+
Requires-Dist: build>=1.2.2; extra == "dev"
|
|
37
|
+
Requires-Dist: twine>=5.1.1; extra == "dev"
|
|
38
|
+
Requires-Dist: fakeredis>=2.26.2; extra == "dev"
|
|
39
|
+
Provides-Extra: ocr
|
|
40
|
+
Requires-Dist: easyocr>=1.7.2; extra == "ocr"
|
|
41
|
+
Requires-Dist: presidio-analyzer>=2.2.354; extra == "ocr"
|
|
42
|
+
Requires-Dist: spacy>=3.7.0; extra == "ocr"
|
|
43
|
+
Requires-Dist: opencv-python-headless>=4.10.0; extra == "ocr"
|
|
44
|
+
Requires-Dist: torch>=2.4.1; extra == "ocr"
|
|
45
|
+
Provides-Extra: ui
|
|
46
|
+
Requires-Dist: gradio>=4.44.0; extra == "ui"
|
|
47
|
+
Requires-Dist: requests>=2.32.3; extra == "ui"
|
|
48
|
+
Provides-Extra: llm
|
|
49
|
+
Requires-Dist: openai>=1.54.0; extra == "llm"
|
|
50
|
+
Provides-Extra: jobs
|
|
51
|
+
Requires-Dist: redis>=5.0.8; extra == "jobs"
|
|
52
|
+
Requires-Dist: rq>=1.16.2; extra == "jobs"
|
|
53
|
+
Provides-Extra: auth
|
|
54
|
+
Requires-Dist: flask-limiter>=3.8.0; extra == "auth"
|
|
55
|
+
Requires-Dist: PyJWT>=2.9.0; extra == "auth"
|
|
56
|
+
Requires-Dist: cryptography>=43.0.0; extra == "auth"
|
|
57
|
+
Provides-Extra: all
|
|
58
|
+
Requires-Dist: easyocr>=1.7.2; extra == "all"
|
|
59
|
+
Requires-Dist: presidio-analyzer>=2.2.354; extra == "all"
|
|
60
|
+
Requires-Dist: spacy>=3.7.0; extra == "all"
|
|
61
|
+
Requires-Dist: opencv-python-headless>=4.10.0; extra == "all"
|
|
62
|
+
Requires-Dist: torch>=2.4.1; extra == "all"
|
|
63
|
+
Requires-Dist: openai>=1.54.0; extra == "all"
|
|
64
|
+
Requires-Dist: redis>=5.0.8; extra == "all"
|
|
65
|
+
Requires-Dist: rq>=1.16.2; extra == "all"
|
|
66
|
+
Requires-Dist: flask-limiter>=3.8.0; extra == "all"
|
|
67
|
+
Requires-Dist: PyJWT>=2.9.0; extra == "all"
|
|
68
|
+
Requires-Dist: cryptography>=43.0.0; extra == "all"
|
|
69
|
+
Requires-Dist: gradio>=4.44.0; extra == "all"
|
|
70
|
+
|
|
71
|
+
# Document Intelligence Platform
|
|
72
|
+
|
|
73
|
+
[](https://www.python.org/downloads/)
|
|
74
|
+
[](https://flask.palletsprojects.com/)
|
|
75
|
+
[](docker-compose.yml)
|
|
76
|
+
[](LICENSE)
|
|
77
|
+
[](tests/)
|
|
78
|
+
|
|
79
|
+
Production-ready document AI: PDF annotation, scanned-document PII detection (EasyOCR + Presidio), LLM PDF structuring, resume matching, and extractive summarization. Ship as a REST API, a Gradio upload GUI, or both via Docker.
|
|
80
|
+
|
|
81
|
+
**Version:** 1.0.0
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Install from PyPI
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install docintel
|
|
89
|
+
|
|
90
|
+
# Full stack (OCR, LLM, jobs, auth, UI)
|
|
91
|
+
pip install "docintel[all]"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Python client:**
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from docintel import DocintelClient
|
|
98
|
+
|
|
99
|
+
client = DocintelClient("http://127.0.0.1:5000", api_key="your-key")
|
|
100
|
+
result = client.match_resume(resume_text, job_description)
|
|
101
|
+
pdf_bytes = client.structure_pdf("scan.pdf", async_job=True)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Publish a release to PyPI** (maintainers): tag `v1.0.0` and push, or run `make publish-pypi` with `TWINE_USERNAME` / `TWINE_PASSWORD` or PyPI trusted publishing configured in GitHub Actions.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Deploy in one command
|
|
109
|
+
|
|
110
|
+
No local Python setup required.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
git clone https://github.com/baban9/document-intelligence-platform.git
|
|
114
|
+
cd document-intelligence-platform
|
|
115
|
+
make docker-up
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
| Service | URL | Use case |
|
|
119
|
+
|---------|-----|----------|
|
|
120
|
+
| **Gradio GUI** | http://127.0.0.1:7860 | Upload PDFs, no code |
|
|
121
|
+
| **REST API** | http://127.0.0.1:5000 | Integrations, curl, apps |
|
|
122
|
+
| Health | http://127.0.0.1:5000/health | Load balancer probe |
|
|
123
|
+
| API docs | http://127.0.0.1:5000/docs | Swagger UI (OpenAPI) |
|
|
124
|
+
| OpenAPI | http://127.0.0.1:5000/openapi.json | Machine-readable contract |
|
|
125
|
+
| Metrics | http://127.0.0.1:5000/metrics | Request counts and latency |
|
|
126
|
+
|
|
127
|
+
First startup can take a few minutes while EasyOCR and Presidio models download inside the container.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
make docker-logs # follow api + ui logs
|
|
131
|
+
make docker-down # stop services
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Optional overrides: copy `.env.example` to `.env` (ports, log level, worker count).
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Gradio upload GUI
|
|
139
|
+
|
|
140
|
+
Open http://127.0.0.1:7860 after `make docker-up` (or `make run-ui` locally).
|
|
141
|
+
|
|
142
|
+
| Tab | What it does |
|
|
143
|
+
|-----|--------------|
|
|
144
|
+
| **PDF regex annotate** | Search by pattern, highlight or redact |
|
|
145
|
+
| **Sensitive PDF (OCR + Presidio)** | Scanned docs: OCR, detect PII, annotate boxes |
|
|
146
|
+
| **PDF structure (LLM)** | Scanned or messy PDFs to curated structured PDF |
|
|
147
|
+
| **Resume matching** | Score resume vs job description |
|
|
148
|
+
| **Text summarization** | Extractive summary with TextRank |
|
|
149
|
+
|
|
150
|
+
The GUI calls the same REST API as external clients. Set `DOCINTEL_API_URL` if the API runs on a different host.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## What you get
|
|
155
|
+
|
|
156
|
+
| Capability | API | GUI |
|
|
157
|
+
|------------|-----|-----|
|
|
158
|
+
| PDF regex search and annotation | `POST /v1/pdf/annotate` | PDF regex annotate tab |
|
|
159
|
+
| Scanned PDF PII detection | `POST /v1/pdf/detect-sensitive` | Sensitive PDF tab |
|
|
160
|
+
| LLM PDF structuring | `POST /v1/pdf/structure` | PDF structure tab |
|
|
161
|
+
| Presidio entity catalog | `GET /v1/pdf/entities` | - |
|
|
162
|
+
| Resume vs job matching | `POST /v1/match/resume` | Resume matching tab |
|
|
163
|
+
| Extractive summarization | `POST /v1/text/summarize` | Text summarization tab |
|
|
164
|
+
| Health and metrics | `GET /health`, `GET /metrics` | - |
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Why this exists
|
|
169
|
+
|
|
170
|
+
HR, compliance, and research teams often maintain separate tools:
|
|
171
|
+
|
|
172
|
+
- a PDF highlighter or redaction script
|
|
173
|
+
- a resume keyword matcher
|
|
174
|
+
- a notebook for summarization
|
|
175
|
+
|
|
176
|
+
That split means duplicated config, no shared metrics, and broken workflows on **scanned PDFs** where text extraction returns empty. This platform unifies those flows behind one API and one upload GUI.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Problems it solves
|
|
181
|
+
|
|
182
|
+
### HR and recruiting
|
|
183
|
+
|
|
184
|
+
| Problem | Solution |
|
|
185
|
+
|---------|----------|
|
|
186
|
+
| Manual resume screening at scale | TF-IDF match score plus keyword overlap |
|
|
187
|
+
| Long ATS exports before phone screens | Extractive summary in seconds |
|
|
188
|
+
| Inconsistent reviewer shortlists | Same scoring logic every time |
|
|
189
|
+
|
|
190
|
+
### Compliance and legal
|
|
191
|
+
|
|
192
|
+
| Problem | Solution |
|
|
193
|
+
|---------|----------|
|
|
194
|
+
| Regex search on digital contracts | `POST /v1/pdf/annotate` |
|
|
195
|
+
| **Scanned** contracts with no text layer | EasyOCR + Presidio on `POST /v1/pdf/detect-sensitive` |
|
|
196
|
+
| Redact SSN, email, phone before external share | Highlight or redact on exact bounding boxes |
|
|
197
|
+
| Audit trail | Structured JSON logs and `/metrics` |
|
|
198
|
+
|
|
199
|
+
### Research intake
|
|
200
|
+
|
|
201
|
+
| Problem | Solution |
|
|
202
|
+
|---------|----------|
|
|
203
|
+
| Long reports need triage | TextRank summarization |
|
|
204
|
+
| Key terms buried in PDFs | Regex annotate or Presidio entity detection |
|
|
205
|
+
|
|
206
|
+
### Before vs after
|
|
207
|
+
|
|
208
|
+
| Before | After |
|
|
209
|
+
|--------|-------|
|
|
210
|
+
| 3 scripts, 3 configs | 1 API + 1 GUI + 1 Docker deploy |
|
|
211
|
+
| Scanned PDFs fail regex tools | OCR fallback with Presidio PII boxes |
|
|
212
|
+
| Desktop-only redaction | Programmatic HTTP + downloadable output PDF |
|
|
213
|
+
| No observability | JSON logs, health check, metrics endpoint |
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Local development
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
git clone https://github.com/baban9/document-intelligence-platform.git
|
|
221
|
+
cd document-intelligence-platform
|
|
222
|
+
make setup
|
|
223
|
+
make setup-ocr # EasyOCR + Presidio + spaCy en model
|
|
224
|
+
make setup-llm # OpenAI client for PDF structuring
|
|
225
|
+
make setup-ui # Gradio client
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Terminal 1 (API):**
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
make run
|
|
232
|
+
curl http://127.0.0.1:5000/health
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Terminal 2 (GUI):**
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
make run-ui
|
|
239
|
+
# open http://127.0.0.1:7860
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Tests:**
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
make test
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Install extras:**
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
pip install -e ".[dev]" # tests
|
|
252
|
+
pip install -e ".[ocr]" # scanned PDF pipeline
|
|
253
|
+
pip install -e ".[llm]" # LLM PDF structuring
|
|
254
|
+
pip install -e ".[ui]" # Gradio GUI
|
|
255
|
+
python -m spacy download en_core_web_sm
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Architecture
|
|
261
|
+
|
|
262
|
+
Modular monolith: one Flask app, separate service modules, optional Gradio front end.
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
Browser / curl Docker Compose
|
|
266
|
+
| |
|
|
267
|
+
v v
|
|
268
|
+
+-----------+ +-------+--------+
|
|
269
|
+
| Gradio | -- HTTP :5000 --> | Flask API |
|
|
270
|
+
| UI :7860 | | (Gunicorn) |
|
|
271
|
+
+-----------+ +-------+--------+
|
|
272
|
+
|
|
|
273
|
+
+-----------------------------+-----------------------------+
|
|
274
|
+
| | |
|
|
275
|
+
+-----v-----+ +-----v-----+ +-----v-----+
|
|
276
|
+
| PDF | | Matching | | Summary |
|
|
277
|
+
| service | | service | | service |
|
|
278
|
+
+-----------+ +-----------+ +-----------+
|
|
279
|
+
| | |
|
|
280
|
+
PyMuPDF regex TF-IDF cosine TextRank graph
|
|
281
|
+
EasyOCR (scanned) keyword overlap extractive output
|
|
282
|
+
Presidio PII boxes LLM PDF structure
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Decision records: [modular monolith](docs/adr/001-modular-monolith.md), [OCR + Presidio](docs/adr/002-ocr-presidio-pipeline.md)
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## API reference
|
|
290
|
+
|
|
291
|
+
OpenAPI spec: `GET /openapi.json` | Interactive docs: `GET /docs`
|
|
292
|
+
|
|
293
|
+
### Sensitive PDF detection (scanned + digital)
|
|
294
|
+
|
|
295
|
+
When native PDF text is empty, the service runs **EasyOCR (English)**, analyzes text with **Microsoft Presidio**, and returns a new PDF with highlights or redactions on bounding boxes. Optionally embeds an invisible text layer so the output stays searchable.
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
curl -X POST http://127.0.0.1:5000/v1/pdf/detect-sensitive \
|
|
299
|
+
-F "file=@scanned_contract.pdf" \
|
|
300
|
+
-F "action=Highlight" \
|
|
301
|
+
-o marked_contract.pdf
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
JSON report with findings:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
curl -X POST "http://127.0.0.1:5000/v1/pdf/detect-sensitive?format=json" \
|
|
308
|
+
-F "file=@scanned_contract.pdf" \
|
|
309
|
+
-F "action=Redact" \
|
|
310
|
+
-F "entities=EMAIL_ADDRESS,PHONE_NUMBER,US_SSN,CREDIT_CARD,PERSON"
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
List Presidio entities (extend with [custom recognizers](https://microsoft.github.io/presidio/analyzer/adding_recognizers/)):
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
curl http://127.0.0.1:5000/v1/pdf/entities
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
| Field | Required | Description |
|
|
320
|
+
|-------|----------|-------------|
|
|
321
|
+
| `file` | Yes | PDF upload |
|
|
322
|
+
| `action` | No | `Highlight` (default), `Redact`, `Frame`, `Underline`, `Squiggly`, `Strikeout` |
|
|
323
|
+
| `entities` | No | Comma-separated Presidio types (default preset below) |
|
|
324
|
+
| `pattern` | No | Extra regex on top of Presidio |
|
|
325
|
+
| `force_ocr` | No | `true` to OCR every page |
|
|
326
|
+
| `add_text_layer` | No | `true` (default) adds searchable invisible text |
|
|
327
|
+
| `min_score` | No | Presidio confidence threshold (default `0.35`) |
|
|
328
|
+
| `async` | No | `true` queues the job (returns `202`); poll `GET /v1/jobs/<job_id>` |
|
|
329
|
+
| `callback_url` | No | Webhook URL when async job completes |
|
|
330
|
+
|
|
331
|
+
**Async mode:**
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
curl -X POST "http://127.0.0.1:5000/v1/pdf/detect-sensitive?async=true" \
|
|
335
|
+
-H "Authorization: Bearer your-key" \
|
|
336
|
+
-F "file=@scanned_contract.pdf" \
|
|
337
|
+
-F "action=Highlight"
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Default Presidio entities:** `EMAIL_ADDRESS`, `PHONE_NUMBER`, `US_SSN`, `CREDIT_CARD`, `US_BANK_NUMBER`, `US_DRIVER_LICENSE`, `US_ITIN`, `US_PASSPORT`, `PERSON`, `LOCATION`, `DATE_TIME`, `IP_ADDRESS`, `IBAN_CODE`, `MEDICAL_LICENSE`, `URL`.
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
### LLM PDF structuring (scanned to curated PDF)
|
|
345
|
+
|
|
346
|
+
Turn unstructured or scanned PDFs into a clean digital PDF. EasyOCR extracts text when the native layer is missing. An OpenAI-compatible LLM cleans and structures the content, then the service returns a curated typeset PDF or a searchable layer on the original pages.
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
curl -X POST http://127.0.0.1:5000/v1/pdf/structure \
|
|
350
|
+
-F "file=@scanned_notes.pdf" \
|
|
351
|
+
-F "mode=curate" \
|
|
352
|
+
-o structured_notes.pdf
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
| Field | Required | Description |
|
|
356
|
+
|-------|----------|-------------|
|
|
357
|
+
| `file` | Yes | PDF upload |
|
|
358
|
+
| `mode` | No | `curate` (default, new typeset PDF) or `searchable` (invisible text on original pages) |
|
|
359
|
+
| `force_ocr` | No | `true` to OCR every page |
|
|
360
|
+
| `redact_before_llm` | No | `true` masks Presidio PII before text is sent to the LLM |
|
|
361
|
+
| `callback_url` | No | Webhook URL notified when an async job completes or fails |
|
|
362
|
+
| `async` | No | `true` queues the job in Redis (returns `202`); `false` waits in the request (default) |
|
|
363
|
+
|
|
364
|
+
**Async mode (recommended for scanned PDFs):**
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
# 1) Queue the job
|
|
368
|
+
curl -X POST "http://127.0.0.1:5000/v1/pdf/structure?async=true" \
|
|
369
|
+
-F "file=@scanned_notes.pdf" \
|
|
370
|
+
-F "mode=curate"
|
|
371
|
+
|
|
372
|
+
# 2) Poll until job_status is completed
|
|
373
|
+
curl http://127.0.0.1:5000/v1/jobs/<job_id>
|
|
374
|
+
|
|
375
|
+
# 3) Download from download_url in the poll response
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Start Redis and the worker locally: `make setup-jobs`, then `make run-worker` in a second terminal. Docker Compose starts `redis`, `api`, and `worker` automatically.
|
|
379
|
+
|
|
380
|
+
**Model used:** OpenAI **`gpt-4o-mini`** by default (set via `DOCINTEL_LLM_MODEL`). The service uses the official OpenAI Python client and any OpenAI-compatible endpoint if you set `DOCINTEL_LLM_BASE_URL`.
|
|
381
|
+
|
|
382
|
+
**Install LLM extras:**
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
pip install -e ".[ocr,llm,jobs]"
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Get an OpenAI API key**
|
|
389
|
+
|
|
390
|
+
Official guide: [OpenAI API quickstart](https://platform.openai.com/docs/quickstart)
|
|
391
|
+
Manage keys: [platform.openai.com/api-keys](https://platform.openai.com/api-keys)
|
|
392
|
+
|
|
393
|
+
1. Create an account at [platform.openai.com](https://platform.openai.com) (or sign in).
|
|
394
|
+
2. Open [API keys](https://platform.openai.com/api-keys) and click **Create new secret key**.
|
|
395
|
+
3. Copy the key once (it is shown only at creation time).
|
|
396
|
+
4. Add billing or credits on the OpenAI platform if required for your account.
|
|
397
|
+
5. Export the key before starting the API:
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
export DOCINTEL_LLM_API_KEY="sk-..."
|
|
401
|
+
export DOCINTEL_LLM_MODEL="gpt-4o-mini" # optional; this is the default
|
|
402
|
+
make run
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
For Docker or persistent local use, copy `.env.example` to `.env` and set `DOCINTEL_LLM_API_KEY` there. Do not commit `.env` or share the key in git.
|
|
406
|
+
|
|
407
|
+
**Optional:** use another OpenAI-compatible provider by setting `DOCINTEL_LLM_BASE_URL` and the matching model name for that provider.
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
### PDF regex annotation
|
|
412
|
+
|
|
413
|
+
For digital PDFs with a text layer.
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
curl -X POST http://127.0.0.1:5000/v1/pdf/annotate \
|
|
417
|
+
-F "file=@contract.pdf" \
|
|
418
|
+
-F "pattern=CONFIDENTIAL" \
|
|
419
|
+
-F "action=Redact" \
|
|
420
|
+
-o redacted_contract.pdf
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
| Action | Description |
|
|
424
|
+
|--------|-------------|
|
|
425
|
+
| `Highlight` | Yellow highlight (default) |
|
|
426
|
+
| `Redact` | Black out matched text |
|
|
427
|
+
| `Frame` | Red bounding box |
|
|
428
|
+
| `Underline` / `Squiggly` / `Strikeout` | Text markup |
|
|
429
|
+
| `Remove` | Delete existing annotations |
|
|
430
|
+
|
|
431
|
+
Optional: `pages` (comma-separated, zero-based), `?format=json` for metadata + download URL.
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
### Resume matching
|
|
436
|
+
|
|
437
|
+
```bash
|
|
438
|
+
curl -X POST http://127.0.0.1:5000/v1/match/resume \
|
|
439
|
+
-H "Content-Type: application/json" \
|
|
440
|
+
-d '{
|
|
441
|
+
"resume": "Python engineer with Flask, pytest, Docker, and NLP experience.",
|
|
442
|
+
"job_description": "Seeking Python developer with Flask, Docker, API, and NLP skills.",
|
|
443
|
+
"top_keywords": 10
|
|
444
|
+
}'
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
```json
|
|
448
|
+
{
|
|
449
|
+
"status": "ok",
|
|
450
|
+
"score": 42.15,
|
|
451
|
+
"matched_keywords": ["python", "flask", "docker", "nlp"],
|
|
452
|
+
"missing_keywords": ["developer", "api", "skills"]
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
### Text summarization
|
|
459
|
+
|
|
460
|
+
```bash
|
|
461
|
+
curl -X POST http://127.0.0.1:5000/v1/text/summarize \
|
|
462
|
+
-H "Content-Type: application/json" \
|
|
463
|
+
-d '{"text": "Your long document here...", "sentences": 3}'
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
### Metrics
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
curl http://127.0.0.1:5000/metrics
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Returns request counts, error counts, average latency, and per-endpoint breakdown. Metrics are per Gunicorn worker; use `WEB_CONCURRENCY=1` for OCR workloads (Docker default).
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Configuration
|
|
479
|
+
|
|
480
|
+
| Variable | Default | Purpose |
|
|
481
|
+
|----------|---------|---------|
|
|
482
|
+
| `DOCINTEL_HOST` | `127.0.0.1` | API bind address (`0.0.0.0` in Docker) |
|
|
483
|
+
| `DOCINTEL_PORT` | `5000` | API port |
|
|
484
|
+
| `DOCINTEL_UPLOAD_DIR` | `uploads` | PDF job storage |
|
|
485
|
+
| `DOCINTEL_LOG_LEVEL` | `INFO` | JSON log verbosity |
|
|
486
|
+
| `WEB_CONCURRENCY` | `1` | Gunicorn workers (keep at 1 for OCR) |
|
|
487
|
+
| `DOCINTEL_API_URL` | `http://127.0.0.1:5000` | Gradio UI backend URL |
|
|
488
|
+
| `DOCINTEL_LLM_API_KEY` | unset | OpenAI-compatible API key for `/v1/pdf/structure` |
|
|
489
|
+
| `DOCINTEL_LLM_MODEL` | `gpt-4o-mini` | Model name for structuring |
|
|
490
|
+
| `DOCINTEL_LLM_BASE_URL` | unset | Optional compatible API base URL |
|
|
491
|
+
| `DOCINTEL_API_KEYS` | unset | Comma-separated API keys (`Authorization: Bearer ...`) |
|
|
492
|
+
| `DOCINTEL_AUTH_REQUIRED` | `false` | Require auth on `/v1/*` when `true` or keys are set |
|
|
493
|
+
| `DOCINTEL_RATE_LIMIT_ENABLED` | `true` | Per-key rate limits via Redis |
|
|
494
|
+
| `DOCINTEL_OIDC_ISSUER` | unset | Optional OIDC issuer for JWT bearer tokens |
|
|
495
|
+
| `DOCINTEL_OIDC_AUDIENCE` | unset | Expected JWT audience |
|
|
496
|
+
| `DOCINTEL_OIDC_JWKS_URL` | unset | JWKS URL (defaults to issuer `/.well-known/jwks.json`) |
|
|
497
|
+
| `DOCINTEL_API_KEY` | unset | API key used by the Gradio UI client |
|
|
498
|
+
| `GRADIO_SERVER_NAME` | `127.0.0.1` | Gradio bind (`0.0.0.0` in Docker) |
|
|
499
|
+
| `GRADIO_SERVER_PORT` | `7860` | Gradio port |
|
|
500
|
+
|
|
501
|
+
### API authentication
|
|
502
|
+
|
|
503
|
+
Protect `/v1/*` routes with API keys and optional OIDC JWTs.
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
export DOCINTEL_API_KEYS="dev-key-1,dev-key-2"
|
|
507
|
+
export DOCINTEL_AUTH_REQUIRED=true
|
|
508
|
+
|
|
509
|
+
curl -H "Authorization: Bearer dev-key-1" \
|
|
510
|
+
http://127.0.0.1:5000/v1/pdf/entities
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**OIDC (enterprise SSO tokens):**
|
|
514
|
+
|
|
515
|
+
```bash
|
|
516
|
+
export DOCINTEL_OIDC_ISSUER="https://your-idp.example.com"
|
|
517
|
+
export DOCINTEL_OIDC_AUDIENCE="docintel-api"
|
|
518
|
+
pip install -e ".[auth]"
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Send `Authorization: Bearer <jwt>` from your identity provider. API keys still work when both are configured.
|
|
522
|
+
|
|
523
|
+
Install auth extras: `pip install -e ".[auth]"` (Flask-Limiter + PyJWT).
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## Project layout
|
|
528
|
+
|
|
529
|
+
```
|
|
530
|
+
document-intelligence-platform/
|
|
531
|
+
src/docintel/
|
|
532
|
+
app.py Flask factory
|
|
533
|
+
ui.py Gradio upload GUI
|
|
534
|
+
wsgi.py Gunicorn entry
|
|
535
|
+
routes/ HTTP endpoints
|
|
536
|
+
services/
|
|
537
|
+
pdf/ PyMuPDF, EasyOCR, Presidio
|
|
538
|
+
matching/ TF-IDF resume scoring
|
|
539
|
+
summary/ TextRank summarizer
|
|
540
|
+
ops/ JSON logging, metrics
|
|
541
|
+
run.py Start API locally
|
|
542
|
+
run_ui.py Start Gradio locally
|
|
543
|
+
Dockerfile API + OCR stack image
|
|
544
|
+
docker-compose.yml api + ui services
|
|
545
|
+
docs/adr/ Architecture decisions
|
|
546
|
+
tests/ pytest suite
|
|
547
|
+
Makefile
|
|
548
|
+
pyproject.toml
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## Makefile commands
|
|
554
|
+
|
|
555
|
+
| Command | Description |
|
|
556
|
+
|---------|-------------|
|
|
557
|
+
| `make setup` | venv + core package |
|
|
558
|
+
| `make setup-ocr` | EasyOCR + Presidio + spaCy model |
|
|
559
|
+
| `make setup-llm` | OpenAI client for PDF structuring |
|
|
560
|
+
| `make build-dist` | Build PyPI wheel and sdist |
|
|
561
|
+
| `make publish-pypi` | Upload to PyPI with twine |
|
|
562
|
+
| `make setup-ui` | Gradio GUI dependencies |
|
|
563
|
+
| `make run` | Start API (:5000) |
|
|
564
|
+
| `make run-ui` | Start Gradio (:7860) |
|
|
565
|
+
| `make test` | Run pytest |
|
|
566
|
+
| `make docker-up` | Build and start API + UI containers |
|
|
567
|
+
| `make docker-down` | Stop containers |
|
|
568
|
+
| `make docker-logs` | Tail all service logs |
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
## Roadmap
|
|
573
|
+
|
|
574
|
+
| Milestone | Scope | Status |
|
|
575
|
+
|-----------|-------|--------|
|
|
576
|
+
| M1 | Project scaffold, health endpoint | Done |
|
|
577
|
+
| M2 | PDF regex annotation | Done |
|
|
578
|
+
| M3 | Resume matching | Done |
|
|
579
|
+
| M4 | Extractive summarization | Done |
|
|
580
|
+
| M5 | Docker, logging, metrics | Done |
|
|
581
|
+
| M5+ | OCR + Presidio scanned PDF pipeline | Done |
|
|
582
|
+
| M5+ | Gradio upload GUI | Done |
|
|
583
|
+
| M8 | LLM PDF structuring | Done |
|
|
584
|
+
| M6 | Offline eval harness | Planned |
|
|
585
|
+
| M7 | Production checklist | Planned |
|
|
586
|
+
|
|
587
|
+
Details: [docs/ROADMAP.md](docs/ROADMAP.md)
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
## Limits and notes
|
|
592
|
+
|
|
593
|
+
- OCR requests are CPU-heavy; expect higher latency on scanned PDFs.
|
|
594
|
+
- Presidio entity types are extensible; defaults cover common US PII.
|
|
595
|
+
- First EasyOCR run downloads models (~100MB+).
|
|
596
|
+
- LLM structuring sends page text to your configured model provider when native OCR text is used.
|
|
597
|
+
- Not intended for real-time collaborative editing or generative long-form writing.
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## License
|
|
602
|
+
|
|
603
|
+
MIT. See [LICENSE](LICENSE).
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
Built by [Babandeep Singh](https://github.com/baban9). Open an issue for bugs or feature requests.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
docintel/__init__.py,sha256=Fd5aFqnl4eeo7Zr6mCUscGmjNYGyEBoV-AGnzjmNBIA,181
|
|
2
|
+
docintel/app.py,sha256=I0mS7XeUMOQYwZnTIKzfO6C3d03VSV_zRqnNz_uHgUA,1303
|
|
3
|
+
docintel/cli.py,sha256=V0L26Iv6-dKwLDVekUUkeNCytUMXgr4InsW9ZAXhcKE,554
|
|
4
|
+
docintel/client.py,sha256=6ej3x4fj6ZaDZVIWiuzpgcDr0SwhPbyD0AGexYeGKGE,6485
|
|
5
|
+
docintel/config.py,sha256=R3LcNliZCrswkka76kXSX5BBEcC6ElHgYnTBuVoIE9U,965
|
|
6
|
+
docintel/ui.py,sha256=mKuyIGSSvuVPlN20FS8z-ygMByu5hxQqvevZkrTpoNY,12484
|
|
7
|
+
docintel/wsgi.py,sha256=S-e_kd6igFr1L6BDfVq8hHtaObvDH4T-z0W_gaWPULM,104
|
|
8
|
+
docintel/auth/__init__.py,sha256=O9U9PzR97iOmKh8SDEPuFrfJzHIFHAqF_4rSswSiMcg,321
|
|
9
|
+
docintel/auth/api_keys.py,sha256=c4WgPMbNbMQjohhyw4pfaI3-8gL5tO-cHmcqdGRGE-Q,1186
|
|
10
|
+
docintel/auth/limiter.py,sha256=POkBHPce4VGAIP1Dxvy7GTVESQRnxwCxOS20tNOk3Pk,894
|
|
11
|
+
docintel/auth/middleware.py,sha256=_A6Gi1fQrojhDJB0EfDwAz3On9WhexYX1ff054zN_Gs,1028
|
|
12
|
+
docintel/auth/oidc.py,sha256=4vmddIL1WWKA0dMjWn-jNKj-DbTorrMzvmDmOrqQs5o,1367
|
|
13
|
+
docintel/jobs/__init__.py,sha256=yatjlOWCVxfKTuFeYJWubiGwYPdNuw7XEwgRZDAy4MQ,403
|
|
14
|
+
docintel/jobs/helpers.py,sha256=6yibvNqW1pPj4SYdjrlfXRI8wyEMUYpNELotm5j6fqY,1203
|
|
15
|
+
docintel/jobs/models.py,sha256=KyFl2AocxIc4NW_5O2UvQUReCRsaMO-ge3t__N-wuLY,2491
|
|
16
|
+
docintel/jobs/queue.py,sha256=AyI_H1ucg5VpM8GkoUw7rIJH4ONHj2yBlCgUZOoYUpk,1845
|
|
17
|
+
docintel/jobs/store.py,sha256=ZTl8g-W638XkI42EyhW7cWxaMafbXvccLrCWYkGw560,2356
|
|
18
|
+
docintel/jobs/tasks.py,sha256=tv2tToXeCt22cDPQe3dFLqNGMK0OAxsb0DzlTyHOK2U,4849
|
|
19
|
+
docintel/jobs/webhooks.py,sha256=I_0GmmTeg5EaJynN2p1h7pg9tK5qo9-EQ8DVKzEhRys,957
|
|
20
|
+
docintel/openapi/__init__.py,sha256=LKZtH8ufr32pRr1EwDAWyPLRUQxnjT3d6ixxkDQzyqs,36
|
|
21
|
+
docintel/openapi/openapi.yaml,sha256=IiSV9_TjWeh1R_YcaP8yxoBTMrEsCxnpRMtasij24Xk,9021
|
|
22
|
+
docintel/ops/__init__.py,sha256=Oa46ba46YnpIGXgq2wrIIxTlFhyM5W4k2sqLjTrpD1I,65
|
|
23
|
+
docintel/ops/logging.py,sha256=mn_GoKgmvgrPEbKhxmhcQxzzC3ffsTkyjFhvqqzWXCo,1272
|
|
24
|
+
docintel/ops/metrics.py,sha256=s2vtDULwf_fh0rUNU01zzIKlBDbVU3X8gYIxpr9yjJo,2290
|
|
25
|
+
docintel/ops/middleware.py,sha256=3WCIlRa7bndPCMDb8r3ToA7XuvATWxTQMHYSxwyk1c0,1035
|
|
26
|
+
docintel/routes/__init__.py,sha256=molos3snoU8nyCIkeGizlgqm4VzZHI7PCxhIUusc8eA,29
|
|
27
|
+
docintel/routes/jobs.py,sha256=gIDa9zYS9KxfKTxtENhxg3PnukxcKWXWDeCZ4cyJP8E,668
|
|
28
|
+
docintel/routes/match.py,sha256=Ot04T56ie28XFo2evxMkvu4C_9HEA4M57ZTZC6A5X4c,1480
|
|
29
|
+
docintel/routes/openapi_docs.py,sha256=2Fct_QCjSCK4PNAmFqsui6xGPlH7GoB5DZCI0BOr65k,1453
|
|
30
|
+
docintel/routes/ops.py,sha256=WryyHAJ9mKCYb316Xfep9lH8T2uJr8MTtjsFpNW0aKQ,493
|
|
31
|
+
docintel/routes/pdf.py,sha256=SsSASpH4F47o16hGomjSDYaKg9ovbrRIjsCY7jqFfd4,13335
|
|
32
|
+
docintel/routes/text.py,sha256=fRCiJ6qgLrtsE95ePa6UDFx_wbuGgUXHtW-CV_AH_9c,1413
|
|
33
|
+
docintel/services/__init__.py,sha256=zfqSZlxwaJx45Qapqx3qoyMYR00MOzUbmOKWU4oIdNY,36
|
|
34
|
+
docintel/services/matching/__init__.py,sha256=23kYGDyBgmrzXVCRhViByYiMWZQ2VwqYwwxr9uMjU2s,213
|
|
35
|
+
docintel/services/matching/models.py,sha256=dox_e_Os4IKrKtkgL5p0abnx2NRGp8SBSc0iyyhdc14,432
|
|
36
|
+
docintel/services/matching/scorer.py,sha256=_Kxul8NKymxi4h88_EL6vIeHajfSo_39NlGPr7v4f8s,1957
|
|
37
|
+
docintel/services/pdf/__init__.py,sha256=Tnc8pXpuaMvuuJ__EIsM7esrJwDg4y-WvSRxEpfkTzI,904
|
|
38
|
+
docintel/services/pdf/annotator.py,sha256=BvH9BsVz_bygDuv8sG5IYF-ourqIBRWFleNgBP-2dHs,5795
|
|
39
|
+
docintel/services/pdf/models.py,sha256=nRx26an5eNFh93g_YwXtFKC5qS5H9KPfOb5QRg0ohOE,2744
|
|
40
|
+
docintel/services/pdf/ocr.py,sha256=9Za-EAVEbUr867Cy9tc02o7iolEQ8Ez5MQiTHtuqFOI,3717
|
|
41
|
+
docintel/services/pdf/pii.py,sha256=KMjoK4ZMGIT045QWJbnQB08Jxl6JqKvlMP_bfRcb8xo,2673
|
|
42
|
+
docintel/services/pdf/presets.py,sha256=HP-eBYCkH1QRghacw9cKyIAqfetCQSxvbR67ubnG7Zc,673
|
|
43
|
+
docintel/services/pdf/search.py,sha256=h1qkwwMXfv-0icPR9hNUFQsQkdOD7VZvoifpTNe_PTA,798
|
|
44
|
+
docintel/services/pdf/sensitive.py,sha256=6lZW8rcX4-h_L6diFwO1tgl23V1IpbClF28E9As7js8,6756
|
|
45
|
+
docintel/services/pdf/structure.py,sha256=XWrJG1NVTCHDAckNIdDsRpu4AUeAlqKOvnYpUEXMCVg,3931
|
|
46
|
+
docintel/services/pdf/structure_llm.py,sha256=mgmPWTNsz_mBS07i8CUbUi0XEfvBpC70glO3TOPX_uw,4543
|
|
47
|
+
docintel/services/pdf/structure_render.py,sha256=RuHVr6Jb4N1FEiK5DQJUdNcWDwfQn3XrZ2BecyGY_ak,4109
|
|
48
|
+
docintel/services/pdf/structure_schema.py,sha256=Cg9adDshW1psgyj7WD9G2LnV7zvyLBPyiaxfGBX-5JM,3185
|
|
49
|
+
docintel/services/summary/__init__.py,sha256=58pi2nrUEMFZgQJr_8EvATa4sUCrpo6M5kWXn9nAHnw,203
|
|
50
|
+
docintel/services/summary/models.py,sha256=oED_uUmfLeEFp-ydbOsoQrRh0-ljca6jiTvQeudt4C4,504
|
|
51
|
+
docintel/services/summary/textrank.py,sha256=Ho6fZ1IFVRw2H6Gty608bYpPKQhXFI7XehXIoXo1cd0,1942
|
|
52
|
+
docintel_platform-1.0.2.dist-info/METADATA,sha256=xikGgxm6s_zKJiTbHkRnOUf9gRmorOyMx2kSHHH54c4,21206
|
|
53
|
+
docintel_platform-1.0.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
54
|
+
docintel_platform-1.0.2.dist-info/entry_points.txt,sha256=qnv0aRtUX3-24zhOrwvKhnFiYrhFyjlUZ8L2sMLjeDs,83
|
|
55
|
+
docintel_platform-1.0.2.dist-info/top_level.txt,sha256=6Iqz3eNp_HVXPxl2Mxxqq8wZRcDre1hX1--B4G7pOJU,9
|
|
56
|
+
docintel_platform-1.0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
docintel
|