folioiq 0.1.0__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.
- folioiq-0.1.0/.env.example +12 -0
- folioiq-0.1.0/.gitignore +30 -0
- folioiq-0.1.0/LICENSE +21 -0
- folioiq-0.1.0/PKG-INFO +98 -0
- folioiq-0.1.0/PLAN.md +91 -0
- folioiq-0.1.0/README.md +64 -0
- folioiq-0.1.0/examples/invoice.md +13 -0
- folioiq-0.1.0/examples/invoice.yaml +64 -0
- folioiq-0.1.0/examples/invoice_explicit_di.yaml +24 -0
- folioiq-0.1.0/pyproject.toml +68 -0
- folioiq-0.1.0/src/folioiq/__init__.py +20 -0
- folioiq-0.1.0/src/folioiq/azure_di.py +61 -0
- folioiq-0.1.0/src/folioiq/client.py +183 -0
- folioiq-0.1.0/src/folioiq/config.py +36 -0
- folioiq-0.1.0/src/folioiq/eval.py +187 -0
- folioiq-0.1.0/src/folioiq/exceptions.py +13 -0
- folioiq-0.1.0/src/folioiq/io.py +49 -0
- folioiq-0.1.0/src/folioiq/llm.py +80 -0
- folioiq-0.1.0/src/folioiq/models.py +16 -0
- folioiq-0.1.0/src/folioiq/pdf_text.py +48 -0
- folioiq-0.1.0/src/folioiq/prompt.py +42 -0
- folioiq-0.1.0/src/folioiq/router.py +68 -0
- folioiq-0.1.0/src/folioiq/specs.py +181 -0
- folioiq-0.1.0/src/folioiq/validation.py +80 -0
- folioiq-0.1.0/tests/fixtures/README.md +14 -0
- folioiq-0.1.0/tests/fixtures/packs/contract/document.pdf +0 -0
- folioiq-0.1.0/tests/fixtures/packs/contract/expected.json +8 -0
- folioiq-0.1.0/tests/fixtures/packs/contract/instructions.md +1 -0
- folioiq-0.1.0/tests/fixtures/packs/contract/spec.yaml +35 -0
- folioiq-0.1.0/tests/fixtures/packs/dr/document.jpg +0 -0
- folioiq-0.1.0/tests/fixtures/packs/dr/expected.json +7 -0
- folioiq-0.1.0/tests/fixtures/packs/dr/instructions.md +1 -0
- folioiq-0.1.0/tests/fixtures/packs/dr/spec.yaml +27 -0
- folioiq-0.1.0/tests/fixtures/packs/po/document.pdf +0 -0
- folioiq-0.1.0/tests/fixtures/packs/po/expected.json +8 -0
- folioiq-0.1.0/tests/fixtures/packs/po/instructions.md +2 -0
- folioiq-0.1.0/tests/fixtures/packs/po/spec.yaml +35 -0
- folioiq-0.1.0/tests/fixtures/packs/so/document.pdf +0 -0
- folioiq-0.1.0/tests/fixtures/packs/so/expected.json +9 -0
- folioiq-0.1.0/tests/fixtures/packs/so/instructions.md +1 -0
- folioiq-0.1.0/tests/fixtures/packs/so/spec.yaml +39 -0
- folioiq-0.1.0/tests/test_phase0.py +30 -0
- folioiq-0.1.0/tests/test_phase1.py +63 -0
- folioiq-0.1.0/tests/test_phase2.py +76 -0
- folioiq-0.1.0/tests/test_phase3.py +88 -0
- folioiq-0.1.0/tests/test_phase4.py +93 -0
- folioiq-0.1.0/tests/test_phase5.py +56 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# FolioIQ — copy to .env and fill in
|
|
2
|
+
|
|
3
|
+
# Azure OpenAI (required for extract() without a custom llm=)
|
|
4
|
+
AZURE_API_KEY=
|
|
5
|
+
AZURE_API_BASE=
|
|
6
|
+
AZURE_API_VERSION=
|
|
7
|
+
# Deployment name (also accepts env var `model` for billing-app compatibility)
|
|
8
|
+
FOLIOIQ_AZURE_MODEL=
|
|
9
|
+
|
|
10
|
+
# Azure Document Intelligence (Phase 3)
|
|
11
|
+
AZURE_DOC_INTELLIGENCE_ENDPOINT=
|
|
12
|
+
AZURE_DOC_INTELLIGENCE_KEY=
|
folioiq-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Byte-compiled / cache
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
.eggs/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
|
|
15
|
+
# Env / secrets
|
|
16
|
+
.env
|
|
17
|
+
.env.local
|
|
18
|
+
|
|
19
|
+
# OS / IDE
|
|
20
|
+
.DS_Store
|
|
21
|
+
.idea/
|
|
22
|
+
.vscode/
|
|
23
|
+
|
|
24
|
+
# Test / local artifacts
|
|
25
|
+
uploads/
|
|
26
|
+
*.pdf
|
|
27
|
+
!tests/fixtures/**/*.pdf
|
|
28
|
+
!tests/fixtures/**/*.jpg
|
|
29
|
+
!tests/fixtures/**/*.jpeg
|
|
30
|
+
!tests/fixtures/**/*.png
|
folioiq-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FolioIQ contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
folioiq-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: folioiq
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Schema-driven document extraction with pluggable providers
|
|
5
|
+
Project-URL: Homepage, https://github.com/auspicious123/folioiq-sdk
|
|
6
|
+
Project-URL: Repository, https://github.com/auspicious123/folioiq-sdk
|
|
7
|
+
Project-URL: Issues, https://github.com/auspicious123/folioiq-sdk/issues
|
|
8
|
+
Author-email: auspicious123 <auspicious123@users.noreply.github.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: azure,document-extraction,llm,ocr,pdf,pydantic
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: pydantic-settings>=2.0
|
|
21
|
+
Requires-Dist: pydantic>=2.0
|
|
22
|
+
Requires-Dist: pymupdf>=1.24.0
|
|
23
|
+
Requires-Dist: python-dotenv>=1.0
|
|
24
|
+
Requires-Dist: pyyaml>=6.0
|
|
25
|
+
Provides-Extra: azure
|
|
26
|
+
Requires-Dist: azure-ai-documentintelligence>=1.0.0; extra == 'azure'
|
|
27
|
+
Requires-Dist: langchain-core>=0.3.0; extra == 'azure'
|
|
28
|
+
Requires-Dist: langchain-openai>=0.2.0; extra == 'azure'
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# FolioIQ
|
|
36
|
+
|
|
37
|
+
[](https://pypi.org/project/folioiq/)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
|
|
40
|
+
Schema-driven document extraction. YAML spec (+ optional MD) in → structured data out.
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from folioiq import DocumentExtractor
|
|
44
|
+
|
|
45
|
+
extractor = DocumentExtractor.from_env()
|
|
46
|
+
result = extractor.extract("invoice.pdf", spec="examples/invoice.yaml")
|
|
47
|
+
|
|
48
|
+
if result.is_valid:
|
|
49
|
+
print(result.data)
|
|
50
|
+
else:
|
|
51
|
+
print(result.validation_errors)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install folioiq
|
|
58
|
+
pip install "folioiq[azure]" # Azure OpenAI + Document Intelligence
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Or from source:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git clone https://github.com/auspicious123/folioiq-sdk.git
|
|
65
|
+
cd folioiq-sdk
|
|
66
|
+
python -m venv .venv && source .venv/bin/activate
|
|
67
|
+
pip install -e ".[azure,dev]"
|
|
68
|
+
cp .env.example .env # fill Azure vars
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Spec only (no LLM)
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
spec = extractor.load_spec("examples/invoice.yaml")
|
|
75
|
+
Model = extractor.build_model(spec)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Pipeline override (YAML)
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
pipeline:
|
|
82
|
+
mode: explicit
|
|
83
|
+
parser: azure_di # or pymupdf
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Eval
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pip install -e ".[azure,dev]"
|
|
90
|
+
folioiq-eval # all fixture packs
|
|
91
|
+
folioiq-eval tests/fixtures/packs/po
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
MIT — see [LICENSE](LICENSE).
|
|
97
|
+
|
|
98
|
+
See [PLAN.md](PLAN.md) for design phases.
|
folioiq-0.1.0/PLAN.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# FolioIQ — Build Plan
|
|
2
|
+
|
|
3
|
+
Provider-agnostic document extraction. Spec (YAML + optional MD) in → structured data out.
|
|
4
|
+
|
|
5
|
+
**Package:** `folioiq`
|
|
6
|
+
**Promise:** consistent interface + providers + validation — not “always accurate.”
|
|
7
|
+
|
|
8
|
+
Keep it simple. Ship one phase at a time.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Public API (target)
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from folioiq import DocumentExtractor
|
|
16
|
+
|
|
17
|
+
extractor = DocumentExtractor.from_env()
|
|
18
|
+
result = extractor.extract("invoice.pdf", spec="specs/invoice.yaml")
|
|
19
|
+
|
|
20
|
+
if result.is_valid:
|
|
21
|
+
process(result.data)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Phases
|
|
27
|
+
|
|
28
|
+
### Phase 0 — Scaffold ✅ done
|
|
29
|
+
- Repo layout, `pyproject.toml`, installable package
|
|
30
|
+
- Stub `DocumentExtractor` + `ExtractionResult`
|
|
31
|
+
- Config / env skeleton
|
|
32
|
+
- Extras: `[azure]` declared (deps unused until later)
|
|
33
|
+
|
|
34
|
+
**Done when:** `pip install -e .` and `from folioiq import DocumentExtractor` works.
|
|
35
|
+
|
|
36
|
+
### Phase 1 — Spec + models ✅ done
|
|
37
|
+
- Load YAML → field schema → Pydantic model (`load_spec`, `build_model`)
|
|
38
|
+
- Optional sibling `.md` instructions
|
|
39
|
+
- Example: `examples/invoice.yaml` + `invoice.md`
|
|
40
|
+
- No LLM yet (`extract()` still Phase 2)
|
|
41
|
+
|
|
42
|
+
### Phase 2 — Native parse + Azure OpenAI ✅ done
|
|
43
|
+
- PyMuPDF → embedded PDF text
|
|
44
|
+
- Prompt from YAML + MD
|
|
45
|
+
- Azure OpenAI structured output (`folioiq[azure]`)
|
|
46
|
+
- Digital PDF path only; inject `llm=` for tests / custom clients
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
extractor = DocumentExtractor.from_env()
|
|
50
|
+
result = extractor.extract("invoice.pdf", spec="examples/invoice.yaml")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Phase 3 — Azure DI + simple router ✅ done
|
|
54
|
+
- Auto: text PDF → `pymupdf`; scant/image → `azure_di`
|
|
55
|
+
- Explicit: `pipeline.mode: explicit` + `parser: pymupdf|azure_di`
|
|
56
|
+
- Inject `ocr=` for tests (same pattern as `llm=`)
|
|
57
|
+
|
|
58
|
+
### Phase 4 — Validation + one fallback ✅ done
|
|
59
|
+
- Required / type checks via `validate_extraction`
|
|
60
|
+
- One retry with the alternate parser (`pymupdf` ↔ `azure_di`)
|
|
61
|
+
- `is_valid`, `validation_errors`, `metadata.fallback_used`
|
|
62
|
+
|
|
63
|
+
### Phase 5 — Fixtures / eval ✅ done
|
|
64
|
+
- Packs under `tests/fixtures/packs/{po,so,contract,dr}`
|
|
65
|
+
- `expected.json` ground truth (dr pack placeholder until labeled)
|
|
66
|
+
- `python -m folioiq.eval [pack_dir]` or `folioiq-eval`
|
|
67
|
+
- Field accuracy scoring (loose string / number match)
|
|
68
|
+
|
|
69
|
+
### Later (only if needed)
|
|
70
|
+
- OpenAI / Bedrock / Textract / Docling extras
|
|
71
|
+
- Rich layout IR, business-rule engine, async API
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## V1 providers (hard cut)
|
|
76
|
+
|
|
77
|
+
| In | Out for now |
|
|
78
|
+
|----|-------------|
|
|
79
|
+
| PyMuPDF | Docling |
|
|
80
|
+
| Azure DI | Textract |
|
|
81
|
+
| Azure OpenAI | Other LLMs |
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Rules
|
|
86
|
+
|
|
87
|
+
1. Don’t add providers until the previous phase works.
|
|
88
|
+
2. One YAML dialect only (new nested `fields` style).
|
|
89
|
+
3. Secrets in env only.
|
|
90
|
+
4. Prefer small files and clear names over deep abstractions.
|
|
91
|
+
5. Stop after each phase and review before the next.
|
folioiq-0.1.0/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# FolioIQ
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/folioiq/)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
Schema-driven document extraction. YAML spec (+ optional MD) in → structured data out.
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
from folioiq import DocumentExtractor
|
|
10
|
+
|
|
11
|
+
extractor = DocumentExtractor.from_env()
|
|
12
|
+
result = extractor.extract("invoice.pdf", spec="examples/invoice.yaml")
|
|
13
|
+
|
|
14
|
+
if result.is_valid:
|
|
15
|
+
print(result.data)
|
|
16
|
+
else:
|
|
17
|
+
print(result.validation_errors)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install folioiq
|
|
24
|
+
pip install "folioiq[azure]" # Azure OpenAI + Document Intelligence
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or from source:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git clone https://github.com/auspicious123/folioiq-sdk.git
|
|
31
|
+
cd folioiq-sdk
|
|
32
|
+
python -m venv .venv && source .venv/bin/activate
|
|
33
|
+
pip install -e ".[azure,dev]"
|
|
34
|
+
cp .env.example .env # fill Azure vars
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Spec only (no LLM)
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
spec = extractor.load_spec("examples/invoice.yaml")
|
|
41
|
+
Model = extractor.build_model(spec)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Pipeline override (YAML)
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
pipeline:
|
|
48
|
+
mode: explicit
|
|
49
|
+
parser: azure_di # or pymupdf
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Eval
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install -e ".[azure,dev]"
|
|
56
|
+
folioiq-eval # all fixture packs
|
|
57
|
+
folioiq-eval tests/fixtures/packs/po
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT — see [LICENSE](LICENSE).
|
|
63
|
+
|
|
64
|
+
See [PLAN.md](PLAN.md) for design phases.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Invoice Extraction Instructions
|
|
2
|
+
|
|
3
|
+
Extract invoice information exactly as represented in the document.
|
|
4
|
+
|
|
5
|
+
Rules:
|
|
6
|
+
|
|
7
|
+
- Never invent missing values.
|
|
8
|
+
- Use null when a field is not present.
|
|
9
|
+
- Preserve invoice and PO numbers exactly (keep leading zeros).
|
|
10
|
+
- Do not infer currency from the vendor's country.
|
|
11
|
+
- Preserve decimal amounts.
|
|
12
|
+
- Extract every visible line item.
|
|
13
|
+
- Use the invoice total, not subtotal, as total_amount.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
name: invoice
|
|
2
|
+
version: "1.0"
|
|
3
|
+
|
|
4
|
+
document:
|
|
5
|
+
allowed_types:
|
|
6
|
+
- pdf
|
|
7
|
+
- png
|
|
8
|
+
- jpg
|
|
9
|
+
|
|
10
|
+
pipeline:
|
|
11
|
+
mode: auto
|
|
12
|
+
|
|
13
|
+
extraction:
|
|
14
|
+
fields:
|
|
15
|
+
invoice_number:
|
|
16
|
+
type: string
|
|
17
|
+
required: true
|
|
18
|
+
description: >
|
|
19
|
+
Unique invoice identifier assigned by the supplier.
|
|
20
|
+
|
|
21
|
+
invoice_date:
|
|
22
|
+
type: date
|
|
23
|
+
required: true
|
|
24
|
+
description: Date the invoice was issued.
|
|
25
|
+
|
|
26
|
+
vendor_name:
|
|
27
|
+
type: string
|
|
28
|
+
required: true
|
|
29
|
+
description: Legal or trading name of the supplier.
|
|
30
|
+
|
|
31
|
+
po_number:
|
|
32
|
+
type: string
|
|
33
|
+
required: false
|
|
34
|
+
description: Purchase order number referenced by the invoice, if any.
|
|
35
|
+
|
|
36
|
+
currency:
|
|
37
|
+
type: string
|
|
38
|
+
required: true
|
|
39
|
+
description: ISO currency code (e.g. USD, PHP, INR).
|
|
40
|
+
|
|
41
|
+
total_amount:
|
|
42
|
+
type: number
|
|
43
|
+
required: true
|
|
44
|
+
description: Invoice grand total (not subtotal).
|
|
45
|
+
|
|
46
|
+
line_items:
|
|
47
|
+
type: array
|
|
48
|
+
required: false
|
|
49
|
+
description: Line items on the invoice.
|
|
50
|
+
items:
|
|
51
|
+
type: object
|
|
52
|
+
fields:
|
|
53
|
+
description:
|
|
54
|
+
type: string
|
|
55
|
+
required: false
|
|
56
|
+
quantity:
|
|
57
|
+
type: number
|
|
58
|
+
required: false
|
|
59
|
+
unit_price:
|
|
60
|
+
type: number
|
|
61
|
+
required: false
|
|
62
|
+
amount:
|
|
63
|
+
type: number
|
|
64
|
+
required: false
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: invoice
|
|
2
|
+
version: "1.0"
|
|
3
|
+
|
|
4
|
+
document:
|
|
5
|
+
allowed_types:
|
|
6
|
+
- pdf
|
|
7
|
+
- png
|
|
8
|
+
- jpg
|
|
9
|
+
|
|
10
|
+
# Force Azure DI even for digital PDFs (optional)
|
|
11
|
+
pipeline:
|
|
12
|
+
mode: explicit
|
|
13
|
+
parser: azure_di
|
|
14
|
+
|
|
15
|
+
extraction:
|
|
16
|
+
fields:
|
|
17
|
+
invoice_number:
|
|
18
|
+
type: string
|
|
19
|
+
required: true
|
|
20
|
+
description: Invoice id
|
|
21
|
+
total_amount:
|
|
22
|
+
type: number
|
|
23
|
+
required: false
|
|
24
|
+
description: Grand total
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "folioiq"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Schema-driven document extraction with pluggable providers"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "auspicious123", email = "auspicious123@users.noreply.github.com" },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"document-extraction",
|
|
17
|
+
"llm",
|
|
18
|
+
"ocr",
|
|
19
|
+
"pdf",
|
|
20
|
+
"azure",
|
|
21
|
+
"pydantic",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 3 - Alpha",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"License :: OSI Approved :: MIT License",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
31
|
+
]
|
|
32
|
+
dependencies = [
|
|
33
|
+
"pydantic>=2.0",
|
|
34
|
+
"pydantic-settings>=2.0",
|
|
35
|
+
"pyyaml>=6.0",
|
|
36
|
+
"python-dotenv>=1.0",
|
|
37
|
+
"pymupdf>=1.24.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
azure = [
|
|
42
|
+
"langchain-openai>=0.2.0",
|
|
43
|
+
"langchain-core>=0.3.0",
|
|
44
|
+
"azure-ai-documentintelligence>=1.0.0",
|
|
45
|
+
]
|
|
46
|
+
dev = [
|
|
47
|
+
"pytest>=8.0",
|
|
48
|
+
"build>=1.0",
|
|
49
|
+
"twine>=5.0",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[project.scripts]
|
|
53
|
+
folioiq-eval = "folioiq.eval:main"
|
|
54
|
+
|
|
55
|
+
[project.urls]
|
|
56
|
+
Homepage = "https://github.com/auspicious123/folioiq-sdk"
|
|
57
|
+
Repository = "https://github.com/auspicious123/folioiq-sdk"
|
|
58
|
+
Issues = "https://github.com/auspicious123/folioiq-sdk/issues"
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.targets.wheel]
|
|
61
|
+
packages = ["src/folioiq"]
|
|
62
|
+
|
|
63
|
+
[tool.hatch.build.targets.wheel.sources]
|
|
64
|
+
"src/folioiq" = "folioiq"
|
|
65
|
+
|
|
66
|
+
[tool.pytest.ini_options]
|
|
67
|
+
testpaths = ["tests"]
|
|
68
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""FolioIQ — schema-driven document extraction."""
|
|
2
|
+
|
|
3
|
+
from folioiq.client import DocumentExtractor
|
|
4
|
+
from folioiq.exceptions import FolioIQError, NotImplementedPhaseError, SpecError
|
|
5
|
+
from folioiq.models import ExtractionResult
|
|
6
|
+
from folioiq.specs import ExtractionSpec, FieldSpec, build_model, load_spec
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"DocumentExtractor",
|
|
10
|
+
"ExtractionResult",
|
|
11
|
+
"ExtractionSpec",
|
|
12
|
+
"FieldSpec",
|
|
13
|
+
"FolioIQError",
|
|
14
|
+
"NotImplementedPhaseError",
|
|
15
|
+
"SpecError",
|
|
16
|
+
"build_model",
|
|
17
|
+
"load_spec",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Azure Document Intelligence OCR (requires folioiq[azure])."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Protocol
|
|
6
|
+
|
|
7
|
+
from folioiq.config import FolioIQSettings
|
|
8
|
+
from folioiq.exceptions import FolioIQError
|
|
9
|
+
from folioiq.io import DocumentInput, read_document_bytes
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OCRClient(Protocol):
|
|
13
|
+
def extract_text(self, document: DocumentInput) -> str:
|
|
14
|
+
...
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AzureDIClient:
|
|
18
|
+
"""OCR via Azure Document Intelligence prebuilt-read."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, settings: FolioIQSettings, model_id: str = "prebuilt-read") -> None:
|
|
21
|
+
self.settings = settings
|
|
22
|
+
self.model_id = model_id
|
|
23
|
+
self._client = self._build_client()
|
|
24
|
+
|
|
25
|
+
def _build_client(self):
|
|
26
|
+
endpoint = self.settings.azure_doc_intelligence_endpoint
|
|
27
|
+
key = self.settings.azure_doc_intelligence_key
|
|
28
|
+
if not endpoint or not key:
|
|
29
|
+
raise FolioIQError(
|
|
30
|
+
"Azure Document Intelligence is not configured. "
|
|
31
|
+
"Set AZURE_DOC_INTELLIGENCE_ENDPOINT and AZURE_DOC_INTELLIGENCE_KEY."
|
|
32
|
+
)
|
|
33
|
+
try:
|
|
34
|
+
from azure.ai.documentintelligence import DocumentIntelligenceClient
|
|
35
|
+
from azure.core.credentials import AzureKeyCredential
|
|
36
|
+
except ImportError as exc:
|
|
37
|
+
raise FolioIQError(
|
|
38
|
+
"Azure DI requires: pip install 'folioiq[azure]'"
|
|
39
|
+
) from exc
|
|
40
|
+
|
|
41
|
+
return DocumentIntelligenceClient(
|
|
42
|
+
endpoint=endpoint,
|
|
43
|
+
credential=AzureKeyCredential(key),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def extract_text(self, document: DocumentInput) -> str:
|
|
47
|
+
data, _ = read_document_bytes(document)
|
|
48
|
+
poller = self._client.begin_analyze_document(
|
|
49
|
+
self.model_id,
|
|
50
|
+
body=data,
|
|
51
|
+
content_type="application/octet-stream",
|
|
52
|
+
)
|
|
53
|
+
result = poller.result()
|
|
54
|
+
text = (result.content or "").strip()
|
|
55
|
+
if not text:
|
|
56
|
+
raise FolioIQError("Azure DI returned empty OCR text")
|
|
57
|
+
return text
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_default_ocr(settings: FolioIQSettings) -> OCRClient:
|
|
61
|
+
return AzureDIClient(settings)
|