docs: VOICE_DEPLOYMENT_SPEC — adaptations app patiente pour déploiement .cvps
Spec livrée au dev kazeia-android (stack figée §11) : - BLOQUANT : /voices doit inclure cosyvoice/*.cvps (cvps_exists, state=ready, ids∪cvps) — sinon une voix dont le WAV est supprimé (workflow voulu) DISPARAÎT de l'inventaire. - voices_reload (rescan + ACTION_RELOAD_VOICES) après push direct. - conditionnel : exclusivité on-device si tablettes multi-patients (sinon la règle de déploiement côté central suffit en mono-patient/tablette). Fondé sur le code réel (voicesCursor blind aux .cvps, résolution par nom de fichier, pas de notion de propriété). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
23033384c7
commit
94f8dc0993
|
|
@ -0,0 +1,126 @@
|
||||||
|
# VOICE_DEPLOYMENT_SPEC — adaptations app patiente pour le déploiement des voix `.cvps`
|
||||||
|
|
||||||
|
> **Destinataire : dev de l'app patiente `com.kazeia`.**
|
||||||
|
> **Auteur : Kazeia-central.** Même démarche que `PROVIDER_RPC_SPEC.md` /
|
||||||
|
> `VOICE_ENROLLMENT_SPEC.md` : la stack patiente est **figée** (`FROZEN.md`), donc
|
||||||
|
> ces changements sont décrits ici et livrés, pas réécrits sauvagement.
|
||||||
|
>
|
||||||
|
> **Contexte** : Kazeia-central devient le maître de l'enrôlement et du déploiement des
|
||||||
|
> voix CosyVoice (`.cvps`). Il enrôle un WAV → `.cvps`, le pousse sur la tablette, et
|
||||||
|
> **supprime le WAV d'origine** (archivé chiffré côté PC). Le workflow exige trois
|
||||||
|
> adaptations côté app, dont une **bloquante** (sans elle, une voix déployée disparaît
|
||||||
|
> de l'inventaire).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. État observé (code actuel, vérifié)
|
||||||
|
|
||||||
|
- `KazeiaTelemetryProvider.voicesCursor()` construit l'inventaire `/voices` comme
|
||||||
|
`ids = basenames(*.wav) ∪ basenames(<id>_voice_prefix.bin)` (legacy **Qwen3-TTS**).
|
||||||
|
Le `state` vaut `ready` si les `.bin` prefix+suffix existent, `recorded` si seul le
|
||||||
|
WAV est là, sinon `error`. **Le dossier `cosyvoice/*.cvps` n'est jamais lu.**
|
||||||
|
- TTS de prod = **CosyVoice uniquement** (`CosyVoiceTtsEngine`). Résolution voix purement
|
||||||
|
par nom : `cvpsPath(voiceId) = "<modelsDir>/cosyvoice/<voiceId>.cvps"`,
|
||||||
|
`ensureVoice()` charge le `.cvps` à la demande (cache par `voiceId`). Repli gracieux
|
||||||
|
sur `DEFAULT_VOICE_ID` si le `.cvps` est absent.
|
||||||
|
- `Profile.voiceId` (nullable) = la voix d'un profil ; `KazeiaService.setVoiceId()` →
|
||||||
|
`CosyVoiceTtsEngine.setVoice()`. **Aucune notion d'« exclusivité/propriété » d'une
|
||||||
|
voix par un profil** : un `.cvps` présent est chargeable par n'importe quel profil.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Adaptation **BLOQUANTE** — `/voices` doit refléter les `.cvps` déployés
|
||||||
|
|
||||||
|
**Problème** : Kazeia-central supprime le WAV de la tablette après enrôlement (le WAV
|
||||||
|
est archivé sur le PC). Une voix qui n'a plus que son `.cvps` n'a **ni `.wav` ni
|
||||||
|
embedding Qwen3** → l'`ids = wav ∪ prefix` actuel **l'omet**. La voix est déployée et
|
||||||
|
fonctionnelle, mais **invisible** de `/voices` (donc du picker admin et de
|
||||||
|
Kazeia-central). Incident « zelda » généralisé.
|
||||||
|
|
||||||
|
**Demande** : étendre `voicesCursor()` pour **inclure les basenames de
|
||||||
|
`<modelsDir>/cosyvoice/*.cvps`** dans l'union d'ids, et exposer leur présence.
|
||||||
|
|
||||||
|
- Union d'ids = `basenames(*.wav) ∪ basenames(prefix.bin) ∪ basenames(*.cvps)`.
|
||||||
|
- Nouvelle colonne **`cvps_exists`** (0/1) à `/voices`.
|
||||||
|
- `state` : une voix avec `.cvps` présent est **`ready`** (CosyVoice = TTS de prod).
|
||||||
|
Proposition de cascade : `ready` si `cvps_exists` **ou** (prefix+suffix Qwen3) ;
|
||||||
|
`recorded` si seul le WAV ; `error` sinon.
|
||||||
|
- Colonnes inchangées par ailleurs (compat ascendante : ajout additif d'une colonne).
|
||||||
|
|
||||||
|
> Sans cette adaptation, le reste du workflow « enrôle → déploie → supprime le WAV »
|
||||||
|
> casse l'inventaire. C'est le **prérequis n°1**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Adaptation — `voices_reload` (rescan après push direct)
|
||||||
|
|
||||||
|
Kazeia-central pousse le `.cvps` par `adb push` direct (hors WebDAV). Le `.cvps` est
|
||||||
|
**utilisable immédiatement** (chargé à la demande par `ensureVoice()` au prochain usage
|
||||||
|
de la voix), mais l'**inventaire** `/voices` et le picker admin ne se rafraîchissent pas
|
||||||
|
tant qu'aucun évènement ne le déclenche.
|
||||||
|
|
||||||
|
**Demande** : méthode `call()` **`voices_reload`** (déjà listée nice-to-have dans
|
||||||
|
`PROVIDER_RPC_SPEC §9`) — re-scanne le dossier voix et **diffuse `RELOAD_VOICES`**
|
||||||
|
(constante `ACTION_RELOAD_VOICES` à créer au passage ; aujourd'hui la string est en dur,
|
||||||
|
cf. provider ~l.656). Permet à Kazeia-central de finaliser un déploiement par un refresh
|
||||||
|
propre, sans aller-retour WebDAV ni redémarrage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Politique de déploiement (RAPPEL — appliquée côté Kazeia-central)
|
||||||
|
|
||||||
|
Pour mémoire, **pas une demande de code app** : la règle est **enforced par
|
||||||
|
Kazeia-central au moment du déploiement** —
|
||||||
|
- une voix **verrouillée** (exclusive à un profil patient) n'est poussée **que** sur
|
||||||
|
la/les tablette(s) où ce profil est déployé ;
|
||||||
|
- seules les voix **globales/non-exclusives** sont déployées sur les tablettes (parc).
|
||||||
|
|
||||||
|
L'app n'a donc **rien à faire** pour cette règle dans le cas **1 patient ↔ 1 tablette**
|
||||||
|
(modèle de prod implicite, cf. `CLAUDE.md §9` « serial → label patient ») : l'exclusivité
|
||||||
|
= la localisation du `.cvps`, décidée par le PC.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Adaptation **CONDITIONNELLE** — exclusivité on-device (tablettes multi-patients)
|
||||||
|
|
||||||
|
⚠️ **À trancher (Richard / dev)** : si une tablette héberge **plusieurs profils
|
||||||
|
patients** (observé sur la tablette de dev : `p_test01`, `p_test02`), alors un `.cvps`
|
||||||
|
exclusif à A, présent sur la tablette parce que A y est, est **techniquement chargeable
|
||||||
|
par le profil B** (résolution par nom de fichier, aucun contrôle de propriété). La règle
|
||||||
|
« déploiement uniquement où le profil est » ne suffit alors plus à garantir l'exclusivité
|
||||||
|
*à l'intérieur* d'une tablette partagée.
|
||||||
|
|
||||||
|
Deux options :
|
||||||
|
- **(a) Mono-patient par tablette (recommandé, défaut supposé)** : aucune adaptation.
|
||||||
|
L'exclusivité = la localisation du `.cvps`. C'est le modèle « une tablette = un
|
||||||
|
patient » du parc.
|
||||||
|
- **(b) Multi-patients par tablette** : l'app doit connaître le **propriétaire** d'une
|
||||||
|
voix exclusive et empêcher un autre profil de la sélectionner. Nécessite :
|
||||||
|
- un marqueur de propriété par voix (ex. fichier `cosyvoice/<id>.owner` = profileId,
|
||||||
|
poussé par Kazeia-central, ou colonne `owner_profile_id` à `/voices`) ;
|
||||||
|
- côté picker/`setVoiceId` : masquer/refuser une voix exclusive pour un profil ≠
|
||||||
|
propriétaire (repli sur la voix par défaut).
|
||||||
|
|
||||||
|
> Décision attendue : **(a)** suffit si le parc est mono-patient/tablette. Sinon **(b)**
|
||||||
|
> et on étend cette spec. Kazeia-central est prêt pour les deux (il connaît déjà
|
||||||
|
> `locked_profile_id` par voix).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Récap — ce qui est demandé au dev app patiente
|
||||||
|
|
||||||
|
| # | Adaptation | Criticité | Forme |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | `/voices` inclut les `.cvps` (`cvps_exists`, `state=ready`, ids ∪ cvps) | **bloquant** | curseur `voicesCursor()` |
|
||||||
|
| 2 | `voices_reload` (rescan + `RELOAD_VOICES`, constante `ACTION_RELOAD_VOICES`) | important | méthode `call()` |
|
||||||
|
| 4 | exclusivité on-device | conditionnel (si multi-patients/tablette) | à décider §4 |
|
||||||
|
|
||||||
|
Côté Kazeia-central (déjà fait / à faire, **pas** côté app) : enrôlement, archive
|
||||||
|
chiffrée du WAV, suppression du WAV device, **politique de déploiement verrou-conscient**,
|
||||||
|
et la **distribution flotte** (pousser les voix globales sur le parc, les exclusives chez
|
||||||
|
leur patient) — en cours.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Source de vérité : `KazeiaTelemetryProvider.voicesCursor()`, `CosyVoiceTtsEngine.kt`,
|
||||||
|
`Profile.kt`, `KazeiaService.setVoiceId()` (kazeia-android). Vérifié le 2026-06-19.*
|
||||||
Loading…
Reference in New Issue