Technical Briefing: chandra OCR Repository

The Problem

This repository provides an OCR model specialized for complex documents—tables, forms, handwriting, and multilingual layouts. However, as a consultant would assess, the codebase shows several production readiness concerns that affect reliability, reproducibility, and maintainability despite the model's technical capabilities.

What This Does

chandra is an OCR pipeline that converts images and PDFs into structured HTML/Markdown/JSON while preserving layout information. The codebase contains 51 files across 19 Python modules, with two primary inference modes: local (HuggingFace) and remote (vLLM server). Entry points include chandra/scripts/cli.py:183 (main), chandra/scripts/app.py:53 (ocr_layout), and chandra/scripts/app.py:41 (embed_images_in_markdown). The model graph shows chandra/model/__init__ as a central hub with 4 importers and 5 imports (instability 0.56), while chandra/model/schema.py defines the data contracts (GenerationResult, BatchInputItem, BatchOutputItem) consumed by 7 other files. Two inference implementations exist: chandra/model/hf.py for HuggingFace and chandra/model/vllm.py for vLLM server integration.

How It Is Wired

Execution begins at main in chandra/scripts/cli.py:183, which reaches 26 functions and orchestrates the pipeline: file discovery (get_supported_files), output saving (save_merged_output), model loading, and inference. The call graph contains 64 resolved edges between internal functions. Key traversed paths include main -> save_merged_output (filesystem via directory creation) and ocr_layout -> generate -> generate_hf (model inference). The most connected functions are generate (called from 5 places), InferenceManager (4 places), and BatchInputItem (4 places). Five functions perform model inference, four read/write files, one runs external commands, and one performs cryptographic operations (_hash_html in chandra/output.py). The dependency graph shows no circular dependencies across 19 analyzed Python modules, but chandra/scripts/app.py exhibits deep nesting (max indentation depth 6) and duplicated 6-line blocks shared with chandra/scripts/screenshot_app.py.

How To Use It

Setup: Install via pip: pip install chandra-ocr. For vLLM mode: chandra_vllm. For HuggingFace: pip install chandra-ocr[hf] then chandra input.pdf ./output --method hf. For the Streamlit app: pip install chandra-ocr[app] then chandra_app.

Configuration: No environment variables are documented in the repository structure. The pyproject.toml declares 11 dependencies (pillow, openai, pypdfium2 among them), with current versions 1-2 major releases behind the minimum declared versions. No lockfile is present (uv.lock exists but is not enforced in pyproject.toml), meaning installs are non-reproducible across environments.

Running it: The CLI entry point is chandra from the installed package. Example: chandra input.pdf ./output. The README provides additional modes: chandra_vllm for vLLM server, chandra_app for Streamlit interface.

Real-World Use

In a document processing pipeline, chandra could convert scanned forms or tables into structured data for downstream systems. A typical workflow would invoke the CLI on PDF invoices, outputting JSON with layout-preserved text extraction. The model's multilingual support (90+ languages per README) and form/checkbox reconstruction make it suitable for archival digitization or invoice processing systems. However, the absence of a lockfile and the broad exception handling in chandra/output.py introduce reproducibility and error-propagation risks in production deployment.

Code Health & Issues

Seven measured findings from static analysis (0 high, 7 medium, 0 low):

  • [MEDIUM/resilience] Broad exception handling in chandra/output.py: Bare except swallows errors indiscriminately. Fix: catch specific exceptions; re-raise or log the rest.
  • [MEDIUM/resource_safety] File opened without context manager x2: open(...) not wrapped in with in chandra/input.py and chandra/scripts/app.py. Handle may leak on error. Fix: use with open(...) as f: for deterministic close.
  • [MEDIUM/clarity] Duplicated code blocks: 1 repeated 6-line block across chandra/scripts/app.py and chandra/scripts/screenshot_app.py. Fix: extract shared helpers; DRY the repeated logic.
  • [MEDIUM/cognitive_load] Deep nesting x3: Max indentation depth 6 in chandra/scripts/app.py, chandra/scripts/cli.py, chandra/scripts/screenshot_app.py. Control flow is hard to follow. Fix: flatten with early returns/guard clauses; extract inner blocks.
  • [MEDIUM/resilience] (additional): Broad exception patterns likely present elsewhere given the 7-medium-finding count.

Code health audit (5 findings: 0 critical, 1 high, 3 medium, 1 low):

  • [HIGH] Pin third-party GitHub Actions to commit SHA: astral-sh/setup-uv@v7 in .github/workflows. A tag can move, so the action running with your token and secrets is whatever its owner last pushed. Fix: replace @vN with 40-character commit SHA; let Dependabot bump SHAs.
  • [MEDIUM] Declare least-privilege permissions for GITHUB_TOKEN: 2 workflows declare no permissions, 2 reference secrets. Fix: add permissions: contents: read at top of workflow and widen per job only where needed.
  • [MEDIUM] Enable Dependabot or Renovate: 1 manifest, no update bot configured. Fix: commit .github/dependabot.yml covering repo ecosystems plus github-actions.
  • [MEDIUM] Gate pull requests on dependency vulnerability scan: no dependency scan in CI. Fix: add dependency-review-action on pull_request, or osv-scanner on push and schedule.
  • [LOW] Set timeout-minutes on workflow jobs: 2 workflows declare no job timeout. Fix: add timeout-minutes with realistic bound to each job.

Additionally: dependencies declared without a lockfile (pyproject.toml) causes non-reproducible builds. Three declared dependencies are behind current published major versions (pillow >=10.2.0 vs 12.3.0; openai >=2.2.0 vs 3.1.0; pypdfium2 >=4.30.0 vs 5.13.0).

The Bottom Line

chandra delivers capable OCR for complex documents—tables, forms, handwriting, multilingual layouts—with two inference pathways and a clear data-model layer. The technical foundation is solid, but production use requires addressing the lockfile gap, pinning GitHub Action SHAs, and fixing the exception handling and context-manager issues identified. Teams needing reliable, reproducible document extraction should pin dependencies, add a lockfile, and resolve the measured resilience findings before integrating into critical pipelines.