ti-pote/docs/data-model.md
ordinarthur 674109ea22 feat: initial project setup — Phase 0
Monorepo pnpm avec NestJS backend en architecture hexagonale.
- Structure hexagonale complète (ports, adapters, domain entities)
- 9 entities TypeORM (Home, User, Device, Credentials, Session, Message, Memory, Timer)
- Migration initiale SQL avec pgvector support
- Docker Compose (PostgreSQL 16 + pgvector + Redis 7)
- Config partagée (tsconfig, ESLint, Prettier)
- Outbound ports définis (STT, TTS, LLM, Cache, Storage, VectorStore)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 09:01:52 +01:00

12 KiB

Modèle de données

Diagramme des entités

┌──────────────┐       ┌──────────────┐       ┌──────────────┐
│    Home      │ 1───N │    User      │ 1───N │  UserService │
│              │       │              │       │  Credential  │
│ id           │       │ id           │       │              │
│ name         │       │ home_id (FK) │       │ id           │
│ created_at   │       │ email        │       │ user_id (FK) │
│ updated_at   │       │ password_hash│       │ service_type │
└──────────────┘       │ display_name │       │ encrypted_   │
                       │ role         │       │   tokens     │
                       │ preferences  │       │ metadata     │
                       │ created_at   │       │ created_at   │
                       │ updated_at   │       │ updated_at   │
                       └──────┬───────┘       └──────────────┘
                              │
                              │ 1───N
                              ▼
┌──────────────┐       ┌──────────────┐
│   Device     │       │Conversation  │
│              │       │  Session     │
│ id           │       │              │
│ home_id (FK) │       │ id           │
│ name         │       │ user_id (FK) │
│ device_token │       │ device_id(FK)│
│ config       │       │ status       │
│ status       │       │ started_at   │
│ last_seen_at │       │ ended_at     │
│ created_at   │       │ summary      │
│ updated_at   │       │ created_at   │
└──────────────┘       └──────┬───────┘
                              │
                              │ 1───N
                              ▼
                       ┌──────────────┐       ┌──────────────┐
                       │   Message    │       │MemoryEntry   │
                       │              │       │              │
                       │ id           │       │ id           │
                       │ session_id   │       │ user_id (FK) │
                       │   (FK)       │       │ session_id   │
                       │ role         │       │   (FK, opt)  │
                       │ content      │       │ type         │
                       │ tool_calls   │       │ content      │
                       │ audio_url    │       │ tags         │
                       │ created_at   │       │ created_at   │
                       └──────────────┘       │ updated_at   │
                                              └──────────────┘

                       ┌──────────────┐       ┌──────────────┐
                       │MemoryEmbed   │       │  Timer       │
                       │              │       │              │
                       │ id           │       │ id           │
                       │ memory_id(FK)│       │ user_id (FK) │
                       │ user_id (FK) │       │ device_id(FK)│
                       │ embedding    │       │ type         │
                       │   vector(1536)       │ label        │
                       │ created_at   │       │ trigger_at   │
                       └──────────────┘       │ recurrence   │
                                              │ status       │
                                              │ created_at   │
                                              └──────────────┘

Détail des tables

Home

Représente un foyer. Permet de regrouper plusieurs utilisateurs et robots dans un même espace logique (inspiré du modèle Google Home).

Colonne Type Description
id UUID (PK) Identifiant unique
name VARCHAR(100) Nom du foyer ("Maison d'Arthur")
created_at TIMESTAMPTZ Date de création
updated_at TIMESTAMPTZ Dernière modification

User

Un utilisateur du système. Rattaché à un Home.

Colonne Type Description
id UUID (PK) Identifiant unique
home_id UUID (FK → Home) Foyer de l'utilisateur
email VARCHAR(255) UNIQUE Email de connexion
password_hash VARCHAR(255) Hash bcrypt du mot de passe
display_name VARCHAR(100) Nom affiché
role ENUM('owner', 'member') Rôle dans le foyer
preferences JSONB Préférences utilisateur (langue, timezone, etc.)
created_at TIMESTAMPTZ Date de création
updated_at TIMESTAMPTZ Dernière modification

Le champ preferences est un JSONB flexible pour stocker les préférences sans migration de schéma à chaque ajout :

{
  "language": "fr",
  "timezone": "Europe/Paris",
  "tts_voice": "elevenlabs_rachel",
  "llm_model": "gpt-4",
  "wake_word": "hey-ti-pote",
  "do_not_disturb": {
    "enabled": true,
    "start": "23:00",
    "end": "07:00"
  }
}

Device

Un robot Ti-Pote physique. Rattaché à un Home, partagé entre les Users du Home.

Colonne Type Description
id UUID (PK) Identifiant unique
home_id UUID (FK → Home) Foyer du robot
name VARCHAR(100) Nom du robot ("Ti-Pote Bureau")
device_token_hash VARCHAR(255) Hash du JWT token du device
config JSONB Configuration hardware (modules actifs, volume, etc.)
status ENUM('online', 'offline', 'updating') Statut actuel
firmware_version VARCHAR(20) Version du firmware
last_seen_at TIMESTAMPTZ Dernier ping reçu
created_at TIMESTAMPTZ Date d'enregistrement
updated_at TIMESTAMPTZ Dernière modification

