DOCS/test-plan-studio-narration.md
Thomas Fransolet a588c53549 Studio : ordre de passage du plan de test et socle d'upload
Le plan de test gagne un § 0bis : le lot 0 a réécrit tout le chemin d'upload
(URL V4 signée puis POST /ingest) et rien n'a jamais été déposé depuis un
navigateur, alors que les § 1 à 5 en dépendent tous. Ajout aussi de l'ordre de
passage en quatre séances, de la façon de reporter un échec, et des résultats
déjà obtenus sur le § 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 15:18:40 +02:00

248 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Plan de test — Studio lots 6 à 8 (mention IA, calibrage, narration, casting)
> Écrit le 2026-09-15, pour une première passe le 16/09. **Rien de ce plan n'a encore tourné en réel** :
> tout est couvert par des tests unitaires (357 verts côté service) et l'analyse Flutter / TypeScript, mais
> aucun écran n'a été affiché et aucun audio n'a été généré. Les résultats viennent de Thomas, pas du code.
>
> Légende : colonne ✓ à remplir (✅ / ❌ + une note). Un ❌ dans § 0 arrête la suite.
> Référence : [v2/studio-plan.md](v2/studio-plan.md) § 9.4 (calibrage) et lot 8.
---
## Comment passer ce plan — ajouté le 2026-09-16
**Quatre séances, dans cet ordre.** Chacune est arrêtée par la précédente : inutile de chercher un bug
de narration si l'upload est cassé en dessous.
| Séance | Sections | Durée | Ce qu'un ❌ arrête |
|---|---|---|---|
| **1. Le socle** | § 0, § 0bis | ~45 min | Tout. Rien d'autre ne se teste |
| **2. L'argent et l'image** | § 1, § 2, § 2bis | ~1 h | Les § 3 à 5 (pas de génération fiable, pas de narration) |
| **3. La narration** | § 3, § 4 | ~1 h 30 | Les § 5 et 5bis côté visiteur |
| **4. Les trois fronts** | § 5, § 5bis, § 6 | ~1 h | Rien — c'est la fin |
**Comment reporter.** Un ✅ tout court suffit quand c'est conforme. Un ❌ demande trois choses et pas
une de plus : *ce que tu as fait*, *ce que tu as vu*, *ce que tu attendais*. Une capture vaut mieux
qu'une description pour tout ce qui est visuel. N'essaie pas de diagnostiquer — c'est mon travail, et
un diagnostic dans la colonne ✓ masque souvent le vrai symptôme.
**Ce qui n'est pas un bug** : la liste des limites connues en fin de document. À lire **avant** de
commencer, pas après avoir signalé l'une d'elles.
---
## 0. Prérequis — une fois
| # | Étape | Attendu | ✓ |
|---|---|---|---|
| 0.1 | `winget install Gyan.FFmpeg`, rouvrir le terminal, `ffmpeg -version` | Une version s'affiche. Sans ffmpeg, la génération audio échoue et les crédits sont remboursés | ✅ 16/09 — 9.0.1-full_build, alias winget dans le Path persistant |
| 0.2 | `dotnet user-secrets set "AI:ApiKey" "<clé Gemini>" --project manager-service/ManagerService` | `dotnet user-secrets list` montre `AI:ApiKey` (et `Studio:Fal:Key`) | ✅ 16/09 — « Successfully saved AI:ApiKey » (Thomas) |
| 0.3 | Lancer Postgres puis `dotnet ef database update` avec `MIGRATIONS_CONNECTION` (voir la note mémoire EF) | Migrations appliquées : `AddAiWatermarkBurned`, `StudioCalibrationEngravingStyle`, `StudioCreditUnitPerReference`, `AddNarrators`, `AddGeoPointAudio`, `AddCasting` | ✅ 16/09 — les 6 appliquées en une passe, « Done. » |
| 0.4 | `dotnet run` (manager-service), `flutter run -d chrome` (manager-app) | Les deux démarrent, connexion SuperAdmin OK | |
| 0.5 | **Dialogue d'instance** — il n'est pas dans un écran : **pied du menu de gauche → icône ⇄ « Changer d'instance »** (visible seulement en SuperAdmin) → dans la liste, le **crayon** « Configurer le plan » de la ligne voulue. Activer **Assistant IA**, puis **Studio**, puis accorder **100 crédits** | Solde 100 dans la jauge du menu | |
| 0.5b | ⚠️ Dans ce même dialogue, **décocher Assistant IA** | L'interrupteur **Studio se désactive tout seul** et devient grisé : pas de Studio sans assistant (décision 12). Recocher les deux avant de continuer | |
| 0.6 | Guide IA Personnages : un personnage **Léon** avec une **voix** (Umbriel) et une intonation, un second **La sentinelle** avec une autre voix (Kore) | Les deux apparaissent dans la liste, sans « (sans voix) » | |
| 0.7 | Donner un **portrait** à Léon (onglet Visage → générer ou choisir une vue Portrait) | La vue Portrait est enregistrée | |
---
## 0bis. Le socle d'upload — ajouté le 2026-09-16
> **Pourquoi cette section n'existait pas, et pourquoi elle doit exister.** Le lot 0 a **réécrit tout le
> chemin d'upload** de manager-app : plus aucun appel au SDK Firebase, tout passe par une URL V4 signée
> puis `POST /ingest`. C'est codé depuis le 13/09 et couvert par des tests unitaires, mais **aucun
> fichier n'a jamais été déposé depuis un navigateur**. Les § 1 à 5 supposent tous que ça marche : la
> médiathèque reçoit les images générées, les MP3 de narration, les portraits. Tester la narration
> avant ce socle, c'est chercher une fuite au toit avant d'avoir regardé les fondations.
| # | Étape | Attendu | ✓ |
|---|---|---|---|
| 0b.1 | Médiathèque : déposer une **image JPEG** de plus de 2560 px | Elle monte (barre de progression), apparaît dans la grille, s'ouvre. Réduite à 2560 px côté long | |
| 0b.2 | Déposer un **PNG à transparence** | Reste un PNG, l'alpha est préservé (le voir sur fond coloré) | |
| 0b.3 | Déposer un **MP3**, un **PDF** | Montent tels quels, lisibles / téléchargeables depuis la fiche | |
| 0b.4 | **Remplacer** le fichier d'une ressource existante | Le nouveau s'affiche partout où la ressource était utilisée, l'ancien ne traîne pas | |
| 0b.5 | **Supprimer** une ressource non utilisée | Disparaît de la grille ; recharger la page ne la ramène pas | |
| 0b.6 | ⚠️ Fichier **au-dessus du plafond de son type** (ex. une image > 30 Mo) | Refusé avec un message traduit, **pas** une erreur brute ni un échec muet. Tester en FR, EN **et** NL | |
| 0b.7 | ⚠️ **Sélecteur de ressource** : champ image d'une section → ajouter un fichier depuis la modale → Enregistrer | Le fichier arrive **et** le champ de la section le pointe. C'est le piège historique : la modale embarque `ResourcesScreen`, une régression ici touche les 13 types de section | |
| 0b.8 | Ouvrir une ressource déposée depuis **visitapp-web** et depuis **mymuseum-visitapp** | L'URL s'ouvre des deux côtés (le jeton de téléchargement est bien posé à l'écriture) | |
| 0b.9 | Remplacer l'image d'une visite **déjà téléchargée** dans mymuseum-visitapp | L'app la re-télécharge, n'affiche pas l'ancienne | |
| 0b.10 | Sélecteur de langues d'une configuration | **Le luxembourgeois (LB) est proposé**, avec son drapeau (ajouté au lot 0, jamais vu à l'écran) | |
---
## 1. Mention IA dans le fichier (lot 6)
| # | Étape | Attendu | ✓ |
|---|---|---|---|
| 1.1 | Studio : générer une image et la **valider** | L'image arrive dans la médiathèque avec la facette IA | |
| 1.2 | Télécharger le fichier validé, l'ouvrir dans un lecteur de métadonnées (ex. exiftool, ou Propriétés Détails) | XMP `DigitalSourceType = trainedAlgorithmicMedia`, droits = nom de l'instance | |
| 1.3 | Dialogue d'instance : cocher **Graver la mention IA**, générer et valider une nouvelle image | Bandeau « Généré par IA · AI-generated » en bas à droite de l'image. L'ancienne image n'a pas changé | |
| 1.4 | ⚠️ Enregistrer l'instance **depuis un autre écran** (ex. Abonnement) | La case gravure reste cochée : un enregistrement qui ne l'envoie pas ne la remet pas à faux | |
| 1.5 | Décocher la gravure | Les images suivantes n'ont plus de bandeau | |
---
## 2. Calibrage et unité de crédit
| # | Étape | Attendu | ✓ |
|---|---|---|---|
| 2.1 | Studio Identité : ouvrir la grille des styles | **8 tuiles avec un moulin**, un style chacune (plus aucune tuile vide) | |
| 2.2 | Guide IA Personnages Visage : grille des archétypes | **12 mannequins au trait**, un par pose | |
| 2.3 | Générer 3 variantes **sans image de référence** | Estimation et débit = **6 crédits** (2 par image) | |
| 2.4 | Ajouter 3 images de référence à l'identité, relancer 3 variantes | Estimation = **15 crédits** (3 × (2 + 3)) | |
| 2.5 | Style Gravure, gabarit « Décor d'énigme », 3 variantes | Images **noir et blanc**, **aucune légende ni signature** (c'était ~4/10 avant correction) | |
| 2.6 | Gabarit « Objet d'époque » depuis une photo d'objet **portant une date** | La date reste gravée sur l'objet | |
| 2.7 | Relire les planches `outputs/studio-calibrage-2026-09-15/` | Ton verdict « du même projet » par série, à reporter dans studio-plan § 9.4 | |
---
## 2bis. Les crédits, pour de vrai — ajouté le 2026-09-16
> Le § 2 vérifie que **l'estimation** affichée est juste. Cette section-ci vérifie que **le solde**
> bouge juste, ce qui n'est pas la même chose : le mécanisme est une réservation puis un débit réel,
> avec remboursement en cas d'échec, et rien de tout ça n'a tourné en conditions réelles.
| # | Étape | Attendu | ✓ |
|---|---|---|---|
| 2b.1 | Noter le solde, lancer une génération de 3 variantes, **regarder la jauge pendant** le travail | Le solde baisse **dès le lancement** (réservation), pas à l'arrivée des images | |
| 2b.2 | À la fin | Le débit définitif égale l'estimation annoncée, ni plus ni moins | |
| 2b.3 | Fermer le panneau **sans valider** aucune variante | Les crédits restent dépensés (l'appel a coûté), mais **rien** ne s'ajoute au quota de stockage | |
| 2b.4 | Lancer **deux générations coup sur coup** sans attendre la première | Les deux réservations se cumulent ; le solde ne descend jamais sous zéro | |
| 2b.5 | Solde insuffisant pour 3 variantes mais suffisant pour 1 | Refus **avant** l'appel, message clair, aucun crédit pris | |
| 2b.6 | ⚠️ Génération qui échoue (couper le réseau du serveur vers fal, ou fausse clé) | Le solde **revient** à sa valeur d'avant. C'est le point le plus important de la section | |
| 2b.7 | Abonnement journal des crédits | Une ligne par mouvement (réservation, débit, remboursement), export CSV lisible | |
| 2b.8 | SuperAdmin : accorder 50 crédits à l'instance | Solde +50, et la **date d'expiration repoussée à +12 mois sur tout le solde** (pas seulement sur les 50) | |
---
## 3. Qui raconte quoi (lot 8a et 8c)
Préparer une configuration avec un **parcours de 4 étapes** (titre + description remplis en FR et NL) et un **article** avec du texte.
| # | Étape | Attendu | ✓ |
|---|---|---|---|
| 3.1 | Écran Configuration : carte **Narration** visible | Deux champs : « Guide de cette visite » et « Narrateur par défaut de la visite » | |
| 3.2 | Guide de la visite = *Guide de l'instance*, narrateur par défaut = *Le guide raconte*, **Enregistrer** | Enregistré ; recharger l'écran garde les valeurs | |
| 3.3 | Ouvrir le parcours : dans le rail, une entrée **Narrateurs** sous « Parcours » | L'onglet s'ouvre, une ligne par étape | |
| 3.4 | Sans rien choisir | Chaque étape affiche en gris « ↳ » + le guide de l'instance, ou « Aucun narrateur » s'il n'y en a pas | |
| 3.5 | Narrateur par défaut du parcours = **Léon** | Toutes les lignes passent à « ↳ Léon » en gris | |
| 3.6 | Étape 3 : choisir **La sentinelle** | La ligne 3 affiche La sentinelle **en gras** ; les autres restent « ↳ Léon » | |
| 3.7 | Fermer et rouvrir le parcours | Le choix de l'étape 3 et le défaut sont conservés | |
| 3.8 | **Toutes les étapes suivent le défaut** | Confirmation « Retirer le narrateur choisi sur 1 étape(s) ? », puis l'étape 3 repasse à « ↳ Léon » | |
| 3.9 | Remettre La sentinelle sur l'étape 3 (pour la suite) | — | |
| 3.10 | ⚠️ Archiver La sentinelle (Personnages) puis revenir à l'onglet | L'étape 3 retombe sur « ↳ Léon » : un personnage archivé ne parle plus | |
| 3.11 | Désarchiver La sentinelle | L'étape 3 la retrouve | |
| 3.12 | Instance **sans Studio** (décocher dans le dialogue) | L'entrée Narrateurs disparaît du rail et le bloc Narration de l'article aussi. Réactiver ensuite | |
---
## 4. Générer l'audio (lot 8b et 8c)
| # | Étape | Attendu | ✓ |
|---|---|---|---|
| 4.1 | Onglet Narrateurs : colonne Audio | « 2 à générer · N crédits » par étape (FR + NL) ; barre du bas « X fichier(s) audio à générer · Y crédits » | |
| 4.2 | **Générer l'audio** | Confirmation qui répète le total. Rien ne part avant « Oui » | |
| 4.3 | Confirmer | Notification « Génération lancée » ; en moins d'une minute les lignes passent à **Audio à jour** sans rien toucher (relecture toutes les 5 s) | |
| 4.4 | Jauge de crédits | Débit ≈ **1 crédit par minute d'audio par langue**, jamais plus que l'estimation affichée | |
| 4.5 | Médiathèque, filtre Audio | Un MP3 par étape et par langue, libellé « Léon · titre (FR) », facette IA | |
| 4.6 | Écouter un MP3 (étape 3) | Voix de **La sentinelle**, dans la bonne langue, avec l'intonation du personnage | |
| 4.7 | ⚠️ **Piège corrigé** : modifier le **titre** d'une étape juste après la génération, attendre l'enregistrement, rouvrir | L'audio de l'étape est **toujours rattaché** (avant correction, l'enregistrement le détachait) | |
| 4.8 | Revenir à l'onglet Narrateurs | L'étape modifiée est « 2 à régénérer » : son texte a changé | |
| 4.9 | Changer la voix de Léon (Personnages Voix) | Toutes les étapes de Léon passent « à régénérer » ; celle de La sentinelle reste à jour | |
| 4.10 | Régénérer | L'ancien MP3 **disparaît** de la médiathèque, le nouveau le remplace | |
| 4.11 | Étape avec un **audio déposé à la main** en FR, puis générer FR | L'audio généré prend sa place dans l'étape, **le fichier déposé reste** dans la médiathèque | |
| 4.12 | Article : bloc **Narration audio** sous les réglages audio | Narrateur « ↳ Léon » (hérité), état, bouton Générer | |
| 4.12b | Carte : réglages de la section, **Narrateur par défaut des points** = La sentinelle, enregistrer la section | Chaque fiche de point affiche « ↳ La sentinelle » dans son bloc Narration audio | |
| 4.12c | Carte : ouvrir un point, choisir **Léon** dans son bloc Narration audio | Le point s'enregistre seul ; moins d'une seconde après, l'état se relit (Léon, « à générer ») | |
| 4.12d | Carte : générer l'audio d'un point, puis modifier son titre | L'audio reste rattaché au point après rechargement ; l'état passe « à régénérer » | |
| 4.12e | web : ouvrir ce point sur la carte | Lecteur audio en tête de la fiche, portrait + nom du narrateur au-dessus (lot 8e) | |
| 4.12f | mymuseum-visitapp : ouvrir ce point | Lecteur audio au-dessus de la description, portrait + nom | |
| 4.12g | tablet-app : ouvrir ce point | Portrait + nom à gauche du bouton de lecture | |
| 4.12h | Les trois : langue sans audio pour ce point | Pas de lecteur sur mobile et tablette (pas de repli de langue) ; le web retombe sur la première langue, comme pour l'article | |
| 4.13 | Article : générer, puis **Enregistrer** la section après le passage à « Audio à jour » | L'audio reste sur l'article après rechargement | |
| 4.14 | Personnage **sans voix** en narrateur, Générer | Message « Ce narrateur n'a pas de voix », aucun crédit réservé | |
| 4.15 | Solde à 0, Générer | Message « Crédits Studio insuffisants » | |
| 4.16 | Couper le réseau du **serveur** vers Gemini (ou mauvaise clé), Générer | Les lignes ne passent jamais à jour, et les crédits réservés sont **remboursés** (jauge) | |
---
## 5. Côté visiteur — le portrait du narrateur (lot 8d)
Même configuration, avec l'audio généré en § 4. Léon a un portrait, La sentinelle non.
| # | App | Étape | Attendu | ✓ |
|---|---|---|---|---|
| 5.1 | visitapp-web | Ouvrir l'**article** | Le lecteur audio joue ; à la place de « GUIDE AUDIO », **portrait + « Léon »** | |
| 5.2 | visitapp-web | Parcours **en liste**, étapes 1 et 3 | L'audio **joue** (avant, un audio rangé par identifiant n'avait pas de lecteur) ; portrait de Léon à l'étape 1, disque neutre + « La sentinelle » à l'étape 3 | |
| 5.3 | visitapp-web | Parcours **en carte** (`showMap`) | Même chose dans la vue carte | |
| 5.4 | visitapp-web | Changer la langue en NL | L'audio NL joue, même narrateur | |
| 5.5 | mymuseum-visitapp (en ligne) | Article | Lecteur replié à droite ; déplié, le **portrait** ouvre la ligne | |
| 5.6 | mymuseum-visitapp (en ligne) | Parcours, vue contenu **et** vue carte | L'audio de l'étape joue, portrait + nom au-dessus du lecteur | |
| 5.7 | mymuseum-visitapp (**hors ligne**) | Article téléchargé, mode avion | L'audio joue ; **pas de portrait** (limite connue, il n'est pas téléchargé) | |
| 5.8 | tablet-app | Article | Portrait + nom à gauche du bouton de lecture | |
| 5.9 | Les trois | Un audio **déposé à la main** (pas une narration) | Lecteur habituel, **aucun** portrait | |
| 5.10 | Les trois | Une ancienne étape dont l'audio est une **URL** | L'audio joue toujours | |
---
## 5bis. Casting — « les personnes que vous allez rencontrer » (lot 8bis)
Migration supplémentaire à appliquer : `AddCasting`. Même configuration qu'en § 3 (Léon et La sentinelle avec portrait,
ajouter **La cuisinière sans portrait** comme narratrice d'une étape).
| # | App | Étape | Attendu | ✓ |
|---|---|---|---|---|
| 5b.1 | manager | Configuration **sans aucun narrateur ni guide** | Carte « Personnages de la visite » : « Aucun personnage ne raconte cette visite » | |
| 5b.2 | manager | Seul le guide (Léon) raconte | « Léon raconte toute cette visite (le guide) », **ni liste ni interrupteur** | |
| 5b.3 | manager | Étape 3 = La sentinelle, étape 4 = La cuisinière | Liste de 3 : Léon en **gris** « ↳ » (hérité), les deux autres en gras, avec « N étape(s) » ; ordre = ordre d'apparition dans la visite | |
| 5b.4 | manager | Ligne La cuisinière | « Pas de portrait : ne peut pas être montré », case grisée | |
| 5b.5 | manager | Réordonner (glisser La sentinelle en tête), décocher Léon, armer « Présenter les personnages au visiteur », **Enregistrer**, recharger | Ordre, cases et interrupteur conservés | |
| 5b.6 | manager | Toucher l'ordre puis **Annuler** | Retour à l'état enregistré | |
| 5b.7 | web | Ouvrir la visite (Léon décoché) | **Pas** d'écran d'entrée : un seul portrait montré | |
| 5b.8 | manager + web | Recocher Léon, enregistrer, rouvrir la visite (attendre ~1 min, cache) | Écran d'entrée : La sentinelle puis Léon, portraits ronds, bouton « Commencer la visite » | |
| 5b.9 | web | Fermer, revenir à la liste des sections | L'écran ne revient pas dans la même session ; un nouvel onglet le remontre | |
| 5b.10 | web | Passer en NL | Titre « De personen die u zult ontmoeten » | |
| 5b.11 | mymuseum-visitapp | Ouvrir la visite | Même écran, une fois par lancement de l'app | |
| 5b.12 | tablet-app | Charger la configuration | Même écran au chargement | |
| 5b.13 | manager | Désarmer l'interrupteur, enregistrer | Plus aucun écran d'entrée sur les trois apps | |
---
## 6. Non-régression
| # | Étape | Attendu | ✓ |
|---|---|---|---|
| 6.1 | Guide IA (assistant) : poser une question sur un contenu dont le **titre** contient du gras ou un `&` | Réponse normale, titre cité sans balises ni `&amp;` (le nettoyage HTML de l'assistant est désormais partagé avec la narration) | |
| 6.2 | Image générée côté visiteur | Mention « Image générée par IA » toujours présente (lot 6) | |
| 6.3 | Enregistrer un article **sans** toucher à la narration | Rien ne change dans ses audios | |
| 6.4 | Modifier un point de carte dans l'ancienne fenêtre | Enregistrement OK ; son audio éventuel n'est pas effacé | |
---
## Limites connues (pas des bugs)
- Les **points de carte** n'ont pas encore d'assignation de narrateur ni de bouton Générer dans le manager.
- Hors ligne, mymuseum-visitapp ne montre pas le portrait.
- Le portrait affiché est le portrait **actuel** du personnage ; le nom et la voix sont ceux de l'audio entendu.
- Les extraits d'écoute des voix (lot 7) ne sont pas encore produits.
---
## Ce que ce plan ne couvre **pas** — ajouté le 2026-09-16
À savoir avant de conclure « le Studio est testé » :
- **L'infra du bucket de prod.** Le lot 0 est codé mais son volet infra reste ouvert : CORS des origines
de prod, cycle de vie `incoming/`, et surtout **fermeture des règles Storage en écriture** — qui doit
se faire *après* le déploiement du nouveau manager-app, sinon l'upload de la version encore en ligne
casse. Rien de tout ça ne se voit depuis un poste de dev.
- **Le plafond journalier par utilisateur** : il *est* configurable, champ « Plafond par utilisateur et
par jour (crédits, 0 = aucun) » dans le dialogue d'instance, sous l'interrupteur Studio. Il vaut 0 par
défaut, donc rien ne se déclenche tant qu'on n'y met pas une valeur — à tester le jour où on veut
vérifier le garde-fou « stagiaire », pas dans cette passe.
- **Le webhook fal** en conditions réelles : en local `Studio:WebhookBaseUrl` est vide, la relecture se
fait par sondage. La vérification de signature ED25519 ne sera exercée qu'en préprod.
- **La charge** : tout ce plan se passe à un seul utilisateur. Le sémaphore à 2 images simultanées de
l'ingestion ne se voit pas à cette échelle.
- **Les lots 9 à 11** (avant/après, vidéo, 3D) : pas commencés.