Projet client · anonymisé · 2026
Plateforme d'interventions terrain
Un système d'exploitation terrain pour une entreprise de charpente-couverture — construit pour des toits sans réseau, puis cassé par eux. Chaque panne rencontrée est devenue une règle qu'une machine revérifie.
- Rôle
- Conception produit, UX terrain, développement full-stack
- PWA
- JavaScript
- Service Worker
- Supabase
- PostgreSQL
- RLS
- GPS
- Python
- FastAPI
- Claude
- Gmail
- Microsoft Graph
- Résultat
- 69 tests d'invariants, chacun né d'un bug de production. Le bureau voit l'intervention le jour même au lieu de trois jours plus tard.
Stack
Le métier, avant que tout ça existe
Un couvreur monte sur un toit, ouvre une feuille de papier, et note ce qu’il trouve : l’état du faîtage, des rives, de la couverture, des solins, de chaque conduit qui traverse. Il photographie ce qui compte. Il redescend. Trois jours plus tard, quelqu’un au bureau ressaisit la feuille dans un ordinateur, le rappelle pour lui demander ce qu’un mot voulait dire, et en fait un rapport que le client va lire.
Tout le problème est là, et ce n’est pas un problème de saisie. L’information naît au seul endroit où il n’y a pas de réseau, sous des gants, sur un écran illisible en plein soleil, et elle doit survivre au trajet du retour — parce qu’au bout, un client reçoit un document, et ce document doit dire ce que le couvreur a réellement vu.
Une application de saisie ordinaire ne survit pas à ce trajet. Elle suppose que le réseau est là au moment où on enregistre, que personne d’autre ne modifie la même fiche, et qu’une réponse verte veut dire que la donnée est arrivée. Sur un toit, les trois sont faux.
Les quatre conditions qui ont tout décidé
Ce ne sont pas toutes les contraintes. Ce sont celles qui ont chacune imposé une décision de structure — le reste n’était que du détail.
Contrainte → décision
Ce que le terrain impose
- L'absence de réseau est le cas NORMAL, pas l'exception — caves, toits, chantiers isolés
- Des téléphones Android durcis de plusieurs années, dont l'appareil photo refuse souvent de s'ouvrir depuis un navigateur
- Deux personnes modifient la même fiche en même temps : le technicien hors-ligne sur le toit, le bureau en direct
- Ce qui sort est un document que lit un client — ce qui entre doit ressortir
Ce que ça force dans l'architecture
- Toute saisie se pose d'abord sur le téléphone et rejoint une file ; le réseau n'est jamais sur le chemin critique
- La prise de vue vit DANS l'application, par le navigateur, jamais déléguée au téléphone
- Fusionner champ par champ, avec une règle écrite de qui gagne — et un conflit jamais silencieux
- Un test qui vérifie la FORME de la sortie ne suffit pas : il doit vérifier que l'entrée en ressort
Comment tout ça tient ensemble
Les quatre parties, et la règle de chaque passage
Le terrain
- Le téléphone du technicien
- Relevé, photos, croquis, position
- Fonctionne en mode avion
Rien ne part sans passer par la file locale
La donnée
- Une base par entreprise, cloisonnée ligne à ligne
- Les photos dans un stockage privé
- La fusion se fait champ par champ
Le bureau lit la même ligne, en direct
Le bureau
- Carte des équipes, compteurs, planning
- Annuaire des lieux et des donneurs d’ordre
- Le rapport client en PDF
L’agent propose, il n’écrit jamais
L’automatisation
- Deux boîtes mail relues chaque minute
- Devis, factures, réclamations extraits des PDF
- Tout finit dans une file de propositions
Le trajet d’un relevé
Du toit au rapport du client
- 01 Sur le toit Le technicien remplit, photographie, valide. Quinze étapes au maximum, et seulement celles qui concernent ce type d'intervention.
- 02 Écrit sur le téléphone Immédiatement — la fiche dans le stockage local, les photos dans une base locale à elles.
- 03 La file La modification attend son tour, avec un numéro de révision qui change à chaque retouche.
- 04 Le réseau revient La file se rejoue dans l'ordre. Avant chaque écriture, elle relit la ligne du serveur et fusionne champ par champ.
- 05 Le bureau Carte en direct, compteurs du jour, planning du mois — et le rapport client en PDF, en un geste.
À côté, un agent surveille deux boîtes mail : devis signés, factures et réclamations étaient jusque-là ressaisis à la main dans les fiches clients. Il les lit, ouvre les PDF, en extrait les chiffres — et s’arrête. Il n’écrit jamais dans la base vivante. Tout ce qu’il produit atterrit dans une file où une personne relit et clique.
Signed quote — Oakwood HOA
- Client
- Oakwood HOA ▾
- Quote no.
- DV 2845
- Amount
- $5,078.50 excl. tax
- Address
- 1420 Cedar Ridge Dr, Austin
- ZIP
- 78701
- Unit
- APT 42
- Text to copy
- DV 2845 - OAK / BC-883603
Là où ça a cassé
Ce qui est intéressant dans ce projet, ce n’est pas qu’il ait été construit. C’est qu’il est passé entre les mains de gens sur des toits, qu’il a cassé d’une manière que la conception n’avait pas imaginée, et que chaque casse a laissé une règle derrière elle. Ce qui suit n’est pas une liste de bugs : ces quatre-là sont ceux qui ont changé la façon dont le système est bâti.
1 — « je suis revenu et tout était reparti au début »
Un technicien a perdu un rapport déjà rempli
Remonté d'un chantier, en juillet
- Ce qu’on croyait
- Le brouillon était sauvé à chaque saisie et à chaque photo. Le travail était donc à l'abri : revenir dans l'application allait forcément le retrouver.
- Ce qui s’est passé
- Il est sorti de l'application pendant un rapport. En revenant, il était sur l'accueil, au début, et les photos du brouillon avaient disparu aussi.
- Pourquoi
- Cinq défauts, dont aucun n'était inacceptable pris seul, se sont enchaînés : un cache qui servait une version périmée, un stockage de photos devenu privé, une course entre deux opérations, des adresses d'images expirées, et une synchronisation fragile. Le brouillon existait bien — mais le rouvrir demandait de taper un bouton qu'il n'a jamais vu, et la photo était encore en train de s'écrire dans la base locale quand il a quitté l'app.
- Ce qu’on a changé
- Rouvrir l'application sur un brouillon actif rouvre maintenant le rapport à l'étape sauvée, sans bouton à trouver. La photo est écrite dans le stockage local ET attendue avant que le brouillon qui la référence soit sauvé. Et le brouillon est sauvé à la SORTIE de l'application, plus seulement à l'entrée.
- La preuve
- La chaîne est désormais couverte par des tests d'invariants qui lisent la source elle-même, et la version en ligne se vérifie en allant chercher le service worker déployé plutôt qu'en faisant confiance au déploiement.
2 — toutes les photos, d'un coup, pour tout le monde
Toutes les photos ont disparu, et rien n'était perdu
Signalé comme « pas de photos sur une intervention », en juillet
- Ce qu’on croyait
- Le stockage des photos est privé, donc l'app signe des adresses temporaires pour les afficher — et une adresse publique était gardée à côté, en repli, au cas où.
- Ce qui s’est passé
- Toutes les photos ont disparu d'un coup, pour tous les utilisateurs. Pas une intervention : toutes, sans aucune erreur à l'écran.
- Pourquoi
- Sur un stockage privé, une adresse publique répond par une erreur — le repli était mort, et l'était depuis le début. Toutes les adresses étaient signées en un seul appel groupé dont l'échec était avalé. Un seul hoquet passager suffisait donc à vider l'affichage entier, sans recours — alors que chaque fichier et chaque ligne étaient intacts.
- Ce qu’on a changé
- Les adresses signées valides sont conservées pour survivre à un rechargement, l'affichage se reconstruit à partir d'elles au démarrage, et la signature se fait par petits lots isolés : un lot qui échoue ne prive plus que de lui-même.
- La preuve
- Un diagnostic en trois couches, écrit et rejouable : la ligne existe-t-elle, le fichier existe-t-il, la signature fonctionne-t-elle. Si les trois répondent oui, rien n'est perdu — et la réponse est dans l'affichage, pas dans la donnée.
3 — la file qui mangeait ce qu'on venait de taper
Une saisie faite pendant une synchronisation disparaissait sans trace
Trouvé par un audit adverse, en juillet
- Ce qu’on croyait
- Envoyer les fiches en attente une par une, puis réécrire ce qui reste. Simple, et ça marchait depuis des mois.
- Ce qui s’est passé
- Tout ce qui était tapé pendant une synchronisation pouvait s'évaporer. Le serveur avait une version, l'écran en affichait une autre, et plus aucune tentative n'existait pour rattraper.
- Pourquoi
- Lire la file, envoyer sur le réseau et réécrire la file ne sont pas un seul instant : le réseau est au milieu. Réécrire le reste écrase une file qui a bougé entre-temps. Pire : le moyen évident de le détecter, l'horodatage existant, était volontairement préservé au ré-enfilage pour permettre la détection de conflit. Il ne pouvait pas voir un changement de contenu.
- Ce qu’on a changé
- La vidange n'écrase plus, elle réconcilie. Chaque entrée porte un compteur de révision qui change à CHAQUE retouche ; à l'acquittement, la file est relue et une issue n'est appliquée que si la révision correspond encore. Ce qui a été enfilé pendant la vidange est préservé tel quel.
- La preuve
- La règle de fusion vit entre deux marqueurs dans la source, et un script teste exactement cette zone — déplacer le code sans déplacer le test se voit.
4 — une réponse verte qui ne faisait rien
Un bouton qui répondait « c'est fait » depuis des semaines, et ne supprimait rien
Trouvé en écrivant un test contre la vraie base
- Ce qu’on croyait
- La base isole les données entreprise par entreprise, ligne par ligne. Les règles étaient écrites et relues, donc elles s'appliquaient.
- Ce qui s’est passé
- Un bouton de remise à zéro annonçait la réussite à chaque fois et ne retirait rien. Pendant des semaines. Personne ne l'a vu, parce que rien n'échouait.
- Pourquoi
- Quand une suppression ne correspond à aucune règle de sécurité, la base ne refuse pas : elle répond « pas de contenu, tout va bien » et ne retire aucune ligne. Le succès et l'interdiction silencieuse se ressemblent parfaitement vus de l'extérieur.
- Ce qu’on a changé
- L'isolation ne se vérifie plus en relisant les règles. Un contrôle se connecte avec un vrai compte de technicien sur la vraie base, crée une fiche jetable, et vérifie ce que ce compte peut atteindre et ce qu'il ne peut pas.
- La preuve
- Le contrôle tourne avec les droits d'un technicien, jamais d'un dirigeant, et rougirait sur une règle manquante — c'est-à-dire exactement le cas pour lequel il a été écrit.
Ce que « ça marche » veut dire ici
La règle que le fichier de tests énonce sur lui-même
À quoi servent les invariants
- Chaque bug de production trouvé à la main gagne un invariant ici, pour ne jamais revenir
- 69 aujourd'hui — aucun ne teste un plantage
- Ils testent des CONTRADICTIONS : deux endroits du code qui doivent rester d'accord, et ne l'étaient plus
- Ils lisent la source livrée elle-même, pas une copie qui peut dériver
Ce que les tests d’avant ne voyaient pas
- Ils ne couvraient que le démarrage, le service worker et le mode hors-ligne
- Ils ont laissé passer, pendant des semaines, un blocage TOTAL à l'étape 5
- Et un relevé qui ne quittait jamais le téléphone
- Rien ne plantait — on vérifiait une sortie bien formée, jamais que l'entrée en ressortait
Trois habitudes sont sorties de ces quatre incidents, et elles s’appliquent maintenant à tout ce qu’on ajoute au système.
- Un contrôle incapable de rougir ne prouve rien. Quand un garde-fou est écrit, on le casse exprès pour vérifier qu’il réagit — la deuxième fuite trouvée dans le jeu de démonstration a été fermée avec quatre capteurs, chacun saboté puis gardé.
- Des tests verts ne prouvent pas que la donnée a survécu. Vérifier qu’une sortie est bien formée n’est pas vérifier que ce qui a été saisi en ressort ; ce seul contrôle manquant, ajouté une fois, a fait tomber cinq défauts vivants en une passe.
- Regarder en amont avant d’accuser le code. Une vague de doublons d’interventions ressemblait à une déduplication cassée. Mesuré : zéro identifiant en double sur 55, zéro même client le même jour, zéro identifiant partagé sur 181 événements d’agenda. Les doublons venaient de gens qui supprimaient puis recréaient un rendez-vous dans leur agenda, ce qui attribue un identifiant neuf à chaque fois. Le correctif était en amont, pas dans la fusion.
Ce que l’agent a le droit de faire
La frontière, écrite noir sur blanc
Ce que l'agent fait tout seul
- Lire deux boîtes mail, toutes les minutes, et classer chaque message
- Ouvrir les PDF joints et en extraire client, références, montants, dates
- Interroger la base du client — en lecture seule
- Rédiger une réponse, laissée non envoyée
Ce qu'il n'a pas le droit de faire
- Écrire dans la base vivante du client
- Envoyer un mail en son nom
- Créer une fiche que personne n’a relue
- Supprimer ou écraser une donnée existante
Cette frontière n’est pas de la prudence de principe. Une version antérieure de ce pipeline recréait toutes les propositions qu’elle avait déjà traitées, parce que son seul vrai garde-fou contre le double traitement était de savoir si un mail semblait encore non lu — un indicateur externe qui peut se remettre à zéro, et dont la mise à jour pouvait échouer en silence. Le correctif durable a été un registre persistant de ce qui a été traité, écrit à chaque succès et consulté avant chaque passage.
Exploitation
- Cloisonnement par entreprise au niveau de la base, prévu dès le départ plutôt que rajouté après — et vérifié contre la vraie base, pas contre les règles sur le papier.
- Sauvegardes chiffrées hors machine, avec restauration vérifiée octet par octet.
- Installable depuis le navigateur, sans magasin d’applications en travers.
- Utilisable au clavier, contraste au seuil AA — une app qui doit se lire en plein soleil.