Première exécution réelle de la stack (build des images, PostgreSQL 16, parcours fonctionnels en HTTP) : 28 tables, 105 index, extension pg_trgm, et 181 assertions rejouées après correction. Santé : filtres enum invalides renvoyaient 500 au lieu d'une erreur française ; plan_line s'arrêtait à la fin de la fenêtre du graphique au lieu de la date d'atteinte de l'objectif ; deficit_target_kcal était recalculé après le plancher calorique ; « dernière pesée » affichait deux valeurs différentes selon l'endpoint ; objectif protéines absent. Vape : durée de vie moyenne des résistances incluait la résistance en cours ; archiver la recette active la laissait active ; coût théorique inventé avant la date d'arrêt ; économies projetées dans le futur ; €/ml arrondi à 2 décimales écrasait le modèle de coût DIY. Finances : le sankey compensait crédits et débits non catégorisés ; rows_total excluait les lignes filtrées, faussant l'arithmétique du rapport d'import. Socle : les erreurs HTTP du framework fuitaient en anglais dans l'enveloppe française ; nginx renvoyait sa page 413 HTML au lieu du JSON français ; fins de ligne normalisées en LF. 348 tests pytest (+7), ruff, tsc et vite build au vert. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
601 lines
27 KiB
Markdown
601 lines
27 KiB
Markdown
# LifeTrack
|
||
|
||
> Tracker de vie **auto-hébergé** : poids et calories, sevrage tabagique (vape) et finances
|
||
> personnelles — le tout dans une interface web sombre et riche en graphiques.
|
||
|
||
Vos données restent chez vous, dans votre PostgreSQL. Aucun compte tiers, aucune télémétrie,
|
||
aucune ressource chargée depuis Internet au moment de l'affichage.
|
||
|
||
Une seule dépendance réseau existe côté serveur, et elle est facultative : la recherche
|
||
d'aliments interroge **Open Food Facts** (base publique, sans clé d'API) et met chaque
|
||
produit consulté en cache local. Tout le reste fonctionne hors ligne.
|
||
|
||
---
|
||
|
||
## Sommaire
|
||
|
||
- [À quoi sert LifeTrack](#à-quoi-sert-lifetrack)
|
||
- [Fonctionnalités par module](#fonctionnalités-par-module)
|
||
- [Captures d'écran](#captures-décran)
|
||
- [Démarrage rapide (Docker Compose)](#démarrage-rapide-docker-compose)
|
||
- [Premier démarrage](#premier-démarrage)
|
||
- [Mode développement](#mode-développement)
|
||
- [Architecture](#architecture)
|
||
- [Stack technique](#stack-technique)
|
||
- [Feuille de route et écarts connus](#feuille-de-route-et-écarts-connus)
|
||
- [Documentation](#documentation)
|
||
- [Licence](#licence)
|
||
|
||
---
|
||
|
||
## À quoi sert LifeTrack
|
||
|
||
Trois besoins réunis dans une seule application, parce qu'ils s'alimentent mutuellement :
|
||
|
||
1. **Perdre du poids sans se tromper de chiffres.** LifeTrack calcule votre métabolisme de
|
||
base, votre dépense réelle et votre budget calorique quotidien, lisse vos pesées en une
|
||
tendance, et projette la date d'atteinte de votre objectif.
|
||
2. **Tenir l'arrêt de la cigarette.** Le module vape chiffre au centime près ce que vous
|
||
dépensez en e-liquide et en résistances, et le compare à ce que le tabac vous coûterait
|
||
aujourd'hui.
|
||
3. **Savoir où part l'argent.** Les relevés bancaires s'importent en quelques clics,
|
||
se catégorisent tout seuls et alimentent budgets, récurrents et graphiques.
|
||
|
||
L'application est **mono-utilisateur en pratique** : le premier compte créé est le vôtre,
|
||
il n'y a pas d'inscription publique.
|
||
|
||
---
|
||
|
||
## Fonctionnalités par module
|
||
|
||
L'interface est découpée en modules auto-découverts. Voici ce qui existe réellement
|
||
aujourd'hui.
|
||
|
||
### Tableau de bord (`/`)
|
||
|
||
- Carte « Aujourd'hui » : ce qui est planifié le jour même (pesée, séance, journal
|
||
alimentaire), fait ou non, avec les séries en cours.
|
||
- Ajout rapide d'une pesée sans quitter la page.
|
||
- Widgets KPI et graphiques de synthèse tirés des modules installés.
|
||
|
||
### Santé (`/sante/...`)
|
||
|
||
| Page | Contenu |
|
||
|---|---|
|
||
| **Poids & Objectif** (`/sante/poids`) | Historique des pesées, tendance lissée (EMA), projection de la date d'atteinte, mensurations, calendrier d'assiduité |
|
||
| **Nutrition** (`/sante/nutrition`) | Journal alimentaire par repas, recherche d'aliments, favoris, aliments récents, suivi de l'hydratation |
|
||
| **Activité & Sport** (`/sante/activite`) | Activité quotidienne (pas, distance, calories, minutes actives, étages), séances de sport |
|
||
| **Balance énergétique** (`/sante/balance`) | Calories ingérées contre calories dépensées, déficit cumulé, calibration adaptative du TDEE |
|
||
|
||
Détails utiles :
|
||
|
||
- **Profil corporel** (taille, sexe, date de naissance, niveau d'activité) → BMR par la
|
||
formule **Mifflin-St Jeor**, puis TDEE.
|
||
- **Objectif de poids** en trois modes : rythme hebdomadaire, date cible, ou maintien.
|
||
Budget calorique = TDEE lissé sur 7 jours − (rythme × 7 700 kcal ⁄ 7), avec un plancher
|
||
de sécurité.
|
||
- **Planning de suivi** : vous choisissez les jours de la semaine où vous prévoyez de vous
|
||
peser (`weigh_in`), de faire une séance (`workout`) et de tenir votre journal alimentaire
|
||
(`food_log`). LifeTrack en déduit assiduité, jours manqués et séries — sans case à cocher :
|
||
l'habitude est considérée faite dès qu'une donnée existe pour ce jour.
|
||
- **Recherche d'aliments** : cache local d'abord, puis **Open Food Facts** (recherche
|
||
plein texte et code-barres) via un proxy côté serveur. Chaque produit consulté est mis en
|
||
cache et n'est plus jamais redemandé.
|
||
- **Composition corporelle** : IMC, et estimation de masse grasse par la méthode US Navy
|
||
quand les mensurations nécessaires sont saisies.
|
||
|
||
### Vape / sevrage (`/vape`)
|
||
|
||
| Onglet | Contenu |
|
||
|---|---|
|
||
| **Consommation** | Recharges quotidiennes en ml, nicotine consommée, tendance, équivalent cigarettes |
|
||
| **Coûts & modèle** | Catalogue de produits (base, boosters, arômes, résistances), recettes DIY, coût de revient au ml, achats réels |
|
||
| **Résistances** | Changement de résistance en un clic, durée de vie moyenne, volume passé par résistance |
|
||
| **Économies** | Économies cumulées face à la référence tabac, cigarettes évitées, temps de vie récupéré, jalons santé |
|
||
|
||
- **Référence tabac gelée** : date d'arrêt, cigarettes par jour avant l'arrêt, cigarettes par
|
||
paquet, prix du paquet. C'est l'ancre de tout le calcul d'économies.
|
||
- **Modèle de coût DIY** : chaque recette additionne `quantité × (prix du flacon ⁄ contenance)`
|
||
pour donner un coût au ml, auquel s'ajoute l'amortissement de la résistance.
|
||
- **Assistant de recette** : à partir d'un volume total, d'un taux de nicotine visé et d'un
|
||
booster, LifeTrack calcule les millilitres de booster, d'arôme et de base.
|
||
- **Jalons santé** : 12 étapes chronométrées depuis la date d'arrêt (20 minutes, 8 heures,
|
||
24 heures, 48 heures, 72 heures, 2 semaines, 3 mois, 9 mois, 1 an, 5 ans, 10 ans, 15 ans).
|
||
|
||
### Finances (`/finances`)
|
||
|
||
| Page | Contenu |
|
||
|---|---|
|
||
| **Aperçu** (`/finances`) | Flux de trésorerie, dépenses par catégorie, diagramme Sankey, principaux marchands |
|
||
| **Transactions** (`/finances/transactions`) | Liste filtrable, catégorisation unitaire ou en masse, saisie manuelle |
|
||
| **Budgets** (`/finances/budgets`) | Budget mensuel par catégorie, avancement, projection de fin de mois |
|
||
| **Récurrents** (`/finances/recurrents`) | Abonnements et prélèvements détectés automatiquement, prochaine échéance estimée |
|
||
| **Comptes** (`/finances/comptes`) | Comptes courants, épargne, PayPal, espèces |
|
||
| **Catégories** (`/finances/categories`) | Arborescence de catégories (un jeu français complet est créé automatiquement) |
|
||
|
||
- **Import de relevés** avec choix du compte, choix du profil bancaire, **aperçu avant
|
||
écriture** et **annulation d'un lot** en un clic.
|
||
- **Presets de banques intégrés** : CSV générique, BoursoBank / Boursorama, Crédit Agricole,
|
||
BNP Paribas, Société Générale, La Banque Postale, Caisse d'Épargne, Fortuneo, Revolut, N26,
|
||
OFX (toutes banques) et PayPal.
|
||
- **Déduplication** systématique : ré-importer le même fichier n'ajoute rien deux fois.
|
||
- **Moteur de règles** de catégorisation (libellé contenant, expression régulière, sens,
|
||
fourchette de montant, compte), avec priorité, aperçu et application en masse. Une
|
||
catégorisation manuelle n'est jamais écrasée.
|
||
- **Virements internes** : détection automatique des paires de transactions qui s'annulent
|
||
entre deux de vos comptes, pour ne pas les compter comme dépense.
|
||
|
||
### Imports et connecteurs (`/imports`)
|
||
|
||
- Assistant en trois étapes : **Fichier → Vérification → Import**. Le format est détecté à
|
||
partir de l'en-tête du fichier ; pour un relevé bancaire, le profil de banque est
|
||
présélectionné à partir de ce format et reste modifiable avant l'aperçu.
|
||
- Éditeur de mappage de colonnes (séparateur, encodage, format de date, colonnes) enregistré
|
||
dans un profil personnel.
|
||
- **Formats reconnus** : relevé bancaire CSV, relevé bancaire OFX, PayPal CSV,
|
||
Foodvisor CSV, Health Sync / Health Connect CSV, historique de poids CSV (date + poids).
|
||
- **Historique des imports** avec statistiques par lot et **annulation (rollback)** :
|
||
supprimer un lot supprime les lignes qu'il avait créées.
|
||
- Taille maximale d'un fichier : **20 Mio**.
|
||
- Carte « Connecteurs & API » : URL d'ingestion à coller dans une passerelle Android, lien
|
||
vers les clés d'appareil, et procédure d'export Foodvisor.
|
||
|
||
### Réglages (`/reglages`)
|
||
|
||
Cinq onglets : **Profil**, **Objectif**, **Vape**, **Appareils & API** (création et
|
||
révocation de clés d'API par appareil), **Application** (préférences locales au navigateur).
|
||
|
||
---
|
||
|
||
## Captures d'écran
|
||
|
||
**Il n'y en a pas, et ce n'est pas un oubli.**
|
||
|
||
La pile a bien été construite et exécutée (voir « Ce qui est vérifié » ci-dessous), mais la
|
||
validation s'est faite **en HTTP, pas dans un navigateur** : aucune session graphique n'a
|
||
été ouverte, donc aucune capture authentique n'a pu être prise. Plutôt que de publier des
|
||
images de synthèse trompeuses, cette section restera vide jusqu'à votre premier démarrage.
|
||
Les pages à capturer en priorité : le tableau de bord, `/sante/poids`, `/vape/economies` et
|
||
`/finances`.
|
||
|
||
---
|
||
|
||
## Démarrage rapide (Docker Compose)
|
||
|
||
### Prérequis
|
||
|
||
- **Docker installé et démarré** ; `docker compose version` doit répondre sans erreur
|
||
(Compose v2 ou plus récent). Sous Windows, vérifiez que Docker Desktop est bien actif.
|
||
- Environ 2 Go d'espace disque pour les images et la base.
|
||
- Aucun autre service n'écoute sur les ports choisis (80 et 8000 par défaut).
|
||
|
||
### Ce qui est vérifié
|
||
|
||
La séquence ci-dessous a été **exécutée telle quelle** sur un hôte Ubuntu (Docker Compose
|
||
v5.1.1, 8 vCPU, 1,8 Go de RAM), à partir d'un volume de données vide :
|
||
|
||
- les deux images se construisent (`lifetrack-api` ≈ 330 Mo, `lifetrack-web` ≈ 76 Mo) ;
|
||
- les trois conteneurs passent `healthy` en une vingtaine de secondes ;
|
||
- le schéma est créé sur un **vrai PostgreSQL 16** (28 tables, 105 index, extension
|
||
`pg_trgm` activée) et les données survivent à un `down` / `up` ;
|
||
- l'assistant de premier démarrage, puis un parcours par module (pesée + checklist du jour,
|
||
réglages vape + recharge + économies cumulées, import d'un relevé bancaire CSV en cp1252
|
||
avec ré-import dédupliqué et annulation) ont été rejoués **en HTTP à travers nginx** :
|
||
SPA, liens profonds, proxy `/api/` et gros fichiers compris.
|
||
|
||
Ce qui n'a **pas** été vérifié : le rendu graphique dans un navigateur (voir « Captures
|
||
d'écran »). Si un conteneur refuse de démarrer, commencez par
|
||
`docker compose logs api`.
|
||
|
||
> **Hôte avec moins de 2 Go de RAM** : construisez les images **une par une** (étape 3
|
||
> ci-dessous) et, si `vite build` se fait tuer par l'OOM killer, décommentez
|
||
> `NODE_OPTIONS=--max-old-space-size=1024` dans `.env`.
|
||
|
||
### 1. Créer le fichier `.env`
|
||
|
||
Depuis la racine du dépôt :
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
|
||
Sous Windows (PowerShell) :
|
||
|
||
```powershell
|
||
Copy-Item .env.example .env
|
||
```
|
||
|
||
### 2. Modifier les deux variables obligatoires
|
||
|
||
Ouvrez `.env` et changez **impérativement** ces deux valeurs :
|
||
|
||
| Variable | Valeur d'exemple à remplacer | Pourquoi |
|
||
|---|---|---|
|
||
| `LIFETRACK_JWT_SECRET` | `generate-a-long-random-string` | Signe vos jetons de session. Laissée telle quelle, n'importe qui pourrait forger une session valide. |
|
||
| `POSTGRES_PASSWORD` | `change-me` | Mot de passe de la base PostgreSQL. |
|
||
|
||
Pour générer un secret solide :
|
||
|
||
```bash
|
||
openssl rand -hex 32
|
||
```
|
||
|
||
Sans `openssl` (PowerShell) :
|
||
|
||
```powershell
|
||
-join ((1..64) | ForEach-Object { '0123456789abcdef'[(Get-Random -Maximum 16)] })
|
||
```
|
||
|
||
Les autres variables ont des valeurs par défaut utilisables telles quelles :
|
||
|
||
| Variable | Défaut | Rôle |
|
||
|---|---|---|
|
||
| `POSTGRES_USER` | `lifetrack` | Utilisateur PostgreSQL |
|
||
| `POSTGRES_DB` | `lifetrack` | Nom de la base |
|
||
| `LIFETRACK_TIMEZONE` | `Europe/Paris` | Fuseau des agrégations journalières |
|
||
| `WEB_PORT` | `80` | Port d'écoute de l'interface web |
|
||
|
||
Deux variables restent commentées et ne servent qu'au développement :
|
||
`LIFETRACK_CORS_ORIGINS` (origines autorisées quand Vite tourne sans proxy) et
|
||
`LIFETRACK_MODULES` (liste blanche de modules backend à charger ; vide = tous).
|
||
|
||
> `LIFETRACK_DATABASE_URL` n'est **pas** à renseigner dans `.env` : `docker-compose.yml` la
|
||
> compose automatiquement à partir de `POSTGRES_USER`, `POSTGRES_PASSWORD` et `POSTGRES_DB`.
|
||
|
||
### 3. Construire les images
|
||
|
||
C'est la séquence exacte qui a été validée. Les deux builds sont **séparés** : le build web
|
||
(`npm ci` + `vite build`) est le seul gros consommateur de mémoire, mieux vaut ne pas le
|
||
faire tourner en même temps qu'autre chose.
|
||
|
||
```bash
|
||
docker compose build api # ~30 s
|
||
docker compose build web # ~40 s
|
||
```
|
||
|
||
`docker compose build` (sans argument) fonctionne aussi et construit les deux à la suite.
|
||
|
||
### 4. Lancer la pile
|
||
|
||
```bash
|
||
docker compose up -d
|
||
```
|
||
|
||
Trois conteneurs démarrent : `postgres` (base de données), `api` (FastAPI) et `web`
|
||
(nginx servant le frontend compilé et relayant `/api/` vers l'API). Depuis un volume vide,
|
||
il faut une vingtaine de secondes pour que les trois soient `healthy`.
|
||
|
||
Vérifier :
|
||
|
||
```bash
|
||
docker compose ps # attendre (healthy) sur postgres, api et web
|
||
docker compose logs -f # au besoin, suivre le démarrage
|
||
```
|
||
|
||
### 5. Ouvrir l'application
|
||
|
||
<http://localhost>
|
||
|
||
Si vous avez changé `WEB_PORT`, adaptez l'adresse (par exemple `http://localhost:8080`).
|
||
Depuis un autre appareil du réseau local, utilisez l'adresse IP du serveur. L'assistant de
|
||
premier démarrage s'ouvre automatiquement et crée le compte administrateur.
|
||
|
||
Deux adresses utiles :
|
||
|
||
- <http://localhost/api/healthz> — doit répondre `{"status":"ok"}`
|
||
- <http://localhost/api/docs> — documentation interactive de l'API
|
||
|
||
> `docker-compose.yml` porte `name: lifetrack` : toutes les commandes ci-dessus sont donc
|
||
> déjà rattachées au projet `lifetrack`, sans avoir à passer `-p`.
|
||
|
||
### Arrêter, mettre à jour, repartir de zéro
|
||
|
||
```bash
|
||
docker compose down # arrêt, les données sont conservées
|
||
docker compose build api # après mise à jour du code…
|
||
docker compose build web
|
||
docker compose up -d # …puis redémarrage
|
||
docker compose down -v # ⚠️ supprime AUSSI le volume : toutes vos données
|
||
```
|
||
|
||
---
|
||
|
||
## Premier démarrage
|
||
|
||
Au premier accès, LifeTrack détecte qu'aucun compte n'existe et vous redirige vers l'écran
|
||
de création.
|
||
|
||
**Étape 1 — Compte administrateur.** Adresse e-mail, prénom ou pseudonyme (facultatif) et
|
||
mot de passe de **8 caractères minimum**. C'est le seul compte de l'instance : il n'y a pas
|
||
de page d'inscription publique. Vous êtes connecté immédiatement après validation et arrivez
|
||
sur le tableau de bord.
|
||
|
||
**Étape 2 — Profil corporel.** Rendez-vous dans **Réglages → Profil** et renseignez taille,
|
||
sexe, date de naissance et niveau d'activité. Sans ces informations, LifeTrack ne peut pas
|
||
calculer votre métabolisme de base, donc ni votre dépense estimée ni votre budget calorique.
|
||
|
||
**Étape 3 — Objectif.** Dans **Réglages → Objectif**, indiquez poids de départ, poids cible
|
||
et mode de calcul (rythme hebdomadaire, date cible ou maintien). Le budget calorique
|
||
quotidien et la date d'atteinte estimée s'affichent aussitôt. C'est aussi ici que vous
|
||
accédez à votre **planning de pesées et de séances**.
|
||
|
||
**Étape 4 (facultative) — Vape.** Dans **Réglages → Vape**, saisissez votre date d'arrêt du
|
||
tabac et votre consommation d'avant : sans cette référence, le module vape reste en mode
|
||
« non configuré » et n'affiche aucune économie.
|
||
|
||
Ensuite seulement : importez vos premières données depuis **Imports**, ou saisissez-les à la
|
||
main. Le [guide d'utilisation](docs/GUIDE.md) détaille chaque parcours.
|
||
|
||
---
|
||
|
||
## Mode développement
|
||
|
||
Commandes réellement exécutées et validées sur ce dépôt (Windows, PowerShell ou Git Bash).
|
||
|
||
### Base de données seule
|
||
|
||
Le démon Docker doit tourner. Depuis la racine :
|
||
|
||
```bash
|
||
docker compose -f docker-compose.yml -f docker-compose.dev.yml up postgres
|
||
```
|
||
|
||
L'overlay de développement publie le port `5432` sur l'hôte, ce qui permet de faire tourner
|
||
l'API directement sur la machine.
|
||
|
||
### Backend (FastAPI)
|
||
|
||
L'environnement virtuel Python est déjà présent dans `apps/api/.venv`.
|
||
|
||
```bash
|
||
cd apps/api
|
||
|
||
# Suite de tests — 348 tests, exécutés sur SQLite en mémoire
|
||
.venv/Scripts/python.exe -m pytest
|
||
|
||
# Lint et formatage
|
||
.venv/Scripts/ruff.exe check .
|
||
.venv/Scripts/ruff.exe format .
|
||
|
||
# Serveur de développement avec rechargement à chaud
|
||
.venv/Scripts/python.exe -m uvicorn app.main:app --reload --port 8000
|
||
```
|
||
|
||
L'API écoute alors sur <http://localhost:8000>, documentation sur
|
||
<http://localhost:8000/api/docs>.
|
||
|
||
Si l'environnement virtuel doit être recréé :
|
||
|
||
```bash
|
||
cd apps/api
|
||
python -m venv .venv
|
||
.venv/Scripts/python.exe -m pip install -r requirements.txt -r requirements-dev.txt
|
||
```
|
||
|
||
> Les tests tournent sur **SQLite en mémoire** alors que la production utilise
|
||
> **PostgreSQL 16** : tous les types de colonnes doivent rester portables (voir
|
||
> [CONVENTIONS.md](CONVENTIONS.md) §C8). `app/tests/test_postgres_ddl.py` vérifie hors
|
||
> ligne le DDL réellement émis pour PostgreSQL (types natifs, index partiels, cascades
|
||
> d'import) ; ce DDL a par ailleurs été confronté à un vrai serveur PostgreSQL 16.
|
||
|
||
### Frontend (React + Vite)
|
||
|
||
```bash
|
||
cd apps/web
|
||
|
||
npm install # une seule fois
|
||
npm run dev # serveur de développement sur http://localhost:5173
|
||
npm run build # tsc --noEmit puis build de production
|
||
npm run preview # prévisualiser le build
|
||
```
|
||
|
||
**Proxy Vite** : `vite.config.ts` redirige tout `/api` vers `http://localhost:8000`. Vous
|
||
n'avez donc rien à configurer côté CORS tant que l'API tourne sur le port 8000 — le frontend
|
||
appelle simplement `/api/...` en chemin relatif, exactement comme en production derrière
|
||
nginx.
|
||
|
||
Si vous préférez ne pas utiliser le proxy, décommentez `LIFETRACK_CORS_ORIGINS` dans `.env` :
|
||
|
||
```
|
||
LIFETRACK_CORS_ORIGINS=["http://localhost:5173"]
|
||
```
|
||
|
||
### Tout en conteneurs, avec rechargement à chaud
|
||
|
||
```bash
|
||
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
|
||
```
|
||
|
||
L'API tourne alors en `--reload` sur le port 8000 et Vite sur le port 5173, les sources
|
||
étant montées depuis l'hôte.
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
### Organisation du dépôt
|
||
|
||
```
|
||
LifeTrack/
|
||
├─ apps/
|
||
│ ├─ api/ # Backend FastAPI
|
||
│ │ ├─ app/
|
||
│ │ │ ├─ core/ # config, base, sécurité, pagination, erreurs,
|
||
│ │ │ │ # framework d'import et d'ingestion, chargeur de modules
|
||
│ │ │ ├─ modules/ # auth, health, vape, finance, imports
|
||
│ │ │ ├─ tests/ # 348 tests pytest
|
||
│ │ │ └─ main.py # création de l'app — ne jamais modifier pour un module
|
||
│ │ ├─ requirements.txt
|
||
│ │ └─ openapi.json # schéma OpenAPI exporté
|
||
│ └─ web/ # Frontend React + TypeScript
|
||
│ └─ src/
|
||
│ ├─ app/ # routeur, layout, authentification — ne pas modifier
|
||
│ ├─ components/ # composants partagés (ui/, charts/)
|
||
│ ├─ lib/ # wrapper API, formatage fr-FR
|
||
│ └─ modules/ # home, health, vape, finance, imports, settings
|
||
├─ docker/ # Dockerfiles et configuration nginx
|
||
├─ docs/ # documents de conception et de recherche
|
||
├─ docker-compose.yml
|
||
├─ docker-compose.dev.yml
|
||
├─ .env.example
|
||
├─ CONVENTIONS.md # règles obligatoires pour toute contribution
|
||
└─ DESIGN.md # synthèse des décisions de conception
|
||
```
|
||
|
||
### Modules auto-découverts
|
||
|
||
C'est la décision structurante du projet : **ajouter un module ne demande de modifier aucun
|
||
fichier existant.**
|
||
|
||
- **Côté backend**, `app/core/module_loader.py` parcourt `app/modules/` avec `pkgutil`. Tout
|
||
dossier qui expose un `router.py` contenant `router = APIRouter(prefix="/<nom>")` est monté
|
||
automatiquement sous `/api`. Les fichiers `models.py`, `importers.py` et `ingest.py` sont
|
||
importés au démarrage pour peupler les métadonnées SQLAlchemy et les registres de
|
||
connecteurs.
|
||
- **Côté frontend**, `src/app/modules.ts` utilise `import.meta.glob` sur
|
||
`src/modules/*/index.ts`. Chaque module exporte par défaut un `ModuleManifest` déclarant son
|
||
identifiant, son titre français, son ordre, ses routes et ses entrées de navigation.
|
||
|
||
Pour créer un module, suivez [CONVENTIONS.md](CONVENTIONS.md) §C2 (backend) et §C5
|
||
(frontend). Les règles y sont normatives : nommage, unités canoniques, pagination, gestion
|
||
des erreurs, portabilité SQLite.
|
||
|
||
### Où vivent les données
|
||
|
||
- **PostgreSQL 16**, dans le volume Docker nommé `pgdata`. Il survit à `docker compose down`
|
||
mais **pas** à `docker compose down -v`.
|
||
- Le schéma est créé au démarrage par `Base.metadata.create_all` — il n'y a **pas encore de
|
||
migrations Alembic** en v1.
|
||
- Conventions de stockage : horodatages en **UTC**, jours locaux en `Date`, poids en **kg**,
|
||
distances en **m**, volumes en **ml**, énergie en **kcal**, durées en **secondes**, argent
|
||
en **centimes d'euro**. L'affichage en `fr-FR` (virgule décimale, `€` après le montant) est
|
||
fait côté interface.
|
||
- Sauvegarde recommandée :
|
||
```bash
|
||
docker compose exec postgres pg_dump -U lifetrack lifetrack > sauvegarde.sql
|
||
```
|
||
|
||
### API
|
||
|
||
- Toutes les routes sont préfixées par **`/api`**, suivi du nom du module :
|
||
`/api/auth`, `/api/health`, `/api/vape`, `/api/finance`, `/api/imports`, `/api/ingest`.
|
||
- En production, nginx relaie `/api/` vers le conteneur `api` sur le port 8000 ; tout le
|
||
reste retombe sur `index.html` (application monopage).
|
||
- Format d'erreur unique : `{"error": {"code", "message", "details"}}`, `code` stable en
|
||
snake_case, `message` en français.
|
||
- Les dates-heures sont en UTC ISO 8601, les jours locaux en `YYYY-MM-DD`, et les agrégations
|
||
journalières acceptent un paramètre `?tz=` (défaut `Europe/Paris`).
|
||
|
||
### Authentification
|
||
|
||
Deux mécanismes, pour deux usages :
|
||
|
||
1. **JWT** — pour vous, dans le navigateur. Obtenu via `POST /api/auth/login`, envoyé dans
|
||
l'en-tête `Authorization: Bearer <jeton>`. Mots de passe hachés en argon2. Durée de
|
||
validité : **7 jours** (choix assumé pour un déploiement domestique).
|
||
2. **Clés d'appareil** — pour vos machines. Créées dans **Réglages → Appareils & API**,
|
||
préfixées `ltk_`, envoyées dans l'en-tête **`X-API-Key`**, et portant des **portées**
|
||
explicites (`ingest:health`, `ingest:*`, …). La clé en clair n'est affichée **qu'une seule
|
||
fois** à la création ; seul son empreinte est stockée. Chaque clé est révocable
|
||
individuellement.
|
||
|
||
Tout endpoint hors `POST /api/auth/login`, `POST /api/auth/setup`, `GET /api/auth/status` et
|
||
`/api/healthz` exige une authentification et filtre systématiquement les données par
|
||
utilisateur.
|
||
|
||
---
|
||
|
||
## Stack technique
|
||
|
||
| Couche | Technologies |
|
||
|---|---|
|
||
| Backend | Python 3.12 · FastAPI · SQLAlchemy 2.0 typé · Pydantic v2 · PyJWT · argon2 (pwdlib) |
|
||
| Base de données | PostgreSQL 16 (pilote psycopg 3) — SQLite en mémoire pour les tests |
|
||
| Frontend | React 18 · TypeScript strict · Vite 6 · TailwindCSS · Apache ECharts · TanStack Query · React Router |
|
||
| Déploiement | Docker Compose : `postgres` + `api` + `web` (nginx 1.27) |
|
||
| Qualité | pytest (348 tests) · ruff · `tsc --noEmit` |
|
||
|
||
Aucune ressource externe n'est chargée à l'exécution : polices, icônes et bibliothèques sont
|
||
embarquées dans le build.
|
||
|
||
---
|
||
|
||
## Feuille de route et écarts connus
|
||
|
||
Ce qui suit est **décrit dans les documents de conception mais absent du code
|
||
d'aujourd'hui**. Rien de tout cela n'est nécessaire pour utiliser LifeTrack, mais autant le
|
||
savoir avant de le chercher dans l'interface.
|
||
|
||
### Écarts entre la conception et le code actuel
|
||
|
||
- **Table CIQUAL absente.** La recherche d'aliments prévoyait une base d'aliments génériques
|
||
français (ANSES). Le code ne contient ni script d'import ni données : seule la recherche
|
||
Open Food Facts fonctionne. Les plats maison se saisissent donc à la main.
|
||
- **Adaptateur webhook sans en-tête d'authentification non implémenté.** La recherche
|
||
prévoyait une route acceptant un jeton dans l'URL, pour les passerelles Android incapables
|
||
de poser un en-tête HTTP. Elle n'existe pas : `POST /api/ingest/{domain}` exige
|
||
`X-API-Key` ou un jeton JWT. Conséquence pratique détaillée dans le
|
||
[guide](docs/GUIDE.md#connecter-health-connect).
|
||
- **Ingestion limitée au domaine `health`.** L'écran de création de clé propose les portées
|
||
`ingest:vape` et `ingest:finance`, mais aucun gestionnaire ne les traite :
|
||
`POST /api/ingest/vape` et `POST /api/ingest/finance` répondent « Domaine d'ingestion
|
||
inconnu ». Ces portées sont réservées pour plus tard.
|
||
- **Pas de table de charges utiles brutes.** Chaque ligne conserve sa charge utile d'origine
|
||
dans une colonne `raw`, mais il n'existe pas de table dédiée permettant de rejouer une
|
||
normalisation après coup.
|
||
- **Importeurs de séances absents** : pas de lecteur TCX, GPX ou FIT. Les séances de sport se
|
||
saisissent à la main ou arrivent par l'ingestion JSON.
|
||
- **Importeurs MyFitnessPal et Cronometer absents** : seul Foodvisor dispose d'un profil
|
||
nutrition dédié.
|
||
- **Pas de migrations Alembic** : le schéma est créé au démarrage. Une modification de schéma
|
||
sur une base existante devra être gérée manuellement.
|
||
|
||
### v2 — envisagé
|
||
|
||
- Application compagnon Android maison (Kotlin, SDK Health Connect), avec vraie
|
||
authentification par jeton et rattrapage d'historique au-delà de 48 heures.
|
||
- Connecteur bancaire **Enable Banking** (PSD2, gratuit pour ses propres comptes) en
|
||
remplacement des imports de fichiers.
|
||
- Calculateur DIY avancé, migrations Alembic, notifications.
|
||
|
||
### v3 — exploratoire
|
||
|
||
- Enregistrement direct des séances de tapis de course via **Web Bluetooth + FTMS**
|
||
(Chrome/Edge uniquement, HTTPS requis).
|
||
- Analyse photo des repas, multi-utilisateurs complet, thème clair.
|
||
|
||
---
|
||
|
||
## Documentation
|
||
|
||
| Document | Contenu |
|
||
|---|---|
|
||
| [docs/GUIDE.md](docs/GUIDE.md) | **Guide d'utilisation au quotidien** : connecter Health Connect, suivre poids et calories, nutrition, vape, finances, dépannage |
|
||
| [CONVENTIONS.md](CONVENTIONS.md) | Règles obligatoires pour contribuer au code |
|
||
| [DESIGN.md](DESIGN.md) | Synthèse des décisions de conception |
|
||
| [docs/design/architecture.md](docs/design/architecture.md) | Architecture technique complète |
|
||
| [docs/design/datamodel-health-vape.md](docs/design/datamodel-health-vape.md) | Modèle de données et formules santé, nutrition, vape |
|
||
| [docs/design/datamodel-finance.md](docs/design/datamodel-finance.md) | Modèle de données et logique finances |
|
||
| [docs/design/ux-pages.md](docs/design/ux-pages.md) | Spécification UX page par page |
|
||
| [docs/design/addendum-planning.md](docs/design/addendum-planning.md) | Planning d'habitudes, assiduité, séries |
|
||
| [docs/research/health-connect.md](docs/research/health-connect.md) | Comment sortir les données de Health Connect |
|
||
| [docs/research/nutrition-sources.md](docs/research/nutrition-sources.md) | Foodvisor, Open Food Facts, CIQUAL |
|
||
| [docs/research/finance-sources.md](docs/research/finance-sources.md) | Formats d'export réels des banques françaises |
|
||
|
||
---
|
||
|
||
## Licence
|
||
|
||
**Aucune licence n'est déclarée** : ce dépôt est un projet personnel auto-hébergé, il n'est
|
||
pas publié sous licence libre. En l'absence de fichier `LICENSE`, tous droits sont réservés
|
||
par défaut.
|
||
|
||
Notez les obligations attachées aux données tierces que vous consommerez :
|
||
|
||
- **Open Food Facts** — base sous **ODbL**, contenus sous DbCL. Un usage personnel ne pose
|
||
aucune difficulté ; une attribution « Données produits : Open Food Facts (ODbL) » reste la
|
||
bonne pratique.
|
||
- Les applications passerelles Android citées dans la documentation ont leurs propres
|
||
licences (AGPL-3.0 pour `health-connect-webhook`, GPL-3.0 pour HCGateway) — elles ne sont
|
||
pas distribuées avec LifeTrack.
|