Skip to main content

Architecture overview

ContentsVision is a two-tier web application with an asynchronous AI processing pipeline. This page is the map: the stack, the repo layout, the runtime topology, and how requests and jobs flow. The following pages drill into each layer.

Tech stack​

LayerTechnology
FrontendNext.js 14 (App Router), React 18, TypeScript, Tailwind CSS, Framer Motion, TanStack Query and Virtual
BackendPython 3.11, FastAPI, SQLAlchemy 2 (async), Pydantic v2
DatabaseSQLite via aiosqlite (async)
MediaFFmpeg (via ffmpeg-python), Pillow, OpenCV (headless), PyMuPDF (reading photos out of PDFs)
AIOpenRouter (Claude vision and analysis), Deepgram Nova-2 (transcription)
AuthAdjustSquare SSO, JSON Web Tokens (python-jose, HS256)
AnalyticsPostHog (product analytics + session replay), optional, off by default
Exportspandas + openpyxl (Excel), hand-rolled CSV
DeploymentDocker Compose, nginx, Cloudflare Tunnel

Repository layout​

contentsvision/
├── backend/
│ ├── app/
│ │ ├── main.py # FastAPI app, router registration, CORS, /health
│ │ ├── api/routes/ # HTTP endpoints (auth, claims, rooms, upload, jobs, inventory, settings, activity, billing)
│ │ ├── core/ # config, database, security (JWT), auth_deps, actor, observability
│ │ ├── models/ # SQLAlchemy ORM models (claim, room, job, inventory, team, user, activity)
│ │ ├── schemas/ # Pydantic request/response shapes
│ │ └── services/ # pipeline, video_processor, audio_processor, claude_analyzer,
│ │ # numbering, auto_approve, prompt_settings, billing, activity, adjust_square
│ ├── tests/ # pytest suite (offline, in-memory DB)
│ └── requirements.txt
├── frontend/
│ └── src/
│ ├── app/ # Next.js App Router pages
│ ├── components/ # React components
│ ├── lib/ # api client, auth, settings contexts, helpers
│ └── types/ # TypeScript interfaces
├── docs/ # this handbook (Docusaurus)
├── nginx/nginx.conf
├── docker-compose.yml # local
└── docker-compose.prod.yml # production (nginx + Cloudflare Tunnel)

Runtime topology​

In production, four containers run behind a Cloudflare Tunnel. No ports are exposed to the public internet; all ingress arrives through the tunnel into nginx.

The same logical split exists in local development, just without nginx or the tunnel: the Next dev server proxies /api to the uvicorn backend. See local development.

Backend layering​

The backend follows a conventional layered shape. Requests flow down; the pipeline runs to the side, in the worker container in production.

LayerResponsibilityKey files
RoutesHTTP surface, validation, auth, response shapingapp/api/routes/*
AuthValidate JWT, resolve current user and team, build the Actorcore/auth_deps.py, core/actor.py, core/security.py
ServicesBusiness logic: the pipeline, billing, numbering, auto-approve, Adjust Square clientapp/services/*
ModelsSQLAlchemy ORM tablesapp/models/*
SchemasPydantic request/response contractsapp/schemas/*
CoreConfig, database engine, observabilitycore/config.py, core/database.py, core/observability.py

Two request paths: synchronous API vs background pipeline​

ContentsVision has two distinct execution paths, and understanding the split explains most of the design.

Synchronous API calls​

Standard REST. The route reads or writes the database and returns immediately. Most endpoints (claims, rooms, inventory edits, settings) work this way.

Background pipeline jobs​

Uploading media returns right away, but the heavy AI work never runs inside the request. Every pipeline start goes through app/core/queue.enqueue_job: in production (REDIS_URL set) the route pushes the job id onto a Redis queue and a separate worker container (app/worker.py, ARQ) runs the pipeline against its own database session; in local development (no REDIS_URL) the same call schedules a FastAPI background task in the web server instead. The frontend then watches progress over Server-Sent Events, which the backend serves from the job row plus the log lines the worker mirrors through Redis (see observability).

This is why a long video does not block the HTTP request, and why one user's processing never slows another user's pages: the web server only does front-desk work. The Adjust Square submission still hands its slow photo uploads to an in-process background task (network waiting, no CPU; see Adjust Square).

Where to go next​