Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

122 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Nutrisense-AI

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


The Problem

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.


User Roles

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.


Architecture

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)

Pages & Routes

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)

Installation β€” Frontend

Prerequisites

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

Edit .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

Installation β€” ML API (local)

Prerequisites

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


Database Setup (Supabase)

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.

Edge Functions

Deploy the create-nutritionist Edge Function:

npx supabase functions deploy create-nutritionist --project-ref your-project-ref

This function handles admin-initiated nutritionist account creation using the service role key server-side.


API Reference

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

Food prediction

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

Risk prediction

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.


ML Pipeline

Food Classifier (ViT-B/16)

  • Architecture: vit_base_patch16_224.orig_in21k via timm β€” 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

Risk Models (GradientBoosting Γ— 3)

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

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)

Key Design Decisions

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

Testing

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

Project Structure

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

ML Training

The notebook is in ML/nutrisense_training.ipynb. Run on Kaggle (GPU T4 or P100 recommended):

  1. Add the dansbecker/food-101 dataset as input (Food-101)
  2. Add KenyanFood13 dataset for East African classes
  3. Add your HF_TOKEN as a Kaggle secret
  4. 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.


Deploying API Updates

Option 1 β€” Upload individual files (fast):

python push_to_hf.py --token hf_YOUR_TOKEN_HERE

Uploads 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 --force

Pushes 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-ref

Limitations & Future Work

Current 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:

  1. Build a Rwanda-specific food image dataset covering dishes outside the current 114 classes
  2. Audit for data leakage and report 5-fold cross-validation results
  3. Calibrate probabilities using Platt scaling or isotonic regression
  4. Add automatic portion-size estimation from photos
  5. Validate prospectively against clinical biomarkers (haemoglobin, blood glucose)

Ethics & Privacy

  • 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

Author

JABO Jean Jacques β€” BSc Software Engineering, African Leadership University Supervisor: Murairi Dirac Contact: j.jabo@alustudent.com

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages