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.
Files changed (47) hide show
  1. folioiq-0.1.0/.env.example +12 -0
  2. folioiq-0.1.0/.gitignore +30 -0
  3. folioiq-0.1.0/LICENSE +21 -0
  4. folioiq-0.1.0/PKG-INFO +98 -0
  5. folioiq-0.1.0/PLAN.md +91 -0
  6. folioiq-0.1.0/README.md +64 -0
  7. folioiq-0.1.0/examples/invoice.md +13 -0
  8. folioiq-0.1.0/examples/invoice.yaml +64 -0
  9. folioiq-0.1.0/examples/invoice_explicit_di.yaml +24 -0
  10. folioiq-0.1.0/pyproject.toml +68 -0
  11. folioiq-0.1.0/src/folioiq/__init__.py +20 -0
  12. folioiq-0.1.0/src/folioiq/azure_di.py +61 -0
  13. folioiq-0.1.0/src/folioiq/client.py +183 -0
  14. folioiq-0.1.0/src/folioiq/config.py +36 -0
  15. folioiq-0.1.0/src/folioiq/eval.py +187 -0
  16. folioiq-0.1.0/src/folioiq/exceptions.py +13 -0
  17. folioiq-0.1.0/src/folioiq/io.py +49 -0
  18. folioiq-0.1.0/src/folioiq/llm.py +80 -0
  19. folioiq-0.1.0/src/folioiq/models.py +16 -0
  20. folioiq-0.1.0/src/folioiq/pdf_text.py +48 -0
  21. folioiq-0.1.0/src/folioiq/prompt.py +42 -0
  22. folioiq-0.1.0/src/folioiq/router.py +68 -0
  23. folioiq-0.1.0/src/folioiq/specs.py +181 -0
  24. folioiq-0.1.0/src/folioiq/validation.py +80 -0
  25. folioiq-0.1.0/tests/fixtures/README.md +14 -0
  26. folioiq-0.1.0/tests/fixtures/packs/contract/document.pdf +0 -0
  27. folioiq-0.1.0/tests/fixtures/packs/contract/expected.json +8 -0
  28. folioiq-0.1.0/tests/fixtures/packs/contract/instructions.md +1 -0
  29. folioiq-0.1.0/tests/fixtures/packs/contract/spec.yaml +35 -0
  30. folioiq-0.1.0/tests/fixtures/packs/dr/document.jpg +0 -0
  31. folioiq-0.1.0/tests/fixtures/packs/dr/expected.json +7 -0
  32. folioiq-0.1.0/tests/fixtures/packs/dr/instructions.md +1 -0
  33. folioiq-0.1.0/tests/fixtures/packs/dr/spec.yaml +27 -0
  34. folioiq-0.1.0/tests/fixtures/packs/po/document.pdf +0 -0
  35. folioiq-0.1.0/tests/fixtures/packs/po/expected.json +8 -0
  36. folioiq-0.1.0/tests/fixtures/packs/po/instructions.md +2 -0
  37. folioiq-0.1.0/tests/fixtures/packs/po/spec.yaml +35 -0
  38. folioiq-0.1.0/tests/fixtures/packs/so/document.pdf +0 -0
  39. folioiq-0.1.0/tests/fixtures/packs/so/expected.json +9 -0
  40. folioiq-0.1.0/tests/fixtures/packs/so/instructions.md +1 -0
  41. folioiq-0.1.0/tests/fixtures/packs/so/spec.yaml +39 -0
  42. folioiq-0.1.0/tests/test_phase0.py +30 -0
  43. folioiq-0.1.0/tests/test_phase1.py +63 -0
  44. folioiq-0.1.0/tests/test_phase2.py +76 -0
  45. folioiq-0.1.0/tests/test_phase3.py +88 -0
  46. folioiq-0.1.0/tests/test_phase4.py +93 -0
  47. 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=
@@ -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
+ [![PyPI](https://img.shields.io/badge/pypi-folioiq-blue)](https://pypi.org/project/folioiq/)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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.
@@ -0,0 +1,64 @@
1
+ # FolioIQ
2
+
3
+ [![PyPI](https://img.shields.io/badge/pypi-folioiq-blue)](https://pypi.org/project/folioiq/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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)