- Docker and Docker Compose
- Python 3.12+ (3.12 or 3.13 recommended; 3.14 has limited ML library support)
uv(Python package manager)
# Start PostgreSQL, RabbitMQ, and the worker
docker compose up -d
# Verify services are healthy/running
docker compose psThis starts:
postgres: PostgreSQL with pgvector onlocalhost:5433rabbitmq: RabbitMQ onlocalhost:5673and management UI onlocalhost:15673worker: RabbitMQ consumer for asynchronous document processing
The worker mounts the local ./data/documents directory at
/app/data/documents, so PDFs uploaded by the local API are readable by the
worker container.
# Core dependencies
uv sync
# For local embeddings (requires Python 3.12 or 3.13):
uv sync --extra mluv run uvicorn src.main:app --reload --port 8000This is the recommended development flow: edit code locally, keep FastAPI on
--reload, and let Docker run PostgreSQL, RabbitMQ, and the worker.
Run migrations from your host when the schema changes:
uv run alembic upgrade headAlternatively, run the one-shot migration container:
docker compose --profile migrate up migrateIf you want to run both API and worker manually on your host, start only PostgreSQL and RabbitMQ:
docker compose up -d postgres rabbitmq
uv run alembic upgrade headThen run the worker manually in a separate terminal:
uv run python -m src.document_processing.infrastructure.worker_entrypointThe API container is available behind the api Compose profile:
docker compose --profile api up -d --buildThis starts the opositaria-api container on localhost:8000. For day-to-day
local development, prefer the local uvicorn --reload command above.
The migrations prepare the database schema, including pgvector, users, study documents, document processing jobs, semantic-search chunks, and study spaces.
curl -X POST http://localhost:8000/study-documents/upload \
-F "file=@/path/to/document.pdf"Returns 202 Accepted with document ID and PENDING_PROCESSING status on success.
Error responses:
| Status | Code | Description |
|---|---|---|
413 Payload Too Large |
file_too_large |
Upload exceeds the 50 MB size limit |
422 Unprocessable Content |
empty_upload |
Upload content is empty |
422 Unprocessable Content |
invalid_file_type |
File is not a PDF (wrong content type or extension) |
422 Unprocessable Content |
invalid_filename |
Filename contains invalid characters (blank, reserved, path separators, control characters, or exceeds 255 chars) |
Error payloads are structured JSON:
{
"detail": {
"code": "file_too_large",
"message": "Upload content exceeds the 50 MB size limit"
}
}curl http://localhost:8000/study-documents/{document_id}/statusReturns the current lifecycle status: PENDING_PROCESSING, PROCESSING, READY, or FAILED.
curl -X POST http://localhost:8000/semantic-search/search \
-H "Content-Type: application/json" \
-d '{"query": "derecho administrativo", "limit": 10}'Returns matched chunks ordered by cosine similarity with parent document metadata.
The vector extension is enabled via Alembic migration. The Docker image pgvector/pgvector:pg17 already includes the extension - it is a PostgreSQL 17 image with pgvector pre-installed.
Document lifecycle state and processing job state are persisted exclusively in PostgreSQL. Redis is intentionally out of scope for the MVP. If Redis is added later, it would only be for transient progress reporting, not for canonical state.
RabbitMQ is used for the MVP's asynchronous document processing. Kafka is intentionally out of scope - its offset management, consumer groups, and partitioning are unnecessary overhead for a single document-processing worker flow.
Only selectable-text PDFs are supported. OCR for scanned/image-only PDFs is out of scope. If a PDF has no extractable text, the document transitions to FAILED with a clear failure reason.
Local embeddings are generated using numpy (development) or sentence-transformers (production, requires uv sync --extra ml). The embedding dimension is 384 (all-MiniLM-L6-v2). The dimension is validated before persistence to prevent data corruption.
# Unit tests (fast, no external deps)
uv run pytest src/ -m unit -v
# Integration tests (require PostgreSQL)
uv run pytest src/ -m integration -v
# All tests
uv run pytest src/ -v