Compare commits
2 Commits
016e880a39
...
2d9eaf74dc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d9eaf74dc | ||
|
|
438a631c69 |
26
STATUS.md
26
STATUS.md
@ -12,7 +12,7 @@
|
||||
|
||||
Résumé de la table de suivi de ce fichier :
|
||||
- **Fait** : SectionEvent, Escape/Puzzle games, GuidedPath + progression carte, push, stats tracking, sync agenda/météo, traduction IA, quotas (IA/stockage/stats) par plan, audit log backend fiabilisé, refacto SectionParcours/GuidedPath/SectionGame (backend, vérifié 2026-07-15).
|
||||
- **Partiel** : Beacons/geofencing (manager-app UI ⚠️), AI Assistant (manager-app ⚠️), **mode offline — diagnostic posé le 2026-08-07, bien pire qu'un « sélectif » : le pipeline de collecte des ressources est commenté des deux côtés, une visite téléchargée n'embarque ni images d'articles ni audios → [v2/offline-visit-plan.md](v2/offline-visit-plan.md)**, repasse UI globale, refacto SectionParcours côté manager-app/visitapp-web/mymuseum-visitapp (UI présente mais sous-comportements non revérifiés en détail — voir section "Refactoring majeur" de todo-features.md).
|
||||
- **Partiel** : Beacons/geofencing (manager-app UI ⚠️), AI Assistant (manager-app ⚠️), ~~**mode offline**~~ ✅ **corrigé le 2026-08-11 (D1) puis complété le 13/08 (D5)** — le pipeline de collecte n'est plus commenté, une visite embarque désormais contenus, audios et icônes, et une visite incomplète ne s'annonce plus téléchargée. **Reste à confirmer sur device** (§21 / D0), c'est le seul point encore ouvert du volet offline. Repasse UI globale, refacto SectionParcours côté manager-app/visitapp-web/mymuseum-visitapp (UI présente mais sous-comportements non revérifiés en détail — voir section "Refactoring majeur" de todo-features.md).
|
||||
- **🧪 Implémenté mais jamais testé (2026-08-05)** : **onboarding self-service complet** — inscription sur myinfomate-landing (`/[lang]/signup`), création auto de l'instance + du premier user, essai 14j, Stripe (customer/Checkout/webhook/Tax), 9 emails Resend (domaine vérifié, envoi réel validé), job Hangfire du cycle d'essai, watermark « Aperçu » visitapp-web, écran Abonnement manager-app, mot de passe oublié + invitation user, plafond IA d'essai. Les 4 repos compilent, migrations appliquées en base locale, **aucun parcours exécuté** → checklist de test dans `todo-features.md` (section "🧪 Checklist de test end-to-end"). Reste à faire côté config : alias mail `onboarding@myinfomate.be`, et `whsec_` de la Stripe CLI pour tester le webhook en local.
|
||||
- **À faire** : dashboard super-admin V2, facturation V2. ✅ **Le Stripe Customer Portal est complet le 2026-08-13** (backend + bouton manager-app).
|
||||
- ⚠️ **Cette ligne était périmée sur six points, revérifiés dans le code le 2026-08-13.** Sont **faits** : écran audit log manager-app (front livré le 12/08), **rate limiting API Keys** (livré, pas seulement décidé — politique `ai` dans `Startup.cs:279`, `UseRateLimiter()` en `:344`, les deux endpoints décorés), gestion des users par instance (plafond serveur + compteur UI, 12/08), nettoyage `SectionEvent.ParcoursIds` (11/08), et le **backend** du Customer Portal (13/08). L'**unification `QuestionType`** n'est pas « à faire » mais **écartée** : le trio TextLibre/Digicode/ExpectedAnswer n'a jamais existé dans le code, l'enum a trois valeurs et le digicode est un comportement dérivé, pas un type — reste une propreté front, elle aussi livrée (alias nommés, 11/08).
|
||||
@ -290,7 +290,9 @@ Ingestion documentaire (PdfPig + OCR + formats + UI `IncludeInAiKnowledge`, avec
|
||||
|
||||
**Ordre de grandeur total : ~3 semaines**, dont une bonne moitié dans le lot 3 et les 13 sous-types.
|
||||
|
||||
### 📍 Reprendre ici — ordre arrêté le 2026-08-10
|
||||
### 📍 ~~Reprendre ici~~ — périmé, conservé pour la trace (ordre arrêté le 2026-08-10)
|
||||
|
||||
> ⛔ **Ne plus repartir d'ici : les trois étapes ci-dessous sont terminées** (A le 10/08, B le 10/08, C le 12/08), et le développement V1 est clos depuis le 2026-08-13. **Le point d'entrée est [v1-plan.md](v1-plan.md)** ; l'état du jour est au § « Les lots F et J sont clos » ci-dessus. Ce qui reste avant la prod est du test, du juridique et de l'infra — plus une ligne de code applicatif.
|
||||
|
||||
Les lots 1 et 2 sont terminés. **Le lot 3 et le lot médias sont tous les deux obligatoires** — l'ordre ci-dessous ne trie pas par importance mais par coût de report. Ni l'un ni l'autre ne bloque plus la bascule : ce qui devait partir avec la migration, c'était le schéma, et il est en place.
|
||||
|
||||
@ -439,6 +441,26 @@ Remplacé par `GetReferencedResourceIds()`, déjà implémentée sur les 13 sous
|
||||
|
||||
> ⚠️ **Ce qui reste du lot F n'est plus dans `manager-service`** : le bouton du portail et le diviseur `aiTokensPerQuestion` dans `manager-app` (petit), et surtout les trois chantiers `mymuseum-visitapp` — déclenchement proactif (+ M3), miroir de la conversation vocale, émission des événements vocaux. Plus l'aperçu de conversation (**L9**), indépendant.
|
||||
|
||||
---
|
||||
|
||||
### 🎉 Les lots F et J sont clos le 2026-08-13 — il ne reste plus de développement V1
|
||||
|
||||
> Tout ce qui suit a été livré dans la même journée, sur 5 repos. `dotnet test` **229** (213 passés, 16 sautés faute de démon Docker), APK `dev` de `mymuseum-visitapp` ✅, `flutter build web` de `manager-app` ✅, `npm run build` de `visitapp-web` ✅, `flutter analyze lib` **sans erreur** sur les deux apps Flutter.
|
||||
|
||||
**Lot F** — Stripe Customer Portal (backend + bouton), ratio jetons → questions mesuré, déclenchement proactif, **M3**, miroir de la conversation vocale, `conversationId`, émission des événements vocaux. **L9 n'était pas un chantier** : la « cible Assistant / Persona » du Mode preview est ce que le §5 du Guide IA a livré le 11/08 — un malentendu entre deux documents, pas du travail.
|
||||
|
||||
**Lot J** — table d'agrégats de thèmes, job de regroupement quotidien, interrupteur de collecte par instance (avec son UI), mention d'information dans les **deux** apps visiteur, purge du journal d'audit. **D5** est livré avec eux.
|
||||
|
||||
**Les cinq pièges de la journée, à ne pas re-découvrir :**
|
||||
|
||||
1. ⚠️ **La garde du proactif recommandée par le plan aurait coûté de l'argent.** `_trigger` appelle le LLM **avant** de parler, et le `?.` sur l'orchestrateur avale le cas « aucun mode vocal actif » : un visiteur traversant une zone avec l'app en poche et le vocal éteint consommait des jetons facturés au client pour une phrase que personne n'entend. La garde interroge l'orchestrateur, pas le matériel.
|
||||
2. ⚠️ **Un écran peut mentir sans erreur.** Sans état d'échec, une visite incomplète restait sur « téléchargement en cours » **indéfiniment** — le compteur n'étant incrémenté qu'en cas de succès, il n'atteignait jamais le total. Le bug d'origine (D5) était le `print` avalé ; celui-là était pire et n'était écrit nulle part.
|
||||
3. ⚠️ **Le choix de voix du client n'était pas honoré.** Le sélecteur Viva/Marco existait depuis le 07/08 et écrivait `GuideVoiceId` ; l'app visiteur ne lisait jamais ce champ. **Même forme que W1.** Et aucun APK ne parlait avec la voix du produit, `GEMINI_API_KEY` n'étant injectée nulle part — repli **silencieux** sur la voix système.
|
||||
4. ⚠️ **Trois écrans journalisaient de fausses questions de visiteur** : le mode proactif (son prompt machine) et l'aperçu de conversation (le gestionnaire testant sa personnalité). Le drapeau s'appelle `IsVisitorQuestion`, défaut `true` — un client publié qui ne l'envoie pas continue de journaliser.
|
||||
5. ⚠️ **Le même code copié trois fois porte sa décision une seule fois.** `_toLangCode` limitait le vocal à FR/NL/EN/DE dans les trois copies, mais ne le **disait** que dans une : les deux autres ressemblaient à un oubli à corriger. Idem pour les trois `AssistantService`, qui coupaient la conversation en morceaux.
|
||||
|
||||
**Ce qui reste, et qui ne s'écrit pas en code** : la relecture juridique du §8 des CGU, les conditions réelles de Google, le DPA séparé, et les traductions relues de la mention visiteurs (FR/NL/EN aujourd'hui, repli anglais assumé). Puis **K6**, **K9**, le **lot H** (tests, dont D0 sur device) et le **lot I**.
|
||||
|
||||
**1. Le journal d'audit voyait tout sauf le contenu.** `AuditedTypes.Contains(entry.Entity.GetType())` exigeait l'égalité **exacte** de type, or `Section` est **abstraite** : le type runtime est toujours `SectionMap`, `SectionQuiz`… Aucune des 13 sortes de section n'était journalisée — soit précisément ce que l'écran du 12/08 devait tracer. `Resource`, `Configuration`, `Device`, `User` et `Instance` passaient, eux : ils sont concrets, et **c'est ce qui rendait le trou invisible**. Remplacé par une remontée à la classe de base auditée (`IsInstanceOfType`).
|
||||
|
||||
> **Tranché : normalisation côté serveur.** `EntityType` porte `Section`, pas `SectionMap`. Trois raisons : le filtre « Section » **déjà présent** dans l'écran se met à rendre des lignes sans toucher `manager-app`, donc sans coordonner deux repos ; élargir le filtre front aurait coûté 13 entrées de liste et 39 clés i18n **et** laissé tout futur sous-type sortir du filtre en silence — la classe de panne exacte qu'on ferme ici ; et le sous-type concret **n'est pas perdu**, le discriminateur TPH est une propriété du modèle, donc sérialisée dans `NewValues` (`"Discriminator": "Article"`). Un test le vérifie — ce n'était pas une supposition.
|
||||
|
||||
61
kanban.html
61
kanban.html
@ -446,10 +446,11 @@
|
||||
<div>
|
||||
<h1>MyInfoMate — tableau de chantiers</h1>
|
||||
<p>Vue d'état consolidée depuis <code>DOCS/</code>. Source de vérité : <code>STATUS.md</code> — cette page en est le reflet, pas le remplaçant. Filtrez sur <strong>V1</strong> pour ne voir que ce qui reste avant la mise en prod. <strong>L'ordre d'exécution</strong>, lui, est dans <code>v1-plan.md</code> : ce tableau dit où en est chaque chantier, pas lequel bloque lequel.</p>
|
||||
<p><strong>🎉 Le développement V1 est clos depuis le 13/08.</strong> Les lots F et J sont terminés et il ne reste aucun bug ouvert. Ce qui suit est du <strong>test</strong> (dont le §21 sur device, qui n'a jamais été joué), du <strong>juridique</strong> (relecture du §8 des CGU, conditions Google, DPA) et de l'<strong>infra</strong> (secrets, pg_dump, bascule) — plus une ligne de code applicatif à écrire, hors K9 qui est du confort visuel sacrifiable.</p>
|
||||
</div>
|
||||
<div class="stamp">
|
||||
Mise à jour · 2026-08-13<br>
|
||||
Postgres v3 · pas encore en prod
|
||||
Dev V1 clos · pas encore en prod
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@ -457,10 +458,10 @@
|
||||
<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-warn">0</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">22</span><span class="k">Planifié</span></div>
|
||||
<div class="stat"><span class="n">7</span><span class="k">À tester</span></div>
|
||||
<div class="stat"><span class="n">25</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-good">59</span><span class="k">Fait récemment</span></div>
|
||||
<div class="stat"><span class="n n-good">60</span><span class="k">Fait récemment</span></div>
|
||||
</section>
|
||||
|
||||
<div class="filters" role="group" aria-label="Filtrer par domaine">
|
||||
@ -525,9 +526,20 @@
|
||||
|
||||
<!-- À TESTER -->
|
||||
<section class="col" style="--stripe: var(--brand)">
|
||||
<div class="col-head"><h2>À tester</h2><span class="count">6</span></div>
|
||||
<div class="col-head"><h2>À tester</h2><span class="count">7</span></div>
|
||||
<div class="stack">
|
||||
|
||||
<article class="card" data-area="visitapp">
|
||||
<div class="card-meta"><span class="tag">visitapp</span><span class="tag">vocal</span><span class="flag f-good">Livré 13/08</span></div>
|
||||
<h3>Latence vocale et commandes multilingues — à valider sur le terrain</h3>
|
||||
<p>Cinq réglages livrés le 13/08 dans <code>voice_orchestrator.dart</code>, tous à éprouver en visite réelle avec Viva <strong>et</strong> Marco, sur lunettes <strong>et</strong> téléphone.</p>
|
||||
<p><strong>1. <code>done.mp3</code> retardait la réponse de toute sa durée</strong> — <code>await _playDoneSound()</code> juste avant <code>speak()</code>, et dans <code>just_audio</code> le future de <code>play()</code> ne se résout qu'à la <em>fin</em> de la lecture. En prime un doublon : la parole <em>est</em> le signal de fin. Retiré, helper et constante supprimés.</p>
|
||||
<p><strong>2. Sons préchargés</strong> — <code>setAsset</code> décodait à chaque wake word, sur le chemin le plus sensible à la latence du pipeline. <strong>3. Escalade du son de réflexion</strong> — silence 700 ms, puis fondu d'entrée à 45 % ; une réponse rapide ne déclenche plus aucun son. ⚠️ <code>stop()</code> n'appelait pas <code>_stopThinkingLoop()</code> : le timer pouvait jouer une nappe après l'arrêt de l'orchestrateur.</p>
|
||||
<p><strong>4. Les commandes n'existaient qu'en français</strong> — <code>_isStopCommand</code> et les trois autres cherchaient « répète », « arrête », « prends » en dur. Les quatre listes couvrent FR/NL/EN/DE, et le matching passe de <code>contains</code> à <strong>mot entier</strong>. ⛔ Ce changement a révélé deux faux positifs déjà présents : « je ne com<strong>prends</strong> pas » déclenchait une photo, « raconte <strong>encore</strong> une histoire » rejouait la réponse précédente. Et <code>code</code> nu ne déclenche plus de scan QR (« what's the code of this painting »). <strong>16 cas joués via <code>dart run</code>, 16/16.</strong></p>
|
||||
<p><strong>5. Limite 4 langues actée</strong> dans le code. <code>flutter analyze lib/Services/Glasses/</code> → 0 erreur.</p>
|
||||
<span class="src">voice-latency-plan.md §1 · STATUS.md §5bis</span>
|
||||
</article>
|
||||
|
||||
<article class="card" data-area="manager">
|
||||
<div class="card-meta"><span class="tag">manager-app</span><span class="flag f-warn">Jamais lancé</span></div>
|
||||
<h3>Écran Statistiques refondu + export PDF</h3>
|
||||
@ -577,11 +589,39 @@
|
||||
|
||||
<!-- PLANIFIÉ -->
|
||||
<section class="col" style="--stripe: var(--ink-3)">
|
||||
<div class="col-head"><h2>Planifié</h2><span class="count">22</span></div>
|
||||
<div class="col-head"><h2>Planifié</h2><span class="count">25</span></div>
|
||||
|
||||
|
||||
<div class="stack">
|
||||
|
||||
<article class="card" data-area="visitapp" data-horizon="v1">
|
||||
<div class="card-meta"><span class="tag">visitapp</span><span class="tag">vocal</span><span class="flag f-warn">Feu vert à donner</span></div>
|
||||
<h3>Découpage du TTS par phrase — le seul vrai levier de latence</h3>
|
||||
<p><strong>Le time-to-first-audio est aujourd'hui la somme de tout</strong> : <code>LlmClient.chat()</code> retourne un future de réponse <em>complète</em> (pas de flux) et <code>GeminiTtsEngine._synthesize()</code> fait un <code>generateContent</code> unaire qui attend <strong>tout</strong> le PCM avant d'écrire le WAV. C'est ce trou que la nappe de réflexion bouche.</p>
|
||||
<p><strong>Le remède ne dépend d'aucune API nouvelle</strong> : découper <code>result.reply</code> en phrases, synthétiser et jouer la première pendant que les suivantes se préparent. Le time-to-first-audio tombe à <code>LLM + TTS(1 phrase)</code>. Contenu dans <code>GeminiTtsEngine</code> — ni l'orchestrateur ni le backend ne bougent.</p>
|
||||
<p>⏸️ <strong>Volontairement pas fait avec le reste du lot du 13/08</strong> : c'est le seul chantier structurant des six, il mérite un feu vert séparé. ⚠️ <strong>À vérifier avant</strong> : <code>:streamGenerateContent</code> émet-il des chunks audio progressifs sur <code>gemini-2.5-flash-preview-tts</code> ? Test curl de 20 min — à faire, pas à supposer.</p>
|
||||
<span class="src">voice-latency-plan.md §1.3</span>
|
||||
</article>
|
||||
|
||||
<article class="card" data-area="visitapp" data-horizon="v1">
|
||||
<div class="card-meta"><span class="tag">visitapp</span><span class="tag">vocal</span><span class="flag f-warn">Après les tests</span></div>
|
||||
<h3>Accusé de réception parlé à la place du bip du wake word</h3>
|
||||
<p>Remplacer <code>wake_detected.mp3</code> par une phrase dans la langue et la voix du visiteur — « Oui, je vous écoute » — avec 2-3 variantes, un visiteur entendant l'ack des dizaines de fois sur une visite.</p>
|
||||
<p><strong>Retenu, mais explicitement après les tests</strong> : ça demande de générer et valider <em>à l'oreille</em> ~40 fichiers (2 voix × 4 langues × 5 phrases), et une partie du besoin que l'ack compense disparaît si le découpage par phrase fait baisser la latence. Décider avant d'avoir entendu le flux, ce serait décider à l'aveugle.</p>
|
||||
<p>⚠️ <strong>Trois pièges déjà identifiés.</strong> <code>pubspec.yaml:133</code> déclare <code>assets/sounds/</code> mais <strong>les déclarations de dossier ne sont pas récursives en Flutter</strong>. Générer avec exactement le même <code>voicePrompt</code> que le runtime, sinon le timbre décroche entre l'ack et la réponse — et Gemini TTS n'est pas déterministe, prévoir plusieurs prises. ⛔ Et une <strong>règle de cohérence non négociable</strong> : sans <code>GEMINI_API_KEY</code> le moteur retombe sur la voix système Android — des acks en Sulafat suivis d'une réponse en voix système seraient <em>pires</em> que le bip. Acks actifs seulement si le moteur runtime est Gemini et que la voix correspond à <code>guideVoiceId</code>.</p>
|
||||
<p>Ordonnancement à calibrer sur place : ouvrir le micro à <code>player.duration - 150ms</code> plutôt que de faire de l'AEC — sur Ray-Ban, micro et haut-parleur partagent la monture.</p>
|
||||
<span class="src">voice-latency-plan.md §2</span>
|
||||
</article>
|
||||
|
||||
<article class="card" data-area="backend visitapp" data-horizon="v2">
|
||||
<div class="card-meta"><span class="tag">3 repos</span><span class="flag f-warn">V2 — spike d'abord</span></div>
|
||||
<h3>Live API Gemini — audio natif bidirectionnel</h3>
|
||||
<p>Supprimerait les trois maillons Whisper → LLM → Gemini TTS au profit d'un <strong>WebSocket permanent</strong> : flux micro brut en entrée (PCM 16 kHz), flux audio en sortie (24 kHz), sans jamais passer par du texte. Débloque trois choses que l'architecture actuelle ne peut <strong>structurellement</strong> pas faire : le <strong>barge-in</strong> (couper la parole à l'assistant), le <strong>VAD côté serveur</strong> (fin du <code>timeout: 5 secondes</code> en dur de <code>_listenForFollowUp</code>) et une latence de l'ordre de la seconde. Les voix prébuilt sont de la même famille — Viva/Marco survivraient.</p>
|
||||
<p><strong>Le point dur n'est pas l'audio, c'est le tool calling.</strong> Les outils (<code>GetSectionDetail</code>, RAG) vivent dans <code>manager-service</code>. Deux options : l'app parle directement à Gemini (latence minimale, mais logique métier déplacée dans le client et jetons éphémères obligatoires — on ne met pas une clé Gemini dans un APK de visiteurs), ou <strong><code>manager-service</code> proxifie le WebSocket</strong> (clé au chaud, prompt et stats gardés, mais relais audio temps réel en C# avec sessions, reconnexions et backpressure). <strong>C'est l'option B qui a du sens, et c'est elle qui coûte cher.</strong></p>
|
||||
<p>⚠️ <strong>Le modèle de coût change de nature</strong> : plus à la requête mais à la session ouverte, avec des jetons audio bien plus chers. 80 visiteurs simultanés = 80 sessions. <strong>À chiffrer, les tarifs ne sont pas connus ici.</strong> ⚠️ Sessions à durée limitée, reprise à gérer sur réseau mobile en bâtiment de pierre. ⛔ <strong>Modèles en preview</strong> — après l'historique ElevenLabs → Gemini, y accrocher une fonction vendue serait imprudent. <strong>Verdict : spike chiffré d'une journée avant toute décision.</strong></p>
|
||||
<span class="src">voice-latency-plan.md §3.2</span>
|
||||
</article>
|
||||
|
||||
<article class="card" data-area="visitapp" data-horizon="v2">
|
||||
<div class="card-meta"><span class="tag">visitapp</span><span class="tag">code mort</span><span class="flag f-warn">V2</span></div>
|
||||
<h3>Nettoyer downloadConfiguration.dart</h3>
|
||||
@ -834,9 +874,16 @@
|
||||
|
||||
<section class="done">
|
||||
<h2>Fait récemment</h2>
|
||||
<p>Cinquante-neuf chantiers clos entre le 5 et le 13 août 2026. <strong>Les lots F et J sont clos, et il ne reste aucun bug ouvert.</strong></p>
|
||||
<p>Soixante chantiers clos entre le 5 et le 13 août 2026. <strong>Les lots F et J sont clos, et il ne reste aucun bug ouvert.</strong></p>
|
||||
<div class="done-grid">
|
||||
|
||||
<div class="done-item">
|
||||
<strong>Purge du journal d'audit, mention dans visitapp-web, et <code>_toLangCode</code> dédoublonné</strong>
|
||||
<span>⚠️ <strong>La purge d'<code>AuditLog</code> avait été oubliée</strong> à la première passe du lot J, alors que le plan l'y assignait explicitement. <code>AuditLog</code> porte un <code>UserId</code>, et les valeurs avant/après d'une modification de <code>User</code> contiennent e-mail, prénom et nom — <strong>conservés sans limite</strong>, quand <code>VisitEvent</code> purge à 13 mois et <code>VisitorQuestion</code> à 90 jours. <strong>12 mois, uniforme</strong> ; pas 13, les 13 mois des stats servent à comparer une saison à la précédente et les recopier serait du mimétisme — un test fige l'écart. Inerte tant qu'<code>Audit:RetentionDays</code> n'est pas défini : pas de pg_dump, pas de suppression.</span>
|
||||
<span>⚠️ <strong><code>ExecuteDeleteAsync</code> n'est pas traduisible par le provider InMemory</strong> : la suppression se teste contre un vrai Postgres. L'écrire en chargeant les lignes puis <code>RemoveRange</code> l'aurait rendue testable en mémoire au prix de charger un an de journal — dégrader le code de production pour satisfaire un provider de test.</span>
|
||||
<span><strong>Mention visiteurs dans <code>visitapp-web</code></strong>, panneau dépliable dans l'en-tête de l'assistant, même texte que l'app mobile. <strong><code>_toLangCode</code> était copié trois fois</strong> et la décision « FR/NL/EN/DE seulement » n'était écrite que dans une copie : les deux autres ressemblaient à un oubli qu'on aurait « corrigé » en ajoutant des langues, produisant un support partiel silencieux. Une seule copie désormais, une seule décision.</span>
|
||||
</div>
|
||||
|
||||
<div class="done-item">
|
||||
<strong>Lot J — le volet codable est livré (thèmes, interrupteur, mention)</strong>
|
||||
<span><strong>Table d'agrégats</strong> <code>(instance, mois, thème, compteur)</code> : c'est elle qui tient la promesse du §8.4 des CGU. Sans elle le regroupement vit dans la ligne <code>VisitorQuestion</code>, donc la purge du 90<sup>e</sup> jour l'emporte avec la question et le client perd tout au 91<sup>e</sup>. Elle ne porte <strong>que des compteurs</strong> — aucune donnée personnelle, ce qui est précisément ce qui l'autorise à survivre. ⚠️ <code>Insights</code> lit désormais les thèmes <strong>dans cette table</strong>, pas dans les questions de la fenêtre.</span>
|
||||
|
||||
@ -365,7 +365,7 @@ Ordre du §2 de STATUS.md, corrigé par **L12**.
|
||||
|
||||
Les CGU §8 ont été réécrites le 2026-08-11 (`cgu-myinfomate.md`) et le texte d'information visiteurs existe (`mention-information-visiteurs.md`). Ce qui reste :
|
||||
|
||||
✅ **Volet codable du lot J livré le 2026-08-13** — J1, J2, J3 et J4 (côté `mymuseum-visitapp`). `dotnet test` **226**, APK `dev` ✅, `flutter build web` ✅. Migration unique `LotJ_ThemeAggregatesAndCollectionSwitch` : une colonne + une table, rien d'autre — vérifié dans le fichier généré.
|
||||
✅ **Lot J : tout le codable est livré le 2026-08-13** — J1, J2, J3, J4 (les deux apps visiteur) et la purge du journal d'audit. `dotnet test` **229**, APK `dev` ✅, `flutter build web` ✅, `npm run build` ✅. Migration unique `LotJ_ThemeAggregatesAndCollectionSwitch` : une colonne + une table, rien d'autre — vérifié dans le fichier généré. **Ce qui reste au lot J ne s'écrit pas en code** : relecture juridique du §8, conditions réelles de Google, DPA séparé, et les traductions relues de la mention.
|
||||
|
||||
⚠️ **Les thèmes sont une liste fixe de 8, pas des thèmes découverts par l'IA** — décidé le 2026-08-13. Des libellés régénérés à chaque passage produiraient « Horaires » en janvier et « Questions d'horaires » en février : deux lignes d'agrégat distinctes, et une courbe qui ne veut rien dire. Or la table existe précisément pour porter cet historique. **Ajouter un thème reste possible, en renommer un coupe l'historique en deux.**
|
||||
|
||||
@ -380,7 +380,8 @@ Les CGU §8 ont été réécrites le 2026-08-11 (`cgu-myinfomate.md`) et le text
|
||||
| ~~J1~~ ✅ | **Table d'agrégats `QuestionThemeMonthly`** | `(InstanceId, mois, thème, compteur)`, index sur `(InstanceId, Month)`. **Ne porte que des compteurs** — ni texte, ni session, ni langue : aucune donnée personnelle, donc rien qui justifierait de la purger, ce qui est précisément ce qui l'autorise à survivre. ⚠️ **`Insights` lit désormais `topics` et `themes` dans cette table, pas dans les questions de la fenêtre** — les lire dans les questions faisait disparaître l'historique au 91ᵉ jour, sans erreur ni trace |
|
||||
| ~~J2~~ ✅ | **`QuestionThemingService`**, quotidien à 2 h | Classement par lots de 25 en un appel — un appel par question multiplierait le coût par 25 pour le même travail. **6 tests** |
|
||||
| ~~J3~~ ✅ | **`Instance.IsVisitorQuestionCollectionEnabled`**, défaut `true` | Garde dans `Chat`, exposé au DTO, et **interrupteur dans l'écran Guide IA** — sans UI le client ne pourrait pas exercer le refus dont il est responsable. Le libellé dit ce que couper coûte (l'onglet cesse de se remplir) et ce que couper ne fait pas (rien n'est effacé, les questions déjà là vivent jusqu'à leur purge) |
|
||||
| 🔨 J4 | **Mention aux visiteurs** | ✅ **`mymuseum-visitapp`** : icône dans l'en-tête de l'assistant → `VisitorPrivacyNotice`, texte repris **mot pour mot** de `mention-information-visiteurs.md`.<br>⚠️ **Trois langues seulement (FR/NL/EN), repli sur l'anglais — c'est un choix.** L'app en porte dix, mais traduire une mention de protection des données sans relecture humaine serait pire que la servir en anglais : une nuance perdue sur « nous n'enregistrons pas votre adresse IP » n'est pas une coquille d'interface. **À demander avec la relecture juridique (J5).**<br>⛔ **Reste `visitapp-web`** — même mention, autre repo |
|
||||
| ~~J4~~ ✅ | **Mention aux visiteurs** | Livrée dans **`mymuseum-visitapp`** (`VisitorPrivacyNotice`) **et `visitapp-web`** (panneau dépliable dans l'en-tête de l'assistant), texte repris **mot pour mot** de `mention-information-visiteurs.md` dans les deux.<br>⚠️ **Trois langues seulement (FR/NL/EN), repli sur l'anglais — c'est un choix.** Les apps en portent dix et six, mais traduire une mention de protection des données sans relecture humaine serait pire que la servir en anglais : une nuance perdue sur « nous n'enregistrons pas votre adresse IP » n'est pas une coquille d'interface. **À demander avec la relecture juridique (J5).**<br>⚠️ **Trois copies du même texte** (le document, le Dart, le TSX) : une correction se fait dans le document d'abord, les deux autres suivent. C'est écrit dans les deux fichiers de code |
|
||||
| ~~J8~~ ✅ | **Purge du journal d'audit** — ajouté le 2026-08-13 | ⚠️ **Poste oublié à la première passe du lot J** : le plan l'y assignait explicitement (« à traiter au lot J, pas au lot F »), il n'avait pas été fait. `AuditLog` porte un `UserId`, et les valeurs avant/après d'une modification de `User` contiennent e-mail, prénom et nom — **conservés sans limite** jusqu'ici, quand `VisitEvent` purge à 13 mois et `VisitorQuestion` à 90 jours.<br>**12 mois, uniforme**, via `AuditLogPurgeService` calqué sur `VisitEventPurgeService`. Pas 13 : les 13 mois des stats servent à comparer une saison à la précédente, les recopier ici serait du mimétisme — **un test fige cet écart voulu**.<br>⚠️ **Inerte tant qu'`Audit:RetentionDays` n'est pas défini**, même verrou que les `VisitEvent` : pas de pg_dump, pas de suppression définitive.<br>⚠️ **`ExecuteDeleteAsync` n'est pas traduisible par le provider InMemory** — la suppression se teste contre un vrai Postgres. L'écrire en chargeant les lignes puis `RemoveRange` l'aurait rendue testable en mémoire au prix de charger un an de journal : dégrader le code de production pour satisfaire un provider de test |
|
||||
| J5 | **Faire relire le §8 des CGU** | Document contractuel, rédigé côté produit et non validé juridiquement. Priorité au §8.6 (sous-traitants) et au §8.5 (transfert hors UE) |
|
||||
| J6 | **Vérifier les conditions réelles de Google** | Le §8.5 dit « peut impliquer un transfert hors UE » — prudent mais vague. Savoir si l'API Gemini utilisée offre une résidence européenne, et si un DPA est signé. La réponse réécrit le §8.5 |
|
||||
| J7 | **Décider si un DPA séparé est requis** | L'article 28 exige un acte écrit. Le §8 en tient partiellement lieu ; une commune ou un musée subsidié en demandera un en annexe |
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user