acquivela 0.2.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.
- acquivela-0.2.0/LICENSE +21 -0
- acquivela-0.2.0/PKG-INFO +215 -0
- acquivela-0.2.0/README.md +188 -0
- acquivela-0.2.0/acquivela/__init__.py +28 -0
- acquivela-0.2.0/acquivela/capabilities.py +19 -0
- acquivela-0.2.0/acquivela/cdm.py +106 -0
- acquivela-0.2.0/acquivela/execution_context.py +27 -0
- acquivela-0.2.0/acquivela/facade.py +47 -0
- acquivela-0.2.0/acquivela/models.py +49 -0
- acquivela-0.2.0/acquivela/pipeline.py +317 -0
- acquivela-0.2.0/acquivela/plugin.py +68 -0
- acquivela-0.2.0/acquivela/plugins/__init__.py +14 -0
- acquivela-0.2.0/acquivela/plugins/docling.py +213 -0
- acquivela-0.2.0/acquivela/plugins/mineru.py +281 -0
- acquivela-0.2.0/acquivela/public_result.py +114 -0
- acquivela-0.2.0/acquivela/registry.py +200 -0
- acquivela-0.2.0/acquivela/result.py +43 -0
- acquivela-0.2.0/acquivela.egg-info/PKG-INFO +215 -0
- acquivela-0.2.0/acquivela.egg-info/SOURCES.txt +38 -0
- acquivela-0.2.0/acquivela.egg-info/dependency_links.txt +1 -0
- acquivela-0.2.0/acquivela.egg-info/requires.txt +14 -0
- acquivela-0.2.0/acquivela.egg-info/top_level.txt +1 -0
- acquivela-0.2.0/pyproject.toml +48 -0
- acquivela-0.2.0/setup.cfg +4 -0
- acquivela-0.2.0/tests/test_capabilities.py +37 -0
- acquivela-0.2.0/tests/test_capability_selection.py +383 -0
- acquivela-0.2.0/tests/test_cdm_v02.py +400 -0
- acquivela-0.2.0/tests/test_contracts.py +129 -0
- acquivela-0.2.0/tests/test_contracts_extended.py +363 -0
- acquivela-0.2.0/tests/test_diagnostics.py +485 -0
- acquivela-0.2.0/tests/test_facade.py +188 -0
- acquivela-0.2.0/tests/test_fallback.py +719 -0
- acquivela-0.2.0/tests/test_import_linter.py +56 -0
- acquivela-0.2.0/tests/test_import_linter_extended.py +124 -0
- acquivela-0.2.0/tests/test_integration.py +488 -0
- acquivela-0.2.0/tests/test_pipeline.py +530 -0
- acquivela-0.2.0/tests/test_plugin_docling.py +275 -0
- acquivela-0.2.0/tests/test_plugin_health.py +494 -0
- acquivela-0.2.0/tests/test_plugin_mineru.py +244 -0
- acquivela-0.2.0/tests/test_registry.py +466 -0
acquivela-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AcquiVela 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.
|
acquivela-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: acquivela
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: AcquiVela — Document Acquisition Framework
|
|
5
|
+
License: MIT
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Provides-Extra: docling
|
|
17
|
+
Requires-Dist: docling>=2.119.0; extra == "docling"
|
|
18
|
+
Provides-Extra: mineru
|
|
19
|
+
Requires-Dist: mineru>=3.4.4; extra == "mineru"
|
|
20
|
+
Provides-Extra: all
|
|
21
|
+
Requires-Dist: acquivela[docling,mineru]; extra == "all"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
24
|
+
Requires-Dist: importlinter>=0.16; extra == "dev"
|
|
25
|
+
Requires-Dist: acquivela[all]; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# AcquiVela
|
|
29
|
+
|
|
30
|
+
Document Acquisition Framework — Frozen Architecture v0.1
|
|
31
|
+
|
|
32
|
+
> **Note:** AcquiVela v0.1.0 is a pre-1.0 release. Some internal orchestration APIs (`Pipeline`, `Registry`) are exposed for use but are **not frozen public contracts** and may change. See [Governance](#governance) below.
|
|
33
|
+
|
|
34
|
+
## What is AcquiVela?
|
|
35
|
+
|
|
36
|
+
AcquiVela is a capability-oriented document acquisition framework that orchestrates specialized acquisition engines (Docling, MinerU) through frozen public contracts and a versioned Canonical Document Model (CDM).
|
|
37
|
+
|
|
38
|
+
**Scope:** Document acquisition only (PDF, DOCX, scanned images, and more).
|
|
39
|
+
**Non-goals:** Embeddings, retrieval, vector DB, chunking, LLM reasoning, knowledge extraction.
|
|
40
|
+
|
|
41
|
+
## Governed Public Contracts
|
|
42
|
+
|
|
43
|
+
The following are frozen per ADR-0001 through ADR-0006 and carry semantic-versioning compatibility guarantees:
|
|
44
|
+
|
|
45
|
+
| Contract | Module | Description |
|
|
46
|
+
|----------|--------|-------------|
|
|
47
|
+
| `AcquireRequest` | `models.py` | Input request (file path, engine hint, capability) |
|
|
48
|
+
| `AcquireResult` | `public_result.py` | Public result wrapper (status, canonical document, error) |
|
|
49
|
+
| `CanonicalDocument` | `cdm.py` | CDM root — pages + title |
|
|
50
|
+
| `Page` | `cdm.py` | A document page — text blocks + page number |
|
|
51
|
+
| `TextBlock` | `cdm.py` | A text block — text + type classification |
|
|
52
|
+
| `Plugin` | `plugin.py` | Plugin SPI (ABC) — `metadata()`, `acquire()`, `capabilities()` |
|
|
53
|
+
| `PluginHealth` | `plugin.py` | Plugin health status |
|
|
54
|
+
| `Capabilities` | `capabilities.py` | Canonical capability vocabulary |
|
|
55
|
+
| `Diagnostics` | `public_result.py` | Per-execution diagnostic metadata |
|
|
56
|
+
|
|
57
|
+
## Internal Orchestration Infrastructure
|
|
58
|
+
|
|
59
|
+
The following components are **internal** and not frozen public contracts:
|
|
60
|
+
|
|
61
|
+
| Component | Module | Status |
|
|
62
|
+
|-----------|--------|--------|
|
|
63
|
+
| `Pipeline` | `pipeline.py` | Internal — may change before stable API decision |
|
|
64
|
+
| `Registry` | `registry.py` | Internal — may change before stable API decision |
|
|
65
|
+
| `PluginResult` | `result.py` | Internal SPI — not exported at package root |
|
|
66
|
+
| `ExecutionContext` | `execution_context.py` | Internal |
|
|
67
|
+
|
|
68
|
+
Plugins may NOT import `Pipeline`, `Registry`, or `public_result` — enforced via import-linter (ADR-0004 §3.2, ADR-0006 §3.3).
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Base installation (no acquisition engines)
|
|
74
|
+
pip install acquivela
|
|
75
|
+
|
|
76
|
+
# With Docling support (PDF, DOCX, images, and more)
|
|
77
|
+
pip install acquivela[docling]
|
|
78
|
+
|
|
79
|
+
# With MinerU support (high-accuracy PDF)
|
|
80
|
+
pip install acquivela[mineru]
|
|
81
|
+
|
|
82
|
+
# With both engines
|
|
83
|
+
pip install acquivela[docling,mineru]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### System Dependencies
|
|
87
|
+
|
|
88
|
+
Some formats require additional system-level tools:
|
|
89
|
+
|
|
90
|
+
- **Legacy Office formats** (`.doc`, `.ppt`, `.xls`): Requires LibreOffice installed and on PATH
|
|
91
|
+
- **OpenDocument formats** (`.odt`, `.ods`, `.odp`): Requires `odfdo` Python package (included with `docling` extras)
|
|
92
|
+
|
|
93
|
+
## Quick Start
|
|
94
|
+
```python
|
|
95
|
+
# Recommended: use the M15 stable facade
|
|
96
|
+
from acquivela import AcquireRequest, acquire
|
|
97
|
+
|
|
98
|
+
result = acquire(
|
|
99
|
+
AcquireRequest(file_path="document.pdf")
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if result.status == "success":
|
|
103
|
+
doc = result.canonical_document
|
|
104
|
+
for page in doc.pages:
|
|
105
|
+
for block in page.text_blocks:
|
|
106
|
+
print(block.text)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Using the M15 facade (recommended normal path)
|
|
110
|
+
```python
|
|
111
|
+
from acquivela import AcquireRequest, acquire
|
|
112
|
+
|
|
113
|
+
result = acquire(
|
|
114
|
+
AcquireRequest(file_path="document.pdf")
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if result.status == "success":
|
|
118
|
+
doc = result.canonical_document
|
|
119
|
+
for page in doc.pages:
|
|
120
|
+
for block in page.text_blocks:
|
|
121
|
+
print(block.text)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Using Engine Hints
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
# Force a specific engine
|
|
128
|
+
request = AcquireRequest(
|
|
129
|
+
file_path="document.pdf",
|
|
130
|
+
engine_hint="docling"
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Using Capability Filtering
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from acquivela.capabilities import Capabilities
|
|
138
|
+
|
|
139
|
+
# Require text extraction capability
|
|
140
|
+
request = AcquireRequest(
|
|
141
|
+
file_path="document.pdf",
|
|
142
|
+
required_capability=Capabilities.DOCUMENT_TEXT
|
|
143
|
+
)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Known Limitations
|
|
147
|
+
|
|
148
|
+
### MinerU
|
|
149
|
+
|
|
150
|
+
MinerU has a known upstream issue (`PPDocLayoutV2Config` missing `reading_order_config`) that causes acquisition failures. AcquiVela correctly contains this failure — the pipeline returns `status="failure"` without exceptions escaping. The MinerU plugin reports `healthy` at the health-check level because it is importable, but actual acquisition will fail until the upstream issue is resolved.
|
|
151
|
+
|
|
152
|
+
### Legacy Office Formats
|
|
153
|
+
|
|
154
|
+
`.doc`, `.ppt`, and `.xls` files require LibreOffice to be installed and available on PATH. Without LibreOffice, Docling cannot convert these legacy formats to modern equivalents.
|
|
155
|
+
|
|
156
|
+
### OpenDocument Formats
|
|
157
|
+
|
|
158
|
+
`.odt`, `.ods`, and `.odp` files require the `odfdo` Python package. This is included when installing with `pip install acquivela[docling]`.
|
|
159
|
+
|
|
160
|
+
### XML and JSON
|
|
161
|
+
|
|
162
|
+
Generic XML files are not supported. Docling supports specific XML dialects only (USPTO, JATS, XBRL, DOCLANG). Similarly, only `json_docling` format is supported for JSON input.
|
|
163
|
+
|
|
164
|
+
### CDM v0.2 Exclusions
|
|
165
|
+
|
|
166
|
+
The Canonical Document Model (CDM) v0.2 captures text content only. Tables, images, equations, bounding boxes, and rich provenance are intentionally excluded from the current model.
|
|
167
|
+
|
|
168
|
+
## Architecture
|
|
169
|
+
|
|
170
|
+
### Package Structure
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
acquivela/
|
|
174
|
+
├── __init__.py # Public exports + internal orchestration (see Governance)
|
|
175
|
+
├── cdm.py # CanonicalDocument, Page, TextBlock (PUBLIC/STABLE)
|
|
176
|
+
├── models.py # AcquireRequest (PUBLIC/STABLE)
|
|
177
|
+
├── plugin.py # Plugin SPI, PluginHealth (PUBLIC/STABLE)
|
|
178
|
+
├── result.py # PluginResult (INTERNAL - not exported at root)
|
|
179
|
+
├── public_result.py # AcquireResult, Diagnostics (PUBLIC/STABLE)
|
|
180
|
+
├── capabilities.py # Capabilities (PUBLIC/STABLE)
|
|
181
|
+
├── registry.py # Registry (INTERNAL - may change)
|
|
182
|
+
├── pipeline.py # Pipeline (INTERNAL - may change)
|
|
183
|
+
├── execution_context.py # ExecutionContext (INTERNAL)
|
|
184
|
+
└── plugins/ # Plugin implementations (one class per module)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Status
|
|
188
|
+
|
|
189
|
+
- Charter: FROZEN
|
|
190
|
+
- ADR-0001 Project Boundaries: FROZEN
|
|
191
|
+
- ADR-0002 Execution Model: FROZEN
|
|
192
|
+
- ADR-0003 Core Data Contracts: FROZEN
|
|
193
|
+
- ADR-0004 Plugin & Execution Strategy: FROZEN
|
|
194
|
+
- ADR-0005 Observability & Diagnostics: FROZEN
|
|
195
|
+
- ADR-0006 Registry & Plugin Discovery: FROZEN
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# Install with dev dependencies
|
|
201
|
+
pip install -e .[dev]
|
|
202
|
+
|
|
203
|
+
# Run contract tests
|
|
204
|
+
pytest tests/test_contracts.py
|
|
205
|
+
|
|
206
|
+
# Run import-linter validation
|
|
207
|
+
pytest tests/test_import_linter.py
|
|
208
|
+
|
|
209
|
+
# Or run import-linter directly
|
|
210
|
+
importlinter lint
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# AcquiVela
|
|
2
|
+
|
|
3
|
+
Document Acquisition Framework — Frozen Architecture v0.1
|
|
4
|
+
|
|
5
|
+
> **Note:** AcquiVela v0.1.0 is a pre-1.0 release. Some internal orchestration APIs (`Pipeline`, `Registry`) are exposed for use but are **not frozen public contracts** and may change. See [Governance](#governance) below.
|
|
6
|
+
|
|
7
|
+
## What is AcquiVela?
|
|
8
|
+
|
|
9
|
+
AcquiVela is a capability-oriented document acquisition framework that orchestrates specialized acquisition engines (Docling, MinerU) through frozen public contracts and a versioned Canonical Document Model (CDM).
|
|
10
|
+
|
|
11
|
+
**Scope:** Document acquisition only (PDF, DOCX, scanned images, and more).
|
|
12
|
+
**Non-goals:** Embeddings, retrieval, vector DB, chunking, LLM reasoning, knowledge extraction.
|
|
13
|
+
|
|
14
|
+
## Governed Public Contracts
|
|
15
|
+
|
|
16
|
+
The following are frozen per ADR-0001 through ADR-0006 and carry semantic-versioning compatibility guarantees:
|
|
17
|
+
|
|
18
|
+
| Contract | Module | Description |
|
|
19
|
+
|----------|--------|-------------|
|
|
20
|
+
| `AcquireRequest` | `models.py` | Input request (file path, engine hint, capability) |
|
|
21
|
+
| `AcquireResult` | `public_result.py` | Public result wrapper (status, canonical document, error) |
|
|
22
|
+
| `CanonicalDocument` | `cdm.py` | CDM root — pages + title |
|
|
23
|
+
| `Page` | `cdm.py` | A document page — text blocks + page number |
|
|
24
|
+
| `TextBlock` | `cdm.py` | A text block — text + type classification |
|
|
25
|
+
| `Plugin` | `plugin.py` | Plugin SPI (ABC) — `metadata()`, `acquire()`, `capabilities()` |
|
|
26
|
+
| `PluginHealth` | `plugin.py` | Plugin health status |
|
|
27
|
+
| `Capabilities` | `capabilities.py` | Canonical capability vocabulary |
|
|
28
|
+
| `Diagnostics` | `public_result.py` | Per-execution diagnostic metadata |
|
|
29
|
+
|
|
30
|
+
## Internal Orchestration Infrastructure
|
|
31
|
+
|
|
32
|
+
The following components are **internal** and not frozen public contracts:
|
|
33
|
+
|
|
34
|
+
| Component | Module | Status |
|
|
35
|
+
|-----------|--------|--------|
|
|
36
|
+
| `Pipeline` | `pipeline.py` | Internal — may change before stable API decision |
|
|
37
|
+
| `Registry` | `registry.py` | Internal — may change before stable API decision |
|
|
38
|
+
| `PluginResult` | `result.py` | Internal SPI — not exported at package root |
|
|
39
|
+
| `ExecutionContext` | `execution_context.py` | Internal |
|
|
40
|
+
|
|
41
|
+
Plugins may NOT import `Pipeline`, `Registry`, or `public_result` — enforced via import-linter (ADR-0004 §3.2, ADR-0006 §3.3).
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Base installation (no acquisition engines)
|
|
47
|
+
pip install acquivela
|
|
48
|
+
|
|
49
|
+
# With Docling support (PDF, DOCX, images, and more)
|
|
50
|
+
pip install acquivela[docling]
|
|
51
|
+
|
|
52
|
+
# With MinerU support (high-accuracy PDF)
|
|
53
|
+
pip install acquivela[mineru]
|
|
54
|
+
|
|
55
|
+
# With both engines
|
|
56
|
+
pip install acquivela[docling,mineru]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### System Dependencies
|
|
60
|
+
|
|
61
|
+
Some formats require additional system-level tools:
|
|
62
|
+
|
|
63
|
+
- **Legacy Office formats** (`.doc`, `.ppt`, `.xls`): Requires LibreOffice installed and on PATH
|
|
64
|
+
- **OpenDocument formats** (`.odt`, `.ods`, `.odp`): Requires `odfdo` Python package (included with `docling` extras)
|
|
65
|
+
|
|
66
|
+
## Quick Start
|
|
67
|
+
```python
|
|
68
|
+
# Recommended: use the M15 stable facade
|
|
69
|
+
from acquivela import AcquireRequest, acquire
|
|
70
|
+
|
|
71
|
+
result = acquire(
|
|
72
|
+
AcquireRequest(file_path="document.pdf")
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if result.status == "success":
|
|
76
|
+
doc = result.canonical_document
|
|
77
|
+
for page in doc.pages:
|
|
78
|
+
for block in page.text_blocks:
|
|
79
|
+
print(block.text)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Using the M15 facade (recommended normal path)
|
|
83
|
+
```python
|
|
84
|
+
from acquivela import AcquireRequest, acquire
|
|
85
|
+
|
|
86
|
+
result = acquire(
|
|
87
|
+
AcquireRequest(file_path="document.pdf")
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if result.status == "success":
|
|
91
|
+
doc = result.canonical_document
|
|
92
|
+
for page in doc.pages:
|
|
93
|
+
for block in page.text_blocks:
|
|
94
|
+
print(block.text)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Using Engine Hints
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
# Force a specific engine
|
|
101
|
+
request = AcquireRequest(
|
|
102
|
+
file_path="document.pdf",
|
|
103
|
+
engine_hint="docling"
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Using Capability Filtering
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from acquivela.capabilities import Capabilities
|
|
111
|
+
|
|
112
|
+
# Require text extraction capability
|
|
113
|
+
request = AcquireRequest(
|
|
114
|
+
file_path="document.pdf",
|
|
115
|
+
required_capability=Capabilities.DOCUMENT_TEXT
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Known Limitations
|
|
120
|
+
|
|
121
|
+
### MinerU
|
|
122
|
+
|
|
123
|
+
MinerU has a known upstream issue (`PPDocLayoutV2Config` missing `reading_order_config`) that causes acquisition failures. AcquiVela correctly contains this failure — the pipeline returns `status="failure"` without exceptions escaping. The MinerU plugin reports `healthy` at the health-check level because it is importable, but actual acquisition will fail until the upstream issue is resolved.
|
|
124
|
+
|
|
125
|
+
### Legacy Office Formats
|
|
126
|
+
|
|
127
|
+
`.doc`, `.ppt`, and `.xls` files require LibreOffice to be installed and available on PATH. Without LibreOffice, Docling cannot convert these legacy formats to modern equivalents.
|
|
128
|
+
|
|
129
|
+
### OpenDocument Formats
|
|
130
|
+
|
|
131
|
+
`.odt`, `.ods`, and `.odp` files require the `odfdo` Python package. This is included when installing with `pip install acquivela[docling]`.
|
|
132
|
+
|
|
133
|
+
### XML and JSON
|
|
134
|
+
|
|
135
|
+
Generic XML files are not supported. Docling supports specific XML dialects only (USPTO, JATS, XBRL, DOCLANG). Similarly, only `json_docling` format is supported for JSON input.
|
|
136
|
+
|
|
137
|
+
### CDM v0.2 Exclusions
|
|
138
|
+
|
|
139
|
+
The Canonical Document Model (CDM) v0.2 captures text content only. Tables, images, equations, bounding boxes, and rich provenance are intentionally excluded from the current model.
|
|
140
|
+
|
|
141
|
+
## Architecture
|
|
142
|
+
|
|
143
|
+
### Package Structure
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
acquivela/
|
|
147
|
+
├── __init__.py # Public exports + internal orchestration (see Governance)
|
|
148
|
+
├── cdm.py # CanonicalDocument, Page, TextBlock (PUBLIC/STABLE)
|
|
149
|
+
├── models.py # AcquireRequest (PUBLIC/STABLE)
|
|
150
|
+
├── plugin.py # Plugin SPI, PluginHealth (PUBLIC/STABLE)
|
|
151
|
+
├── result.py # PluginResult (INTERNAL - not exported at root)
|
|
152
|
+
├── public_result.py # AcquireResult, Diagnostics (PUBLIC/STABLE)
|
|
153
|
+
├── capabilities.py # Capabilities (PUBLIC/STABLE)
|
|
154
|
+
├── registry.py # Registry (INTERNAL - may change)
|
|
155
|
+
├── pipeline.py # Pipeline (INTERNAL - may change)
|
|
156
|
+
├── execution_context.py # ExecutionContext (INTERNAL)
|
|
157
|
+
└── plugins/ # Plugin implementations (one class per module)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Status
|
|
161
|
+
|
|
162
|
+
- Charter: FROZEN
|
|
163
|
+
- ADR-0001 Project Boundaries: FROZEN
|
|
164
|
+
- ADR-0002 Execution Model: FROZEN
|
|
165
|
+
- ADR-0003 Core Data Contracts: FROZEN
|
|
166
|
+
- ADR-0004 Plugin & Execution Strategy: FROZEN
|
|
167
|
+
- ADR-0005 Observability & Diagnostics: FROZEN
|
|
168
|
+
- ADR-0006 Registry & Plugin Discovery: FROZEN
|
|
169
|
+
|
|
170
|
+
## Development
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# Install with dev dependencies
|
|
174
|
+
pip install -e .[dev]
|
|
175
|
+
|
|
176
|
+
# Run contract tests
|
|
177
|
+
pytest tests/test_contracts.py
|
|
178
|
+
|
|
179
|
+
# Run import-linter validation
|
|
180
|
+
pytest tests/test_import_linter.py
|
|
181
|
+
|
|
182
|
+
# Or run import-linter directly
|
|
183
|
+
importlinter lint
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""AcquiVela — Document Acquisition Framework.
|
|
2
|
+
|
|
3
|
+
Public exports per frozen architecture (ADR-0001 through ADR-0006).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from acquivela.cdm import CanonicalDocument, Page, TextBlock
|
|
7
|
+
from acquivela.capabilities import Capabilities
|
|
8
|
+
from acquivela.models import AcquireRequest
|
|
9
|
+
from acquivela.plugin import Plugin
|
|
10
|
+
from acquivela.public_result import AcquireResult, Diagnostics
|
|
11
|
+
from acquivela.facade import acquire
|
|
12
|
+
from acquivela.pipeline import Pipeline
|
|
13
|
+
from acquivela.registry import Registry
|
|
14
|
+
|
|
15
|
+
__version__ = "0.2.0"
|
|
16
|
+
__all__ = [
|
|
17
|
+
"CanonicalDocument",
|
|
18
|
+
"Page",
|
|
19
|
+
"TextBlock",
|
|
20
|
+
"Capabilities",
|
|
21
|
+
"AcquireRequest",
|
|
22
|
+
"Plugin",
|
|
23
|
+
"AcquireResult",
|
|
24
|
+
"Diagnostics",
|
|
25
|
+
"Pipeline",
|
|
26
|
+
"Registry",
|
|
27
|
+
"acquire",
|
|
28
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Capability Vocabulary — Canonical capability constants for plugin selection.
|
|
2
|
+
|
|
3
|
+
Per ADR-0014: Plugins declare capabilities; consumers request them via
|
|
4
|
+
AcquireRequest.required_capability. Vocabulary is validated against this
|
|
5
|
+
module's constants before plugin lookup.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Capabilities:
|
|
10
|
+
"""Canonical capability constants.
|
|
11
|
+
|
|
12
|
+
Plugins declare capabilities as strings matching these constants.
|
|
13
|
+
Consumers request capabilities via AcquireRequest.required_capability.
|
|
14
|
+
|
|
15
|
+
Unknown plugin capabilities are tolerated at registration but inert
|
|
16
|
+
for selection — they cannot satisfy a required_capability request.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
DOCUMENT_TEXT = "document.text"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Canonical Document Model (CDM) v0.2 — Frozen per ADR-0003, extended per ADR-0011."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CanonicalDocument:
|
|
9
|
+
"""CanonicalDocument — frozen public contract.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
cdm_version: Version string, e.g., "0.2"
|
|
13
|
+
pages: List of Page objects (at least one required)
|
|
14
|
+
title: Document title, or None if unavailable (ADR-0011)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
pages: List["Page"],
|
|
20
|
+
cdm_version: str = "0.2",
|
|
21
|
+
title: str | None = None,
|
|
22
|
+
):
|
|
23
|
+
if not isinstance(pages, list):
|
|
24
|
+
raise TypeError(f"pages must be a list, got {type(pages).__name__}")
|
|
25
|
+
if len(pages) < 1:
|
|
26
|
+
raise ValueError("CanonicalDocument must contain at least one Page")
|
|
27
|
+
for i, page in enumerate(pages):
|
|
28
|
+
if not isinstance(page, Page):
|
|
29
|
+
raise TypeError(f"pages[{i}] must be a Page, got {type(page).__name__}")
|
|
30
|
+
self.cdm_version = cdm_version
|
|
31
|
+
self.pages = pages
|
|
32
|
+
self.title = title
|
|
33
|
+
|
|
34
|
+
def __repr__(self) -> str:
|
|
35
|
+
return (
|
|
36
|
+
f"CanonicalDocument(cdm_version={self.cdm_version!r}, "
|
|
37
|
+
f"pages={len(self.pages)}, title={self.title!r})"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def __eq__(self, other: object) -> bool:
|
|
41
|
+
if not isinstance(other, CanonicalDocument):
|
|
42
|
+
return NotImplemented
|
|
43
|
+
return (
|
|
44
|
+
self.cdm_version == other.cdm_version
|
|
45
|
+
and self.pages == other.pages
|
|
46
|
+
and self.title == other.title
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Page:
|
|
51
|
+
"""Page within CanonicalDocument — frozen public contract.
|
|
52
|
+
|
|
53
|
+
Attributes:
|
|
54
|
+
text_blocks: List of TextBlock objects (at least one required)
|
|
55
|
+
page_number: 1-based physical page number, or None if unknown (ADR-0011)
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
text_blocks: List["TextBlock"],
|
|
61
|
+
page_number: int | None = None,
|
|
62
|
+
):
|
|
63
|
+
if not isinstance(text_blocks, list):
|
|
64
|
+
raise TypeError(f"text_blocks must be a list, got {type(text_blocks).__name__}")
|
|
65
|
+
if len(text_blocks) < 1:
|
|
66
|
+
raise ValueError("Page must contain at least one TextBlock")
|
|
67
|
+
for i, block in enumerate(text_blocks):
|
|
68
|
+
if not isinstance(block, TextBlock):
|
|
69
|
+
raise TypeError(f"text_blocks[{i}] must be a TextBlock, got {type(block).__name__}")
|
|
70
|
+
self.text_blocks = text_blocks
|
|
71
|
+
self.page_number = page_number
|
|
72
|
+
|
|
73
|
+
def __repr__(self) -> str:
|
|
74
|
+
return f"Page(text_blocks={len(self.text_blocks)}, page_number={self.page_number!r})"
|
|
75
|
+
|
|
76
|
+
def __eq__(self, other: object) -> bool:
|
|
77
|
+
if not isinstance(other, Page):
|
|
78
|
+
return NotImplemented
|
|
79
|
+
return (
|
|
80
|
+
self.text_blocks == other.text_blocks
|
|
81
|
+
and self.page_number == other.page_number
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TextBlock:
|
|
86
|
+
"""TextBlock within Page — frozen public contract.
|
|
87
|
+
|
|
88
|
+
Attributes:
|
|
89
|
+
text: Text content string
|
|
90
|
+
text_type: Structural text classification, or None if unknown (ADR-0011).
|
|
91
|
+
Allowed values: "title", "paragraph", "list_item", "code", "caption"
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(self, text: str, text_type: str | None = None):
|
|
95
|
+
if not isinstance(text, str):
|
|
96
|
+
raise TypeError(f"text must be a string, got {type(text).__name__}")
|
|
97
|
+
self.text = text
|
|
98
|
+
self.text_type = text_type
|
|
99
|
+
|
|
100
|
+
def __repr__(self) -> str:
|
|
101
|
+
return f"TextBlock(text={self.text!r}, text_type={self.text_type!r})"
|
|
102
|
+
|
|
103
|
+
def __eq__(self, other: object) -> bool:
|
|
104
|
+
if not isinstance(other, TextBlock):
|
|
105
|
+
return NotImplemented
|
|
106
|
+
return self.text == other.text and self.text_type == other.text_type
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""ExecutionContext — Internal component per ADR-0005."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ExecutionContext:
|
|
7
|
+
"""Internal execution context — NOT a frozen contract.
|
|
8
|
+
|
|
9
|
+
This is an internal mechanism for carrying execution metadata
|
|
10
|
+
that does not belong in the frozen public contracts.
|
|
11
|
+
It is internal to the pipeline and carries no compatibility guarantee.
|
|
12
|
+
|
|
13
|
+
Per ADR-0005: Pipeline propagates ExecutionContext.selection_reason
|
|
14
|
+
into Diagnostics.selection_reason.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.selection_reason: str | None = None
|
|
19
|
+
self.selected_plugin_id: str | None = None
|
|
20
|
+
self.available_plugins: list[str] = []
|
|
21
|
+
self.selection_method: str | None = None
|
|
22
|
+
|
|
23
|
+
def __repr__(self) -> str:
|
|
24
|
+
return (
|
|
25
|
+
f"ExecutionContext(reason={self.selection_reason!r}, "
|
|
26
|
+
f"plugin={self.selected_plugin_id!r}, method={self.selection_method!r})"
|
|
27
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""AcquiVela high-level acquisition facade.
|
|
2
|
+
|
|
3
|
+
Provides a governed public execution entry point that allows consumers to perform
|
|
4
|
+
document acquisition using AcquiVela's stable public contracts without depending
|
|
5
|
+
directly on INTERNAL Pipeline or Registry APIs.
|
|
6
|
+
|
|
7
|
+
The facade delegates to existing internal orchestration (Pipeline/Registry/plugin
|
|
8
|
+
selection/health/fallback) while hiding those components from the consumer.
|
|
9
|
+
|
|
10
|
+
Do NOT promote Pipeline or Registry to PUBLIC/STABLE contracts.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from acquivela import AcquireRequest
|
|
18
|
+
from acquivela.public_result import AcquireResult
|
|
19
|
+
from acquivela.pipeline import Pipeline
|
|
20
|
+
from acquivela.registry import Registry
|
|
21
|
+
from acquivela.plugins.docling import DoclingPlugin
|
|
22
|
+
from acquivela.plugins.mineru import MinerUPlugin
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def acquire(request: AcquireRequest) -> AcquireResult:
|
|
26
|
+
"""Acquire a document using stable public contracts.
|
|
27
|
+
|
|
28
|
+
Consumers do not need to import Pipeline or Registry.
|
|
29
|
+
Concrete plugin instantiation is hidden behind the facade.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
request: Acquisition request specifying file path, engine hint,
|
|
33
|
+
and/or required capability.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
AcquireResult with status, canonical document, diagnostics, and error.
|
|
37
|
+
|
|
38
|
+
Normal consumer usage requires no direct Pipeline, Registry, or plugin
|
|
39
|
+
class construction.
|
|
40
|
+
"""
|
|
41
|
+
# Internal bootstrap: create Registry, auto-register built-in plugins,
|
|
42
|
+
# then delegate to existing Pipeline orchestration.
|
|
43
|
+
registry = Registry()
|
|
44
|
+
registry.register(DoclingPlugin())
|
|
45
|
+
registry.register(MinerUPlugin())
|
|
46
|
+
pipeline = Pipeline(registry=registry)
|
|
47
|
+
return pipeline.execute_with_registry(request)
|