Quand l’implémentation remet tout à plat : 6 pivots de conception (partie 2/3)
La partie 1 de cette série décrivait la conception initiale : un questionnaire en deux phases, une formule de scoring contextuel et un stack de sept composants hébergés en propre. Tout était posé sur le papier, l’architecture tenait la route, l’estimation était à 10-15 heures.
Ce qui suit, c’est ce qui s’est passé pendant l’implémentation. Six points ont évolué par rapport au plan initial, chacun pour une raison technique précise. Aucun de ces pivots n’était une correction d’erreur, c’étaient des décisions d’architecture prises au contact du réel.
Sommaire :
Pivot 1 : une collection devient sept tables
Le plan initial prévoyait une seule table PocketBase (base de données) appelée audit_questions. Chaque ligne devait contenir tout ce qui concerne une question : son libellé, son poids, l’action recommandée en cas de réponse négative, la piste de réflexion, le lien vers l’article associé, la règle du flag contextuel dérivé.
En pratique, cette structure posait un problème de couplage. Modifier le libellé d’une action sans toucher à la définition de la question était impossible. Ajouter un modificateur contextuel pour un nouveau profil d’entreprise obligeait à modifier la ligne de question elle-même. Et le moteur Python devait embarquer la logique de quel champ appartient à quelle responsabilité.
La collection a été découpée en sept tables distinctes :
| Table | Responsabilité |
|---|---|
questions | Définition de la question : libellé, phase, ordre, format de réponse |
audit_actions | Action recommandée + piste de réflexion + article lié |
audit_flags | Règles contextuelles dynamiques, évaluées par la matrice de décision |
audit_modifiers | Modificateurs de score par flag actif |
audit_template_vars | Mapping variable -> chemin pour le rendu du PDF |
question_field_options | Référentiel des phases et formats de réponse possibles |
audit_run_drafts | Brouillons de saisie en cours (app admin) |
Ce découpage a une conséquence directe sur la maintenabilité : modifier une action recommandée ne touche pas à la question. Changer un poids de modificateur ne nécessite pas de migration. Ajouter un nouveau type de format de réponse s’ajoute dans question_field_options sans modifier le code.
C’est aussi ce découpage qui rend le moteur Python entièrement générique, point développé dans le pivot suivant.
Pivot 2 : le moteur passe de spécifique à générique
Le plan initial décrivait un script Python (langage de programmation) avec la logique de scoring du diagnostic écrite en dur. Les modificateurs contextuels étaient des conditions pré-définies statiquement dans le code, les seuils de priorité étaient des constantes et le rendu HTML dépendait du template de l’audit SI spécifiquement.
Le moteur livré ne contient aucune logique métier codée en dur. Il lit entièrement sa configuration depuis la base de données PocketBase au moment de l’exécution :
- les questions et leur phase depuis
questions - les règles de flags depuis
audit_flags.flag_rule_json, évaluées dynamiquement avec un moteur de règles récursif supportant les opérateurs égal/différent, et/ou, contient… - les modificateurs depuis
audit_modifiers - les variables de template depuis
audit_template_vars - le template HTML lui-même, téléchargé depuis le fichier attaché à l’enregistrement
audits
La conséquence pratique : déployer un second audit (maîtrise des risques IA, diagnostic cybersécurité de base, audit processus pour une mission client) revient à créer un enregistrement dans audits, y associer ses questions et ses modificateurs, et uploader un template HTML. Le workflow Shuffle, le moteur Python, l’application admin, l’envoi email, l’intégration Twenty CRM : rien ne change.
Ce n’était pas l’objectif initial. C’est l’objectif qui s’est imposé pendant l’implémentation, parce que le découpage en sept tables rendait la généricité accessible sans surcoût.
La couverture de tests a suivi la même logique : 84 tests unitaires répartis sur 12 classes, couvrant toutes les fonctions pures du moteur (évaluation des règles, calcul des scores, résolution des variables de template, génération du radar SVG). Avec l’IA pour la rédaction du code de test et la stratégie d’import, ces tests ont été produits sans effort notable. Ils restent à jour au fil des modifications.
Vous réfléchissez à automatiser un processus métier ?
30 minutes pour évaluer la faisabilité et identifier les bons outils selon votre contexte.
Pivot 3 : le template HTML change deux fois de forme
Le plan initial stockait le template HTML du rapport PDF dans un champ editor de PocketBase (un champ texte riche, pratique pour l’édition dans l’interface d’administration).
Problème découvert à l’implémentation : PocketBase encode les accolades lors du stockage et de la récupération. Un mot-clé {{nom_entreprise}} dans le template devenait {{nom_entreprise}} en base et la substitution dans le code du moteur ne trouvait plus rien à remplacer.
Premier correctif : passer le template en champ “fichier” (une page web attachée à l’audit), téléchargé par le moteur via l’API avant substitution. Le problème d’encodage disparaît.
Deuxième problème, découvert plus tard : Shuffle (l’automate) tronque silencieusement les grandes valeurs lors des échanges entre nœuds. Le template HTML était trop grand et passer cette valeur d’un nœud à l’autre provoquait une troncature sans message d’erreur. Les substitutions semblaient fonctionner mais des blocs du PDF étaient vides.
La solution retenue : le second nœud Python relit le template directement depuis la base de données, sans dépendre de ce qu’il se passe avant. Seules des chaînes courtes et des structures encodées transitent entre les nœuds.
Troisième ajustement, lié au premier : la syntaxe des variables a migré de {{var}} vers __var__ (double underscore). Les accolades présentaient un risque d’encodage résiduel selon les contextes. Le double underscore ne souffre d’aucun traitement spécial dans les langages de programmation utilisés.
Pivot 4 : la limite OpenSearch n’a pas été résolue en découpant le code
Ce point mérite une correction par rapport à ce qu’on pourrait lire dans certains comptes rendus du projet : le découpage du script Python en deux nœuds Shuffle n’a pas résolu la limite OpenSearch.
Shuffle repose sur une version d’OpenSearch (une base de données spécialisée sur la recherche dans des grands volumes de données) pour indexer les résultats d’exécution. OpenSearch refuse d’indexer des valeurs dépassant 32 766 bytes. Le HTML généré par le moteur et le PDF produit par Gotenberg dépassent chacun cette limite. Découper le code en deux nœuds déplace le problème, il ne le supprime pas.
La solution fonctionnelle : désactiver l’indexation pour les champs concernés via un index template OpenSearch. Ce contenu n’a pas besoin d’être indexé : le script Python est versionné (conservation des modifications) dans le projet et Shuffle conserve les données de chaque exécution pour le rejeu en cas d’erreur. L’indexation en texte du code source à chaque run n’apporte rien ici.
C’est aussi cet épisode qui illustre pourquoi Shuffle a été conservé malgré ses contraintes. Ces limites sont maintenant documentées et contournées. Introduire un nouvel outil d’orchestration aurait signifié découvrir d’autres contraintes, sans bénéficier de la base de connaissance déjà constituée sur Shuffle.
Pivot 5 : le formulaire Formbricks a été créé par script, pas à la main
Le plan initial prévoyait de créer le formulaire directement dans l’interface Formbricks. 29 questions à saisir, avec les bons identifiants de champs pour que le webhook corresponde aux question_id de PocketBase.
La saisie manuelle a rapidement montré ses limites, pas uniquement pour des raisons de volume. L’API de gestion Formbricks utilise un modèle différent de celui attendu. Ce modèle a été découvert par inspection directe d’un formulaire existant. Les types de questions qui semblaient adaptés au questionnaire ne permettent pas de forcer l’identifiant associé, ce qui complique grandement la correspondance avec l’interface de réponse aux questions.
La solution retenue : utiliser un type de question basique (avec choix oui/non) permettant de positionner l’identifiant du bloc sur le question_id correspondant de PocketBase. Le mapping est direct, sans aucune couche de traduction.
Un script Python lit les questions depuis PocketBase et appelle l’API de gestion Formbricks pour créer le formulaire complet. Le formulaire est reproductible, versionnable et recréable en quelques secondes si nécessaire.
Un projet d'automatisation en tête ?
Échange de 30 minutes pour évaluer la faisabilité et les bons outils selon votre contexte.
Pivot 6 : une application admin complète, non prévue au départ
Le plan initial prévoyait un formulaire public sur oguhnas.fr/diagnostics/serenite/ et un workflow Shuffle qui traite les réponses. L’application admin n’était pas dans le périmètre.
Elle a été construite parce que le besoin est apparu pendant le développement : tester le workflow sans passer par la page publique, saisir un diagnostic pour un prospect en réunion, rejouer un diagnostic sur un profil différent pour vérifier le rendu. Un formulaire public ne couvre pas ces usages.
L’application regroupe deux modules principaux. Le premier est le formulaire de diagnostic : saisie guidée en quatre écrans (avec auto-sauvegarde à chaque réponse et bannière de reprise au rechargement de l’application) :
- Sélection de l’audit et coordonnées
- Phase 1 : questions techniques par catégorie
- Phase 2 : personnalisation du contexte, question par question
- Récapitulatif avant envoi
Le second est le module de paramétrage : modifier une question, ajuster un poids de modificateur, changer une action recommandée ou uploader un nouveau template HTML se fait depuis cette interface, sans toucher à la base de données directement.
Ce module de paramétrage est ce qui concrétise la généricité du moteur. Ajouter un audit sur la maîtrise des risques IA, c’est ouvrir l’onglet Audits, créer un enregistrement, renseigner les questions dans l’onglet Questions, leurs modificateurs dans Modificateurs, et uploader le template dans le champ prévu. Le reste de l’application ne change pas.
Ce que ces six pivots ont en commun
Aucun de ces pivots n’était une erreur de conception initiale. La conception initiale était raisonnable avec les informations disponibles au moment du cadrage. Les pivots sont apparus au contact des outils réels, de leurs contraintes effectives et des besoins qui ont émergé pendant le développement.
C’est précisément ce que l’itération produit : des décisions prises au bon moment avec les bonnes informations. Une conception trop figée en amont aurait verrouillé des choix qui se seraient révélés sous-optimaux.
La partie 3 décrit le produit final — ce qu’il fait, ce qu’il permet, et ce qui reste à construire.
Votre SI mérite un regard extérieur
Échange de 30 minutes pour identifier vos vraies fragilités, sans engagement.
Prendre rendez-vous