691 lines
92 KiB
Markdown
691 lines
92 KiB
Markdown
# MyInfoMate — Tableau de bord
|
||
|
||
> Point d'entrée unique : "où j'en suis, qu'est-ce que je peux avancer". Chaque section renvoie vers le document détaillé pertinent — ce fichier ne duplique pas le contenu, il donne juste la vue d'ensemble et l'état.
|
||
>
|
||
> Dernière mise à jour : 2026-08-09.
|
||
|
||
---
|
||
|
||
## 1. Produit — features manquantes / en cours
|
||
|
||
→ Détail complet, granulaire, à jour : **[todo-features.md](todo-features.md)**
|
||
|
||
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).
|
||
- **🧪 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** : écran audit log manager-app (backend ✅, front ❌ — ajouté le 2026-07-15), rate limiting API Keys, gestion users par instance admin, unification QuestionType (TextLibre/Digicode/ExpectedAnswer), nettoyage SectionEvent.ParcoursIds, Stripe Customer Portal, dashboard super-admin V2, facturation V2.
|
||
- **📦 Reporté en V2 (décidé le 2026-08-07)** : **SectionForm**, **ressource 360°**, **AR image tracking (Mind AR)**. Les trois sont spécifiés en détail dans [todo-features.md](todo-features.md) mais sortent du périmètre V1 — aucun n'est un prérequis de la migration Postgres ni de la mise en prod. À reprendre tels quels quand la V1 sera stabilisée.
|
||
|
||
**Prochaine chose à faire si tu as 1h** : jouer le **§19.13 cas E** du plan de test (chasse au trésor / carnaval) de bout en bout, depuis la création dans manager-app. C'est le scénario le plus complet — il valide SectionMap + SectionParcours + géodéclenchement + questions + mode jeu d'un coup.
|
||
|
||
---
|
||
|
||
## 1bis. État des builds — mesuré le 2026-08-06
|
||
|
||
> Aucune app n'était buildable au début de l'audit. Deux corrections ont débloqué les 3 apps Flutter.
|
||
> Détail : **[parity-manager-visitapp.md §6](parity-manager-visitapp.md)** · checklist : **[test-plan.md §0](test-plan.md)**
|
||
|
||
| Repo | Build | Note |
|
||
|---|---|---|
|
||
| manager-service | ✅ `dotnet build` et ✅ `dotnet test` **124/124** (2026-08-06) | La remise en route de la suite a révélé un **bug backend réel** : `BuildAuditEntries()` ajoutait un `AuditLog` au contexte *pendant* l'énumération du `ChangeTracker` → `InvalidOperationException: Collection was modified` sur toute écriture d'entité auditée (Section, Resource, Configuration, Device, User, Instance). Corrigé : énumération matérialisée, logs ajoutés après la boucle. Invisible jusque-là parce que les tests ne compilaient plus |
|
||
| manager-app | ✅ `flutter build web` | débloqué : `// @dart=2.18` manquant dans `manager_api_new/lib/api/onboarding_api.dart` (fichier ajouté à la main pour l'onboarding, sans l'annotation de version que porte `api.dart`) — **cassait les 3 apps Flutter d'un coup** |
|
||
| mymuseum-visitapp | ❌ `flutter build apk` (**revérifié le 2026-08-11**) | Était ✅ le 2026-08-06, débloqué par la même correction + typage explicite dans `guided_step_challenge.dart:83`. **Casse maintenant côté Gradle**, pas côté Dart : `ndkVersion = "28.2.13676358"` réclamé dans `android/app/build.gradle`, avertissements KGP sur 11 plugins. Côté Dart il reste 4 erreurs, **toutes sur la branche Ray-Ban** (`kElevenLabsApiKey`/`kElevenLabsVoiceId` absents de `constants.dart`, jamais committés — voir §5bis), dans des fichiers hors du graphe de `main.dart`. Même famille que tablet-app : l'environnement Android a bougé |
|
||
| visitapp-web | ✅ `npm run build` | débloqué : helper `getGeoPointLatLng()` dans `src/lib/geo.ts`, les 7 erreurs venaient d'un `unknown` non narrowé. ⚠️ ce type d'erreur est **invisible en `next dev`** |
|
||
| tablet-app | ❓ | échec Gradle sur `flutter_tools/gradle/build.gradle.kts` — **seul build encore cassé** au 2026-08-06. ⚠️ **Peut-être corrigé depuis** : commit `5a6701d` « Drapeaux du migrateur Flutter (builtInKotlin, newDsl) ». À reconfirmer par un vrai build, pas par le log. **Le problème est purement Gradle** : une piste Dart (`ContentGeoPoint`) a été ouverte puis **écartée** le 2026-08-11, cf. §1sexies — ne pas la rouvrir |
|
||
|
||
**Leçon** : `flutter analyze` et `next dev` ne suffisent pas. Seuls `flutter build` / `npm run build` / `dotnet test` disent la vérité — d'où la nouvelle section §0 du plan de test.
|
||
|
||
---
|
||
|
||
## 1ter. Chantier « migration Postgres v3 » — ordre de bataille (ajouté le 2026-08-07)
|
||
|
||
> Postgres n'est pas encore en prod. Trois chantiers ont été analysés le 2026-08-07 et découpés selon une règle unique :
|
||
>
|
||
> **Ce qui touche aux données déjà écrites part avec la migration. Ce qui est purement additif attend.**
|
||
>
|
||
> Chaque ligne est faite pour être attaquée dans une conversation dédiée : lire le doc, faire l'étape.
|
||
>
|
||
> ⚠️ Cette section dit **ce qui doit entrer dans le schéma**, pas comment on remplit la base. L'opération de bascule elle-même — et l'état de `MigrationController` — sont au **§1quinquies**.
|
||
|
||
### Doit partir avec la migration (schéma — quelques heures)
|
||
|
||
| # | Quoi | Doc | Pourquoi maintenant |
|
||
|---|---|---|---|
|
||
| 1 | ✅ **Fait 2026-08-09** — Image Docker `postgis` + `pgvector` (`Deployment/Dockerfile.postgres`, digest épinglé) | [v2/rag-pgvector-integration-plan.md](v2/rag-pgvector-integration-plan.md) | Base vide = 4 lignes de Dockerfile. Plus tard = fenêtre de maintenance sur la prod |
|
||
| 2 | ✅ **Fait 2026-08-09** — Table `ContentEmbedding` + index HNSW | idem | Table vide, ne dérange personne. Additif mais gratuit maintenant |
|
||
| 3 | ✅ **Fait 2026-08-09** — `IEmbeddingService` + `GoogleEmbeddingService`, **dimension 768 validée contre l'API réelle** | idem | **Gèle la dimension du vecteur** — décision de schéma, à valider avant la migration |
|
||
| 4 | ✅ **Fait 2026-08-09** — Colonnes `Resource` : `StoragePath`, `FileName`, `IncludeInAiKnowledge`, `AiIndexStatus`… (`SizeBytes` existait déjà) | [v2/media-storage-plan.md](v2/media-storage-plan.md) | Les URL Firebase absolues sont dispersées jusque dans le JSONB des sections |
|
||
| 5 | Backfill `SizeBytes` + `StoragePath` + inventaire des blobs orphelins | idem | La migration touche déjà chaque ligne `Resource` |
|
||
| 6 | Flag watermark sur `Instance` (+ exposition dans la config) | idem | Remplace un `if` sur un id d'instance codé en dur |
|
||
|
||
### Avant la release, hors schéma
|
||
|
||
| # | Quoi | Doc |
|
||
|---|---|---|
|
||
| 7 | Compression images côté client — **2560 px / JPEG q82**, ~12× de gain | [v2/media-storage-plan.md](v2/media-storage-plan.md) |
|
||
| 8 | Alerte de budget GCP (5 min) | idem |
|
||
| 9 | Quota : pré-vol + contrôle autoritaire à `Create` + `Delete` du blob | idem |
|
||
|
||
### Bugs découverts — indépendants de la migration
|
||
|
||
| # | Quoi | Doc |
|
||
|---|---|---|
|
||
| 10 | **Visite hors ligne quasi non fonctionnelle** — le `switch` de collecte des ressources est commenté côté backend *et* côté visitapp : ni images d'articles, ni audios | [v2/offline-visit-plan.md](v2/offline-visit-plan.md) |
|
||
| 11 | Ressource modifiée jamais re-téléchargée (filtre sur la présence, pas la version) | idem |
|
||
| 12 | `audio/mpeg` absent de la table d'extensions → MP3 probablement en `.unknown` | idem |
|
||
| 13 | Purge des fichiers obsolètes désactivée sur le device | idem |
|
||
|
||
### V1 — après la migration (décidé le 2026-08-07)
|
||
|
||
Le RAG **est** dans la V1, dans son périmètre restreint : `IVectorStoreService`, jobs Hangfire sur le contenu CMS, endpoint `/ask`, et l'écran **[Guide IA](v2/guide-ia-screen-plan.md)** dans manager-app. Pas d'ingestion documentaire, pas d'OCR, pas de TTS — c'est ce qui le rend tenable.
|
||
|
||
**Deux écrans manager-app sont spécifiés et maquettés** :
|
||
|
||
| Écran | Plan | Maquette |
|
||
|---|---|---|
|
||
| **Guide IA** — 🔨 **onglet Configuration livré le 2026-08-07** | [v2/guide-ia-screen-plan.md](v2/guide-ia-screen-plan.md) | https://claude.ai/code/artifact/c43fe02d-3283-48c2-992f-d687e18acdfa |
|
||
| **Statistiques** — 🔨 **refonte livrée le 2026-08-07** | [v2/stats-screen-plan.md](v2/stats-screen-plan.md) | https://claude.ai/code/artifact/052fe8f3-0288-4847-b63f-4c7de06068ec |
|
||
|
||
Tableau de chantiers global : https://claude.ai/code/artifact/4c317904-2e23-45d3-ac14-dcca678ae5d3
|
||
|
||
> 🔨 **Premier morceau livré le 2026-08-07** — migration `AddGuideIaConfigToInstance` (4 colonnes nullable, additive), champs sur `Instance`/`InstanceDTO`, client `manager_api_new` étendu à la main, écran `Screens/GuideIa/` avec 35 clés i18n FR/EN/NL. `dotnet build` et `flutter build web` passent.
|
||
>
|
||
> ✅ **`AssistantService` branché sur la configuration le 2026-08-07** — l'écran ne pilote plus dans le vide. C'est le lot 1 du **§1quater** ci-dessous, qui donne l'ordre d'exécution de tout le chantier Guide IA.
|
||
>
|
||
> 📄 **Repartir de zéro dans une conversation neuve** : ordre de lecture et amorce à copier dans [v2/guide-ia-screen-plan.md](v2/guide-ia-screen-plan.md) § « Reprendre ce chantier dans une conversation neuve ».
|
||
>
|
||
> ~~Relevé en vérifiant : tout le system prompt est codé en dur, dans deux blocs…~~ **Corrigé le 2026-08-07.** Il y avait en fait **quatre** blocs de prompt et non deux ; les quatre lisent maintenant la configuration du client. Détail : [v2/guide-ia-screen-plan.md](v2/guide-ia-screen-plan.md).
|
||
|
||
> 🔨 **Statistiques — refonte livrée le 2026-08-07, export PDF compris**, `Screens/Statistics/statistics_screen.dart` réécrit, **aucun changement backend**. Les 7 défauts sont corrigés : barres horizontales monochromes à la place des barres verticales tronquées et des deux anneaux, barre de filtres unique avec les volumes par canal, canaux filtrés sur ceux que l'instance possède réellement, règle mono-canal, 4 KPI portant chacun leur variation, bandeau « à retenir », courbe en aire avec bandes de week-end et survol, bloc rapport avec **bouton de téléchargement actif** (PDF généré côté client, paquet `pdf` Dart). ~55 clés i18n FR/EN/NL. `flutter analyze lib/Screens/Statistics` est propre ; `flutter test test/statistics_report_test.dart` 4/4.
|
||
>
|
||
> ⚠️ **Rien de tout ça n'a été ouvert dans un navigateur** — ni l'écran sur des données réelles, ni le PDF produit. **Cases de test : [test-plan.md §8bis](test-plan.md) (écran, 9 cas) et [§8ter](test-plan.md) (export PDF, 6 cas)**. Trois écarts assumés par rapport au plan (comparaison de période côté client, bandeau généré côté client, « visiteurs uniques » remplacé par « contenus consultés » car non calculable) : détail dans [v2/stats-screen-plan.md](v2/stats-screen-plan.md) § État d'implémentation.
|
||
>
|
||
> ℹ️ **Précision sur `flutter analyze`** : le projet a **68 erreurs**, toutes dans les fichiers modèle orphelins de `manager_api_new` (+ `lib/api/openApiTest.dart`) — c'est la dette « client généré désynchronisé » déjà listée, pas une régression. `flutter build web` passe malgré elles : ces fichiers ne sont pas dans le graphe de compilation. Ne pas lire un `flutter analyze` global comme un feu vert, filtrer sur le dossier travaillé.
|
||
|
||
⚠️ Point de correction V1 : l'indexation doit respecter `IsActive`. Une section non publiée ne doit pas être indexée, et la désactiver doit purger ses embeddings — sinon le guide révèle du contenu que le client prépare.
|
||
|
||
### V2 — incréments
|
||
|
||
**Côté IA** : ingestion documentaire (PDF/Word/images + OCR Gemini), TTS pré-généré, talking head, génération de visites, guides multiples et guide thématique par section. Détail : [v2/rag-pgvector-integration-plan.md](v2/rag-pgvector-integration-plan.md).
|
||
|
||
**Côté produit — reporté en V2 le 2026-08-07** (spécifié dans [todo-features.md](todo-features.md), rien n'en dépend en V1) :
|
||
|
||
| Chantier | Pourquoi ça peut attendre |
|
||
|---|---|
|
||
| **SectionForm** — formulaires personnalisés (feedback, satisfaction, inscription atelier) | Nouveau type de section + table de réponses : purement additif, aucun impact sur le schéma existant |
|
||
| **Ressource 360°** — images panoramiques (`ResourceType.Panorama360` + package `panorama`) | Une valeur d'enum et un branchement dans l'affichage des ressources ; se greffe sans toucher aux sections |
|
||
| **AR image tracking (Mind AR)** — modèle `ArAnchor`, microservice Node de compilation `.mind`, WebView scanner unifiée QR + image | Le plus gros des trois : nouveau service Docker, nouveau modèle, refonte du scanner visiteur. Fort effet démo, zéro urgence |
|
||
| **Kiosk web** — version Flutter Web de `tablet-app` | Aucun client ne le demande, et `tablet-app` ne compile pas aujourd'hui : il faudrait d'abord réparer le build natif pour en dériver une cible web. ⚠️ **Ne pas confondre avec le visiteur web** (`visitapp-web`), lui déjà écrit et attendu en V1 |
|
||
|
||
**Prochaine chose à faire si tu as 1h** : le **§21 du plan de test** — vérifier sur un device l'état réel de la visite hors ligne. C'est peut-être déjà remonté comme « l'appli marche mal dans le fort » sans que la cause ait été identifiée, et ça conditionne l'urgence des points 10-13.
|
||
|
||
---
|
||
|
||
## 1quater. Chantier « Guide IA V1 » — ordre d'exécution (arrêté le 2026-08-07)
|
||
|
||
> Cette section ne décrit **rien de neuf** : elle donne l'ordre dans lequel exécuter ce que trois plans détaillent déjà, parce que le chantier les traverse tous les trois et qu'aucun ne porte la vue d'ensemble.
|
||
>
|
||
> **Décision : tout est fait d'un coup, sans mise en prod intermédiaire.** Rien ne part en prod avant que le backlog (kanban + ce fichier) soit terminé — voir §1ter, la base Postgres est vide et c'est une fenêtre à ne pas refermer.
|
||
|
||
### ✅ Lot 1 — Rendre vrai ce qui est déjà livré · **fait le 2026-08-07**
|
||
|
||
→ **[v2/guide-ia-screen-plan.md](v2/guide-ia-screen-plan.md)** § « Configuration branchée sur `AssistantService` »
|
||
|
||
`BuildGuideIdentity()` et `BuildFallbackInstruction()` dans `AssistantService`, consommés par les **4** blocs de prompt — le relevé initial n'en annonçait que deux, il en existe quatre (scope configuration / scope instance × vocal / texte). Ton codé en dur retiré, seconde politique de repli de la ligne 550 réconciliée, règle hors-sujet **ajoutée aux deux variantes texte qui n'en avaient aucune**. Les 4 `new HttpClient()` des outils d'agenda passent par `IHttpClientFactory` (§3, priorité haute).
|
||
|
||
`dotnet build` 0 erreur · `dotnet test` **124/124**.
|
||
|
||
⚠️ **Pas encore vu tourner** : aucun échange réel joué contre un guide configuré. À couvrir au moment du plan de test.
|
||
|
||
### ✅ Lot 2 — Le schéma · **livré le 2026-08-09**
|
||
|
||
→ **[v2/rag-pgvector-integration-plan.md](v2/rag-pgvector-integration-plan.md)** · **[v2/media-storage-plan.md](v2/media-storage-plan.md)**
|
||
|
||
| # §1ter | Livré | Vérifié en base |
|
||
|---|---|---|
|
||
| 1 | Image `postgis` + `pgvector` — `Deployment/Dockerfile.postgres`, base **épinglée par digest** | `postgis 3.4.3` + `vector 0.8.6` |
|
||
| 2 | Table `ContentEmbedding` + 3 index (dont HNSW `vector_cosine_ops`) | migration `AddContentEmbedding` |
|
||
| 3 | `IEmbeddingService` + `GoogleEmbeddingService` (batch par 50) | **768 validé contre l'API réelle**, insertion + recherche jouées en base |
|
||
| 4 | Colonnes `Resource` (7) + `Word`/`PowerPoint`/`Text` en **fin** d'enum | migration `AddResourceStorageAndAiColumns` |
|
||
| 7 | Table `VisitorQuestion` + `ConversationId` sur `AiChatRequest` | migration `AddVisitorQuestion` |
|
||
|
||
`dotnet build` 0 erreur · `dotnet test` **124/124** · 62 migrations, 26 tables · données intactes (52 sections, 45 ressources).
|
||
|
||
**Ce que l'appel réel à l'API a appris (2026-08-09)** — trois choses qu'aucun plan ne disait :
|
||
|
||
1. **`gemini-embedding-001` renvoie 3072 dimensions par défaut.** Le paramètre `dimensions: 768` est **obligatoire** dans la requête, sinon la colonne `vector(768)` rejette l'insertion.
|
||
2. **Les vecteurs réduits ne sont pas normalisés.** Norme L2 mesurée : **0,578**, pas 1. La distance cosinus de pgvector suppose des vecteurs normalisés — sans normalisation côté client, le classement des résultats serait faussé **sans qu'aucune erreur ne soit levée**. `GoogleEmbeddingService.Normalize()` s'en charge.
|
||
3. **Google ne renvoie pas le champ `index`** du contrat OpenAI (chaque item ne porte que `embedding` et `object`). Un `OrderBy(d => d.Index)` trierait sur des zéros : retiré, l'ordre du tableau fait foi.
|
||
|
||
**Recherche cross-lingue vérifiée en base**, question posée en néerlandais sur du contenu FR/NL :
|
||
|
||
| Contenu | Similarité |
|
||
|---|---|
|
||
| NL, même sujet | **0,8145** |
|
||
| FR, même sujet | **0,7982** |
|
||
| FR, hors sujet | 0,4818 |
|
||
|
||
Le pari « pas de filtre de langue » tient : le FR pertinent bat largement le FR hors-sujet. Et l'écart NL/FR n'est que de **0,016** — trop faible pour trier tout seul, ce qui confirme la nécessité du **bonus de score** par langue plutôt que de s'en remettre à la similarité brute.
|
||
|
||
Vérifié aussi : la contrainte anti-doublon rejette bien un rejeu Hangfire, et un vecteur de 512 dimensions est refusé par la colonne.
|
||
|
||
**Schéma `ContentEmbedding` validé le 2026-08-09** — il diverge du plan écrit sur trois points, sur la base du code réel :
|
||
- `string InstanceId` et non `int VenueId` : tous les Id du projet sont des `string` (héritage ObjectId Mongo). **Filtre dur** — frontière entre deux clients.
|
||
- `string? ConfigurationId` **nullable**, absent du plan : `Resource` ne porte pas de `ConfigurationId`. Sert de **bonus de score**, jamais de filtre — décision produit : le guide connaît tout le lieu et priorise la visite en cours. Même patron que le multilingue.
|
||
- `long Id` : table technique à fort volume, jamais référencée ailleurs.
|
||
|
||
**Relevé en chemin :**
|
||
- ⚠️ **La base locale avait une migration de retard.** `AddGuideIaConfigToInstance` (lot 1) n'était pas appliquée : les colonnes `Guide*` n'existaient pas, donc **toute requête sur `Instances` aurait planté** — l'API entière, dès le login. Appliqué le 2026-08-09. Le compilateur ne dit rien de l'état de la base.
|
||
- ⚠️ **Warning de collation** sur la base locale (`glibc 2.36` → `2.31`), corrigé par `REINDEX` **puis** `REFRESH COLLATION VERSION` — dans cet ordre, le refresh seul masque le symptôme sans réparer les index. C'est la raison de l'épinglage par digest : un tag mobile rejouerait ça sur des données clients.
|
||
- `SizeBytes` **existait déjà** sur `Resource`, contrairement à ce qu'annonçait le plan médias — mais jamais renseigné (`= 0` partout). Le backfill (point 5) reste entier.
|
||
- `ResourceDTO` volontairement non modifié : exposer les nouvelles colonnes désynchroniserait `manager_api_new`, qui s'édite à la main. L'UI est V2.
|
||
- pgvector **0.8.6** et non 0.5 : les *iterative index scans* existent (`hnsw.iterative_scan`), vraie parade au post-filtrage HNSW là où le plan ne prévoyait que de monter `ef_search`. À régler au lot 3.
|
||
|
||
### ⚠️ Dette ouverte — aucun test ne couvrira le RAG
|
||
|
||
Ajouter `ContentEmbedding` a cassé **116 des 124 tests** d'un coup : ils tournent sur **EF InMemory**, qui ne connaît pas le type `Vector` et refuse de valider le modèle. Contournement appliqué — l'entité est exclue quand le provider n'est pas Npgsql (`Database.IsNpgsql()` dans `OnModelCreating`).
|
||
|
||
Les tests repassent, mais **la conséquence est structurelle** : vector store, recherche cosinus, index HNSW, contrainte anti-doublon Hangfire — rien de tout ça n'est testable tant que la suite ne tourne pas sur un vrai Postgres. C'est la dette « tests EF InMemory peu représentatifs » du §3, qui cesse d'être théorique : elle rend le cœur du lot 3 non couvert.
|
||
|
||
Piste : Testcontainers (`Testcontainers.PostgreSql`) sur l'image `Dockerfile.postgres`, au moins pour les tests du vector store. Pas chiffré, pas planifié.
|
||
|
||
### Lot 3 — Le RAG · 1 à 2 semaines
|
||
|
||
→ **[v2/rag-pgvector-integration-plan.md](v2/rag-pgvector-integration-plan.md)** § « Ensuite — incrément indépendant, à froid »
|
||
|
||
`IVectorStoreService` (delete-then-insert transactionnel) · jobs Hangfire sur les saves de `SectionController`, queue dédiée · **respect d'`IsActive` avec purge des embeddings à la désactivation** (non négociable) · endpoint `/ask`, recherche cross-lingue sans filtre de langue avec bonus de score, réglage du post-filtrage HNSW, **sources renvoyées dans tous les cas**.
|
||
|
||
> **Le point de charge caché du chantier.** `GetEmbeddableText()` est à écrire pour chacun des **13 sous-types de section** — ingrat, sans difficulté technique, mais c'est lui qui détermine la qualité des réponses : ce qu'on n'extrait pas, le guide ne le connaîtra jamais.
|
||
>
|
||
> Il se double de `GetReferencedResourceIds()`, réclamé par [v2/offline-visit-plan.md](v2/offline-visit-plan.md) — mêmes sous-types, mêmes structures parcourues. **À écrire dans la même passe.** Les faire séparément, c'est payer deux fois.
|
||
>
|
||
> ✅ **La passe commune a bien eu lieu** — vérifié le 2026-08-11 : `GetReferencedResourceIds()` existe dans les 13 sous-types de `Data/SubSection/`. Mais il n'est **appelé nulle part** : le switch de collecte de `ConfigurationController.Export` est toujours commenté en bloc (~180 lignes mortes). Le bug offline nº1 coûte donc le branchement d'une méthode existante, pas une passe sur 13 sous-types — il descend en coût, pas en priorité.
|
||
|
||
> **✅ Pipeline d'ingestion livré le 2026-08-10.** `IIngestionService` / `IngestionService` : chargement des collections filles par sous-type (sans `Include`, `GetEmbeddableText` rendait un texte amputé de ses événements, étapes et questions **sans rien signaler**), un jeu de morceaux par langue renseignée, `ChunkIndex` continu toutes langues confondues pour respecter la contrainte d'unicité. Branché sur les trois chemins de `SectionController` (création, mise à jour, suppression), sur `AgendaSyncService.SyncSectionAsync` — après son `SaveChanges`, sinon on indexerait les anciennes dates — et servi par un **serveur Hangfire séparé** (`ingestion`, 2 workers) pour que les extractions ne prennent pas les workers de la file générale.
|
||
>
|
||
> **✅ Garde par plan.** `AiTokensPerMonth > 0` vérifié dans le job, pas seulement à l'enqueue : le plan peut changer entre les deux, et c'est ce contrôle-là qui décide de l'appel facturé.
|
||
>
|
||
> **✅ Rattrapage à l'upgrade branché le 2026-08-10.** `BackfillInstanceAsync` part automatiquement depuis `InstanceController.Updateinstance` quand `AiTokensPerMonth` passe de 0 à une valeur, et manuellement via `POST /api/Ai/reindex/{instanceId}` — **SuperAdmin uniquement**, avec le bouton correspondant dans l'écran Guide IA de `manager-app`. Réservé au support, volontairement : exposé au client, il serait cliqué à chaque réponse décevante du guide, pour un coût d'embedding complet et sans rien améliorer.
|
||
>
|
||
> ⚠️ **Le webhook Stripe n'est pas un point d'accroche**, contrairement à ce que supposait le plan : `checkout.session.completed` ne touche ni `SubscriptionPlanId` ni `AiTokensPerMonth`, il bascule `IsTrialActive` et stocke l'id d'abonnement. L'attribution de plan est manuelle. Le jour où le webhook changera le plan, il devra reprendre la même garde 0 → >0.
|
||
>
|
||
> 🐛 **`Updateinstance` ne recopiait pas les quotas du nouveau plan** — `CreateInstance` le faisait, pas lui. Passer un client de Starter à Premium changeait `SubscriptionPlanId` et rien d'autre : 1 Go de stockage et 0 jeton IA conservés. L'endpoint `/quota` masquait la moitié du problème en retombant sur le plan à la lecture, mais `AiController` lit `instance.AiTokensPerMonth` — **le client payait un plan avec IA et restait sans IA**. Extrait dans `ApplyPlanQuotas`, appelé par les deux chemins, avec test.
|
||
>
|
||
> **✅ `SearchKnowledge`** ajouté aux outils d'`AssistantService` — pas d'endpoint `/ask` séparé, qui aurait donné deux assistants concurrents : l'un sachant interroger l'agenda mais ignorant le contenu, l'autre l'inverse. Rend une phrase explicite quand la recherche ne trouve rien, sinon le modèle comble le silence avec ses connaissances générales.
|
||
>
|
||
> **✅ Deux angles morts du découpage corrigés le 2026-08-10.** Ils frappaient le même contenu — l'article, le texte le plus riche du CMS :
|
||
> - **HTML retiré** avant l'embedding (`StripHtml`, remplacement par une espace + `HtmlDecode`). Les balises sont identiques dans tous les contenus : elles tiraient les vecteurs vers un fond commun et écrasaient les écarts de score, bien plus gênant que leur coût en jetons. Volontairement distinct du `StripHtml` d'`AssistantService`, qui remplace par `""` — correct pour de l'affichage, mais y accoler deux paragraphes fusionnerait leurs mots.
|
||
> - **ligne plus longue que `MaxChunkChars` découpée** (`SplitLongLine`, coupe à la dernière fin de phrase, repli sur l'espace). Le HTML ne contient pas de `\n` : une fois les balises retirées, un article formait une seule ligne de toute sa longueur, dépassait l'entrée maximale du modèle, et l'échec emportait les 49 autres morceaux de son lot d'embedding. Symptôme : « le guide ne connaît pas mes articles », visible nulle part ailleurs que dans le dashboard `/hangfire`.
|
||
>
|
||
> ⚠️ Vérifier l'entrée maximale réelle de `gemini-embedding-001` dans la doc Google au moment du test de charge — `MaxChunkChars = 1200` (~300 jetons) a de la marge, mais la valeur n'a pas été confirmée.
|
||
>
|
||
> **✅ Downgrade tranché le 2026-08-10 : on conserve.** Le code purgeait les embeddings dès que l'instance perdait l'IA. Rétabli sur la décision du plan — quelques Mo de vecteurs ne pèsent rien, l'usage est bloqué en amont, et purger ferait payer une réindexation complète pour un incident de paiement réglé le lendemain. La purge reste sur les deux cas qui la justifient : section supprimée, section désactivée (`IsActive`).
|
||
>
|
||
> 🐛 **Trou de facturation trouvé et corrigé le 2026-08-10.** `CheckQuota` ne bloquait que si `quota > 0 && AiTokensThisMonth >= quota` : avec `AiTokensPerMonth = 0`, **aucun blocage et aucun compteur**. L'ambiguïté vient du code lui-même — `0` veut dire *illimité* pour `StorageQuotaBytes` mais *pas d'IA* pour `AiTokensPerMonth`. Comme `IsAssistant` est un drapeau manuel indépendant du plan (`InstanceController:197`), une instance `plan-starter` avec `IsAssistant = true` consommait de l'IA gratuite et non comptée. **Pas théorique** : `MigrationController` ne reprend pas `SubscriptionPlanId` (écart **c** du §1quinquies), donc toute instance migrée depuis Mongo arrive à 0 — rien ne s'indexe *et* l'IA tourne sans compteur. Corrigé en 403 avant tout appel au modèle, avec un test dédié.
|
||
>
|
||
> **✅ Déclenchement unifié le 2026-08-10 → [v2/rag-indexing-trigger-decision.md](v2/rag-indexing-trigger-decision.md).** Deux mécanismes concurrents coexistaient (un `Enqueue` par contrôleur *et* un intercepteur EF), ce qui doublait chaque enqueue et cassait 2 tests — `BackgroundJob.Enqueue` est statique et lève sans `JobStorage.Current`. **L'enqueue par contrôleur seul ne pouvait pas marcher** : mesuré, les 5 sous-contrôleurs totalisent **30 `SaveChanges` et 0 `Enqueue`**, et ils enregistrent l'entité fille sans jamais toucher la ligne `Section` (`SectionMapController:148`). Un client ajoutant 40 points d'intérêt à une carte n'aurait rien réindexé — précisément le contenu que les 13 `GetEmbeddableText()` savent extraire. Retenu : **`SectionIndexingInterceptor`** seul, avec sa table de résolution enfant → section et un filtre `{Order, DateUpdate}` qui évite de payer un embedding par section déplacée lors d'un réordonnancement.
|
||
|
||
> **✅ Première exécution réelle — 2026-08-10.** Le pipeline a tourné contre la base locale et l'API Google, hors HTTP (harnais jetable, pas de contournement d'auth). **52 sections → 2150 morceaux en 53 s.** L'index se remplit, la recherche cosinus répond, le parcours itératif HNSW ne bronche pas, et le bonus de langue fait son travail : question NL → morceaux NL en tête, avec du contenu d'une autre langue qui remonte quand même.
|
||
>
|
||
> ⚠️ **Constat en passant, en données réelles** : l'instance était sur `plan-standard` (5 M jetons) avec `AiTokensPerMonth = 0` sur la ligne `Instance`. C'est le bug `Updateinstance` en vrai, pas en théorie — sans le correctif, la garde d'indexation aurait écarté les 52 sections en silence.
|
||
>
|
||
> **Deux défauts que seule l'exécution pouvait montrer, corrigés :**
|
||
> - **Les gabarits étaient indexés.** `LanguageInit.Init` pose « FR - Title », « NL - Description » à la création d'une section, et ils restent tant que la langue n'est pas remplie. Résultat mesuré : les **cinq** premiers résultats d'une question en néerlandais étaient des `NL - Title NL - Description`. Le bonus de langue suffit à faire passer un gabarit vide devant du vrai contenu français — soit exactement le cas que la recherche cross-lingue devait servir. Filtrés à l'indexation (`WithoutPlaceholders`) : une langue qui n'a que des gabarits ne produit plus aucun morceau. −255 morceaux sur 2405.
|
||
> - **Le même texte occupait trois des cinq résultats**, au même score : une description recopiée sur chacun des événements d'un agenda. `DistinctBy(Text)` avant le `Take(topK)` — sinon c'est 60 % du contexte du modèle gâché.
|
||
>
|
||
> Après correction, les cinq premiers résultats FR **et** NL sont du contenu réel et distinct.
|
||
>
|
||
> ⚠️ Restent non vérifiés : l'entrée maximale réelle de `gemini-embedding-001` (aucun morceau n'a atteint la limite sur ce jeu) et le comportement du post-filtrage HNSW **à plusieurs instances** — il n'y en a qu'une en base locale, ce qui est précisément le cas où le problème ne se voit pas.
|
||
|
||
**Jalon démontrable en clientèle** : à ce point le guide répond correctement sur le contenu CMS seul. Tout le reste est de l'amélioration.
|
||
|
||
### Lot 4 — Le journal et le reste de l'écran · ~1 semaine
|
||
|
||
→ **[v2/guide-ia-screen-plan.md](v2/guide-ia-screen-plan.md)** §7 et § « Ensuite »
|
||
|
||
Insertion `VisitorQuestion` dans `AiController.Chat`, à côté du `RecordUsage` existant · job de regroupement en thèmes · job de purge à 90 jours · onglet « Ce que demandent vos visiteurs » · aperçu de conversation (à faire converger avec le « Mode preview / cible Assistant-Persona » de [todo-features.md](todo-features.md), pas construire deux fois) · onglet « Vocal » dans les stats. **Volet RGPD dans les CGU avant mise en service.**
|
||
|
||
### ⚠️ Ce que les 4 lots ne couvrent PAS
|
||
|
||
**Les lots 1 à 4 découpent le chantier Guide IA, pas tout le §1ter.** Le lot 2 a pris le point 4 (colonnes `Resource`) parce que c'est du schéma partagé avec le RAG, mais **les points 5, 6, 8 et 9 n'appartiennent à aucun lot** — ils relèvent du chantier médias & stockage, jamais découpé. Constaté le 2026-08-09 : le tableau du lot 2 liste `1, 2, 3, 4, 7` sans le dire.
|
||
|
||
### Lot médias — orphelin de lot jusqu'au 2026-08-09
|
||
|
||
→ **[v2/media-storage-plan.md](v2/media-storage-plan.md)** § « État mesuré en base le 2026-08-09 »
|
||
|
||
Ordre à respecter, sous peine de travailler deux fois :
|
||
|
||
| # | Quoi | Note |
|
||
|---|---|---|
|
||
| 1 | `IStorageService` + **`StoragePath` écrit à `Create`** | La colonne existe mais **n'est affectée nulle part** : elle reste vide même pour les nouvelles ressources |
|
||
| 2 | **`SizeBytes` renseigné à `Create`** | Aujourd'hui alimenté dans `MigrationController`, l'endpoint legacy `Upload` et `Update` — jamais dans `Create`, le chemin réel |
|
||
| 3 | Backfill (§1ter point 5) | **`StoragePath` est un simple `UPDATE` SQL** (chemin déterministe) ; seul `SizeBytes` exige de lister Firebase |
|
||
| 4 | Flag watermark (§1ter point 6) | Le `if instanceId == "633ee379…"` de `ResourceController:265` est intact, TODO d'origine compris |
|
||
| 5-7 | Compression images, quota autoritaire, alerte budget GCP (§1ter 7-9) | Hors schéma, avant la release |
|
||
|
||
⚠️ **Le backfill ne porte pas sur « chaque ligne `Resource` ».** Mesuré : sur 45 lignes, **37** ont un blob, **7** sont des types URL (`ImageUrl`/`VideoUrl`/`JSONUrl`) sans aucun fichier — leur donner un `StoragePath` ou les compter dans le quota serait faux — et **1** est de type fichier sans URL du tout. `SizeBytes` vaut **0 partout** aujourd'hui : le quota de stockage ne veut rien dire pour personne.
|
||
|
||
### Restes non chiffrés, à caser en cours de route
|
||
|
||
Ratio jetons → questions (`/1000`) à caler sur du réel · endpoint de mise à jour d'`ApplicationInstance` pour rendre les canaux activables depuis l'écran · quotas seed 5M/20M tokens à ajuster · ~~`features.reqPerMonth` de la landing~~ ✅ corrigé, la clé n'existe plus dans `myinfomate-landing/src` (vérifié le 2026-08-11).
|
||
|
||
### Hors périmètre V1, assumé
|
||
|
||
Ingestion documentaire (PdfPig + OCR + formats + UI `IncludeInAiKnowledge`, avec quota et `StoragePath` en prérequis), TTS pré-généré, guides multiples et thématiques, talking head, génération de visites.
|
||
|
||
### Décisions figées — ne pas rouvrir
|
||
|
||
`gemini-embedding-001` à 768 dims · voix **Viva** (`Sulafat`) / **Marco** (`Umbriel`), ✅ **écoutées et validées le 2026-08-07** · découpage V1/V2 · pas de bascule « répondre avec ses connaissances générales » · sources visibles côté client, pas côté visiteur.
|
||
|
||
**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
|
||
|
||
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.
|
||
|
||
| Étape | Quoi | Pourquoi dans cet ordre |
|
||
|---|---|---|
|
||
| ~~**A**~~ ✅ 2026-08-10 | **`StoragePath` + `SizeBytes` renseignés à `Create`** (`ResourceController:340`, types URL 2/3/7 exclus) + `manager-app` envoie `sizeBytes` **avant** l'upload (`resources_screen.dart:210`) | Ce n'est pas un chantier, c'est une fuite : chaque upload créait une ligne incomplète de plus. Corrigée, le backfill de l'étape C est **définitif**. Reste non autoritaire : la taille vient du client (§4 point 2 du plan médias) |
|
||
| **B** ✅ **exécuté** 2026-08-10 | **Lot 3 — le RAG**, livré *et* joué contre la vraie base + la vraie API Google : **52 sections → 2150 morceaux en 53 s**, recherche cosinus et bonus de langue vérifiés en conditions réelles. Voir le § « Première exécution réelle » ci-dessous | Le jalon démontrable en clientèle est atteint sur le contenu CMS. Reste le jugement produit — la qualité des réponses — qu'aucun test ne donnera |
|
||
| **C** | **Reste du lot médias** : backfill des 37 lignes + inventaire des orphelins, watermark, compression, quota autoritaire, alerte budget | Le backfill exige de lister le bucket Firebase — plus long, et aucun intérêt à le jouer deux fois |
|
||
|
||
**Amorce pour l'étape A**, utilisable telle quelle :
|
||
|
||
> *« Lis `DOCS/v2/media-storage-plan.md` §5. Renseigne `StoragePath` et `SizeBytes` à la création d'une `Resource` dans `ResourceController.Create` — c'est le chemin réellement emprunté depuis la bascule Firebase, et aucun des deux n'y est écrit aujourd'hui. Le chemin est `pictures/{instanceId}/{resourceId}`. Ne rien écrire pour les types URL (`ImageUrl`, `VideoUrl`, `JSONUrl`) : ils n'ont pas de blob. »*
|
||
|
||
**Amorce pour l'étape B** :
|
||
|
||
> *« Lis `DOCS/STATUS.md` §1quater puis `DOCS/v2/rag-pgvector-integration-plan.md`. Le schéma, `IEmbeddingService`, `IVectorStoreService`, les 13 `GetEmbeddableText()`, le pipeline Hangfire et l'outil `SearchKnowledge` sont livrés et compilent. Reste à **faire tourner la chaîne pour de vrai** contre une base Postgres avec pgvector : vérifier qu'une section enregistrée produit bien ses morceaux, que le guide les retrouve, et régler les deux angles morts du découpage (HTML non retiré, ligne plus longue que la limite non coupée). »*
|
||
|
||
---
|
||
|
||
## 1sexies. Ordre d'exécution de toute la V1 — et le lot design (ajouté le 2026-08-11)
|
||
|
||
> Ce fichier dit **ce qu'il reste**, par domaine. Le kanban dit **où en est chaque chantier**. Ni l'un ni l'autre ne disait **dans quel ordre**, ni ce qui bloque quoi.
|
||
>
|
||
> → **[v1-plan.md](v1-plan.md)** — 9 lots (A à I plus D-bis), 18 liens de dépendance, chemin critique, arbitrages en attente. **4 à 5 semaines**, portées à 5-6 avec le lot design.
|
||
|
||
**Chemin critique** : A (assainir) → B (geler le schéma) → G (`MigrationController`) → H (tests) → **J (RGPD)** → I (bascule). C, D, D-bis, E, F se parallélisent.
|
||
|
||
**D1 — le bug offline nº1 est corrigé (2026-08-11).** Le `switch` de collecte des ressources était commenté **des deux côtés** : 157 lignes mortes dans `ConfigurationController.Export`, 126 dans `Import`, et le pendant dans `downloadConfiguration.dart`. Une visite téléchargée n'embarquait donc que l'image de la configuration, celle du loader et l'image de chaque section.
|
||
|
||
Remplacé par `GetReferencedResourceIds()`, déjà implémentée sur les 13 sous-types. Côté import et côté client, la charge est enregistrée en une passe au lieu d'être redécouverte section par section — ce qui répare au passage la purge des fichiers obsolètes, pilotée par `usedImageOrAudioIds`. **4 tests**, dont un qui vérifie par réflexion que les 13 sous-types implémentent la collecte : le trou venait d'un `switch` où un type oublié passait dans le `default` sans bruit. `dotnet test` **148/148**.
|
||
|
||
⚠️ Le volet visiteur ne tient que sur `flutter analyze` — l'APK de `mymuseum-visitapp` ne compile pas (Gradle/NDK, §1bis). **À confirmer sur device au §21.**
|
||
|
||
**Lot G — les 8 écarts sont clos et le dry run est joué (2026-08-11).** `dotnet build` vert, `dotnet test` **143/143**.
|
||
|
||
**Trois des huit n'existaient pas.** Le plan les décrivait en « perte de données » ; l'export Mongo dit qu'il n'y a rien à perdre :
|
||
- **(a)** `BuildSection` couvre exactement les types présents : les 327 sections de l'export ne contiennent que les valeurs 0 à 10. `SectionEvent` et `SectionParcours` sont nés avec Postgres v3.
|
||
- **(f)** `IsQRCode`/`IsSearchText`/`IsSearchNumber` n'existent pas dans Mongo — `false` est le bon défaut. Contrôle inverse fait : les cinq réglages qui *existent* (`IsDate`, `IsHour`, `IsSectionImageBackground`, `RoundedValue`, `ScreenPercentageSectionsMainPage`) sont bien mappés sur `AppConfigurationLink`.
|
||
- **(g)** tombé avec le rename du lot B.
|
||
|
||
**Recette de bascule** : [test-plan.md §22](test-plan.md) — comparaison contenu par contenu entre l'app d'aujourd'hui et la base migrée, à jouer **pendant que Mongo est encore lisible**. Reliée depuis I7.
|
||
|
||
**Dry run** : automatisé en test (`MigrationDryRunTests`, se saute sans Mongo), joué sur l'export du 1ᵉʳ avril. Il a trouvé du premier coup **20 sections manquantes avec `Erreurs : 0`** — elles référencent 2 configurations supprimées dans Mongo (contenu MDLF). Pas un défaut de migration, mais un silence : elles sont désormais signalées dans `Skipped`. ⚠️ **Décision produit en attente** : recréer les 2 configurations avant la bascule pour récupérer ce contenu, ou acter sa perte. ⚠️ **À rejouer sur un dump frais** avant le jour J — l'export a 4 mois et la prod tourne encore sur Mongo.
|
||
|
||
**Les cinq vrais, corrigés** :
|
||
- **(b)** mesuré : **5 sections quiz portent 41 questions**, toutes jetées. Migrées avec leurs réponses, en `MultipleChoice` (le type de validation n'existait pas dans l'ancien modèle). Les agendas n'ont aucun événement dans Mongo et les parcours n'y existent pas : leurs collections vides sont correctes.
|
||
- **(c)** tout est généré ou dérivé, cf. v1-plan.
|
||
- **(d)** Mongo n'a **pas de champ Role** : le `ContentEditor` en dur dégradait les 10 utilisateurs en silence. Passé à `InstanceAdmin`. ⚠️ Aucun `SuperAdmin` n'est créé par la migration.
|
||
- **(e)** passe par `ResourceStorage` (L5) ; `StoragePath` n'était pas écrit du tout. L'échec du `HEAD` remonte désormais dans le rapport.
|
||
- **(h)** transaction unique, et **500 au lieu de 200 OK** sur échec fatal.
|
||
|
||
**Lot C1 — livré le 2026-08-11.** Calculateur `StoragePath`/`SizeBytes` extrait dans `Helpers/ResourceStorage.cs`, avec 13 tests (`dotnet test` **143/143**). Il ferme le lien **L5** : C2 (backfill) et l'écart (e) du lot G appelleront le même code au lieu d'en écrire deux.
|
||
|
||
- ⚠️ **L'extraction a prouvé la divergence qu'elle devait empêcher.** `ResourceController` a **deux** chemins de création : le chemin **multipart** écrivait `SizeBytes` mais laissait **`StoragePath` nul**, seul le chemin JSON écrivait les deux. Le §1quater annonçait « `StoragePath`/`SizeBytes` écrits à `Create` depuis le 10/08 » — c'était vrai d'un chemin sur deux. Une partie des lignes à rattraper par C2 vient de là.
|
||
- **Angle mort laissé ouvert sciemment** : `Update` peut faire passer `Type` d'un type URL à un type fichier, laissant `StoragePath` nul sur une ligne qui a désormais un blob. Recalculer le chemin sur une ressource existante pointerait vers un objet qui n'a pas bougé dans le bucket — à trancher avec C3 (quota).
|
||
|
||
**Lot B — livré le 2026-08-11. Le schéma est gelé, le lot G est débloqué.** Une seule migration EF (`20260811130738_LotB_FreezeSchema`) : rename `SectionMap.MapResourceId` → `IconResourceId` (**l'écart (g) du §1quinquies tombe avec**), suppression de `SectionEvent.ParcoursIds`, et `Instance.IsImageWatermark` remplaçant le `instanceId == "633ee379…"` en dur de `ResourceController`. **130/130 tests avant comme après**, build Debug **et** Release verts.
|
||
|
||
- ⚠️ **Le rename imposait aussi la propriété de navigation** `MapResource` → `IconResource` : la convention EF l'appariait au FK, la laisser en place aurait fabriqué un FK fantôme. Elle n'était utilisée nulle part ailleurs.
|
||
- ✅ **Point de risque levé par vérification** : EF a généré un `RenameColumn`, **pas** un drop+add — les icônes déjà configurées survivent. L'avertissement « may result in the loss of data » ne porte que sur le `DropColumn` de `ParcoursIds`, qui est l'intention du lot.
|
||
- ⛔ **« Supprimer `SectionEvent.IconResourceId` » était une erreur de doc — ne pas rouvrir.** `SectionEvent` n'a pas ce champ : la ligne visée appartient à la classe **imbriquée `MapAnnotation`**, partagée par SectionEvent, SectionAgenda et SectionMap, et lue par cinq contrôleurs plus les deux `SelectMany` de `GetReferencedResourceIds`. La supprimer aurait cassé les icônes d'annotation des trois types de section et la collecte offline.
|
||
- **Sécurité rapide du lot A, dans la même passe** : le « backdoor `#if DEBUG` » était un bloc de `AuthenticationController.Authenticate` qui **écrasait l'email et le mot de passe reçus** par `test@email.be` — toute compilation Debug authentifiait n'importe quelle saisie. Retiré. `EnableSensitiveDataLogging` passe sous `#if DEBUG` (idiome déjà utilisé dans `Startup.cs` pour CORS et Hangfire ; le Dockerfile publiant en `-c Release`, c'est un verrou réel).
|
||
- **Reste du volet sécurité, chiffré et non fait** : **112** retours `new ObjectResult(ex.Message) { StatusCode = 500 }` exposent le message d'exception interne, répartis sur 17 contrôleurs. Les 136 autres `ex.Message` sont des 400/404 volontaires, à garder. `Startup.UseExceptionHandler(HandleError)` existe mais ne traite que `RequestException`. C'est un chantier à part, pas une retouche.
|
||
|
||
**Lot E — livré le 2026-08-11.** PDF en média d'étape (les trois volets : liste de types paramétrable, `ResourceViewer.tsx`, `CachedCustomResource`), comparaisons `QuestionType` par entier brut, et **W1 tranché : le web reste sur Leaflet** (option a — ni MapBox ni Google Maps JS, donc aucun coût récurrent ajouté avant la prod).
|
||
|
||
⚠️ **W1 ne pouvait pas se faire comme l'option le disait.** « Retirer le champ pour les instances web » est **impossible** : le fournisseur est porté par la **section** — `SectionMap.MapMapProvider` et `SectionAgenda.AgendaMapProvider` — pas par l'instance. Et une même `Configuration` est liée à plusieurs `ApplicationInstance` via `AppConfigurationLink`, donc servie au mobile **comme** au web : masquer le champ aurait supprimé le réglage mobile. Le champ est donc conservé, et manager-app cesse de laisser croire qu'il vaut pour le web (mention sous les deux sélecteurs). Côté `visitapp-web`, zéro ligne : Leaflet inconditionnel était déjà le comportement.
|
||
|
||
⚠️ **Piège de doc corrigé au passage** : le plan disait « cas PDF dans `getElementForResource` ». Cette fonction de `mymuseum-visitapp` fait un `return CachedCustomResource(...)` **avant** son `switch` — tout son switch est du code mort. Le vrai dispatcher est `CachedCustomResource`, et il en a **deux** (ressource distante / fichier local de l'offline). Suivre la doc à la lettre aurait produit un correctif inopérant et « vérifié » par une analyse verte.
|
||
|
||
### Contrôle du code du 2026-08-11 (2e passe) — ce qui a bougé depuis la rédaction du plan
|
||
|
||
> Tout vérifié dans le code, pas dans les docs. Deux points du lot A tombent, un point du lot A est différé, un nouveau suspect apparaît.
|
||
|
||
| Point | Ce que disaient les docs (matin du 11/08) | Ce que dit le code (soir du 11/08) |
|
||
|---|---|---|
|
||
| **Lot A étape 1 — committer** | « 44 fichiers modifiés dont 9 non suivis, prérequis absolu » | ✅ **Fait.** **9 repos** propres et synchronisés avec leur remote — `DOCS/` en est un, ce qu'on oublie en énumérant les repos de code. Voir §4 |
|
||
| **Lot A — rotation des secrets** | Verrou L1, **ouvre** le lot A | 📅 **Différée en fin de backlog**, juste avant I3, à la demande du 2026-08-11 — repos privés auto-hébergés. **Reste le verrou de I3**, ne disparaît pas. Voir §3 |
|
||
| **Lot A — nettoyage `manager_api_new`** | « 14 fichiers modèle orphelins » | ✅ **Diagnostic fermé** : 139 `part` déclarés pour 153 fichiers, les 14 sont hors graphe de compilation → suppression sans effet possible sur un build. Plus `openApiTest.dart`, qui est le déclencheur de génération. Voir §3 |
|
||
| **Lot A — build tablet-app** | « échec Gradle, seul build cassé » | ❓ commit `5a6701d` (« drapeaux du migrateur Flutter ») a peut-être réglé le Gradle, à reconfirmer par un vrai build. **Le problème reste purement Gradle** : une piste Dart a été ouverte puis écartée le même jour — `marker_view.dart:456` référence bien `ContentGeoPoint` (classe hors graphe), mais **la ligne est dans un bloc commenté**, comme sa jumelle de `mymuseum-visitapp:367`. `flutter analyze` sur les deux fichiers : zéro erreur. Ne pas rouvrir cette piste |
|
||
| **DB3 — écran Guide IA** | « 🔨 front livré » | ✅ **Confirmé livré des deux côtés.** Onglets par boutons custom (`guide_ia_screen.dart:249-265`) — pas un `TabBar`, ce qui explique pourquoi le grep du plan concluait « aucun onglet » : la coquille existe bien. Backend : `AiController` `knowledge/{instanceId}:190` et `insights/{instanceId}:232`, `VisitorQuestionPurgeService.cs` présent |
|
||
| **Onglet « Vocal » des stats** | « reste à faire » | ✅ Confirmé absent. `statsChannelVoice` n'est qu'un **libellé de canal** (`statistics_screen.dart:189`), pas un onglet |
|
||
| **Lot B — les 4 changements de modèle** | « rien fait » | ✅ Confirmé, aucun n'a bougé : `SectionEvent.ParcoursIds:28`, `SectionMap.MapResourceId:25`, hardcode watermark `ResourceController.cs:265`, et `MeterZoneGPS` **sans aucun usage visiteur** (zéro occurrence dans `mymuseum-visitapp/lib` hors swagger, zéro dans `visitapp-web/src`) — le bug M3 est réel |
|
||
| **Lot C1 — calculateur extrait** | « à faire » | ✅ Confirmé à faire : `StoragePath` n'apparaît que dans `ResourceController` et `Resource.cs`, aucun service |
|
||
| **Lot D1** | « méthode existe, appelée nulle part » | ✅ Confirmé : hors des 13 sous-types, `GetReferencedResourceIds` n'apparaît que dans sa déclaration abstraite (`Section.cs:85`) et un test |
|
||
|
||
**RGPD reporté en fin de backlog le 2026-08-11** (lot J) — table d'agrégats de thèmes, job de regroupement, interrupteur de collecte par instance, mention affichée aux visiteurs, relecture juridique du §8 des CGU, vérification des conditions Google. C'est tenable **parce que** la porte « rien en prod avant la fin du backlog » tient : la journalisation ne tourne d'ici là que sur des bases de développement, aucun visiteur réel n'est concerné. Si un déploiement anticipé était décidé, ce lot redeviendrait bloquant.
|
||
|
||
**Le lot B n'existait nulle part** : tout changement de modèle encore ouvert (rename `MapResourceId`, arbitrage `ParcoursIds`, unification `QuestionType`, flag watermark) doit passer **avant** la réécriture de `BuildSection`, sinon on écrit le mapping deux fois — et après la bascule, c'est une migration de données sur la prod.
|
||
|
||
### Lot D-bis — les trois écrans de design
|
||
|
||
Constat du 2026-08-11, plus ouvert que ce que ce fichier annonçait : l'écran Guide IA « livré le 07/08 » était **5 cartes en colonne unique sans onglets**, les stats de l'assistant étaient **à zéro**, et la refonte SectionParcours n'avait pas commencé.
|
||
|
||
**Cause identifiée** : les maquettes vivaient dans des artifacts d'un autre compte, illisibles depuis la machine de dev — chaque session redessinait. **Réglé** : le HTML est rapatrié dans `claude design/` (`guide-ia-screen.html`, `statistics-screen.html`, `sectionparcours-refonte-flux.html`). Même découpage que le kanban — contenu dans le repo, diffusion par artifact.
|
||
|
||
| Étape | État |
|
||
|---|---|
|
||
| DB0 — rapatrier les maquettes | ✅ 2026-08-11 |
|
||
| DB1 — socle visuel `constants.dart` | ✅ 2026-08-11 — 11 rôles typographiques, 8 espacements, 5 rayons. **Échelle des maquettes, couleurs de l'app** : un écran neuf se fond dans manager-app, il n'y ouvre pas une seconde palette. Pas de thème sombre |
|
||
| DB2 — sauvegarde au fil de l'eau SectionParcours | ✅ 2026-08-11, **puis retiré le même jour par DB4**. Le garde-fou (confirmation d'abandon sur les trois dialogues + `PopScope`) a fermé la perte silencieuse le temps que la persistance arrive. Les trois dialogues n'existent plus et il n'y a plus de travail non enregistré à perdre : les 3 clés i18n sont supprimées. C'était l'issue prévue par l'arbitrage — les deux étaient alternatives, pas cumulables |
|
||
| DB3 — écran Guide IA 2 onglets | 🔨 2026-08-11 — coquille à onglets, aperçu de conversation sur le **vrai** `/api/AI/chat`, onglet « Ce que demandent vos visiteurs » **alimenté** par `GET /api/Ai/insights/{id}`, carte « Ce que connaît votre guide » sur `GET /api/Ai/knowledge/{id}`, journalisation `VisitorQuestion` branchée dans `AiController.Chat`. 34 clés i18n FR/EN/NL. **Reste** : le job de regroupement en thèmes, la purge 90 j, **le RGPD dans les CGU avant mise en service**, l'onglet « Vocal » des stats |
|
||
| DB4 — forme SectionParcours | 🔨 2026-08-11 — **option A livrée : une fenêtre, un rail d'étapes, profondeur 2**. Les trois `showNewOrUpdate…` sont supprimés au profit de `guided_path_editor.dart` (coquille : fil d'Ariane, rail réordonnable, panneau, pied « Enregistré ») et de trois widgets de champs autonomes — `ParcoursFields`, `EtapeFields`, `QuestionFields` dans `Parcours/Fields/`. Une question se **déplie dans le panneau de l'étape**, plus dans une 4ᵉ fenêtre.<br>**Sauvegarde à la saisie** : `guided_path_api.dart` masque le choix `sectionParcoursApi` / `sectionMapApi`, un débounce de 700 ms écrit le parcours (PUT), chaque étape (POST/PUT/DELETE) et ses questions avec elle. Le parcours est **créé à la première modification**, pas à l'ouverture — fermer une fenêtre neuve sans rien saisir ne laisse rien en base. **Deux pièges du backend** : `UpdateGuidedPath` **supprime les étapes absentes du DTO**, donc le payload n'emporte que celles qui ont déjà un id (les autres attendent leur `CreateGuidedStep` et feraient un doublon) ; et les questions n'ayant pas d'endpoint, leur id entier est récupéré après coup **par `order`**, seul repère stable entre les deux listes. Échec d'écriture : la fenêtre refuse de se fermer et le pied de page porte un « Réessayer ». 14 clés i18n FR/EN/NL, 3 retirées (DB2). `flutter analyze` propre sur le dossier, `flutter build web` ✅.<br>**Reste** : DB5, la vérification à l'œil contre la maquette |
|
||
|
||
**`AIApi` était dans le client généré mais exposé nulle part** dans `client.dart` — les 18 autres façades y sont. Câblé le 2026-08-11.
|
||
|
||
**Arbitrages tranchés le 2026-08-11** — le lot B s'allège :
|
||
|
||
- **`SectionEvent.ParcoursIds` → supprimer.** Le champ n'est **lu ni écrit nulle part** (entité, DTO, migrations — zéro contrôleur, zéro service), et son commentaire dit `// Liens vers GeoPoints spécifiques`, ce qui contredit son nom. Le lien événement↔parcours existe dans l'autre sens, `GuidedPath.SectionEventId`, lui bien entretenu. Décisif : `ParcoursIds` est porté par **`SectionEvent`, pas par `ProgrammeBlock`** — il ne peut donc pas exprimer « les parcours de tel jour », précisément le cas carnaval invoqué pour le garder.
|
||
- **`QuestionType` → on ne touche pas au schéma, et il sort du lot B.** Le trio TextLibre/Digicode/ExpectedAnswer **n'existe pas dans le code** : l'enum vaut `Simple` / `MultipleChoice` / `Puzzle`, référencé à **un seul endroit** côté serveur, et le digicode est un comportement *dérivé* d'une réponse numérique, pas un type. Reste une propreté front (comparaisons par entier brut dans `showNewOrUpdateGuidedStep.dart:409` et `guided_step_challenge.dart:14`), déplacée au lot E. **`MigrationController` n'est plus bloqué que par un seul changement de modèle.**
|
||
|
||
---
|
||
|
||
## 1quinquies. La bascule elle-même — Mongo → Postgres en prod (ajouté le 2026-08-09)
|
||
|
||
> **Trou identifié le 2026-08-09** : le §1ter dit *ce qui doit entrer dans le schéma avant que la base se remplisse*. Il ne dit nulle part **comment on remplit la base**. L'opération de bascule n'était écrite ni ici, ni dans le kanban, ni dans aucun plan de `v2/`.
|
||
>
|
||
> Rappel : la prod tourne **encore sur MongoDB**. Le transfert se fait par `MigrationController` (`POST /api/migration/run`, paramètres `dryRun` et `instanceId`), qui rejoue Instances → Resources → Users → Configurations → ApplicationInstances → Sections → Devices, puis relie les menus et enrichit les ressources PDF.
|
||
|
||
### ⚠️ `MigrationController` a pris du retard sur le modèle
|
||
|
||
Écrit avant l'essentiel des chantiers de 2026 (`SectionEvent`, `SectionParcours`, abonnements, onboarding, guide IA, colonnes médias). Relevé le 2026-08-09 sur [ManagerService/Controllers/MigrationController.cs](../manager-service/ManagerService/Controllers/MigrationController.cs) :
|
||
|
||
| # | Écart | Où | Conséquence si on bascule tel quel |
|
||
|---|---|---|---|
|
||
| a | `BuildSection` ne couvre que **11 types** (Map, Slider, Video, Web, Menu, Quiz, Article, PDF, Game, Agenda, Weather) | `:458-613` | **`SectionEvent` et `SectionParcours` tombent dans le `default`** → ligne d'erreur dans le rapport, section non migrée, silencieusement |
|
||
| b | Les collections filles ne sont jamais remplies : `QuizQuestions = new()`, `EventAgendas = new()`, aucun `GuidedPath`/`GuidedStep` | `:526`, `:591` | Un quiz arrive **sans ses questions**, un agenda sans ses événements |
|
||
| c | `Instance` : **4 champs migrés sur ~35** | `:106-112` | `SubscriptionPlanId` null → instance sans plan (quotas à 0, `HasStats` false) ; `PublicApiKey` null → **les apps visiteur ne s'authentifient plus** (`X-Api-Key`) ; `WebSlug` null → visitapp-web injoignable ; `IsMobile`/`IsTablet`/`IsWeb`/`IsAssistant` à false |
|
||
| d | `Role = UserRole.ContentEditor` en dur | `:225` | **Plus aucun administrateur** après la bascule |
|
||
| e | `Resource` : `StoragePath`, `FileName`, `IncludeInAiKnowledge`, `AiIndexStatus` non renseignés ; `SizeBytes` déduit d'un `HEAD` sur l'URL Firebase, échec avalé → 0 | `:172-181` | C'est **le point 5 du §1ter** (backfill) : à faire *dans* la migration, pas après |
|
||
| f | `IsQRCode` / `IsSearchText` / `IsSearchNumber` forcés à `false` | `:276-278` | Réglages de configuration perdus |
|
||
| g | `MapResourceId = dto?.iconResourceId` | `:469` | À répercuter le jour où la colonne est renommée en `IconResourceId` (§6bis) |
|
||
| h | Pas de transaction globale : 9 étapes, un `SaveChangesAsync` chacune, `catch` qui renvoie **200 OK** avec `FatalError` dans le corps | `:68-86` | Un échec au milieu laisse la base à moitié remplie. La reprise ne tient qu'aux tests `AnyAsync` d'idempotence — qui existent, eux, et sont corrects |
|
||
|
||
> Le point **c** est le plus dangereux : il ne produit **aucune erreur**. La migration se déclare réussie et le lieu est inaccessible aux visiteurs.
|
||
|
||
### La bascule, dans l'ordre
|
||
|
||
Numérotation continue avec le §1ter (1-13).
|
||
|
||
| # | Quoi | Note |
|
||
|---|---|---|
|
||
| 14 | **Remettre `MigrationController` à niveau** — les 8 écarts a→h ci-dessus | Le plus gros morceau. À faire avant tout le reste |
|
||
| 15 | **Dry run** sur un export Mongo de prod, puis comparer les comptages par entité aux comptages Mongo | `dryRun=true` n'écrit rien. Un export existe déjà dans `manager-service/migration-data/` |
|
||
| 16 | **Créer l'environnement prod Postgres** : `Deployment/Dockerfile.postgres` (épinglé par digest), compose prod, Traefik, **port 5432 fermé** | Dépend de la rotation des secrets (§3) — ne pas créer la prod avec les clés committées |
|
||
| 17 | `dotnet ef database update` sur la base vide, puis vérifier `postgis` **et** `vector` présents | Le §1quater rappelle qu'une base en retard d'une migration fait planter l'API dès le login |
|
||
| 18 | **`pg_dump` avant et après**, puis cron quotidien | Déjà noté en §4 comme non fait. Ici il devient bloquant : c'est le seul filet du jour J |
|
||
| 19 | **Rejouer pour de vrai, instance par instance** (`instanceId=…`), en commençant par la plus petite | Le paramètre existe précisément pour ça |
|
||
| 20 | **Vérifications post-bascule** : login manager-app, une app visiteur avec sa clé API, un parcours, une carte, un PDF, un quiz avec ses questions | Les points b et c ne se voient qu'ici |
|
||
| 21 | **Bascule DNS / API**, Mongo gardé en lecture seule quelques jours | |
|
||
| 22 | **Plan de retour arrière écrit avant le jour J** | Un rollback qu'on improvise à 23 h n'est pas un rollback |
|
||
|
||
### Après la bascule — relationnel, pas technique
|
||
|
||
| Quoi | Note |
|
||
|---|---|
|
||
| **Reprendre contact avec Louise Smets — Musée d'Ixelles** | À faire **une fois la prod stabilisée**, pas avant : on n'ouvre pas une conversation en montrant un produit en cours de bascule. Objectif d'abord d'écoute — comprendre ce qu'est réellement son travail au quotidien et sur quoi elle bute — avant de parler de MyInfoMate. L'entraide se décide après, pas dans le premier message. Rien n'est su de son rôle exact à ce stade : à ne pas supposer. |
|
||
|
||
**Rappel de la porte** : rien de tout ça ne démarre avant que le reste du backlog soit terminé (§1quater). La base Postgres vide est une fenêtre — chaque chantier de schéma passé maintenant est un chantier qui n'exigera pas de fenêtre de maintenance plus tard.
|
||
|
||
---
|
||
|
||
## 2. Tests
|
||
|
||
→ Détail complet : **[test-plan.md](test-plan.md)**
|
||
|
||
Checklist manuelle organisée en 20 sections + non-régression. À cocher au fur et à mesure avant une mise en prod. Section "Hors scope" = features non implémentées, pas testées.
|
||
|
||
Ordre conseillé :
|
||
0. **§21 — Visite hors ligne** (ajouté le 2026-08-07) : bugs suspectés d'après le code, à confirmer sur device avant de planifier le chantier
|
||
1. **§0 — Porte d'entrée build** (5 commandes, bloquant absolu)
|
||
2. **§19 — Parité manager-app → apps visiteur**, par type de section : c'est le chapitre qui répond à « tout ce que je configure est-il visible ? ». Le §19.13 (SectionParcours, 5 cas d'usage) est le plus à risque.
|
||
3. §18 — Onboarding self-service (jamais exécuté)
|
||
4. §1-17 — features par lot, §16 non-régression
|
||
|
||
---
|
||
|
||
## 2bis. Parité manager-app → apps visiteur
|
||
|
||
→ **[parity-manager-visitapp.md](parity-manager-visitapp.md)** (audit du 2026-08-05, sur le code réel)
|
||
|
||
- **Couverture par type de section : complète** — les 13 `SectionType` sont dispatchés dans `mymuseum-visitapp` et `visitapp-web`, aucun écran vide.
|
||
- **17 écarts au niveau champ** : 7 côté mobile (dont le centrage de carte, les horaires des points d'intérêt, et surtout le mode contenu d'un parcours sans géodéclenchement), 10 côté web (dont le provider de carte, les icônes de catégorie, et l'absence totale d'assistant IA).
|
||
- Suivi des corrections : table du **[test-plan.md §20](test-plan.md)**.
|
||
|
||
**✅ Résolu le 2026-08-06 — assistant IA dans `visitapp-web`** (écart W8)
|
||
|
||
L'assistant n'existait que sur mobile, alors que le plan Essentiel est web-only et que l'IA est vendue en add-on. `components/assistant/AssistantBubble.tsx` : bulle flottante montée par le layout de configuration, uniquement si `isAssistant` est vrai **sur l'instance et sur l'app Web** (même règle que `AiController`). Consomme `POST /api/AI/chat` en `AppType.Web` — aucun changement backend.
|
||
|
||
- Rend les trois formes de réponse du modèle : texte HTML, `cards[]`, et l'action `navigation` qui route vers la section.
|
||
- **Suggestions d'ouverture dérivées du contenu réel** (`lib/assistantSuggestions.ts`, 9 tests) : les sections déjà chargées donnent les questions proposées — un lieu sans agenda n'en propose pas sur l'agenda, et la carte cite un point réel. Zéro configuration client, zéro token.
|
||
- `expectsReply` sert uniquement à ne pas réarmer la dictée quand le modèle clôt l'échange — pas au focus du champ, où il n'apporterait rien.
|
||
- Quota atteint (429) → message neutre, le visiteur ne lit jamais le motif technique.
|
||
- Maquette de référence : artifact « Assistant de visite — maquette visitapp-web ».
|
||
|
||
**Répercuté sur `mymuseum-visitapp` le 2026-08-06** — l'assistant mobile n'était pas aligné :
|
||
- **Quota IA (429) traité** : `AssistantChatSheet` affichait « Une erreur est survenue, **réessayez** » pour *toute* erreur, y compris le quota épuisé — le visiteur était donc invité à boucler sur un mur. Nouvelle `AssistantUnavailableException` levée par `assistantService`, message neutre côté sheet.
|
||
- **Suggestions d'ouverture** ajoutées (`Helpers/assistantSuggestions.dart`), dérivées de `VisitAppContext.currentSections` déjà en mémoire. Même liste de phrases que le web — garder les deux fichiers alignés.
|
||
- **Forme alignée sur la maquette** : pastille ronde pleine + « Votre guide » + nom du lieu en en-tête ; trois points animés au lieu du `CircularProgressIndicator` + « ... » ; bandeau « Je vous écoute » avec onde pendant la dictée. Le pattern reste une **modale bottom sheet** (`showGeneralDialog` + voile) — c'est le bon geste sur mobile, la bulle flottante du web n'aurait pas de sens sur 375 px.
|
||
|
||
**✅ Résolu le 2026-08-06 — refonte de la configuration des parcours** (écarts M4, M7, M8, W6, W7, X1)
|
||
|
||
Le client ne manipule plus 9 booléens répartis sur 3 niveaux, mais répond à **3 questions** :
|
||
|
||
| Question | Où | Ce qu'elle pilote |
|
||
|---|---|---|
|
||
| **Où se déroule le parcours ?** — En salle / Sur le terrain | SectionParcours | `ShowMap`. « En salle » masque automatiquement position et zone sur toutes les étapes |
|
||
| **Quelle ambiance ?** — Visite / Jeu | GuidedPath | `IsGameMode` + messages de début/fin |
|
||
| **Comment le visiteur progresse-t-il ?** — Libre / Dans l'ordre / Étape par étape (+ masquer les étapes non atteintes) | GuidedPath | `IsLinear`, `RequireSuccessToAdvance`, `HideNextStepsUntilComplete` — dérivés dans `Parcours/progression_mode.dart` |
|
||
|
||
- `IsStepLocked`, `IsHiddenInitially`, `FactContent` **supprimés** du modèle (migration `RemoveDeadGuidedStepFlags`). `IsStepLocked` rendait une étape *définitivement* infranchissable même après réussite du défi.
|
||
- Navigation libre réellement implémentée (pins et segments de progression cliquables), verrouillage dérivé de la progression, masquage appliqué aussi en mode contenu — mobile **et** web.
|
||
- Bascule carte/contenu unifiée sur `ShowMap` seul dans les deux apps.
|
||
- Note ajoutée dans l'éditeur de question : une réponse attendue uniquement numérique déclenche automatiquement le pavé digicode côté visiteur.
|
||
|
||
**Reprise du 2026-08-09 — 4 correctifs sur ce même écran** (relecture après coup, à valider via test-plan §19.13 cas 0)
|
||
|
||
- **La progression enregistrée ne correspondait pas à celle affichée.** Un `GuidedPathDTO` neuf naissait avec `isLinear` nul : `progressionModeOf` affichait « Dans l'ordre », mais `isLinear ??= false` à la sauvegarde enregistrait « Libre ». Tout parcours créé sans toucher à la question partait donc en mode libre. Les valeurs sont désormais posées à la construction. Le test `progression_mode_test.dart` (12/12) ne pouvait pas l'attraper : il couvre la lecture/écriture des booléens, pas le DTO fabriqué par la popup.
|
||
- **« Quelle ambiance ? »** était restée une case à cocher alors que les deux autres questions sont des radios — remise en Visite / Jeu, conforme au tableau ci-dessus.
|
||
- **Médias d'étape rendus en dur comme des images** dans les deux apps visiteur (`guided_path_content_progression_page.dart` forçait `ResourceType.Image`, `StepCarousel` un `<img>`). Le sélecteur accepte pourtant vidéo et audio depuis toujours : ces deux types donnaient une vignette cassée. Corrigé des deux côtés ; le libellé « Images » devient « Médias ». Le PDF, lui, reste à faire — voir todo-features.md.
|
||
- **Menu « Carte de base »** : disparaissait en silence quand la configuration n'avait aucune SectionMap ; il reste maintenant visible et désactivé, avec l'explication.
|
||
|
||
**Reste à trancher — la forme, pas le vocabulaire.** Configurer une question sur une étape et l'écrire en NL traverse **5 surfaces empilées**. Deux options maquettées le 2026-08-09 : **[artifact « Configurer un parcours sans empiler quatre fenêtres »](https://claude.ai/code/artifact/c9d9a5c0-1ba4-4e50-a522-28a0cf2ecd15)** — A, une fenêtre unique avec rail d'étapes (recommandée) ; B, écran plein cadre à 3 colonnes. Rien ne se code avant l'arbitrage ; détail et ordre d'exécution dans [todo-features.md](todo-features.md) § « SectionParcours — refonte du flux de configuration ».
|
||
|
||
---
|
||
|
||
## 3. Sécurité & dette technique
|
||
|
||
→ Détail complet : **[security/audit-securite-manager-service.md](security/audit-securite-manager-service.md)** et **[security/audit-manager-app.md](security/audit-manager-app.md)** (audit du 2026-07-13/14)
|
||
|
||
### manager-service — reste à traiter (par priorité)
|
||
- [ ] **Critique en exposition, différée en calendrier** — Secrets committés en clair dans Git (JWT signing key, clés API, connection strings, mot de passe MQTT, token Telegram) → rotation + variables d'environnement + purge historique Git + suppression `RELEASE/`
|
||
- 📅 **Déplacée en fin de backlog le 2026-08-11, à la demande.** Motif : les 6 repos sont **privés, sur un serveur Gitea auto-hébergé** — l'exposition réelle est celle d'un poste de dev, pas d'un dépôt public. La gravité ne change pas, la fenêtre d'urgence oui.
|
||
- ⚠️ **Ce que ça déplace dans le plan** : la rotation était le verrou L1 du lot A (« ne pas créer la prod avec les clés committées »). Elle reste ce verrou, mais **juste avant le lot I** au lieu d'ouvrir le lot A. La contrainte à ne pas perdre : **elle doit précéder I3** (compose/Traefik prod), sinon la prod naît avec les clés de l'historique et il faut rotationner deux fois.
|
||
- 🔒 **Condition de validité** : tenable tant que les repos restent privés et qu'aucun collaborateur externe n'est ajouté. Un repo rendu public, un fork, ou un CI tiers qui clone l'historique → la rotation remonte devant immédiatement.
|
||
- [ ] **Haute** — `EnableSensitiveDataLogging` actif en prod ; services Mongo legacy en singleton à corriger ; `ValueComparer` manquant sur les JSONB ; ~~`AssistantService` `new HttpClient()` en boucle~~ ✅ corrigé le 2026-08-07 (`IHttpClientFactory`, 4 occurrences)
|
||
- [ ] **Moyenne** — JWT sans expiration forcée, clés API en clair + comparaison non constant-time, bootstrap PIN sans rate limiting, exceptions internes exposées, backdoor `#if DEBUG`, double `SaveChangesAsync` audit (⚠️ vérifier après le fix du 2026-07-15 sur `SaveChanges`/`SaveChangesAsync` — peut être partiellement résolu), `SectionFactory` dupliqué, tests EF InMemory peu représentatifs
|
||
- [ ] **Basse** — conventions REST hétérogènes, `PasswordUtils` avec RNG non crypto, code mort (MQTT, CORS AllowAll), CORS en dur, ordre middleware
|
||
|
||
### manager-app — reste à traiter (par priorité)
|
||
- [x] ✅ **Critique — résolu le 2026-08-11.** Client généré désynchronisé (`manager_api_new/`) : 14 fichiers modèle orphelins → `flutter analyze` inexploitable (183/200 erreurs de bruit, 68 au 09/08). **Mesuré après nettoyage** : `flutter analyze manager_api_new` passe de **67 à 3 issues**, l'analyse globale de manager-app de 68 erreurs de bruit à **3 erreurs**, toutes dans `test/widget_test.dart` (cassé et connu, voir « Basse » ci-dessous). `flutter build web` ✅ avant comme après — attendu, ces fichiers n'étant dans aucun graphe. **`flutter analyze` redevient un feu vert utilisable**, ce qui lève le lien L8
|
||
- **Caractérisé précisément le 2026-08-11.** `lib/api.dart` déclare **139 `part`** pour **153 fichiers** dans `lib/model/`. Les 14 restants portent bien `part of openapi.api;` mais **ne sont déclarés `part` par personne** — en Dart, un fichier `part` non listé par sa bibliothèque est **hors du graphe de compilation**. D'où l'asymétrie qui a coûté du temps : l'analyzer les lit (il parcourt tout `lib/`), le compilateur ne les voit jamais. **Les supprimer ne peut pas changer un build.**
|
||
- Les 14 : `agenda_event_stat_dto`, `app_configuration_link_dto_application_instance`, `content_dto_resource`, `content_geo_point`, `day_stat_dto`, `game_stat_dto`, `level_dto`, `map_dto_map_provider`, `map_dto_map_type`, `map_dto_map_type_mapbox`, `poi_stat_dto`, `quiz_stat_dto`, `section_stat_dto`, `user`.
|
||
- **Fausse alerte levée le 2026-08-11, à ne pas rouvrir** : `ContentGeoPoint` (un des 14) *semble* référencé par du code vivant — `tablet-app/lib/Screens/Map/marker_view.dart:456` et `mymuseum-visitapp/lib/Screens/Sections/Map/marker_view.dart:367`, les deux apps dépendant de `manager_api_new` par `path:`. **Les deux lignes sont dans un bloc commenté** (`/*` ouvert respectivement en 418 et 329) : du code mort, invisible pour le compilateur *et* pour l'analyzer. Vérifié par comptage de délimiteurs et confirmé par `flutter analyze` sur les deux fichiers — zéro erreur. **Aucun des 14 n'a de référence vivante.**
|
||
- 🔒 **Les déclencheurs de génération partent avec eux, et pour une autre raison** : ce n'est pas du modèle mort, c'est le moyen de régénérer (annotation `@Openapi` + `build_runner`). Importés nulle part. Les supprimer ne retire aucun code exécuté — ça **verrouille la règle « `manager_api_new` s'édite à la main »** : tant qu'ils sont là, un `flutter pub run build_runner build` lancé par réflexe régénère le client et écrase les éditions manuelles (`onboarding_api.dart`, le câblage d'`AIApi`, le mapping `isGood` → `isCorrect`).
|
||
- ⚠️ **Ils étaient trois, pas un — trouvé le 2026-08-11.** La règle n'était verrouillée que dans manager-app :
|
||
- `manager-app/lib/api/openApiTest.dart` (dernier run daté du 2026-05-07)
|
||
- `mymuseum-visitapp/lib/api/openApiTest.dart` — **le plus dangereux** : il génère depuis **son propre `lib/api/swagger.yaml`** vers un `manager_api_new` local, ce qui aurait créé un second client divergent dans un repo qui consomme celui de manager-app par `path:`. Il portait en plus une des 5 erreurs Dart du repo
|
||
- `tablet-app/lib/api/openApi.dart` — même configuration (nom de fichier différent, d'où le fait qu'il ait échappé aux recherches précédentes sur `openApiTest`)
|
||
- plus un résidu `tablet-app/manager_api_new/` ne contenant qu'un `pubspec.lock`, non suivi par git, daté d'avril — une génération avortée
|
||
Les trois sont supprimés. **Contrôle de non-régression du verrou** : `grep -rl "@Openapi" */lib` ne doit renvoyer aucun fichier.
|
||
- [ ] **Haute** — Duplication du shell de dialog (15 occurrences), composants quasi-copiés (translation containers), méthodes `build()` monolithiques (440-640 lignes), i18n contournée (~100+ strings en dur)
|
||
- [ ] **Moyenne** — Clé AES en dur + IV fixe (chiffrement de session inutile), erreurs API avalées silencieusement (`catch (_) {}`), `_localPath` cassé, fichiers morts, TODO visibles à l'écran en prod, dépendances pubspec inutilisées
|
||
- [ ] **Basse** — façades API nullable jamais renullifiées, 58 `print()` de debug, credentials de test en dur, nommage non conforme Dart, `test/widget_test.dart` cassé (seul test du projet, ne compile pas)
|
||
|
||
**Prochaine chose à faire si tu as 1h** : ~~la rotation des secrets Git~~ → **le nettoyage de `manager_api_new`** (Critique manager-app). La rotation des secrets a été déplacée en fin de backlog le 2026-08-11 (repos privés auto-hébergés, cf. ci-dessus) ; le nettoyage du client, lui, est une demi-heure sans risque de build et il rend `flutter analyze` utilisable pour tout le lot design.
|
||
|
||
---
|
||
|
||
## 4. Infrastructure & Ops
|
||
|
||
- [x] ✅ **Travail non committé — résorbé. Revérifié le 2026-08-11 (2e passe) : plus rien en attente.** Les **9 repos** (DOCS compris) sont propres (`git status --porcelain` vide partout) **et à jour avec leur remote** (aucun `ahead`/`behind`). L'alerte du 2026-08-09 et sa révision du matin du 11/08 (« ~41 fichiers en working tree dont 9 non suivis ») sont toutes deux **périmées** : le lot 3 RAG est dans `18e4240`, le socle visuel + Guide IA dans `eb8e849`. **L'étape 1 du lot A est terminée** — ne pas la re-planifier
|
||
- Derniers commits par repo : `manager-service` `18e4240` (RAG : pipeline d'ingestion, endpoints du guide IA, journalisation RGPD) · `manager-app` `eb8e849` (socle visuel et écran Guide IA à deux onglets) · `visitapp-web` `473fc3a` · `mymuseum-visitapp` `af44927` · `tablet-app` `5a6701d` (drapeaux du migrateur Flutter) · `myinfomate-landing` `d91cf7f` · `unov-landing` `cfa70c4` · `OpenGlasses` `1d887d7`
|
||
- Rappel de l'état des branches : `manager-service` → `MyInfoMate_3.0`, `manager-app` → `Interface-refacto`, `mymuseum-visitapp` → `Meta-Rayban-Test` (23 commits d'avance sur `master`, voir §5bis), `tablet-app` → `AI-Assistant-test`, `visitapp-web` et les deux landings → `master`
|
||
- [ ] **pg_dump quotidien** pour `manager-service` (Postgres `my_info_mate`), en complément du snapshot OVH — voir mémoire `project_pgdump_todo`, pas encore fait au 2026-07-15. ⚠️ **Devient bloquant au moment de la bascule** : c'est le point 18 du [§1quinquies](#1quinquies-la-bascule-elle-même--mongo--postgres-en-prod-ajouté-le-2026-08-09), le seul filet du jour J
|
||
- ⛔ **Deuxième chose qu'il débloque** : le job `visit-events-purge` (purge des stats au-delà de 13 mois) est codé et enregistré mais **volontairement inactif** — il ne supprimera rien tant que `Stats:RetentionDays` n'est pas défini. Ne l'activer (`Stats__RetentionDays=395`) qu'une fois ce pg_dump en place
|
||
- [ ] **Fermer le port PostgreSQL exposé publiquement** via Traefik/Docker (détail dans [todo-features.md](todo-features.md), section "Infrastructure — Sécurité réseau") — accès DBeaver via tunnel SSH uniquement
|
||
- [ ] **Backup Mongo automatique en cron** — voir [todo-landing-deploy.md](todo-landing-deploy.md), section 5 (dump + `docker cp` vers l'hôte, pas seulement dans le conteneur)
|
||
- [x] Déploiement landing page (`docker-compose-landing.yml` séparé, ne jamais toucher au compose principal) — voir [todo-landing-deploy.md](todo-landing-deploy.md) pour les règles à respecter
|
||
|
||
---
|
||
|
||
## 5. Roadmap produit — nouveaux canaux & IA
|
||
|
||
Plans détaillés, pas encore démarrés en dev (sauf mention contraire) :
|
||
|
||
| Chantier | Détail | Statut |
|
||
|---|---|---|
|
||
| Kiosk web (Flutter Web, extension tablet-app) | [architecture-web-saas.md](architecture-web-saas.md) | 📦 **V2** (2026-08-07) — pas commencé, ~2-3j estimés. Aucun client ne l'attend, et `tablet-app` ne compile même pas aujourd'hui |
|
||
| Visiteur web (Next.js, `app.myinfomate.be/[slug]`) | [architecture-web-saas.md](architecture-web-saas.md) | ⚠️ **V1, presque fini** — `visitapp-web` existe, build ✅, les 13 types de section sont rendus, assistant IA inclus. **Il ne manque que le Dockerfile + l'entrée `app.myinfomate.be` dans le compose.** Bloquant : le plan Essentiel vendu par l'onboarding est web-only |
|
||
| Export PDF stats — **à la demande** | [plan-import-ia-stats-subsides.md](plan-import-ia-stats-subsides.md) §1 | ✅ **Livré le 2026-08-07**, généré côté client (paquet `pdf` Dart), aucun changement backend. **Inclus dans tous les plans qui ont les stats** (décidé le 09/08) — la différenciation vient du contenu, pas d'un verrou |
|
||
| Historique des stats — **13 mois pour tous** | idem · [todo-features.md](todo-features.md) § Plans & Quotas | ✅ **Décidé et appliqué le 2026-08-09**. L'axe « durée » disparaît, `HasAdvancedStats` devient le seul différenciateur stats. Migration `UnifyStatsRetentionTo13Months` **à appliquer**. Purge codée mais inactive jusqu'au pg_dump |
|
||
| Export PDF stats — **envoi mensuel automatique** | idem | v2. C'est là que QuestPDF + Hangfire reprennent leur sens. Manquent : config destinataires/fréquence, méthode d'envoi avec pièce jointe, et `libfontconfig1` au Dockerfile |
|
||
| Import IA de contenu + RAG (pgvector) | [plan-import-ia-stats-subsides.md](plan-import-ia-stats-subsides.md), [v2/rag-pgvector-integration-plan.md](v2/rag-pgvector-integration-plan.md) | Priorité 2 — chantier fusionné, ~4-6 semaines. **Le schéma part avec la migration v3, voir §1ter** |
|
||
| Médias & stockage (compression, `IStorageService`, quota) | [v2/media-storage-plan.md](v2/media-storage-plan.md) | **Prérequis à la release de migration** — voir §1ter |
|
||
| Visite hors ligne — remise en état | [v2/offline-visit-plan.md](v2/offline-visit-plan.md) | **Bugs**, pas de la roadmap — voir §1ter |
|
||
| Écran Guide IA (manager-app) | [v2/guide-ia-screen-plan.md](v2/guide-ia-screen-plan.md) | **V1**, spécifié et maquetté — voir §1ter |
|
||
| Écran Statistiques — refonte | [v2/stats-screen-plan.md](v2/stats-screen-plan.md) | ✅ **Livré le 2026-08-07**, export PDF à la demande compris — les 7 défauts corrigés. **Pas encore ouvert dans un navigateur** : [test-plan.md §8bis et §8ter](test-plan.md) |
|
||
| TTS pré-généré (audioguide multilingue) | [v2/tts-pregenerated-plan.md](v2/tts-pregenerated-plan.md) | Priorité 3 |
|
||
| Talking head (avatar animé) | [v2/talking-head-plan.md](v2/talking-head-plan.md) | Priorité 6 — polish, bouche-trou |
|
||
| AI Persona par venue (guide IA personnalisable) | [v2/myinfomate-ai-persona-analysis.md](v2/myinfomate-ai-persona-analysis.md) | Architecture de référence pour les chantiers IA ci-dessus |
|
||
| Lunettes Ray-Ban Meta | [roadmap.md](roadmap.md) (section XR), [rayban-meta-integration.md](rayban-meta-integration.md) | 🔨 **POC fonctionnel — bien plus avancé que ce que cette ligne disait jusqu'au 2026-08-09.** Voir §5bis |
|
||
| VR Meta Quest (borne office tourisme) | [roadmap.md](roadmap.md) (section XR) | Idée, **zéro ligne de code** dans tous les repos — vérifié le 2026-08-09. La landing annonce pourtant « Meta Quest — Bientôt » en 4 langues (`translations.ts`, clé `mode4Desc`) |
|
||
| Canal subsides | [plan-import-ia-stats-subsides.md](plan-import-ia-stats-subsides.md) | Actions administratives/réseau, pas de dev |
|
||
|
||
---
|
||
|
||
## 5bis. Lunettes Ray-Ban Meta — état réel du code (relevé le 2026-08-09)
|
||
|
||
> Cette section corrige une erreur de ce fichier : la roadmap classait l'AR lunettes en « pas commencé, pilote subventionné quand le SDK sort de preview ». **Un POC fonctionnel existe depuis mai-juin 2026**, ~2 700 lignes de Dart dans `mymuseum-visitapp`, sur la branche **`Meta-Rayban-Test`** (23 commits d'avance sur `master`, jamais mergée — c'est la branche de travail courante du repo).
|
||
|
||
| Brique | Fichier | État |
|
||
|---|---|---|
|
||
| SDK Meta | `pubspec.yaml:84` — `meta_wearables_dat: ^0.1.3` | Dépendance **active**, pas commentée |
|
||
| Connexion / photo / stream vidéo | `lib/Services/meta_glasses_service.dart` (241 l.) | Cycle de vie complet, routage audio HFP via canal natif |
|
||
| Pipeline mains-libres | `lib/Services/Glasses/voice_orchestrator.dart` (408 l.) | wake word → STT → dispatch → LLM ou scan QR → TTS, sons d'état |
|
||
| Moteurs interchangeables | `lib/Services/Glasses/engines/` + `impl/` (~1 040 l.) | 4 interfaces, **11 implémentations** : openWakeWord, Porcupine, speech_to_text, Whisper, Home Assistant STT, Gemini TTS, ElevenLabs, flutter_tts, client LLM MyInfoMate |
|
||
| Service de fond | `lib/Services/Glasses/glasses_background_service.dart` | Téléphone en poche, écran verrouillé |
|
||
| UI | `VoiceModeSheet`, `GlassesStatusWidget`, `GlassesDebugPanel` (450 l., via `AdminPopup`) | Monté dans `main.dart:109-112` |
|
||
|
||
Commit `c652604` : *« Working flow also in background ! With custom wakeword working (hey visit et hey viva). flow llm working + take photo + scan qr code working »*.
|
||
|
||
### Ce qui bloque réellement la commercialisation
|
||
|
||
Pas « le SDK est en preview » — la liste est plus concrète :
|
||
|
||
- **Android uniquement** (`if (!Platform.isAndroid) return;` dans `meta_glasses_service.dart`). Le modèle **BYOD tombe** pour tous les visiteurs iPhone. Seul le modèle « kit prêté par le lieu » tient aujourd'hui.
|
||
- **Pas de distribution store** tant que le SDK est en developer preview : chaque installation passe par les credentials développeur.
|
||
- **TTS = ElevenLabs, facturé au caractère.** C'est le seul poste qui peut casser la marge d'un add-on à 20-30€/mois : un audioguide vocal génère du volume continu, contrairement au chat texte. `gemini_tts_engine.dart` existe déjà — **basculer dessus avant de vendre l'add-on**.
|
||
- **Wake word de prod non réglé** : `porcupine_flutter` est commenté dans `pubspec.yaml` (payant), l'implémentation courante s'appuie sur `speech_to_text` / openWakeWord.
|
||
- **Branche jamais mergée** dans `master`.
|
||
- **Le POC ne compile plus en l'état — relevé le 2026-08-11.** `flutter analyze lib` sur `mymuseum-visitapp` remonte **4 erreurs**, toutes le même manque : `kElevenLabsApiKey` et `kElevenLabsVoiceId` sont **absents de `constants.dart`** alors que `glasses_qr_scanner_service.dart:110-111` et `wake_word_service.dart:133-134` les utilisent et importent bien `constants.dart`. La doc de `glasses_tts_service.dart:31-32` confirme l'intention (« typiquement `kElevenLabsApiKey` de `constants.dart` »). Ces deux constantes sont des **secrets qui n'ont jamais été committés** — le POC tournait avec un `constants.dart` local. Conséquence pratique : **« démontrable » suppose de les remettre**, ce n'est pas un `git checkout` qui suffit. À traiter avec la bascule TTS → Gemini ci-dessus, qui les rend inutiles.
|
||
- ⚠️ **`flutter build apk` ne passe plus non plus**, pour une raison distincte et non liée au Dart : Gradle s'arrête sur `Gradle build failed to produce an .apk file`, avec une demande de `ndkVersion = "28.2.13676358"` dans `android/app/build.gradle` et des avertissements KGP sur 11 plugins. Le §1bis le donnait ✅ au 2026-08-06 : **c'est l'environnement Android qui a bougé, pas le code.** Même famille de problème que le build `tablet-app` du lot A.
|
||
|
||
### Ce que ça change côté commercial
|
||
|
||
Le POC est **démontrable**. Face à un concurrent mono-usage type Musa Guide, une démo qui tourne pèse plus qu'une ligne « bientôt » sur la landing. À condition d'assumer le kit Android prêté, et de ne pas vendre l'add-on avant la bascule TTS.
|
||
|
||
---
|
||
|
||
## 6. Sous-projets MyInfoMate
|
||
|
||
- **MyInfoMate Sport** — pilote Hockey Namur. Vue d'ensemble : [sport/solution-overview.md](sport/solution-overview.md), spec : [sport/myinfomate-sport-spec.md](sport/myinfomate-sport-spec.md), pitch : [sport/pitch-commercial.md](sport/pitch-commercial.md)
|
||
- **MyInfoMate Crèche** — pilote childcare en cours. Vue d'ensemble : [creche/solution-overview.md](creche/solution-overview.md), spec : [creche/myinfomate-creche-spec.md](creche/myinfomate-creche-spec.md), pitch : [creche/pitch-commercial.md](creche/pitch-commercial.md)
|
||
|
||
---
|
||
|
||
## 6bis. Cohérence de la doc — audit du 2026-08-07
|
||
|
||
Passage en revue de tous les fichiers `DOCS/` (hors `sport/` et `creche/`). Corrigé directement :
|
||
|
||
| Fichier | Ce qui était faux |
|
||
|---|---|
|
||
| [roadmap.md](roadmap.md) | Annonçait **Claude API** comme LLM à 4 endroits — c'est **Gemini 2.5 Flash-Lite**. Quota IA en requêtes → tokens |
|
||
| [parity-manager-visitapp.md](parity-manager-visitapp.md) | L'intro du §6 disait « visitapp-web reste cassé » et B2 « tests backend ❌ » alors que les deux sont corrigés depuis le 2026-08-06 — le doc se contredisait lui-même |
|
||
| [v2/tts-pregenerated-plan.md](v2/tts-pregenerated-plan.md) | Chemin Firebase « à confirmer » et faux. Le vrai est `pictures/{instanceId}/{resourceId}`, sans extension. Le refactor uploads est partiellement devancé par `media-storage-plan.md` |
|
||
| [plan-import-ia-stats-subsides.md](plan-import-ia-stats-subsides.md) | « Aucune infra email n'existe » → **Resend est en place** (9 templates, domaine vérifié). Quota `AiRequestsPerMonth` → `AiTokensPerMonth` |
|
||
| [security/audit-securite-manager-service.md](security/audit-securite-manager-service.md) | `ImageHelper` : confirmé **code mort** qui planterait sur Linux. Double `SaveChangesAsync` : à revérifier après le fix `BuildAuditEntries` du 2026-08-06 |
|
||
| [solution-overview.md](solution-overview.md) | Grille tarifaire **Starter/Standard/Premium (69/99/199 €)** — structure abandonnée. Marquée obsolète |
|
||
|
||
### 🔴 Demande ton arbitrage — non corrigé volontairement
|
||
|
||
**[cgu-myinfomate.md](cgu-myinfomate.md) §5 décrit des plans qui n'existent plus** : Starter / Standard / Premium (69 / 99 / 199 € HTVA), quotas IA en requêtes. La réalité est Essentiel / Pro / Premium / Enterprise (39 / 99 / 179 €), quotas en tokens.
|
||
|
||
C'est un **document contractuel** — je ne l'ai pas réécrit. À reprendre en vérifiant d'abord ce que les clients existants ont effectivement signé.
|
||
|
||
### Ménage — décidé le 2026-08-07
|
||
|
||
- **`offre-commerciale.md` : ne pas supprimer.** Vérification faite, il ne contient pas qu'une grille : clients cibles, services complémentaires, forfait événementiel, frais de mise en place, projection de revenus. **Retirer les seules tables de prix** et garder le reste comme document de stratégie commerciale.
|
||
- Quatre documents portaient une grille tarifaire (roadmap, solution-overview, offre-commerciale, cgu) alors que la source de vérité est `myinfomate-landing` → **supprimer les tables, garder un lien**.
|
||
- ✅ **CGU §5 reprises le 2026-08-07** depuis la landing : Essentiel/Pro/Premium/Enterprise (39/99/179/devis), et un §5.1bis qui définit le quota IA **en tokens** en renvoyant au back-office pour les valeurs — plutôt que de figer un chiffre dans un contrat.
|
||
- ✅ **Landing corrigée** — la clé `features.reqPerMonth` n'existe plus dans `myinfomate-landing/src` (vérifié le 2026-08-11). Ce point était encore listé comme ouvert à deux endroits de ce fichier alors que le kanban le donnait fait : c'est le kanban qui avait raison.
|
||
- ⚠️ **Champs morts — partiellement traités.** `FactContent`, `IsStepLocked`, `IsHiddenInitially` sont bien supprimés (migration `RemoveDeadGuidedStepFlags`). **Restent en base** : `SectionEvent.ParcoursIds`, `SectionEvent.IconResourceId`, `SectionMap.MapResourceId` — ce dernier étant en plus mappé vers `iconResourceId` dans son DTO, ce qui ajoute une confusion de nommage.
|
||
- **Champs « morts » — décision révisée le 2026-08-07 après vérification du code.** Deux des trois ne sont pas morts :
|
||
- `SectionMap.MapResourceId` → **à renommer, pas à supprimer.** Il est branché de bout en bout (`SectionFactory:179/511`, `SectionMap.ToDTO:58`, `MigrationController:469`) et exposé au front sous le nom `iconResourceId` : la colonne porte l'icône de la carte, pas une « ressource carte ». Le renommer en `IconResourceId` supprime la confusion. Reste à décider si le web doit l'honorer (écart W10).
|
||
- `SectionEvent.ParcoursIds` → **à investiguer avant de trancher.** Intention produit documentée (depuis un SectionEvent mis en avant, retrouver les parcours liés aux jours d'un carnaval). `GuidedPath.SectionEventId` couvre le lien au niveau *événement* ; reste à vérifier qu'il couvre aussi le niveau *jour / bloc de programme*. Ne pas supprimer tant que ce n'est pas établi.
|
||
- `SectionEvent.IconResourceId` → sans usage visiteur identifié, seul candidat réel à la suppression.
|
||
- **`Section.meterZoneGPS` : à implémenter, pas à retirer.** Le rayon de déclenchement a une vraie raison d'être variable — une salle de musée demande ~10 m, un parcours extérieur comme le Fort en demande 50-100. La constante en dur à 100 m est fausse dans les deux cas.
|
||
|
||
---
|
||
|
||
## 7. Documents de référence (pas des todos — pas besoin de "traiter")
|
||
|
||
- [solution-overview.md](solution-overview.md) — présentation produit core
|
||
- [parity-manager-visitapp.md](parity-manager-visitapp.md) — audit de parité manager-app → apps visiteur (2026-08-05) : couverture par section, 17 écarts au niveau champ, état des builds, méthode pour refaire l'audit
|
||
- [guided-path-types.md](guided-path-types.md) — guide de référence SectionParcours
|
||
- [section-event-map-setup.md](section-event-map-setup.md) — guide setup SectionEvent/SectionMap
|
||
- [design-prompts.md](design-prompts.md) — prompts design pour Google Stitch/Claude Design
|
||
- [competitor-analysis.md](competitor-analysis.md) — comparatif concurrents
|
||
- [cgu-myinfomate.md](cgu-myinfomate.md) — CGU
|
||
- ⚠️ [offre-commerciale.md](offre-commerciale.md) — **obsolète**, source de vérité = myinfomate-landing (voir mémoire `project_pricing_plans`)
|
||
- ⚠️ [roadmap.md](roadmap.md) — contient aussi une table "Modules existants" + une table de prix en dehors de la section XR (section 5 ci-dessus) : cette table de prix est une **ancienne copie**, potentiellement désynchronisée de myinfomate-landing (source de vérité) — à vérifier avant de s'y fier
|
||
- `openwakeword/` — scripts/notebook d'entraînement de modèle wake word (code, pas de la doc à jour manuellement)
|
||
- `claude design/` — mockups HTML générés (MyInfoMate Parcours), à consulter visuellement, pas du texte à synchroniser
|
||
|
||
---
|
||
|
||
## Comment mettre à jour ce fichier
|
||
|
||
> 🗂️ **Le kanban en est le reflet.** Source de vérité du contenu : **[kanban.html](kanban.html)**, versionné à côté de ce fichier.
|
||
> Publié à l'adresse https://claude.ai/code/artifact/4c317904-2e23-45d3-ac14-dcca678ae5d3
|
||
>
|
||
> ⚠️ **L'artifact appartient à un autre compte Claude que celui de la machine de dev.** Constaté le 2026-08-10 : ni la lecture ni la republication n'y sont possibles depuis ici — même partagé en « anyone with the link », la réponse est *« reading public artifacts that way is not enabled yet »*. D'où le découpage :
|
||
>
|
||
> | | Qui | Quoi |
|
||
> |---|---|---|
|
||
> | Contenu | n'importe quelle session | éditer `DOCS/kanban.html` — cartes **et** compteurs (colonnes + bandeau du haut) |
|
||
> | Diffusion | le compte propriétaire | republier ce fichier sur l'artifact |
|
||
>
|
||
> Vérification avant publication : chaque compteur annoncé doit égaler le nombre réel de `<article class="card">` de sa colonne.
|
||
>
|
||
> Un hook `PostToolUse` (`.claude/hooks/status-kanban-reminder.py`) rappelle ce report à chaque écriture sur ce fichier. Il rappelle, il ne publie pas — une correction de formulation ne justifie pas de toucher au tableau.
|
||
|
||
Ce fichier est un index, pas une source de vérité duplicée :
|
||
- Nouvelle feature terminée/à faire → éditer **todo-features.md**, pas ici (sauf si ça change le résumé de la section 1)
|
||
- Nouveau point sécurité traité → cocher dans la section 3 ici **et** noter "Déjà corrigé" dans le fichier audit correspondant
|
||
- Nouveau chantier roadmap → ajouter une ligne dans la table de la section 5, créer le plan détaillé séparément si ça dépasse quelques lignes
|