Le bloquant du proactif est levé — IsAutoTriggered

Plan, STATUS et kanban répercutent le correctif des trois repos. La carte quitte
Bugs ouverts, qui retombe à 1 (les échecs de téléchargement silencieux, D5).

Compteurs mesurés : Bugs ouverts 1, Planifié 24, Fait récemment 55.
Kanban republié sur l'artifact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Thomas Fransolet 2026-08-13 13:14:30 +02:00
parent c92c28098e
commit a2d4eb2944
2 changed files with 19 additions and 17 deletions

View File

@ -456,11 +456,11 @@
<section class="summary" aria-label="Chiffres clés"> <section class="summary" aria-label="Chiffres clés">
<div class="stat"><span class="n n-critical">1</span><span class="k">Urgent</span></div> <div class="stat"><span class="n n-critical">1</span><span class="k">Urgent</span></div>
<div class="stat"><span class="n n-info">1</span><span class="k">Migration v3</span></div> <div class="stat"><span class="n n-info">1</span><span class="k">Migration v3</span></div>
<div class="stat"><span class="n n-warn">2</span><span class="k">Bugs ouverts</span></div> <div class="stat"><span class="n n-warn">1</span><span class="k">Bugs ouverts</span></div>
<div class="stat"><span class="n">6</span><span class="k">À tester</span></div> <div class="stat"><span class="n">6</span><span class="k">À tester</span></div>
<div class="stat"><span class="n">24</span><span class="k">Planifié</span></div> <div class="stat"><span class="n">24</span><span class="k">Planifié</span></div>
<div class="stat"><span class="n n-gate">8</span><span class="k">Bascule prod</span></div> <div class="stat"><span class="n n-gate">8</span><span class="k">Bascule prod</span></div>
<div class="stat"><span class="n n-good">54</span><span class="k">Fait récemment</span></div> <div class="stat"><span class="n n-good">55</span><span class="k">Fait récemment</span></div>
</section> </section>
<div class="filters" role="group" aria-label="Filtrer par domaine"> <div class="filters" role="group" aria-label="Filtrer par domaine">
@ -517,21 +517,10 @@
<!-- BUGS --> <!-- BUGS -->
<section class="col" style="--stripe: var(--warn)"> <section class="col" style="--stripe: var(--warn)">
<div class="col-head"><h2>Bugs ouverts</h2><span class="count">2</span></div> <div class="col-head"><h2>Bugs ouverts</h2><span class="count">1</span></div>
<div class="stack"> <div class="stack">
<article class="card" data-area="backend visitapp" data-horizon="v1"> <article class="card" data-area="visitapp">
<div class="card-meta"><span class="tag">backend</span><span class="tag">visitapp</span><span class="flag f-critical">Bloque l'activation — 13/08</span></div>
<h3>Les déclenchements proactifs pollueraient « Ce que demandent vos visiteurs »</h3>
<p><strong>Le proactif est câblé le 13/08</strong> (garde, cycle de vie, points GPS depuis <code>meterZoneGPS</code>, réglage visiteur) et <strong>M3 est fermé avec lui</strong> — APK <code>dev</code> vert. <strong>Il reste ce point, et il est bloquant avant d'activer le mode chez qui que ce soit.</strong></p>
<p>⚠️ <strong><code>RecordVisitorQuestion</code> écrit <code>Question = request.Message</code></strong> (<code>AiController:135</code>). Or le prompt d'un déclenchement automatique est une consigne machine : « Tu es un guide audio de musée. Le visiteur vient d'entrer dans la zone X. Accueille-le… ». Chaque passage devant une œuvre <strong>ajouterait donc une fausse « question de visiteur »</strong> dans l'onglet Guide IA — un écran qui existe précisément pour montrer ce que les humains demandent.</p>
<p><strong>Et ça ne s'arrête pas à du bruit</strong> : <code>HasAnswer</code> se déduit des sources, donc un prompt d'accueil sans citation viendrait aussi <strong>gonfler le bloc ambre « questions sans réponse »</strong>, celui qui sert à repérer les trous de contenu. Même famille que <code>WeatherSyncService</code> noyant le journal d'audit : le flot machine noie le signal humain dans l'écran fait pour lui.</p>
<p><strong>À faire</strong> : un drapeau porté par <code>AiChatRequest</code> (déclenchement automatique vs question posée), et <code>RecordVisitorQuestion</code> qui n'enregistre pas — ou marque distinctement — les tours d'origine machine. <strong>Deux repos</strong> : <code>manager-service</code> puis <code>mymuseum-visitapp</code>. ⚠️ À trancher en même temps : ces tours consomment des jetons du quota client, donc <code>RecordUsage</code>, lui, doit continuer à les compter.</p>
<p><strong>Écouteurs : ne pas les détecter</strong> — confirmé le 13/08. Le mode est opt-in via un switch dans <code>VoiceModeSheet</code> ; le sous-titre dit « écouteurs recommandés ». On informe, on n'arbitre pas à la place du visiteur.</p>
<span class="src">v1-plan.md lot F — proactif &amp; M3</span>
</article>
<article class="card" data-area="visitapp">
<div class="card-meta"><span class="tag">visitapp</span></div> <div class="card-meta"><span class="tag">visitapp</span></div>
<h3>Échecs de téléchargement silencieux</h3> <h3>Échecs de téléchargement silencieux</h3>
<p>Un fichier raté fait un <code>print</code> et passe. La visite est annoncée téléchargée alors qu'elle est incomplète.</p> <p>Un fichier raté fait un <code>print</code> et passe. La visite est annoncée téléchargée alors qu'elle est incomplète.</p>
@ -875,9 +864,16 @@
<section class="done"> <section class="done">
<h2>Fait récemment</h2> <h2>Fait récemment</h2>
<p>Cinquante-quatre chantiers clos entre le 5 et le 13 août 2026.</p> <p>Cinquante-cinq chantiers clos entre le 5 et le 13 août 2026.</p>
<div class="done-grid"> <div class="done-grid">
<div class="done-item">
<strong>Les prompts du mode proactif ne polluent plus « Ce que demandent vos visiteurs »</strong>
<span>Le dernier bloquant du proactif. <code>RecordVisitorQuestion</code> écrivait <code>Question = request.Message</code> : chaque passage devant une œuvre aurait ajouté <strong>une fausse question de visiteur</strong> — une consigne que le système s'écrit à lui-même — dans l'écran fait pour montrer ce que les <strong>humains</strong> demandent. Et <code>HasAnswer</code> se déduisant des sources, un prompt d'accueil sans citation aurait <strong>gonflé le bloc ambre des questions sans réponse</strong>. Même famille que <code>WeatherSyncService</code> noyant le journal d'audit.</span>
<span><strong>Tranché : ne rien journaliser, plutôt que marquer d'un drapeau.</strong> Une ligne <code>VisitorQuestion</code> sert au rapport de trous de contenu et aux thèmes du lot J ; un prompt machine n'alimente ni l'un ni l'autre, et le garder « marqué » obligerait <strong>chaque futur agrégat</strong> à penser à l'exclure — la classe de panne qu'on venait de fermer sur <code>AuditedTypes</code>. ⚠️ <strong>Mais les jetons restent comptés</strong> : ces tours coûtent de l'argent réel au quota, et un audioguide proactif peut en consommer beaucoup. 2 tests fixent la paire — pas de ligne, compteur incrémenté — parce que c'est la combinaison qui compte.</span>
<span>Trois repos : <code>manager-service</code> (<code>dotnet test</code> 213), <code>manager-app</code> (<code>manager_api_new</code> étendu à la main, <code>flutter build web</code> ✅ — le client est partagé, donc vérifié) et <code>mymuseum-visitapp</code> (APK <code>dev</code> ✅).</span>
</div>
<div class="done-item"> <div class="done-item">
<strong>Déclenchement proactif câblé, et M3 fermé avec lui</strong> <strong>Déclenchement proactif câblé, et M3 fermé avec lui</strong>
<span>Quatre branchements : la garde, le cycle de vie, les points GPS, le réglage visiteur. APK <code>dev</code> vert. ⚠️ <strong>La garde recommandée par le plan aurait coûté de l'argent</strong> : <code>_trigger</code> appelle le LLM d'abord et ne parle qu'ensuite via <code>activeVoiceOrchestrator?.ttsEngine</code>, dont le <code>?.</code> avale le cas « aucun mode vocal actif ». Avec <code>proactiveModeEnabled</code> seul, un visiteur traversant une zone avec l'app en poche et le vocal éteint consommait des jetons Gemini facturés au client pour une phrase que personne n'entend. La garde retenue interroge l'orchestrateur : la vraie condition n'est pas « quel matériel » mais « y a-t-il quelqu'un pour écouter ».</span> <span>Quatre branchements : la garde, le cycle de vie, les points GPS, le réglage visiteur. APK <code>dev</code> vert. ⚠️ <strong>La garde recommandée par le plan aurait coûté de l'argent</strong> : <code>_trigger</code> appelle le LLM d'abord et ne parle qu'ensuite via <code>activeVoiceOrchestrator?.ttsEngine</code>, dont le <code>?.</code> avale le cas « aucun mode vocal actif ». Avec <code>proactiveModeEnabled</code> seul, un visiteur traversant une zone avec l'app en poche et le vocal éteint consommait des jetons Gemini facturés au client pour une phrase que personne n'entend. La garde retenue interroge l'orchestrateur : la vraie condition n'est pas « quel matériel » mais « y a-t-il quelqu'un pour écouter ».</span>

