diff --git a/docs/PROFILE_LANGUAGE_SPEC.md b/docs/PROFILE_LANGUAGE_SPEC.md new file mode 100644 index 0000000..0c155bf --- /dev/null +++ b/docs/PROFILE_LANGUAGE_SPEC.md @@ -0,0 +1,90 @@ +# PROFILE_LANGUAGE_SPEC — langue par profil patient (pipeline STT/LLM/TTS) + +> **Destinataire : dev de l'app patiente `com.kazeia`.** +> **Auteur : Kazeia-central.** Stack figée (`FROZEN.md`) → changement décrit ici, livré. +> +> **Objet** : la langue du pipeline doit être **choisie par patient** (porté par le +> profil), pas figée en `"fr"`. Kazeia-central gère/édite la langue par profil et la +> pousse ; l'app doit l'**exposer** (provider) et l'**appliquer** (pipeline). + +--- + +## 0. État observé (vérifié) + +- Le pipeline est **déjà paramétré par langue** : `KazeiaPipeline` passe `context.language` + à `sttEngine.transcribe(audio, context.language)` et `ttsEngine.synthesizeAndPlay(text, + context.language, …)`. `OmniVoiceTtsEngine.langCode()` résout un nom/alias → code (646 + langues). `WhisperHybridEngine.transcribe(audio, language)` prend aussi la langue. +- **MAIS la langue est codée en dur `"fr"`** : `KazeiaService` appelle + `stt.transcribe(audioSnapshot, language = "fr")` (≈ lignes **1100** et **1351**) ; + `core/Pipeline.kt` a `language: String = "fr"` par défaut. La langue **n'est pas lue + depuis le profil**. +- `Profile` (`profiles/Profile.kt`) **n'a aucun champ langue** : `id, displayName, + avatarColor, voiceId, systemPromptOverride, pinHash, createdAt, lastUsedAt, notes, + isDefault`. +- `KazeiaService` a **déjà** l'accès au profil actif et y réagit : + `ProfileStore.get(ctx).activeProfile()` ; `onProfileStoreChanged()` applique déjà + `activeProfile().voiceId → setVoice`. **C'est exactement là** qu'il faut résoudre la + langue. + +--- + +## 1. Adaptation demandée + +### 1.1 `Profile` — nouveau champ `language` +`profiles/Profile.kt` : ajouter +```kotlin +val language: String = "fr", // code/nom résolu par OmniVoiceTtsEngine.langCode() (déf. fr) +``` +- `toJson()` : `put("language", language)`. +- `fromJson()` : `language = js.optString("language", "fr")` (rétro-compat : profils + existants sans le champ → `"fr"`). +- Valeur : un **code court** (`fr`, `en`, `de`, `es`, `it`, `pt`, `ar`, `zh`, …) ou un + nom que `langCode()` sait résoudre. Kazeia-central enverra un code court. + +### 1.2 Provider `/profiles` — exposer + accepter `language` +- `voicesCursor`/`profilesCursor` : ajouter la colonne **`language`** (déf. `"fr"`). +- `upsertProfile(ContentValues)` : lire la clé `language` si présente (sinon conserver + l'existant / défaut `"fr"`). Ajout additif → compat ascendante. +- (Le canal d'écriture riche `profile_upsert_json` du `PROVIDER_RPC_SPEC §4.4` doit aussi + inclure `language` quand il sera implémenté ; en attendant, `update /profiles` avec la + clé `language` suffit pour ce champ simple.) + +### 1.3 `KazeiaService` — appliquer la langue du profil actif +Remplacer le `"fr"` figé par la langue du profil actif (avec repli `"fr"`) : +```kotlin +val lang = ProfileStore.get(applicationContext).activeProfile()?.language ?: "fr" +// … construire le PipelineContext avec language = lang ; +// … stt.transcribe(audio, language = lang) aux ~l.1100 et ~l.1351. +``` +- À recalculer sur changement de profil actif (`onProfileStoreChanged()`, déjà le point + où la voix est ré-appliquée) — pour qu'un changement de patient change la langue à chaud. + +--- + +## 2. Ce que fait Kazeia-central (pour info — pas côté app) + +- Édite la langue par profil et la **pousse** (via `update /profiles` / futur + `profile_upsert_json`). +- Tient la **fiche patient complète** (identité civile, contact/référent, clinique) dans + son **store chiffré**, clé `(serial, profile_id)`. **Cette fiche ne va PAS sur la + tablette** (PII clinique, minimisation on-device). Seul l'**opérationnel** (langue, + voix, prompt) concerne le device. + +--- + +## 3. Récap + +| # | Adaptation app | Criticité | +| --- | --- | --- | +| 1 | `Profile.language` (+ toJson/fromJson, déf. `"fr"`) | requis | +| 2 | `/profiles` expose + accepte `language` | requis | +| 3 | `KazeiaService` : `context.language = activeProfile.language ?: "fr"` (remplace le `"fr"` figé), ré-appliqué au changement de profil | requis | + +Changement borné : le pipeline sait déjà gérer la langue ; il ne manque que **la +relier au profil**. + +--- + +*Source de vérité : `Profile.kt`, `KazeiaService.kt` (~l.1100/1351), `KazeiaPipeline.kt`, +`OmniVoiceTtsEngine.langCode()`, `WhisperHybridEngine.transcribe()`. Vérifié le 2026-06-23.*