Commit Graph

7 Commits

Author SHA1 Message Date
alf 2731b37b20 refactor: architecture modulaire core/features + routers par domaine (phase 1)
Refonte évolutive (pas de page blanche) : on garde le code validé hardware, on impose
la structure.
- core/ : adb, provider, store (socle validé) + context.py (Ctx injecté + guard
  centralisé exceptions→HTTP) + operator_router (santé/unlock/audit).
- features/ : voices/ (ex-voice/, sous-système complet) + fleet/ (service + router) ;
  chaque feature expose make_router(ctx). Ajouter un domaine = un dossier + l'enregistrer.
- app.py slim : compose les routers (_FEATURES). Fini le monolithe api/app.py.
- imports migrés, __main__ et tests adaptés. VoiceWorkerError.http_status=503 (découplage).
- 42/42 ; validé live (santé, devices, voices/catalog).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 16:57:26 +02:00
alf 2894771bf4 feat(voice): ré-enrôlement (régénère .ovsp + re-déploie) — pour appliquer le fix §7
deploy_voice réutilise le .ovsp stocké → une voix enrôlée avec l'ancien cap garde son
artefact pollué. reenroll_voice : ré-enrôle depuis le WAV archivé (enroll §7) + re-pousse
sur toutes les tablettes déployées. API POST /voices/catalog/{id}/reenroll, bouton UI
(si WAV archivé). tests +2. 42/42.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 23:45:52 +02:00
alf 6640cfd79e feat(voice): catalogue GLOBAL (flotte) + déploiement par tablette
Refonte : une voix n'est plus liée à sa tablette d'enregistrement. Catalogue central
clé voice_id, .ovsp stocké chiffré → déployable sur N tablettes sans ré-enrôler.
- store : voices PK voice_id (+ origin_serial, ovsp_enc) + table voice_deployments
  (many-to-many) + migration de l'ancien schéma (serial,voice_id)→global. Méthodes
  globales + add/remove_deployment, voices_on, deployments_of, store/voice_ovsp_bytes.
- orchestrator : catalog(), deploy_voice/undeploy_voice (scope-conscient), list_for_tablet
  (chargées + chargeables), ingest_voice/sync_admin au catalogue global. Suppr. legacy
  /voices-based (list_voices/prepare/enroll_deploy/sync_device).
- API : GET /voices/catalog, POST /voices/catalog/{vid}/lock|unlock, GET /voices/{serial}
  (chargées+chargeables), POST /voices/{serial}/deploy|undeploy.
- UI : carte Catalogue flotte (visible sans tablette) + par tablette chargées/chargeables
  avec charger/retirer.
- tests réécrits (40/40). Validé live : catalog [], /voices/{serial} chargées+chargeables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 19:39:00 +02:00
alf 06b5cda214 feat(voice): ingestion depuis le stockage ADMIN (manifestes record-time §4.2)
Comble le gap : les voix fraîches + leur manifeste (scope/owner/reference_text) vivent
dans Android/data/com.kazeia.admin/files/voix/ (VoiceStorage.kt), pas dans /voices.
- admin_ingest.py : list_manifests (ls + pull .json), delete_admin_voice. Schéma fondé
  sur VoiceStorage.save().
- orchestrator : list_admin_voices, ingest_voice (pull→archive→scope/owner/consentement
  →verrou si exclusive→enroll ASR→deploy selon scope), sync_admin. pending = archivée,
  ni enrôlée ni déployée. exclusive owner absent = verrou_profil_absent.
- _patient_omnivoice_dir robuste : env → wav_path legacy → sonde MODELS_DIR device
  (post-migration /voices.wav_path est VIDE pour une voix déployée).
- store : colonnes scope/owner_name/consent_text_enc (consentement chiffré, PII) +
  migration + set_voice_manifest. consent_text déchiffré dans voice_record/voices.
- autosync → sync_admin (le déclencheur auto ingère depuis l'admin).
- API : GET /voices/{serial}/admin, POST /voices/{serial}/admin/sync.
- enroll = ASR du 16s (migration §6) ; reference_text gardé comme trace de consentement.
- tests +8. 46/46. Validé live : manifeste synthétique → .ovsp déployé sur tablette.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 10:30:58 +02:00
alf adc0904009 feat(voice): migration CosyVoice → OmniVoice (.cvps → .ovsp)
Suit docs/OMNIVOICE_VOICE_MIGRATION.md (app patiente migrée vc17+, 22/06).
- enroll.py : réécriture encodeur OmniVoice (create_voice_clone_prompt → format binaire
  OVSP : magic|T_ref|text_len|tokens[8*T_ref]|ref_text). Cap réf 16s. prepare_wav 24kHz.
- transcribe.py : ASR interne OmniVoice (plus d'openai-whisper, absent de ov_venv).
- transfer.py : omnivoice_dir_for (<MODELS_DIR>/omnivoice/voices), deployed_ovsp,
  push_ovsp, suffixe .ovsp.
- bridge.py : OV_PYTHON (/opt/Kazeia/ov_venv). worker dispatch inchangé.
- orchestrator.py : .ovsp, ovsp_bytes ; verrou d'exclusivité + archive WAV INCHANGÉS.
- store : colonne cvps_bytes → ovsp_bytes (+ migration ALTER RENAME idempotente).
- tests adaptés. 39/39.
- validé live sous ov_venv : enroll → .ovsp format byte-identique à damien.ovsp (réf
  engine) ; auto-ASR FR correcte ; bridge ping/transcribe/enroll chaud OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 10:15:11 +02:00
alf 23033384c7 feat(voice): verrou voix↔profil + sync verrou-conscient + déclencheur auto
- store : colonne locked_profile_id (+ migration ALTER idempotente), lock_voice/
  unlock_voice. Verrou = assignation exclusive à un profil ; NULL = voix généraliste.
- orchestrator : list_voices expose locked_profile_id + used_by_profiles (croise
  profile.voice_id) ; sync_device enrôle+déploie les voix en attente, REFUSE de
  déployer une voix verrouillée sur une tablette sans le profil propriétaire.
- autosync : VoiceAutoSync — watcher de connexions armable depuis l'UI (désarmé par
  défaut), statut par tablette, ne fait rien si store verrouillé (PII), delete opt-in.
- API : lock/unlock, POST /{serial}/sync, GET/POST /voices/autosync (déclarées AVANT
  /{serial} pour éviter la capture "autosync" comme serial).
- tests : +8 (verrou, sync verrou-conscient, autosync). 39/39.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 16:20:55 +02:00
alf 4ad513afda feat(voice): orchestration enrôlement — pull/archive/transcribe/enroll/deploy
- store : table voices + WAV chiffré sur disque (archive_voice_wav, voice_wav_bytes,
  transcription chiffrée, marques archived/enrolled/deployed/wav_deleted).
- voice/transfer.py : dérivation du dossier cosyvoice depuis wav_path (device-dépendant),
  pull WAV / push .cvps / delete WAV device (retour vérifié §9).
- voice/orchestrator.py : list_voices (statut par voix), prepare (pull+archive+whisper),
  enroll_deploy (enroll+push + suppression WAV device optionnelle, opt-in).
- API : GET /api/voices/{serial}, /voices/health, POST .../prepare, .../enroll.
- tests : +10 (chemins, archive chiffrée, orchestration via faux adb/bridge). 33/33.
- validé live : jerome (muet) → transcrit FR → .cvps déployé sur tablette + WAV
  archivé chiffré ; WAV device conservé (delete_source=false).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:39:29 +02:00