Personal disease risk intelligence, calibrated for East Africa.
Most nutrition apps were built for Western diets and have never heard of Isombe, Ibishyimbo, or Matoke. Nutrisense-AI is built differently: it lets Rwandan users photograph their everyday meals, tracks nutritional intake in real time, and predicts personalised risk scores for anaemia, type 2 diabetes, and overweight using machine learning models trained on Rwanda DHS 2019β20 + STEPS Rwanda 2012 microdata β so the predictions reflect East African realities, not Western averages.
Live app β nutrisense-seven-omega.vercel.app ML API β huggingface.co/spaces/JeanJabo/nutrisense-api Demo video β Demo
Rwanda and East Africa face a dual burden of malnutrition. Existing tools (ZOE, DayTwo) cost USD 300β2,000 and are trained on Western food databases. Apps like MyFitnessPal lack disease prediction entirely and do not cover Rwandan staples like Isombe, Ibihaza, Ugali, or Matoke.
| Disease | Rwanda prevalence (DHS 2019β20) |
|---|---|
| Anaemia (women 15β49) | ~38% |
| Overweight | ~22% |
| Diabetes | ~3.4% |
Nutrisense-AI is a free, accessible web platform that predicts personalised risk for all three conditions from daily food logs, calibrated to this population.
The platform supports three distinct roles, each with their own portal:
| Role | Portal | Access |
|---|---|---|
patient |
/dashboard |
Log meals, view risk scores, share Connect Code |
nutritionist |
/nutritionist |
Monitor assigned patients' logs and risk scores |
admin |
/admin |
Create nutritionist accounts, approve/revoke access |
A patient links to a nutritionist by sharing their Connect Code (their user UUID) from the Profile sheet. The nutritionist enters it in the "Add Patient" dialog β no data is shared until the patient initiates this.
Browser (React 19 + TanStack Router)
β
βββ Supabase (PostgreSQL + RLS + Auth + Edge Functions)
β βββ profiles
β βββ food_logs
β βββ nutritionist_applications
β βββ patient_assignments
β
βββ HuggingFace Spaces (FastAPI)
βββ POST /api/predict/food β ViT-B/16 image classifier (114 classes)
βββ POST /api/predict/risk β GradientBoosting Γ 3 + SHAP
βββ GET /health
β
βββ HuggingFace Model Repo (weights downloaded at startup)
| Layer | Technology |
|---|---|
| Frontend | React 19, TanStack Router, TailwindCSS 4, Vite |
| Auth & DB | Supabase (PostgreSQL + Row-Level Security) |
| Edge Functions | Supabase Edge Functions (Deno / TypeScript) |
| Food classifier | ViT-B/16 (vit_base_patch16_224.orig_in21k) fine-tuned on Food-101 + KenyanFood13 (114 classes) |
| Risk models | GradientBoostingClassifier Γ 3 (scikit-learn) + SHAP TreeExplainer |
| Nutrition lookup | nutrition_db.py β 114 entries, 12 nutrient fields, sourced from USDA SR Legacy + FAO/INFOODS East Africa |
| Training data | DHS Rwanda 2019β20 + STEPS Rwanda 2012 microdata (risk) Β· Food-101 + KenyanFood13 (food classifier) |
| Model storage | HuggingFace Model Repo JeanJabo/nutrisense-food-model |
| API | FastAPI + Uvicorn on HuggingFace Spaces |
| Deployment | Vercel (frontend) Β· HuggingFace Spaces (API) |
| Route | Description |
|---|---|
/ |
Landing page β hero, features, call to action |
/login |
Email/password login + Google OAuth |
/signup |
Patient registration |
/dashboard |
Patient portal β Overview, Risk Engine, Food Lab, Trends tabs |
/nutritionist |
Nutritionist portal β patient list, risk scores, food logs |
/admin |
Admin portal β create/revoke nutritionist accounts |
/apply-nutritionist |
Self-application form for clinicians |
/find-nutritionist |
Browse approved nutritionists |
/reset-password |
Password reset (linked from email) |
/legal |
Privacy Policy & Terms of Use (12 sections) |
- Node.js β₯ 18
- A Supabase project (free tier works)
# 1. Clone
git clone https://github.com/JaboJean/nutrisense.git
cd nutrisense
# 2. Install dependencies
npm install
# 3. Configure environment
cp .env.local.example .env.localEdit .env.local:
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key-here
VITE_ML_API_URL=https://jeanjabox-nutrisense-api.hf.space# 4. Run
npm run dev
# β http://localhost:3000- Python β₯ 3.10
cd api
# 1. Create virtual environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# 2. Install dependencies
pip install -r requirements.txt
# 3. Start server
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
# β http://localhost:8000/docs (Swagger UI)On first start the API downloads food_finetuned_model.pth, class_names.txt, and nutrisense_model.joblib from the HuggingFace Model Repo automatically.
Set VITE_ML_API_URL=http://localhost:8000 in .env.local to point the frontend at your local API.
Run this SQL in your Supabase SQL editor (SQL Editor β New query):
-- Profiles (one row per user)
create table profiles (
id uuid primary key references auth.users(id) on delete cascade,
name text,
age integer,
sex text check (sex in ('male','female')),
weight_kg numeric,
height_cm numeric,
role text default 'patient'
check (role in ('patient','nutritionist','admin','pending_nutritionist')),
created_at timestamptz default now()
);
-- Food logs
create table food_logs (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users(id) on delete cascade,
name text not null,
meta text,
meal text,
tag text,
tone text,
glyph text,
img text,
logged_at timestamptz default now()
);
-- Nutritionist credential records
create table nutritionist_applications (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users(id) on delete cascade,
full_name text,
email text,
credential_no text,
institution text,
note text,
status text default 'pending' check (status in ('pending','approved','rejected')),
reviewed_at timestamptz,
created_at timestamptz default now()
);
-- Patientβnutritionist assignments
create table patient_assignments (
nutritionist_id uuid references auth.users(id) on delete cascade,
patient_id uuid references auth.users(id) on delete cascade,
created_at timestamptz default now(),
primary key (nutritionist_id, patient_id)
);
-- Row-Level Security
alter table profiles enable row level security;
alter table food_logs enable row level security;
alter table nutritionist_applications enable row level security;
alter table patient_assignments enable row level security;
-- Patients manage their own data
create policy "Users manage own profile"
on profiles for all using (auth.uid() = id);
create policy "Users manage own logs"
on food_logs for all using (auth.uid() = user_id);
-- Nutritionists can read their assigned patients' logs
create policy "Nutritionists read assigned patient logs"
on food_logs for select
using (
exists (
select 1 from patient_assignments
where nutritionist_id = auth.uid()
and patient_id = food_logs.user_id
)
);
-- Nutritionists manage their own assignments
create policy "Nutritionists manage assignments"
on patient_assignments for all using (auth.uid() = nutritionist_id);Then go to Authentication β Email and disable "Confirm email" so the onboarding flow redirects directly to the dashboard after signup.
Deploy the create-nutritionist Edge Function:
npx supabase functions deploy create-nutritionist --project-ref your-project-refThis function handles admin-initiated nutritionist account creation using the service role key server-side.
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Liveness check |
POST |
/api/predict/food |
Classify a food photo (multipart/form-data, field: image) |
POST |
/api/predict/risk |
Predict risk scores from food logs + profile |
POST /api/predict/food
Content-Type: multipart/form-data
Field: image (file)
Response:
{
"name": "ugali",
"confidence": 0.823,
"kcal": 357,
"protein": 7.2,
"iron": 1.8,
"fiber": 3.2,
"vitC": 0.0,
"glyph": "π«",
"tone": "amber",
"tag": "Staple"
}Confidence thresholds:
| Confidence | Behaviour |
|---|---|
| < 15% | 422 not_food β "Dish not recognised" |
| 15β65% | Returns result with low-confidence warning |
| β₯ 65% | Returns confirmed result |
POST /api/predict/risk
Content-Type: application/json
Request:
{
"logs": [
{ "name": "ugali", "meal": "Lunch" },
{ "name": "isombe", "meal": "Lunch" }
],
"profile": { "age": 24, "sex": "female", "weightKg": 58, "heightCm": 165 }
}Response:
{
"scores": { "anemia": 62, "diabetes": 8, "overweight": 25, "overall": 32 },
"shap": {
"anemia": [
{ "f": "Iron intake", "v": -0.21 },
{ "f": "Vitamin C", "v": -0.08 }
],
"diabetes": [...],
"overweight": [{ "f": "Body Mass Index", "v": 0.04 }, ...]
}
}SHAP values: positive = increases disease risk, negative = reduces risk. Top 5 features returned per disease.
- Architecture:
vit_base_patch16_224.orig_in21kviatimmβ pretrained on ImageNet-21k, fine-tuned on food dataset - Classes: 114 total β 101 from Food-101 + 13 from KenyanFood13 (ugali, pilau, chapati, mandazi, githeri, sukuma wiki, nyama choma, matoke, kukuchoma, bhaji, kachumbari, masala chips, mukimo)
- Input: 224Γ224 RGB, normalised with ImageNet mean/std
- Output: Top-1 class name + softmax confidence
- Accuracy: 80.3% top-1 Β· 96.8% top-5 on held-out test set
- Input features (14): age, sex, energy_kcal, protein_g, fat_g, carb_g, iron_mg, vitC_mg, vitA_mcg, fiber_g, sugar_g, calcium_mg, zinc_mg, sodium_mg
- Scaling: If fewer than 3 meals are logged, nutrients are scaled to full-day estimate (
scale = 3.0 / n_meals) - Training data: DHS Rwanda 2019β20 (primary) + STEPS Rwanda 2012 (supplementary)
- Explainability: SHAP TreeExplainer β top-5 feature contributions per prediction
| Model | AUROC | Notes |
|---|---|---|
| Anaemia | 0.973 | Haemoglobin-derived label from DHS |
| Overweight | 0.980 | BMI-derived label; score overridden with direct BMI lookup at inference |
| Diabetes | 0.598 | Proxy label (no blood glucose in training data); score overridden with glycaemic load heuristic |
Post-processing overrides:
- Overweight: GBM output replaced by a BMI lookup table (BMI β₯ 25 β score 48, BMI β₯ 30 β score 80, etc.) since BMI from user profile is more reliable than dietary proxies alone
- Diabetes: GBM output replaced by a glycaemic load heuristic (daily sugar + net carbs) due to AUROC 0.598
- All scores: Capped at 90 to prevent display of false certainty from uncalibrated GBM probabilities
nutrition_db.py maps all 114 food class names to 12 nutrient fields per typical serving:
- Food-101 values: USDA SR Legacy database
- East African values: FAO/INFOODS East African Food Composition Table (2012) + Kenya NFCT (KEBS 2018)
| Decision | Rationale |
|---|---|
| DHS Rwanda + STEPS Rwanda microdata | Ground-truth population data from Rwanda rather than generic Western datasets |
| ViT-B/16 over CNN | Self-attention captures global image context; outperforms ResNet on Food-101 benchmark (80.3% vs ~68%) |
| KenyanFood13 added to Food-101 | Food-101 has no East African dishes; 13 additional classes cover regional staples |
| GradientBoosting over neural networks | Tabular data; smaller dataset; directly compatible with SHAP TreeExplainer for exact Shapley values |
| SHAP TreeExplainer | Per-prediction explainability β users see which specific nutrients drive each risk score |
| Meal-count scaling | Models trained on daily totals; scale = max(1.0, 3.0 / n_meals) extrapolates partial logs |
| BMI override for overweight | Clinical gold standard is more reliable than dietary proxy alone |
| Glycaemic load heuristic for diabetes | Replaces weak GBM (AUROC 0.598) with a physiologically sound score based on daily sugar + net carbs |
| Scores capped at 90 | Raw GBM probabilities are uncalibrated; cap prevents display of false certainty |
| Supabase RLS | Data access enforced at the database layer, not application layer β a bug in React code cannot leak another user's data |
| Edge Function for account creation | Service role key must never reach the browser; privileged operations run server-side |
| URL-based tab state (TanStack Router) | Tab persists across page refresh and browser back button without extra state management |
| Optimistic updates in food log | Meal appears instantly in UI; Supabase insert runs in background with rollback on failure |
Five levels of testing were applied:
Unit tests (api/test_api.py β 9 tests):
- Health endpoint returns 200
- Food endpoint returns correct schema
- 422 returned for non-food input
- 413 returned for images > 10 MB
- Risk endpoint returns correct schema with SHAP values
- CORS headers present on all responses
Validation: ML models evaluated on held-out test sets (AUROC, top-1/top-5 accuracy)
Integration: Frontend β API β Supabase end-to-end flows
Functional/System: Complete user journeys β signup β onboarding β log meal β view risk; admin β create nutritionist β nutritionist login
Acceptance testing (8 participants):
| Metric | Result |
|---|---|
| Tasks completed unaided | 82.5% |
| Preferred photo logging over manual entry | 89% |
| Photo logging time vs manual entry | 15s vs 60s |
| Mean usability score | 4.1 / 5 |
| SHAP panel interpreted correctly unaided | 67% |
Formal criteria:
| Criterion | Target | Achieved |
|---|---|---|
| Response time | β€ 3s | 1.2s mean Β· 2.6s p95 |
| AUROC per model | β₯ 0.75 | 2 of 3 met (diabetes: 0.598) |
| Cross-user data access | Denied | DB refuses query (RLS) |
| Explainability | 100% | 100% (SHAP on every prediction) |
| Recurring cost | USD 0 | USD 0 |
nutrisense/
βββ api/ # FastAPI ML backend
β βββ main.py # Routes + lifespan handler
β βββ food_predictor.py # ViT-B/16 inference (114 classes)
β βββ risk_predictor.py # GradientBoosting inference + SHAP
β βββ nutrition_db.py # Nutrient lookup (114 entries, 12 fields)
β βββ test_api.py # 9 unit tests (UT-01 to UT-09)
β βββ requirements.txt
βββ src/
β βββ routes/ # File-based routing (TanStack Router)
β β βββ index.tsx # Landing page
β β βββ dashboard.tsx # Patient portal (4 tabs)
β β βββ nutritionist.tsx # Nutritionist portal
β β βββ admin.tsx # Admin portal
β β βββ login.tsx
β β βββ signup.tsx
β β βββ reset-password.tsx
β β βββ apply-nutritionist.tsx
β β βββ find-nutritionist.tsx
β β βββ legal.tsx # Privacy Policy & Terms of Use
β β βββ __root.tsx
β βββ components/
β β βββ dashboard/ # Feature components
β β β βββ PhotoCapture.tsx # Photo β food log pipeline
β β β βββ RiskGauges.tsx # Animated risk score gauges
β β β βββ AIInsightPanel.tsx # SHAP-driven narrative panel
β β β βββ HeroSection.tsx # Daily summary header
β β β βββ FoodLog.tsx # Today's meal list
β β β βββ LogMealSheet.tsx # Manual meal search & log
β β β βββ ProfileSheet.tsx # Profile editor + Connect Code
β β β βββ MealDetailSheet.tsx # Per-meal nutrient detail
β β β βββ TrendChart.tsx # 7-day calorie trend chart
β β β βββ Recommendations.tsx # Diet recommendations
β β β βββ OnboardingFlow.tsx # New user setup (name/age/sex/weight/height)
β β β βββ PredictionPipeline.tsx # ML pipeline diagram
β β β βββ sections/
β β β βββ RiskEngineSection.tsx
β β β βββ FoodLabSection.tsx
β β β βββ TrendsSection.tsx
β β βββ ui/ # shadcn/ui primitives (40+ components)
β βββ hooks/
β β βββ useAuth.ts # Auth state + register/login/logout/updateProfile
β β βββ useFoodLogs.ts # Food log state + optimistic add/remove
β β βββ useAdmin.ts # Admin actions + nutritionist management
β β βββ usePatients.ts # Nutritionist patient list + assignments
β β βββ useProfile.ts # UserProfile type definition
β β βββ useNutritionistDirectory.ts
β β βββ use-count-up.ts # Number animation hook
β βββ lib/
β β βββ mlApi.ts # predictRisk() β API call + 6s timeout + mock fallback
β β βββ supabase.ts # Supabase client initialisation
β βββ data/
β βββ mock.ts # LogItem/FoodEntry types + FOOD_DATABASE + RISKS config
βββ supabase/
β βββ functions/
β βββ create-nutritionist/
β βββ index.ts # Edge Function β admin-only nutritionist account creation
βββ ML/
βββ nutrisense_training.ipynb # Full training pipeline
βββ outputs/ # ROC curves, SHAP plots, confusion matrices
The notebook is in ML/nutrisense_training.ipynb. Run on Kaggle (GPU T4 or P100 recommended):
- Add the
dansbecker/food-101dataset as input (Food-101) - Add KenyanFood13 dataset for East African classes
- Add your
HF_TOKENas a Kaggle secret - Run end-to-end β trains ViT-B/16 on 114 classes and three GradientBoosting classifiers, then pushes weights to
JeanJabo/nutrisense-food-model
Important: Risk model training requires DHS Rwanda 2019β20 + STEPS Rwanda 2012 microdata. These are restricted-licence WHO/DHS datasets and must not be committed to any public repository or redistributed in any form.
Option 1 β Upload individual files (fast):
python push_to_hf.py --token hf_YOUR_TOKEN_HEREUploads risk_predictor.py, nutrition_db.py, food_predictor.py, and requirements.txt to the Space.
Option 2 β Full git subtree push:
git push space "$(git subtree split --prefix api main)":main --forcePushes the entire api/ directory as the Space's main branch and triggers a rebuild.
Supabase Edge Functions:
npx supabase functions deploy create-nutritionist --project-ref your-project-refCurrent limitations:
- Food classifier covers 114 classes. Rwanda-specific dishes outside Food-101 and KenyanFood13 (e.g. Ibihaza, Umutsima, Isombe) return a low-confidence best guess
- Diabetes model AUROC 0.598 β proxy label used in training (no blood glucose data in DHS/STEPS); output is exploratory only
- STEPS Rwanda data is from 2012; Rwanda's nutritional profile has changed since
- Risk scores are uncalibrated probabilities (capped at 90, not Platt-scaled)
- HuggingFace Spaces free tier sleeps after inactivity β first request has 30β60s cold-start delay
Planned future work:
- Build a Rwanda-specific food image dataset covering dishes outside the current 114 classes
- Audit for data leakage and report 5-fold cross-validation results
- Calibrate probabilities using Platt scaling or isotonic regression
- Add automatic portion-size estimation from photos
- Validate prospectively against clinical biomarkers (haemoglobin, blood glucose)
- Platform complies with Rwanda Law No. 058/2021 on Personal Data Protection
- Training microdata (DHS + STEPS) held under restricted licence β never redistributed
- All connections use HTTPS; passwords are never stored in plain text
- Row-Level Security enforced at the PostgreSQL layer β cross-user data access is structurally impossible
- The platform is not a medical device and does not provide clinical diagnoses
- Full Privacy Policy & Terms of Use available at
/legal
JABO Jean Jacques β BSc Software Engineering, African Leadership University Supervisor: Murairi Dirac Contact: j.jabo@alustudent.com