Files
2026-05-24 14:10:18 +02:00

17 KiB
Raw Permalink Blame History

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