medishield / IMPLEMENTATION_PLAN.md
sriny2131's picture
deploy: sync from local repo
d86db02 verified
|
Raw
History Blame Contribute Delete
15.8 kB

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

  • 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
  • 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
  • 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
  • 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

  • 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

  • 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)

  • 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

  • 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

  • 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

  • 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