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
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.compileregex instead ofstr.startswith()as requested - β 11/11 tests passing
- Compiled regex
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
Readeris a lazy singleton β loaded once on first use, not at import time readeris 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-itviagoogle-genai - Prompt instructs model to return exactly one category name
_parse_category()does case-insensitive match + strips whitespace, falls back to"Unknown"- Captures
input_tokensandoutput_tokensfromresponse.usage_metadata clientis 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
- Sends image bytes + structured prompt to
Step 4 β Pipeline Orchestrator (
src/classifier.py)classify(filename, image_bytes, ocr_reader, llm_client)β single documentclassify_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 arrayGET /healthβ liveness probeGET /metricsβ in-memory counters per method/doc_type/token usageGET /docsβ auto Swagger UI- Changed from plan:
asyncio.gather+run_in_executorruns all uploaded files concurrently βbill_files return in < 10 ms without waiting behind OCR/LLM calls - easyocr
Readerand GeminiClientloaded once at startup via FastAPIlifespan - 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 sobill_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 Mtext - 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
@traceablefunctions forming a parent/child span tree:trace_classifyβ top-levelchainspan per documenttrace_rules_engineβtoolspan for Stage 1trace_kyc_ocrβtoolspan for Stage 2; recordsocr_text_lengthnot raw text (PII safety)trace_llm_classifyβllmspan for Stage 3; records token breakdown
record_token_usage()extractsinput/output/total_tokensfrom Geminiusage_metadata- Tracing is a no-op when
LANGCHAIN_TRACING_V2is not set β CI safe - Required env vars:
LANGCHAIN_TRACING_V2=true LANGCHAIN_API_KEY=<key> LANGCHAIN_PROJECT=medishield-classification - β 13/13 tests passing
- Four
Phase 5 β Docker
- Step 8 β Dockerfile
- Two-stage build:
uvbuilder βpython:3.12-slimruntime - 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) HEALTHCHECKpolls/healthevery 30s, 60s start period- 2 uvicorn workers for concurrency
- Fix applied during build: Created home dir for
appuserand setEASYOCR_MODULE_PATHto fix permission error on model cache write - β
Build verified,
/classifytested inside container
- Two-stage build:
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.shfor full cleanup- CI/CD via
.github/workflows/deploy.yml:- Tests gate deploy (deploy only runs if tests pass)
az acr buildbuilds in Azure cloud (no local Docker in CI)az containerapp updaterolling deploy- Smoke tests live
/healthendpoint 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.ymlruns 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 |