Compare commits
7 Commits
32c871ad52
...
2944fb5a05
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2944fb5a05 | ||
|
|
30c2f6c3e2 | ||
|
|
40323b1440 | ||
|
|
d36394cabd | ||
|
|
4206fde7f0 | ||
|
|
2824ca63c1 | ||
|
|
c525a936c8 |
31
STATUS.md
31
STATUS.md
@ -14,6 +14,7 @@ Résumé de la table de suivi de ce fichier :
|
||||
- **Fait** : SectionEvent, Escape/Puzzle games, GuidedPath + progression carte, push, stats tracking, sync agenda/météo, traduction IA, quotas (IA/stockage/stats) par plan, audit log backend fiabilisé, refacto SectionParcours/GuidedPath/SectionGame (backend, vérifié 2026-07-15).
|
||||
- **Partiel** : Beacons/geofencing (manager-app UI ⚠️), AI Assistant (manager-app ⚠️), ~~**mode offline**~~ ✅ **corrigé le 2026-08-11 (D1) puis complété le 13/08 (D5)** — le pipeline de collecte n'est plus commenté, une visite embarque désormais contenus, audios et icônes, et une visite incomplète ne s'annonce plus téléchargée. **Reste à confirmer sur device** (§21 / D0), c'est le seul point encore ouvert du volet offline. Repasse UI globale, refacto SectionParcours côté manager-app/visitapp-web/mymuseum-visitapp (UI présente mais sous-comportements non revérifiés en détail — voir section "Refactoring majeur" de todo-features.md).
|
||||
- **🧪 Implémenté mais jamais testé (2026-08-05)** : **onboarding self-service complet** — inscription sur myinfomate-landing (`/[lang]/signup`), création auto de l'instance + du premier user, essai 14j, Stripe (customer/Checkout/webhook/Tax), 9 emails Resend (domaine vérifié, envoi réel validé), job Hangfire du cycle d'essai, watermark « Aperçu » visitapp-web, écran Abonnement manager-app, mot de passe oublié + invitation user, plafond IA d'essai. Les 4 repos compilent, migrations appliquées en base locale, **aucun parcours exécuté** → checklist de test dans `todo-features.md` (section "🧪 Checklist de test end-to-end"). Reste à faire côté config : alias mail `onboarding@myinfomate.be`, et `whsec_` de la Stripe CLI pour tester le webhook en local.
|
||||
- **🧪 Codé et committé le 2026-09-10 (rien de poussé), pas encore testé** : scan d'id brut (QR du Fort) depuis l'accueil, drapeau « afficher le scan QR » et **nom de l'application** par app (Mobile/Web), liens stores (SuperAdmin), QR du manager vers la nouvelle page **`app.myinfomate.be/download/…`**, suggestions de proximité beacon + GPS avec **notification écran verrouillé** (mobile) et suggestion GPS dans la page (web). Tests : [test-plan.md §23](test-plan.md) ; restes hors code (redirection `web.mymuseum.be`, déclarations stores) : carte kanban « QR codes, nom d'application… ».
|
||||
- **À faire** : dashboard super-admin V2, facturation V2. ✅ **Le Stripe Customer Portal est complet le 2026-08-13** (backend + bouton manager-app).
|
||||
- ⚠️ **Cette ligne était périmée sur six points, revérifiés dans le code le 2026-08-13.** Sont **faits** : écran audit log manager-app (front livré le 12/08), **rate limiting API Keys** (livré, pas seulement décidé — politique `ai` dans `Startup.cs:279`, `UseRateLimiter()` en `:344`, les deux endpoints décorés), gestion des users par instance (plafond serveur + compteur UI, 12/08), nettoyage `SectionEvent.ParcoursIds` (11/08), et le **backend** du Customer Portal (13/08). L'**unification `QuestionType`** n'est pas « à faire » mais **écartée** : le trio TextLibre/Digicode/ExpectedAnswer n'a jamais existé dans le code, l'enum a trois valeurs et le digicode est un comportement dérivé, pas un type — reste une propreté front, elle aussi livrée (alias nommés, 11/08).
|
||||
- **📦 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.
|
||||
@ -131,10 +132,19 @@ Ordre conseillé :
|
||||
- [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
|
||||
- [ ] **pg_dump quotidien** pour `manager-service` (Postgres `my_info_mate`), en complément du snapshot OVH. ⚠️ **Bloquant au moment de la bascule** : 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
|
||||
- 🔨 **Scripts livrés et testés le 2026-09-07** — `manager-service/ManagerService/Deployment/backup/`, installation et restauration documentées dans `README.md` et **`RESTORE.md`**. Cycle complet validé sur la base de dev : dump → vérification de relecture → restauration dans une base jetable → **25 tables aux comptages identiques**. Extraction par instance validée aussi (les deux extractions totalisent exactement le global). Embeddings exclus : dump de 12 Mo → **1,7 Mo**
|
||||
- ✅ **Destination créée le 2026-09-07** — projet GCP `myinfomate-backups` (séparé de `mymuseum-3b97f`), bucket `gs://unov-myinfomate-backups` en `EU` multi-région, versioning activé, accès public interdit, lifecycle 400 jours sous `pg/` seulement. Service account `backup-writer` en `objectCreator` + `objectViewer` : **incapable de supprimer, et vérifié** (l'écriture passe, le `rm` renvoie `403 storage.objects.delete denied`). Détail et test rejouable dans `Deployment/backup/README.md`
|
||||
- ⛔ **Ce n'est pas encore une sauvegarde** : `rclone` n'est ni installé ni configuré sur le VPS, donc rien ne part encore hors site. Et `notify.sh` n'est branché sur aucun canal — carte kanban « Brancher `notify.sh` sur un canal réel »
|
||||
- 📌 **Volume des médias : 1,43 Go** (`gcloud storage du -s gs://mymuseum-3b97f.appspot.com`, 2026-09-07). Le bucket connaissait la réponse que le plan attendait du backfill. Assez petit pour tenir dans le même bucket de sauvegarde sous `media/` — **pas besoin d'OVH Object Storage**, la disposition croisée envisagée le 07/09 est abandonnée pour cette raison
|
||||
- ✅ **Versioning activé sur le bucket des médias le 2026-09-07** — `versioning_enabled: True` sur `gs://mymuseum-3b97f.appspot.com`, plus une lifecycle rule qui purge les versions périmées à 30 jours (le bucket n'avait **aucune** règle auparavant, rien n'a été écrasé). L'ordre importait : le filet est en place **avant** que `Firebase:StorageBucket` soit renseignée (carte « Poser `Firebase:StorageBucket` »), jour où la suppression de blob devient réelle. Le bucket portait déjà une soft delete de 7 jours depuis mars 2024
|
||||
- 📌 **Décision de conception** : **un dump global, pas un par client.** Une restauration par client se fait par extraction depuis le dump global (`extract-instance.sh`) — 14 tables portent `InstanceId`, 7 tables filles se rattachent par leur parent. Des sauvegardes par client multiplieraient par N les jobs à planifier, les échecs à surveiller et les restaurations à tester, pour la même capacité de récupération
|
||||
- ✅ **Médias copiés le 2026-09-07** — 1,43 Go, 2407 objets, `gcloud storage rsync` serveur à serveur vers `gs://unov-myinfomate-backups/media/`. **Taille identique à l'octet**, zéro erreur. C'est la première sauvegarde des médias depuis le début du projet. ⛔ **Pas encore planifiée** : c'est une copie unique. **Storage Transfer Service envisagé puis écarté le 2026-09-07** — 5 attributions IAM et un agent de service avec droit de suppression, pour automatiser une commande à relancer toutes les quelques semaines sur des données qui bougent de ~2 ressources par mois : disproportionné. Le mécanisme reste le `rsync`, qui tourne avec un compte humain sans aucun changement de permission, et sera ajouté aux timers du VPS avec la sauvegarde Postgres
|
||||
- 🔍 **L'inventaire des orphelins est à moitié fait, gratuitement** — 27 objets sur 2408 n'appartiennent à aucun client : 19 sous un id à un caractère de celui de MyInfoMate (`…f1` vs `…f2`), 2 sous MNAHA (qui n'existe qu'en base de dev), et 6 fichiers de test à la racine du bucket (`giphy.gif`, une vidéo Cybertruck, des `file_example_*`) qui ne suivent pas le schéma `pictures/{instanceId}/{id}` et sont donc invisibles au quota. Détail et réserves dans [v2/media-storage-plan.md](v2/media-storage-plan.md)
|
||||
- ⛔ **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 réellement hors site
|
||||
- [ ] **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] 🔨 **Sauvegarde de la prod Mongo — faite le 2026-09-07.** `Deployment/backup/dump-mongo-prod.sh`, lecture seule (aucun `mongorestore` dans le script, volontairement). L'export de référence datait du 1er avril. Delta sur 5 mois : 11 sections créées, 3 supprimées, 17 modifiées, 9 ressources créées. **VisitNamur crée du contenu, MDLF retouche l'existant, Fort Saint Héribert n'a rien touché** — utile à savoir avant de reprendre contact avec eux. ⚠️ Piège : le formatage des dates change entre versions de `mongoexport` (`.420Z` → `.42Z`) et fait apparaître 239 fausses modifications ; normaliser avant de comparer
|
||||
- [ ] **Backup Mongo automatique en cron** — le script existe désormais, reste à le planifier. 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
|
||||
|
||||
---
|
||||
@ -160,6 +170,21 @@ Dix-neuf chantiers de canaux et d'IA, chacun avec son plan détaillé. État au
|
||||
|
||||
---
|
||||
|
||||
## 5ter. Visibilité — SEO/GEO de la landing & LinkedIn (ouvert le 2026-09-10)
|
||||
|
||||
→ Plan en 21 actions et suivi : **[myinfomate-landing/audit-seo-geo.md](../myinfomate-landing/audit-seo-geo.md)** (§0) · posts : **[linkedin-posts-lancement.md](linkedin-posts-lancement.md)**
|
||||
|
||||
- ✅ **Corrections rapides (actions 1 à 5)** faites le 2026-09-10 : titres, schema `Organization` (adresse, TVA), 3 offres dans le schema, 9 modules dans le HTML, liens morts. Build OK, vérifié dans le HTML servi. **Stagées, pas committées.**
|
||||
- ✅ **LinkedIn** : vitrine MyInfoMate créée sous Unov, fiche produit soumise à LinkedIn.
|
||||
- ✅ **Actions 6 à 8** faites le 2026-09-10 : `llms.txt` en français, **prérendu statique rétabli** (58 pages, le layout racine est désormais `[lang]/layout.tsx`), page **`/{lang}/tarifs`** avec FAQ tarifaire. Stagées, pas committées.
|
||||
- ✅ **`/assistant-ia` livrée le 2026-09-10 dans les 4 langues** (relue par Thomas en FR) — 6 FAQ par langue, `hreflang` complet. **62 pages prérendues, 54 URLs au sitemap.** Au passage, les liens légaux des footers sont passés en `<Link>` : le lint est à **0 erreur** (15 avant).
|
||||
- ✅ **CGU publiées le 2026-09-10** sur `myinfomate.be/conditions-generales` — le lien de la case de consentement de `/signup` pointait jusque-là vers `/{lang}#`, c'est-à-dire nulle part. Lien ajouté aussi dans les pieds de page des 4 langues et dans le sitemap. **Deux contradictions corrigées dans [cgu-myinfomate.md](cgu-myinfomate.md)** : §6.1 citait Starter/Standard (2 et 10 Go) et §7.1 réservait l'IA aux « plans Standard et Premium ». Ce fichier reste la source ; la page Next en est le rendu, à tenir synchrone.
|
||||
- ⚠️ **Reste dû : la validation juridique des CGU** (jamais faite, le document le dit depuis avril) et le **DPA** qu'un acheteur public demandera. Carte kanban dédiée ; à faire **avant** d'ouvrir l'inscription à de vrais clients payants, pas avant la mise en prod technique.
|
||||
- ⏭️ **Prochain** : vocabulaire acheteur dans les 6 segments, puis page marchés publics. **Angle validé le 10/09** : MyInfoMate a été retenu sur **cahier des charges publié** par Visit Namur — c'est la preuve à mettre en avant. ⚠️ Aucun document d'appel d'offres n'existe encore (attestations fiscale/ONSS, DPA RGPD, attestations de bonne exécution) : la page ne doit rien promettre à ce sujet.
|
||||
- ⚠️ **Posts LinkedIn en deux vagues** : les posts sur des déploiements réels peuvent partir maintenant ; ceux qui poussent à l'inscription (IA, prix, essai) attendent la bascule prod et un onboarding testé.
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
# Conditions Générales d'Utilisation — MyInfoMate
|
||||
**Unov — Version 1.0 — Avril 2026**
|
||||
|
||||
> **Note :** Ce document constitue une base de travail à faire valider par un juriste spécialisé en droit IT/commercial belge avant toute utilisation contractuelle.
|
||||
> **Note interne, non publiée :** ce document constitue une base de travail à faire valider par un juriste spécialisé en droit IT/commercial belge avant toute utilisation contractuelle.
|
||||
>
|
||||
> **Publié le 2026-09-10** sur `myinfomate.be/conditions-generales` — c'est ce fichier qui reste la source ; toute modification doit être répercutée dans `myinfomate-landing/src/app/(legal)/conditions-generales/page.tsx`. Corrigé le même jour : le §6.1 citait les plans abandonnés (Starter/Standard 2/10 Go) et le §7.1 réservait l'IA aux « plans Standard et Premium ».
|
||||
|
||||
---
|
||||
|
||||
@ -106,9 +108,10 @@ Le Prestataire se réserve le droit de modifier ses tarifs avec un préavis de 6
|
||||
|
||||
### 6.1 Quotas de stockage
|
||||
Chaque plan inclut un quota de stockage pour les ressources uploadées (images, PDF, documents) :
|
||||
- Starter : 2 GB
|
||||
- Standard : 10 GB
|
||||
- Premium : 50 GB
|
||||
- Essentiel : 1 Go
|
||||
- Pro : 15 Go
|
||||
- Premium : 50 Go
|
||||
- Enterprise : sur mesure
|
||||
|
||||
### 6.2 Dépassement
|
||||
En cas de dépassement du quota, le Prestataire en informera le Client. Le Client dispose de 30 jours pour réduire son usage ou upgrader son plan. Sans action, le Prestataire pourra suspendre les uploads jusqu'à régularisation.
|
||||
@ -121,10 +124,10 @@ Les ressources sont hébergées sur des services tiers (Firebase Storage ou équ
|
||||
## 7. Service d'assistant IA
|
||||
|
||||
### 7.1 Disponibilité selon le plan
|
||||
L'assistant IA conversationnel est disponible uniquement dans les plans Standard et Premium.
|
||||
L'assistant IA conversationnel est inclus dans le plan Premium. Il est disponible en option sur les autres plans, et sur mesure dans le plan Enterprise (voir §5.1).
|
||||
|
||||
### 7.2 Quotas de requêtes
|
||||
Le nombre de requêtes IA est réinitialisé chaque mois calendaire. Les requêtes non utilisées ne sont pas reportées.
|
||||
L'usage de l'assistant est encadré par le quota mensuel décrit au §5.1 bis. Ce quota est réinitialisé au début de chaque période de facturation mensuelle ; le solde non utilisé n'est pas reporté.
|
||||
|
||||
### 7.3 Fournisseur IA
|
||||
Le service IA est fourni via un fournisseur tiers (Google Gemini ou équivalent). Le Prestataire se réserve le droit de changer de fournisseur sans impact fonctionnel pour le Client.
|
||||
|
||||
58
kanban.html
58
kanban.html
@ -461,10 +461,10 @@
|
||||
<div class="stat"><span class="n n-critical">2</span><span class="k">Urgent</span></div>
|
||||
<div class="stat"><span class="n n-info">1</span><span class="k">Migration v3</span></div>
|
||||
<div class="stat"><span class="n n-warn">2</span><span class="k">Bugs ouverts</span></div>
|
||||
<div class="stat"><span class="n">11</span><span class="k">À tester</span></div>
|
||||
<div class="stat"><span class="n">36</span><span class="k">Planifié</span></div>
|
||||
<div class="stat"><span class="n">12</span><span class="k">À tester</span></div>
|
||||
<div class="stat"><span class="n">38</span><span class="k">Planifié</span></div>
|
||||
<div class="stat"><span class="n n-gate">9</span><span class="k">Bascule prod</span></div>
|
||||
<div class="stat"><span class="n n-good">62</span><span class="k">Fait récemment</span></div>
|
||||
<div class="stat"><span class="n n-good">63</span><span class="k">Fait récemment</span></div>
|
||||
</section>
|
||||
|
||||
<div class="filters" role="group" aria-label="Filtrer par domaine">
|
||||
@ -560,7 +560,7 @@
|
||||
|
||||
<!-- À TESTER -->
|
||||
<section class="col" style="--stripe: var(--brand)">
|
||||
<div class="col-head"><h2>À tester</h2><span class="count">11</span></div>
|
||||
<div class="col-head"><h2>À tester</h2><span class="count">12</span></div>
|
||||
<div class="stack">
|
||||
|
||||
<article class="card" data-area="visitapp">
|
||||
@ -660,12 +660,33 @@
|
||||
<span class="src">conversation du 2026-09-08 — captures du Fort de Saint-Héribert</span>
|
||||
</article>
|
||||
|
||||
<article class="card" data-area="visitapp" data-horizon="v1">
|
||||
<div class="card-meta"><span class="tag">§23</span><span class="tag">manager-service</span><span class="tag">manager-app</span><span class="tag">visitapp-web</span><span class="tag">mymuseum-visitapp</span><span class="flag f-warn">Codé 10/09 · device requis · redirection serveur à faire</span></div>
|
||||
<h3>QR codes, nom d'application, page <code>/download</code> et notifications de proximité</h3>
|
||||
<p><strong>Codé</strong> dans les quatre repos, rien n'est committé :</p>
|
||||
<ul>
|
||||
<li><strong>ApplicationInstance</strong> (par app, Mobile ≠ Web) : <code>IsQRCodeEnabled</code> (défaut <code>true</code>), <code>AppName</code> (traductions), <code>AppStoreUrl</code>/<code>PlayStoreUrl</code> (modifiables par un SuperAdmin seulement, contrôlé côté API). Migration <code>AddAppNameQrAndStoresToApplicationInstance</code>.</li>
|
||||
<li><strong>Scan mobile</strong> : id brut (QR du Fort) résolu via la section puis sa visite, y compris depuis l'accueil ; les URL <code>web.mymuseum.be</code> (MDLF) restent lues ; nouvelle URL <code>app.myinfomate.be/download/…</code>. QR d'une autre instance refusé.</li>
|
||||
<li><strong>QR du manager</strong> : <code>app.myinfomate.be/download/{instance}/{config}/{section}</code> — ou la section web directement pour une instance web seule.</li>
|
||||
<li><strong>visitapp-web</strong> : page <code>/download</code> (nom, image principale, liens stores), bouton de scan conditionné, nom de l'app sur l'accueil et dans l'onglet, suggestion de proximité GPS dans la page. Slugs <code>download</code>/<code>demo</code>/<code>api</code> réservés.</li>
|
||||
<li><strong>Proximité mobile</strong> : beacon + zones GPS (<code>meterZoneGPS</code>). App ouverte → popup ; écran verrouillé pendant une visite → notification, dont le tap ouvre la section. Android : service de premier plan <code>location</code> ; iOS : mode d'arrière-plan <code>location</code>. App tuée : rien, par choix.</li>
|
||||
</ul>
|
||||
<p>⚠️ <strong>Reste à faire hors code</strong> :</p>
|
||||
<ul>
|
||||
<li><strong>Redirection 301</strong> <code>web.mymuseum.be/{i}/{c}/{s}</code> → <code>app.myinfomate.be/download/{i}/{c}/{s}</code>, pour les QR MDLF scannés à l'appareil photo. Le domaine n'est routé dans aucun compose des repos : à poser là où il est servi.</li>
|
||||
<li><code>app.myinfomate.be</code> n'est pas encore déployé (carte 210) : les QR générés par le manager y mènent, les deux partent en prod ensemble.</li>
|
||||
<li>Stores : déclarer le service de premier plan de type <em>location</em> dans la Play Console ; justifier le mode <code>location</code> à la revue Apple.</li>
|
||||
</ul>
|
||||
<p>Bug corrigé au passage : la popup beacon (<code>BeaconArticleFound</code>) lisait les maps JSON brutes de <code>currentSections</code> comme des <code>SectionDTO</code> et plantait.</p>
|
||||
<span class="src">test-plan.md §23</span>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- PLANIFIÉ -->
|
||||
<section class="col" style="--stripe: var(--ink-3)">
|
||||
<div class="col-head"><h2>Planifié</h2><span class="count">36</span></div>
|
||||
<div class="col-head"><h2>Planifié</h2><span class="count">38</span></div>
|
||||
<div class="stack">
|
||||
|
||||
<article class="card" data-area="visitapp" data-horizon="v1">
|
||||
@ -846,14 +867,25 @@
|
||||
</article>
|
||||
|
||||
<article class="card" data-area="doc" data-horizon="v1">
|
||||
<div class="card-meta"><span class="tag">myinfomate-landing</span><span class="flag f-warn">Nouveau 28/08 · priorité 1</span></div>
|
||||
<div class="card-meta"><span class="tag">myinfomate-landing</span><span class="flag f-warn">10/09 : Thomas ne veut pas afficher de montant</span></div>
|
||||
<h3>Chiffrer les frais de mise en place sur la page tarifs</h3>
|
||||
<p><strong>Le défaut est de confiance, pas de design.</strong> La carte Pro annonce <code>€99/mois</code> et, juste dessous, <code>setupFeeNote</code> = « Frais de mise en place + engagement 12 mois (app mobile) » — <strong>sans aucun chiffre</strong> (<code>translations.ts:272</code>, repris tel quel en EN/NL/DE). L'acheteur budgète 1 200 €/an, puis découvre le setup en rendez-vous : c'est le pire moment pour l'apprendre. Remède : « <strong>à partir de X € selon le volume de contenu</strong> », dans les 4 langues. <strong>Priorité n°1 de la passe.</strong></p>
|
||||
<p><strong>⚖️ Arbitré le 10/09 : pas de montant affiché.</strong> Thomas ne veut pas publier de plancher. Mesure de repli appliquée le même jour, dans les 4 langues : <code>setupFeeNote</code> devient « <strong>Frais de mise en place uniques, chiffrés dans le devis</strong> + engagement 12 mois », et la FAQ de la nouvelle page <code>/tarifs</code> précise ce que couvrent ces frais (configuration et publication sur les stores) et qu'ils sont chiffrés <strong>avant signature</strong>. L'acheteur sait donc qu'une ligne s'ajoute, et quand il en connaîtra le montant. <strong>Ce qui reste ouvert</strong> : si un plancher est un jour décidé, c'est cette carte qui le porte.</p>
|
||||
<p><strong>Secondaire, même passe</strong> : badge « <strong>IA incluse</strong> » sur Premium — le badge « Recommandé » <em>reste</em> sur Pro (<code>HomeClient.tsx:525</code>), il ne se déplace pas ; regrouper assistant IA + traduction automatique + audio sous un intitulé unique type « <strong>Studio IA</strong> » plutôt que trois lignes de features indistinctes ; remplacer <strong>le stockage en GB</strong> (1 / 15 / 50 GB, en dur dans <code>HomeClient.tsx</code> et <code>SegmentPageClient.tsx</code>) par une métrique que l'acheteur sait évaluer : <strong>POI / parcours / langues / sites</strong>. Personne ne sait ce que pèsent ses contenus en gigaoctets.</p>
|
||||
<p>⚠️ <strong>Le « Bientôt disponible » sur Essentiel n'existe pas dans le code</strong> — vérifié le 28/08 : le seul badge « Bientôt » de la landing est sur le 4<sup>e</sup> mode de déploiement (XR, <code>HomeClient.tsx:154</code>), et la carte Essentiel pointe déjà vers <code>/signup</code>. Ce qui bloque réellement Essentiel est le <strong>déploiement de <code>app.myinfomate.be</code></strong> (carte dédiée dans ce tableau). L'arbitrage est donc : déployer, ou <em>ajouter</em> un badge honnête en attendant — pas retirer un badge absent.</p>
|
||||
<span class="src">myinfomate-landing/src/data/translations.ts · HomeClient.tsx §pricing · todo-features.md — Site vitrine</span>
|
||||
</article>
|
||||
|
||||
<article class="card" data-area="commercial" data-horizon="v1">
|
||||
<div class="card-meta"><span class="tag">myinfomate-landing</span><span class="tag">seo</span><span class="tag">linkedin</span><span class="flag f-good">Démarré 10/09 · actions 1-9 faites</span></div>
|
||||
<h3>Plan SEO/GEO de la landing et lancement LinkedIn</h3>
|
||||
<p><strong>Audit du 09/09 : 21 actions classées par rapport impact/effort.</strong> Faites le 10/09 et vérifiées dans le HTML servi : titres <code>h2</code>/<code>h3</code>, schema <code>Organization</code> ancré en Belgique avec la TVA, trois offres dans le schema, les 9 modules de la home enfin dans le HTML, liens morts, <code>llms.txt</code> en français, <strong>prérendu statique rétabli</strong> (58 pages, le layout racine est passé dans <code>[lang]</code>) et une page <strong><code>/tarifs</code></strong> avec FAQ tarifaire. <strong>Stagé, pas committé.</strong></p>
|
||||
<p><strong><code>/assistant-ia</code> est écrite le 10/09, en français seulement</strong> (~850 mots, 6 FAQ, schema <code>FAQPage</code>) : en attente de relecture, puis traduction NL/EN/DE. Les autres langues répondent 404, c'est volontaire.</p>
|
||||
<p><strong>Suite</strong> : vocabulaire acheteur dans les 6 segments, puis page marchés publics — <strong>preuve à mettre en avant, confirmée le 10/09 : Visit Namur a été gagné sur cahier des charges publié</strong>, mais ⚠️ aucun document d'appel d'offres n'existe encore, donc ne rien promettre. Les frais de mise en place restent <strong>sans montant, par décision de Thomas</strong> (carte 180).</p>
|
||||
<p><strong>LinkedIn</strong> : vitrine MyInfoMate créée sous Unov, fiche produit soumise. Huit posts rédigés en deux vagues : ⚠️ ceux qui poussent à l'inscription attendent la bascule prod.</p>
|
||||
<span class="src">myinfomate-landing/audit-seo-geo.md §0 · DOCS/linkedin-posts-lancement.md</span>
|
||||
</article>
|
||||
|
||||
<article class="card" data-area="backend visitapp" data-horizon="v2">
|
||||
<div class="card-meta"><span class="tag">priorité 4</span><span class="flag f-warn">V2</span></div>
|
||||
<h3>Génération de visites à la demande</h3>
|
||||
@ -861,6 +893,15 @@
|
||||
<span class="src">plan-import-ia-stats-subsides.md §3</span>
|
||||
</article>
|
||||
|
||||
<article class="card" data-area="doc" data-horizon="v1">
|
||||
<div class="card-meta"><span class="tag">juridique</span><span class="tag">myinfomate-landing</span><span class="flag f-warn">CGU publiées le 10/09 sans relecture juridique</span></div>
|
||||
<h3>Faire valider les CGU par un juriste belge</h3>
|
||||
<p><strong>Les CGU sont en ligne depuis le 10/09</strong> — publier valait mieux que faire cocher « j'accepte » sur un lien mort — mais elles n'ont <strong>jamais été relues par un juriste</strong>, ce que le document source dit lui-même depuis avril. Deux points appellent un œil professionnel : le <strong>§8 (RGPD)</strong>, réécrit le 11/08 quand le guide IA s'est mis à enregistrer les questions des visiteurs, et le <strong>§9 (SLA à 99 %)</strong>, qui est un engagement chiffré tenu par une infrastructure mono-serveur.</p>
|
||||
<p><strong>Deux sources à garder synchrones</strong> : <code>DOCS/cgu-myinfomate.md</code> fait foi, la page Next en est le rendu. Toute correction du juriste passe par les deux.</p>
|
||||
<p>⏱️ <strong>À faire avant d'ouvrir l'inscription self-service à de vrais clients payants</strong>, pas avant la mise en prod technique. Prévoir aussi le <strong>contrat de traitement des données (DPA)</strong> qu'un acheteur public demandera — il n'existe pas encore.</p>
|
||||
<span class="src">DOCS/cgu-myinfomate.md (note d'en-tête) · myinfomate-landing/src/app/(legal)/conditions-generales/page.tsx</span>
|
||||
</article>
|
||||
|
||||
<article class="card" data-area="manager backend" data-horizon="v2">
|
||||
<div class="card-meta"><span class="tag">guide IA</span><span class="flag f-warn">V2</span></div>
|
||||
<h3>Guides multiples & guide thématique</h3>
|
||||
@ -1456,6 +1497,11 @@
|
||||
<span>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.</span>
|
||||
</div>
|
||||
|
||||
<div class="done-item">
|
||||
<strong>Le lien « conditions générales » de l'inscription ne mène nulle part</strong>
|
||||
<p>Relevé et corrigé le 10/09. Les CGU sont désormais publiées sur <code>myinfomate.be/conditions-generales</code> (groupe <code>(legal)</code>, page statique), la case de consentement de <code>/signup</code> pointe dessus, et le lien figure aussi dans les pieds de page des 4 langues et dans le sitemap. Deux contradictions du document source ont été corrigées au passage : le §6.1 citait les plans abandonnés (Starter 2 Go / Standard 10 Go) et le §7.1 réservait l'assistant IA aux « plans Standard et Premium ». ⚠️ La <strong>validation juridique</strong> reste due — carte dédiée.</p>
|
||||
</div>
|
||||
|
||||
<div class="done-item">
|
||||
<strong>Écran Guide IA — onglet Configuration livré</strong>
|
||||
<span>Migration additive (4 colonnes nullable), champs sur <code>Instance</code>, client généré étendu à la main, écran avec 35 clés i18n FR/EN/NL. <code>dotnet build</code> et <code>flutter build web</code> passent. Menu conditionné à <code>isAssistant</code>, le même drapeau que la garde d'<code>AiController</code>.</span>
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Home v3 — téléchargement hors ligne porté, et mort de l'ancienne home
|
||||
area: visitapp
|
||||
tags: mymuseum-visitapp, UI, hors ligne
|
||||
flag: warn | Jamais vu tourner sur device
|
||||
src: conversation du 2026-09-08 — captures du Fort de Saint-Héribert
|
||||
---
|
||||
<p><strong>Trois trous de <code>home_3.0.dart</code> comblés le 2026-09-08, aucun vu tourner sur device.</strong></p>
|
||||
<p><strong>(1) Les configurations désactivées s'affichaient.</strong> <code>isActive</code> ne vit pas sur <code>ConfigurationDTO</code> mais sur le <em>lien</em> config↔canal (<code>AppConfigurationLinkDTO.isActive</code>, ce que bascule <code>app_configuration_link_screen:407</code>). Ni le fetch par liens ni le set <code>mobileConfigIds</code> ne le lisaient. ⚠️ <strong>L'ancienne home avait le même bug</strong> — ce n'était pas une régression de la v3. La référence était à côté : <code>visitapp-web/src/lib/api/client.ts:170</code> filtre déjà <code>link.isActive && link.configuration</code>.</p>
|
||||
<p><strong>(2) Une visite hors ligne était un cul-de-sac.</strong> Pas de bouton télécharger, pas de garde : la tuile ouvrait <code>ConfigurationPage</code>, et <code>body.dart:250</code> lisant ses sections <em>uniquement</em> en base locale quand <code>isOffline</code> est vrai, le visiteur voyait un détail vide, sans message ni erreur. Porté depuis <code>configurations_list.dart</code> : badge en pastille de verre sur la tuile bento (↓ / ✓), tuile ternie tant qu'elle n'est pas prête, dialogue sombre choix de langue + progression (<code>DownloadConfigurationWidget</code> réutilisé tel quel). Zéro nouvelle clé i18n, tout existait dans les 10 langues.</p>
|
||||
<p>⚠️ <strong>Piège trouvé en câblant</strong> : <code>alreadyDownloaded</code> se déduisait des lignes de la table <code>configurations</code> — table dans laquelle le fetch <strong>écrit une ligne par visite</strong> pour cacher <code>order</code>/<code>gridSpan</code>. Au deuxième lancement, <em>toutes</em> les visites se seraient déclarées téléchargées, et le clic aurait rouvert un détail vide. L'état se déduit maintenant des <strong>sections en base locale</strong>, ce que lit le détail.</p>
|
||||
<p><strong>(3) Passe visuelle.</strong> Nouveau <code>Components/GlassPill.dart</code> (verre dépoli, 3 usages) : le cog devient <code>Icons.tune</code> + drapeau de la langue courante en pastille, <code>GlassesStatusWidget</code> passe sur la même pastille, et la feuille de réglages passe en sombre — une feuille blanche dans une app noire était le contraste le plus violent de l'écran.</p>
|
||||
<p><strong>✅ À supprimer une fois validé sur device : <code>Screens/Home/home.dart</code> et <code>Screens/Home/configurations_list.dart</code>.</strong> Ils ne sont plus référencés par personne — <code>HomePage</code> est orphelin, <code>CustomAppBar</code>, <code>main.dart:220</code>, <code>body.dart:122</code> et <code>menu_page.dart:136</code> pointent tous sur <code>HomePage3</code>. Ils n'étaient gardés que pour le téléchargement, qui vient d'être porté. <strong>Ne les supprimer qu'après avoir joué §21 du test-plan sur device</strong> : ce sont les seules références du comportement d'origine si le portage se révèle incomplet.</p>
|
||||
<p>⚠️ <strong>Deux verrues mises en lumière, pas corrigées.</strong> <code>downloadPrompt</code> annonce « 39,8MB » <strong>en dur dans les 10 langues</strong> et aucune donnée de poids n'existe côté DTO (la colonne <code>weightMasonryGrid</code> a été abandonnée en v3) : soit la taille entre dans l'export, soit le chiffre sort du texte. Et <code>home_3.0.dart:904</code> porte <code>isOnline = true; // Todo remove if not local test</code> — la branche hors ligne <strong>ne s'exécute jamais</strong> (l'analyzer le confirme, <code>dead_code</code> juste après) : une visite téléchargée s'ouvre, mais l'accueil en mode avion passe quand même par le réseau.</p>
|
||||
@ -8,6 +8,8 @@ src: analyse du code le 2026-09-04 — MapProvider, flutter_map, mapbox_maps_flu
|
||||
---
|
||||
<p><strong>Aujourd'hui aucune carte ne fonctionne hors ligne</strong>, et ce n'est pas une question de données : <code>MapDTO</code> porte déjà <code>points</code>, <code>centerLatitude</code>, <code>zoom</code> et <code>iconResourceId</code>, et les icônes sont téléchargées avec la visite. C'est le <strong>fond</strong> qui manque — les tuiles sont chargées en réseau, sans cache.</p>
|
||||
<p><strong>Le GPS, lui, n'est pas le problème</strong> : <code>geolocator ^13.0.0</code> lit le GNSS, qui est autonome. Seule réserve à annoncer au client : sans A-GPS, le premier point peut demander 30 à 60 s sous couvert forestier.</p>
|
||||
<p><strong>Le chemin court existe déjà dans le pubspec</strong> : <code>mapbox_maps_flutter ^2.0.0</code> expose <code>OfflineManager</code> (style packs) et <code>TileStore</code> (tile regions) — on télécharge une emprise bornée au moment du téléchargement de la visite. Pour un domaine comme le Fourneau Saint-Michel, quelques dizaines de Mo. <code>MapProvider.MapBox</code> existe déjà côté modèle.</p>
|
||||
<p><strong>Le chemin court existe déjà dans le pubspec</strong> : la version <strong>résolue est 2.8.0</strong> (pas 2.0.0 comme le laisse croire la contrainte), et le paquet installé contient bien <code>OfflineManager</code>, <code>TileStore</code>, <code>StylePackLoadOptions</code>, <code>TileRegionLoadOptions</code> et <code>TileRegion</code> — <strong>vérifié dans le cache pub, pas supposé</strong>. On télécharge une emprise bornée au moment du téléchargement de la visite ; pour un domaine comme le Fourneau, quelques dizaines de Mo.</p>
|
||||
<p>✅ <strong>Mapbox est désormais le fournisseur par défaut</strong> (04/09) : le <code>null</code> retombait sur Google dans <code>map_page.dart</code> (deux endroits) et <code>map_config.dart</code>. ⚠️ Effet de bord à surveiller — les sections carte existantes de MDLF et Fort Saint-Héribert dont le <code>mapProvider</code> est <code>null</code> <strong>basculent silencieusement de Google à Mapbox</strong>. Le token est déjà en place, en dur dans <code>main.dart:60</code>.</p>
|
||||
<p>⚠️ <strong>Ce n'est pas gratuit, et le compteur est le mauvais</strong> : ce qui coûte n'est pas la surface de l'emprise mais le <strong>nombre de téléchargements</strong> — un tile pack par visiteur. La facture monte donc avec la fréquentation, c'est-à-dire avec le succès du client. Grille à vérifier chez Mapbox, ligne « tile packs » et pas seulement « map loads ». C'est exactement ce calcul que supprime <code>v2/plan-illustre-plan.md</code>.</p>
|
||||
<p>⛔ <strong>Google est une impasse, et pas seulement techniquement.</strong> Le SDK Maps pour Android n'expose aucune API de tuiles hors ligne — les « zones hors connexion » sont une fonction de l'app Google Maps, pas du SDK intégrable. Et le code n'utilise même pas ce SDK pour les sections carte : il passe par <code>flutter_map</code> sur <code>https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}</code>, un endpoint non documenté dont la mise en cache est explicitement interdite. <strong>Conclusion : Mapbox devient le fournisseur du mode hors ligne, Google reste en ligne seulement.</strong></p>
|
||||
<p>⚠️ Tant que ce chantier n'est pas fait, <code>SectionType.Map</code> reste volontairement hors de <code>offlineCapableSectionTypes</code> (<code>downloadConfiguration.dart</code>) : l'ajouter donnerait une carte grise, ce qui est pire que ne pas la proposer. Une fois les tuiles packagées, c'est <strong>une ligne à ajouter dans cette constante</strong>.</p>
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
---
|
||||
title: Plan illustré géoréférencé — un 3<sup>e</sup> fournisseur de carte
|
||||
title: Plan illustré — un 3<sup>e</sup> fournisseur de carte, sans tuiles
|
||||
area: manager backend visitapp
|
||||
horizon: v2
|
||||
tags: carto, offline, manager-app
|
||||
flag: good | hors ligne par construction, et c'est ce qu'un musée dessine déjà
|
||||
src: analyse du code le 2026-09-04 — MapProvider n'a que Google et MapBox
|
||||
flag: good | le seul chemin vers une carte hors ligne sans coût tiers
|
||||
src: v2/plan-illustre-plan.md — conçu le 2026-09-04
|
||||
---
|
||||
<p><strong>Un musée de plein air ne distribue pas un fond OpenStreetMap, il distribue son plan dessiné.</strong> Aujourd'hui <code>MapProvider</code> ne connaît que <code>Google</code> et <code>MapBox</code> : il n'existe aucun moyen de téléverser une image de plan et d'y placer les points.</p>
|
||||
<p><strong>Ce qu'il faut</strong> : une troisième valeur de <code>MapProvider</code>, une ressource image portée par la section, et un calage — deux points d'ancrage suffisent (coin haut-gauche / bas-droit en coordonnées réelles) pour projeter un <code>GeoPoint</code> sur l'image et y afficher la position du visiteur.</p>
|
||||
<p><strong>Pourquoi c'est le meilleur des trois</strong> : aucune tuile, donc <strong>hors ligne par construction</strong> — l'image part avec la visite comme n'importe quelle ressource, et le mécanisme de téléchargement n'a rien de nouveau à apprendre. Le rendu est aussi plus lisible qu'un fond routier sur un site de plusieurs dizaines de bâtiments.</p>
|
||||
<p><strong>Périmètre réel</strong> : c'est le plus gros des trois chemins carto — backend (modèle + migration), manager-app (téléversement du plan et pose des ancres), <code>mymuseum-visitapp</code> et <code>visitapp-web</code> (rendu). À faire après <em>Carte hors ligne — tile packs Mapbox</em>, qui débloque le besoin immédiat avec un fond classique.</p>
|
||||
<p><strong>Un musée de plein air ne distribue pas un fond OpenStreetMap, il distribue son plan dessiné.</strong> Conception complète, modèle de données et périmètre par repo : <strong><code>DOCS/v2/plan-illustre-plan.md</code></strong>. Rien n'est implémenté.</p>
|
||||
<p><strong>La décision qui structure tout</strong> : les repères se posent <strong>directement sur l'image</strong>, à la main, et c'est cette position qui fait foi — un plan dessiné n'étant ni à l'échelle ni orienté au nord, projeter des <code>GeoPoint</code> depuis leurs coordonnées les ferait tomber à côté des bâtiments. Le calage GPS ne sert qu'à afficher <strong>le visiteur</strong>, et devient donc <strong>facultatif</strong>.</p>
|
||||
<p>⚠️ <strong>Deux affirmations de la première version de cette carte étaient fausses</strong>, corrigées le 04/09 : on ne projette pas les repères, et <strong>deux points d'ancrage ne suffisent pas</strong> — il en faut trois pour une transformation affine qui absorbe rotation et étirement, et pour pouvoir vérifier le calage au lieu de diluer l'erreur.</p>
|
||||
<p>✅ <strong>Le calage se fait assis</strong> : cliquer le même angle de bâtiment sur le plan puis sur une carte réelle en regard. <code>GeolocInputContainer</code> ouvre déjà un <code>FlutterLocationPicker</code> (vraie carte, recherche d'adresse) — le composant est écrit, il faut le mettre à côté du plan. Plus besoin de prestation d'installation.</p>
|
||||
<p>✅ <strong>Hors ligne gratuit</strong> : le plan est une <code>Resource</code>, une ligne dans <code>GetReferencedResourceIds()</code> et le pipeline existant l'embarque. Aucun tiers, aucun quota — contrairement aux tile packs Mapbox, facturés <strong>par visiteur</strong>.</p>
|
||||
<p>⚠️ <strong>Conséquence web à trancher</strong> : <code>visitapp-web</code> reste sur Leaflet (lot E, W1), et <code>mapProviderMobileOnlyNote</code> le dit déjà au client. Pour un plan illustré c'est bloquant — le plan <em>est</em> le produit. Soit on porte le rendu en CSS dans la foulée, soit le <strong>plan Essentiel, web-only, n'y a pas droit</strong>.</p>
|
||||
<p><strong>Livrable qui se vend seul</strong> : le plan <em>sans</em> calage — téléversement, pose des repères, rendu, hors ligne. C'est le produit que les audioguides vendent depuis trente ans.</p>
|
||||
|
||||
@ -3,9 +3,10 @@ title: Chiffrer les frais de mise en place sur la page tarifs
|
||||
area: doc
|
||||
horizon: v1
|
||||
tags: myinfomate-landing
|
||||
flag: warn | Nouveau 28/08 · priorité 1
|
||||
flag: warn | 10/09 : Thomas ne veut pas afficher de montant
|
||||
src: myinfomate-landing/src/data/translations.ts · HomeClient.tsx §pricing · todo-features.md — Site vitrine
|
||||
---
|
||||
<p><strong>Le défaut est de confiance, pas de design.</strong> La carte Pro annonce <code>€99/mois</code> et, juste dessous, <code>setupFeeNote</code> = « Frais de mise en place + engagement 12 mois (app mobile) » — <strong>sans aucun chiffre</strong> (<code>translations.ts:272</code>, repris tel quel en EN/NL/DE). L'acheteur budgète 1 200 €/an, puis découvre le setup en rendez-vous : c'est le pire moment pour l'apprendre. Remède : « <strong>à partir de X € selon le volume de contenu</strong> », dans les 4 langues. <strong>Priorité n°1 de la passe.</strong></p>
|
||||
<p><strong>⚖️ Arbitré le 10/09 : pas de montant affiché.</strong> Thomas ne veut pas publier de plancher. Mesure de repli appliquée le même jour, dans les 4 langues : <code>setupFeeNote</code> devient « <strong>Frais de mise en place uniques, chiffrés dans le devis</strong> + engagement 12 mois », et la FAQ de la nouvelle page <code>/tarifs</code> précise ce que couvrent ces frais (configuration et publication sur les stores) et qu'ils sont chiffrés <strong>avant signature</strong>. L'acheteur sait donc qu'une ligne s'ajoute, et quand il en connaîtra le montant. <strong>Ce qui reste ouvert</strong> : si un plancher est un jour décidé, c'est cette carte qui le porte.</p>
|
||||
<p><strong>Secondaire, même passe</strong> : badge « <strong>IA incluse</strong> » sur Premium — le badge « Recommandé » <em>reste</em> sur Pro (<code>HomeClient.tsx:525</code>), il ne se déplace pas ; regrouper assistant IA + traduction automatique + audio sous un intitulé unique type « <strong>Studio IA</strong> » plutôt que trois lignes de features indistinctes ; remplacer <strong>le stockage en GB</strong> (1 / 15 / 50 GB, en dur dans <code>HomeClient.tsx</code> et <code>SegmentPageClient.tsx</code>) par une métrique que l'acheteur sait évaluer : <strong>POI / parcours / langues / sites</strong>. Personne ne sait ce que pèsent ses contenus en gigaoctets.</p>
|
||||
<p>⚠️ <strong>Le « Bientôt disponible » sur Essentiel n'existe pas dans le code</strong> — vérifié le 28/08 : le seul badge « Bientôt » de la landing est sur le 4<sup>e</sup> mode de déploiement (XR, <code>HomeClient.tsx:154</code>), et la carte Essentiel pointe déjà vers <code>/signup</code>. Ce qui bloque réellement Essentiel est le <strong>déploiement de <code>app.myinfomate.be</code></strong> (carte dédiée dans ce tableau). L'arbitrage est donc : déployer, ou <em>ajouter</em> un badge honnête en attendant — pas retirer un badge absent.</p>
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
---
|
||||
title: Plan SEO/GEO de la landing et lancement LinkedIn
|
||||
area: commercial
|
||||
horizon: v1
|
||||
tags: myinfomate-landing, seo, linkedin
|
||||
flag: good | Démarré 10/09 · actions 1-9 faites
|
||||
src: myinfomate-landing/audit-seo-geo.md §0 · DOCS/linkedin-posts-lancement.md
|
||||
---
|
||||
<p><strong>Audit du 09/09 : 21 actions classées par rapport impact/effort.</strong> Faites le 10/09 et vérifiées dans le HTML servi : titres <code>h2</code>/<code>h3</code>, schema <code>Organization</code> ancré en Belgique avec la TVA, trois offres dans le schema, les 9 modules de la home enfin dans le HTML, liens morts, <code>llms.txt</code> en français, <strong>prérendu statique rétabli</strong> (58 pages, le layout racine est passé dans <code>[lang]</code>) et une page <strong><code>/tarifs</code></strong> avec FAQ tarifaire. <strong>Stagé, pas committé.</strong></p>
|
||||
<p><strong><code>/assistant-ia</code> est écrite le 10/09, en français seulement</strong> (~850 mots, 6 FAQ, schema <code>FAQPage</code>) : en attente de relecture, puis traduction NL/EN/DE. Les autres langues répondent 404, c'est volontaire.</p>
|
||||
<p><strong>Suite</strong> : vocabulaire acheteur dans les 6 segments, puis page marchés publics — <strong>preuve à mettre en avant, confirmée le 10/09 : Visit Namur a été gagné sur cahier des charges publié</strong>, mais ⚠️ aucun document d'appel d'offres n'existe encore, donc ne rien promettre. Les frais de mise en place restent <strong>sans montant, par décision de Thomas</strong> (carte 180).</p>
|
||||
<p><strong>LinkedIn</strong> : vitrine MyInfoMate créée sous Unov, fiche produit soumise. Huit posts rédigés en deux vagues : ⚠️ ceux qui poussent à l'inscription attendent la bascule prod.</p>
|
||||
@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Faire valider les CGU par un juriste belge
|
||||
area: doc
|
||||
horizon: v1
|
||||
tags: juridique, myinfomate-landing
|
||||
flag: warn | CGU publiées le 10/09 sans relecture juridique
|
||||
src: DOCS/cgu-myinfomate.md (note d'en-tête) · myinfomate-landing/src/app/(legal)/conditions-generales/page.tsx
|
||||
---
|
||||
<p><strong>Les CGU sont en ligne depuis le 10/09</strong> — publier valait mieux que faire cocher « j'accepte » sur un lien mort — mais elles n'ont <strong>jamais été relues par un juriste</strong>, ce que le document source dit lui-même depuis avril. Deux points appellent un œil professionnel : le <strong>§8 (RGPD)</strong>, réécrit le 11/08 quand le guide IA s'est mis à enregistrer les questions des visiteurs, et le <strong>§9 (SLA à 99 %)</strong>, qui est un engagement chiffré tenu par une infrastructure mono-serveur.</p>
|
||||
<p><strong>Deux sources à garder synchrones</strong> : <code>DOCS/cgu-myinfomate.md</code> fait foi, la page Next en est le rendu. Toute correction du juriste passe par les deux.</p>
|
||||
<p>⏱️ <strong>À faire avant d'ouvrir l'inscription self-service à de vrais clients payants</strong>, pas avant la mise en prod technique. Prévoir aussi le <strong>contrat de traitement des données (DPA)</strong> qu'un acheteur public demandera — il n'existe pas encore.</p>
|
||||
@ -2,7 +2,10 @@
|
||||
title: pg_dump avant / après + cron quotidien
|
||||
area: infra
|
||||
tags: étape 18
|
||||
flag: critical | Filet du jour J
|
||||
flag: warn | scripts + destination prêts, attend la prod Postgres
|
||||
src: STATUS.md §4 · §1quinquies — étape 18
|
||||
---
|
||||
<p>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.</p>
|
||||
<p><strong>Scripts écrits et testés le 07/09</strong> dans <code>manager-service/ManagerService/Deployment/backup/</code> : dump <code>-Fc</code> avec vérification de relecture et rotation, test de restauration comparant les comptages table par table (25 tables identiques), extraction par instance, contrôle de fraîcheur, units systemd, et une procédure de restauration écrite (<code>RESTORE.md</code>).</p>
|
||||
<p><strong>La destination existe depuis le 07/09</strong> : <code>BACKUP_DEST=gcs:unov-myinfomate-backups/pg</code>, service account incapable de supprimer (vérifié par un 403). Voir la carte close « Trancher la destination des sauvegardes hors site ».</p>
|
||||
<p><strong>Ce qui reste</strong> : <code>rclone</code> n'est ni installé ni configuré sur le VPS, et il n'y a de toute façon <strong>pas encore de base Postgres en prod à sauvegarder</strong>. Cette carte est donc une étape à l'intérieur de « Créer l'environnement prod Postgres », pas un chantier parallèle — une vingtaine de minutes le jour venu, plus l'acheminement de la clé du service account en <code>chmod 600</code>.</p>
|
||||
<p>Deux constats du test : les embeddings exclus font passer le dump de 12 Mo à 1,7 Mo, et le « collation version mismatch » du README s'est reproduit — il bloque <code>createdb</code>, donc toute restauration.</p>
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Brancher <code>notify.sh</code> sur un canal réel
|
||||
area: infra
|
||||
tags: étape 18
|
||||
flag: warn | une alerte que personne ne lit n'est pas une alerte
|
||||
src: conversation 07/09 — chantier sauvegardes
|
||||
---
|
||||
<p>Trois surveillances sont en place — échec du script (<code>OnFailure=</code>), sauvegarde plus vieille que 48 h, test de restauration mensuel — et toutes appellent <code>backup/notify.sh</code>, qui ne fait aujourd'hui qu'un <code>logger</code>.</p>
|
||||
<p>Le mode de panne réel n'est pas « le script plante » : c'est « le script ne tourne plus depuis trois semaines et personne ne l'a vu ». C'est précisément celui qu'un <code>OnFailure</code> n'attrape pas et que le contrôle de fraîcheur attrape — à condition que l'alerte sorte de la machine. Renseigner <code>BACKUP_ALERT_WEBHOOK</code>, ou brancher le canal mail du service.</p>
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Trancher la destination des sauvegardes hors site
|
||||
---
|
||||
<p>Arrêté et créé le 07/09 : projet GCP <code>myinfomate-backups</code>, bucket <code>gs://unov-myinfomate-backups</code> en <code>EU</code> multi-région, versioning, accès public interdit, lifecycle 400 jours sous <code>pg/</code>. Service account <code>backup-writer</code> en <code>objectCreator</code> + <code>objectViewer</code> — <strong>incapable de supprimer, vérifié par un 403</strong>.</p>
|
||||
<p>Les médias pèsent <strong>1,43 Go</strong> (mesuré, pas estimé) : ils tiennent dans le même bucket sous <code>media/</code>, donc pas d'OVH Object Storage. La disposition croisée envisagée le matin même est abandonnée — elle se justifiait sur un volume supposé de 45 Go, qui venait en réalité du stockage Google One personnel.</p>
|
||||
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: Le lien « conditions générales » de l'inscription ne mène nulle part
|
||||
---
|
||||
<p>Relevé et corrigé le 10/09. Les CGU sont désormais publiées sur <code>myinfomate.be/conditions-generales</code> (groupe <code>(legal)</code>, page statique), la case de consentement de <code>/signup</code> pointe dessus, et le lien figure aussi dans les pieds de page des 4 langues et dans le sitemap. Deux contradictions du document source ont été corrigées au passage : le §6.1 citait les plans abandonnés (Starter 2 Go / Standard 10 Go) et le §7.1 réservait l'assistant IA aux « plans Standard et Premium ». ⚠️ La <strong>validation juridique</strong> reste due — carte dédiée.</p>
|
||||
152
linkedin-posts-lancement.md
Normal file
152
linkedin-posts-lancement.md
Normal file
@ -0,0 +1,152 @@
|
||||
# LinkedIn — série de lancement MyInfoMate
|
||||
|
||||
> Rédigé le 2026-09-10. Pages : vitrine MyInfoMate `linkedin.com/showcase/myinfomate/`, rattachée à Unov.
|
||||
> Mode d'emploi : publier depuis le **profil perso de Thomas**, puis repartager depuis la vitrine. 1 à 2 posts par semaine, mardi ou jeudi matin.
|
||||
> Lien du site **en premier commentaire**, pas dans le post (LinkedIn pénalise les liens sortants dans le corps).
|
||||
|
||||
## Règle de prudence
|
||||
|
||||
La V3 n'est pas encore en prod (voir `STATUS.md`) et `app.myinfomate.be` n'est pas déployé : le plan Essentiel en self-service n'est donc pas encore utilisable.
|
||||
|
||||
- **Vague 1 (maintenant)** : posts 1 à 5 — ils ne parlent que de déploiements réels et du métier. Aucun ne promet une inscription immédiate.
|
||||
- **Vague 2 (après la bascule prod)** : posts 6 à 8 — assistant IA, prix publics, essai gratuit. Ils amènent des gens à s'inscrire : ne pas les publier avant que l'inscription fonctionne de bout en bout (carte kanban « Onboarding self-service complet »).
|
||||
|
||||
Suivi : cocher la case et noter la date de publication.
|
||||
|
||||
---
|
||||
|
||||
## Vague 1
|
||||
|
||||
### 1. [ ] L'histoire — pourquoi MyInfoMate existe
|
||||
|
||||
> En 2021, le Musée de la Fraise de Wépion avait un problème simple : du contenu riche, et aucun moyen de le mettre à jour sans faire appel à un prestataire.
|
||||
>
|
||||
> On leur a installé une borne tablette. Mais surtout, on leur a donné les clés : un back-office où l'équipe change elle-même les textes, les photos et les vidéos, sans nous appeler.
|
||||
>
|
||||
> C'était le premier déploiement de ce qui est devenu MyInfoMate.
|
||||
>
|
||||
> Depuis, la même idée tient toujours :
|
||||
> → le lieu reste maître de son contenu
|
||||
> → un seul back-office, et le contenu part sur une borne, une app mobile ou le web
|
||||
> → pas une ligne de code à écrire
|
||||
>
|
||||
> Aujourd'hui, j'ouvre une page dédiée à MyInfoMate pour raconter ce qu'on construit, avec les lieux qui l'utilisent. Si vous travaillez dans un musée, un office du tourisme ou un site patrimonial, abonnez-vous : ça va parler de vous.
|
||||
>
|
||||
> #MédiationNumérique #Musées #Tourisme #Wallonie
|
||||
|
||||
### 2. [ ] Cas client — Fort de Saint-Héribert (hors ligne)
|
||||
|
||||
> Comment guider des visiteurs dans des galeries souterraines où aucun téléphone ne capte ?
|
||||
>
|
||||
> C'est la question que nous a posée le Fort de Saint-Héribert.
|
||||
>
|
||||
> La réponse tient en trois choses :
|
||||
> 1. Tout le contenu se télécharge à l'entrée du fort, pendant qu'il y a encore du réseau.
|
||||
> 2. Dans les galeries, l'application fonctionne à 100 % hors ligne : textes, images, audioguide.
|
||||
> 3. Des QR codes à chaque point d'intérêt permettent d'ouvrir directement la bonne fiche.
|
||||
>
|
||||
> Le visiteur ne voit rien de tout ça. Il avance, il écoute, il découvre.
|
||||
>
|
||||
> Beaucoup de sites patrimoniaux ont ce problème : caves, forts, carrières, forêts, sites en plein air loin de tout. Ce n'est pas une raison pour renoncer au numérique.
|
||||
>
|
||||
> Vous gérez un lieu où le réseau ne passe pas ? Racontez-moi en commentaire.
|
||||
>
|
||||
> #Patrimoine #MédiationNumérique #Audioguide
|
||||
|
||||
### 3. [ ] Cas client — Visit Namur (borne)
|
||||
|
||||
> À l'office du tourisme de Namur, les visiteurs posent toujours les mêmes questions : qu'est-ce qu'on peut voir, qu'est-ce qui se passe ce week-end, comment on y va.
|
||||
>
|
||||
> Depuis 2024, une borne interactive MyInfoMate y répond : carte de la ville avec les points d'intérêt, agenda des événements tenu à jour, itinéraires.
|
||||
>
|
||||
> Ce qui compte pour l'équipe : l'agenda change toutes les semaines, et c'est elle qui le met à jour, depuis son navigateur. La borne suit.
|
||||
>
|
||||
> Une borne n'est pas là pour remplacer l'accueil. Elle absorbe les questions répétitives, et l'équipe garde du temps pour les vraies conversations.
|
||||
>
|
||||
> #Tourisme #OfficeDeTourisme #Namur #BorneInteractive
|
||||
|
||||
### 4. [ ] Le métier — borne ou application sur le téléphone du visiteur ?
|
||||
|
||||
> « On part sur une borne ou sur une app ? »
|
||||
>
|
||||
> C'est souvent la première question d'un musée. Ma réponse : ça dépend de votre public, pas de la technologie.
|
||||
>
|
||||
> Une borne, c'est bien quand :
|
||||
> • les visiteurs passent par un point fixe (accueil, entrée de salle)
|
||||
> • vous voulez que tout le monde y ait accès, sans téléchargement
|
||||
> • votre public est peu à l'aise avec son smartphone
|
||||
>
|
||||
> Une application sur le téléphone du visiteur, c'est bien quand :
|
||||
> • la visite se fait en mouvement (parcours, extérieur, grand site)
|
||||
> • vous voulez de l'audio au casque, du jeu, une visite en plusieurs langues
|
||||
> • vous voulez que la visite continue après la sortie
|
||||
>
|
||||
> Et souvent, la bonne réponse, c'est les deux, avec le même contenu. Au Musée de la Fraise, on a commencé par la borne, puis on a ajouté l'app, sans rien réécrire.
|
||||
>
|
||||
> Et vous, borne, app, ou les deux ?
|
||||
>
|
||||
> #Musées #MédiationNumérique #ExpérienceVisiteur
|
||||
|
||||
### 5. [ ] Le métier — faire jouer les visiteurs (escape game, chasse au trésor)
|
||||
|
||||
> Un escape game dans un musée, c'est sérieux ?
|
||||
>
|
||||
> Oui. Surtout pour deux publics qu'on a du mal à retenir : les familles et les ados.
|
||||
>
|
||||
> Un parcours ludique, c'est :
|
||||
> • des énigmes liées aux vraies œuvres ou aux vrais lieux
|
||||
> • un code à trouver pour débloquer l'étape suivante
|
||||
> • une histoire qui donne une raison de regarder de près
|
||||
>
|
||||
> Dans MyInfoMate, l'équipe du lieu construit ces parcours elle-même, dans le même back-office que le reste du contenu : quiz, chasses au trésor, escape games avec énigmes.
|
||||
>
|
||||
> Le meilleur compliment qu'on puisse recevoir : « les enfants ont voulu refaire le parcours ».
|
||||
>
|
||||
> #Musées #Gamification #EscapeGame #Famille
|
||||
|
||||
---
|
||||
|
||||
## Vague 2 — après la bascule prod
|
||||
|
||||
### 6. [ ] L'assistant IA — le différenciateur
|
||||
|
||||
> Un visiteur allemand devant une œuvre, un mardi à 16 h. Il a une question. Personne à l'accueil ne parle allemand.
|
||||
>
|
||||
> C'est pour ce moment-là qu'on a intégré un assistant IA dans MyInfoMate.
|
||||
>
|
||||
> Il répond dans la langue du visiteur, à toute heure. Et surtout, il ne parle **que** de votre lieu : il s'appuie sur vos contenus, pas sur Internet. Hors sujet, il décline.
|
||||
>
|
||||
> Ce n'est pas un gadget, c'est une réponse à une vraie contrainte : on ne peut pas traduire tout un musée en dix langues, ni avoir quelqu'un disponible en permanence.
|
||||
>
|
||||
> Je fais des démos à ceux qui veulent le voir sur leurs propres contenus : écrivez-moi.
|
||||
>
|
||||
> #IA #Musées #Multilingue #ExpérienceVisiteur
|
||||
|
||||
### 7. [ ] Les prix, publiquement
|
||||
|
||||
> Dans notre secteur, les prix sont rarement affichés. Nous, on les affiche.
|
||||
>
|
||||
> • Essentiel — 39 €/mois : l'application web et le back-office complet, sans engagement
|
||||
> • Pro — 99 €/mois : + votre application à votre marque sur les stores, mode hors ligne, notifications
|
||||
> • Premium — 179 €/mois : + l'assistant IA et la traduction automatique
|
||||
> • Enterprise — sur devis, pour les réseaux multi-sites
|
||||
>
|
||||
> (Prix HTVA.)
|
||||
>
|
||||
> Pourquoi afficher les prix ? Parce qu'un musée, une commune ou un office du tourisme doit pouvoir budgéter avant de prendre rendez-vous. Ça fait gagner du temps à tout le monde.
|
||||
>
|
||||
> #Transparence #Musées #Tourisme #SaaS
|
||||
|
||||
⚠️ Avant de publier : vérifier que les prix correspondent toujours à la landing (source de vérité) et que les frais de mise en place du plan Pro sont chiffrés (carte kanban 180).
|
||||
|
||||
### 8. [ ] Essai gratuit
|
||||
|
||||
> Vous voulez voir à quoi ressemblerait votre lieu dans MyInfoMate ?
|
||||
>
|
||||
> L'inscription prend quelques minutes, sur myinfomate.be : 14 jours d'essai, vous construisez votre première visite vous-même, sans carte bancaire à l'entrée.
|
||||
>
|
||||
> Et si vous préférez qu'on le fasse ensemble, je fais la démo avec vos propres contenus.
|
||||
>
|
||||
> #Musées #Tourisme #Patrimoine
|
||||
|
||||
⚠️ Avant de publier : vérifier le parcours d'inscription de bout en bout en prod. « 14 jours, sans carte bancaire » est ce qu'annonce la landing (`trialBadge`, `translations.ts:201`) : le post ne doit pas en dire plus.
|
||||
@ -25,13 +25,33 @@ L'architecture existante (AssistantService, QR scanner, beacons, géoloc) est **
|
||||
| **ElevenLabs** | Synthèse vocale TTS | https://elevenlabs.io — tier gratuit ou payant selon volume |
|
||||
|
||||
### SDK Flutter (developer preview)
|
||||
Les packages Meta SDK doivent être ajoutés à `pubspec.yaml` quand publiés :
|
||||
```yaml
|
||||
# Décommenter quand disponibles :
|
||||
# meta_wearables_dat: any # Android
|
||||
# meta_wearables: any # iOS (ChunkyTofuStudios)
|
||||
flutter_meta_wearables_dat: ^0.9.1 # Android API 29+ et iOS 17.2+ — SDK DAT 0.9.0
|
||||
app_links: ^6.4.1 # retour du deep link Meta AI → handleUrl()
|
||||
```
|
||||
Vérifier les versions actuelles sur pub.dev : `meta_wearables_dat`, `meta_wearables`.
|
||||
|
||||
Le SDK natif est publié sur **GitHub Packages**, pas sur Maven Central. Il faut un
|
||||
PAT GitHub avec le scope `read:packages`, dans `android/local.properties` :
|
||||
|
||||
```properties
|
||||
github_token=ghp_xxxx
|
||||
```
|
||||
|
||||
Sans lui, le build échoue en `401 Unauthorized` sur `maven.pkg.github.com`. Un build
|
||||
peut sembler passer alors que le token est expiré : Gradle sert les artefacts depuis
|
||||
son cache local. Vérifier le token, pas le build.
|
||||
|
||||
**iOS n'est pas activé** malgré le support du package. Le coût réel :
|
||||
- deployment target à **17.2** pour toute l'app visiteur (Podfile est à 12.0)
|
||||
- dictionnaire `MWDAT` dans Info.plist (`MetaAppID`, `ClientToken`, `TeamID`) — le
|
||||
mode développeur `"0"` d'Android ne suffit pas
|
||||
- choix d'un transport caméra : Wi-Fi (entitlements Hotspot Configuration + Wi-Fi
|
||||
Information, `NSLocalNetworkUsageDescription`, +10 s à la 1re connexion) ou
|
||||
Bluetooth Classic (`UISupportedExternalAccessoryProtocols`, bande passante moindre)
|
||||
- la publication publique reste limitée aux partenaires Meta sous developer preview
|
||||
|
||||
Le SDK DAT n'expose **ni micro, ni haut-parleur, ni bouton** des lunettes. Tout le
|
||||
pipeline vocal passe hors SDK (routing Bluetooth HFP/A2DP via `AudioRoutingChannel`).
|
||||
|
||||
---
|
||||
|
||||
@ -130,7 +150,7 @@ flutter run --dart-define=ELEVENLABS_API_KEY=xxx --dart-define=PICOVOICE_ACCESS_
|
||||
Lunettes Ray-Ban Meta
|
||||
│ Bluetooth (HFP mic, A2DP speakers, camera stream, button)
|
||||
▼
|
||||
[MetaGlassesService] ← SDK DAT lifecycle + photo capture button
|
||||
[MetaGlassesService] ← SDK DAT lifecycle + capture photo
|
||||
│
|
||||
├── [WakeWordService] ← Porcupine détecte "Hey MyVisit" (on-device)
|
||||
│ └── speech_to_text ← transcrit la commande après wake word
|
||||
@ -173,9 +193,10 @@ Le wake word fonctionne en permanence en background (Foreground Service sur Andr
|
||||
|
||||
### 2. Scanner QR via lunettes
|
||||
|
||||
**Déclencheurs :**
|
||||
- Pression courte sur le bouton hardware des lunettes
|
||||
- Commande vocale "Hey MyVisit, scanne ce QR"
|
||||
**Déclencheur :** commande vocale "Hey MyVisit, scanne ce QR"
|
||||
|
||||
⚠️ Le bouton hardware des lunettes n'est **pas** un déclencheur : le SDK DAT ne
|
||||
l'expose pas, et rien n'est implémenté pour lui côté app.
|
||||
|
||||
**Fonctionnement :**
|
||||
1. Les lunettes capturent une photo
|
||||
|
||||
81
test-plan.md
81
test-plan.md
@ -50,6 +50,7 @@ tests fonctionnels ci-dessous.
|
||||
19. [**Parité manager-app → apps visiteur (par type de section)**](#19-parité-manager-app--apps-visiteur-par-type-de-section)
|
||||
20. [Écarts de parité connus — à rejouer après correction](#20-écarts-de-parité-connus--à-rejouer-après-correction)
|
||||
21. [Visite hors ligne — diagnostic terrain](#21-visite-hors-ligne--diagnostic-terrain) ⚠️ **bugs suspectés, à jouer tôt**
|
||||
23. [QR codes, nom d'application, page de téléchargement, proximité](#23-qr-codes-nom-dapplication-page-de-téléchargement-proximité)
|
||||
|
||||
---
|
||||
|
||||
@ -274,14 +275,14 @@ Voir [v2/stats-screen-plan.md](v2/stats-screen-plan.md) § État d'implémentati
|
||||
|
||||
| # | Action | Résultat attendu | ✓ |
|
||||
|---|--------|-----------------|---|
|
||||
| 9.1 | Visitapp en range d'un iBeacon connu | Notification locale `beaconFound` affichée | |
|
||||
| 9.1 | Visitapp en range d'un iBeacon connu | App au premier plan : popup de suggestion, **sans** notification. App en arrière-plan / écran verrouillé : notification locale `beaconFound` (cf. §23) | |
|
||||
| 9.2 | Notification beacon | Titre/corps dans la langue active (clé `beaconFound` / `beaconFoundBody`) | |
|
||||
| 9.3 | Entrée dans une zone geo d'un `GuidedStep` | Notification locale `geoZone` affichée | |
|
||||
| 9.4 | Filtre par `minorId` + `accuracy` + cooldown | Pas de spam de notifications pour le même beacon | |
|
||||
| 9.5 | `beacon_scanner: ^0.0.4` | Scanner opérationnel sur Android et iOS | |
|
||||
| 9.6 | **iOS** — balise émettant un `proximityUUID` **autre** que `FDA50693-A4E2-4FB1-AFCF-C6EB07647825` | Le cas doit **échouer** : la région iOS est filtrée sur cet UUID en dur (`geo_beacon_trigger_service.dart:148-155`). Si ça déclenche quand même, le code ne fait pas ce qu'il dit — Android, lui, scanne sans filtre | |
|
||||
| 9.7 | Déclenchement **proactif de l'assistant** par balise (chemin distinct de 9.1) | Le prompt part depuis `geo_beacon_trigger_service._onBeaconResult`, uniquement si le mode proactif est actif | |
|
||||
| 9.8 | Distance réelle de déclenchement, mètre en main | ⚠️ `configuration_page.dart:43` = `meterToBeacon = 100` alors que le commentaire dit 15 m, et `Section.meterZoneGPS` est ignoré. Noter la distance **mesurée**, pas celle attendue | |
|
||||
| 9.8 | Distance réelle de déclenchement, mètre en main | ⚠️ `proximity_suggestion_service.dart` = `beaconMaxDistanceMeters = 100` (accuracy du ranging). `Section.meterZoneGPS` sert désormais aux zones **GPS**, pas aux beacons. Noter la distance **mesurée**, pas celle attendue | |
|
||||
| 9.9 | Stationner 2 min devant un POI | Une seule notification (cooldown), pas de répétition | |
|
||||
| 9.10 | Balise avec `major` porteur d'un niveau de batterie | Aucun effet sur l'identification du POI — l'app ne lit que le `minorId`. C'est ce qui rend le `major` utilisable pour la télémétrie | |
|
||||
|
||||
@ -747,6 +748,22 @@ Points de vigilance à ne pas oublier au moment de tester :
|
||||
| 21.1.5 | Ouvrir un Quiz avec images de questions/réponses | Les images s'affichent | |
|
||||
| 21.1.6 | Ouvrir une SectionMap téléchargée | Fond de carte, icônes et points présents | |
|
||||
|
||||
### 21.1 bis — Entrée dans le téléchargement depuis la home v3
|
||||
|
||||
> Ajouté le 2026-09-08 avec le portage du téléchargement dans le bento (`home_3.0.dart`). Avant ce portage, une visite hors ligne non téléchargée ouvrait un détail **vide**, sans message.
|
||||
|
||||
| # | Action | Attendu | OK |
|
||||
|---|--------|---------|-----|
|
||||
| 21.1.7 | Ouvrir la home v3 avec une configuration `isOffline` jamais téléchargée | Tuile ternie, pastille `↓` en haut à droite | |
|
||||
| 21.1.8 | Taper la tuile (pas la pastille) | Le dialogue de téléchargement s'ouvre — **pas** un détail vide | |
|
||||
| 21.1.9 | Télécharger, fermer, revenir à la home | La pastille passe au `✓` vert, la tuile n'est plus ternie | |
|
||||
| 21.1.10 | Taper la tuile téléchargée | Le détail s'ouvre **avec ses sections** | |
|
||||
| 21.1.11 | **Relancer l'app** puis regarder les tuiles | ⚠️ Les visites non téléchargées restent en `↓`. Le piège : le fetch écrit une ligne par visite dans la table `configurations` pour cacher `order`/`gridSpan` — si l'état se remettait à se déduire de ces lignes, tout passerait en `✓` au 2ᵉ lancement | |
|
||||
| 21.1.12 | Choisir une langue dans le dialogue, puis télécharger | Seules les ressources de cette langue sont récupérées (sauf mode admin toutes langues) | |
|
||||
| 21.1.13 | Taper la pastille `✓` d'une visite déjà téléchargée | Dialogue de **mise à jour** (`downloadPromptUpdate`), pas de premier téléchargement | |
|
||||
| 21.1.14 | Ouvrir une visite dont les langues ne contiennent pas la langue courante | Snackbar « langue non supportée », pas de navigation | |
|
||||
| 21.1.15 | Désactiver une configuration dans manager-app (interrupteur du lien de canal), relancer la home | La tuile disparaît | |
|
||||
|
||||
### 21.2 — Fraîcheur du contenu
|
||||
|
||||
| # | Action | Attendu | OK |
|
||||
@ -902,6 +919,66 @@ Les comptages ne disent rien des traductions, des médias liés ni de l'ordre. *
|
||||
|
||||
---
|
||||
|
||||
## 23. QR codes, nom d'application, page de téléchargement, proximité
|
||||
|
||||
Prérequis : migration `AddAppNameQrAndStoresToApplicationInstance` appliquée ; visitapp buildée après `flutter clean` (empreinte de `libapp.so` vérifiée).
|
||||
|
||||
### 23.1 — Réglages dans manager-app (écrans Mobile et Web)
|
||||
|
||||
| # | Action | Résultat attendu | ✓ |
|
||||
|---|--------|-----------------|---|
|
||||
| 23.1.1 | Saisir le **nom de l'application** en FR/EN/NL, recharger | Valeurs conservées, par app (Mobile ≠ Web) | |
|
||||
| 23.1.2 | Décocher **Afficher le scan de QR code**, recharger | Case restée décochée | |
|
||||
| 23.1.3 | En **SuperAdmin**, saisir les liens App Store / Play Store (écran Mobile) | Enregistrés | |
|
||||
| 23.1.4 | En **InstanceAdmin** | Les champs de liens stores n'apparaissent pas ; un `PUT` forgé avec d'autres liens ne les modifie pas | |
|
||||
| 23.1.5 | QR d'une section (instance avec app mobile) | Encode `https://app.myinfomate.be/download/{instanceId}/{configId}/{sectionId}` | |
|
||||
| 23.1.6 | QR d'une section (instance **web seule**) | Encode `https://app.myinfomate.be/{slug}/{configId}/sections/{sectionId}` | |
|
||||
|
||||
### 23.2 — Scan dans l'app mobile
|
||||
|
||||
| # | QR scanné | Depuis | Résultat attendu | ✓ |
|
||||
|---|-----------|--------|-----------------|---|
|
||||
| 23.2.1 | Id brut d'une section (QR du **Fort**) | Accueil | La visite s'ouvre directement sur la section | |
|
||||
| 23.2.2 | Id brut d'une section de la visite ouverte | Visite | La section s'ouvre | |
|
||||
| 23.2.3 | Id brut d'une section d'une autre visite | Visite | Popup « autre visite » puis ouverture sur la section | |
|
||||
| 23.2.4 | URL `web.mymuseum.be/…` d'un vrai QR **MDLF** imprimé | Accueil et visite | Même comportement qu'avant | |
|
||||
| 23.2.5 | Nouvelle URL `app.myinfomate.be/download/…` | Accueil et visite | Ouvre la section | |
|
||||
| 23.2.6 | QR d'une **autre instance** | Accueil | « QR code invalide » | |
|
||||
| 23.2.7 | Visite hors ligne, mode avion, id brut | Accueil | Résolu depuis la base locale | |
|
||||
| 23.2.8 | Scan QR désactivé dans le manager | — | Bouton absent sur l'accueil, dans la visite et dans un menu | |
|
||||
| 23.2.9 | Nom de l'app renseigné | — | En-tête de l'accueil = nom de l'app (langue active) ; sans nom : titre de la 1re visite comme avant | |
|
||||
|
||||
### 23.3 — Web (visitapp-web)
|
||||
|
||||
| # | Action | Résultat attendu | ✓ |
|
||||
|---|--------|-----------------|---|
|
||||
| 23.3.1 | Ouvrir `/download/{instanceId}/{c}/{s}` (téléphone, appareil photo) | Image principale, nom de l'app, boutons stores renseignés, textes dans la langue du navigateur | |
|
||||
| 23.3.2 | Même URL sans liens stores | « Bientôt disponible sur les stores » | |
|
||||
| 23.3.3 | Instance sans app mobile | 404 | |
|
||||
| 23.3.4 | Scanner un QR `download` depuis l'app web | Ouvre la section web | |
|
||||
| 23.3.5 | Scan QR désactivé sur l'app **Web** | Bouton absent (accueil et visite) | |
|
||||
| 23.3.6 | Nom de l'app renseigné | Titre sur l'accueil et dans l'onglet | |
|
||||
| 23.3.7 | Visite avec sections géolocalisées, activer le bouton de proximité, forcer la position dans les DevTools | Carte « À proximité » une fois par section et par session, 20 s minimum entre deux | |
|
||||
| 23.3.8 | ⚠️ **Prendre l'`instanceId` d'un vrai QR MDLF imprimé** et ouvrir `/download/{cet id}/…` | La page **de MDLF** s'affiche (son nom, son image, ses liens). Si elle ne trouve rien, l'id a changé à la migration Mongo → Postgres : la redirection ne servirait à rien, à traiter avant de la poser | |
|
||||
| 23.3.9 | Instance sans **nom d'application** renseigné | Le titre retombe sur le nom de l'instance, jamais sur « Application mobile » | |
|
||||
| 23.3.10 | Ancien QR d'une instance **web seule** redirigé vers `/download/…` | Redirection vers la visite web à la bonne section | |
|
||||
|
||||
### 23.4 — Proximité et notifications (mobile, device réel)
|
||||
|
||||
| # | Action | Résultat attendu | ✓ |
|
||||
|---|--------|-----------------|---|
|
||||
| 23.4.1 | Visite avec zones GPS (sans beacon) | Le bouton de proximité apparaît une fois les sections chargées | |
|
||||
| 23.4.2 | Activer, entrer dans une zone (mock location Android), app ouverte | Popup de suggestion, **pas** de notification | |
|
||||
| 23.4.3 | Même chose écran verrouillé | Notification « Contenu à proximité » + titre de la section | |
|
||||
| 23.4.4 | Toucher la notification | L'app s'ouvre sur la section | |
|
||||
| 23.4.5 | Rester dans la zone écran verrouillé | Une seule notification par section et par visite | |
|
||||
| 23.4.6 | Android : pendant le scan | Notification persistante « Suggestions de contenu à proximité activées » ; disparaît en coupant le bouton ou en quittant la visite | |
|
||||
| 23.4.7 | iOS : écran verrouillé | Indicateur de localisation en arrière-plan ; notification reçue (beacon **et** GPS) | |
|
||||
| 23.4.8 | App tuée | Aucune notification (hors périmètre, par choix) | |
|
||||
| 23.4.9 | Mode vocal lunettes sans permission de localisation (Android 14+) | Pas de crash ; le service de premier plan ne démarre pas | |
|
||||
|
||||
---
|
||||
|
||||
## Hors scope (non implémenté)
|
||||
|
||||
| Feature | Statut |
|
||||
|
||||
@ -129,19 +129,49 @@ La ligne `Resource` est créée **avant** l'upload, et l'URL écrite après coup
|
||||
|
||||
## 4. Quota de stockage
|
||||
|
||||
### État actuel : non appliqué
|
||||
### État actuel — relevé dans le code le 2026-09-07
|
||||
|
||||
Le contrôle `StorageQuotaBytes` n'existe que dans l'endpoint legacy `Upload` (chemin base64, plus utilisé). `Create` — le chemin réellement emprunté — **ne vérifie aucun quota et ne renseigne pas `SizeBytes`**.
|
||||
⚠️ **Cette section était en retard sur le code.** Les points 1 et 3 sont livrés, et le point 2
|
||||
tel qu'il était écrit ne peut pas fonctionner. Corrigé ci-dessous.
|
||||
|
||||
### Corrections, dans cet ordre
|
||||
| # | Correction prévue | État réel |
|
||||
|---|---|---|
|
||||
| 1 | Endpoint de pré-vol | ✅ **livré** — `ExceedsStorageQuota` ([ResourceController.cs:966](../../manager-service/ManagerService/Controllers/ResourceController.cs#L966)) résout quota d'instance puis quota de plan, et garde les deux chemins ([:246](../../manager-service/ManagerService/Controllers/ResourceController.cs#L246) multipart, [:330](../../manager-service/ManagerService/Controllers/ResourceController.cs#L330) JSON) |
|
||||
| 2 | Contrôle autoritaire à `Create`, taille lue depuis Firebase | ⛔ **infaisable tel quel** — voir ci-dessous |
|
||||
| 3 | `Delete` doit supprimer le blob | ✅ **codé** — blob avant ligne, 502 qui interrompt plutôt que de créer un orphelin ([:581](../../manager-service/ManagerService/Controllers/ResourceController.cs#L581)). **Mais inerte** : `Firebase:StorageBucket` vaut `""`, donc `IsConfigured` est faux, `DeleteAsync` renvoie `NotConfigured`, et l'appelant ne bloque que sur `Failed`. Verrou = la carte kanban « Poser `Firebase:StorageBucket` » |
|
||||
|
||||
1. **Endpoint de pré-vol** — `POST /api/Resource/check-quota { instanceId, sizeBytes }`, appelé **avant** de pousser dans Firebase. Répond OK / dépassement + octets restants. Purement UX : éviter d'attendre l'upload de 180 Mo pour se faire refuser. Non autoritaire, une course reste possible, sans gravité.
|
||||
### Pourquoi le point 2 ne peut pas être un contrôle synchrone
|
||||
|
||||
2. **Contrôle autoritaire à `Create`, taille lue depuis Firebase.** Ne **jamais** faire confiance au `sizeBytes` envoyé par le client — un appelant qui envoie `0` bypasse le quota définitivement. `FirebaseAdmin` est déjà référencé dans le `.csproj`. En cas de dépassement : supprimer le blob puis renvoyer 413. Sans la suppression, un refus laisse un orphelin qui occupe du stockage sans être compté.
|
||||
manager-app crée la ligne **en annonçant** `sizeBytes`, **puis** téléverse le blob. À l'instant
|
||||
du `Create`, le blob n'existe pas encore : il n'y a rien à interroger dans Firebase. Le
|
||||
commentaire du code le dit déjà.
|
||||
|
||||
3. **`Delete` doit supprimer le blob.** Aujourd'hui il nettoie les références en base et laisse le fichier. Le quota calculé (`SUM(SizeBytes)`) diverge donc du stockage réellement facturé, et l'écart ne fait que croître.
|
||||
Le trou reste réel — `Create` croit le client, donc un appelant qui envoie `0` passe sous le
|
||||
quota définitivement — mais le correctif est une **réconciliation asynchrone**, pas une
|
||||
vérification à l'écriture :
|
||||
|
||||
`SizeBytes` doit être renseigné à la création, pas seulement sur `Update`.
|
||||
- un job Hangfire récurrent (il y en a déjà huit, [Startup.cs:359-401](../../manager-service/ManagerService/Startup.cs#L359)) qui repasse sur les blobs, lit la taille réelle et corrige `SizeBytes`, en signalant les lignes où le déclaré divergeait ;
|
||||
- la logique de sondage existe déjà dans le backfill ([:912](../../manager-service/ManagerService/Controllers/ResourceController.cs#L912)) : il s'agit de la rendre récurrente et de ne plus la limiter à `SizeBytes == 0` ;
|
||||
- il faut ajouter une lecture de taille à `IResourceBlobService`, qui n'expose aujourd'hui que `DeleteAsync` ([ResourceBlobService.cs:32](../../manager-service/ManagerService/Services/ResourceBlobService.cs#L32)).
|
||||
|
||||
### Et le garde-fou déclaratif, qui n'existe pas du tout
|
||||
|
||||
Il n'y a **aucun `storage.rules` dans le repo**. Comme manager-app téléverse directement vers
|
||||
Firebase, une règle `request.resource.size < N` est le seul contrôle qu'un client ne puisse pas
|
||||
contourner — un endpoint de pré-vol l'est par construction. Ça ne plafonne pas le total, mais
|
||||
ça bloque le fichier de 4 Go.
|
||||
|
||||
### Un plafond de taille par bucket n'existe pas — vérifié le 2026-09-07
|
||||
|
||||
Ni chez Google, ni chez OVH. À ne pas rechercher une seconde fois :
|
||||
|
||||
- **GCS / Firebase Storage** : stockage illimité au niveau bucket, seule limite 5 TiB **par objet** ([doc](https://docs.cloud.google.com/storage/quotas)). Le plafond de 1 To concerne les Rapid Buckets zonaux.
|
||||
- **OVH Object Storage S3** : 100 buckets par projet, 48 TiB par objet, nombre d'objets « unlimited », **aucun quota de taille** ([limitations](https://docs.ovhcloud.com/en/guides/storage-and-backup/object-storage/s3-limitations)).
|
||||
- L'ancienne interface **Swift** d'OVH avait un `X-Container-Meta-Quota-Bytes` — c'est Swift, pas S3, et c'est l'offre legacy. Ne s'applique pas.
|
||||
|
||||
Et même s'il existait, ce serait la mauvaise granularité : le quota est **par instance**, alors
|
||||
que tous les clients partagent un bucket préfixé (§ « Un bucket global, préfixé par instance »).
|
||||
Le quota vit en base, il ne peut pas vivre dans le stockage.
|
||||
|
||||
---
|
||||
|
||||
@ -205,7 +235,68 @@ Une fois les tailles réelles connues, certains clients seront peut-être déjà
|
||||
|
||||
### Situation
|
||||
|
||||
Bucket **en Europe**. Or les quotas gratuits Firebase Storage ne s'appliquent qu'aux régions `us-central1`, `us-west1` et `us-east1`. **Il n'y a donc aucun quota gratuit** : la facturation court depuis le premier octet.
|
||||
Bucket **en Europe** — vérifié le 2026-09-07 : `gs://mymuseum-3b97f.appspot.com` est en `EU`, `multi-region`. Or les quotas gratuits Firebase Storage ne s'appliquent qu'aux régions `us-central1`, `us-west1` et `us-east1`. **Il n'y a donc aucun quota gratuit** : la facturation court depuis le premier octet.
|
||||
|
||||
### 📏 Le volume réel, mesuré le 2026-09-07 : **1,43 Go**
|
||||
|
||||
```sh
|
||||
gcloud storage du -s gs://mymuseum-3b97f.appspot.com # 1433427569 octets
|
||||
```
|
||||
|
||||
Ce chiffre était attendu du backfill (§5) alors que **le bucket le connaissait depuis le début**.
|
||||
Le backfill reste nécessaire pour la comptabilité *par client* et l'inventaire des orphelins,
|
||||
mais plus pour dimensionner un stockage ou arbitrer un fournisseur.
|
||||
|
||||
Conséquence directe : les médias tiennent dans le bucket de sauvegarde déjà créé
|
||||
(`gs://unov-myinfomate-backups`, préfixe `media/`), et **aucun second fournisseur n'est
|
||||
nécessaire**. Voir `manager-service/ManagerService/Deployment/backup/README.md`.
|
||||
|
||||
### 🔍 Inventaire des orphelins — première moitié faite le 2026-09-07
|
||||
|
||||
Obtenu en comparant les 2408 objets du bucket aux instances réelles, sans backfill.
|
||||
**~27 objets ne peuvent appartenir à aucun client :**
|
||||
|
||||
| Quoi | Objets | Pourquoi c'est un orphelin |
|
||||
|---|---|---|
|
||||
| `pictures/63514fd67ed8c735aaa4b8f1/` | 19 | L'id finit par **f1** ; l'instance MyInfoMate est `…f2`. Un caractère d'écart — instance supprimée ou faute de frappe historique |
|
||||
| `pictures/3181820d61fb46639d234dc7/` | 2 | C'est MNAHA, qui existe en base **de dev** mais dans aucune instance de la prod Mongo |
|
||||
| Racine du bucket | 6 | `giphy.gif`, `All 24 Cybertruck Accessories Revealed!.mp4`, `file_example_{AVI,MOV,WEBM}`, `video_2023-12-13_14-49-12.mp4` — des fichiers de test |
|
||||
|
||||
Les 6 fichiers de la racine sont le cas le plus net : ils ne suivent pas le schéma
|
||||
`pictures/{instanceId}/{id}`, donc **aucun code actuel ne peut les produire ni les désigner**.
|
||||
Ils sont invisibles au quota et au `StoragePath`, et facturés depuis 2023.
|
||||
|
||||
Répartition des 2402 objets restants, par instance :
|
||||
|
||||
| Instance | Objets |
|
||||
|---|---|
|
||||
| Fort Saint Héribert (`633ee379…`) | 1204 |
|
||||
| VisitNamur (`65c5e576…`) | 711 |
|
||||
| MDLF (`65ccc672…`) | 430 |
|
||||
| MyInfoMate démo (`63514fd6…f2`) | 36 |
|
||||
|
||||
Fort Saint Héribert porte **la moitié du bucket** — cohérent avec l'instance la plus ancienne,
|
||||
et avec le fait qu'elle n'a plus rien modifié depuis avril (voir STATUS.md §4).
|
||||
|
||||
⚠️ À confirmer avant toute suppression : croiser ces 27 objets avec la table `Resources`. Le
|
||||
raisonnement ci-dessus repose sur les préfixes, pas sur les références.
|
||||
|
||||
### ✅ Versioning activé le 2026-09-07 — avant la clé, comme il fallait
|
||||
|
||||
Le bucket n'avait **ni versioning ni aucune règle de cycle de vie**. Les deux sont posés :
|
||||
|
||||
```sh
|
||||
gcloud storage buckets update gs://mymuseum-3b97f.appspot.com --versioning
|
||||
# + lifecycle : {"condition":{"daysSinceNoncurrentTime":30},"action":{"type":"Delete"}}
|
||||
```
|
||||
|
||||
L'ordre importait. Tant que `Firebase:StorageBucket` est vide, `Delete` ne supprime aucun blob
|
||||
et le risque est nul. Le jour où cette clé est renseignée, la suppression devient réelle — un
|
||||
mauvais préfixe ou un bug emporterait les médias d'un client **sans retour**. Le filet est
|
||||
désormais antérieur à ce basculement.
|
||||
|
||||
La purge des versions non courantes à 30 jours évite que les écrasements s'accumulent sans fin.
|
||||
Et le bucket portait déjà une `soft_delete_policy` de 7 jours depuis mars 2024 — seconde couche.
|
||||
|
||||
Tarifs Blaze (bucket legacy `*.appspot.com`, hors quotas gratuits) :
|
||||
|
||||
|
||||
164
v2/plan-illustre-plan.md
Normal file
164
v2/plan-illustre-plan.md
Normal file
@ -0,0 +1,164 @@
|
||||
# Plan illustré — un 3ᵉ fournisseur de carte, sans tuiles
|
||||
|
||||
> **Contexte** : conception du 2026-09-04, déclenchée par la demande du **Fourneau Saint-Michel**
|
||||
> (musée de plein air, plusieurs dizaines de bâtiments, réseau quasi absent). Fait suite à l'analyse
|
||||
> carto du même jour, qui a établi que ni Google ni Mapbox ne donnent une carte hors ligne gratuite.
|
||||
>
|
||||
> Maquette et diagramme : <https://claude.ai/code/artifact/6e8b681e-1697-48e8-b010-d77569c6b9d7>
|
||||
>
|
||||
> ⚠️ **Rien n'est implémenté.** Ce document est la conception, pas un état d'avancement.
|
||||
|
||||
---
|
||||
|
||||
## Pourquoi ce troisième fournisseur existe
|
||||
|
||||
`MapProvider` ne connaît que `Google` et `MapBox`. Les deux chargent leurs tuiles en réseau, et
|
||||
aucun des deux ne donne un fond hors ligne gratuit :
|
||||
|
||||
| Fournisseur | Hors ligne | Coût |
|
||||
|---|---|---|
|
||||
| Google | ❌ Aucune API de tuiles hors ligne dans le SDK Android. Les « zones hors connexion » sont une fonction de l'app grand public, pas du SDK. De plus le code passe par `flutter_map` sur `https://mt1.google.com/vt/…`, un endpoint non documenté dont la mise en cache est interdite | — |
|
||||
| Mapbox | ✅ `OfflineManager` + `TileStore`, présents dans `mapbox_maps_flutter 2.8.0` (version résolue) | ⚠️ **Un tile pack par visiteur** : le compteur monte avec la fréquentation, donc avec le succès du client. Grille à vérifier chez Mapbox |
|
||||
| Plan illustré | ✅ **Par construction** — l'image est une `Resource`, elle part avec la visite | ✅ Aucun tiers. Le coût se déplace sur notre propre stockage : quelques Mo par visite téléchargée, prévisible |
|
||||
|
||||
Le plan illustré est aussi **ce qu'un musée de plein air possède déjà** : un plan dessiné, plus
|
||||
lisible qu'un fond routier sur cinquante bâtiments.
|
||||
|
||||
---
|
||||
|
||||
## La décision qui structure tout le reste
|
||||
|
||||
Un plan de musée est stylisé, pas à l'échelle, rarement orienté au nord. **Ne pas projeter les
|
||||
repères depuis leurs coordonnées GPS** — ils tomberaient à côté des bâtiments dessinés.
|
||||
|
||||
> **Les repères se posent directement sur l'image, à la main, et c'est cette position qui fait foi.**
|
||||
> Le calage GPS ne sert qu'à afficher **le visiteur** — la seule donnée qui traverse la frontière
|
||||
> entre le terrain et le dessin.
|
||||
|
||||
⚠️ **Correction par rapport à la première note du 2026-09-04** : celle-ci disait « deux points
|
||||
d'ancrage suffisent pour projeter un `GeoPoint` sur l'image ». Les deux moitiés étaient fausses —
|
||||
on ne projette pas les repères, et deux points ne suffisent pas (voir plus bas).
|
||||
|
||||
Conséquence directe : **le calage est facultatif**. Un lieu qui ne veut pas de position visiteur
|
||||
téléverse son plan, pose ses repères, et c'est fini. Deux niveaux d'effort, deux produits vendables.
|
||||
|
||||
### Trois points de calage, pas deux
|
||||
|
||||
Deux points donnent une échelle et une translation — suffisant seulement si le plan est à l'échelle
|
||||
et orienté au nord, ce qu'un plan dessiné n'est presque jamais. Trois points donnent une
|
||||
transformation affine, qui absorbe la rotation et l'étirement. Le quatrième n'apporte rien tant
|
||||
qu'on ne veut pas corriger de perspective.
|
||||
|
||||
Raison de terrain en plus : trois croix bien réparties permettent de **vérifier** le calage. Avec
|
||||
deux, toute erreur de relevé se répartit silencieusement sur toute l'image.
|
||||
|
||||
---
|
||||
|
||||
## Modèle de données — additif, aucune colonne existante modifiée
|
||||
|
||||
| Champ | Où | Note |
|
||||
|---|---|---|
|
||||
| `MapProvider.IllustratedPlan` | `DTOs/SubSection/MapDTO.cs` | Troisième valeur, **position 2**. Les lignes existantes gardent 0 et 1, rien à migrer. ⚠️ À répercuter **à la main** dans `manager_api_new/lib/model/map_provider.dart` — la règle « pas de regénération du client » s'applique |
|
||||
| `PlanResourceId` / `PlanResource` | `Data/SubSection/SectionMap.cs` | Exactement sur le modèle de `IconResourceId` / `IconResource`, déjà présents dans la même classe. C'est ce parallèle qui rend le hors ligne gratuit |
|
||||
| `PlanX`, `PlanY` | `GeoPoint` (dans `SectionMap.cs`) | `double?` **normalisés 0→1**, pas des pixels : le plan peut être ré-encodé ou redimensionné sans invalider un repère |
|
||||
| `PlanAnchors` | `Data/SubSection/SectionMap.cs` | `[{planX, planY, latitude, longitude}]` en `jsonb`, comme `MapCategories` l'est déjà. Nul si le lieu ne veut pas de position visiteur |
|
||||
|
||||
---
|
||||
|
||||
## Le geste dans manager-app
|
||||
|
||||
Écran concerné : `lib/Screens/Configurations/Section/SubSection/Map/map_config.dart` (481 lignes).
|
||||
|
||||
1. **Choisir « Plan illustré »** — une valeur de plus dans `map_providers`, servie par le
|
||||
`SingleSelectContainer` existant. Le choix masque les réglages Google/Mapbox et fait apparaître
|
||||
la zone de plan.
|
||||
2. **Téléverser le plan** — `ResourceInputContainer`, déjà utilisé juste à côté pour l'icône.
|
||||
Passe par la médiathèque, le quota et la compression 2560 px livrée en C4.
|
||||
3. **Poser les repères** — le plan s'affiche, on clique, un repère apparaît ; la liste des
|
||||
`GeoPoint` existants est à côté et on y fait glisser un point déjà rempli. **Seul écran vraiment
|
||||
neuf du chantier.**
|
||||
4. **Caler** (facultatif) — voir ci-dessous.
|
||||
|
||||
### Le calage se fait assis, en deux clics par ancre
|
||||
|
||||
**Personne ne va relever de coordonnées sur le terrain.** Le geste est : *cliquer le même angle de
|
||||
bâtiment deux fois* — une fois sur le plan dessiné, une fois sur une carte réelle affichée juste à
|
||||
côté. L'outil déduit les coordonnées du second clic. Aucun chiffre n'est jamais tapé.
|
||||
|
||||
✅ **Le composant existe déjà** : `GeolocInputContainer` (`lib/Components/geoloc_input_container.dart`)
|
||||
ouvre un `FlutterLocationPicker` — vraie carte, recherche d'adresse, centrée sur Namur
|
||||
(50.429333, 4.891434) par défaut. C'est déjà lui qui sert à poser le point central d'une section
|
||||
carte. Le calage n'invente rien : il appelle deux fois le même composant dans un écran qui les met
|
||||
en regard.
|
||||
|
||||
⚠️ **i18n obligatoire** : tout texte de cet écran passe par `AppLocalizations`, FR/EN/NL.
|
||||
|
||||
---
|
||||
|
||||
## Le rendu — aucun SDK de carte
|
||||
|
||||
Une image, des repères positionnés en pourcentage par-dessus, un conteneur qui zoome.
|
||||
|
||||
- **`mymuseum-visitapp`** : une `PlanView` à côté de `GoogleMapView` et `MapBoxView`
|
||||
(`lib/Screens/Sections/Map/`). `InteractiveViewer` enveloppant un `Stack` donne le pincer-zoomer
|
||||
et le déplacement sans écrire une ligne.
|
||||
- **`visitapp-web`** : le même rendu en CSS (`transform`). À écrire **après** le Flutter, qui reste
|
||||
la référence de comportement.
|
||||
|
||||
Ni `flutter_map`, ni `google_maps_flutter`, ni `mapbox_maps_flutter` : donc ni jeton, ni quota, ni
|
||||
compte à surveiller.
|
||||
|
||||
La position du visiteur est un repère de plus, calculé à l'affichage par la transformation affine,
|
||||
et **masqué dès qu'il sort du cadre du plan** — un visiteur au parking ne doit pas voir son point
|
||||
collé au bord de l'image.
|
||||
|
||||
Le GPS lui-même ne pose pas de problème : `geolocator ^13.0.0` lit le GNSS, autonome sans réseau.
|
||||
Seule réserve à annoncer au client : sans A-GPS, le premier point peut demander 30 à 60 s sous
|
||||
couvert forestier.
|
||||
|
||||
### Ce que le hors ligne gagne
|
||||
|
||||
Le plan est une `Resource`. Une ligne dans `SectionMap.GetReferencedResourceIds()`, à côté de
|
||||
`ResourceId(IconResourceId)`, et le pipeline de téléchargement existant l'embarque — **aucun
|
||||
mécanisme neuf**. `SectionType.Map` peut alors entrer dans `offlineCapableSectionTypes`
|
||||
(`mymuseum-visitapp/lib/Services/downloadConfiguration.dart`) pour ce fournisseur.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ La conséquence côté web
|
||||
|
||||
`mapProviderMobileOnlyNote` prévient déjà le client que le fournisseur de carte ne vaut que pour le
|
||||
mobile et la borne, parce que **`visitapp-web` reste sur Leaflet** (décision du lot E, W1, le
|
||||
2026-08-11). Pour un plan illustré cette note devient gênante : le plan **est** le produit, pas un
|
||||
réglage de fond.
|
||||
|
||||
Deux issues, à trancher : porter le rendu en web dans la foulée (c'est du CSS, quelques heures), ou
|
||||
assumer que le plan est une fonctionnalité d'app mobile et le dire à la vente. ⚠️ Le plan
|
||||
**Essentiel étant web-only**, la seconde option lui interdit le plan illustré.
|
||||
|
||||
---
|
||||
|
||||
## Ordre d'exécution
|
||||
|
||||
`manager-service` → `manager_api_new` → `manager-app` → `mymuseum-visitapp` → `visitapp-web`.
|
||||
Rien ne peut être configuré tant que le modèle n'existe pas.
|
||||
|
||||
| Repo | Travail | Ampleur |
|
||||
|---|---|---|
|
||||
| `manager-service` | Enum, 4 champs, une migration, une ligne dans `GetReferencedResourceIds` | petit |
|
||||
| `manager_api_new` | Enum et champs répercutés **à la main** | petit |
|
||||
| `manager-app` | Écran de pose des repères et du calage (le picker de carte est déjà écrit) | **le gros morceau** |
|
||||
| `mymuseum-visitapp` | `PlanView` + projection GPS | moyen |
|
||||
| `visitapp-web` | Le même rendu en CSS | moyen |
|
||||
|
||||
### Un raccourci pour valider vite
|
||||
|
||||
Une `PlanView` dans `mymuseum-visitapp` avec un plan et des repères **en dur** valide le rendu et le
|
||||
pincer-zoomer en une heure, avant d'engager la migration. À faire si on veut voir quelque chose
|
||||
bouger avant de toucher au schéma.
|
||||
|
||||
### Ce qui peut être livré en premier et se vendre seul
|
||||
|
||||
**Le plan sans calage** : téléversement, pose des repères, rendu, hors ligne. Ni transformation
|
||||
affine, ni position visiteur — et un musée de plein air a déjà le produit complet que les
|
||||
audioguides vendent depuis trente ans. Le calage GPS se greffe après sans rien casser.
|
||||
Loading…
x
Reference in New Issue
Block a user