NutriWeb / README.md
Krish264's picture
Deploy: Pin Streamlit to 1.61.1; add deployment parity tests
89d9642
|
Raw
History Blame Contribute Delete
5.98 kB
metadata
title: NutriWeb
emoji: πŸ₯—
colorFrom: green
colorTo: blue
sdk: streamlit
sdk_version: 1.61.1
app_file: app.py
pinned: false
license: agpl-3.0

NutriWeb

Find a packaged food, and get a healthier alternative of the same kind that respects your allergens, your diet and your health profile.

Built on the full Open Food Facts product database (4.66M products), filtered to 796,542 US products.


Why this was rebuilt

The previous version ran on a hand-cleaned CSV subset of unknown provenance, downloaded from Google Drive at startup. Its recommendations weren't trustworthy, and the data turned out to be only one of three causes:

Problem Old approach Now
Coverage A static CSV subset, no lineage 796,542 US products from the live upstream dataset, rebuildable in one command
Ingredient / allergen logic ~90 hardcoded substrings matched with if key in name, so "sugar" fired on "sugar-free" Exact matching on Open Food Facts' canonical allergens_tags, additives_tags, ingredients_analysis_tags
Additive risk A hand-written opinion list EFSA overexposure ratings and the ANSES watch list, via the OFF additives taxonomy
Retrieval FAISS index rebuilt on every query, with a positional slice applied to a filtered frame that misaligned scores against products Category-constrained SQL candidate generation, ranked in NumPy
Relevance No category constraint β€” a soda could return a candy bar Candidates must share a category tag; regression-tested
Blood-pressure rule sodium_100g > 5.0 β€” five grams per 100 g, a level no food reaches, so it never fired FDA-derived per-100g ceilings for salt and saturated fat
Passwords Stored and compared in plaintext bcrypt via passlib
Dependencies torch + transformers + faiss β‰ˆ 2 GB None of them; ~40 MB of pure Python and DuckDB

What it does

Health score (0–100) β€” the single quantity used for ranking: 70% Nutri-Score 2023 + 30% NOVA processing group, minus a capped penalty for additives flagged by EFSA or ANSES.

Nutri-Score, computed where Open Food Facts has none. Only 44% of US products carry an OFF grade, because Nutri-Score is a European scheme and OFF usually cannot assign one without a category. NutriWeb ports the official 2023 algorithm from OFF's own reference implementation and computes the rest:

products share
Published by Open Food Facts 352,295 44.2%
Computed by NutriWeb 290,908 36.5%
Total graded 643,203 80.7%

Validated against OFF's own grades on 317,265 products where both exist: 98.37% exact agreement, 99.73% within one letter, 95.99% exact numeric score.

Grades are never silently merged β€” the UI labels every grade with its source, and grades computed for products with no category are marked low confidence.

Recommendations β€” three stages:

  1. Candidates β€” products sharing a category tag, choosing the most specific tag whose pool clears a minimum size. Falls back to ingredient-tag overlap for the 49% of US products with no category.
  2. Hard filters β€” allergens (including traces), diet, and health-condition ceilings, applied in SQL before the limit so the pool is filtered, not truncated. These exclude; they are never traded off against a good score.
  3. Ranking β€” 45% health gain, 30% macro similarity, 20% ingredient overlap, 5% popularity. Every component is shown to the user.

Running it

python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

# Build the catalog (one-off; ~7.2 GB download, then about a minute)
python pipeline/01_download.py      # fetch food.parquet from the Hub
python pipeline/02_curate.py        # filter, flatten, pivot -> DuckDB
python pipeline/03_score.py         # Nutri-Score + health score + validation
python pipeline/04_audit.py         # coverage report β€” read this

streamlit run app.py

01_download.py needs a Hugging Face token (hf auth login, or HF_TOKEN): anonymous requests get rate-limited on a file that size.

Deploying to Spaces

Publish the built catalog so the Space doesn't rebuild from 7.2 GB on cold start:

python pipeline/05_publish.py <your-username>/nutriweb-us-catalog

Then set on the Space:

  • NUTRIWEB_CATALOG_REPO = <your-username>/nutriweb-us-catalog
  • MONGODB_URI (optional) β€” as a repository secret, never in a file. Without it the app runs with session-only profiles.

Layout

app.py                  entry point; st.navigation
views/                  search Β· product Β· recommend Β· compare Β· profile Β· insights
components/             badges, cards, nutrient tables
nutriweb/
  data/catalog.py       DuckDB access, FTS search
  scoring/              nutriscore.py (2023 algorithm) Β· health_score.py Β· additives.py
  reco/                 engine.py Β· filters.py Β· similarity.py
  profile/              model.py Β· auth.py
pipeline/               01_download β†’ 02_curate β†’ 03_score β†’ 04_audit β†’ 05_publish
tests/                  64 tests: algorithm, filters, engine

Tests

pytest

Covers Nutri-Score variant selection and grade boundaries, the safety filters (an allergen must never reach a result), and engine guarantees β€” every recommendation is strictly healthier, shares the source category, and a soda never returns confectionery.

Data and licence

Product data Β© Open Food Facts contributors, licensed ODbL. The Nutri-Score implementation is ported from openfoodfacts-server (lib/ProductOpener/Nutriscore.pm), AGPL-3.0. This project is AGPL-3.0.

NutriWeb is an informational tool, not medical or dietary advice.