Yuno15
← Tous les travaux

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

Stack

  • PWA
  • JavaScript
  • Service Worker
  • Supabase
  • PostgreSQL
  • RLS
  • GPS
  • PDF
  • 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.
Le même tableau de bord au même instant sur MacBook Pro en thème sombre et iPad en thème clair
Le même tableau de bord au même instant, et les deux thèmes d'un seul coup d'œil : le bureau tourne en sombre, le technicien bascule en clair quand il est sur un toit en plein soleil. Captures de l'app en production, mode démonstration — marque, lieux et données neutralisés. Cadres officiels d'Apple, à l'échelle réelle.

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
À lire comme une chaîne : chaque ligne de gauche rend la ligne de droite non négociable.

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
Les flèches ne sont pas décoratives : chacune porte la règle qui gouverne ce passage. Les deux en ambre sont les seuls endroits où quelque chose peut être refusé — une file qui attend le réseau, une personne qui clique.

Le trajet d’un relevé

Du toit au rapport du client
  1. 01 Sur le toit Le technicien remplit, photographie, valide. Quinze étapes au maximum, et seulement celles qui concernent ce type d'intervention.
  2. 02 Écrit sur le téléphone Immédiatement — la fiche dans le stockage local, les photos dans une base locale à elles.
  3. 03 La file La modification attend son tour, avec un numéro de révision qui change à chaque retouche.
  4. 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.
  5. 05 Le bureau Carte en direct, compteurs du jour, planning du mois — et le rapport client en PDF, en un geste.
La seule étape qui touche au réseau est la quatrième. Tout ce qui précède fonctionne téléphone en mode avion.

À 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.

L'écran de relecture : chaque champ a été extrait du mail, chaque champ reste modifiable, et c'est le bouton de droite qui déclenche l'écriture. En ambre, le seul champ corrigé à la main. Interface reconstituée — données fictives (confidentialité client)

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.
Le premier vrai retour du terrain, et celui qui a fixé le niveau d'exigence pour tous les autres : perdre du travail fait sur un toit n'est pas une panne acceptable.
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.
L'incident qui a séparé « la donnée est perdue » de « l'écran n'arrive pas à la dessiner » — deux mots d'écart, et une réparation complètement différente.
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.
Aucun plantage, aucune erreur, rien de rouge nulle part. C'est le mode de panne contre lequel une application hors-ligne doit être conçue, et la raison pour laquelle la file porte un numéro de révision.
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.
La raison la plus nette pour laquelle ce projet a cessé de faire confiance au vert : un contrôle incapable de rougir ne prouve absolument rien.

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
Reprise mot pour mot de l'en-tête du fichier d'invariants — c'est la phrase qui décrit comment ce système est entretenu.

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
L'unique exception en écriture est étroite et affichée : un champ de statut peut passer à « planifié », rien d'autre.

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.