docqwise 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- docqwise/__init__.py +9 -0
- docqwise/_version.py +1 -0
- docqwise/agent/__init__.py +0 -0
- docqwise/benchmark/__init__.py +0 -0
- docqwise/bridges/__init__.py +0 -0
- docqwise/chunkers/__init__.py +0 -0
- docqwise/classifier/__init__.py +0 -0
- docqwise/cli/__init__.py +0 -0
- docqwise/cli/main.py +79 -0
- docqwise/comparator/__init__.py +0 -0
- docqwise/config.py +130 -0
- docqwise/connectors/__init__.py +0 -0
- docqwise/core/__init__.py +13 -0
- docqwise/core/bases.py +180 -0
- docqwise/core/chunk.py +80 -0
- docqwise/core/document.py +101 -0
- docqwise/core/element.py +163 -0
- docqwise/core/field.py +72 -0
- docqwise/databases/__init__.py +0 -0
- docqwise/embedders/__init__.py +0 -0
- docqwise/engine.py +377 -0
- docqwise/exceptions.py +49 -0
- docqwise/export/__init__.py +0 -0
- docqwise/extractors/__init__.py +0 -0
- docqwise/graph/__init__.py +0 -0
- docqwise/graph/backends/__init__.py +0 -0
- docqwise/incremental/__init__.py +0 -0
- docqwise/layout/__init__.py +0 -0
- docqwise/learning/__init__.py +0 -0
- docqwise/learning/correction.py +94 -0
- docqwise/llm/__init__.py +0 -0
- docqwise/ocr/__init__.py +0 -0
- docqwise/pipeline/__init__.py +0 -0
- docqwise/pipeline/prebuilt/__init__.py +0 -0
- docqwise/readers/__init__.py +3 -0
- docqwise/readers/auto_reader.py +127 -0
- docqwise/readers/base.py +21 -0
- docqwise/readers/csv_reader.py +3 -0
- docqwise/readers/docx_reader.py +3 -0
- docqwise/readers/email_reader.py +3 -0
- docqwise/readers/excel_reader.py +3 -0
- docqwise/readers/html_reader.py +3 -0
- docqwise/readers/image_reader.py +3 -0
- docqwise/readers/json_reader.py +3 -0
- docqwise/readers/parquet_reader.py +3 -0
- docqwise/readers/pdf_reader.py +79 -0
- docqwise/readers/pptx_reader.py +3 -0
- docqwise/readers/text_reader.py +3 -0
- docqwise/retrieval/__init__.py +0 -0
- docqwise/schema/__init__.py +0 -0
- docqwise/security/__init__.py +0 -0
- docqwise/server/__init__.py +0 -0
- docqwise/server/routes/__init__.py +0 -0
- docqwise/speed/__init__.py +0 -0
- docqwise/stores/__init__.py +0 -0
- docqwise/strategy/__init__.py +0 -0
- docqwise/templates/__init__.py +0 -0
- docqwise-0.1.0.dist-info/METADATA +338 -0
- docqwise-0.1.0.dist-info/RECORD +63 -0
- docqwise-0.1.0.dist-info/WHEEL +5 -0
- docqwise-0.1.0.dist-info/entry_points.txt +2 -0
- docqwise-0.1.0.dist-info/licenses/LICENSE +189 -0
- docqwise-0.1.0.dist-info/top_level.txt +1 -0
docqwise/core/element.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Document element types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
from docqwise.core.chunk import BoundingBox, DocqwiseChunk, ChunkMetadata, ElementType
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class TableCell:
|
|
13
|
+
"""A single cell in a table."""
|
|
14
|
+
|
|
15
|
+
text: str
|
|
16
|
+
row: int
|
|
17
|
+
col: int
|
|
18
|
+
row_span: int = 1
|
|
19
|
+
col_span: int = 1
|
|
20
|
+
is_header: bool = False
|
|
21
|
+
bbox: Optional[BoundingBox] = None
|
|
22
|
+
confidence: float = 1.0
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class Table:
|
|
27
|
+
"""Extracted table from a document."""
|
|
28
|
+
|
|
29
|
+
table_id: str
|
|
30
|
+
cells: list[TableCell]
|
|
31
|
+
rows: int
|
|
32
|
+
cols: int
|
|
33
|
+
page: int = 0
|
|
34
|
+
bbox: Optional[BoundingBox] = None
|
|
35
|
+
caption: str = ""
|
|
36
|
+
confidence: float = 1.0
|
|
37
|
+
context: str = ""
|
|
38
|
+
has_merged_cells: bool = False
|
|
39
|
+
is_cross_page: bool = False
|
|
40
|
+
|
|
41
|
+
def to_dataframe(self):
|
|
42
|
+
"""Convert to pandas DataFrame."""
|
|
43
|
+
import pandas as pd
|
|
44
|
+
|
|
45
|
+
grid = [["" for _ in range(self.cols)] for _ in range(self.rows)]
|
|
46
|
+
headers = []
|
|
47
|
+
|
|
48
|
+
for cell in self.cells:
|
|
49
|
+
if cell.row < self.rows and cell.col < self.cols:
|
|
50
|
+
grid[cell.row][cell.col] = cell.text
|
|
51
|
+
if cell.is_header:
|
|
52
|
+
headers.append(cell.text)
|
|
53
|
+
|
|
54
|
+
if headers:
|
|
55
|
+
return pd.DataFrame(grid[1:], columns=grid[0])
|
|
56
|
+
return pd.DataFrame(grid)
|
|
57
|
+
|
|
58
|
+
def to_csv(self, path: str = "") -> str:
|
|
59
|
+
"""Export as CSV."""
|
|
60
|
+
df = self.to_dataframe()
|
|
61
|
+
if path:
|
|
62
|
+
df.to_csv(path, index=False)
|
|
63
|
+
return path
|
|
64
|
+
return df.to_csv(index=False)
|
|
65
|
+
|
|
66
|
+
def to_json(self) -> list[dict]:
|
|
67
|
+
"""Export as list of row dicts."""
|
|
68
|
+
df = self.to_dataframe()
|
|
69
|
+
return df.to_dict(orient="records")
|
|
70
|
+
|
|
71
|
+
def to_excel(self, path: str) -> None:
|
|
72
|
+
"""Export as Excel file."""
|
|
73
|
+
df = self.to_dataframe()
|
|
74
|
+
df.to_excel(path, index=False)
|
|
75
|
+
|
|
76
|
+
def to_markdown(self) -> str:
|
|
77
|
+
"""Export as markdown table."""
|
|
78
|
+
df = self.to_dataframe()
|
|
79
|
+
return df.to_markdown(index=False)
|
|
80
|
+
|
|
81
|
+
def to_chunk(self) -> DocqwiseChunk:
|
|
82
|
+
"""Convert to retrieval chunk with table context."""
|
|
83
|
+
text = f"Table: {self.caption}\n{self.to_markdown()}" if self.caption else self.to_markdown()
|
|
84
|
+
return DocqwiseChunk(
|
|
85
|
+
chunk_id=f"table_{self.table_id}",
|
|
86
|
+
text=text,
|
|
87
|
+
metadata=ChunkMetadata(
|
|
88
|
+
page_num=self.page,
|
|
89
|
+
element_type=ElementType.TABLE,
|
|
90
|
+
bbox=self.bbox,
|
|
91
|
+
confidence=self.confidence,
|
|
92
|
+
),
|
|
93
|
+
table_data=self.to_json(),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class ExtractedImage:
|
|
99
|
+
"""Image or figure extracted from a document."""
|
|
100
|
+
|
|
101
|
+
image_id: str
|
|
102
|
+
page: int = 0
|
|
103
|
+
bbox: Optional[BoundingBox] = None
|
|
104
|
+
caption: str = ""
|
|
105
|
+
alt_text: str = ""
|
|
106
|
+
image_type: str = "" # photo | chart | diagram | logo | signature
|
|
107
|
+
chart_type: str = "" # bar | line | pie | scatter
|
|
108
|
+
chart_data: Any = None
|
|
109
|
+
image_bytes: Optional[bytes] = None
|
|
110
|
+
image_path: Optional[str] = None
|
|
111
|
+
resolution: tuple[int, int] = (0, 0)
|
|
112
|
+
confidence: float = 1.0
|
|
113
|
+
|
|
114
|
+
def save(self, directory: str, filename: str = "") -> str:
|
|
115
|
+
"""Save extracted image to file."""
|
|
116
|
+
import os
|
|
117
|
+
|
|
118
|
+
if not filename:
|
|
119
|
+
filename = f"image_{self.image_id}.png"
|
|
120
|
+
path = os.path.join(directory, filename)
|
|
121
|
+
if self.image_bytes:
|
|
122
|
+
os.makedirs(directory, exist_ok=True)
|
|
123
|
+
with open(path, "wb") as f:
|
|
124
|
+
f.write(self.image_bytes)
|
|
125
|
+
return path
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@dataclass
|
|
129
|
+
class Entity:
|
|
130
|
+
"""Named entity extracted from a document."""
|
|
131
|
+
|
|
132
|
+
text: str
|
|
133
|
+
entity_type: str # PERSON, ORG, MONEY, DATE, LOCATION, custom
|
|
134
|
+
page: int = 0
|
|
135
|
+
bbox: Optional[BoundingBox] = None
|
|
136
|
+
confidence: float = 1.0
|
|
137
|
+
normalized_value: Any = None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass
|
|
141
|
+
class Relation:
|
|
142
|
+
"""Relation between two entities."""
|
|
143
|
+
|
|
144
|
+
subject: str
|
|
145
|
+
predicate: str
|
|
146
|
+
object: str
|
|
147
|
+
confidence: float = 1.0
|
|
148
|
+
page: int = 0
|
|
149
|
+
source_entities: list[Entity] = field(default_factory=list)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass
|
|
153
|
+
class FormField:
|
|
154
|
+
"""Form field extracted from a document."""
|
|
155
|
+
|
|
156
|
+
field_name: str
|
|
157
|
+
field_type: str # text | checkbox | radio | dropdown | signature
|
|
158
|
+
value: Any = None
|
|
159
|
+
is_checked: Optional[bool] = None
|
|
160
|
+
options: list[str] = field(default_factory=list)
|
|
161
|
+
bbox: Optional[BoundingBox] = None
|
|
162
|
+
page: int = 0
|
|
163
|
+
confidence: float = 1.0
|
docqwise/core/field.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Field extraction result models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
from docqwise.core.chunk import BoundingBox
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class FieldResult:
|
|
13
|
+
"""Result of a single field extraction."""
|
|
14
|
+
|
|
15
|
+
value: Any
|
|
16
|
+
confidence: float
|
|
17
|
+
raw_text: str = ""
|
|
18
|
+
bbox: Optional[BoundingBox] = None
|
|
19
|
+
page: int = 0
|
|
20
|
+
extraction_method: str = ""
|
|
21
|
+
is_corrected: bool = False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class ExtractionResult:
|
|
26
|
+
"""Result of extracting fields from a document."""
|
|
27
|
+
|
|
28
|
+
doc_id: str
|
|
29
|
+
source_path: str
|
|
30
|
+
fields: dict[str, FieldResult]
|
|
31
|
+
confidence: float
|
|
32
|
+
strategy_used: str = ""
|
|
33
|
+
processing_time_ms: float = 0.0
|
|
34
|
+
warnings: list[str] = field(default_factory=list)
|
|
35
|
+
|
|
36
|
+
_engine: Any = field(default=None, repr=False)
|
|
37
|
+
|
|
38
|
+
def correct(self, corrections: dict[str, Any], scope: str = "global") -> None:
|
|
39
|
+
"""Apply user corrections and store for future use."""
|
|
40
|
+
for field_name, correct_value in corrections.items():
|
|
41
|
+
if field_name in self.fields:
|
|
42
|
+
self.fields[field_name] = FieldResult(
|
|
43
|
+
value=correct_value,
|
|
44
|
+
confidence=1.0,
|
|
45
|
+
raw_text=self.fields[field_name].raw_text,
|
|
46
|
+
bbox=self.fields[field_name].bbox,
|
|
47
|
+
page=self.fields[field_name].page,
|
|
48
|
+
extraction_method="user_correction",
|
|
49
|
+
is_corrected=True,
|
|
50
|
+
)
|
|
51
|
+
else:
|
|
52
|
+
self.fields[field_name] = FieldResult(
|
|
53
|
+
value=correct_value,
|
|
54
|
+
confidence=1.0,
|
|
55
|
+
extraction_method="user_correction",
|
|
56
|
+
is_corrected=True,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if self._engine is not None:
|
|
60
|
+
self._engine._correction_store.save(
|
|
61
|
+
doc_id=self.doc_id,
|
|
62
|
+
source_path=self.source_path,
|
|
63
|
+
corrections=corrections,
|
|
64
|
+
scope=scope,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def to_dict(self) -> dict[str, Any]:
|
|
68
|
+
return {k: v.value for k, v in self.fields.items()}
|
|
69
|
+
|
|
70
|
+
def to_json(self) -> str:
|
|
71
|
+
import json
|
|
72
|
+
return json.dumps(self.to_dict(), default=str, indent=2)
|
|
File without changes
|
|
File without changes
|
docqwise/engine.py
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"""Main Docqwise engine class."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Optional, Union
|
|
7
|
+
|
|
8
|
+
from docqwise.config import DocqwiseConfig
|
|
9
|
+
from docqwise.core.document import DocqwiseDocument
|
|
10
|
+
from docqwise.core.chunk import DocqwiseChunk
|
|
11
|
+
from docqwise.core.field import ExtractionResult
|
|
12
|
+
from docqwise.core.element import Table, ExtractedImage, Entity, Relation
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger("docqwise")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Docqwise:
|
|
18
|
+
"""
|
|
19
|
+
DocQWise: Read. Extract. Retrieve.
|
|
20
|
+
|
|
21
|
+
Document intelligence engine that reads any format, extracts exact
|
|
22
|
+
structured data, and retrieves with semantic search.
|
|
23
|
+
|
|
24
|
+
Usage:
|
|
25
|
+
from docqwise import Docqwise
|
|
26
|
+
|
|
27
|
+
dq = Docqwise()
|
|
28
|
+
dq.ingest("documents/")
|
|
29
|
+
results = dq.retrieve("payment terms", top_k=5)
|
|
30
|
+
answer = dq.ask("What is the total amount?")
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
# Strategy
|
|
36
|
+
strategy: str = "auto",
|
|
37
|
+
confidence_threshold: float = 0.85,
|
|
38
|
+
max_api_cost_per_doc: float = 0.05,
|
|
39
|
+
# Speed
|
|
40
|
+
workers: Union[int, str] = 1,
|
|
41
|
+
gpu_workers: int = 0,
|
|
42
|
+
threads: int = 4,
|
|
43
|
+
batch_size: int = 32,
|
|
44
|
+
prefetch: bool = False,
|
|
45
|
+
async_mode: bool = False,
|
|
46
|
+
# Models
|
|
47
|
+
ocr: Any = "easyocr",
|
|
48
|
+
layout_model: Any = "yolov8",
|
|
49
|
+
embedder: Any = "all-MiniLM-L6-v2",
|
|
50
|
+
llm: Any = None,
|
|
51
|
+
reranker: Any = None,
|
|
52
|
+
# Storage
|
|
53
|
+
vector_store: Any = "sqlite",
|
|
54
|
+
database: Any = None,
|
|
55
|
+
graph_store: Any = None,
|
|
56
|
+
store_path: str = "./docqwise_db",
|
|
57
|
+
# Search
|
|
58
|
+
search_mode: str = "vector",
|
|
59
|
+
# Incremental
|
|
60
|
+
incremental: bool = True,
|
|
61
|
+
cache: bool = True,
|
|
62
|
+
cache_dir: str = ".docqwise_cache",
|
|
63
|
+
# Learning
|
|
64
|
+
learning_mode: str = "exact",
|
|
65
|
+
# Config override
|
|
66
|
+
config: Optional[Union[str, DocqwiseConfig]] = None,
|
|
67
|
+
):
|
|
68
|
+
if isinstance(config, str):
|
|
69
|
+
self._config = DocqwiseConfig.from_yaml(config)
|
|
70
|
+
elif isinstance(config, DocqwiseConfig):
|
|
71
|
+
self._config = config
|
|
72
|
+
else:
|
|
73
|
+
self._config = DocqwiseConfig()
|
|
74
|
+
self._config.strategy.mode = strategy
|
|
75
|
+
self._config.strategy.confidence_threshold = confidence_threshold
|
|
76
|
+
self._config.speed.workers = workers
|
|
77
|
+
self._config.speed.gpu_workers = gpu_workers
|
|
78
|
+
self._config.speed.threads = threads
|
|
79
|
+
self._config.speed.batch_size = batch_size
|
|
80
|
+
self._config.speed.prefetch = prefetch
|
|
81
|
+
self._config.speed.async_mode = async_mode
|
|
82
|
+
self._config.storage.vector_store = vector_store if isinstance(vector_store, str) else "custom"
|
|
83
|
+
self._config.storage.store_path = store_path
|
|
84
|
+
self._config.storage.cache = cache
|
|
85
|
+
self._config.storage.cache_dir = cache_dir
|
|
86
|
+
self._config.search.mode = search_mode
|
|
87
|
+
self._config.incremental.enabled = incremental
|
|
88
|
+
|
|
89
|
+
# Store raw component references for lazy init
|
|
90
|
+
self._ocr_ref = ocr
|
|
91
|
+
self._layout_ref = layout_model
|
|
92
|
+
self._embedder_ref = embedder
|
|
93
|
+
self._llm_ref = llm
|
|
94
|
+
self._reranker_ref = reranker
|
|
95
|
+
self._vector_store_ref = vector_store
|
|
96
|
+
self._database_ref = database
|
|
97
|
+
self._graph_store_ref = graph_store
|
|
98
|
+
self._learning_mode = learning_mode
|
|
99
|
+
|
|
100
|
+
# Lazy-initialized components
|
|
101
|
+
self._reader = None
|
|
102
|
+
self._ocr_engine = None
|
|
103
|
+
self._layout_model = None
|
|
104
|
+
self._embedder = None
|
|
105
|
+
self._llm = None
|
|
106
|
+
self._reranker = None
|
|
107
|
+
self._vector_store = None
|
|
108
|
+
self._database = None
|
|
109
|
+
self._graph_store = None
|
|
110
|
+
self._correction_store = None
|
|
111
|
+
self._strategy_router = None
|
|
112
|
+
self._change_detector = None
|
|
113
|
+
|
|
114
|
+
self._initialized = False
|
|
115
|
+
logger.info("DocQWise engine created (lazy init)")
|
|
116
|
+
|
|
117
|
+
def _ensure_initialized(self) -> None:
|
|
118
|
+
"""Lazy initialization of components on first use."""
|
|
119
|
+
if self._initialized:
|
|
120
|
+
return
|
|
121
|
+
self._init_reader()
|
|
122
|
+
self._init_store()
|
|
123
|
+
self._init_correction_store()
|
|
124
|
+
self._initialized = True
|
|
125
|
+
logger.info("DocQWise engine initialized")
|
|
126
|
+
|
|
127
|
+
def _init_reader(self) -> None:
|
|
128
|
+
from docqwise.readers.auto_reader import AutoReader
|
|
129
|
+
self._reader = AutoReader()
|
|
130
|
+
|
|
131
|
+
def _init_store(self) -> None:
|
|
132
|
+
if isinstance(self._vector_store_ref, str) and self._vector_store_ref == "sqlite":
|
|
133
|
+
from docqwise.stores.sqlite_store import SQLiteVectorStore
|
|
134
|
+
self._vector_store = SQLiteVectorStore(self._config.storage.store_path)
|
|
135
|
+
elif hasattr(self._vector_store_ref, "insert"):
|
|
136
|
+
self._vector_store = self._vector_store_ref
|
|
137
|
+
|
|
138
|
+
def _init_embedder(self) -> None:
|
|
139
|
+
if self._embedder is not None:
|
|
140
|
+
return
|
|
141
|
+
if isinstance(self._embedder_ref, str):
|
|
142
|
+
from docqwise.embedders.sentence_transformer import SentenceTransformerEmbedder
|
|
143
|
+
self._embedder = SentenceTransformerEmbedder(self._embedder_ref)
|
|
144
|
+
elif hasattr(self._embedder_ref, "embed"):
|
|
145
|
+
self._embedder = self._embedder_ref
|
|
146
|
+
|
|
147
|
+
def _init_correction_store(self) -> None:
|
|
148
|
+
from docqwise.learning.correction import CorrectionStore
|
|
149
|
+
self._correction_store = CorrectionStore(self._config.storage.store_path)
|
|
150
|
+
|
|
151
|
+
# ═══════════════════════════════════════
|
|
152
|
+
# INGESTION
|
|
153
|
+
# ═══════════════════════════════════════
|
|
154
|
+
|
|
155
|
+
def ingest(self, source: str, **kwargs) -> dict:
|
|
156
|
+
"""Ingest documents from file, folder, URL, or database connection string."""
|
|
157
|
+
self._ensure_initialized()
|
|
158
|
+
# TODO: implement full ingestion pipeline
|
|
159
|
+
logger.info(f"Ingesting from: {source}")
|
|
160
|
+
raise NotImplementedError("Ingestion pipeline coming in v0.1.0")
|
|
161
|
+
|
|
162
|
+
async def ingest_async(self, source: str, **kwargs) -> dict:
|
|
163
|
+
"""Async ingestion with parallel processing."""
|
|
164
|
+
self._ensure_initialized()
|
|
165
|
+
raise NotImplementedError("Async ingestion coming in v0.1.0")
|
|
166
|
+
|
|
167
|
+
def sync(self, source: str = None, mode: str = "incremental",
|
|
168
|
+
interval: int = 300) -> None:
|
|
169
|
+
"""Sync with database — incremental by default."""
|
|
170
|
+
raise NotImplementedError("Database sync coming in v0.1.0")
|
|
171
|
+
|
|
172
|
+
def watch(self, path: str, poll_interval: int = 60,
|
|
173
|
+
on_new: Any = None) -> None:
|
|
174
|
+
"""Watch folder for new documents."""
|
|
175
|
+
raise NotImplementedError("File watching coming in v0.1.0")
|
|
176
|
+
|
|
177
|
+
# ═══════════════════════════════════════
|
|
178
|
+
# EXTRACTION
|
|
179
|
+
# ═══════════════════════════════════════
|
|
180
|
+
|
|
181
|
+
def extract_all(self, source: str, **kwargs) -> DocqwiseDocument:
|
|
182
|
+
"""Extract everything from a document."""
|
|
183
|
+
self._ensure_initialized()
|
|
184
|
+
raise NotImplementedError("Full extraction coming in v0.1.0")
|
|
185
|
+
|
|
186
|
+
def extract_text(self, source: str, **kwargs) -> str:
|
|
187
|
+
"""Extract text from a document."""
|
|
188
|
+
self._ensure_initialized()
|
|
189
|
+
raise NotImplementedError("Text extraction coming in v0.1.0")
|
|
190
|
+
|
|
191
|
+
def extract_fields(self, source: str, schema: dict = None,
|
|
192
|
+
template: str = None, **kwargs) -> ExtractionResult:
|
|
193
|
+
"""Extract fields — schema-driven or auto-detect."""
|
|
194
|
+
self._ensure_initialized()
|
|
195
|
+
raise NotImplementedError("Field extraction coming in v0.1.0")
|
|
196
|
+
|
|
197
|
+
def extract_fields_batch(self, source: str, **kwargs) -> list[ExtractionResult]:
|
|
198
|
+
"""Batch field extraction across multiple documents."""
|
|
199
|
+
self._ensure_initialized()
|
|
200
|
+
raise NotImplementedError("Batch extraction coming in v0.1.0")
|
|
201
|
+
|
|
202
|
+
def extract_tables(self, source: str, **kwargs) -> list[Table]:
|
|
203
|
+
"""Extract tables from a document."""
|
|
204
|
+
self._ensure_initialized()
|
|
205
|
+
raise NotImplementedError("Table extraction coming in v0.1.0")
|
|
206
|
+
|
|
207
|
+
def extract_images(self, source: str, **kwargs) -> list[ExtractedImage]:
|
|
208
|
+
"""Extract images and figures from a document."""
|
|
209
|
+
self._ensure_initialized()
|
|
210
|
+
raise NotImplementedError("Image extraction coming in v0.1.0")
|
|
211
|
+
|
|
212
|
+
def extract_form(self, source: str) -> dict:
|
|
213
|
+
"""Extract form fields."""
|
|
214
|
+
self._ensure_initialized()
|
|
215
|
+
raise NotImplementedError("Form extraction coming in v0.1.0")
|
|
216
|
+
|
|
217
|
+
def extract_entities(self, source: str, types: list[str] = None,
|
|
218
|
+
custom_types: dict = None) -> list[Entity]:
|
|
219
|
+
"""Extract named entities."""
|
|
220
|
+
self._ensure_initialized()
|
|
221
|
+
raise NotImplementedError("Entity extraction coming in v0.1.0")
|
|
222
|
+
|
|
223
|
+
def extract_relations(self, source: str) -> list[Relation]:
|
|
224
|
+
"""Extract entity relations."""
|
|
225
|
+
self._ensure_initialized()
|
|
226
|
+
raise NotImplementedError("Relation extraction coming in v0.1.0")
|
|
227
|
+
|
|
228
|
+
def extract_layout(self, source: str) -> dict:
|
|
229
|
+
"""Extract full layout map with bounding boxes."""
|
|
230
|
+
self._ensure_initialized()
|
|
231
|
+
raise NotImplementedError("Layout extraction coming in v0.1.0")
|
|
232
|
+
|
|
233
|
+
def auto_extract(self, source: str) -> ExtractionResult:
|
|
234
|
+
"""Auto-detect document type and extract all relevant fields."""
|
|
235
|
+
self._ensure_initialized()
|
|
236
|
+
raise NotImplementedError("Auto extraction coming in v0.1.0")
|
|
237
|
+
|
|
238
|
+
# ═══════════════════════════════════════
|
|
239
|
+
# RETRIEVAL + Q&A
|
|
240
|
+
# ═══════════════════════════════════════
|
|
241
|
+
|
|
242
|
+
def retrieve(self, query: str, top_k: int = 5,
|
|
243
|
+
mode: str = None, **kwargs) -> list[dict]:
|
|
244
|
+
"""Semantic search across ingested corpus."""
|
|
245
|
+
self._ensure_initialized()
|
|
246
|
+
raise NotImplementedError("Retrieval coming in v0.1.0")
|
|
247
|
+
|
|
248
|
+
def ask(self, question: str, source: str = None, **kwargs) -> str:
|
|
249
|
+
"""Natural language Q&A."""
|
|
250
|
+
self._ensure_initialized()
|
|
251
|
+
raise NotImplementedError("Q&A coming in v0.1.0")
|
|
252
|
+
|
|
253
|
+
def ask_batch(self, question: str = None, questions: list = None,
|
|
254
|
+
documents: str = None, **kwargs) -> list:
|
|
255
|
+
"""Batch Q&A across documents."""
|
|
256
|
+
raise NotImplementedError("Batch Q&A coming in v0.1.0")
|
|
257
|
+
|
|
258
|
+
def sql(self, query: str) -> list[dict]:
|
|
259
|
+
"""Run SQL directly against ingested structured data."""
|
|
260
|
+
raise NotImplementedError("SQL queries coming in v0.1.0")
|
|
261
|
+
|
|
262
|
+
# ═══════════════════════════════════════
|
|
263
|
+
# CLASSIFICATION + COMPARISON
|
|
264
|
+
# ═══════════════════════════════════════
|
|
265
|
+
|
|
266
|
+
def classify(self, source: str, **kwargs) -> list[dict]:
|
|
267
|
+
"""Classify document type."""
|
|
268
|
+
raise NotImplementedError("Classification coming in v0.1.0")
|
|
269
|
+
|
|
270
|
+
def classify_batch(self, source: str, **kwargs) -> list:
|
|
271
|
+
"""Batch classification."""
|
|
272
|
+
raise NotImplementedError("Batch classification coming in v0.1.0")
|
|
273
|
+
|
|
274
|
+
def set_taxonomy(self, labels: list[str]) -> None:
|
|
275
|
+
"""Set custom classification taxonomy."""
|
|
276
|
+
raise NotImplementedError("Taxonomy coming in v0.1.0")
|
|
277
|
+
|
|
278
|
+
def train_classifier(self, examples: dict) -> None:
|
|
279
|
+
"""Few-shot classifier training."""
|
|
280
|
+
raise NotImplementedError("Classifier training coming in v0.1.0")
|
|
281
|
+
|
|
282
|
+
def compare(self, source_a: str, source_b: str) -> dict:
|
|
283
|
+
"""Compare two documents."""
|
|
284
|
+
raise NotImplementedError("Comparison coming in v0.1.0")
|
|
285
|
+
|
|
286
|
+
def compare_batch(self, pairs: list, **kwargs) -> list:
|
|
287
|
+
"""Batch comparison."""
|
|
288
|
+
raise NotImplementedError("Batch comparison coming in v0.1.0")
|
|
289
|
+
|
|
290
|
+
def find_duplicates(self, source: str, threshold: float = 0.85) -> list:
|
|
291
|
+
"""Find near-duplicate documents."""
|
|
292
|
+
raise NotImplementedError("Deduplication coming in v0.1.0")
|
|
293
|
+
|
|
294
|
+
# ═══════════════════════════════════════
|
|
295
|
+
# SCHEMA
|
|
296
|
+
# ═══════════════════════════════════════
|
|
297
|
+
|
|
298
|
+
def detect_schema(self, source: str) -> dict:
|
|
299
|
+
"""Auto-detect schema from structured data."""
|
|
300
|
+
raise NotImplementedError("Schema detection coming in v0.1.0")
|
|
301
|
+
|
|
302
|
+
def validate(self, source: str, schema: dict) -> dict:
|
|
303
|
+
"""Validate data against schema."""
|
|
304
|
+
raise NotImplementedError("Validation coming in v0.1.0")
|
|
305
|
+
|
|
306
|
+
def discover_schema(self, connection_string: str) -> dict:
|
|
307
|
+
"""Discover database schema."""
|
|
308
|
+
raise NotImplementedError("Schema discovery coming in v0.1.0")
|
|
309
|
+
|
|
310
|
+
# ═══════════════════════════════════════
|
|
311
|
+
# GRAPH
|
|
312
|
+
# ═══════════════════════════════════════
|
|
313
|
+
|
|
314
|
+
def build_graph(self, source: str = None) -> Any:
|
|
315
|
+
"""Build document graph from ingested corpus."""
|
|
316
|
+
raise NotImplementedError("GraphRAG coming in v0.1.0")
|
|
317
|
+
|
|
318
|
+
# ═══════════════════════════════════════
|
|
319
|
+
# LEARNING
|
|
320
|
+
# ═══════════════════════════════════════
|
|
321
|
+
|
|
322
|
+
def learn_from_labels(self, documents: str, labels: str) -> None:
|
|
323
|
+
"""Bulk learning from labeled dataset."""
|
|
324
|
+
raise NotImplementedError("Bulk learning coming in v0.1.0")
|
|
325
|
+
|
|
326
|
+
def learning_report(self) -> dict:
|
|
327
|
+
"""Report on corrections and accuracy improvements."""
|
|
328
|
+
raise NotImplementedError("Learning report coming in v0.1.0")
|
|
329
|
+
|
|
330
|
+
def export_learnings(self, path: str) -> None:
|
|
331
|
+
"""Export learned corrections."""
|
|
332
|
+
raise NotImplementedError("Export learnings coming in v0.1.0")
|
|
333
|
+
|
|
334
|
+
def import_learnings(self, path: str) -> None:
|
|
335
|
+
"""Import corrections from another instance."""
|
|
336
|
+
raise NotImplementedError("Import learnings coming in v0.1.0")
|
|
337
|
+
|
|
338
|
+
# ═══════════════════════════════════════
|
|
339
|
+
# UTILITIES
|
|
340
|
+
# ═══════════════════════════════════════
|
|
341
|
+
|
|
342
|
+
def to_dataframe(self, source: str, **kwargs):
|
|
343
|
+
"""Convert structured source to DataFrame."""
|
|
344
|
+
raise NotImplementedError("DataFrame conversion coming in v0.1.0")
|
|
345
|
+
|
|
346
|
+
def detect_pii(self, source: str) -> list:
|
|
347
|
+
"""Detect PII in document."""
|
|
348
|
+
raise NotImplementedError("PII detection coming in v0.1.0")
|
|
349
|
+
|
|
350
|
+
def redact(self, source: str, output: str, redact_types: list = None) -> None:
|
|
351
|
+
"""Redact PII."""
|
|
352
|
+
raise NotImplementedError("PII redaction coming in v0.1.0")
|
|
353
|
+
|
|
354
|
+
def checkpoint(self) -> None:
|
|
355
|
+
"""Save current processing state."""
|
|
356
|
+
raise NotImplementedError("Checkpointing coming in v0.1.0")
|
|
357
|
+
|
|
358
|
+
def metrics(self) -> dict:
|
|
359
|
+
"""Pipeline health and performance metrics."""
|
|
360
|
+
return {
|
|
361
|
+
"version": "0.1.0",
|
|
362
|
+
"store_path": self._config.storage.store_path,
|
|
363
|
+
"strategy": self._config.strategy.mode,
|
|
364
|
+
"workers": self._config.speed.workers,
|
|
365
|
+
"initialized": self._initialized,
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
def export(self, path: str, format: str = "json", **kwargs) -> None:
|
|
369
|
+
"""Export processed data."""
|
|
370
|
+
raise NotImplementedError("Export coming in v0.1.0")
|
|
371
|
+
|
|
372
|
+
def __repr__(self) -> str:
|
|
373
|
+
return (
|
|
374
|
+
f"Docqwise(strategy='{self._config.strategy.mode}', "
|
|
375
|
+
f"store='{self._config.storage.vector_store}', "
|
|
376
|
+
f"workers={self._config.speed.workers})"
|
|
377
|
+
)
|
docqwise/exceptions.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""DocQWise exceptions."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DocqwiseError(Exception):
|
|
5
|
+
"""Base exception for docqwise."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ReaderError(DocqwiseError):
|
|
9
|
+
"""Error reading a document."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UnsupportedFormatError(ReaderError):
|
|
13
|
+
"""Document format is not supported."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ExtractionError(DocqwiseError):
|
|
17
|
+
"""Error extracting content from a document."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class OCRError(ExtractionError):
|
|
21
|
+
"""Error during OCR processing."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class StoreError(DocqwiseError):
|
|
25
|
+
"""Error with vector/database store operations."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConnectionError(DocqwiseError):
|
|
29
|
+
"""Error connecting to external service."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SchemaError(DocqwiseError):
|
|
33
|
+
"""Error with schema detection or validation."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PipelineError(DocqwiseError):
|
|
37
|
+
"""Error in pipeline execution."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CycleDetectedError(PipelineError):
|
|
41
|
+
"""Pipeline DAG contains a cycle."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ConfigError(DocqwiseError):
|
|
45
|
+
"""Invalid configuration."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class CorrectionError(DocqwiseError):
|
|
49
|
+
"""Error applying or storing corrections."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|