chore: ajout .gitignore, CLAUDE.md, design system et docs Phase 2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 14:10:18 +02:00
parent 2b34abf4b0
commit b87c96ceab
15 changed files with 7485 additions and 0 deletions
+388
View File
@@ -0,0 +1,388 @@
# HomeHub — Spécification Fonctionnelle
> Version 0.1 — mai 2026
> Application d'organisation personnelle auto-hébergée (PWA)
> Stack : FastAPI · React/Vite/TypeScript · PostgreSQL 16 · Docker Compose
---
## 1. Contexte et objectifs
HomeHub est une PWA auto-hébergée sur Proxmox 9, conçue pour un usage intensif mobile (smartphone en magasin, sur le terrain) et une consultation plus complète sur laptop. L'application est **mono-utilisateur** au démarrage, avec une architecture prévue pour évoluer vers le multi-utilisateur sans refactoring majeur (colonne `owner_id` nullable dans toutes les tables).
---
## 2. Architecture technique
### 2.1 Services Docker Compose (6 services)
| Service | Image | Port interne | Rôle |
|---------|-------|-------------|------|
| `frontend` | Nginx léger | 3000 | Sert le build React/Vite (PWA statique) |
| `backend` | Python 3.12 / FastAPI | 8000 | API REST + MCP server |
| `db` | PostgreSQL 16 | 5432 | Base de données multi-schémas |
| `ocr` | Tesseract + Pillow | 8001 | Service OCR partagé (toutes les photos de l'app) |
| `product-search` | Python slim | 8002 | Lookup OpenFoodFacts + fallback SearXNG |
| `searxng` | SearXNG officiel | 8080 | Métamoteur de recherche — fallback image produits |
**Services isolés et arrêtables indépendamment :**
- `ocr` (4) — arrêtable sans impact sur la liste de courses. Seule la saisie OCR de prix devient indisponible
- `product-search` (5) — arrêtable sans impact. Seule la recherche OpenFoodFacts devient indisponible
- `searxng` (6) — arrêtable sans impact. Seul le fallback image produit devient indisponible
Les services 1/2/3 (`frontend`, `backend`, `db`) sont les seuls **obligatoires** pour le fonctionnement de base. Un `docker compose stop ocr product-search searxng` laisse l'app 100% fonctionnelle pour la liste de courses.
**Nginx Proxy Manager** (déjà en place sur le homelab) gère l'entrée HTTPS et le routage vers ces services.
### 2.2 Évolutions prévues (non bloquantes)
- Redis → ajouté avec la sync Google Calendar (Phase 6)
- Auth JWT → activation de `owner_id` + écran de connexion (Phase 7)
- Agent Hermes (Vision LLM) → analyse photo frigo
- Webhooks Gitea → intégration Kanban
- Home Assistant → capteurs tâches en retard
### 2.3 Stockage fichiers et module media
Volume Docker `/uploads/` servi par le backend. Toutes les images passent par le module `media` du backend à la réception.
**Pipeline de traitement à l'upload :**
1. Compression côté client (Canvas API → WebP) avant envoi — réduit la bande passante
2. Validation format serveur (JPG, PNG, SVG, WebP acceptés)
3. Génération automatique d'une miniature (Pillow) — stockée séparément
4. Les deux fichiers sont enregistrés sur le volume
**Structure du volume :**
```
/uploads/
images/
originals/{uuid}.webp ← image compressée (pleine résolution utile)
thumbnails/{uuid}_thumb.webp ← miniature générée par Pillow
audio/
{uuid}.webm ← enregistrements audio
```
**Tailles de miniatures par contexte :**
| Contexte | Taille thumbnail |
|----------|-----------------|
| Produit catalogue shopping | 150 × 150 px (carré, centré) |
| Note (vignette liste) | 300 × 300 px (carré, centré) |
| Pièce jointe (aperçu inline) | 400 × 300 px (paysage, centré) |
Les miniatures sont toujours servies pour les vues liste/grille. L'original n'est chargé qu'à la demande (plein écran, zoom). Formats audio acceptés : WebM, M4A.
### 2.4 MCP Server
Intégré dans FastAPI sur `/mcp` (protocole SSE). Expose les outils suivants aux agents IA (Hermes, Claude, etc.) :
| Outil | Description |
|-------|-------------|
| `get_todos()` | Retourne les tâches urgentes / en cours |
| `add_todo(title, due_date)` | Crée une tâche |
| `get_shopping_list()` | Retourne la liste de courses active |
| `add_shopping_item(name, category)` | Ajoute un article à la liste active |
| `search_notes(query)` | Recherche full-text dans les notes |
| `add_note(title, content, tags)` | Crée une note |
---
## 3. Schéma de base de données
### 3.1 Schema `shopping`
#### `shopping.products` — Catalogue global
| Colonne | Type | Description |
|---------|------|-------------|
| id | UUID PK | |
| name | VARCHAR(150) | Nom produit |
| brand | VARCHAR(100) | Marque |
| category | VARCHAR(50) | Rayon magasin (tri in-store) |
| image_path | VARCHAR(255) | Chemin original `/uploads/images/originals/` |
| thumbnail_path | VARCHAR(255) | Chemin miniature `/uploads/images/thumbnails/` |
| default_unit | VARCHAR(20) | kg / L / unité |
| barcode | VARCHAR(50) | Code-barres (scan futur) |
| frequency_score | INT DEFAULT 0 | Score d'habitude (suggestions auto) |
| owner_id | UUID NULL | Prévu multi-user |
| created_at | TIMESTAMPTZ | |
**Catégories produits (tri rayon en magasin) :**
`Fruits` · `Légumes` · `Viandes` · `Charcuterie` · `Poissons` · `Produits laitiers` · `Boulangerie` · `Épicerie salée` · `Épicerie sucrée` · `Condiments` · `Boissons` · `Entretien` · `Pharmacie` · `Animaux` · `Carburant` · `Électronique` · `Divers`
**Magasins pré-configurés (seed) :** Lidl · Intermarché · Super U · Gamm Vert · Weldom · Cosi · Bricocash · Tinel · Marie Blachère
**Données de démarrage :** 113 produits dans `backend/app/data/seed_products.json`, 9 magasins dans `seed_stores.json` — chargés automatiquement au premier démarrage.
#### `shopping.stores` — Magasins
| Colonne | Type | Description |
|---------|------|-------------|
| id | UUID PK | |
| name | VARCHAR(100) | Ex: Lidl, Carrefour |
| location | TEXT | Adresse ou coordonnées GPS |
| owner_id | UUID NULL | |
#### `shopping.price_history` — Historique des prix
| Colonne | Type | Description |
|---------|------|-------------|
| id | UUID PK | |
| product_id | UUID → products | |
| store_id | UUID → stores NULL | |
| price | NUMERIC(8,2) | Prix relevé |
| unit | VARCHAR(20) | kg / L / unité |
| quantity | NUMERIC(8,3) | Quantité associée |
| source | VARCHAR(20) | `manual` / `ocr_tag` / `ocr_receipt` |
| recorded_at | TIMESTAMPTZ | |
#### `shopping.lists` — Listes de courses
| Colonne | Type | Description |
|---------|------|-------------|
| id | UUID PK | |
| name | VARCHAR(100) | Ex: "Semaine du 26 mai" |
| store_id | UUID NULL | Magasin cible |
| week_date | DATE | Date de la semaine cible |
| status | VARCHAR(20) | `draft` / `active` / `done` |
| owner_id | UUID NULL | |
| created_at | TIMESTAMPTZ | |
#### `shopping.list_items` — Articles dans une liste
| Colonne | Type | Description |
|---------|------|-------------|
| id | UUID PK | |
| list_id | UUID → lists | |
| product_id | UUID → products NULL | Lié au catalogue (optionnel) |
| custom_name | VARCHAR(150) | Si article hors catalogue |
| quantity | NUMERIC(8,3) | |
| unit | VARCHAR(20) | |
| is_checked | BOOLEAN DEFAULT FALSE | Coché en magasin |
| price_recorded | NUMERIC(8,2) | Prix relevé pendant les courses |
| carried_over | BOOLEAN DEFAULT FALSE | Reporté depuis la semaine précédente |
| sort_order | INT | Tri par rayon/catégorie |
### 3.2 Schema `todos`
#### `todos.items`
| Colonne | Type | Description |
|---------|------|-------------|
| id | UUID PK | |
| title | VARCHAR(255) | |
| body | TEXT | Texte libre |
| url | TEXT | Lien optionnel |
| domain | VARCHAR(50) | Voir liste des domaines |
| category | VARCHAR(50) | Sous-catégorie libre |
| tags | VARCHAR(50)[] | Array · index GIN |
| status | VARCHAR(20) | `pending` / `done` / `cancelled` |
| priority | VARCHAR(10) | `low` / `medium` / `high` |
| due_date | TIMESTAMPTZ | Date objectif |
| postponed_count | INT DEFAULT 0 | Nombre de reports |
| created_at | TIMESTAMPTZ DEFAULT NOW() | Auto |
| updated_at | TIMESTAMPTZ | |
| owner_id | UUID NULL | |
**Domaines disponibles** : `informatique` · `diy` · `electronique` · `domotique` · `bricolage` · `jardin` · `cuisine` · `voyage` · `animaux`
### 3.3 Schema `notes`
#### `notes.items`
| Colonne | Type | Description |
|---------|------|-------------|
| id | UUID PK | |
| title | VARCHAR(255) | Optionnel |
| content | TEXT NOT NULL | Texte de la note |
| category | VARCHAR(50) | |
| tags | VARCHAR(50)[] | Index GIN |
| gps_lat | NUMERIC(10,7) | Latitude GPS |
| gps_lon | NUMERIC(10,7) | Longitude GPS |
| metadata | JSONB | Paires clé/valeur libres (référence, magasin…) |
| created_at | TIMESTAMPTZ DEFAULT NOW() | |
| owner_id | UUID NULL | |
Index : `GIN(tags)` + `GIN(to_tsvector('french', title || ' ' || content))` (recherche full-text)
#### `notes.attachments`
| Colonne | Type | Description |
|---------|------|-------------|
| id | UUID PK | |
| note_id | UUID → items | |
| file_path | VARCHAR(255) | Chemin original `/uploads/images/originals/` |
| thumbnail_path | VARCHAR(255) | Chemin miniature (NULL si audio) |
| file_type | VARCHAR(20) | `image` / `audio` |
| original_name | VARCHAR(255) | |
| created_at | TIMESTAMPTZ | |
---
## 4. Modules fonctionnels
### 4.1 Module Shopping — Liste de courses
#### Principe fondamental — ne jamais bloquer
> **L'objectif principal est simple : créer une liste en début de semaine, cocher les articles pendant les courses.**
> Toutes les autres fonctionnalités (prix, OCR, stats, suggestions automatiques) sont **optionnelles** et n'apparaissent jamais dans le chemin principal. Un utilisateur qui ignore tout ça doit pouvoir utiliser l'app sans friction.
#### Concept général
La liste de courses est générée **à partir du catalogue global** de produits. Le `frequency_score` de chaque produit augmente à chaque achat — ce score alimente les suggestions automatiques de la semaine. L'utilisateur valide/modifie/complète la liste avant de partir en courses.
Les articles **non cochés** à la fin des courses sont automatiquement reportés (`carried_over = true`) dans la liste de la semaine suivante.
La saisie de prix est **toujours optionnelle** — le champ prix est discret, accessible d'un tap long ou d'un bouton secondaire, jamais dans le chemin de cocher un article.
#### Interface mobile (priorité)
- **Création de liste** : interface ultra-simple — picker de la semaine, suggestion automatique des produits habituels (top frequency_score), ajout rapide d'un produit (recherche dans le catalogue ou création à la volée)
- **Mode courses** (liste active) :
- Grands boutons tactiles (min 48px)
- Articles triés par rayon/catégorie
- Tap pour cocher un article
- Swipe gauche → supprimer de la liste
- Champ prix rapide au tap (clavier numérique)
- **Wake Lock API** : l'écran ne se verrouille pas pendant les courses
- Bouton "Terminer les courses" → génère la liste suivante avec les articles non cochés
#### Interface laptop (complète)
- Vue tableau avec filtre par magasin, catégorie, statut
- Gestion du catalogue global de produits (CRUD complet)
- Gestion des magasins
- Graphiques d'évolution des prix par produit
- Export CSV de l'historique des prix
#### Module OCR (service Docker isolé, partagé entre tous les modules)
Le service OCR tourne dans son propre conteneur Docker (`ocr:8001`). Il est utilisable par tous les modules de l'application via l'endpoint unifié `POST /api/ocr/extract` du backend.
| Module | Usage OCR |
|--------|-----------|
| Shopping | Lecture étiquette prix en rayon → pré-remplissage prix |
| Shopping | Lecture ticket de caisse → réconciliation finale liste |
| Notes | Extraction texte depuis une photo (photo de document, panneau, référence) |
| Futur | Tout import photo dans l'application |
Le module OCR est **toujours optionnel** : si le service est arrêté, les autres fonctionnalités de l'app ne sont pas impactées. Backend : Tesseract (local). Fallback possible : Ollama Vision (Hermes) en remplaçant simplement le service `ocr` dans le Docker Compose.
#### Suivi des prix
Chaque prix saisi (manuel, OCR étiquette ou OCR ticket) alimente `price_history`. Sur laptop, un graphique en courbe par produit montre l'évolution du prix dans le temps, par magasin.
### 4.2 Module Notes / Listes diverses
Saisie rapide de notes avec support multimédia et géolocalisation.
**Champs** :
- Titre (optionnel)
- Contenu texte libre (saisie rapide)
- Date (auto à la création, modifiable)
- Catégorie
- Tags (multi-valeurs, autocomplétion)
- Photo(s) : capture directe via Camera API ou import depuis galerie. Compression WebP avant upload
- Audio : enregistrement via MediaRecorder API. Format WebM/M4A
- GPS : bouton "Localiser" → Geolocation API → stocke lat/lon, affiche adresse via reverse geocoding (optionnel)
- Métadonnées libres : paires clé/valeur (ex: "Référence: X12-34", "Magasin: Brico Dépôt")
**Recherche** : full-text PostgreSQL en français sur titre + contenu + métadonnées. Filtres par catégorie, tags, présence de photo/audio/GPS.
### 4.3 Module Todos
Gestion de tâches classées par domaine.
**Champs** :
- Titre
- Texte libre (description)
- URL (lien externe optionnel)
- Domaine (liste fermée : voir section 3.2)
- Catégorie (libre)
- Tags (multi-valeurs)
- Statut : `pending` / `done` / `cancelled`
- Priorité : `low` / `medium` / `high`
- Date de création (auto)
- Date objectif (due_date)
- Compteur de reports (`postponed_count`)
**Interface mobile** : ajout rapide en 1 clic, vue liste épurée, actions swipe (reporter / terminer). Boutons "Reporter d'1 jour", "Reporter à la semaine prochaine".
**Interface laptop** : vue complète avec filtres multi-critères (domaine, statut, priorité, tags), tri, recherche textuelle.
---
## 5. Module scan code-barres / QR code
### Fonctionnement
- Librairie JS `zxing-js` (open-source, cross-platform) intégrée dans le frontend
- Accès via le flux Camera API (pas de capture photo — lecture en temps réel)
- Fonctionne sur iOS Safari et Android Chrome
- Déclenché par un bouton 📷 "Scanner" dans le formulaire d'ajout de produit
### Flux de résolution produit
```
Scan code-barres (EAN-13 / QR)
→ service product-search : GET /lookup?barcode={code}
→ 1. OpenFoodFacts API (base ~3M produits alimentaires)
→ trouvé : nom, marque, catégorie, image → auto-remplit le formulaire
→ 2. Si pas trouvé : retourne vide → saisie manuelle
→ enrichissement optionnel via recherche texte + SearXNG (laptop uniquement)
```
### Service `product-search` (Docker)
API Python légère qui centralise :
- `GET /lookup?barcode={code}` → OpenFoodFacts par code-barres
- `GET /search?q={nom}` → OpenFoodFacts par nom de produit
- `GET /image-search?q={nom}` → SearXNG image search (fallback, laptop uniquement)
Le backend FastAPI expose ces fonctionnalités via `/api/products/lookup`, `/api/products/search`, `/api/products/image-search`.
### Service `searxng` (Docker)
Instance SearXNG auto-hébergée, utilisée exclusivement pour la recherche d'images de produits non trouvés dans OpenFoodFacts. Accessible uniquement depuis l'interface laptop lors de l'enrichissement du catalogue. Non exposé publiquement via NPM.
---
## 6. Capacités natives smartphone (PWA)
| API navigateur | Usage |
|---------------|-------|
| Camera API | Capture photo directe (notes, OCR, produits) |
| MediaRecorder API | Enregistrement audio (notes vocales) |
| Geolocation API | Localisation GPS sur les notes |
| Wake Lock API | Écran actif pendant les courses |
| Web Share API | Partage de notes/listes vers autres apps |
| Liens `webcal://` | Abonnement calendrier natif iOS/Android (futur) |
---
## 7. Design system
Le design system **Gruvbox seventies** est intégré dès le départ.
- Fichiers : `design_system/tokens/tokens.css` · `design_system/components/ui-kit.jsx`
- Thème : dark par défaut, light disponible via `data-theme`
- Palette : orange brûlé `#fe8019` (accent), fond brun `#2a231d`, texte `#f2e5c7`
- 14 composants React disponibles (Button, IconButton, Toggle, StatusLed, BatteryGauge, RadialGauge, Popup, TreeNav, Sparkline, LineChart, Icon…)
- Fonts : Inter (UI) · JetBrains Mono (données) · Share Tech Mono (terminal/logs)
- Règles absolues : jamais de hex en dur, toujours `var(--token)`, `data-theme` obligatoire sur un parent
---
## 8. Interface utilisateur — principes
### Mobile-first
- Touch target minimum : 48px
- Composants d'action accessibles en 1 main
- Swipe sur les listes (reporter, terminer, supprimer)
- Chargement hors-ligne via Service Worker (cache assets + données critiques)
- Feedback haptique si disponible
### Distinction mobile / laptop
Certains modules ont deux pages dédiées selon la taille d'écran :
| Module | Mobile | Laptop |
|--------|--------|--------|
| Shopping | Création liste simple + mode courses | Catalogue produits + gestion magasins + graphiques prix |
| Todos | Ajout rapide + liste swipeable | Tableau filtrable multi-critères |
| Notes | Saisie rapide + media capture | Liste complète + recherche avancée |
---
## 9. Hors scope initial (Phase 1-5)
- Authentification multi-utilisateurs (Phase 7)
- Google Calendar / CalDAV (Phase 6)
- Kanban / Gitea webhooks (Phase 8)
- Home Assistant (Phase 8)
- Analyse frigo par Vision LLM / Hermes (Phase 9)
- Scan code-barres produits