View File

@ -191,7 +191,13 @@ Ne change pas : la popup de traduction (un niveau justifié, langues verticales,
⚠️ **Deux pièges de format sur les points GPS, vérifiés plutôt que supposés.** `currentSections` porte des **maps JSON brutes**, pas des DTO (même idiome que `assistantSuggestions` et `ScannerDialog`), et `latitude`/`longitude` y sont des **chaînes** — c'est le type du `SectionDTO` généré. D'où `double.tryParse`. `meterZoneGPS` devient le rayon par section, la constante ne servant plus que de défaut : **c'est là que M3 se ferme**. ⚠️ **Deux pièges de format sur les points GPS, vérifiés plutôt que supposés.** `currentSections` porte des **maps JSON brutes**, pas des DTO (même idiome que `assistantSuggestions` et `ScannerDialog`), et `latitude`/`longitude` y sont des **chaînes** — c'est le type du `SectionDTO` généré. D'où `double.tryParse`. `meterZoneGPS` devient le rayon par section, la constante ne servant plus que de défaut : **c'est là que M3 se ferme**.
**Un point n'est PAS livré, et il bloque l'activation du mode chez qui que ce soit.** `RecordVisitorQuestion` écrit `Question = request.Message` (`AiController:135`) — or le prompt d'un déclenchement automatique est une consigne machine (« Tu es un guide audio de musée. Le visiteur vient d'entrer dans la zone X. Accueille-le… »). Chaque passage devant une œuvre ajouterait donc **une fausse question de visiteur** dans l'onglet Guide IA, écran qui existe précisément pour montrer ce que les humains demandent. Et `HasAnswer` se déduisant des sources, un prompt d'accueil sans citation **gonflerait aussi le bloc ambre « questions sans réponse »**. C'est la famille de `WeatherSyncService` noyant le journal d'audit : le flot machine noie le signal humain dans l'écran fait pour lui. **À faire dans `manager-service` puis `mymuseum-visitapp`** : un drapeau sur `AiChatRequest`, et une journalisation qui distingue les tours machine — sans cesser de compter leurs jetons au quota, eux sont bien consommés. Le code proactif est en place et compile, mais **ne pas allumer le réglage avant ça**. **Le bloquant est levé le 2026-08-13 — `AiChatRequest.IsAutoTriggered`.** `RecordVisitorQuestion` écrivait `Question = request.Message` (`AiController:135`), or le prompt d'un déclenchement automatique est une consigne machine (« Tu es un guide audio de musée. Le visiteur vient d'entrer dans la zone X. Accueille-le… »). Chaque passage devant une œuvre aurait ajouté **une fausse question de visiteur** dans l'onglet Guide IA, écran qui existe précisément pour montrer ce que les humains demandent — et, `HasAnswer` se déduisant des sources, aurait **gonflé le bloc ambre « questions sans réponse »**. Même famille que `WeatherSyncService` noyant le journal d'audit.
> **Tranché : ne pas journaliser du tout, plutôt que marquer d'un drapeau.** Une ligne `VisitorQuestion` sert au rapport de trous de contenu et aux thèmes du lot J ; un prompt que le système s'est écrit à lui-même n'alimente ni l'un ni l'autre. La garder « marquée » obligerait **chaque futur agrégat** à penser à l'exclure — la classe de panne exacte qu'on venait de fermer sur `AuditedTypes`.
>
> ⚠️ **Mais les jetons restent comptés.** `RecordUsage` est appelé dans tous les cas : ces tours coûtent de l'argent réel au quota du client, et un audioguide proactif peut en consommer beaucoup. Les masquer du compteur serait pire que le bruit qu'on retire. **2 tests** fixent la paire — pas de ligne journalisée, compteur incrémenté — parce que c'est la combinaison qui compte, pas chaque moitié isolément.
>
> Trois repos, trois commits : `manager-service` (DTO + contrôleur + tests, `dotnet test` **213**), `manager-app` (`manager_api_new` étendu **à la main**, `flutter build web` ✅ — le client est partagé, donc vérifié) et `mymuseum-visitapp` (`AssistantService.chat` + l'appelant proactif, APK `dev` ✅).
**Code mort supprimé le 2026-08-13 — `flutter analyze lib` rend zéro erreur, une première pour ce repo.** Trois fichiers Dart : `wake_word_service.dart`, `glasses_qr_scanner_service.dart` et `glasses_tts_service.dart` (l'ancien TTS ElevenLabs, importé par les deux seuls autres). Ils portaient les **4 erreurs** `kElevenLabsApiKey` / `kElevenLabsVoiceId` et l'APK se construisait quand même : personne ne les importait — un îlot hors du graphe de compilation, même mécanique que les 14 fichiers orphelins de `manager_api_new`. **C'étaient les ancêtres de `Services/Glasses/`.**<br>⚠️ **Piège d'homonymie, à ne pas re-déclencher** : `android/…/WakeWordService.kt` porte le même nom et est **bien vivant** — service natif déclaré au manifeste et piloté par `MainActivity`, c'est lui qui fait tourner le wake word. Seul le Dart est parti.<br>**`impl/elevenlabs_tts_engine.dart` est conservé** : c'est l'implémentation de génération courante, gardée comme option si des clients jugeaient la voix Gemini insuffisante — beaucoup plus cher, donc pas un défaut.<br>⚠️ **Corollaire qui rétrécit le chantier du miroir vocal** : j'avais compté **cinq** `AssistantService` distincts, donc cinq historiques. Deux étaient dans ce code mort. **Il y en a trois de vivants** : le chat (`AssistantChatSheet:70`), le vocal (`MyInfoMateLlmClient:18`, `maxHistory: 6`) et le proactif (`geo_beacon_trigger_service:62`). À unifier, mais trois, pas cinq. **Code mort supprimé le 2026-08-13 — `flutter analyze lib` rend zéro erreur, une première pour ce repo.** Trois fichiers Dart : `wake_word_service.dart`, `glasses_qr_scanner_service.dart` et `glasses_tts_service.dart` (l'ancien TTS ElevenLabs, importé par les deux seuls autres). Ils portaient les **4 erreurs** `kElevenLabsApiKey` / `kElevenLabsVoiceId` et l'APK se construisait quand même : personne ne les importait — un îlot hors du graphe de compilation, même mécanique que les 14 fichiers orphelins de `manager_api_new`. **C'étaient les ancêtres de `Services/Glasses/`.**<br>⚠️ **Piège d'homonymie, à ne pas re-déclencher** : `android/…/WakeWordService.kt` porte le même nom et est **bien vivant** — service natif déclaré au manifeste et piloté par `MainActivity`, c'est lui qui fait tourner le wake word. Seul le Dart est parti.<br>**`impl/elevenlabs_tts_engine.dart` est conservé** : c'est l'implémentation de génération courante, gardée comme option si des clients jugeaient la voix Gemini insuffisante — beaucoup plus cher, donc pas un défaut.<br>⚠️ **Corollaire qui rétrécit le chantier du miroir vocal** : j'avais compté **cinq** `AssistantService` distincts, donc cinq historiques. Deux étaient dans ce code mort. **Il y en a trois de vivants** : le chat (`AssistantChatSheet:70`), le vocal (`MyInfoMateLlmClient:18`, `maxHistory: 6`) et le proactif (`geo_beacon_trigger_service:62`). À unifier, mais trois, pas cinq.