Files
lifetrack/docs/research/nutrition-sources.md
T
MeeJayandClaude Opus 5 93f0689c1e Initial import: LifeTrack v1 (santé, vape, finances)
Tracker de vie auto-hébergé : suivi poids/calories/sport avec planning de
pesées, sevrage tabac (vape) avec modèle de coût DIY et économies, et
finances personnelles avec import de relevés bancaires.

Architecture : FastAPI + SQLAlchemy 2.0 + PostgreSQL 16, React 18 + TS +
Vite + Tailwind + ECharts, déploiement Docker Compose. Modules
auto-découverts des deux côtés (pkgutil / import.meta.glob) et framework
de connecteurs à deux voies (importeurs de fichiers + ingestion JSON)
pour brancher de nouvelles sources sans toucher au noyau.

Validé : 292 tests pytest, tsc + vite build, contrat API/web vérifié
contre le schéma OpenAPI, et déploiement Docker réel sur PostgreSQL 16
(28 tables, SPA servie par nginx, wizard de premier démarrage).

Documentation : README.md, docs/GUIDE.md, CONVENTIONS.md, et les
documents de conception et de recherche dans docs/.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 10:48:57 +02:00

299 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Recherche — Sources de données NUTRITION / CALORIES pour LifeTrack
> Document de recherche destiné aux agents d'implémentation. Rédigé le 2026-08-13.
> Langue : prose en français, identifiants de code en anglais (convention projet).
> Périmètre : ingestion des données alimentaires (repas, calories, macros) dans le module Santé/Fitness.
---
## 1. Résumé exécutif
| Source | Accès | Verdict v1 |
|---|---|---|
| Foodvisor — export in-app / RGPD | CSV limité, sur demande, pas d'automatisation | **Importeur CSV "best effort" + procédure RGPD documentée** (import ponctuel d'historique) |
| Foodvisor — API consommateur | **N'existe pas** | Hors périmètre |
| Foodvisor — Vision API (analyse photo) | Produit entreprise, tarif non public, contrat commercial | **Non viable v1** ; garder un port d'extension `photo_analyzer` |
| Foodvisor — via Health Connect | Non confirmé (Foodvisor Android se synchronise à Google Fit, pas à Health Connect de façon documentée) | Chemin indirect **à tester** via Health Sync ; ne pas en dépendre |
| Health Connect `NutritionRecord` (bridge Android) | Complet, structuré, sur l'appareil uniquement (pas d'API cloud) | **OUI — endpoint REST d'ingestion + app/bridge Android** (stratégie cible) |
| Open Food Facts API | Gratuit, ouvert (ODbL), code-barres + recherche | **OUI — pilier de la saisie manuelle assistée** |
| CIQUAL (ANSES) | Téléchargement Excel/XML, licence ouverte | **OUI — table locale d'aliments génériques français** |
| MyFitnessPal / Cronometer / Yazio — exports CSV | Formats variés, semi-documentés | Importeurs CSV **optionnels** (framework générique, mapping par profil) |
| Saisie manuelle rapide | — | **OUI — socle indispensable** |
**Stratégie v1 recommandée** : saisie manuelle rapide + recherche produit Open Food Facts (proxy backend + cache) + table CIQUAL locale pour les aliments génériques + framework d'importeurs CSV (Foodvisor, MyFitnessPal, Cronometer) + endpoint REST `POST /api/v1/ingest/nutrition` consommé par un bridge Health Connect. Un connecteur Foodvisor "direct" reste impossible aujourd'hui faute d'API ; on préserve la possibilité future via l'architecture connecteurs (section 8).
---
## 2. Foodvisor (l'app utilisée par l'utilisateur)
### 2.1 Ce que Foodvisor est (et n'est pas)
Foodvisor (société française, éditeur de l'app `io.foodvisor.foodvisor` sur Android et `id1064020872` sur iOS) est une app grand public de comptage de calories par photo (IA de reconnaissance alimentaire), scan de code-barres et saisie manuelle, avec coaching payant. **Il n'existe aucune API consommateur** (pas d'OAuth, pas d'endpoint "mes repas", pas de webhook). Toute intégration "live" est donc exclue.
### 2.2 Export de données utilisateur (in-app + RGPD)
Constats issus de plusieurs sources (guides Nutrola 2025-2026, politique de confidentialité Foodvisor) :
- **Export intégré** : dans l'app, `Réglages` → section `Compte` → entrée type « Demander mes données » / « Request my data » (l'intitulé et l'emplacement exacts varient selon les versions ; sur le tableau de bord web l'option est sous `Account``Privacy``Data`). L'export est **envoyé par e-mail**, généralement sous 24-72 h (le SLA de la politique de confidentialité autorise jusqu'à 30 jours).
- **Contenu de l'export** (archive ZIP contenant plusieurs CSV, ou CSV/PDF selon les versions) :
- objectifs et réglages en **CSV clé-valeur "à plat"** (objectif calorique, répartition des macros, métriques corporelles, niveau d'activité) ;
- **journal alimentaire** : une ligne par entrée avec horodatage, créneau de repas (petit-déjeuner/déjeuner/dîner/collation), nom de l'aliment, portion, macros calculées (protéines, glucides, lipides) et calories ;
- **totaux quotidiens** calories + macronutriments (parfois limités à une période récente et non à l'historique complet) ;
- **historique de poids** saisi manuellement (date + valeur, une ligne par pesée) ;
- aliments/recettes personnalisés avec leurs macros mais **rarement** la décomposition en ingrédients.
- **Ce qui manque** (confirmé par les guides de migration) : photos et scores de confiance IA, détails des scans code-barres (codes EAN), micronutriments au-delà des macros, horodatages fins au-delà du jour pour les totaux, notes/noms personnalisés, résumés de coaching.
- **Pièges de format observés** : horodatages « généralement ISO 8601 mais parfois en heure locale sans offset » ; encodage UTF-8 (Windows-1252 sur d'anciens exports) ; en-têtes potentiellement en français ou en anglais selon la langue du compte. **L'importeur devra être tolérant** (détection d'encodage, de délimiteur `,`/`;`, et mapping d'en-têtes bilingue).
- **Demande RGPD (article 15, droit d'accès + article 20, portabilité)** : Foodvisor est une société française ; contact **`data@foodvisor.io`** (contact officiel indiqué dans la politique de confidentialité pour l'exercice des droits d'accès, rectification, effacement, portabilité). Délai légal : **1 mois**, prolongeable de 2 mois pour les demandes complexes. Demander explicitement « l'intégralité de mon journal alimentaire au format structuré lisible par machine (CSV ou JSON), incluant les horodatages, portions et valeurs nutritionnelles » — cette voie contourne les limitations du bouton d'export intégré.
**Conséquence pour LifeTrack** : l'import Foodvisor est un **import ponctuel de rattrapage d'historique** (one-shot ou trimestriel), pas un flux continu. L'importeur CSV Foodvisor doit être conçu comme un *profil de mapping* du framework d'import générique (section 7.3), avec un écran de prévisualisation/mapping de colonnes car le format exact n'est **pas documenté officiellement et peut changer**. Ne pas coder en dur des noms de colonnes : livrer un mapping par défaut + éditeur de mapping.
### 2.3 Foodvisor Vision API (produit développeur)
- Produit B2B de Foodvisor : détection d'aliments et calcul nutritionnel à partir d'une photo (items reconnus, calories, macros, estimation des portions). Page produit : `https://www.foodvisor.io/en/vision/` ; documentation : `https://vision.foodvisor.io/docs` (⚠️ lors de nos tests d'août 2026, ce sous-domaine **ne résout plus en DNS** — signe que le produit est peu maintenu ou réservé aux clients sous contrat).
- **Modèle d'accès : contrat commercial entreprise uniquement.** Pas d'inscription self-service, pas de tarif public, pas de free tier, pas de spécification OpenAPI publique ; « les détails d'endpoints et d'authentification sont partagés avec les clients » sous accord. Accès via le formulaire de contact commercial.
- **Verdict** : inutilisable pour un projet personnel auto-hébergé en v1. Si l'analyse de photos de repas devient une exigence, alternatives à évaluer plus tard (non recherchées en détail ici, à vérifier avant usage) : LogMeal API, Passio Nutrition-AI, ou un LLM multimodal généraliste. **Décision d'architecture** : définir côté backend une interface `PhotoAnalyzer` (entrée : image ; sortie : liste de `FoodItemCandidate` avec macros et confiance) pour brancher n'importe quel fournisseur plus tard, y compris Foodvisor Vision si un contrat devenait accessible.
### 2.4 Foodvisor → Health Connect ?
- Sur **iOS**, Foodvisor se synchronise avec **Apple Health** (non pertinent ici, utilisateur Android).
- Sur **Android**, la seule intégration documentée est **Google Fit** (import/export nutrition, exercice, poids vers l'app Google Fit ; des fils de support Google Fit confirment cette synchro et ses ratés). **Aucune mention publique de support Health Connect** par Foodvisor à date (recherches août 2026).
- ⚠️ Les **API Google Fit sont dépréciées** (annonce Google mai 2024, arrêt programmé, migration officielle vers Health Connect). Le chemin `Foodvisor → Google Fit` est donc fragile et sans avenir.
- **Chemin indirect à tester** (effort ~30 min, aucune dépendance de code) : l'app tierce **Health Sync** (`nl.appyhapps.healthsync`, payante ~3-4 €/an) sait recopier des données entre Google Fit et Health Connect pour certains types de données. À tester sur le téléphone de l'utilisateur : si les repas Foodvisor poussés dans Google Fit peuvent être recopiés en `NutritionRecord` Health Connect, alors le bridge Health Connect de LifeTrack (section 3) récupérera les données Foodvisor **sans aucun code spécifique Foodvisor**. Ne pas bloquer la v1 sur ce test ; c'est un bonus.
---
## 3. Health Connect — type de données Nutrition (chemin cible)
### 3.1 Principe et contrainte fondamentale
Health Connect (Android 14+ intégré ; app système) est un magasin de données santé **sur l'appareil uniquement : il n'existe AUCUNE API cloud/REST côté serveur**. Pour amener les données dans LifeTrack, il faut une app Android (le « bridge » déjà prévu au projet) qui lit Health Connect via `androidx.health.connect:connect-client` et pousse vers l'endpoint REST authentifié de LifeTrack.
### 3.2 `NutritionRecord` — champs exacts
Classe : `androidx.health.connect.client.records.NutritionRecord` (miroir plateforme : `android.health.connect.datatypes.NutritionRecord`). Un enregistrement = un repas/une prise alimentaire sur un intervalle de temps.
- **Temporalité** : `startTime`, `endTime` (Instant, UTC), `startZoneOffset`, `endZoneOffset` (nullable).
- **Identité** : `name` (String?, nom du repas/aliment), `mealType` (Int) avec valeurs : `MEAL_TYPE_UNKNOWN = 0`, `MEAL_TYPE_BREAKFAST = 1`, `MEAL_TYPE_LUNCH = 2`, `MEAL_TYPE_DINNER = 3`, `MEAL_TYPE_SNACK = 4`.
- **Énergie** : `energy`, `energyFromFat` — type `Energy` (exposer en kcal via `energy.inKilocalories`).
- **Nutriments** (tous de type `Mass`, à lire en grammes via `inGrams` ; tous nullables) : `protein`, `totalCarbohydrate`, `sugar`, `dietaryFiber`, `totalFat`, `saturatedFat`, `unsaturatedFat`, `monounsaturatedFat`, `polyunsaturatedFat`, `transFat`, `cholesterol`, `sodium`, `potassium`, `calcium`, `iron`, `magnesium`, `phosphorus`, `zinc`, `copper`, `manganese`, `selenium`, `iodine`, `chromium`, `molybdenum`, `chloride`, `caffeine`, `biotin`, `folate`, `folicAcid`, `niacin`, `pantothenicAcid`, `riboflavin`, `thiamin`, `vitaminA`, `vitaminB6`, `vitaminB12`, `vitaminC`, `vitaminD`, `vitaminE`, `vitaminK`.
- ⚠️ Les documentations Google confirment : **tous les nutriments sont en grammes** (y compris sodium — pas en mg — et les vitamines), l'énergie en kcal. Convertir côté bridge ou côté API, mais **stocker en unités SI cohérentes** (voir 7.2).
- **Métadonnées** (`metadata`) : `id` (UUID Health Connect, **clé de déduplication idéale**), `dataOrigin.packageName` (app source, ex. `com.myfitnesspal.android`, permet le champ `source_app`), `lastModifiedTime`, `clientRecordId`/`clientRecordVersion`, `recordingMethod`, `device`.
- **Agrégats disponibles** (API `aggregate`) : `NutritionRecord.ENERGY_TOTAL`, `PROTEIN_TOTAL`, `TOTAL_CARBOHYDRATE_TOTAL`, `TOTAL_FAT_TOTAL`, etc. — utile si le bridge veut pousser des totaux journaliers en plus des repas unitaires. Recommandation : **pousser les enregistrements unitaires** et laisser LifeTrack agréger.
### 3.3 Permissions et limites côté bridge
- Permission de lecture : `android.permission.health.READ_NUTRITION` (+ autres types déjà prévus : steps, calories, distance, poids, exercices).
- **Limite des 30 jours** : par défaut une app ne peut lire que les données antérieures de 30 jours max à la première autorisation. La permission `android.permission.health.READ_HEALTH_DATA_HISTORY` (introduite en 2024) lève cette limite — **à demander dès la v1 du bridge** pour récupérer l'historique complet.
- Lecture incrémentale : utiliser l'API **Changes / changement de jetons** (`getChangesToken` + `getChanges`) pour ne pousser que les deltas ; repli : requête par `TimeRangeFilter` depuis le dernier push réussi.
- Lecture en arrière-plan : permission `READ_HEALTH_DATA_IN_BACKGROUND` + WorkManager périodique (les bridges existants utilisent des périodes de 1-2 h).
### 3.4 Bridges open source réutilisables (au lieu de tout écrire)
- **HCGateway** (`github.com/ShuchirJ/HCGateway`) : bridge REST universel Health Connect ↔ serveur, app Android + serveur auto-hébergeable, sync bidirectionnelle, push toutes les ~2 h. Projet jeune (« API may change without notice ») mais **la partie app Android est une excellente base de code de référence** (lecture de tous les types de records dont nutrition, sérialisation JSON).
- **health-connect-webhook** (`github.com/mcnaveen/health-connect-webhook`) : app Android qui pousse les données Health Connect vers un webhook arbitraire — modèle exactement aligné avec notre endpoint d'ingestion générique.
- **Health Sync** (payant, closed source) : pour la recopie inter-apps sur le téléphone (cf. 2.4), pas pour pousser vers LifeTrack.
**Recommandation** : concevoir l'endpoint d'ingestion LifeTrack (section 7.4) de façon à pouvoir accepter soit notre futur bridge maison, soit un HCGateway/webhook adapté, en gardant un schéma JSON proche du modèle `NutritionRecord`.
---
## 4. Open Food Facts (OFF) — recherche produit + code-barres
Base collaborative mondiale (>3 M produits, très bonne couverture des produits vendus en France). **Gratuit, sans clé d'API.**
### 4.1 Endpoints
- **Produit par code-barres** :
`GET https://world.openfoodfacts.org/api/v2/product/{barcode}` (JSON). Utiliser `https://fr.openfoodfacts.org/...` pour prioriser les libellés français.
Paramètre `fields` pour limiter la réponse, ex. :
`?fields=code,product_name,product_name_fr,brands,quantity,serving_size,serving_quantity,nutriments,nutriscore_grade,nova_group,ecoscore_grade,image_front_small_url,categories_tags,lang`
- **API v3** (courante, recommandée pour les nouvelles intégrations) : `GET /api/v3/product/{barcode}` — v2 reste supportée ; v0/v1 à éviter.
- **Recherche plein texte** : ⚠️ pas disponible dans l'API v2 serveur. Deux options :
- **Search-a-licious** (service de recherche officiel) : `https://search.openfoodfacts.org` (API documentée sur ce domaine, `GET /search?q=...&langs=fr&page_size=20`) — recommandé ;
- legacy : `GET https://fr.openfoodfacts.org/cgi/search.pl?search_terms=...&json=1` (lent, déprécié mais fonctionnel).
- **Recherche filtrée v2** (par catégorie/marque/nutriment, sans plein texte) : `GET /api/v2/search?categories_tags=...&fields=...`.
- **Environnement de staging** : `https://world.openfoodfacts.net` (HTTP Basic `off`/`off`) — utiliser pour les tests d'intégration automatisés.
- **SDK Python officiel** : paquet PyPI `openfoodfacts` (`openfoodfacts-python`) — utilisable directement dans l'API FastAPI, mais un simple client `httpx` suffit.
### 4.2 Champs `nutriments` utiles (clés JSON exactes)
Par 100 g : `energy-kcal_100g`, `energy_100g` (kJ), `proteins_100g`, `carbohydrates_100g`, `sugars_100g`, `fat_100g`, `saturated-fat_100g`, `fiber_100g`, `salt_100g`, `sodium_100g` (en g). Variantes par portion : suffixe `_serving` (ex. `energy-kcal_serving`) + `serving_size` (texte, ex. "30 g") et `serving_quantity` (nombre). Les champs peuvent être **absents ou incohérents** (données collaboratives) : toujours valider (`energy-kcal_100g` manquant → recalculer depuis kJ ÷ 4,184 ; contrôler que macros × facteurs Atwater ≈ énergie à ±20 %).
### 4.3 Règles d'usage impératives
- **User-Agent personnalisé obligatoire** : format `AppName/Version (ContactEmail)`, ex. `LifeTrack/1.0 (meejayproduction@gmail.com)` — sous peine d'être traité comme bot.
- **Rate limits (docs officielles, août 2026)** : **15 req/min/IP** pour les lectures produit, **10 req/min/IP** pour les recherches. Conséquences d'implémentation : (a) toutes les requêtes OFF passent par le **backend LifeTrack (proxy)**, jamais depuis le navigateur ; (b) **cache local** de chaque produit consulté (table `food_items`, cf. 7.2) — un produit scanné une fois ne re-sollicite plus OFF ; (c) throttle côté serveur (token bucket 10/min) + retry avec backoff sur 429.
- **Pas de scraping massif via l'API** : pour un besoin hors-ligne complet, utiliser les **exports intégraux** (CSV ~ plusieurs Go, JSONL, dump MongoDB, Parquet sur Hugging Face) — non nécessaire en v1.
- **Licence** : base sous **ODbL** (contenus sous DbCL, images CC-BY-SA). Pour un usage personnel auto-hébergé : afficher une attribution « Données produits : Open Food Facts (ODbL) » dans l'UI suffit largement ; l'obligation de partage à l'identique ne s'applique que si l'on redistribue une base dérivée.
---
## 5. CIQUAL (ANSES) — table de composition française (aliments génériques)
Complément indispensable d'OFF : OFF couvre les **produits industriels code-barrés**, CIQUAL couvre les **aliments génériques** (« Pomme, crue », « Baguette courante », « Poulet rôti, cuisse ») — exactement ce qu'il faut pour la saisie manuelle de plats maison.
- **Éditeur** : ANSES (Agence nationale de sécurité sanitaire de l'alimentation). Site de consultation : `https://ciqual.anses.fr`.
- **Versions** : table **Ciqual 2020** (~3 185 aliments, ~60 constituants) ; une **table Ciqual 2025** a été publiée fin 2025 (~3 484 aliments, sucres individuels détaillés, fruits/légumes mis à jour incl. outre-mer). Prendre la 2025 si disponible au moment de l'implémentation, sinon 2020.
- **Téléchargement** : formats **Excel (.xls/.xlsx) et XML**, depuis `ciqual.anses.fr` (rubrique téléchargement) et sur **data.gouv.fr** : jeu de données « Table de composition nutritionnelle des aliments Ciqual » (`https://www.data.gouv.fr/datasets/table-de-composition-nutritionnelle-des-aliments-ciqual-2020`). La table 2025 est aussi déposée sur `entrepot.recherche.data.gouv.fr` (DOI `10.57745/RDMHWY`). Documentation PDF officielle du format Excel disponible sur le site Ciqual.
- **Licence** : open data (Licence Ouverte / Etalab 2.0 via data.gouv.fr) — usage libre avec mention de la source « ANSES — Table Ciqual ».
- **Structure du fichier Excel (à connaître pour l'importeur)** :
- une ligne par aliment : `alim_code` (code numérique stable), `alim_nom_fr` (libellé français), `alim_nom_eng`, groupes/sous-groupes (`alim_grp_code`, `alim_grp_nom_fr`, `alim_ssgrp_...`) ;
- une colonne par constituant, valeurs **pour 100 g**, libellés du type « Energie, Règlement UE N° 1169/2011 (kcal/100 g) », « Protéines, N x facteur de Jones (g/100 g) », « Glucides (g/100 g) », « Lipides (g/100 g) », « Sucres (g/100 g) », « Fibres alimentaires (g/100 g) », « Sel chlorure de sodium (g/100 g) », vitamines/minéraux ;
- **pièges de parsing** : séparateur décimal **virgule** ; valeurs non numériques `"-"` (non déterminé), `"traces"`, et bornes `"< 0,5"` — l'importeur doit normaliser (`traces` → 0, `< x` → x/2 ou 0 selon règle choisie, `-` → NULL) ; encodage à vérifier (exports historiques en Windows-1252/latin-1).
- **Intégration recommandée** : script one-shot `scripts/import_ciqual.py` qui charge le fichier Excel/XML dans la table `food_items` avec `source='ciqual'`, `source_id=alim_code`. Le fichier CIQUAL est mis dans un volume/dossier `data/` (préchargé dans l'image ou téléchargé au premier lancement par l'admin) — pas d'appel réseau au runtime.
---
## 6. Exports CSV des autres apps de suivi alimentaire
Objectif : le framework d'import CSV doit accepter les exports des apps majeures, pour migration d'historique ou si l'utilisateur change d'app un jour.
### 6.1 MyFitnessPal
- **Export "File Export" (Premium uniquement)** : Web → `Reports`/`Settings` → export par plage de dates ; production asynchrone (minutes à heures), lien envoyé par e-mail ; **3 CSV : Nutrition, Exercise, Progress**. Le CSV Nutrition est à la granularité **jour × repas** (Breakfast/Lunch/Dinner/Snacks), avec colonnes type : `Date`, `Meal`, `Calories`, `Fat (g)`, `Saturated Fat`, `Polyunsaturated Fat`, `Monounsaturated Fat`, `Trans Fat`, `Cholesterol`, `Sodium (mg)`, `Potassium`, `Carbohydrates (g)`, `Fiber`, `Sugar`, `Protein (g)`, `Vitamin A`, `Vitamin C`, `Calcium`, `Iron`, `Note` (⚠️ liste constatée sur des exports réels et outils communautaires, non documentée officiellement — **valider sur un échantillon réel** avant de figer le mapping ; d'où l'éditeur de mapping, 7.3).
- **Compte gratuit** : demande de téléchargement des données personnelles (RGPD/CCPA) via les réglages du compte — archive moins structurée.
- Il existe une lib Python non officielle `python-myfitnesspal` (scraping avec login) — fragile, **hors périmètre v1**.
### 6.2 Cronometer
- Export self-service (Web → `More`/`Account``Export Data`, plage de dates), **immédiat et gratuit**, en 5 fichiers CSV : `servings.csv` (une ligne par aliment consommé : `Day`, `Group` (repas), `Food Name`, `Amount`, puis ~70 colonnes de nutriments avec unités dans l'en-tête, ex. `Energy (kcal)`, `Protein (g)`, `Sodium (mg)`), `dailysummary.csv` (totaux/jour), `exercises.csv`, `biometrics.csv` (poids !), `notes.csv`. Le plus propre et le plus riche des trois — profil de mapping facile.
### 6.3 Yazio
- **Pas d'export CSV natif** : l'app ne propose qu'un PDF limité ; l'archive complète s'obtient par **demande RGPD** (siège à Erfurt, Allemagne ; réponse sous 1 mois ; en pratique ZIP de JSON/CSV).
- Outils open source non officiels : `github.com/aleksandr-bogdanov/yazio-exporter` (Python ; login e-mail/mot de passe sur l'API non officielle de Yazio, export JSON/CSV/SQLite : journal, aliments consommés, poids, exercices, eau, 40+ micronutriments) et `github.com/tobintax/yazio-csv-exporter`. Utilisables ponctuellement par l'utilisateur **en dehors** de LifeTrack ; LifeTrack se contente d'importer les CSV produits.
### 6.4 Foodvisor (rappel)
Voir 2.2 : ZIP de CSV (journal par entrée, totaux quotidiens, poids, réglages clé-valeur). Profil d'import dédié, tolérant (encodage, délimiteur, en-têtes FR/EN, horodatages sans offset → interpréter en Europe/Paris puis convertir en UTC).
---
## 7. Stratégie d'ingestion nutrition v1 — recommandations concrètes
### 7.1 Les quatre canaux v1 (ordre de priorité d'implémentation)
1. **Saisie manuelle rapide** (UI français) : formulaire "repas" minimal — date/heure (défaut : maintenant, TZ Europe/Paris), type de repas (petit-déjeuner/déjeuner/dîner/collation), soit saisie libre kcal+macros, soit sélection d'un aliment (recherche locale `food_items` + OFF + CIQUAL) × quantité. Duplication d'un repas précédent ("répéter hier") = fonctionnalité à fort ROI.
2. **Recherche produit** : endpoint backend `GET /api/v1/foods/search?q=...` qui interroge (a) le cache local `food_items`, (b) CIQUAL local, (c) Open Food Facts (Search-a-licious + fallback code-barres direct), fusionne et met en cache. Scan de code-barres possible plus tard côté PWA (caméra) → `GET /api/v1/foods/barcode/{ean}`.
3. **Importeurs CSV** via le framework d'import générique du projet : profils `foodvisor`, `myfitnesspal`, `cronometer` (+ `generic_nutrition_csv` avec mapping manuel). Pipeline : upload → détection encodage/délimiteur → proposition de mapping (profil) → prévisualisation → validation → insertion idempotente.
4. **Endpoint d'ingestion REST** pour le bridge Health Connect (voir 7.4) — même endpoint générique que pour les pas/poids, avec le type `nutrition`.
### 7.2 Modèle de données proposé (SQLAlchemy, PostgreSQL)
Deux tables, séparant **référentiel d'aliments** et **journal** :
```text
food_items # referential/cache of foods
id UUID PK
source TEXT NOT NULL -- 'off' | 'ciqual' | 'custom'
source_id TEXT -- OFF barcode | ciqual alim_code | NULL
name TEXT NOT NULL -- French label preferred
brand TEXT
energy_kcal_100g NUMERIC
protein_g_100g NUMERIC
carbs_g_100g NUMERIC
sugar_g_100g NUMERIC
fat_g_100g NUMERIC
sat_fat_g_100g NUMERIC
fiber_g_100g NUMERIC
salt_g_100g NUMERIC -- salt = sodium * 2.5
serving_size_g NUMERIC
raw_payload JSONB -- full OFF/CIQUAL record
UNIQUE (source, source_id)
nutrition_entries # the journal (one row = one food eaten OR one whole meal)
id UUID PK
user_id UUID FK
eaten_at TIMESTAMPTZ NOT NULL -- stored UTC
meal_type TEXT NOT NULL -- 'breakfast'|'lunch'|'dinner'|'snack'|'unknown'
label TEXT -- free-text name
food_item_id UUID FK NULL -- when picked from referential
quantity_g NUMERIC NULL
energy_kcal NUMERIC NOT NULL -- always denormalized at entry level
protein_g NUMERIC
carbs_g NUMERIC
sugar_g NUMERIC
fat_g NUMERIC
sat_fat_g NUMERIC
fiber_g NUMERIC
sodium_mg NUMERIC
source TEXT NOT NULL -- 'manual'|'import:foodvisor'|'import:mfp'|'import:cronometer'|'healthconnect'
source_record_id TEXT -- HC metadata.id, or import row hash
source_app TEXT -- HC dataOrigin.packageName
import_batch_id UUID FK NULL
UNIQUE (user_id, source, source_record_id)
```
- Unités stockées : **kcal, grammes, sodium en mg** (convention affichage FR) — conversions faites à l'ingestion (Health Connect fournit tout en g/kcal, cf. 3.2).
- Le bilan énergétique quotidien (kcal in) = `SUM(energy_kcal)` groupé par jour **en Europe/Paris** (conversion au moment de la requête, pas au stockage).
- **Déduplication inter-sources** : contrainte unique `(user_id, source, source_record_id)` pour l'idempotence intra-source ; pour l'inter-sources (ex. un repas présent à la fois dans le CSV Foodvisor et via Health Connect), règle v1 simple : l'UI signale les jours où deux sources se chevauchent (>1 source sur la même journée) et propose de masquer une source par plage de dates (`source_priority` par jour). Ne pas tenter de matching flou par entrée en v1.
### 7.3 Framework d'import CSV — exigences pour la nutrition
- Détection : BOM/UTF-8/Windows-1252, délimiteur `,`/`;`/tab, format de date (`YYYY-MM-DD`, `DD/MM/YYYY`, ISO 8601 avec/sans offset — sans offset ⇒ supposer Europe/Paris).
- Profils = fichiers de mapping déclaratifs (JSON/YAML versionnés dans le repo) : `column → field`, transformations (`comma_decimal`, `meal_type_map` FR/EN : `Petit-déjeuner→breakfast`, etc.), granularité (`per_food_row` pour Cronometer `servings.csv`, `per_meal_row` pour MFP, `per_entry_row` pour Foodvisor).
- Idempotence : `source_record_id = SHA-256(user_id + source + date + meal + label + kcal)` en absence d'ID natif ; ré-import du même fichier = 0 doublon.
- Chaque import crée un `import_batch` (annulable en bloc — indispensable vu l'instabilité des formats).
### 7.4 Endpoint d'ingestion Health Connect (bridge)
`POST /api/v1/ingest/nutrition` (JWT device token, scope `ingest`) — schéma aligné sur `NutritionRecord` :
```json
{
"records": [
{
"source_record_id": "hc-uuid-from-metadata-id",
"source_app": "io.foodvisor.foodvisor",
"start_time": "2026-08-13T11:45:00Z",
"end_time": "2026-08-13T12:15:00Z",
"meal_type": "lunch",
"name": "Salade poulet",
"energy_kcal": 620.0,
"protein_g": 42.1,
"total_carbohydrate_g": 51.0,
"sugar_g": 8.2,
"total_fat_g": 24.3,
"saturated_fat_g": 6.1,
"dietary_fiber_g": 7.0,
"sodium_g": 1.1
}
]
}
```
Réponse : `{"accepted": n, "duplicates": m, "rejected": [...]}` — upsert sur `(user_id, 'healthconnect', source_record_id)`. Le même pattern (`/ingest/steps`, `/ingest/weight`, ...) sert tout le bridge Health Connect. Côté bridge : demander `READ_NUTRITION` + `READ_HEALTH_DATA_HISTORY`, sync incrémentale par Changes API, batch de 100-500 records.
### 7.5 Garder un futur connecteur Foodvisor possible
1. **Architecture connecteurs** : chaque source = classe `Connector` (métadonnées, capacités `file_import`/`api_pull`/`push`, mapping). `FoodvisorCsvConnector` (v1, `file_import`) et un éventuel `FoodvisorApiConnector` (si une API apparaît) partagent le même normaliseur `FoodvisorRecordNormalizer`.
2. **Interface `PhotoAnalyzer`** (cf. 2.3) : port d'extension pour Foodvisor Vision ou tout autre service d'analyse de photo.
3. **Chemin sans code** : si le test Health Sync (2.4) réussit, les données Foodvisor arrivent déjà par le canal Health Connect avec `source_app` identifiable.
4. **Veille** : re-vérifier périodiquement (a) l'apparition d'un support Health Connect dans l'app Foodvisor Android (fiche Play Store, permissions `android.permission.health.WRITE_NUTRITION`), (b) la résurrection de `vision.foodvisor.io`.
---
## 8. Checklist pour les agents d'implémentation
- [ ] Table `food_items` + import CIQUAL (script one-shot, gestion `traces`/`< x`/virgule décimale).
- [ ] Proxy OFF : client httpx avec `User-Agent: LifeTrack/1.0 (meejayproduction@gmail.com)`, cache DB, throttle 10 req/min, staging `.net` pour les tests.
- [ ] `GET /foods/search`, `GET /foods/barcode/{ean}` ; UI de saisie manuelle FR (types de repas : Petit-déjeuner, Déjeuner, Dîner, Collation).
- [ ] Framework import CSV + profils `cronometer` (le plus simple, commencer par lui), `myfitnesspal`, `foodvisor`, `generic_nutrition_csv` ; éditeur de mapping ; batches annulables.
- [ ] `POST /api/v1/ingest/nutrition` idempotent (+ doc French pour configurer HCGateway/webhook en attendant le bridge maison).
- [ ] Attributions UI : « Open Food Facts (ODbL) », « ANSES — Table Ciqual ».
- [ ] Doc utilisateur FR : procédure d'export Foodvisor in-app + modèle d'e-mail RGPD à `data@foodvisor.io`.
## 9. Sources
- Guide export Foodvisor (FR) : https://nutrola.app/fr/blog/how-to-export-data-from-foodvisor ; migration : https://nutrola.app/en/blog/migrating-from-foodvisor-how-to-import-data
- Politique de confidentialité Foodvisor : https://www.foodvisor.io/en/privacy-policy/ ; support privacy : https://foodvisor.zendesk.com/hc/en-us/categories/360002566060-Privacy
- Foodvisor Vision API : https://www.foodvisor.io/en/vision/ ; https://vision.foodvisor.io/docs (DNS KO août 2026) ; https://github.com/api-evangelist/foodvisor ; https://apis.io/providers/foodvisor/
- Foodvisor ↔ Google Fit (fil support) : https://support.google.com/fit/thread/327566968
- Health Connect `NutritionRecord` : https://developer.android.com/reference/androidx/health/connect/client/records/NutritionRecord ; https://developer.android.com/reference/android/health/connect/datatypes/NutritionRecord ; nutrition data types : https://developers.google.com/health/data-types/nutrition ; écriture : https://developer.android.com/health-and-fitness/health-connect/write-data
- Bridges : https://github.com/ShuchirJ/HCGateway ; https://github.com/mcnaveen/health-connect-webhook ; https://healthsync.app/
- Open Food Facts API : https://openfoodfacts.github.io/openfoodfacts-server/api/ ; recherche : https://search.openfoodfacts.org
- CIQUAL : https://ciqual.anses.fr ; https://www.data.gouv.fr/datasets/table-de-composition-nutritionnelle-des-aliments-ciqual-2020 ; doc table 2025 : https://ciqual.anses.fr/cms/sites/default/files/inline-files/Table%20Ciqual%202025%20doc%20FR_2025_11_19.pdf ; dépôt 2025 : https://entrepot.recherche.data.gouv.fr/dataset.xhtml?persistentId=doi:10.57745/RDMHWY
- MyFitnessPal export : https://support.myfitnesspal.com/hc/en-us/articles/360032273352-Data-Export-FAQs ; https://quantifiedself.com/blog/access-export-myfitnesspal-data/
- Cronometer export : https://support.cronometer.com/hc/en-us/articles/360018760151-Account-Settings ; forums : https://forums.cronometer.com/discussion/460/exporting-data
- Yazio : https://nutrola.app/en/blog/how-to-export-data-from-yazio ; https://github.com/aleksandr-bogdanov/yazio-exporter ; https://github.com/tobintax/yazio-csv-exporter