This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
RADIS (Radiology Report Archive and Discovery System) is a Django-based web application for managing, retrieving, and analyzing radiology reports in hospital environments. It features hybrid search (full-text + semantic), LLM integration for AI-powered analysis, and a subscription system for report notifications.
Status: Early development (v0.0.0) - research purposes only, not a certified medical device License: AGPL 3.0 or later
All commands use the cli.py wrapper (via Typer). Use uv run cli <command> from project root.
# Development setup
uv sync # Install dependencies
cp ./example.env ./.env # Create environment file
uv run cli compose-up -- --watch # Start dev server with hot reload
uv run cli compose-down # Stop containers
# Code quality
uv run cli lint # Run linting (ruff + djlint)
uv run cli format-code # Format code with ruff
# Testing
uv run cli test # Run all tests
uv run cli test -- --cov # Run with coverage
uv run cli test -- -k test_name # Run specific test by name
uv run cli test -- -x # Stop on first failure
uv run pytest radis/reports/tests/ # Run tests in specific directory
# Utilities
uv run cli shell # Django shell
uv run cli generate-example-reports --count 10 # Generate test data with LLM
uv run cli db-backup # Backup database- Backend: Python 3.12+, Django 5.1+, PostgreSQL 17
- Search: pg_vector (semantic), pg_search (full-text), hybrid ranking
- Async: Daphne (ASGI), Django Channels, Procrastinate (task queue)
- Frontend: Django templates, Cotton components, HTMX, Alpine.js, Bootstrap 5
- LLM: OpenAI-compatible API or local Llama.cpp server
- API: Django REST Framework with async support (ADRF)
- radis.core/: Core functionality, UI layouts, abstract base classes. Models:
AnalysisJob,AnalysisTask(abstract bases for job/task pattern). - radis.reports/: Report management and REST API. Models:
Report(main entity with patient info, study metadata, body text),Language,Modality,Metadata. - radis.search/: Full-text and semantic search interface. Contains
SearchView,SearchForm,QueryParserfor complex query syntax, and provider registry. - radis.pgsearch/: PostgreSQL search implementation. Implements search provider interface with hybrid ranking (full-text + vector).
- radis.subscriptions/: Notification system for new reports matching criteria. Background tasks check new reports against user subscriptions.
- radis.collections/: Report bookmarking and organization into custom collections.
- radis.notes/: User annotations on reports for adding context.
- radis.chats/: Chat functionality for interacting with reports using LLM.
- radis.extractions/: Data extraction from reports using LLM. Models:
ExtractionJob,ExtractionTask.
Shared utilities come from adit-radis-shared package (accounts, token auth, common utilities).
Analysis operations follow a Job -> Task pattern (similar to ADIT):
- An AnalysisJob contains multiple AnalysisTasks
- Status flow:
UNVERIFIED->PREPARING->PENDING->IN_PROGRESS->SUCCESS/WARNING/FAILURE - Jobs automatically update state based on task completion
- Email notifications sent on job completion
- Background workers (Procrastinate) process tasks from
defaultandllmqueues
- Provider system: Plugin-based architecture (currently PostgreSQL, extensible for Vespa/ElasticSearch)
- QueryParser: Parses complex queries with operators, field filters, and boolean logic
- Hybrid search: Combines full-text search with semantic vector similarity
- Ranking: Results ranked by relevance score combining both search methods
- web: Django dev server with Daphne (port 8000)
- default_worker: General background task processor (Procrastinate queue:
default) - llm_worker: LLM-specific task processor (Procrastinate queue:
llm) - postgres: PostgreSQL 17 with pg_vector and pg_search extensions (port 5432)
- No profile: Uses external LLM via API (configure
OPENAI_API_BASE_URL) - cpu: Local LLM on CPU using Llama.cpp
- gpu: Local LLM with CUDA acceleration using Llama.cpp
Key variables in .env (see example.env):
ENVIRONMENT:developmentorproductionDJANGO_SECRET_KEY: Cryptographic signing keyPOSTGRES_PASSWORD: Database passwordDJANGO_ALLOWED_HOSTS: Comma-separated allowed hostsOPENAI_API_KEY: API key for OpenAI-compatible LLM serviceOPENAI_API_BASE_URL: Base URL for LLM API (for local or alternative providers)LLM_MODEL_NAME: Model to use for LLM operationsSITE_NAME,SITE_DOMAIN: Site framework settingsADMIN_USERNAME,ADMIN_EMAIL,ADMIN_PASSWORD: Initial superuser
- Style Guide: Google Python Style Guide
- Line Length: 100 characters (Ruff), 120 for templates (djlint)
- Type Checking: pyright in basic mode (migrations excluded)
- Linting: Ruff with E, F, I, DJ rules
- adit-radis-shared: Shared infrastructure (accounts, token auth, CLI commands, UI components)
- radis-client/: Official Python client library for API access (included in repo)
- pgvector: PostgreSQL extension for vector similarity search
- procrastinate: PostgreSQL-backed async task queue
- channels/daphne: WebSocket support for real-time features
- openai: Client for OpenAI-compatible LLM APIs
- Framework: pytest with pytest-django, pytest-playwright, pytest-asyncio
- Acceptance tests: Marked with
@pytest.mark.acceptance, require dev containers - Test locations:
radis/*/tests/directories within each app - Factories: factory-boy with Faker for test data generation
- Timeout: 60 seconds per test
Using radis-client for programmatic access:
from radis_client import RadisClient
# Initialize client
client = RadisClient(server_url="https://radis.example.com", auth_token="your-token")
# Create a new report
report = client.create_report({
"document_id": "DOC-12345",
"patient_id": "PAT-001",
"patient_birth_date": "1980-01-15",
"patient_sex": "M",
"study_datetime": "2024-03-15T10:30:00",
"study_description": "CT Thorax",
"body": "Findings: No acute abnormality...",
"groups": ["radiology"]
})
# Retrieve a report
report = client.retrieve_report("DOC-12345", full=True)
# Update a report (with upsert)
client.update_report("DOC-12345", {"body": "Updated findings..."}, upsert=True)import requests
response = requests.get(
"https://radis.example.com/api/reports/",
headers={"Authorization": "Token your-token"},
params={"search": "pneumonia CT thorax", "limit": 50}
)
reports = response.json()- Check PostgreSQL extensions are installed:
pg_vector,pg_search - Verify report has
bodytext indexed - Check search provider is configured in settings
- Review QueryParser syntax for complex queries
- Verify
OPENAI_API_KEYandOPENAI_API_BASE_URLare set - Check llm_worker is running:
docker compose logs llm_worker - For local LLM, ensure llama.cpp service is healthy
- Review model compatibility (see KNOWLEDGE.md for recommendations)
- Check worker logs:
docker compose logs default_worker - Verify Procrastinate is running:
docker compose ps - Check PostgreSQL connection
- Ensure task is in correct queue (
defaultvsllm)
- Validate document_id is unique
- Check required fields: document_id, patient_id, body
- Verify group exists and user has access
- Check date formats match ISO 8601
- Verify email settings in environment variables
- Check subscription criteria matches new reports
- Review subscription task logs
- Ensure background worker is processing subscription queue