6.7 KiB
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 quePROVIDER_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/voicescommeids = basenames(*.wav) ∪ basenames(<id>_voice_prefix.bin)(legacy Qwen3-TTS). Lestatevautreadysi les.binprefix+suffix existent,recordedsi seul le WAV est là, sinonerror. Le dossiercosyvoice/*.cvpsn'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 parvoiceId). Repli gracieux surDEFAULT_VOICE_IDsi le.cvpsest 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.cvpspré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.cvpsprésent estready(CosyVoice = TTS de prod). Proposition de cascade :readysicvps_existsou (prefix+suffix Qwen3) ;recordedsi seul le WAV ;errorsinon.- 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 colonneowner_profile_idà/voices) ; - côté picker/
setVoiceId: masquer/refuser une voix exclusive pour un profil ≠ propriétaire (repli sur la voix par défaut).
- un marqueur de propriété par voix (ex. fichier
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_idpar 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.