--- 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](#overview) - [Tech Stack](#tech-stack) - [Architecture](#architecture) - [Features](#features) - [Medicines: Find Cheap Alternatives](#medicines-find-cheap-alternatives) - [Getting Started](#getting-started) - [Environment Variables](#environment-variables) - [Database](#database) - [API Reference](#api-reference) - [WebSocket Events](#websocket-events) - [Testing](#testing) - [Deployment](#deployment) - [Contributing](#contributing) - [License](#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 ```mermaid 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 ```mermaid 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 1. **Input Medicine Information** - User enters brand name (e.g., "Aspirin 500mg") or uploads prescription image - System extracts text via OCR or Grok AI parsing 2. **Normalize Medicine Data** - `MedicineNormalizer` service extracts: salt, strength, form (tablet/capsule/liquid), release type (immediate/sustained) - Queries `GenericCatalog` table to validate and standardize extraction 3. **Find Substitutes** - `SubstituteFinder` queries 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) 4. **Locate Pharmacies** (Optional) - User enters location (latitude/longitude) and search radius - `PharmacyLocator` queries Google Places API for pharmacies - Results include Jan Aushadhi Kendras (government pharmacies) and private pharmacies - Results cached for 1 hour to reduce API calls 5. **Save & Track** - User can save medicines to personal list with notes - Each save creates `UserSavedMedicine` entry for quick reference ### Architecture Diagram ```mermaid 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
POST /normalize/batch"] SubstituteRoute["POST /substitutes
POST /substitutes/from-text"] PharmacyRoute["GET /pharmacies/nearby
GET /pharmacies/{id}
POST /pharmacies/{id}/click"] SavedRoute["POST /saved
GET /saved
DELETE /saved/{id}"] end subgraph Services["Backend Services"] MedicineNormalizer["MedicineNormalizer
- normalize()
- normalize_batch()"] SubstituteFinder["SubstituteFinder
- find_substitutes()
- save_medicine()"] PharmacyLocator["PharmacyLocator
- search_nearby()
- get_place_details()
- 1hr cache"] GrokService["GrokMedicineService
- get_alternatives_for_text()"] end subgraph External["External Services"] GooglePlaces["Google Places API
(Pharmacy Search)"] GrokAPI["Grok xAI API
(Medicine Parsing)"] end subgraph Storage["Data Layer"] DB[(PostgreSQL)] GenericCatalog["GenericCatalog Table
(600K+ medicines)"] UserSavedMedicine["UserSavedMedicine Table"] SubstituteQuery["SubstituteQuery Table
(Analytics)"] PharmacyClick["PharmacyClick Table
(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: ```json { "text": "Aspirin 500mg tablets" } ``` Response: ```json { "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: ```json { "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 ```bash git clone https://github.com/darved2305/Co-Code-2.0-ggw.git cd Co-Code-2.0-ggw ``` ### 2. Backend Setup ```bash 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 ```bash 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) ```bash # 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 `mock` for development/testing (logs to console), `twilio` for production - **TWILIO_***: Required only when `SMS_MODE=twilio`. Get credentials from [Twilio Console](https://console.twilio.com/) - **REMINDER_SCHEDULER_ENABLED**: Scheduler runs in-process with APScheduler; disable during tests if needed See [backend/.env.example](backend/.env.example) for a complete template. --- ## Database ### Migrations Migrations are managed with Alembic: ```bash 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 ```mermaid 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: ```json { "email": "user@example.com", "password": "securepassword" } ``` Response: ```json { "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: ```json { "report_id": "550e8400-e29b-41d4-a716-446655440001", "force_regenerate": false } ``` Response: ```json { "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=` ### 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 ```bash 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 endpoints - `test_routes_dashboard.py` - Dashboard endpoints - `test_routes_reports.py` - Report upload/management - `test_routes_recommendations.py` - Recommendation endpoints - `test_lab_parser.py` - Lab value extraction - `test_pdf_extractor.py` - PDF/OCR extraction - `test_metrics_service.py` - Health index computation - `test_websocket_manager.py` - WebSocket connection management ### Manual QA Checklist 1. ☐ Register a new account 2. ☐ Login and verify dashboard loads 3. ☐ Upload a PDF lab report 4. ☐ Verify OCR extraction completes (WebSocket notification) 5. ☐ Check extracted metrics appear in reports list 6. ☐ Verify health index updates on dashboard 7. ☐ Complete health profile wizard 8. ☐ Check recommendations generate 9. ☐ Open AI Summary page, select a report 10. ☐ Generate AI summary, verify it displays 11. ☐ Select 2 reports of same type, generate comparison 12. ☐ Try selecting mixed types, verify warning appears 13. ☐ Test Medicines feature: search for a medicine (e.g., "Aspirin 500mg") 14. ☐ Verify substitutes appear with prices and Jan Aushadhi options highlighted 15. ☐ Test pharmacy search by entering your location 16. ☐ Verify nearby pharmacies appear with ratings and distance 17. ☐ Try filtering pharmacies by type (all/jan_aushadhi/generic) 18. ☐ Save a medicine and verify it appears in saved list ### Medicines Feature Testing ```bash 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 1. **Test Medicine Normalization** ```bash curl -X POST http://localhost:8000/api/medicines/normalize \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"text": "Aspirin 500mg tablet"}' ``` Expected: `NormalizedMedicine` with extracted salt, strength, form 2. **Test Substitute Search (AI-Powered)** ```bash curl -X POST http://localhost:8000/api/medicines/substitutes/from-text \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"text": "Crocin 650mg for fever"}' ``` Expected: Ranked list of alternatives with prices 3. **Test Pharmacy Search** ```bash curl -X GET "http://localhost:8000/api/medicines/pharmacies/nearby?lat=40.7128&lng=-74.0060&radius_m=1000&type=all" \ -H "Authorization: Bearer " ``` Expected: List of nearby pharmacies with ratings 4. **Test Save Medicine** ```bash curl -X POST http://localhost:8000/api/medicines/saved \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "brand_name": "Aspirin", "salt": "Acetylsalicylic Acid", "strength": "500", "form": "tablet", "release_type": "immediate", "notes": "Take with food" }' ``` Expected: `UserSavedMedicine` object saved to database ### Medicines Troubleshooting **Problem: "No alternatives found" when searching for a medicine** - **Cause**: Medicine not in `generic_catalog` table - **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_KEY` not set or API quota exceeded - **Solution**: - Verify `GOOGLE_PLACES_API_KEY` is 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 **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 head` to apply latest migrations - Check database query performance: Enable `EXPLAIN ANALYZE` in PostgreSQL - Profile with: `SELECT pg_size_pretty(pg_total_relation_size('generic_catalog'));` **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 ` or `tail 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_medicines` table exists: `\dt user_saved_medicines` in 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 ` --- ## Deployment ### Hugging Face Spaces (Recommended for Demo) This app is optimized for Hugging Face Spaces with Docker SDK: 1. **Create a new Space** on [huggingface.co/new-space](https://huggingface.co/new-space) 2. **Select Docker SDK** and set port to `7860` 3. **Push this repository** to the Space 4. **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](https://console.groq.com)) - `GEMINI_API_KEY`: Optional fallback for AI features - `GOOGLE_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) ```bash docker-compose up -d ``` Includes: - PostgreSQL 16 (Alpine) - FastAPI backend with hot reload - Vite React frontend with hot reload ### Production Considerations 1. **Database**: - **HF Spaces**: SQLite (automatic, stored in /data) - **Self-hosted**: Use Neon, Supabase, or managed PostgreSQL 2. **Backend**: Deploy to Railway, Render, or AWS ECS 3. **Frontend**: Deploy to Vercel, Netlify, or Cloudflare Pages 4. **Secrets**: Use environment variables, never commit `.env` 5. **HTTPS**: Enable secure cookies in production (automatic on HF Spaces) 6. **CORS**: Update `FRONTEND_ORIGIN` for production domain --- ## Contributing 1. Fork the repository 2. Create a feature branch: `git checkout -b feature/my-feature` 3. Make changes and test 4. Commit with clear messages: `git commit -m "Add: feature description"` 5. 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](LICENSE) for details. --- ## Support For issues or questions, open a GitHub issue or contact the maintainers.