Spaces:
Build error
title: Lumea Unified Medical Platform
emoji: π
colorFrom: purple
colorTo: blue
sdk: docker
app_port: 7860
pinned: false
license: mit
short_description: A full-stack health companion platform
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
Lumea Health Platform
A full-stack health companion platform for preventive health management. Upload medical reports, extract health metrics via OCR, track trends, and receive AI-powered health recommendations.
β οΈ Disclaimer: This platform is a support tool for personal health tracking. It does not provide medical advice, diagnosis, or treatment. Always consult a licensed healthcare professional for medical decisions.
Table of Contents
- Overview
- Tech Stack
- Architecture
- Features
- Medicines: Find Cheap Alternatives
- Getting Started
- Environment Variables
- Database
- API Reference
- WebSocket Events
- Testing
- Deployment
- Contributing
- License
Overview
Lumea is a unified medical companion platform that enables users to:
- Upload medical reports (PDF, images) and automatically extract health metrics via OCR
- Track health profiles with comprehensive intake forms (conditions, medications, allergies, family history)
- Monitor health trends with a computed Health Index and interactive charts
- Receive AI recommendations based on extracted lab values and health patterns
- Compare reports over time with AI-powered summaries and trend analysis
- Chat with an AI assistant grounded in your personal health data
Tech Stack
| Layer | Technology |
|---|---|
| Frontend | React 18, TypeScript, Vite, Framer Motion, Recharts, React Router, i18next |
| Backend | FastAPI (Python 3.10+), SQLAlchemy 2.0, Pydantic |
| Database | PostgreSQL (Neon or local), Alembic migrations |
| OCR/Extraction | PaddleOCR, pdfplumber, PyMuPDF |
| AI/LLM | Grok API (xAI), Ollama (MedGemma), Gemini fallback, ChromaDB (RAG) |
| Realtime | WebSocket (FastAPI native) |
| Auth | JWT (python-jose), bcrypt |
Architecture
flowchart TB
subgraph Client["Frontend (React)"]
UI[React UI]
WS[WebSocket Client]
end
subgraph API["Backend (FastAPI)"]
Auth[Auth Routes]
Dashboard[Dashboard Routes]
Reports[Reports Routes]
Profile[Profile Routes]
Recommendations[Recommendations Routes]
AISummary[AI Summary Routes]
Assistant[Assistant Routes]
WSServer[WebSocket Server]
end
subgraph Services["Backend Services"]
OCR[PDF/OCR Extractor]
Classifier[Document Classifier]
MetricExtractor[Metric Extractor]
MetricsService[Metrics Service]
RecommendationEngine[Recommendation Engine]
AISummaryService[AI Summary Service]
RAG[RAG Service]
LLM[LLM Service]
end
subgraph External["External Services"]
GrokAPI[Grok API]
Ollama[Ollama / MedGemma]
Gemini[Gemini API]
end
subgraph Storage["Data Layer"]
DB[(PostgreSQL / Neon)]
ChromaDB[(ChromaDB Vector Store)]
FileStorage[File Storage]
end
UI --> Auth
UI --> Dashboard
UI --> Reports
UI --> Profile
UI --> Recommendations
UI --> AISummary
UI --> Assistant
WS <--> WSServer
Reports --> OCR
OCR --> Classifier
Classifier --> MetricExtractor
MetricExtractor --> MetricsService
MetricsService --> RecommendationEngine
AISummary --> AISummaryService
AISummaryService --> GrokAPI
Assistant --> RAG
RAG --> ChromaDB
RAG --> LLM
LLM --> Ollama
LLM --> Gemini
Auth --> DB
Dashboard --> DB
Reports --> DB
Reports --> FileStorage
Profile --> DB
Recommendations --> DB
AISummary --> DB
Data Flow: Report Upload to Health Index
sequenceDiagram
participant User
participant Frontend
participant Backend
participant OCR
participant Classifier
participant Extractor
participant DB
participant WebSocket
User->>Frontend: Upload PDF/Image
Frontend->>Backend: POST /api/reports/upload
Backend->>DB: Create report (status: uploaded)
Backend-->>Frontend: {id, status: uploaded}
Backend->>OCR: Extract text (background)
OCR-->>Backend: Raw text
Backend->>Classifier: Classify document
Classifier-->>Backend: Category + doc_type
Backend->>Extractor: Extract metrics
Extractor-->>Backend: Observations[]
Backend->>DB: Save observations + update report
Backend->>DB: Recompute health index
Backend->>WebSocket: emit report_parsed
WebSocket-->>Frontend: report_parsed event
Frontend->>Frontend: Refresh UI
Features
Document Upload & OCR
- Supported formats: PDF, PNG, JPG, JPEG, TIFF
- Max file size: 50MB
- Automatic text extraction (text-first, OCR fallback)
- Document classification: Lab, Dental, MRI, X-ray, Prescription, Sleep
Health Profile & Reminders
- Multi-step wizard with 6 steps (basics, measurements, conditions, medications, lifestyle, etc.)
- Tracks conditions, symptoms, medications, supplements, allergies
- Family medical history and genetic test results
- Once completed, users are never re-asked β profile status persists in DB
- "Profile Complete β " indicator with quick-edit access via Settings page
- Real-time SMS reminders via Twilio (or mock mode for testing)
- Background scheduler processes due reminders every 60 seconds
- Default reminders auto-generated: medication, appointment, checkup, hydration
Health Index & Trends
- Computed health index (0-100) based on lab values
- Factor contributions breakdown (glucose, lipids, vitamins, etc.)
- Time-series trends (1D, 1W, 1M views)
- Abnormal value flagging with reference ranges
AI Recommendations
- Rule-based engine analyzing lab values vs reference ranges
- Severity levels: INFO, WARNING, URGENT
- Categories: lifestyle, screening, follow-up, urgent
- Evidence-based with citations
AI Report Summary
- Single report AI summary with key findings
- Multi-report comparison (2-6 reports, same type)
- Highlights: positive, needs attention, next steps
- Cached results with hash-based invalidation
Health Assistant
- RAG-powered chat grounded in user's health data
- Citations from reports and observations
- WebSocket streaming for real-time responses
Medicines: Find Cheap Alternatives
Lumea includes a comprehensive medicine management system that helps users find affordable generic alternatives to prescribed medicines and locate nearby pharmacies, including government-sponsored Jan Aushadhi Kendras offering subsidized medications.
β οΈ Medical Disclaimer: This feature is a support tool for informational purposes only. It does NOT provide medical advice. Always consult your doctor or pharmacist before switching medicines or starting new treatments.
Overview
The Medicines feature enables users to:
- Search for medicines by brand name or free-text queries
- Upload prescriptions and automatically extract medicines via OCR/AI
- Find affordable alternatives with ranked matching by clinical equivalence
- Compare prices including Jan Aushadhi (government-fixed price) options
- Locate nearby pharmacies including generic and Jan Aushadhi pharmacies
- Track medications with personal notes and dosage schedules
User Flow
Input Medicine Information
- User enters brand name (e.g., "Aspirin 500mg") or uploads prescription image
- System extracts text via OCR or Grok AI parsing
Normalize Medicine Data
MedicineNormalizerservice extracts: salt, strength, form (tablet/capsule/liquid), release type (immediate/sustained)- Queries
GenericCatalogtable to validate and standardize extraction
Find Substitutes
SubstituteFinderqueries database for alternatives with 4-tier ranked matching:- Tier 1 (1.0 score): Exact match on salt + strength + form + release type
- Tier 2 (0.8 score): Same salt + strength + form
- Tier 3 (0.6 score): Same salt + strength
- Tier 4 (0.4 score): Same salt only
- Results sorted by price (Jan Aushadhi first for lowest cost)
Locate Pharmacies (Optional)
- User enters location (latitude/longitude) and search radius
PharmacyLocatorqueries Google Places API for pharmacies- Results include Jan Aushadhi Kendras (government pharmacies) and private pharmacies
- Results cached for 1 hour to reduce API calls
Save & Track
- User can save medicines to personal list with notes
- Each save creates
UserSavedMedicineentry for quick reference
Architecture Diagram
flowchart TB
subgraph Frontend["Frontend (React)"]
MedicinesPage["Medicines.tsx Page"]
SubstitutePanel["Substitute Finder Panel"]
PharmacyPanel["Pharmacy Locator Panel"]
end
subgraph API["Backend API (FastAPI)"]
NormalizeRoute["POST /normalize<br/>POST /normalize/batch"]
SubstituteRoute["POST /substitutes<br/>POST /substitutes/from-text"]
PharmacyRoute["GET /pharmacies/nearby<br/>GET /pharmacies/{id}<br/>POST /pharmacies/{id}/click"]
SavedRoute["POST /saved<br/>GET /saved<br/>DELETE /saved/{id}"]
end
subgraph Services["Backend Services"]
MedicineNormalizer["MedicineNormalizer<br/>- normalize()<br/>- normalize_batch()"]
SubstituteFinder["SubstituteFinder<br/>- find_substitutes()<br/>- save_medicine()"]
PharmacyLocator["PharmacyLocator<br/>- search_nearby()<br/>- get_place_details()<br/>- 1hr cache"]
GrokService["GrokMedicineService<br/>- get_alternatives_for_text()"]
end
subgraph External["External Services"]
GooglePlaces["Google Places API<br/>(Pharmacy Search)"]
GrokAPI["Grok xAI API<br/>(Medicine Parsing)"]
end
subgraph Storage["Data Layer"]
DB[(PostgreSQL)]
GenericCatalog["GenericCatalog Table<br/>(600K+ medicines)"]
UserSavedMedicine["UserSavedMedicine Table"]
SubstituteQuery["SubstituteQuery Table<br/>(Analytics)"]
PharmacyClick["PharmacyClick Table<br/>(Analytics)"]
end
MedicinesPage --> SubstitutePanel
MedicinesPage --> PharmacyPanel
SubstitutePanel --> SubstituteRoute
PharmacyPanel --> PharmacyRoute
PharmacyPanel --> SavedRoute
SubstituteRoute --> GrokService
SubstituteRoute --> MedicineNormalizer
SubstituteRoute --> SubstituteFinder
PharmacyRoute --> PharmacyLocator
MedicineNormalizer --> DB
MedicineNormalizer --> GenericCatalog
SubstituteFinder --> DB
SubstituteFinder --> GenericCatalog
SubstituteFinder --> SubstituteQuery
PharmacyLocator --> GooglePlaces
PharmacyLocator --> PharmacyClick
GrokService --> GrokAPI
GrokAPI -->|Parsed results| SubstituteFinder
API Endpoints
| Method | Endpoint | Description | Auth | Key Parameters |
|---|---|---|---|---|
| POST | /api/medicines/normalize |
Normalize single medicine text | Yes | text: str β Returns NormalizedMedicine |
| POST | /api/medicines/normalize/batch |
Normalize multiple medicines (prescription lines) | Yes | texts: List[str] β Returns List[NormalizedMedicine] |
| POST | /api/medicines/substitutes |
Find substitutes from structured data | Yes | salt, strength, form, release_type β Returns ranked substitutes |
| POST | /api/medicines/substitutes/from-text |
Find substitutes from free-form text (AI-powered) | Yes | text: str β Grok AI parsing β Returns alternatives with prices |
| GET | /api/medicines/pharmacies/nearby |
Search nearby pharmacies by location | Yes | lat, lng, radius_m=1000, type=all, page_token β Returns paginated pharmacies |
| GET | /api/medicines/pharmacies/{place_id} |
Get pharmacy details | Yes | place_id: str β Returns address, phone, hours, rating |
| POST | /api/medicines/pharmacies/{place_id}/click |
Log pharmacy interaction (analytics) | Yes | action: directions|call|website |
| POST | /api/medicines/saved |
Save medicine to user list | Yes | brand_name, salt, strength, form, notes |
| GET | /api/medicines/saved |
Get user's saved medicines | Yes | Returns list of UserSavedMedicine |
| DELETE | /api/medicines/saved/{medicine_id} |
Delete saved medicine | Yes | medicine_id: UUID |
Request/Response Examples
POST /api/medicines/substitutes/from-text
Request:
{
"text": "Aspirin 500mg tablets"
}
Response:
{
"original_text": "Aspirin 500mg tablets",
"normalized": {
"brand_name": "Aspirin",
"salt": "Acetylsalicylic Acid",
"strength": "500",
"form": "tablet",
"release_type": "immediate",
"confidence": 0.95
},
"substitutes": [
{
"rank": 1,
"product_name": "Jan Aushadhi Aspirin 500mg",
"salt": "Acetylsalicylic Acid",
"strength": "500",
"form": "tablet",
"mrp": "5.00",
"is_jan_aushadhi": true,
"match_score": 1.0,
"match_reason": "Exact match: Same salt, strength, form, and type"
},
{
"rank": 2,
"product_name": "Ecosprin 500mg",
"salt": "Acetylsalicylic Acid",
"strength": "500",
"form": "tablet",
"mrp": "8.50",
"is_jan_aushadhi": false,
"match_score": 1.0,
"match_reason": "Exact match: Same salt, strength, form, and type"
}
],
"disclaimer": "Always confirm with your doctor or pharmacist before switching medicines"
}
GET /api/medicines/pharmacies/nearby
Request:
GET /api/medicines/pharmacies/nearby?lat=40.7128&lng=-74.0060&radius_m=1000&type=all
Response:
{
"pharmacies": [
{
"place_id": "ChIJN1blbgBQwokRzKgy6E_B_1Q",
"name": "Jan Aushadhi Kendra - Downtown",
"address": "123 Main St, New York, NY 10001",
"latitude": 40.7128,
"longitude": -74.0060,
"rating": 4.7,
"is_open": true,
"is_jan_aushadhi": true,
"phone": "+1-212-555-0123"
},
{
"place_id": "ChIJrc_p_1BQwokRzKgy6E_B_2Q",
"name": "Metro Pharmacy",
"address": "456 Broadway, New York, NY 10002",
"latitude": 40.7150,
"longitude": -74.0030,
"rating": 4.2,
"is_open": true,
"is_jan_aushadhi": false,
"phone": "+1-212-555-0456"
}
],
"next_page_token": null,
"total_results": 2
}
Getting Started
Prerequisites
- Node.js 18+ and npm/yarn
- Python 3.10+
- PostgreSQL 14+ (or Neon cloud database)
- (Optional) Ollama for local LLM
1. Clone the Repository
git clone https://github.com/darved2305/Co-Code-2.0-ggw.git
cd Co-Code-2.0-ggw
2. Backend Setup
cd backend
# Create virtual environment
python -m venv .venv
# Activate (Windows)
.venv\Scripts\activate
# Activate (macOS/Linux)
source .venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Copy and configure environment
cp .env.example .env
# Edit .env with your DATABASE_URL, JWT_SECRET, GROK_API_KEY
# Run database migrations
alembic upgrade head
# Start the server
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
3. Frontend Setup
cd frontend
# Install dependencies
npm install
# Start development server
npm run dev
4. Access the Application
- Frontend: http://localhost:5173
- Backend API: http://localhost:8000
- API Docs: http://localhost:8000/docs
Docker Setup (Alternative)
# From project root
docker-compose up --build
Services:
- Frontend: http://localhost:5173
- Backend: http://localhost:8000
- PostgreSQL: localhost:5432
Environment Variables
Create a .env file in the backend/ directory:
| Variable | Description | Example | Required |
|---|---|---|---|
DATABASE_URL |
PostgreSQL connection string (asyncpg) | postgresql+asyncpg://user:pass@host:5432/db |
Yes |
JWT_SECRET |
Secret key for JWT signing | your-secret-key-min-32-chars |
Yes |
JWT_ALGORITHM |
JWT algorithm | HS256 |
No (default: HS256) |
ACCESS_TOKEN_EXPIRE_MINUTES |
Token expiration | 10080 (7 days) |
No (default: 10080) |
FRONTEND_ORIGIN |
CORS allowed origin | http://localhost:5173 |
Yes |
GROK_API_KEY |
xAI Grok API key (for AI summaries & medicine parsing) | gsk_... |
Yes |
XAI_API_BASE |
xAI API endpoint base URL | https://api.x.ai/v1 |
No (default provided) |
GROK_MODEL |
Grok model identifier | grok-beta |
No (default: grok-beta) |
GOOGLE_PLACES_API_KEY |
Google Maps API key (for pharmacy search) | AIzaSyD... |
No (fallback to mock data) |
GEMINI_API_KEY |
Google Gemini API key (optional fallback for summaries) | AIza... |
No |
OLLAMA_BASE_URL |
Ollama server URL (optional) | http://localhost:11434 |
No |
OLLAMA_MODEL |
Ollama model name | medgemma:4b |
No |
USE_GEMINI_FALLBACK |
Enable Gemini fallback when Grok unavailable | true |
No (default: false) |
SMS_MODE |
SMS sending mode: twilio (real) or mock (test/log only) |
mock |
No (default: mock) |
SMS_TEST_TO_NUMBER |
Default phone for test SMS (mock mode) | +15551234567 |
No |
TWILIO_ACCOUNT_SID |
Twilio Account SID (required for twilio mode) |
ACxxxxxxxx |
No* |
TWILIO_AUTH_TOKEN |
Twilio Auth Token (required for twilio mode) |
xxxxxxxxx |
No* |
TWILIO_FROM_NUMBER |
Twilio phone number (required for twilio mode) |
+15559876543 |
No* |
REMINDER_SCHEDULER_ENABLED |
Enable/disable background reminder scheduler | true |
No (default: true) |
REMINDER_CHECK_INTERVAL_SECONDS |
How often scheduler checks for due reminders | 60 |
No (default: 60) |
Medicines Feature Notes:
- GROK_API_KEY: Required for free-text medicine parsing (e.g., "Aspirin 500mg tablets" β structured data)
- GOOGLE_PLACES_API_KEY: Optional for pharmacy search. If not set, returns mock pharmacy data for testing
- XAI_API_BASE & GROK_MODEL: Typically use defaults; modify only for different xAI endpoints or model versions
SMS Reminders Feature Notes:
- SMS_MODE: Set to
mockfor development/testing (logs to console),twiliofor production - TWILIO_*: Required only when
SMS_MODE=twilio. Get credentials from Twilio Console - REMINDER_SCHEDULER_ENABLED: Scheduler runs in-process with APScheduler; disable during tests if needed
See backend/.env.example for a complete template.
Database
Migrations
Migrations are managed with Alembic:
cd backend
# Apply all migrations
alembic upgrade head
# Create a new migration
alembic revision --autogenerate -m "description"
# Check current revision
alembic current
Core Tables
| Table | Description |
|---|---|
users |
User accounts (email, password_hash, onboarding status) |
login_events |
Login audit trail |
reports |
Uploaded documents (file path, OCR text, classification) |
observations |
Extracted health metrics (value, unit, reference range, flag) |
health_metrics |
Computed scores (health_index, contributions) |
user_profiles |
Health profile data (basics, measurements, lifestyle, is_completed, phone_number) |
profile_conditions |
User medical conditions |
profile_medications |
Current medications |
profile_allergies |
Allergies and reactions |
profile_family_history |
Family medical history |
profile_recommendations |
Generated recommendations |
reminders |
User health reminders (medication, appointment, checkup, hydration) |
reminder_events |
SMS send audit log (status, error messages, timestamps) |
chat_sessions |
Assistant chat sessions |
chat_messages |
Chat message history |
report_ai_summaries |
Cached AI report summaries |
report_ai_comparisons |
Cached AI report comparisons |
generic_catalog |
Medicine database (600K+ products, indexed by salt/product_name) |
user_saved_medicines |
User's saved medicines (brand name, dosage, notes) |
substitute_queries |
Analytics: medicine searches and results |
pharmacy_clicks |
Analytics: pharmacy interaction tracking (directions, calls, website) |
user_location_consent |
Location permissions for pharmacy search |
Entity Relationships
erDiagram
users ||--o{ reports : uploads
users ||--o{ observations : has
users ||--o{ health_metrics : has
users ||--o| user_profiles : has
users ||--o{ profile_conditions : has
users ||--o{ profile_medications : takes
users ||--o{ profile_allergies : has
users ||--o{ chat_sessions : owns
users ||--o{ report_ai_summaries : generates
users ||--o{ report_ai_comparisons : generates
users ||--o{ user_saved_medicines : saves
users ||--o{ substitute_queries : searches
users ||--o{ pharmacy_clicks : clicks
users ||--o| user_location_consent : grants
user_profiles ||--o{ reminders : has
reminders ||--o{ reminder_events : logs
reports ||--o{ observations : extracts
reports ||--o{ report_ai_summaries : summarized_by
chat_sessions ||--o{ chat_messages : contains
user_saved_medicines ||--o{ generic_catalog : references
substitute_queries ||--o{ generic_catalog : queries
Medicines Database Schema
generic_catalog - Seeded from PMBI (Pharmaceutical Market Bureau of India) data
| Column | Type | Notes |
|---|---|---|
id |
UUID | Primary key |
product_name |
VARCHAR | Brand/product name (indexed) |
salt |
VARCHAR | Active pharmaceutical ingredient (indexed) |
strength |
VARCHAR | e.g., "500mg", "100mcg" |
form |
VARCHAR | e.g., "tablet", "capsule", "liquid", "injection" |
release_type |
VARCHAR | e.g., "immediate", "sustained" |
mrp |
DECIMAL | Maximum retail price |
manufacturer |
VARCHAR | Manufacturing company |
source |
VARCHAR | Data source (e.g., "jan_aushadhi", "pmbi") |
is_jan_aushadhi |
BOOLEAN | Government-fixed price indicator |
created_at |
TIMESTAMP | Record creation time |
user_saved_medicines - User's tracked medicines
| Column | Type | Notes |
|---|---|---|
id |
UUID | Primary key |
user_id |
UUID | Foreign key β users |
original_name |
VARCHAR | User-entered medicine name |
salt |
VARCHAR | Normalized salt |
strength |
VARCHAR | Normalized strength |
form |
VARCHAR | Normalized form |
release_type |
VARCHAR | Normalized release type |
schedule_json |
JSONB | Optional: dosage schedule (morning, noon, evening) |
notes |
TEXT | User's personal notes |
created_at |
TIMESTAMP | When medicine was saved |
substitute_queries - Analytics table
| Column | Type | Notes |
|---|---|---|
id |
UUID | Primary key |
user_id |
UUID | Foreign key β users |
query_raw |
VARCHAR | Original user input |
normalized_json |
JSONB | Extracted fields (salt, strength, form, release_type) |
results_json |
JSONB | Array of matching substitutes |
results_count |
INTEGER | Number of alternatives found |
created_at |
TIMESTAMP | Query timestamp |
pharmacy_clicks - Analytics table
| Column | Type | Notes |
|---|---|---|
id |
UUID | Primary key |
user_id |
UUID | Foreign key β users |
place_id |
VARCHAR | Google Places API place ID |
place_name |
VARCHAR | Pharmacy name |
mode |
VARCHAR | Action: "directions", "call", "website" |
created_at |
TIMESTAMP | Click timestamp |
user_location_consent - Privacy tracking
| Column | Type | Notes |
|---|---|---|
id |
UUID | Primary key |
user_id |
UUID | Foreign key β users (unique) |
consent |
BOOLEAN | Location permission granted/revoked |
updated_at |
TIMESTAMP | Last update |
API Reference
Authentication
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /api/auth/register |
Create new account | No |
| POST | /api/auth/login |
Login, get JWT | No |
| POST | /api/auth/logout |
Logout, clear cookie | Yes |
Dashboard
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/me/bootstrap |
Get user state after login | Yes |
| GET | /api/dashboard/summary |
Health index with factor breakdown | Yes |
| GET | /api/dashboard/trends |
Time-series data for metrics | Yes |
Reports
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/reports |
List user's reports | Yes |
| POST | /api/reports/upload |
Upload new report | Yes |
| GET | /api/reports/{id} |
Get report details | Yes |
| POST | /api/reports/{id}/confirm |
Confirm extracted values | Yes |
| DELETE | /api/reports/{id} |
Delete report | Yes |
| GET | /api/reports/{id}/debug |
Debug extraction info | Yes |
Profile
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/profile |
Get full profile | Yes |
| PUT | /api/profile |
Update profile | Yes |
| POST | /api/profile/conditions |
Add conditions | Yes |
| POST | /api/profile/medications |
Add medications | Yes |
| POST | /api/profile/allergies |
Add allergies | Yes |
| POST | /api/profile/recompute |
Trigger recomputation | Yes |
Profile/Me (Simplified)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/profile/me |
Get current user's profile with completion status | Yes |
| PUT | /api/profile/me |
Create or update profile (upsert) | Yes |
| PATCH | /api/profile/me |
Partial profile update | Yes |
Reminders
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/reminders |
List all reminders for user | Yes |
| POST | /api/reminders |
Create new reminder | Yes |
| PATCH | /api/reminders/{id} |
Update reminder | Yes |
| DELETE | /api/reminders/{id} |
Delete reminder | Yes |
| POST | /api/reminders/generate-default |
Generate default reminders | Yes |
SMS (Testing)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /api/sms/test |
Send test SMS (mock or real based on SMS_MODE) | Yes |
Recommendations
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/recommendations |
Get recommendations | Yes |
| GET | /api/recommendations/summary |
Get summary counts | Yes |
| POST | /api/recommendations/regenerate |
Force regeneration | Yes |
AI Summary
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/ai/reports-for-summary |
List reports for selection | Yes |
| GET | /api/ai/reports/{id}/file |
Get report file | Yes |
| POST | /api/ai/report-summary |
Generate single report summary | Yes |
| POST | /api/ai/report-compare |
Compare multiple reports | Yes |
| POST | /api/ai/validate-comparison |
Validate selection | Yes |
| GET | /api/ai/categories |
Get distinct categories | Yes |
Assistant
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /api/assistant/chat |
Chat with AI assistant | Yes |
Example Request/Response
POST /api/auth/login
Request:
{
"email": "user@example.com",
"password": "securepassword"
}
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"full_name": "John Doe",
"created_at": "2026-01-15T10:30:00Z"
}
}
POST /api/ai/report-summary
Request:
{
"report_id": "550e8400-e29b-41d4-a716-446655440001",
"force_regenerate": false
}
Response:
{
"summary_json": {
"title": "Blood Panel Analysis - January 2026",
"highlights": {
"positive": ["Hemoglobin within normal range", "Glucose levels stable"],
"needs_attention": ["LDL cholesterol slightly elevated"],
"next_steps": ["Consider dietary changes", "Retest in 3 months"]
},
"plain_language_summary": "Your blood panel shows mostly healthy values...",
"key_findings": [
{"item": "LDL Cholesterol", "evidence": "142 mg/dL (ref: <100)"}
],
"confidence": 0.85
},
"cached": false,
"generated_at": "2026-02-02T10:30:00Z",
"model_name": "grok-beta"
}
WebSocket Events
Connect: ws://localhost:8000/ws?token=<jwt_token>
Events Sent to Client
| Event | Payload | Description |
|---|---|---|
connected |
{message, user_id} |
Connection established |
pong |
{timestamp} |
Response to ping |
report_processing_started |
{report_id, progress} |
OCR processing began |
report_parsed |
{report_id, extracted_metrics_count, status} |
OCR completed |
health_index_updated |
{score, breakdown, confidence, updated_at} |
Health index recalculated |
trends_updated |
{metrics: [...]} |
Trend data changed |
reports_list_updated |
{} |
Reports list changed |
recommendations_updated |
{count, urgent_count} |
Recommendations regenerated |
profile_updated |
{updated_at} |
Profile changed |
chat_token |
{token} |
Streaming chat token |
chat_complete |
{full_response, citations, session_id} |
Chat response complete |
Events Received from Client
| Event | Payload | Description |
|---|---|---|
ping |
{} |
Keepalive ping |
subscribe |
{topics: [...]} |
Subscribe to topics |
chat_request |
{message, session_id} |
Start streaming chat |
Testing
Run Tests
cd backend
# Run all tests
pytest
# Run with verbose output
pytest -v
# Run specific test file
pytest tests/test_routes_auth.py
Test Files
test_routes_auth.py- Authentication endpointstest_routes_dashboard.py- Dashboard endpointstest_routes_reports.py- Report upload/managementtest_routes_recommendations.py- Recommendation endpointstest_lab_parser.py- Lab value extractiontest_pdf_extractor.py- PDF/OCR extractiontest_metrics_service.py- Health index computationtest_websocket_manager.py- WebSocket connection management
Manual QA Checklist
- β Register a new account
- β Login and verify dashboard loads
- β Upload a PDF lab report
- β Verify OCR extraction completes (WebSocket notification)
- β Check extracted metrics appear in reports list
- β Verify health index updates on dashboard
- β Complete health profile wizard
- β Check recommendations generate
- β Open AI Summary page, select a report
- β Generate AI summary, verify it displays
- β Select 2 reports of same type, generate comparison
- β Try selecting mixed types, verify warning appears
- β Test Medicines feature: search for a medicine (e.g., "Aspirin 500mg")
- β Verify substitutes appear with prices and Jan Aushadhi options highlighted
- β Test pharmacy search by entering your location
- β Verify nearby pharmacies appear with ratings and distance
- β Try filtering pharmacies by type (all/jan_aushadhi/generic)
- β Save a medicine and verify it appears in saved list
Medicines Feature Testing
cd backend
# Test medicines normalization
pytest tests/test_medicines_normalizer.py
# Test substitute finder
pytest tests/test_medicines_substitutes.py
# Test pharmacy locator
pytest tests/test_medicines_pharmacy_locator.py
# Test medicines routes
pytest tests/test_routes_medicines.py
Medicines Manual Testing
Test Medicine Normalization
curl -X POST http://localhost:8000/api/medicines/normalize \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"text": "Aspirin 500mg tablet"}'Expected:
NormalizedMedicinewith extracted salt, strength, formTest Substitute Search (AI-Powered)
curl -X POST http://localhost:8000/api/medicines/substitutes/from-text \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"text": "Crocin 650mg for fever"}'Expected: Ranked list of alternatives with prices
Test Pharmacy Search
curl -X GET "http://localhost:8000/api/medicines/pharmacies/nearby?lat=40.7128&lng=-74.0060&radius_m=1000&type=all" \ -H "Authorization: Bearer <token>"Expected: List of nearby pharmacies with ratings
Test Save Medicine
curl -X POST http://localhost:8000/api/medicines/saved \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "brand_name": "Aspirin", "salt": "Acetylsalicylic Acid", "strength": "500", "form": "tablet", "release_type": "immediate", "notes": "Take with food" }'Expected:
UserSavedMedicineobject saved to database
Medicines Troubleshooting
Problem: "No alternatives found" when searching for a medicine
- Cause: Medicine not in
generic_catalogtable - Solution:
- Verify PMBI/Jan Aushadhi seed data was loaded during migration
- Check database:
SELECT COUNT(*) FROM generic_catalog;should show 600K+ records - Try searching by generic name instead of brand name (e.g., "Acetylsalicylic Acid" instead of "Aspirin")
- Check logs for Grok API errors if using free-text search
Problem: "Google Places API 401" or pharmacy search returns empty
- Cause:
GOOGLE_PLACES_API_KEYnot set or API quota exceeded - Solution:
- Verify
GOOGLE_PLACES_API_KEYis set in.env:echo $GOOGLE_PLACES_API_KEY - Check Google Cloud Console for API key validity and quota limits
- If quota exceeded, wait 24 hours or upgrade billing
- During development, feature falls back to mock pharmacy data when API key is missing
- Mock data is useful for frontend testing without API costs
- Verify
Problem: Slow medicine normalization or substitute search
- Cause: Missing database indexes on high-cardinality columns
- Solution:
- Verify indexes exist on
generic_catalog:SELECT indexname FROM pg_indexes WHERE tablename='generic_catalog'; - Should see indexes on:
product_name,salt,user_id - If missing, run:
alembic upgrade headto apply latest migrations - Check database query performance: Enable
EXPLAIN ANALYZEin PostgreSQL - Profile with:
SELECT pg_size_pretty(pg_total_relation_size('generic_catalog'));
- Verify indexes exist on
Problem: Grok API errors when parsing prescription images
- Cause: Invalid image format, API key expired, or API rate limit
- Solution:
- Verify image is clear and readable (JPG, PNG, TIFF)
- Test API key directly:
curl -H "Authorization: Bearer $GROK_API_KEY" https://api.x.ai/v1/models - Check Grok API status at https://status.x.ai
- Review backend logs:
docker logs <backend-container>ortail uvicorn.log - If rate-limited, implement request throttling in
medicine_normalizer.py
Problem: Pharmacy search cache not updating
- Cause: In-memory cache TTL (1 hour) not expired
- Solution:
- Manually clear cache by restarting backend service
- Cache is per-search key (lat/lng/radius): different locations create new cache entries
- Check cache size in logs: Search for "pharmacy_cache" debug messages
- To disable caching for testing, set
PharmacyLocator.CACHE_TTL_SECONDS = 0
Problem: User saved medicines not persisting
- Cause: Database migration not applied or user_id foreign key constraint
- Solution:
- Verify
user_saved_medicinestable exists:\dt user_saved_medicinesin psql - Run migrations:
cd backend && alembic upgrade head - Verify user exists and authenticated: Check JWT token contains valid
sub(user_id) - Check database logs for constraint errors:
docker logs <postgres-container>
- Verify
Deployment
Hugging Face Spaces (Recommended for Demo)
This app is optimized for Hugging Face Spaces with Docker SDK:
Create a new Space on huggingface.co/new-space
Select Docker SDK and set port to
7860Push this repository to the Space
Configure Secrets (Settings β Repository Secrets):
JWT_SECRET: Strong random secret for auth tokens (required for security)GROQ_API_KEY: For AI features (get free key at console.groq.com)GEMINI_API_KEY: Optional fallback for AI featuresGOOGLE_PLACES_API_KEY: Optional for pharmacy locator
β οΈ Do NOT set reserved variables like
SPACE_ID,SPACE_AUTHOR, etc. - HF sets these automatically!
The app automatically:
- Uses SQLite stored in
/data(persistent storage) - Serves frontend from same FastAPI server (port 7860)
- Configures HTTPS-compatible secure cookies
- Creates database tables on first startup
No PostgreSQL required - SQLite works out of the box!
Docker Compose (Local Development)
docker-compose up -d
Includes:
- PostgreSQL 16 (Alpine)
- FastAPI backend with hot reload
- Vite React frontend with hot reload
Production Considerations
- Database:
- HF Spaces: SQLite (automatic, stored in /data)
- Self-hosted: Use Neon, Supabase, or managed PostgreSQL
- Backend: Deploy to Railway, Render, or AWS ECS
- Frontend: Deploy to Vercel, Netlify, or Cloudflare Pages
- Secrets: Use environment variables, never commit
.env - HTTPS: Enable secure cookies in production (automatic on HF Spaces)
- CORS: Update
FRONTEND_ORIGINfor production domain
Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make changes and test
- Commit with clear messages:
git commit -m "Add: feature description" - Push and create a Pull Request
Code Style
- Backend: Follow PEP 8, use type hints
- Frontend: Follow ESLint/Prettier config
- Commits: Use conventional commit format
License
This project is licensed under the MIT License. See LICENSE for details.
Support
For issues or questions, open a GitHub issue or contact the maintainers.