DOCS/v2/section-form-plan.md
Thomas Fransolet 556391cf59 Docs en attente : plan SectionForm, cartes 020 (règles Storage) et 285 (loader)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011VxSQeGQYUvPmSEoGdnidA
2026-09-15 20:09:53 +02:00

8.4 KiB

SectionForm — formulaires visiteurs

Chantier V2, reporté le 2026-08-07, analysé dans le code le 2026-09-15. Spec d'origine : todo-features.md § SectionForm · carte kanban cards/5-planifie/240-… Ce document ne rouvre pas la décision « V2 » : il dit comment on le fait quand on le fait.


Pourquoi ce type de section

Tout le flux produit va aujourd'hui du lieu vers le visiteur. Ce qui remonte est dérivé : VisitEvent (télémétrie anonyme) et VisitorQuestion (ce qui a été demandé au guide IA). Aucun mécanisme ne permet à un lieu de poser une question.

Use cases visés, dans l'ordre de valeur commerciale : enquête de satisfaction (les musées publics en produisent pour leurs rapports de subsides), livre d'or, sondage d'exposition, inscription à un atelier.


Décisions actées

Décision Conséquence
Réponses anonymes, sans compte ni identification Pas de champ « nom », pas de liaison à une personne. Ça allège massivement le volet RGPD — on reste sur le régime déjà décrit au §8 des CGU
Les trois fronts visiteur, kiosk compris mymuseum-visitapp, visitapp-web, tablet-app — une borne en fin de parcours est le meilleur emplacement pour une enquête de satisfaction
Table de réponses dédiée, pas VisitEvent.Metadata Rétention propre, export, suppression ciblée, requêtes par champ
Les réponses ne sont jamais indexées pour le RAG Seuls les libellés des questions le sont
Pas d'upload de fichier en V1 Les règles Firebase Storage de prod sont encore ouvertes en écriture — mauvais moment pour ajouter une porte d'upload public

Ce qui sert de socle (déjà écrit)

La moitié de la plomberie existe. Trois précédents à reprendre plutôt qu'à réinventer :

  1. StatsController.TrackEvent (Controllers/StatsController.cs) — le seul endpoint [AllowAnonymous] où un visiteur écrit en base. C'est exactement le contrat de soumission d'un formulaire : modèle à copier, y compris la validation défensive.
  2. SectionQuiz + QuizQuestion (Data/SubSection/) — structurellement un formulaire : liste ordonnée de questions traduites en jsonb, options en jsonb, un QuestionType. SectionForm c'est le même squelette moins la bonne réponse, plus le stockage.
  3. VisitorQuestionPurgeService (Services/) — le job de purge par rétention existe et tourne : FormSubmission s'y branche sans rien inventer.

Et le coût d'ajout d'un type de section est balisé : Scene3D, le dernier ajouté, a touché 11 fichiers backend et 8 dans manager-app. La partie réellement neuve ici, c'est l'écran de réponses, qui n'a aucun équivalent.


Modèle de données

DTOs/SectionType.cs
    Form                            ← ajouté EN FIN d'enum
                                      (persisté en int : cf. le commentaire de Scene3D)

Data/SubSection/SectionForm.cs  : Section
    List<FormField> Fields
    List<TranslationAndResourceDTO> IntroText          jsonb
    List<TranslationAndResourceDTO> ConfirmationText   jsonb
    DateTime? ClosingDate
    int RetentionDays

Data/SubSection/FormField.cs
    Label        jsonb traduit  (même forme que QuizQuestion.Label)
    Order, FieldType, IsRequired
    Options      jsonb          (pour single_choice / multiple_choice)

Data/FormSubmission.cs
    Id, InstanceId, SectionId, ConfigurationId
    SessionId, Language, Answers jsonb, Timestamp
    [Index(InstanceId)] [Index(SectionId)]

FormSubmission vit à la racine de Data/, avec VisitEvent et VisitorQuestion — c'est de la donnée de visite, pas du contenu éditorial.

Types de champs — V1 volontairement courte

text (court), long_text, single_choice, multiple_choice, rating (1-5).

Pas d'email, pas de date, pas d'upload. Les quatre premiers couvrent l'enquête de satisfaction et le sondage, qui sont les deux vrais cas.


