Spaces:
Sleeping
Sleeping
| # MediShield AI Document Classification β Implementation Plan | |
| ## Workflow Diagram | |
| ``` | |
| βββββββββββββββββββββββββββββββββββ | |
| β Uploaded Image β | |
| ββββββββββββββββ¬βββββββββββββββββββ | |
| β | |
| ββββββββββββββββΌβββββββββββββββββββ | |
| β Stage 1: Rules Engine β | |
| β regex: ^bill_ β | |
| ββββββββββββββββ¬βββββββββββββββββββ | |
| β | |
| βββββββββββββββββββ΄βββββββββββββββββββ | |
| bill_ match? no match | |
| β β | |
| ββββββββββββΌβββββββββββ ββββββββββββββββΌβββββββββββββββ | |
| β doc_type = "bill" β β Stage 2: KYC OCR β | |
| β method = "rules" β β easyocr β keyword regex β | |
| β β DONE β ββββββββββββββββ¬βββββββββββββββ | |
| βββββββββββββββββββββββ β | |
| ββββββββββββββββ΄βββββββββββββββ | |
| KYC match? no match | |
| β β | |
| ββββββββββββββΌβββββββββ βββββββββββββββββΌββββββββββββββ | |
| β doc_type = "kyc" β β Stage 3: Gemini LLM β | |
| β method = "ocr" β β gemma-4-31b-it β | |
| β β DONE β β β Patient Bills β | |
| βββββββββββββββββββββββ β β Claim Forms β | |
| β β Medical Reports β | |
| β β Prescriptions β | |
| β β Unknown β | |
| ββββββββββββββββββββββββββββββββ | |
| β | |
| ββββββββββββββΌβββββββββββββββββ | |
| β doc_type = "image" β | |
| β sub_type = <category> β | |
| β method = "llm" β | |
| β β DONE β | |
| βββββββββββββββββββββββββββββββ | |
| All stages emit @traceable spans β LangSmith (traces Β· tokens Β· latency) | |
| All results served via FastAPI β Drag & Drop UI | |
| Container deployed on Azure Container Apps via GitHub Actions CI/CD | |
| ``` | |
| ## Architecture Overview | |
| ``` | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β Frontend UI β | |
| β Drag & Drop Β· frontend/index.html β | |
| β - Batch upload (all files in one POST) β | |
| β - Concurrent server processing (asyncio.gather) β | |
| β - Live progress bar + per-file status rows β | |
| β - Color-coded badges: bill/kyc/image β | |
| βββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ | |
| β POST /classify (multipart) | |
| βΌ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β FastAPI Β· src/api.py Β· Port 8000 β | |
| β POST /classify Β· GET /health Β· GET /metrics β | |
| β asyncio.gather + run_in_executor (concurrent files) β | |
| ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ | |
| β | |
| ββββββββββββΌβββββββββββ | |
| β src/classifier.py β (orchestrator) | |
| ββββ¬βββββββ¬βββββββ¬βββββ | |
| β β β | |
| rules β ocr β llm β | |
| engine β β β | |
| βΌ βΌ βΌ | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β src/monitoring.py β LangSmith @traceable spans β | |
| β trace_rules_engine Β· trace_kyc_ocr β | |
| β trace_llm_classify Β· trace_classify β | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β | |
| ββββββββββββ΄βββββββββββ | |
| βΌ βΌ | |
| LangSmith Azure Monitor | |
| (traces/tokens) (container logs) | |
| ``` | |
| ## Decision Rules | |
| | Condition | doc_type | method | Sent to LLM? | | |
| |---|---|---|---| | |
| | filename matches `^bill_` (regex) | `bill` | `rules` | No | | |
| | OCR text contains KYC keywords | `kyc` | `ocr` | No | | |
| | Everything else | `image` | `llm` | Yes | | |
| ## Final File Layout | |
| ``` | |
| multimodal-ai/ | |
| βββ src/ | |
| β βββ rules_engine.py # Step 1 β | |
| β βββ kyc_detector.py # Step 2 β | |
| β βββ llm_classifier.py # Step 3 β | |
| β βββ classifier.py # Step 4 β | |
| β βββ api.py # Step 5 β | |
| β βββ monitoring.py # Step 7 β | |
| βββ frontend/ | |
| β βββ index.html # Step 6 β | |
| βββ tests/ | |
| β βββ test_rules_engine.py # 11 tests β | |
| β βββ test_kyc_detector.py # 21 tests β | |
| β βββ test_llm_classifier.py # 32 tests β | |
| β βββ test_classifier.py # 29 tests β | |
| β βββ test_api.py # 11 tests β | |
| β βββ test_monitoring.py # 13 tests β (117 total) | |
| βββ infra/ | |
| β βββ deploy.sh # Step 9 β Azure Container Apps | |
| β βββ teardown.sh # β | |
| βββ .github/workflows/ | |
| β βββ ci.yml # Step 10 β test on every push | |
| β βββ deploy.yml # Step 10 β deploy on merge to main | |
| βββ Dockerfile # Step 8 β two-stage build | |
| βββ .dockerignore # Step 8 β | |
| βββ README.md # Step 10 β | |
| βββ pyproject.toml | |
| βββ .env.example | |
| ``` | |
| --- | |
| ## Steps | |
| ### Phase 1 β Core Classification Engine | |
| - [x] **Step 1 β Rules Engine** (`src/rules_engine.py`) | |
| - Compiled regex `re.compile(r"^bill_")` β case-sensitive, anchored to start of filename | |
| - Strips directory prefix so full paths work (`dataset/bill_x.png`) | |
| - Returns `RulesResult(filename, doc_type, send_to_llm)` | |
| - **Changed from plan:** Used `re.compile` regex instead of `str.startswith()` as requested | |
| - β **11/11 tests passing** | |
| - [x] **Step 2 β KYC Detector** (`src/kyc_detector.py`) | |
| - 11 compiled regex patterns covering Aadhaar, PAN, Passport, Govt of India, DOB, 12-digit Aadhaar number, PAN card format | |
| - easyocr `Reader` is a lazy singleton β loaded once on first use, not at import time | |
| - `reader` is injectable (passed as parameter) so tests never load the real model | |
| - Returns `KYCResult(filename, doc_type, send_to_llm, ocr_text)` | |
| - β **21/21 tests passing** | |
| - [x] **Step 3 β LLM Classifier** (`src/llm_classifier.py`) | |
| - Sends image bytes + structured prompt to `gemma-4-31b-it` via `google-genai` | |
| - Prompt instructs model to return exactly one category name | |
| - `_parse_category()` does case-insensitive match + strips whitespace, falls back to `"Unknown"` | |
| - Captures `input_tokens` and `output_tokens` from `response.usage_metadata` | |
| - `client` is injectable for testing β zero live API calls in test suite | |
| - Returns `LLMResult(filename, doc_type, sub_type, method, input_tokens, output_tokens, raw_response)` | |
| - β **32/32 tests passing** | |
| - [x] **Step 4 β Pipeline Orchestrator** (`src/classifier.py`) | |
| - `classify(filename, image_bytes, ocr_reader, llm_client)` β single document | |
| - `classify_dataset(dataset_dir, ...)` β scans all PNGs in a directory | |
| - Returns `ClassificationResult(filename, doc_type, sub_type, method, latency_ms, input_tokens, output_tokens)` | |
| - Each stage emits a LangSmith trace span (added in Step 7) | |
| - β **29/29 tests passing** | |
| --- | |
| ### Phase 2 β FastAPI Server | |
| - [x] **Step 5 β API Server** (`src/api.py`) | |
| - `POST /classify` β multipart file upload, returns JSON array | |
| - `GET /health` β liveness probe | |
| - `GET /metrics` β in-memory counters per method/doc_type/token usage | |
| - `GET /docs` β auto Swagger UI | |
| - **Changed from plan:** `asyncio.gather` + `run_in_executor` runs all uploaded files concurrently β `bill_` files return in < 10 ms without waiting behind OCR/LLM calls | |
| - easyocr `Reader` and Gemini `Client` loaded once at startup via FastAPI `lifespan` | |
| - CORS middleware enabled for browser UI | |
| - β **11/11 tests passing** (patched at `src.api.classify`) | |
| --- | |
| ### Phase 3 β Frontend UI | |
| - [x] **Step 6 β Drag & Drop UI** (`frontend/index.html`) | |
| - Self-contained single HTML file, no external dependencies | |
| - Drag & drop + click-to-browse, deduplicates files by name | |
| - **Changed from plan (sequential β batch):** Sends all files in ONE `POST /classify` β server processes concurrently so `bill_` files don't wait behind slow OCR/LLM calls | |
| - Results table appears immediately with `queuedβ¦` rows; fills in as server responds | |
| - Live progress bar + `Processing file N of M` text | |
| - Color-coded badges: bill=blue, kyc=orange, image=green, rules=purple, ocr=red, llm=teal | |
| - All controls (classify, clear, remove buttons, drop zone) disabled during processing | |
| - Summary bar: counts per type + average latency | |
| - Error banner for API failures and unsupported file types | |
| --- | |
| ### Phase 4 β Monitoring (LangSmith) | |
| - [x] **Step 7 β LangSmith Integration** (`src/monitoring.py`) | |
| - Four `@traceable` functions forming a parent/child span tree: | |
| - `trace_classify` β top-level `chain` span per document | |
| - `trace_rules_engine` β `tool` span for Stage 1 | |
| - `trace_kyc_ocr` β `tool` span for Stage 2; records `ocr_text_length` not raw text (PII safety) | |
| - `trace_llm_classify` β `llm` span for Stage 3; records token breakdown | |
| - `record_token_usage()` extracts `input/output/total_tokens` from Gemini `usage_metadata` | |
| - Tracing is a **no-op** when `LANGCHAIN_TRACING_V2` is not set β CI safe | |
| - **Required env vars:** | |
| ``` | |
| LANGCHAIN_TRACING_V2=true | |
| LANGCHAIN_API_KEY=<key> | |
| LANGCHAIN_PROJECT=medishield-classification | |
| ``` | |
| - β **13/13 tests passing** | |
| --- | |
| ### Phase 5 β Docker | |
| - [x] **Step 8 β Dockerfile** | |
| - Two-stage build: `uv` builder β `python:3.12-slim` runtime | |
| - Installs OS libs for easyocr/opencv/weasyprint in runtime stage | |
| - **Pre-downloads easyocr models at build time** as `appuser` β container starts in ~10s not 60s | |
| - Runs as non-root `appuser` (with home dir so easyocr can write model cache) | |
| - `HEALTHCHECK` polls `/health` every 30s, 60s start period | |
| - 2 uvicorn workers for concurrency | |
| - **Fix applied during build:** Created home dir for `appuser` and set `EASYOCR_MODULE_PATH` to fix permission error on model cache write | |
| - β **Build verified, `/classify` tested inside container** | |
| --- | |
| ### Phase 6 β Azure Deployment | |
| - [x] **Step 9 β Azure Container Apps** (`infra/deploy.sh`) | |
| - **Changed from plan:** Azure instead of AWS (simpler setup, no separate load balancer, built-in HTTPS) | |
| - Provisions: Resource Group β ACR β Log Analytics β Container Apps Environment β Container App | |
| - Container App: 0.5 vCPU / 2 GB RAM, min 1 replica, max 3, public HTTPS ingress | |
| - Secrets (`GOOGLE_API_KEY`, `LANGCHAIN_API_KEY`) injected via Container Apps secret references | |
| - `infra/teardown.sh` for full cleanup | |
| - **CI/CD via `.github/workflows/deploy.yml`:** | |
| - Tests gate deploy (deploy only runs if tests pass) | |
| - `az acr build` builds in Azure cloud (no local Docker in CI) | |
| - `az containerapp update` rolling deploy | |
| - Smoke tests live `/health` endpoint post-deploy | |
| - OIDC login (no long-lived secrets in GitHub) | |
| --- | |
| ### Phase 7 β Documentation | |
| - [x] **Step 10 β README + CI** (`README.md`, `.github/workflows/ci.yml`) | |
| - Professional README with ASCII architecture diagram, workflow diagram, full API reference, setup guide, deployment guide, test matrix, environment variable table | |
| - `ci.yml` runs all 117 tests on every push/PR β no real API keys needed | |
| --- | |
| ## Build Order Summary | |
| | # | Deliverable | Test Gate | Status | | |
| |---|---|---|---| | |
| | 1 | Rules Engine | `pytest tests/test_rules_engine.py` β 11 passed | β | | |
| | 2 | KYC Detector | `pytest tests/test_kyc_detector.py` β 21 passed | β | | |
| | 3 | LLM Classifier | `pytest tests/test_llm_classifier.py` β 32 passed | β | | |
| | 4 | Orchestrator | `pytest tests/test_classifier.py` β 29 passed | β | | |
| | 5 | FastAPI Server | `pytest tests/test_api.py` β 11 passed + Swagger check | β | | |
| | 6 | Frontend UI | Batch POST, live progress, controls locked during processing | β | | |
| | 7 | LangSmith Monitoring | `pytest tests/test_monitoring.py` β 13 passed | β | | |
| | 8 | Docker | `docker build` + `/classify` tested inside container | β | | |
| | 9 | Azure Deploy | `infra/deploy.sh` + GitHub Actions CI/CD pipeline | β | | |
| | 10 | README + CI | `ci.yml` + `deploy.yml` + `README.md` | β | | |
| **Total: 117 tests Β· 10 steps Β· all complete β ** | |
| ## Key Changes vs Original Plan | |
| | Area | Original Plan | What We Actually Built | | |
| |---|---|---| | |
| | Rules matching | `str.startswith("bill_")` | `re.compile(r"^bill_")` regex | | |
| | API concurrency | Sequential file loop | `asyncio.gather` + `run_in_executor` | | |
| | UI upload strategy | One request per file (sequential) | One batch request, server concurrent | | |
| | Cloud provider | AWS ECS Fargate | Azure Container Apps | | |
| | Metrics | OpenTelemetry + CloudWatch | LangSmith + Azure Monitor | | |
| | Docker user | Root | Non-root `appuser` with home dir | | |
| | easyocr models | Downloaded at runtime | Pre-baked into image at build time | | |