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: Bareexceptswallows errors indiscriminately. Fix: catch specific exceptions; re-raise or log the rest. - [MEDIUM/resource_safety] File opened without context manager x2:
open(...)not wrapped inwithinchandra/input.pyandchandra/scripts/app.py. Handle may leak on error. Fix: usewith open(...) as f:for deterministic close. - [MEDIUM/clarity] Duplicated code blocks: 1 repeated 6-line block across
chandra/scripts/app.pyandchandra/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@v7in.github/workflows. A tag can move, so the action running with your token and secrets is whatever its owner last pushed. Fix: replace@vNwith 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: readat 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.ymlcovering 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-minuteswith 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.