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.
Files changed (63) hide show
  1. docqwise/__init__.py +9 -0
  2. docqwise/_version.py +1 -0
  3. docqwise/agent/__init__.py +0 -0
  4. docqwise/benchmark/__init__.py +0 -0
  5. docqwise/bridges/__init__.py +0 -0
  6. docqwise/chunkers/__init__.py +0 -0
  7. docqwise/classifier/__init__.py +0 -0
  8. docqwise/cli/__init__.py +0 -0
  9. docqwise/cli/main.py +79 -0
  10. docqwise/comparator/__init__.py +0 -0
  11. docqwise/config.py +130 -0
  12. docqwise/connectors/__init__.py +0 -0
  13. docqwise/core/__init__.py +13 -0
  14. docqwise/core/bases.py +180 -0
  15. docqwise/core/chunk.py +80 -0
  16. docqwise/core/document.py +101 -0
  17. docqwise/core/element.py +163 -0
  18. docqwise/core/field.py +72 -0
  19. docqwise/databases/__init__.py +0 -0
  20. docqwise/embedders/__init__.py +0 -0
  21. docqwise/engine.py +377 -0
  22. docqwise/exceptions.py +49 -0
  23. docqwise/export/__init__.py +0 -0
  24. docqwise/extractors/__init__.py +0 -0
  25. docqwise/graph/__init__.py +0 -0
  26. docqwise/graph/backends/__init__.py +0 -0
  27. docqwise/incremental/__init__.py +0 -0
  28. docqwise/layout/__init__.py +0 -0
  29. docqwise/learning/__init__.py +0 -0
  30. docqwise/learning/correction.py +94 -0
  31. docqwise/llm/__init__.py +0 -0
  32. docqwise/ocr/__init__.py +0 -0
  33. docqwise/pipeline/__init__.py +0 -0
  34. docqwise/pipeline/prebuilt/__init__.py +0 -0
  35. docqwise/readers/__init__.py +3 -0
  36. docqwise/readers/auto_reader.py +127 -0
  37. docqwise/readers/base.py +21 -0
  38. docqwise/readers/csv_reader.py +3 -0
  39. docqwise/readers/docx_reader.py +3 -0
  40. docqwise/readers/email_reader.py +3 -0
  41. docqwise/readers/excel_reader.py +3 -0
  42. docqwise/readers/html_reader.py +3 -0
  43. docqwise/readers/image_reader.py +3 -0
  44. docqwise/readers/json_reader.py +3 -0
  45. docqwise/readers/parquet_reader.py +3 -0
  46. docqwise/readers/pdf_reader.py +79 -0
  47. docqwise/readers/pptx_reader.py +3 -0
  48. docqwise/readers/text_reader.py +3 -0
  49. docqwise/retrieval/__init__.py +0 -0
  50. docqwise/schema/__init__.py +0 -0
  51. docqwise/security/__init__.py +0 -0
  52. docqwise/server/__init__.py +0 -0
  53. docqwise/server/routes/__init__.py +0 -0
  54. docqwise/speed/__init__.py +0 -0
  55. docqwise/stores/__init__.py +0 -0
  56. docqwise/strategy/__init__.py +0 -0
  57. docqwise/templates/__init__.py +0 -0
  58. docqwise-0.1.0.dist-info/METADATA +338 -0
  59. docqwise-0.1.0.dist-info/RECORD +63 -0
  60. docqwise-0.1.0.dist-info/WHEEL +5 -0
  61. docqwise-0.1.0.dist-info/entry_points.txt +2 -0
  62. docqwise-0.1.0.dist-info/licenses/LICENSE +189 -0
  63. docqwise-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,94 @@
