A production-ready web application for specialty pharmacies to automatically generate care plans using LLM technology.
Customer: A specialty pharmacy
Problem: Pharmacists spend 20-40 minutes per patient manually creating care plans. These are required for compliance and Medicare/pharma reimbursement. The pharmacy is short-staffed and backlogged.
Solution: A web application that allows medical assistants to input patient/order information and automatically generate care plans using LLM.
- ✅ Web form for patient/provider/order data entry
- ✅ Real-time input validation (NPI, MRN, ICD-10, etc.)
- ✅ Duplicate detection for patients and orders
- ✅ LLM-powered care plan generation
- ✅ Async processing with Celery
- ✅ Care plan download
- ✅ Export all data for pharma reporting
| Component | Technology |
|---|---|
| Backend | Django 5.0, Django REST Framework |
| Database | PostgreSQL |
| Task Queue | Celery + Redis |
| LLM | Anthropic Claude / OpenAI (configurable) |
| Frontend | React + Vite |
| Infrastructure | Docker, Terraform, AWS |
- Docker & Docker Compose
# 1. Clone repository
git clone <repo-url>
cd larmar-careplan
# 2. Copy environment file and configure API keys
cp backend/.env.example backend/.env
# Edit backend/.env with your ANTHROPIC_API_KEY or OPENAI_API_KEY
# 3. Start all services (db, redis, backend, worker, frontend)
docker-compose up -d
# 4. (Optional) Create admin user
docker-compose exec backend python manage.py createsuperuser
# 5. (Optional) Import mock data for testing
docker-compose exec backend python manage.py seed_dataThat's it! All services will be running via Docker.
| Service | URL |
|---|---|
| Frontend | http://localhost:3000 |
| API | http://localhost:8000/api/v1/ |
| Admin | http://localhost:8000/admin/ |
| Field | Type | Validation |
|---|---|---|
| patient_first_name | string | Required |
| patient_last_name | string | Required |
| patient_mrn | string | Exactly 6 digits |
| patient_date_of_birth | date | Valid date |
| patient_sex | string | Required |
| patient_weight | number | Required |
| patient_allergies | string | Required |
| Field | Type | Validation |
|---|---|---|
| provider_name | string | Required |
| provider_npi | string | Exactly 10 digits |
| Field | Type | Validation |
|---|---|---|
| medication_name | string | Required |
| primary_diagnosis_code | string | ICD-10 format (e.g., G70.00) |
| additional_diagnosis_codes | list | ICD-10 format |
| medication_history | list | List of strings |
| patient_records | string | Free text (clinical notes) |
| Scenario | Condition | Result |
|---|---|---|
| Exact match | MRN same + first_name + last_name + date_of_birth all same | ✅ Reuse existing patient |
| MRN conflict | MRN same + first_name/last_name/date_of_birth different | |
| Name/DOB conflict | first_name + last_name + date_of_birth same + MRN different | |
| No match | All different | ✅ Create new patient |
| Scenario | Condition | Result |
|---|---|---|
| Exact duplicate | Same patient + same medication + same day | ❌ ERROR (blocked, cannot proceed) |
| Possible duplicate | Same patient + same medication + different day |
| Scenario | Condition | Result |
|---|---|---|
| Exact match | NPI same + provider_name same | ✅ Reuse existing provider |
| NPI conflict | NPI same + provider_name different | ❌ ERROR (blocked, must correct name) |
| No match | NPI different | ✅ Create new provider |
Patient records text that may include:
- Patient demographics (Name, MRN, DOB, Sex, Weight, Allergies)
- Medication
- Primary/Secondary diagnoses
- Home meds
- Recent history
- Clinical notes (e.g., Baseline clinic note, Infusion visit note, Follow-up notes)
The generated care plan MUST include these sections:
- Problem list / Drug therapy problems (DTPs)
- Goals (SMART)
- Pharmacist interventions / plan
- Monitoring plan & lab schedule
# Create order (triggers care plan generation)
POST /api/v1/orders/
# List all orders
GET /api/v1/orders/
# Get order details
GET /api/v1/orders/{id}/
# Regenerate care plan
POST /api/v1/orders/{id}/regenerate/{
"patient_mrn": "123456",
"patient_first_name": "John",
"patient_last_name": "Doe",
"patient_date_of_birth": "1979-06-08",
"patient_sex": "Female",
"patient_weight": 72,
"patient_allergies": "None known",
"provider_npi": "1234567893",
"provider_name": "Dr. Smith",
"medication_name": "IVIG",
"primary_diagnosis_code": "G70.00",
"additional_diagnosis_codes": ["I10", "K21.0"],
"medication_history": ["Pyridostigmine 60mg", "Prednisone 10mg"],
"patient_records": "Name: A.B.\nMRN: 123456\nDOB: 1979-06-08..."
}# Get care plan content
GET /api/v1/care-plans/by-order/{order_id}/
# Get generation status
GET /api/v1/care-plans/status/{order_id}/
# Download care plan as file
GET /api/v1/care-plans/download/{order_id}/# Export all orders and care plans as CSV
GET /api/v1/export/# Run all tests
docker-compose exec backend pytest
# Run with coverage
docker-compose exec backend pytest --cov=apps --cov-report=html
# Run specific test file
docker-compose exec backend pytest tests/unit/test_validators.py
# Run only unit tests
docker-compose exec backend pytest tests/unit/
# Run only integration tests
docker-compose exec backend pytest tests/integration/# Stop all containers (keeps data)
docker-compose down
# Stop all containers and remove volumes (full cleanup, deletes database data)
docker-compose down -v# List running containers
docker ps
# List all containers (including stopped)
docker ps -a
# Force stop a specific container
docker stop <container_id>If you get a "port already in use" error:
# Check what's using port 5432 (PostgreSQL)
lsof -i :5432
# Check what's using port 6379 (Redis)
lsof -i :6379
# Check what's using port 8000 (Backend)
lsof -i :8000
# Kill the process using the port
kill -9 <pid>
# Quick fix: Kill all processes on port 5432
lsof -ti :5432 | xargs kill -9# Access PostgreSQL shell
docker-compose exec db psql -U careplan -d careplan
# View logs
docker-compose logs -f db # Database logs
docker-compose logs -f backend # Backend logs
docker-compose logs -f worker # Celery worker logs
# Restart a specific service
docker-compose restart backendSee .env.example for all available settings.
| Variable | Description |
|---|---|
| DATABASE_URL | PostgreSQL connection string |
| CELERY_BROKER_URL | Redis URL for Celery |
| ANTHROPIC_API_KEY | API key for Claude LLM |
| OPENAI_API_KEY | API key for OpenAI (alternative) |
| LLM_PROVIDER | claude or openai |
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Frontend │────▶│ Django │────▶│ PostgreSQL │
│ (React) │ │ REST API │ │ │
└─────────────┘ └──────┬──────┘ └──────▲──────┘
│ │
│ queue task │ read order /
▼ │ write care plan
┌─────────────┐ ┌──────┴──────┐ ┌─────────────┐
│ Redis │────▶│ Celery │◀───▶│ LLM │
│ (Queue) │ │ Worker │ │ (Claude/ │
└─────────────┘ └─────────────┘ │ OpenAI) │
└─────────────┘
Data Flow:
- Django API saves order to PostgreSQL, then queues task ID to Redis
- Celery Worker picks up task ID from Redis
- Worker reads order data from PostgreSQL
- Worker sends prompt to LLM, receives generated care plan
- Worker writes care plan back to PostgreSQL
- Medical assistant fills out web form with patient/order data
- Frontend validates input (MRN 6 digits, NPI 10 digits, etc.)
- API checks for duplicate patients and orders
- If warning → user can acknowledge and continue
- If error → user must fix the issue
- Order is saved to database
- Celery task is triggered asynchronously
- Worker fetches order from database
- Worker calls LLM to generate care plan
- Care plan is saved to database
- User can download care plan as text file
backend/
├── config/ # Django settings
│ ├── settings/
│ │ ├── base.py
│ │ ├── development.py
│ │ ├── production.py
│ │ └── test.py
│ ├── urls.py
│ ├── celery.py
│ └── wsgi.py
├── apps/
│ ├── core/ # Common utilities
│ │ ├── validators.py # NPI, MRN, ICD-10
│ │ └── exceptions.py # Custom exceptions
│ ├── providers/ # Provider management
│ ├── patients/ # Patient management
│ ├── orders/ # Order management
│ │ └── services.py # Duplicate detection
│ └── care_plans/ # Care plan generation
│ ├── tasks.py # Celery tasks
│ ├── llm_service.py # LLM integration
│ └── prompts.py # LLM prompts
├── tests/
│ ├── unit/
│ └── integration/
└── manage.py
frontend/
├── src/
│ ├── components/
│ ├── pages/
│ └── services/
└── package.json
terraform/
└── ... (AWS infrastructure)
The application includes a full observability stack for monitoring, logging, and alerting.
| Service | URL | Purpose |
|---|---|---|
| Grafana | http://localhost:3001 | Dashboards & visualization (admin/admin) |
| Prometheus | http://localhost:9090 | Metrics storage & queries |
| Loki | http://localhost:3100 | Log aggregation |
A pre-configured dashboard "Care Plan Generator" is available with panels for:
- Order creation metrics (total, errors, duplicates)
- Care plan generation metrics
- LLM token usage
- Request latency (p50, p95)
- Application logs
# Total orders created by status
order_created_total
# Orders created per minute
rate(order_created_total[1m])
# Order creation p95 latency
histogram_quantile(0.95, rate(order_create_duration_seconds_bucket[5m]))
# Care plan generation success rate
sum(care_plan_generation_total{status="success"}) / sum(care_plan_generation_total)
# Total LLM tokens used
sum(llm_tokens_used_total) by (type)
# LLM tokens used per minute
rate(llm_tokens_used_total[5m])
# Duplicate detection breakdown
duplicate_detection_total
# Care plan generation duration p95
histogram_quantile(0.95, rate(care_plan_generation_duration_seconds_bucket[5m]))
# HTTP request rate by status category
sum(rate(django_http_requests_total_by_method_total[5m])) by (method)
# All logs from backend and worker
{container=~".*backend.*|.*worker.*"}
# Filter by log level - errors only
{container=~".*backend.*"} |~ "error|ERROR"
# Order-related logs
{container=~".*backend.*"} |~ "order"
# Care plan generation logs
{container=~".*worker.*"} |~ "care_plan"
# Search for specific order ID
{container=~".*backend.*|.*worker.*"} |~ "order_id=abc123"
# HTTP requests with 4xx/5xx status
{container=~".*backend.*"} | json | status_code >= 400
# Slow requests (> 1 second)
{container=~".*backend.*"} | json | duration_ms > 1000
# Failed care plan generations
{container=~".*worker.*"} |~ "care_plan_generation_failed"
# LLM token usage logs
{container=~".*worker.*"} |~ "llm_generation_completed"
| Metric | Type | Description |
|---|---|---|
order_created_total |
Counter | Orders created (labels: status) |
order_create_duration_seconds |
Histogram | Order creation latency |
duplicate_detection_total |
Counter | Duplicate detection results (labels: type, result) |
care_plan_queued_total |
Counter | Care plans queued for generation |
care_plan_generation_total |
Counter | Care plan generation attempts (labels: status) |
care_plan_generation_duration_seconds |
Histogram | Care plan generation time |
llm_tokens_used_total |
Counter | LLM tokens consumed (labels: type) |
care_plan_retry_total |
Counter | Care plan generation retries |
| Event | Level | Description |
|---|---|---|
http_request |
INFO/WARN/ERROR | HTTP request completed |
order_create_started |
INFO | Order creation initiated |
order_created_success |
INFO | Order created successfully |
order_blocked_duplicate |
WARNING | Order blocked due to duplicate |
order_requires_confirmation |
INFO | Order needs duplicate confirmation |
care_plan_generation_started |
INFO | Care plan generation started |
care_plan_generation_success |
INFO | Care plan generated successfully |
care_plan_generation_failed |
ERROR | Care plan generation failed |
llm_generation_completed |
INFO | LLM response received |
The following features were intentionally excluded from the MVP to focus on the core value proposition (LLM-powered care plan generation). These are planned for Phase 2:
| Feature | What's Missing | Why Deferred |
|---|---|---|
| Authentication | No user login, no role-based access control (RBAC) | MVP focuses on core functionality; auth can be quickly added via Django auth or Auth0 |
| HIPAA Compliance | No PHI encryption at rest, no access audit trails, no BAA with cloud providers | Requires significant infrastructure investment; would use AWS HIPAA-eligible services in production |
| Care Plan Editing | Users cannot edit generated care plans, only download or regenerate | Need to first validate LLM output quality before building editing UI |
| Version History | Regenerating overwrites the previous care plan | Current design stores only the latest version; versioning adds complexity |
| Real-time Updates | Frontend polls for status instead of WebSocket push | Polling is sufficient for MVP; WebSocket is a Phase 2 optimization |
| Audit Logging | No comprehensive audit trail (who did what, when) | Required for healthcare compliance but deferred for MVP |
| Rate Limiting | No API throttling or rate limits | Needed for production but acceptable for internal tool MVP |
| Multi-tenancy | Single-tenant design | Would need redesign if serving multiple pharmacy organizations |
For production deployment with real PHI (Protected Health Information):
- Encryption - Enable encryption at rest (AWS RDS, S3) and in transit (TLS)
- Access Controls - Implement RBAC with minimum necessary access
- Audit Trails - Log all PHI access with immutable audit logs
- BAA - Sign Business Associate Agreements with AWS, Anthropic/OpenAI
- Data Retention - Implement compliant data retention and deletion policies
- Incident Response - Establish breach notification procedures