01. Product & System Overview¶
What the product does¶
Universal OCR Service converts complex documents (PDFs and common image formats) into developer-friendly Markdown and HTML, with optional per-page block metadata. It is optimized for layout-heavy pages: tables, formulas, charts, and figures.
Confirmed product framing: README.md, package description in pyproject.toml (paddle-ocr-service).
Primary users and workflows¶
| User | Workflow |
|---|---|
| Dashboard operator | Upload file → pick provider mode → watch progress → preview / download |
| API consumer | POST /ocr/jobs → poll GET .../status → GET .../download/{md\|html\|zip} |
| Operator / developer | make start or docker compose -f docker-compose-full.yml up |
Primary request-to-result flow¶
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#1e293b",
"primaryTextColor": "#f8fafc",
"primaryBorderColor": "#38bdf8",
"lineColor": "#64748b",
"actorBackground": "#1e293b",
"actorBorder": "#38bdf8",
"actorTextColor": "#f8fafc",
"labelBoxBorderColor": "#38bdf8",
"labelBoxBkgColor": "#1e293b",
"labelTextColor": "#f8fafc",
"noteBorderColor": "#fbbf24",
"noteBkgColor": "#78350f",
"noteTextColor": "#fffbeb"
}
}}%%
sequenceDiagram
actor User
participant UI as React UI
participant API as FastAPI
participant R as Redis
participant C as Celery
participant P as Provider
User->>UI: Select file + vlm_mode
UI->>API: POST /ocr/jobs
API->>API: Save jobs/{id}/input.*
API->>R: HSET job:{id} status=pending
API->>C: run_ocr_job.delay(...)
API-->>UI: 202 {job_id}
loop Every 2s
UI->>API: GET /ocr/jobs/{id}/status
API->>R: HGETALL job:{id}
API-->>UI: progress fields
end
C->>R: status=processing
C->>P: Layout/VLM or third-party OCR
P-->>C: Text / artifacts
C->>C: Write result.md/html + metadata
C->>R: status=done
UI->>API: download md + html
API-->>UI: Preview tabs
Major subsystems¶
| Subsystem | Responsibility | Evidence |
|---|---|---|
| FastAPI app | Job CRUD, status, downloads, health | app/main.py, app/api/ocr.py |
| Celery worker | Run OCR pipeline, update Redis, write files | app/tasks/ocr_task.py |
| Redis | Broker (DB 1) + job hashes (DB 0) | app/tasks/celery_app.py, app/api/ocr.py |
| Pipeline core | Layout + parallel block VLM | app/services/pipeline.py, paddleocr_pipeline.py |
| Provider extractors | Whole-doc NVIDIA / Sarvam / raw VLM | app/services/{nvidia,sarvam,vlm}/ |
| Storage helpers | Job dirs, UUID validation, metadata JSON | app/services/storage.py |
| React dashboard | Upload, poll, history, dual preview | frontend/src/App.tsx |
High-level architecture¶
graph TD
classDef default fill:#1e293b,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef highlight fill:#065f46,stroke:#34d399,stroke-width:2px,color:#f0fdf4
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:2px,color:#fffbeb
Client["Browser / curl"]:::highlight
API["FastAPI<br/>uvicorn"]
Worker["Celery worker"]
Redis["Redis 7"]
Disk["Local jobs/ volume"]
Ext["LiteLLM / NIM / Sarvam / Paddle VL server"]:::warning
Client --> API
API --> Redis
API --> Disk
API --> Worker
Worker --> Redis
Worker --> Disk
Worker --> Ext
linkStyle default stroke:#64748b,stroke-width:2px
Provider modes at a glance¶
| Mode | Behavior | Needs |
|---|---|---|
layout |
PP-DocLayoutV3 → per-block VLM (Paddle or LiteLLM) | Layout model; Paddle server or VLM_MODEL |
vlm |
Whole pages to LiteLLM via Jinja prompt | VLM_MODEL + provider keys |
nvidia |
NeMo Retriever Parse per page | NVIDIA_OCR_API_KEY |
sarvam |
Sarvam async job → md/html/json/zip | SARVAM_API_KEY |
Per-job override: query param vlm_mode on POST /ocr/jobs (app/api/ocr.py).
Key technical bets and tradeoffs¶
| Bet | Tradeoff |
|---|---|
| Async Celery for all OCR | More moving parts than sync FastAPI; needed for long PDFs |
| Ephemeral Redis + disk | Simple ops; not a DMS; TTL vs disk can diverge |
| Pluggable providers behind one API | Mode-specific quality/latency; shared metadata is uneven |
| Polling UI | Simple and robust; not instant push |
| Paddle on amd64 Docker | Works on Apple Silicon via emulation; slower |
Documented in docs/decisions/architectural-decisions.md.
Interview soundbite — "What did you build?"¶
I built an asynchronous OCR microservice that turns PDFs and images into Markdown and HTML. FastAPI handles job submission and status; Celery workers run either a layout-aware Paddle + VLM pipeline or third-party OCR APIs (NVIDIA, Sarvam). Redis tracks progress; results are files on disk. A React dashboard lets you pick a provider, watch per-block progress, and preview dual HTML/Markdown output.