Extracting usable text from images and scanned PDFs is a common but fiddly task: OCR models return raw text, and getting it into a shareable format (Markdown, Word, HTML) usually means manual cleanup. This app wraps DeepSeek-OCR with a web UI and format conversion so a non-technical user can upload a document and download a clean, structured result.
What This Does
A React frontend (frontend/src/App.jsx) talks to a FastAPI backend (backend/main.py) that runs DeepSeek-OCR. It supports single-image OCR with four modes (plain, describe, find, freeform) and multi-page PDF processing with progress tracking. Results export to Markdown, HTML, DOCX, or JSON via backend/format_converter.py.
The whole thing deploys with docker compose up --build. The backend image is based on nvcr.io/nvidia/pytorch:25.09-py3 and downloads the model (~5–10GB) on first run.
How It Is Wired
Execution starts in frontend/src/main.jsx, which mounts App.jsx. App is the clear hub: it has 5 outgoing imports and 0 incoming, giving it instability 0.83 — every feature routes through it. The five components (ImageUpload, PDFProcessor, ModeSelector, ResultPanel, AdvancedSettings) are all leaves (Ca 1, Ce 0): nothing imports them except App, so they are cheap to replace but App itself is the single point of change.
On the backend, backend/main.py is the API entry point. It handles uploads, calls the OCR model, and delegates file handling to pdf_utils.py (PDF extraction) and format_converter.py (export formats). The import graph shows 14 internal modules, 6 edges, and no cycles — the structure is simple and acyclic. App.jsx is the file with the widest blast radius because all UI state and API calls pass through it.
How To Use It
git clone https://github.com/moses-y/deepseek_ocr_app
cd deepseek_ocr_app
cp .env.example .env
docker compose up --build
Then open http://localhost:3000 (frontend) and http://localhost:8000/docs (API docs). The .env file configures ports and upload limits. The first run downloads the model, which takes time.
Real-World Use
A document digitization workflow: a user uploads a scanned 50-page PDF in PDFProcessor, the backend runs OCR page-by-page with progress tracking, and the result converts to .docx for editing in Word. The same pipeline handles academic papers, preserving LaTeX formulas and table structure.
Code Health & Issues
Static analysis found 6 issues (2 high, 4 medium). High: deep nesting in backend/format_converter.py, backend/main.py, and frontend/src/App.jsx (max indentation depth 8) — control flow is hard to follow; fix with early returns and extraction. Medium: backend/pdf_utils.py opens files without a context manager (handle leak risk), and backend/main.py uses broad exception handling that swallows errors.
The broader audit flags: no test suite across 17 source files, no CI configuration, no lockfile beside frontend/package.json (non-reproducible builds), a wildcard CORS origin paired with credentials in backend/main.py, an unpinned base image, and no non-root USER in the backend Dockerfile. No committed secrets were detected.
The Bottom Line
A working, demo-grade OCR app with a clean client-server split and genuinely useful PDF/format-conversion features. It is not production-ready: no tests, no CI, and a wildcard CORS config that browsers will reject with credentials anyway. Fine for a self-hosted tool or a starting point; add tests and a lockfile before putting it behind real traffic.
What the analyser found
Deployment readiness
4/7
✓Container image
✗CI pipeline
✗Lockfile committed
✗Test suite
✓README
✓License
✓No committed secrets
Composition
41 files
JSX7
JavaScript4
JSON4
Python3
Markdown2
CSS2
ReactExpressTailwindDocker
Module dependencies
The 10 most depended-upon modules of 14, from static import analysis. Red outline marks a module in an import cycle.
Ranked by severity × confidence × production reach. Reach is the honest discriminator across a collection that is mostly other people's code: the same finding matters more in something that ships.
high5
medium3
low1
Add a test suite; this repository has nonehigh17 occurrences
17 source files, no test files
Any change ships with no signal that existing behaviour still holds, so a regression reaches production undetected.
Fix: Add one test per public entry point, then a CI step that runs them.
Commit a lockfile beside the manifesthigh
frontend/package.json
manifest with no lockfile in scope
An unlocked range means the artifact you tested and the artifact you ship can contain different transitive code, so a malicious patch release reaches production with no diff.
Fix: Run the package manager once and commit the generated lockfile.
Add a workflow that builds and tests this repositoryhigh
17 source files, no CI configuration
Every change merges with nobody having run the build once, which across this estate is 499 repositories.
Fix: Add a workflow running the project build and test command on push and pull_request.
Add a build gate for the deployable artifacts herehigh3 occurrences
backend/Dockerfile
deployment artifacts with no workflow
Something in the repo is meant to reach a server and no automated step has ever validated it, which is the state of 125 repositories here.
Fix: Add a workflow that at least builds the image and validates the manifests.
Replace the wildcard CORS origin with an explicit allow listhigh
backend/main.py
allow_origins=["*"] together with credentials enabled
Any page on the internet can call the API with the browser's cookies attached, so a logged-in visitor to an unrelated site performs authenticated requests without knowing, and the wildcard combined with credentials is the exact configuration browsers refuse for that reason.
Fix: List the origins the API actually serves, and never pair a wildcard with credentials.
Enable Dependabot or Renovatemedium
2 manifest(s), no update bot configured
Without a bot a published advisory sits unpatched until someone audits by hand, which across 1,322 repositories means never.
Fix: Commit .github/dependabot.yml covering the repo ecosystems plus github-actions.
Pin the container base image by digestmedium
backend/Dockerfile
nvcr.io/nvidia/pytorch:25.09-py3
An untagged or mutable base means today's build and last month's contain different libc and a different CVE set, with no record of which shipped.
Fix: Use image:tag@sha256:<digest> and enable Dependabot's docker ecosystem.
Add a non-root USER to the imagemedium
backend/Dockerfile
CMD or ENTRYPOINT with no USER directive
A process running as root in the container is root against every mounted volume, and it turns any container escape or writable-mount mistake from a contained problem into a host one.
Fix: Create an unprivileged user, chown what it needs, and end the Dockerfile with USER.
Add the repository convention files this project lackslow3 occurrences
missing .editorconfig, .gitattributes, a formatter config
Without them one contributor's editor writes tabs into a Python file, a shell script commits with CRLF and fails in the container, and a notebook diff is unreviewable.
Fix: Add .editorconfig, .gitattributes with text=auto eol=lf, and a formatter config.
Checked deterministically against the repository tree and a bounded set of its files: committed credentials, unpinned actions and base images, missing lockfiles and update bots, workflows that discard failures, published advisories against the declared dependencies, runtime configuration, licensing and notebook reproducibility. No language model is involved in this section.