Backend

  • DTOs/SubSection/FormDTO.cs + branchement dans Services/SectionFactory.cs (le switch de désérialisation et le switch de construction).
  • Controllers/SectionFormController.cs, sur le modèle de SectionScene3DController :
    • CRUD admin, authentifié ;
    • POST /api/SectionForm/submit en [AllowAnonymous] ;
    • GET /api/SectionForm/{id}/submissions (admin) — liste + agrégats ;
    • GET /api/SectionForm/{id}/export — CSV ;
    • DELETE /api/SectionForm/{id}/submissions — purge manuelle.
  • GetEmbeddableText : les libellés de questions oui, les réponses jamais. Le raisonnement est déjà écrit dans SectionQuiz (« le guide les réciterait au premier visiteur qui demande ») ; ici ce serait pire — le guide recracherait les avis d'autres visiteurs, y compris désobligeants, à un visiteur suivant.
  • L'endpoint de soumission est ouvert : rate-limit par SessionId/IP (la politique UseRateLimiter() existe déjà depuis le chantier API Keys), respect de ClosingDate, et cap de soumissions par session.

manager-app

Fichiers à toucher, identifiés sur le précédent Scene3D :

Fichier Nature
lib/client.dart + manager_api_new/ client généré, édité à la main
lib/Screens/Configurations/new_section_popup.dart entrée « Formulaire »
lib/Components/fetch_section_icon.dart icône du type
lib/Screens/Configurations/Section/section_detail_screen.dart routage vers l'éditeur
lib/Screens/Configurations/Section/SubSection/Form/form_config.dart neuf — constructeur de formulaire
lib/Screens/Configurations/Section/SubSection/Form/form_submissions.dart neuf — onglet réponses
lib/l10n/app_{fr,en,nl}.arb i18n obligatoire, aucun littéral dans un widget

Où vivent les réponses : dans un onglet de l'écran de la section, pas dans Screens/Statistics/. L'admin qui ouvre son formulaire veut ses réponses sous les yeux ; Statistics répond à une autre question (la fréquentation).

Contenu de l'onglet : en-tête d'agrégats (nombre de réponses, moyenne des rating, répartition des choix en barres horizontales — la charte de stats-screen-plan.md s'applique), puis la liste des textes libres, puis l'export CSV.


Fronts visiteur

Repo Fichier Note
mymuseum-visitapp lib/Screens/Sections/Form/form_page.dart à côté de Quiz/quizz_page.dart
visitapp-web src/components/sections/FormSection.tsx obligatoire — le plan Essentiel est web-only. Lire le Flutter d'abord, c'est la référence de comportement
tablet-app lib/Screens/Form/form_view.dart à côté de Screens/Quizz/quizz_view.dart

Le point d'attention propre au kiosk

Sur une borne fixe, le visiteur suivant hérite de l'écran du précédent. Même anonyme, une saisie libre à moitié écrite qui reste affichée est un défaut visible. Il faut donc, sur tablet-app uniquement : reset du formulaire après soumission et reset sur inactivité, sur le même timer que celui qui ramène déjà la borne à l'accueil.


RGPD — allégé, pas nul

L'anonymat retire le gros du sujet : pas de personne identifiée, donc pas de droit d'accès à exercer par visiteur. Restent deux points, tous deux déjà traités ailleurs dans le produit :

  • La saisie libre peut contenir des données personnelles spontanées. C'est exactement ce que le §8 des CGU écrit déjà pour le champ de question du guide IA — la même clause couvre ce cas, il suffit de l'étendre au formulaire lors de la relecture juridique déjà planifiée (carte 195-faire-valider-les-cgu-par-un-juriste).
  • Rétention : RetentionDays paramétrable + purge automatique via le service existant.

Hors périmètre V1 du chantier

Logique conditionnelle entre champs, pages multiples, notification email à chaque réponse, upload de fichier, réponses liées à un visiteur identifié. Le risque de ce chantier est de glisser vers « on refait Google Forms » — or la valeur ici n'est pas la richesse de l'éditeur, c'est d'être déclenchable dans le parcours de visite (fin d'un parcours guidé, beacon de sortie, borne de fin).


Positionnement commercial

À réserver aux plans Pro et supérieurs : la collecte de retours a une valeur propre, et le plan Essentiel est déjà le plan « vitrine ». À arbitrer avec la grille de myinfomate-landing, qui reste la source de vérité des prix.