1
+ """Simple correction store — exact overrides, no ML."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import sqlite3
8
+ from datetime import datetime
9
+ from typing import Any, Optional
10
+
11
+
12
+ class CorrectionStore:
13
+ """Stores user corrections as exact overrides. No RL. No weights. Deterministic."""
14
+
15
+ def __init__(self, store_path: str = "./docqwise_db"):
16
+ os.makedirs(store_path, exist_ok=True)
17
+ self._db_path = os.path.join(store_path, "corrections.db")
18
+ self._init_db()
19
+
20
+ def _init_db(self) -> None:
21
+ with sqlite3.connect(self._db_path) as conn:
22
+ conn.execute("""
23
+ CREATE TABLE IF NOT EXISTS corrections (
24
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
25
+ doc_id TEXT,
26
+ source_path TEXT,
27
+ field_name TEXT NOT NULL,
28
+ wrong_value TEXT,
29
+ correct_value TEXT NOT NULL,
30
+ scope TEXT DEFAULT 'global',
31
+ vendor TEXT,
32
+ doc_type TEXT,
33
+ created_at TEXT NOT NULL
34
+ )
35
+ """)
36
+ conn.execute("""
37
+ CREATE INDEX IF NOT EXISTS idx_corrections_field
38
+ ON corrections(field_name, scope, vendor)
39
+ """)
40
+
41
+ def save(self, doc_id: str, source_path: str,
42
+ corrections: dict[str, Any], scope: str = "global") -> None:
43
+ """Save corrections as exact overrides."""
44
+ with sqlite3.connect(self._db_path) as conn:
45
+ for field_name, correct_value in corrections.items():
46
+ conn.execute(
47
+ """INSERT INTO corrections
48
+ (doc_id, source_path, field_name, correct_value, scope, created_at)
49
+ VALUES (?, ?, ?, ?, ?, ?)""",
50
+ (doc_id, source_path, field_name,
51
+ json.dumps(correct_value, default=str),
52
+ scope, datetime.now().isoformat()),
53
+ )
54
+
55
+ def apply(self, doc_type: str, fields: dict[str, Any],
56
+ vendor: Optional[str] = None) -> dict[str, Any]:
57
+ """Apply stored corrections as exact overrides. No scoring. No probability."""
58
+ corrected = dict(fields)
59
+ with sqlite3.connect(self._db_path) as conn:
60
+ for field_name in fields:
61
+ rows = conn.execute(
62
+ """SELECT correct_value FROM corrections
63
+ WHERE field_name = ? AND (scope = 'global' OR vendor = ?)
64
+ ORDER BY created_at DESC LIMIT 1""",
65
+ (field_name, vendor or ""),
66
+ ).fetchall()
67
+ if rows:
68
+ corrected[field_name] = json.loads(rows[0][0])
69
+ return corrected
70
+
71
+ def list_corrections(self, doc_type: Optional[str] = None) -> list[dict]:
72
+ """List all stored corrections."""
73
+ with sqlite3.connect(self._db_path) as conn:
74
+ conn.row_factory = sqlite3.Row
75
+ if doc_type:
76
+ rows = conn.execute(
77
+ "SELECT * FROM corrections WHERE doc_type = ? ORDER BY created_at DESC",
78
+ (doc_type,),
79
+ ).fetchall()
80
+ else:
81
+ rows = conn.execute(
82
+ "SELECT * FROM corrections ORDER BY created_at DESC"
83
+ ).fetchall()
84
+ return [dict(row) for row in rows]
85
+
86
+ def delete_correction(self, correction_id: int) -> None:
87
+ """Remove a correction."""
88
+ with sqlite3.connect(self._db_path) as conn:
89
+ conn.execute("DELETE FROM corrections WHERE id = ?", (correction_id,))
90
+
91
+ def count(self) -> int:
92
+ """Count total corrections."""
93
+ with sqlite3.connect(self._db_path) as conn:
94
+ return conn.execute("SELECT COUNT(*) FROM corrections").fetchone()[0]
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,3 @@
1
+ """Document readers — one reader per format."""
2
+ from docqwise.readers.base import BaseReader
3
+ from docqwise.readers.auto_reader import AutoReader
@@ -0,0 +1,127 @@
1
+ """Auto-detect document format and dispatch to appropriate reader."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Optional
7
+
8
+ from docqwise.core.document import DocqwiseDocument, DocumentType, SourceType
9
+ from docqwise.readers.base import BaseReader
10
+
11
+
12
+ FORMAT_MAP = {
13
+ ".pdf": DocumentType.PDF,
14
+ ".doc": DocumentType.DOCX,
15
+ ".docx": DocumentType.DOCX,
16
+ ".rtf": DocumentType.RTF,
17
+ ".txt": DocumentType.TEXT,
18
+ ".md": DocumentType.MARKDOWN,
19
+ ".rst": DocumentType.TEXT,
20
+ ".log": DocumentType.TEXT,
21
+ ".html": DocumentType.HTML,
22
+ ".htm": DocumentType.HTML,
23
+ ".xhtml": DocumentType.HTML,
24
+ ".jpg": DocumentType.IMAGE,
25
+ ".jpeg": DocumentType.IMAGE,
26
+ ".png": DocumentType.IMAGE,
27
+ ".tiff": DocumentType.IMAGE,
28
+ ".tif": DocumentType.IMAGE,
29
+ ".bmp": DocumentType.IMAGE,
30
+ ".webp": DocumentType.IMAGE,
31
+ ".heic": DocumentType.IMAGE,
32
+ ".xlsx": DocumentType.EXCEL,
33
+ ".xls": DocumentType.EXCEL,
34
+ ".xlsm": DocumentType.EXCEL,
35
+ ".ods": DocumentType.EXCEL,
36
+ ".csv": DocumentType.CSV,
37
+ ".tsv": DocumentType.CSV,
38
+ ".json": DocumentType.JSON,
39
+ ".jsonl": DocumentType.JSON,
40
+ ".xml": DocumentType.XML,
41
+ ".yaml": DocumentType.YAML,
42
+ ".yml": DocumentType.YAML,
43
+ ".toml": DocumentType.YAML,
44
+ ".parquet": DocumentType.PARQUET,
45
+ ".pptx": DocumentType.PPTX,
46
+ ".ppt": DocumentType.PPTX,
47
+ ".eml": DocumentType.EMAIL,
48
+ ".msg": DocumentType.EMAIL,
49
+ }
50
+
51
+
52
+ class AutoReader(BaseReader):
53
+ """Auto-detects document format and dispatches to the correct reader."""
54
+
55
+ def __init__(self):
56
+ self._readers: dict[DocumentType, BaseReader] = {}
57
+
58
+ def can_read(self, path: str) -> bool:
59
+ return True
60
+
61
+ def supported_formats(self) -> list[str]:
62
+ return list(FORMAT_MAP.keys())
63
+
64
+ def detect_type(self, path: str) -> DocumentType:
65
+ """Detect document type from file extension and magic bytes."""
66
+ if path.startswith(("postgresql://", "mysql://", "sqlite:///",
67
+ "mongodb://", "mongodb+srv://")):
68
+ return DocumentType.DATABASE
69
+
70
+ ext = os.path.splitext(path)[1].lower()
71
+ return FORMAT_MAP.get(ext, DocumentType.UNKNOWN)
72
+
73
+ def detect_source_type(self, path: str) -> SourceType:
74
+ """Detect source type from path."""
75
+ if path.startswith(("http://", "https://")):
76
+ return SourceType.URL
77
+ if path.startswith(("postgresql://", "mysql://", "sqlite:///", "mongodb://")):
78
+ return SourceType.DATABASE
79
+ if path.startswith(("s3://", "gs://", "az://")):
80
+ return SourceType.CLOUD_STORAGE
81
+ if path.startswith(("imap://", "pop3://")):
82
+ return SourceType.EMAIL
83
+ return SourceType.FILE
84
+
85
+ def _get_reader(self, doc_type: DocumentType) -> BaseReader:
86
+ """Get or lazy-load the reader for a document type."""
87
+ if doc_type in self._readers:
88
+ return self._readers[doc_type]
89
+
90
+ reader: Optional[BaseReader] = None
91
+
92
+ if doc_type == DocumentType.PDF:
93
+ from docqwise.readers.pdf_reader import PDFReader
94
+ reader = PDFReader()
95
+ elif doc_type == DocumentType.DOCX:
96
+ from docqwise.readers.docx_reader import DOCXReader
97
+ reader = DOCXReader()
98
+ elif doc_type == DocumentType.TEXT or doc_type == DocumentType.MARKDOWN:
99
+ from docqwise.readers.text_reader import TextReader
100
+ reader = TextReader()
101
+ elif doc_type == DocumentType.IMAGE:
102
+ from docqwise.readers.image_reader import ImageReader
103
+ reader = ImageReader()
104
+ elif doc_type in (DocumentType.CSV, DocumentType.EXCEL):
105
+ from docqwise.readers.csv_reader import CSVReader
106
+ reader = CSVReader()
107
+ elif doc_type in (DocumentType.JSON, DocumentType.XML, DocumentType.YAML):
108
+ from docqwise.readers.json_reader import JSONReader
109
+ reader = JSONReader()
110
+
111
+ if reader:
112
+ self._readers[doc_type] = reader
113
+
114
+ return reader
115
+
116
+ def read(self, path: str, **kwargs) -> DocqwiseDocument:
117
+ """Read a document by auto-detecting its format."""
118
+ doc_type = self.detect_type(path)
119
+ reader = self._get_reader(doc_type)
120
+
121
+ if reader is None:
122
+ from docqwise.exceptions import UnsupportedFormatError
123
+ raise UnsupportedFormatError(
124
+ f"No reader available for {doc_type.value} format: {path}"
125
+ )
126
+
127
+ return reader.read(path, **kwargs)
@@ -0,0 +1,21 @@
1
+ """Base reader abstract class."""
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+ from docqwise.core.document import DocqwiseDocument
6
+
7
+
8
+ class BaseReader(ABC):
9
+ """Abstract base class for document readers. Implement to add new format support."""
10
+
11
+ @abstractmethod
12
+ def can_read(self, path: str) -> bool:
13
+ """Return True if this reader can handle the given path or format."""
14
+
15
+ @abstractmethod
16
+ def read(self, path: str, **kwargs) -> DocqwiseDocument:
17
+ """Read a document and return a DocqwiseDocument."""
18
+
19
+ @abstractmethod
20
+ def supported_formats(self) -> list[str]:
21
+ """Return list of supported file extensions (e.g. ['.pdf', '.PDF'])."""
@@ -0,0 +1,3 @@
1
+ """Stub reader — implementation coming in v0.1.0."""
2
+ from docqwise.readers.base import BaseReader
3
+ from docqwise.core.document import DocqwiseDocument
@@ -0,0 +1,3 @@
1
+ """Stub reader — implementation coming in v0.1.0."""
2
+ from docqwise.readers.base import BaseReader
3
+ from docqwise.core.document import DocqwiseDocument
@@ -0,0 +1,3 @@
1
+ """Stub reader — implementation coming in v0.1.0."""
2
+ from docqwise.readers.base import BaseReader
3
+ from docqwise.core.document import DocqwiseDocument
@@ -0,0 +1,3 @@
1
+ """Stub reader — implementation coming in v0.1.0."""
2
+ from docqwise.readers.base import BaseReader
3
+ from docqwise.core.document import DocqwiseDocument
@@ -0,0 +1,3 @@
1
+ """Stub reader — implementation coming in v0.1.0."""
2
+ from docqwise.readers.base import BaseReader
3
+ from docqwise.core.document import DocqwiseDocument
@@ -0,0 +1,3 @@
1
+ """Stub reader — implementation coming in v0.1.0."""
2
+ from docqwise.readers.base import BaseReader
3
+ from docqwise.core.document import DocqwiseDocument
@@ -0,0 +1,3 @@
1
+ """Stub reader — implementation coming in v0.1.0."""
2
+ from docqwise.readers.base import BaseReader
3
+ from docqwise.core.document import DocqwiseDocument
@@ -0,0 +1,3 @@
1
+ """Stub reader — implementation coming in v0.1.0."""
2
+ from docqwise.readers.base import BaseReader
3
+ from docqwise.core.document import DocqwiseDocument
@@ -0,0 +1,79 @@
1
+ """PDF reader using PyMuPDF."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import os
7
+
8
+ import fitz # PyMuPDF
9
+
10
+ from docqwise.core.document import (
11
+ DocqwiseDocument, DocumentMetadata, DocumentType,
12
+ PageContent, SourceType,
13
+ )
14
+ from docqwise.readers.base import BaseReader
15
+
16
+
17
+ class PDFReader(BaseReader):
18
+ """Read PDF documents using PyMuPDF (fitz)."""
19
+
20
+ def can_read(self, path: str) -> bool:
21
+ return path.lower().endswith((".pdf",))
22
+
23
+ def supported_formats(self) -> list[str]:
24
+ return [".pdf"]
25
+
26
+ def read(self, path: str, **kwargs) -> DocqwiseDocument:
27
+ """Read a PDF and return DocqwiseDocument with text and metadata."""
28
+ doc = fitz.open(path)
29
+ file_size = os.path.getsize(path)
30
+
31
+ with open(path, "rb") as f:
32
+ file_hash = hashlib.sha256(f.read()).hexdigest()[:16]
33
+
34
+ pages = []
35
+ full_text_parts = []
36
+ total_words = 0
37
+ has_text = False
38
+
39
+ for page_num in range(len(doc)):
40
+ page = doc[page_num]
41
+ text = page.get_text("text")
42
+ if text.strip():
43
+ has_text = True
44
+
45
+ words = text.split()
46
+ total_words += len(words)
47
+ full_text_parts.append(text)
48
+
49
+ pages.append(PageContent(
50
+ page_num=page_num + 1,
51
+ text=text,
52
+ width=page.rect.width,
53
+ height=page.rect.height,
54
+ is_scanned=not bool(text.strip()),
55
+ ))
56
+
57
+ metadata = DocumentMetadata(
58
+ title=doc.metadata.get("title", ""),
59
+ author=doc.metadata.get("author", ""),
60
+ page_count=len(doc),
61
+ word_count=total_words,
62
+ file_size_bytes=file_size,
63
+ file_hash=file_hash,
64
+ is_scanned=not has_text,
65
+ has_selectable_text=has_text,
66
+ )
67
+
68
+ doc_id = DocqwiseDocument.generate_id(path, file_hash)
69
+ doc.close()
70
+
71
+ return DocqwiseDocument(
72
+ doc_id=doc_id,
73
+ source_path=path,
74
+ source_type=SourceType.FILE,
75
+ doc_type=DocumentType.PDF,
76
+ metadata=metadata,
77
+ text="\n".join(full_text_parts),
78
+ pages=pages,
79
+ )
@@ -0,0 +1,3 @@
1
+ """Stub reader — implementation coming in v0.1.0."""
2
+ from docqwise.readers.base import BaseReader
3
+ from docqwise.core.document import DocqwiseDocument
@@ -0,0 +1,3 @@
1
+ """Stub reader — implementation coming in v0.1.0."""
2
+ from docqwise.readers.base import BaseReader
3
+ from docqwise.core.document import DocqwiseDocument
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes