MyInfoMate — tableau de chantiers

Vue d'état consolidée depuis DOCS/. Source de vérité : STATUS.md — cette page en est le reflet, pas le remplaçant. Filtrez sur V1 pour ne voir que ce qui reste avant la mise en prod. L'ordre d'exécution, lui, est dans v1-plan.md : ce tableau dit où en est chaque chantier, pas lequel bloque lequel.

Mise à jour · 2026-08-11
Postgres v3 · pas encore en prod
5Urgent
5Migration v3
7Bugs ouverts
6À tester
23Planifié
10Bascule prod
19Fait récemment
Domaine
Horizon

Urgent

5
tous les reposRevérifié 11/08

Travail non committé — largement résorbé

L'alerte du 09/08 (« rien depuis le 17/07 », 75 + 40 + 14 + 4 fichiers) est périmée : les quatre repos ont un commit du 09/08. Reste en working tree le travail du 10-11/08 — manager-service ~41 fichiers (lot 3 RAG, IngestionService, colonnes médias, endpoint knowledge), manager-app (socle visuel, écran Guide IA), myinfomate-landing 7, plus un commit non poussé sur visitapp-web.

Reste à committer, mais ce n'est plus trois semaines sans filet.

STATUS.md §4 — Infrastructure & Ops
manager-serviceSécurité

Rotation des secrets committés en clair

Clé de signature JWT, clés API Gemini et OpenWeather, connection strings prod, mot de passe MQTT, token Telegram. Le dossier RELEASE/ republie d'anciens secrets.

security/audit-securite-manager-service.md
infraSécurité

Port PostgreSQL exposé publiquement

À fermer via Traefik/Docker. Accès DBeaver par tunnel SSH uniquement.

todo-features.md — Sécurité réseau
tablet-appBuild

tablet-app ne compile pas

Échec Gradle sur flutter_tools/gradle/build.gradle.kts. Seul build encore cassé des cinq. Le dernier APK date d'avril.

parity-manager-visitapp.md §6 — B3
manager_api_newDette

Client généré désynchronisé

Des fichiers modèle orphelins déclarent part of sans être inclus. Mesuré le 09/08 : 68 erreurs, quasi toutes de là (+ lib/api/openApiTest.dart) — moins que les ~183 relevées en juillet, mais flutter analyze global reste inexploitable comme feu vert : filtrer sur le dossier travaillé. flutter build web passe malgré elles, ces fichiers n'étant pas dans le graphe de compilation.

security/audit-manager-app.md

Migration v3

5
manager-servicereprise

Backfill StoragePath/SizeBytes des 45 lignes

✅ Prérequis levé le 10/08 : Create écrit désormais StoragePath (pictures/{instanceId}/{resourceId}) et SizeBytes, types URL exclus, et manager-app envoie la taille avant l'upload. Le backfill ne sera donc pas à refaire au prochain upload. Reste le backfill lui-même, en deux moitiés : StoragePath est un simple UPDATE SQL (chemin déterministe), seul SizeBytes exige de lister le bucket. ⚠️ Mesuré le 09/08 sur 45 lignes : 37 ont un blob, 7 sont des types URL (Wikipedia, YouTube, agenda.php) sans aucun fichier — les inclure serait faux — et 1 est de type fichier sans URL. SizeBytes vaut 0 partout : le quota de stockage ne veut rien dire pour personne.

v2/media-storage-plan.md §5 — État mesuré · STATUS.md §1quater — Lot médias
manager-appGros gain

Compression images — 2560 px / JPEG q82

Aucune compression aujourd'hui : une photo 4K part entière. ~12× de gain sur le stockage et sur chaque consultation visiteur.

v2/media-storage-plan.md
manager-service

Quota de stockage réellement appliqué

Aujourd'hui contrôlé uniquement dans l'endpoint legacy. Pré-vol + contrôle autoritaire à Create avec la taille lue depuis Firebase + suppression du blob à Delete.

v2/media-storage-plan.md
manager-service

Flag watermark sur Instance

Remplace un if sur un id d'instance codé en dur. Doit remonter dans la config lue par manager-app, puisque le watermark s'applique côté client.

v2/media-storage-plan.md
GCP5 min

Alerte de budget

Bucket en Europe = aucun quota gratuit, facturation dès le premier octet. L'alerte est la vraie protection contre la surprise.

v2/media-storage-plan.md

Bugs ouverts

7
back + visitappBloquant produit

Visite hors ligne quasi non fonctionnelle

Le switch de collecte des ressources est commenté dans Export et côté visitapp. Une visite téléchargée n'embarque ni images d'articles ni audios.

v2/offline-visit-plan.md
visitappFraîcheur

Ressource modifiée jamais re-téléchargée

Le filtre incrémental teste la présence du fichier, pas sa version. Une image remplacée dans le CMS ne remonte jamais sur le device. Invisible pour le client.

v2/offline-visit-plan.md
visitapp

audio/mpeg absent de la table d'extensions

La table mappe audio/mp3, qui n'est pas un MIME standard. Les MP3 atterrissent probablement en .unknown. À vérifier sur device.

v2/offline-visit-plan.md
visitapp

Purge des fichiers obsolètes désactivée

La liste est calculée puis le deleteSync() est commenté. Le stockage occupé sur le téléphone du visiteur ne diminue jamais.

v2/offline-visit-plan.md
visitapp

Échecs de téléchargement silencieux

Un fichier raté fait un print et passe. La visite est annoncée téléchargée alors qu'elle est incomplète.

v2/offline-visit-plan.md
mobileM3

meterZoneGPS ignoré

Le rayon de déclenchement configuré par section n'a aucun effet — une constante en dur à 100 m le remplace.

parity-manager-visitapp.md §2
webW1

Fournisseur de carte ignoré en web

Un client qui choisit « Google Hybrid » voit de l'OSM : LeafletMap.tsx a un TileLayer en dur.

parity-manager-visitapp.md §3

À tester

6
manager-appJamais lancé

Écran Statistiques refondu + export PDF

Écrit et analysé, jamais ouvert dans un navigateur. À vérifier sur données réelles : la règle mono-canal, le rendu des titres longs dans les barres. Sur le PDF : les accents, et le logo — s'il manque, c'est le CORS du bucket Firebase, le rapport doit se générer quand même. Et après database update : que les instances soient bien passées à 395 jours (pas seulement les plans), et que le job visit-events-purge déclenché à la main ne supprime rien.

test-plan.md §8bis, §8ter, §8quater
§21En premier

Visite hors ligne sur device

15 cas : images d'articles, audios, extensions de fichiers, fraîcheur, dégradation des types non offline. Conditionne l'urgence de tous les bugs hors ligne.

test-plan.md §21
§18Jamais exécuté

Onboarding self-service complet

Inscription, création d'instance, essai 14 j, Stripe, 9 emails Resend, job de cycle d'essai. Code écrit et compilé, aucun parcours joué. Reste l'alias onboarding@ et le whsec_ Stripe CLI.

test-plan.md §18
§19.13

Chasse au trésor / carnaval — cas E

Le scénario le plus complet : SectionMap + SectionParcours + géodéclenchement + questions + mode jeu, d'un seul tenant.

test-plan.md §19.13
§19

Parité par type de section

« Tout ce que je configure est-il visible ? » — le chapitre qui valide les 13 types sur les deux apps visiteur.

test-plan.md §19
§0

Porte d'entrée build

5 commandes, bloquant absolu. flutter analyze et next dev ne prouvent rien — seuls les vrais builds disent la vérité.

test-plan.md §0

Planifié

23
après prodrelationnel

Reprendre contact avec Louise Smets — Musée d'Ixelles

Une fois la prod stabilisée, pas avant : on n'ouvre pas une conversation en montrant un produit en cours de bascule. Premier temps 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 ensuite, pas dans le premier message. ⚠️ Rien n'est su de son rôle exact à ce stade : à ne pas supposer.

STATUS.md §1quinquies — Après la bascule
priorité 1V2

Rapport de stats — envoi mensuel automatique

Le rapport à la demande est livré (07/08, généré côté client). Reste l'envoi automatique, seule partie qui exige vraiment le backend : personne n'a son navigateur ouvert le 1er à 6 h. QuestPDF + Hangfire, config destinataires/fréquence, et une méthode d'envoi avec pièce jointe — les 10 méthodes d'IEmailService sont des templates figés sans attachement. ⚠️ QuestPDF exige libfontconfig1 + une police, absents du Dockerfile : ça marche en local sous Windows et casse dans le conteneur.

plan-import-ia-stats-subsides.md §1
manager-serviceDevenu pressant 09/08

GetSummary agrège en mémoire

eventsQuery.ToList() charge tous les événements de la période avant d'agréger. Tenable tant que la fenêtre était de 30 jours ; elle vient de passer à 13 mois. À passer en SQL avant de vendre le rapport annuel à un gros site — sinon c'est la mémoire du conteneur qui arbitre.

todo-features.md — Plans & Quotas
manager-serviceDette · découvert 09/08

Aucun test ne couvrira le RAG

Ajouter ContentEmbedding a cassé 116 des 124 tests d'un coup : la suite tourne sur EF InMemory, qui ignore le type Vector et refuse de valider le modèle. Contourné en excluant l'entité hors Npgsql — les tests repassent, mais vector store, recherche cosinus, index HNSW et contrainte anti-doublon Hangfire restent structurellement non testables. Piste : Testcontainers.PostgreSql sur l'image Dockerfile.postgres.

STATUS.md §1quater — Dette ouverte
priorité 2Exécuté 10/08 — 2150 morceaux

RAG sur le contenu CMS — vector store, jobs, outil SearchKnowledge

Persona simple : réponses sourcées sur le contenu existant. Pas d'ingestion documentaire, pas d'OCR, pas de TTS — c'est ce qui le rend tenable en V1.

Livré le 10/08 : IngestionService (chargement des collections filles par sous-type, un jeu de morceaux par langue), serveur Hangfire dédié à 2 workers, garde AiTokensPerMonth > 0plan-starter est à 0 — et SearchKnowledge ajouté aux outils d'AssistantService plutôt qu'un /ask concurrent, avec sa règle dans les 4 prompts.

Joué contre la vraie base et la vraie API le 10/08 : 52 sections → 2150 morceaux en 53 s. Deux défauts que seule l'exécution pouvait montrer, corrigés dans la foulée. 1) Les gabarits de LanguageInit étaient indexés : les cinq premiers résultats d'une question en néerlandais étaient des NL - Title NL - Description, le bonus de langue suffisant à les faire passer devant du vrai contenu français — le cas même que le cross-lingue devait servir. 2) Le même paragraphe occupait trois des cinq résultats, au même score : DistinctBy(Text) avant le Take. ⚠️ Le post-filtrage HNSW reste non éprouvé à plusieurs instances — il n'y en a qu'une en base locale, soit le cas où le problème ne se voit pas.

Les deux angles morts du découpage sont fermés : HTML retiré avant l'embedding, et ligne trop longue recoupée à la fin de phrase — sans quoi un article dépassait l'entrée max de gemini-embedding-001 et l'échec emportait les 50 morceaux du lot.

Déclenchement unifié : l'Enqueue par contrôleur et un intercepteur EF ont coexisté une demi-journée. Retenu : SectionIndexingInterceptor seul — les 5 sous-contrôleurs totalisaient 30 SaveChanges et 0 Enqueue, donc ajouter 40 points d'intérêt à une carte ne réindexait rien.

STATUS.md §1quater — lot 3 · v2/rag-indexing-trigger-decision.md · v2/rag-pgvector-integration-plan.md §3, §4
manager-appFront fait · backend à faire

RGPD & conformité — lot J, dernier avant la bascule

Reporté en fin de backlog le 11/08. Tenable parce que la porte « rien en prod avant la fin du backlog » tient : d'ici là la journalisation ne tourne 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.

Déjà fait le 11/08 : CGU §8 réécrites (elles décrivaient un service qui ne collectait que des statistiques anonymes, citaient des durées de plans abandonnés, et ne mentionnaient aucun sous-traitant alors que la question du visiteur part chez Google), texte d'information visiteurs en FR/NL/EN, et purge à 90 jours active sans condition de configuration — une durée écrite dans un contrat n'est pas un réglage.

Reste : la table d'agrégats de thèmes (les CGU promettent que les regroupements survivent à la purge, or ThemeId est une colonne de la ligne supprimée — au 91e jour le client perdrait tout), le job de regroupement, un interrupteur de collecte par instance (⚠️ le client est responsable de traitement mais ne peut pas refuser la collecte), la mention à afficher côté visiteur, la relecture juridique du §8 et la vérification des conditions réelles de Google.

v1-plan.md — lot J · cgu-myinfomate.md §8 · mention-information-visiteurs.md
SectionParcoursPerte de travail client

Parcours — sauvegarde au fil de l'eau

Chaque popup travaille sur une copie JSON profonde et ne remonte qu'à la fermeture : fermer la fenêtre du parcours par la croix jette les questions saisies dessous, sans un mot. C'est le seul des trois défauts de cet écran qui fait perdre du travail au client aujourd'hui.

Indépendant de l'arbitrage A/B — à faire sans attendre la refonte de la forme. La maquette prévoit déjà son témoin : « Enregistré · il y a 3 s » en pied de fenêtre.

todo-features.md — SectionParcours · v1-plan.md — DB2
manager-appArbitrage en attente

Parcours — sortir de l'empilement de popups

Cinq surfaces empilées pour poser une question sur une étape et l'écrire en NL. Option A recommandée (fenêtre unique, rail d'étapes toujours visible, profondeur max 2) : le seul gain réel de l'option B était une colonne d'aperçu visiteur, déjà couverte par l'aperçu de conversation du Guide IA. Maquette rapatriée dans claude design/sectionparcours-refonte-flux.html.

Extraire d'abord ParcoursFields / EtapeFields / QuestionFields en widgets autonomes : sert A et B à l'identique.

todo-features.md — SectionParcours · v1-plan.md — DB4, lien L18
manager-appJamais ouvert

Écran Guide IA — vérification à l'œil

La coquille à onglets, l'aperçu de conversation et la carte de connaissance compilent (flutter build web ✅) mais n'ont jamais été affichés dans un navigateur — le défaut récurrent de ce tableau. Exige un manager-service qui tourne et une session ouverte : flutter run -d chrome, puis comparaison côte à côte avec claude design/guide-ia-screen.html.

v1-plan.md — DB5
manager-serviceTranché 11/08

Nettoyage des champs backend

SectionMap.MapResourceId → à renommer en IconResourceId, pas à supprimer : il est branché de bout en bout sous un nom trompeur. Le renommage est l'écart (g) de la migration — le faire ici le fait disparaître là-bas.

SectionEvent.ParcoursIdssupprimer. Vérifié le 11/08 : lu ni écrit nulle part, commentaire (// Liens vers GeoPoints) contredisant son nom, et le lien événement↔parcours existe déjà en sens inverse via GuidedPath.SectionEventId. Décisif : il est sur SectionEvent et non sur ProgrammeBlock, donc incapable du cas « parcours de tel jour » pour lequel on le gardait.

QuestionTypene pas toucher au schéma : le trio TextLibre/Digicode/ExpectedAnswer n'existe pas dans le code, et le digicode est un comportement dérivé. Reste une propreté front. Sort du lot « geler le schéma ».

STATUS.md §6bis et §1sexies · v1-plan.md — lot B
priorité 2V2

Ingestion documentaire + OCR

PdfPig avec cascade OCR Gemini (~0,03 $ / 100 pages). Word, PowerPoint, images. Legacy .doc/.ppt refusés à l'upload.

v2/rag-pgvector-integration-plan.md
priorité 3V2

TTS pré-généré — audioguide multilingue

Le produit que les musées achètent depuis toujours, à coût marginal quasi nul. Aucun concurrent de cette gamme ne le fait.

v2/tts-pregenerated-plan.md
priorité 4V2

Génération de visites à la demande

« Un parcours de 45 min pour familles ». Marche sur le smartphone du visiteur, aucun hardware. Vendable aux villes comme balade sonore IA.

plan-import-ia-stats-subsides.md §3
guide IAV2

Guides multiples & guide thématique

Plusieurs personas, assignables à une section ou une configuration. Le modèle part en V1 (liste, pas champ unique) ; le comportement attend — qui répond depuis l'accueil, et sur quel périmètre de contenu, est une vraie question de conception.

v2/tts-pregenerated-plan.md — PersonaConfig
visitapp-webBloque l'offre Essentiel

Déployer le visiteur web — app.myinfomate.be

L'app existe : build ✅, 13 types de section rendus, assistant IA inclus depuis le 06/08. Il ne manque que le Dockerfile et l'entrée Traefik dans le compose. Le plan Essentiel vendu en self-service est web-only — sans ce déploiement, l'onboarding vend un produit inaccessible.

architecture-web-saas.md — À faire
tablet-appV2

Kiosk web (Flutter Web)

Extension web de tablet-app pour les clients ne voulant pas d'installation native. Reporté en V2 le 07/08 : aucun client demandeur, et la cible native ne compile même plus — il faudrait réparer ce build avant d'en dériver une version web.

architecture-web-saas.md — Kiosk web
produitV2

SectionForm — formulaires personnalisés

Nouveau type de section + table de réponses anonymes, constructeur de formulaire et page de résultats agrégés. Reporté en V2 le 07/08 : purement additif, aucun impact sur le schéma existant — rien n'oblige à le faire passer avec la migration.

todo-features.md — SectionForm
produitV2

Ressource 360° — images panoramiques

Une valeur d'enum (Panorama360) et un branchement dans l'affichage des ressources : s'affiche partout où une ressource s'affiche, sans toucher aux sections. Reporté en V2 le 07/08 — le plus petit des trois, à reprendre en premier.

todo-features.md — Ressource 360°
produitV2

AR image tracking (Mind AR)

Modèle ArAnchor, microservice Node de compilation des .mind, et remplacement du scanner visiteur par une WebView unifiée QR + image. Le plus lourd des trois, fort effet démo. Reporté en V2 le 07/08 — ni la migration ni la mise en prod n'en dépendent.

todo-features.md — AR
produit

Écran d'audit log & gestion des users

Backend fait, front absent. Users : scoping backend fait, manque le plafond de 5 et le compteur dans l'UI.

todo-features.md
SectionParcoursPetit · arbitré 09/08

PDF en média d'étape

Retenu à la place de faire de SectionParcours un conteneur de sections — idée écartée le 09/08 : la moitié des types (Agenda, Météo, Event) n'a pas de sens dans un point de parcours, et le lien vers une section existante existe déjà là où il est cohérent (baseSectionMapId). Le vrai manque tient à un type de ressource : une étape accepte déjà image, vidéo et audio, PDF est le seul absent. Trois points : liste de types paramétrable côté étape, cas PDF dans getElementForResource, cas PDF dans ResourceViewer.tsx. Sans les deux derniers, le PDF s'afficherait en image cassée — le bug corrigé pour la vidéo le 09/08.

todo-features.md — Parcours guidés
polishV2

Talking head — avatar animé

Trois frames par persona, lipsync sur les timestamps Google TTS. Quelques jours, effet démo. Bouche-trou entre deux chantiers.

v2/talking-head-plan.md
XRV2 · subventionné

Ray-Ban Meta & Meta Quest

Pilote presse uniquement, quand le SDK sort de preview. 90 % de l'expérience est livrable en audio smartphone — profil d'appel à projets, pas de ligne produit.

roadmap.md — section XR

Bascule prod

10
MigrationControllerSilencieux

Instance : 4 champs migrés sur ~35

Le plus dangereux, parce qu'il ne produit aucune erreur : SubscriptionPlanId null → quotas à 0, PublicApiKey null → les apps visiteur ne s'authentifient plus, WebSlug null → visitapp-web injoignable, IsMobile/IsTablet/IsWeb/IsAssistant à false. La migration se déclare réussie et le lieu est inaccessible.

MigrationController.cs:106-112 · STATUS.md §1quinquies c
MigrationControllerPerte de données

11 types de section sur 13

BuildSection a été écrit avant SectionEvent et SectionParcours : les deux tombent dans le default, une ligne dans le rapport d'erreurs et la section n'est pas migrée. Ce sont précisément les types du scénario carnaval.

MigrationController.cs:458-613 · §1quinquies a
MigrationControllerPerte de données

Collections filles jamais remplies

QuizQuestions = new(), EventAgendas = new(), aucun GuidedPath/GuidedStep : un quiz arrive sans ses questions, un agenda sans ses événements. Et Role est forcé à ContentEditorplus aucun administrateur après la bascule.

MigrationController.cs:225, :526, :591 · §1quinquies b, d
MigrationControllerRobustesse

Aucune transaction globale

Neuf étapes, un SaveChangesAsync chacune, et un catch qui renvoie 200 OK avec l'erreur dans le corps. Un échec au milieu laisse la base à moitié remplie ; la reprise ne tient qu'aux tests d'idempotence — qui existent et sont corrects. Idem : StoragePath et les colonnes IA ne sont pas renseignés, et SizeBytes sort d'un HEAD dont l'échec est avalé.

MigrationController.cs:68-86, :172-181 · §1quinquies e, h
étape 15Sans risque

Dry run + comparaison des comptages

dryRun=true n'écrit rien. Rejouer sur l'export Mongo déjà présent dans manager-service/migration-data/ et comparer entité par entité aux comptages Mongo. C'est ce qui révèle les quatre cartes ci-dessus sur des données réelles plutôt que par lecture de code.

STATUS.md §1quinquies — étape 15
étape 16-17Après rotation des secrets

Créer l'environnement prod Postgres

Deployment/Dockerfile.postgres (digest épinglé), compose prod, Traefik, port 5432 fermé. Puis dotnet ef database update sur la base vide et vérifier que postgis et vector répondent. Ne pas créer la prod avec les clés encore committées.

STATUS.md §1quinquies — étapes 16-17
étape 18Filet du jour J

pg_dump avant / après + cron quotidien

Noté comme « à faire » depuis juillet et jamais fait. Ici ça cesse d'être une bonne pratique : c'est la seule chose qui permette de recommencer si la bascule tourne mal.

STATUS.md §4 · §1quinquies — étape 18
étape 19

Rejouer pour de vrai, instance par instance

Le paramètre instanceId existe précisément pour ça. Commencer par la plus petite instance : si quelque chose casse, ça casse sur le plus petit périmètre possible.

STATUS.md §1quinquies — étape 19
étape 20Le vrai test

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 défauts « silencieux » ne se voient qu'ici — pas dans le rapport de migration.

STATUS.md §1quinquies — étape 20
étape 21-22

DNS, Mongo en lecture seule, retour arrière

Bascule DNS/API, Mongo conservé en lecture seule quelques jours, et le plan de rollback écrit avant le jour J — un retour arrière qu'on improvise à 23 h n'est pas un retour arrière.

STATUS.md §1quinquies — étapes 21-22

Fait récemment

Seize chantiers clos entre le 5 et le 10 août 2026.

Les maquettes sortent du compte Claude et entrent dans le repo Cause diagnostiquée d'un défaut qui se répétait : les trois maquettes vivaient dans des artifacts d'un autre compte, illisibles depuis la machine de dev — chaque session redessinait. D'où un écran Guide IA « livré » à la moitié de sa spec et des stats d'assistant à zéro. Le HTML est rapatrié dans claude design/, JS interactif compris. Même découpage que ce tableau : contenu dans le repo, diffusion par artifact. Constat en ouvrant les fichiers : Guide IA et Statistiques déclarent des tokens rigoureusement identiques — c'était déjà un design system, jamais porté en Dart — et --brand #264863 est kPrimaryColor.
Socle visuel — 11 tailles de police deviennent 11 rôles Mesuré dans lib/Components/ : onze tailles différentes (9 à 25 px), aucune échelle, constants.dart portant encore // TO FILL WITH CORRECT COLOR et deux styles. L'échelle, les espacements et les rayons sont extraits du CSS des maquettes ; les couleurs restent celles de l'app — un écran neuf se fond dans manager-app, il n'y ouvre pas une seconde palette. Seules les valeurs sans équivalent existant sont reprises : remplissages discrets, bordures, gris atténué, ambre. Pas de thème sombre. La reprise des écrans existants reste son propre chantier, pas un effet de bord.
Écran Guide IA — la coquille à onglets, et ce que le guide sait vraiment L'écran passe de cinq cartes en colonne à la structure de la maquette : deux onglets, deux colonnes. L'aperçu de conversation interroge le vrai /api/AI/chat, pas une simulation — au passage, AIApi était dans le client généré mais exposé nulle part dans client.dart, où les 18 autres façades sont. Câblé. Nouveau GET /api/Ai/knowledge/{id} pour la carte « Ce que connaît votre guide » : tout est agrégé sur ContentEmbedding, donc sur ce qui est réellement indexé — compter les sections publiées donnerait un chiffre plus flatteur et faux, une section désactivée étant purgée de l'index. Le « points d'intérêt » de la maquette devient « morceaux de contenu » : les points d'une carte sont indexés dans le texte de leur SectionMap, les compter à part répondrait à une autre question. 34 clés i18n FR/EN/NL, dotnet test 130/130, flutter build web ✅.
Le changement de plan donne enfin ce qu'il vend Trois défauts qui se tenaient la main. 1) Updateinstance changeait SubscriptionPlanId mais ne recopiait jamais les quotas du nouveau plan — CreateInstance le faisait, pas lui : passer un client de Starter à Premium ne lui donnait ni stockage ni jetons IA. 2) CheckQuota ne bloquait que si quota > 0 : à 0 jeton, ni blocage ni compteur — l'ambiguïté vient du code lui-même, où 0 veut dire illimité pour le stockage et pas d'IA pour les jetons. Une instance Starter avec IsAssistant à true consommait donc gratuitement et sans compteur, et toute instance migrée depuis Mongo arrive à 0 (écart c du §1quinquies). Corrigé en 403 avant tout appel au modèle. 3) BackfillInstanceAsync n'était appelé par personne : branché automatiquement quand AiTokensPerMonth passe de 0 à une valeur, plus un bouton de relance réservé au SuperAdmin dans l'écran Guide IA — outil de réparation, pas fonctionnalité : exposé au client il serait cliqué à chaque réponse décevante, pour un coût d'embedding complet et sans rien améliorer. IBackgroundJobClient injecté au lieu de la façade statique, qui lève sans JobStorage et cassait les tests. 130/130.
La dimension 768 n'est plus un pari IEmbeddingService + GoogleEmbeddingService (batch par 50), et surtout vérifiés contre l'API réelle. Trois choses qu'aucun plan ne disait : le modèle sort en 3072 dimensions par défautdimensions: 768 est obligatoire ; les vecteurs réduits ne sont pas normalisés (norme mesurée 0,578, pas 1) et sans normalisation côté client le classement cosinus serait faussé sans lever d'erreur ; et Google ne renvoie pas le champ index du contrat OpenAI. Recherche cross-lingue jouée en base : question NL → contenu NL 0,8145, FR pertinent 0,7982, FR hors-sujet 0,4818. L'écart NL/FR n'étant que de 0,016, le bonus de score par langue est bien nécessaire.
L'historique des stats passe à 13 mois pour tous L'axe « durée » disparaît du découpage commercial : HasAdvancedStats devient le seul différenciateur stats, et l'export PDF est inclus dès qu'il y a les stats. Motif : un dossier de subside est annuel — plafonné à 30 jours le rapport n'était envoyable à personne. Et la landing ne promettait aucune durée (la chaîne « 30 jours » existe dans translations.ts mais n'est affichée nulle part). Migration UnifyStatsRetentionTo13Months — elle met à jour les plans et reprend les instances en SQL, sans quoi les clients actuels seraient restés à 30 jours : c'est Instance.StatsHistoryDays que lit le contrôleur. dotnet test 124/124. ⛔ La purge associée est codée mais inactive jusqu'à la mise en place du pg_dump.
Configuration des parcours — 4 correctifs Relecture du 09/08 de l'écran refondu le 06/08. Le plus sérieux : un parcours créé sans toucher à la question de progression affichait « Dans l'ordre » et enregistrait « Libre ». Les 12 tests de progression_mode ne pouvaient pas le voir — ils couvrent la relecture des booléens, pas le DTO fabriqué par la popup. Aussi : vidéos et audios d'étape rendus en dur comme des images dans les deux apps visiteur, « Quelle ambiance ? » restée en case à cocher, menu « Carte de base » disparaissant sans un mot. À valider — test-plan §19.13 cas 0.
Le schéma du RAG est en base Image postgis + pgvector (0.8.6, base épinglée par digest), table ContentEmbedding avec index HNSW, 7 colonnes IA/stockage sur Resource, table VisitorQuestion. 62 migrations, dotnet test 124/124, données intactes. Deux surprises : la base locale avait une migration de retard — les colonnes Guide* du lot 1 n'existaient pas, toute requête sur Instances aurait planté ; et un warning de collation (glibc 2.36→2.31) qui désalignait les index texte, corrigé par REINDEX puis REFRESH COLLATION VERSION — dans cet ordre. D'où l'épinglage par digest.
Le guide lit enfin sa configuration L'écran Guide IA ne pilote plus dans le vide. En ouvrant le fichier : quatre blocs de prompt et non deux — le relevé s'était arrêté au scope configuration. Les quatre lisent le nom, la personnalité et les messages de repli du client ; le ton imposé (« tu es chaleureux ») est retiré, la politique de repli divergente de la ligne 550 réconciliée. Deux prompts sur quatre n'avaient aucune règle hors-sujet — sur web et mobile, un visiteur pouvait demander une recette de tarte. Le tirage du repli est fait côté serveur pour que la phrase du client soit reprise mot pour mot. dotnet test 124/124, jamais vu tourner.
Écran Statistiques refondu, export PDF compris Les 7 défauts corrigés, aucun changement backend : barres horizontales monochromes, filtres sur une seule ligne avec le volume de chaque canal, règle mono-canal, 4 KPI portant chacun leur variation, bandeau « à retenir », courbe en aire. La période précédente s'obtient en rappelant le même endpoint. Le rapport PDF se génère dans le navigateur (paquet pdf Dart déjà présent) et partage les valeurs calculées de l'écran — un chiffre ne peut pas diverger entre l'écran et le document envoyé à la commune. Le test de génération a attrapé deux plantages qui seraient sortis au premier clic. Au passage : VisitEvent porte bien l'AppType, le canal vocal est branché ; et deux puces du sommaire promettaient des données inexistantes (parcours terminés, questions au guide IA), retirées.
Les builds sont débloqués 3 apps Flutter + visitapp-web + dotnet test 124/124. Un seul fichier sans // @dart=2.18 cassait les trois apps Flutter d'un coup.
Bug backend révélé par les tests BuildAuditEntries() modifiait le ChangeTracker pendant son énumération — exception sur toute écriture d'entité auditée. Invisible tant que les tests ne compilaient plus.
Assistant IA en web + alignement mobile Débloque l'add-on IA sur le plan Essentiel, qui est web-only. Suggestions dérivées du contenu réel, zéro configuration client.
Refonte de la progression des parcours 9 booléens sur 3 niveaux remplacés par 3 questions. IsStepLocked supprimé : il rendait une étape définitivement infranchissable même après réussite.
Centrage de carte, horaires, icônes M1, M2 et W2 corrigés — des champs que le client saisissait sans qu'ils aient le moindre effet.
Audit de cohérence de la doc Le LLM annoncé était Claude à 4 endroits (c'est Gemini), l'infra email « inexistante » (Resend est en place), et deux docs se contredisaient sur l'état des builds.
CGU reprises et landing corrigée Les CGU décrivaient des plans abandonnés (Starter/Standard/Premium 69/99/199 €). Alignées sur la landing, avec un quota IA défini en tokens renvoyant au back-office plutôt qu'un chiffre figé dans un contrat. Landing : « req/mois » → « questions de visiteurs / mois » en 4 langues.
Écran Guide IA — onglet Configuration livré Migration additive (4 colonnes nullable), champs sur Instance, client généré étendu à la main, écran avec 35 clés i18n FR/EN/NL. dotnet build et flutter build web passent. Menu conditionné à isAssistant, le même drapeau que la garde d'AiController.
Trois plans écrits, deux écrans maquettés Médias & stockage, visite hors ligne, écran Guide IA, écran Statistiques. Le découpage V1/V2 est tranché : le RAG sur contenu CMS part en V1, l'ingestion documentaire en V2. Les deux écrans maquettés sont désormais implémentés.