| --- |
| 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](https://huggingface.co/datasets/openfoodfacts/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 |
|
|
| ```bash |
| 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: |
|
|
| ```bash |
| 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 |
|
|
| ```bash |
| 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](https://opendatacommons.org/licenses/odbl/). The Nutri-Score |
| implementation is ported from |
| [openfoodfacts-server](https://github.com/openfoodfacts/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. |
|
|