Varun2007's picture
initial clean deployment commit with compilers
8c3e275
Raw
History Blame Contribute Delete
6.12 kB
from __future__ import annotations
import hashlib
from pathlib import Path
import typer
from pageparse.config import settings
from pageparse.extract import Extractor
from pageparse.ingest import IMAGE_EXTENSIONS, load_image
from pageparse.ocr.handwriting import HandwritingOCR
from pageparse.ocr.printed import PrintedOCR
from pageparse.preprocess import preprocess
from pageparse.store import Store
app = typer.Typer()
store = Store()
@app.command()
def process(
path: str = typer.Argument(..., help="Path to image or directory"),
batch: bool = typer.Option(False, "--batch", help="Process all images in directory"),
airgap: bool = typer.Option(False, "--airgap", help="Fail if any network call attempted"),
parallel: bool = typer.Option(False, "--parallel", help="Process files in parallel"),
schema: str = typer.Option("auto", "--schema", help="Schema type (auto, todo, meeting, recipe)"),
diff_sync: bool = typer.Option(True, "--diff-sync/--no-diff-sync", help="Skip duplicate files"),
) -> None:
if airgap:
settings.airgap = True
settings.auto_schema = schema == "auto"
settings.diff_sync_enabled = diff_sync
paths = [Path(path)]
if batch and Path(path).is_dir():
paths = []
for ext in IMAGE_EXTENSIONS:
paths.extend(Path(path).glob(f"*{ext}"))
paths.extend(Path(path).glob(f"*{ext.upper()}"))
store.init_db()
if parallel and len(paths) > 1:
from pageparse import pipelines
typer.echo(f"Processing {len(paths)} files in parallel ({settings.max_workers} workers)...")
results = pipelines.process_batch(paths)
for r in results:
if "error" in r:
typer.echo(f" FAILED: {Path(r['path']).name}{r['error']}")
else:
typer.echo(f" Processed: {Path(r['path']).name}{len(r.get('raw_text', ''))} chars")
return
ocr = HandwritingOCR()
printed_ocr = PrintedOCR()
extractor = Extractor()
store.init_db()
for p in paths:
typer.echo(f"Processing: {p.name}")
content_bytes = p.read_bytes()
content_hash = hashlib.sha256(content_bytes).hexdigest()
if diff_sync:
existing = store.get_source_by_hash(content_hash)
if existing:
typer.echo(f" Skipped (duplicate): {p.name}")
continue
img = load_image(p)
cleaned = preprocess(img)
raw_text = ocr.recognize(cleaned)
if not raw_text.strip():
raw_text = printed_ocr.recognize(cleaned)
if schema == "auto":
schema = extractor.detect_schema_type(raw_text)
result = extractor.extract(raw_text, p.name, schema)
source_id = store.save(result, raw_text, content_hash=content_hash)
typer.echo(f" Saved source #{source_id} with {len(result.records)} records")
@app.command()
def list(
priority: str | None = typer.Option(None, "--priority", help="Filter by priority"),
type: str | None = typer.Option(None, "--type", help="Filter by record type"),
) -> None:
records = store.get_records(priority=priority, type=type)
for r in records:
prio = r["priority"] or "None"
typer.echo(f" [{r['type']}] [{prio}] {r['content']}{r['filename']}")
@app.command()
def search(
query: str = typer.Argument(..., help="Search query"),
) -> None:
from pageparse.search import SemanticSearch
searcher = SemanticSearch()
results = searcher.search(query)
for r in results:
typer.echo(f" [{r['type']}] {r['content']} (confidence: {r.get('confidence', 'N/A')})")
@app.command()
def export(
format: str = typer.Option("json", "--format", help="Export format (json or csv)"),
) -> None:
import csv
import io
import json
sources = store.list_sources()
for s in sources:
s["records"] = store.get_records(source_id=s["id"])
if format == "json":
typer.echo(json.dumps(sources, indent=2))
elif format == "csv":
records = store.get_records()
if not records:
typer.echo("No records to export.")
return
output = io.StringIO()
writer = csv.DictWriter(output, fieldnames=records[0].keys())
writer.writeheader()
writer.writerows(records)
typer.echo(output.getvalue().rstrip())
@app.command()
def serve(
host: str = "127.0.0.1",
port: int = 8000,
) -> None:
import os
import uvicorn
env_port = os.environ.get("PORT")
if env_port:
try:
port = int(env_port)
except ValueError:
pass
uvicorn.run("pageparse.web:app", host=host, port=port, reload=False)
@app.command()
def benchmark(
path: str = typer.Argument(..., help="Path to image or directory"),
iterations: int = typer.Option(3, "--iterations", "-n", help="Number of iterations"),
) -> None:
import time
from pageparse.ocr.handwriting import HandwritingOCR
from pageparse.ocr.printed import PrintedOCR
from pageparse.preprocess import preprocess
typer.echo(f"Benchmarking: {path} ({iterations} iterations)")
img = load_image(path)
ocr = HandwritingOCR()
printed_ocr = PrintedOCR()
times = {"preprocess": [], "handwriting_ocr": [], "printed_ocr": []}
for i in range(iterations):
t0 = time.time()
cleaned = preprocess(img)
times["preprocess"].append(time.time() - t0)
t0 = time.time()
ocr.recognize(cleaned)
times["handwriting_ocr"].append(time.time() - t0)
t0 = time.time()
printed_ocr.recognize(cleaned)
times["printed_ocr"].append(time.time() - t0)
for stage, durations in times.items():
avg = sum(durations) / len(durations)
typer.echo(f" {stage}: avg={avg:.3f}s, min={min(durations):.3f}s, max={max(durations):.3f}s")
@app.command()
def stats() -> None:
store = Store()
stats = store.get_stats()
typer.echo(f"Sources: {stats['sources']}")
typer.echo(f"Records: {stats['records']}")
if __name__ == "__main__":
app()