Le champ config :

{
  "modules": {
    "camera": true,
    "mobile_base": false,
    "screen": true
  },
  "volume": 75,
  "led_brightness": 50,
  "mic_sensitivity": "auto"
}

UserServiceCredential

Tokens OAuth et credentials pour les services tiers, chiffrés en AES-256-GCM.

Colonne Type Description
id UUID (PK) Identifiant unique
user_id UUID (FK → User) Propriétaire
service_type ENUM('google', 'apple', 'microsoft', 'smtp', 'whatsapp') Type de service
encrypted_tokens TEXT Tokens chiffrés (access + refresh)
metadata JSONB Infos non sensibles (email du compte, scopes, etc.)
expires_at TIMESTAMPTZ Expiration de l'access token (pour refresh proactif)
created_at TIMESTAMPTZ Date de liaison
updated_at TIMESTAMPTZ Dernier refresh

ConversationSession

Une session de conversation (du wake word au silence / fin de conversation).

Colonne Type Description
id UUID (PK) Identifiant unique
user_id UUID (FK → User) Utilisateur qui parle
device_id UUID (FK → Device) Robot utilisé
status ENUM('active', 'ended', 'timeout') Statut
started_at TIMESTAMPTZ Début de la session
ended_at TIMESTAMPTZ Fin de la session
summary TEXT Résumé généré par le LLM à la clôture
extracted_facts JSONB Faits extraits de la conversation
message_count INTEGER Nombre de messages
total_tokens INTEGER Tokens LLM consommés
created_at TIMESTAMPTZ Date de création

Message

Un message dans une conversation (message utilisateur ou réponse de Ti-Pote).

Colonne Type Description
id UUID (PK) Identifiant unique
session_id UUID (FK → ConversationSession) Session parente
role ENUM('user', 'assistant', 'system', 'tool') Rôle dans la conversation
content TEXT Contenu textuel du message
tool_calls JSONB Si le LLM a appelé des functions
tool_result JSONB Si c'est la réponse d'un tool call
audio_url VARCHAR(500) URL du fichier audio (optionnel, pour replay)
tokens_used INTEGER Tokens consommés pour ce message
created_at TIMESTAMPTZ Timestamp du message

MemoryEntry

Un souvenir extrait d'une conversation ou ajouté manuellement. C'est la mémoire épisodique et sémantique.

Colonne Type Description
id UUID (PK) Identifiant unique
user_id UUID (FK → User) Utilisateur concerné
session_id UUID (FK → ConversationSession, nullable) Session source (si extrait d'une conversation)
type ENUM('fact', 'preference', 'episode', 'profile') Type de souvenir
content TEXT Contenu du souvenir en langage naturel
tags TEXT[] Tags pour le filtrage rapide
importance FLOAT Score d'importance (0-1), utilisé pour le ranking
is_active BOOLEAN DEFAULT true Soft delete (l'utilisateur peut "oublier")
created_at TIMESTAMPTZ Date de création
updated_at TIMESTAMPTZ Dernière modification

MemoryEmbedding

Vecteur d'embedding associé à un souvenir, pour la recherche sémantique.

Colonne Type Description
id UUID (PK) Identifiant unique
memory_id UUID (FK → MemoryEntry) Souvenir associé
user_id UUID (FK → User) Pour l'indexation rapide
embedding VECTOR(1536) Vecteur d'embedding
created_at TIMESTAMPTZ Date de création

Index : USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100) — optimisé pour la recherche par similarité cosinus.

Timer

Minuteurs et alarmes.

Colonne Type Description
id UUID (PK) Identifiant unique
user_id UUID (FK → User) Propriétaire
device_id UUID (FK → Device) Robot qui doit sonner
type ENUM('timer', 'alarm') Minuteur ou alarme
label VARCHAR(200) Description ("Pâtes", "Réveil")
trigger_at TIMESTAMPTZ Quand déclencher
recurrence VARCHAR(50) Règle de récurrence (cron-like, nullable)
status ENUM('active', 'triggered', 'cancelled') Statut
created_at TIMESTAMPTZ Date de création

Données en Redis (non persistées en SQL)

Session active (clé : session:{session_id})

Historique des messages de la conversation en cours. TTL configurable (par défaut 3 minutes de silence, voir memory-system.md). Structure : liste ordonnée de messages JSON.

Statut device (clé : device:{device_id}:status)

État en temps réel du robot (connecté, en écoute, en train de parler…). TTL de 60 secondes, rafraîchi par heartbeat.

Timer actif (clé : timer:{timer_id})

Duplication du timer en Redis avec TTL pour la notification via keyspace events. PostgreSQL est la source de vérité (persistance, récurrence), Redis sert uniquement de trigger temps réel.

Migrations

On utilise un outil de migration TypeScript compatible avec NestJS. Options recommandées : TypeORM Migrations, Prisma Migrate, ou Knex Migrations. Le choix sera fait au moment de l'implémentation en fonction de l'ORM retenu.

Chaque migration est versionnée et réversible. Pas de modification directe du schéma en production.