Repository: github.com/malik-builds/HealthLens
Tagline: See your risk clearly.
HealthLens is a portfolio full-stack app: a Next.js 15 frontend (green wellness UI, Three.js DNA-style hero) and a FastAPI backend that serves RandomForest-based diabetes risk estimates trained on the Pima Indians Diabetes dataset.
Medical disclaimer: For education and demonstration only. Not clinically validated. Not a diagnostic—always consult a licensed professional for medical decisions.
- Features
- Frontend architecture
- Repository layout
- Prerequisites
- Quick start (local, no Docker)
- Docker
- Environment variables
- API
- Model methodology
- Deploying live (optional)
- Troubleshooting
- License
- Landing (
/) — Full-viewport hero with rotating dual-helix background, gradient CTA, animated stats strip. - Assessment (
/predict) — Eight biomarkers with typed inputs and sliders (synced), short tooltips, expandable “What this feature means” copy, animated risk ring, and global feature-importance bars. - About (
/about) — Dataset, model, and limitations. - Backend — On first start (if
model.joblibis absent), trains a pipeline, saves it besidemain.py, and logs out-of-fold F1 from stratified 5-fold CV.
The frontend is a Next.js 15 app (App Router, React 19) that gives users a clear, calm interface around the same eight biomarkers the backend model expects. It does not run ML in the browser: it collects inputs, POSTs them to the FastAPI /predict endpoint, and visualizes the JSON response (probability, label, and global feature importances). Styling is dark green / wellness themed—deep backgrounds, brand green accents, and readable contrast.
- Home (
/) — Full-height hero with headline, short value proposition, primary CTA Run assessment and secondary How it works, plus an animated stats strip (e.g. training set size, biomarker count). The background is a WebGL “DNA-style” double helix: two point-cloud helices in brand green and mint, slowly rotating, loaded client-only (no SSR for Three.js) so the first paint stays fast. - Assessment (
/predict) — A two-column layout: a form of eight biomarkers and a results panel. Users type values or use sliders (stayed in sync); each field has a Radix tooltip, allowed ranges, and an expandable “What this feature means” block with educational copy. Run assessment calls the API; while loading, a placeholder animates. On success, the app shows an SVG risk ring (animated percentage and LOW / MODERATE / HIGH label by threshold), a short outcome message (depends on the binarypredictionand probability), a bar-style chart of the top 5 global feature importances from the response, and a disclaimer. - About (
/about) — Static cards on dataset, model, what the UI shows, and safety—aligned with the medical disclaimer on the home page.
Shell — A fixed navbar (logo, Home, Assessment, About) sits above all pages; the root layout sets page metadata, theme color, and loads DM Serif Display (headings), DM Sans (body), and JetBrains Mono (numbers).
| Area | Details |
|---|---|
| Framework | Next.js 15 App Router, output: "standalone" for Docker |
| Dev server | next dev --turbopack |
| Styling | Tailwind CSS v4 with @theme tokens in app/globals.css (colors, fonts); custom range and input classes for sliders |
| Motion | Framer Motion (page transitions, risk ring, feature bars, stats strip) |
| 3D | @react-three/fiber, three — DNAHelix built with point buffers; HeroCanvas uses next/dynamic with ssr: false |
| UI primitives | Radix Tooltip; class-variance-authority + Button; clsx / tailwind-merge via lib/utils |
| Icons | lucide-react |
lib/api.ts exports postPredict(): it POSTs JSON to `${NEXT_PUBLIC_API_URL}/predict` (default http://localhost:8000 if unset). The request body matches the API’s camelCase fields (pregnancies, glucose, bloodPressure, etc.). The response type includes risk_probability, prediction, and feature_importances. Network failures surface a single friendly error in the form.
| Path | Role |
|---|---|
app/layout.tsx |
Global fonts, Navbar, main padding for fixed header |
app/page.tsx |
Landing: HeroCanvas, copy, links, StatsStrip |
app/predict/page.tsx |
Renders PredictForm |
app/about/page.tsx |
About copy |
components/PredictForm.tsx |
Form state, biomarker rows, submit, results column |
components/biomarkerFields.ts |
Labels, min/max, steps, tooltips, “about” text, defaults |
components/RiskRing.tsx |
Circular progress, color bands by % |
components/FeatureChart.tsx |
Top 5 importances as horizontal bars |
components/DNAHelix.tsx / HeroCanvas.tsx |
3D hero background |
HealthLens/
├── backend/ # FastAPI + sklearn
│ ├── main.py # App, train/load pipeline, /predict, /health
│ ├── diabetes.csv # Pima dataset (bundled)
│ ├── requirements.txt
│ ├── Dockerfile
│ └── .env.example # CORS_ORIGINS template
├── frontend/ # Next.js 15 (App Router)
│ ├── app/ # Routes: /, /predict, /about
│ ├── components/ # DNAHelix, PredictForm, RiskRing, etc.
│ ├── Dockerfile # Multi-stage, output: standalone
│ └── .env.example # NEXT_PUBLIC_API_URL
├── docker-compose.yml
└── README.md
Generated locally (gitignored): backend/model.joblib after the first successful training run.
| Tool | Version (tested / expected) |
|---|---|
| Node.js | 20.x (for frontend/) |
| Python | 3.11+ (for backend/) |
| Docker | Optional, for docker compose |
git clone https://github.com/malik-builds/HealthLens.git
cd HealthLenscd backend
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
uvicorn main:app --reload --host 0.0.0.0 --port 8000Leave this terminal open. First launch may take a minute while the model trains and writes model.joblib.
Sanity check: open http://127.0.0.1:8000/health — you should see {"status":"ok"}.
In a second terminal:
cd frontend
npm install
cp .env.example .env.localEnsure .env.local contains (adjust if your API port differs):
NEXT_PUBLIC_API_URL=http://localhost:8000Then:
npm run devOpen http://localhost:3000. Go to Assessment, enter or slide biomarker values, then Run assessment.
| Command | Purpose |
|---|---|
npm run dev |
Dev server (Turbopack) |
npm run build |
Production build |
npm run start |
Serve production build |
npm run lint |
ESLint |
From the repository root (with Docker running):
docker compose up --build| Service | URL |
|---|---|
| Frontend | http://localhost:3000 |
| Backend | http://localhost:8000 |
The browser loads the frontend from your machine; it must call the API at a URL it can reach. Compose sets NEXT_PUBLIC_API_URL=http://localhost:8000 for the build of the frontend image so client-side fetch targets your published API port.
| Variable | Purpose |
|---|---|
CORS_ORIGINS |
Comma-separated browser origins allowed to call the API. Default in code: http://localhost:3000. Add your production frontend URL when deploying. |
python-dotenv loads .env from the backend working directory when present.
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_API_URL |
Base URL of the FastAPI server as seen by the browser (no trailing slash), e.g. http://localhost:8000. |
Base URL: same as NEXT_PUBLIC_API_URL (e.g. http://localhost:8000).
| Method | Path | Description |
|---|---|---|
GET |
/health |
{ "status": "ok" } |
POST |
/predict |
JSON body (camelCase) — see below |
POST /predict body (example)
{
"pregnancies": 1,
"glucose": 120,
"bloodPressure": 70,
"skinThickness": 20,
"insulin": 80,
"bmi": 32,
"diabetesPedigreeFunction": 0.5,
"age": 45
}Response (truncated)
{
"risk_probability": 0.42,
"prediction": 0,
"feature_importances": { "Glucose": 0.25, "...": "..." }
}Interactive docs: http://localhost:8000/docs (Swagger UI) when the backend is running.
- Data:
backend/diabetes.csv— Pima Indians Diabetes Database (768 rows, 8 features + outcome). - Missing values: zeros in Glucose, BloodPressure, SkinThickness, Insulin, and BMI are treated as missing and imputed with the median (
SimpleImputer). - Scaling:
StandardScaleron all features. - Classifier:
RandomForestClassifier(n_estimators=100, random_state=42). - Evaluation:
StratifiedKFold(n_splits=5)withcross_val_predictand F1 on out-of-fold predictions, then a final fit on all rows. - Artifact:
model.joblibstores the fitted pipeline and global feature importances (not per-patient SHAP-style explanations).
If you later host the frontend and API separately (e.g. Vercel + Railway), set:
NEXT_PUBLIC_API_URLon the frontend to your public API HTTPS URL.CORS_ORIGINSon the backend to your frontend origin(s), comma-separated.
See the Checklist in an earlier version of this doc or platform-specific guides; the same rules apply: browser must reach the API URL, and CORS must allow your site’s origin.
| Issue | What to try |
|---|---|
| Assessment shows a network error | Confirm the backend is running and NEXT_PUBLIC_API_URL matches (including http vs https and port). |
| CORS error in the browser console | Add your frontend origin to CORS_ORIGINS on the backend (comma-separated, no trailing slashes). Restart the API. |
| First API start is slow | Training runs once until model.joblib exists; subsequent starts load the file. |
model.joblib missing after clone |
Expected—train once by starting the backend; the file is gitignored. |
| Layer | Technologies |
|---|---|
| Frontend | Next.js 15 (App Router), Tailwind CSS v4 (@theme), Framer Motion, React Three Fiber, drei, Radix Tooltip |
| Backend | FastAPI, uvicorn, pandas, scikit-learn, joblib, python-dotenv |
| Ops | Docker, Docker Compose |
MIT (or your choice) — adjust if you add a LICENSE file.