Stéphane.
WebArchivé

Constat Minute — diffuser une demande de constat aux huissiers du secteur, et laisser le premier la capturer

Une plateforme PHP qui géolocalise une demande, la diffuse par e-mail et SMS aux huissiers dans un rayon de 20 km, et l’attribue au premier qui clique. Quatre ans d’exploitation, puis liquidation.

Technologies employées

  • PHP 7
  • MySQL
  • PHPMailer
  • Apache
  • crontab
  • Bootstrap 4
  • jQuery
  • Chart.js
  • Raspberry Pi
  • API Vicopo

Un dégât des eaux, un chantier qui déborde, un voisin qui construit trop près : dans tous ces cas la réponse utile est un constat d’huissier, et elle a une date de péremption. Ce qui manque au particulier n’est pas l’information — la liste des huissiers est publique — mais le fait qu’il doive appeler une étude après l’autre, tomber sur des secrétariats, et attendre un rappel.

Constat Minute renversait le sens de l’appel. Le particulier décrivait son besoin en cinq champs ; la plateforme cherchait elle-même les huissiers de son secteur, leur envoyait la demande par e-mail et par SMS, et attribuait l’affaire au premier qui la réclamait. La brique intéressante n’est ni le formulaire ni le back-office : c’est ce qui se passe entre les deux, entièrement piloté par la crontab, et le petit mécanisme de verrou qui décide à qui l’on donne le numéro de téléphone du client.

Le prototype tournait sous le nom huissier-pas-cher.fr — l’archive de code que je relis ici date du 2 mars 2019. La société Constat Minute a été immatriculée au RCS de Marseille le 7 mai suivant, pour une activité déclarée au 16 avril 2019. Elle a été liquidée en 2023.

Ce que le site montrait, et ce qu’il cachait

La page d’accueil tenait en un formulaire : nom, e-mail, téléphone, ville, type de constat, prix. Le champ ville arrivait déjà rempli — index.php interrogeait ipinfo.io avec l’adresse du visiteur et en tirait le nom de la commune. Détail d’ergonomie minuscule, effet réel : sur mobile, il enlève le seul champ que personne n’a envie de taper.

Affiche Constat Minute : sur une photo floutée de livres de droit et d’un marteau de juge, le logo, la mention manuscrite « Simple, rapide, gratuit ! », les trois lignes « Un constat ! Un huissier ! Un RDV ! », et une main tenant un téléphone qui affiche le formulaire de demande.
L’affiche de lancement, version 1. Elle dit la promesse mieux que le site ne le faisait : « un constat, un huissier, un RDV », et le formulaire tient dans une main.

Ce que le formulaire ne disait pas, c’est qu’il ne cherchait personne au moment où on l’envoyait. L’enregistrement en base et l’accusé de réception étaient les deux seules choses synchrones ; tout le reste appartenait à des scripts planifiés, et cette décision structure le projet entier.

L’architecture : une table, quatre drapeaux, deux tâches planifiées

Le socle est un LAMP sans surprise : PHP procédural, MySQL, Apache, PHPMailer pour le courrier, Bootstrap 4 et jQuery côté page. Deux tables portent tout le métier — t_huissier pour les partenaires, t_demande pour les affaires. Il n’y a ni file d’attente, ni démon, ni courtier de messages : l’avancement d’une demande est écrit dans ses propres colonnes, et la crontab fait office d’ordonnanceur.

Trajet d’une demande de constat, du formulaire au verrou de capture Le visiteur remplit un formulaire dont le champ ville est prérempli à partir de son adresse IP. La demande est enregistrée dans la table t_demande avec les drapeaux etat, locked, declencheur et sendcapture à zéro, et le client reçoit aussitôt un accusé de réception par e-mail et par SMS. Une tâche planifiée, declencheur.php, ramasse les demandes déposées dans les deux dernières minutes et lance check_demande.php. Ce script traduit la ville du client en code postal via l’API Vicopo, demande la liste des communes situées dans un rayon de vingt kilomètres, puis croise cette liste avec les villes des huissiers actifs. Chaque huissier retenu reçoit un e-mail et un SMS contenant un lien de capture personnel, mais aucune coordonnée du client. Le premier qui ouvre ce lien pose le verrou : la demande passe à locked égal un et lui est attribuée. Un second script planifié, scapture.php, envoie alors les coordonnées du client à ce seul huissier. Si personne n’a capturé la demande au bout d’un quart d’heure, une relance renvoie la demande vers la diffusion.formulaire publicville préremplie par l’IP · 5 champst_demandeetat=0 · locked=0declencheur=0 · sendcapture=0INSERTaccusé au cliente-mail + SMSdeclencheur.phpcrontab · fenêtre glissante de 2 mincheck_demande.phpville → code postal (Vicopo)communes dans un rayon de 20 kmcroisées avec les villes des huissiersn huissiers du secteurlien de capture personnelaucune coordonnée cliente-mail SMTP + SMScapture.php?flag=…premier clic servilocked=1 · huissier attribuéscapture.phpcoordonnées client → 1 destinatairesans capture à +15 min
Aucune étape n’est synchrone : le formulaire rend la main immédiatement, et c’est la crontab qui fait avancer la demande. Le verrou est posé par le premier huissier qui clique, pas par la plateforme.

Quatre colonnes de t_demande suffisaient à décrire tout le cycle de vie. Les lire dans l’ordre revient à lire la machine à états.

ColonneValeursCe qu’elle signifieQui l’écrit
etat0Demande ouverte. Jamais repassée à 1 dans le code livré
declencheur0 → 1 → 20 : jamais diffusée · 1 : diffusée une fois · 2 : relancée un quart d’heure aprèsdeclencheur.php, declencheur20.php
locked0 → 1Le verrou de capture. 1 signifie « un huissier s’est déjà positionné »capture.php, au premier clic
sendcapture0 → 1Les coordonnées du client ont été transmises au capteurscapture.php

La séparation entre locked et sendcapture mérite d’être expliquée, parce qu’elle n’est pas décorative. Le verrou se pose depuis un navigateur, dans la seconde ; l’envoi des coordonnées se fait depuis un script planifié, plus tard. Si l’envoi avait été fait dans la foulée du clic, le huissier aurait attendu le résultat d’un SMTP distant et d’un appel HTTP à la passerelle SMS pour voir sa page s’afficher — et une erreur d’envoi aurait laissé une demande verrouillée sans destinataire prévenu.

Le calcul de proximité : deux API et une intersection

Le cœur du produit tient dans une trentaine de lignes de check_demande.php. La question à résoudre — « quels huissiers sont proches de cette ville ? » — n’avait pas de réponse en base : t_huissier ne stocke qu’un nom de commune, pas de coordonnées. Plutôt que de géocoder et de calculer des distances, le script passe par les noms de communes et fait une intersection d’ensembles.

// Étape 1 — la ville saisie devient un code postal.
$vicopoUrl = 'http://vicopo.selfbuild.fr/city/' . urlencode($ville);
$json = @json_decode(file_get_contents($vicopoUrl));
$codePostal = ! is_object($json) || empty($json->cities)
    ? 'introuvable'
    : $json->cities[0]->code;

// Étape 2 — le code postal devient la liste des communes à 20 km.
$url_check = '.../cities/around?location=' . $codePostal
           . '&radius=' . $param_distance . '&format=json';

// Étape 3 — la ville du huissier est-elle dans cette liste ?
if (in_array(strtolower($tab_huissier['ville'][$i]), $resultloc)) {
    // diffusion e-mail + SMS
}

C’est une solution de contournement, et elle en a les défauts. Elle dépend de deux services tiers gratuits et sans engagement, dont l’un hébergé chez un particulier ; une commune homonyme renvoie le mauvais code postal ; strtolower seul ne rapproche pas « Saint-Étienne » de « saint-etienne » ; et le rayon de 20 km est écrit en dur dans le script, alors que c’est exactement le paramètre qu’un huissier voudrait régler lui-même.

La course à la capture

Une demande partait vers tous les huissiers du secteur en même temps, chacun recevant un lien qui lui était propre :

/suivi/capture.php?flag=<id-demande>&mail=<email-huissier>

Le premier à ouvrir ce lien passait locked à 1 et se voyait attribuer la demande ; les suivants tombaient sur « Capture de la demande finie ». Ni compte à créer, ni mot de passe à ressaisir : un huissier qui lit son SMS en intervention capture d’un pouce.

Ce choix règle le vrai problème du métier, qui est le temps de réaction, et il le règle sans arbitrage de la plateforme — pas de tour de rôle, pas de score, pas de règle d’attribution à défendre. Il en crée deux autres, que je n’avais pas traités. D’abord, la course favorise mécaniquement celui qui est disponible, pas celui qui est le plus proche ou le plus à l’aise sur ce type de constat. Ensuite, capture.php ne vérifie rien d’autre que l’existence de la demande : l’identifiant est un entier séquentiel et le paramètre mail est recopié tel quel en base, si bien que n’importe qui pouvait s’attribuer n’importe quelle demande ouverte en devinant un numéro.

Le canal SMS, tenu par un Raspberry Pi

L’e-mail seul ne suffisait pas : une étude relève sa boîte quand elle peut, et la promesse tenait en un mot, « minute ». Le SMS était donc le canal principal, et l’e-mail le canal de détail.

Plutôt qu’un fournisseur d’envoi, le projet utilisait une passerelle maison, appelée par une simple requête HTTP :

GET http://<passerelle>/index.php?token=<jeton>&texte=<message>&numero=<destinataire>

Derrière cette URL, un Raspberry Pi équipé d’un module GSM, joignable par un nom DynDNS — l’image disque de juin 2019 est d’ailleurs, et de loin, le plus gros fichier du dossier de projet. Pour un service qui envoyait quelques dizaines de SMS par jour, c’était le bon calcul : coût marginal nul, aucun contrat, et le même boîtier servait à d’autres projets.

Un détail montre que la chaîne a été éprouvée en conditions réelles : avant l’envoi, le message passe par une fonction suppr_accents(). Sans elle, un « Marché » ou un « État des lieux » basculait le SMS en encodage UCS-2, divisant par deux le nombre de caractères disponibles et coupant le lien de capture en fin de message. Ce genre de correctif ne s’invente pas au clavier ; il se rajoute après avoir reçu un SMS tronqué.

Le back-office, et ce qu’il ne mesurait pas

Chaque huissier disposait d’une interface personnelle, un thème d’administration Bootstrap 4 avec Chart.js : volume de constats sur l’année et sur le mois, répartition par type, puis le détail de ses propres affaires.

Interface de gestion « Statistiques huissier » : à gauche un histogramme « Total des constats » montrant environ 59 constats sur l’année contre 1 sur le mois en cours ; à droite un anneau « Type de constat » réparti entre autre, état des lieux, internet, familial, permis de construire, voisinage et permis construction.
Le tableau de bord d’un huissier partenaire, mi-2019, recadré sur les graphiques : le tableau des demandes et le volet d’identification ont été retirés, ils portent les coordonnées de clients réels.

Les deux barres de gauche racontent la vraie difficulté : cinquante-neuf constats sur l’année, un seul sur le mois en cours. Un partenaire qui ouvre cet écran voit d’abord son propre creux. Et ce que le tableau de bord ne montrait nulle part, c’est ce qui se passait entre la diffusion et la capture — combien de demandes lui avaient été proposées, combien il en avait laissé filer, en combien de temps. Ces chiffres existaient pourtant en base, à une jointure près. C’est la mesure que je regrette le plus de ne pas avoir construite : elle répondait à la seule question qui décide de l’adhésion d’un partenaire, celle de savoir si le service lui apporte quelque chose.

Ce que la mesure de performance disait vraiment

Le site a été audité au GTmetrix le 29 février 2020. Le résultat mérite d’être regardé pour ce qu’il est.

Rapport GTmetrix pour https://www.constat-minute.fr/ daté du 29 février 2020 : PageSpeed Score A 100 %, YSlow Score A 97 %, temps de chargement complet 7,8 s, poids total de la page 4,72 Mo, 15 requêtes.
Rapport GTmetrix du 29 février 2020. Deux notes A, et deux chiffres qui disent l’inverse.
IndicateurValeurCe qu’il faut en conclure
PageSpeedA, 100 %Les règles d’optimisation sont respectées : compression, cache, minification
YSlowA, 97 %Idem, sur un autre jeu de règles
Requêtes15Peu de fichiers, donc peu d’allers-retours — le bon réflexe de l’époque
Poids de la page4,72 MoLe vrai problème : l’image de fond n’a jamais été redimensionnée
Chargement complet7,8 sCe que vivait le visiteur, quelles que soient les deux notes A

Deux A et huit secondes d’attente. L’outil notait la conformité à des règles, pas l’expérience ; le poids venait presque entièrement d’un visuel de fond servi en pleine résolution. Sur mobile, en 2020, sur une page dont l’unique but est de faire remplir un formulaire, c’est le genre d’écart qui coûte plus de demandes que n’en rapporte une campagne. C’est aussi pourquoi, sur ce site-ci, la performance est la première des priorités écrites : je l’ai apprise en lisant deux A au-dessus d’un 7,8 s.

Mars 2020

L’export de la table des demandes, tiré le 4 mai 2020, contient les cent dernières affaires — références 884 à 990, du 13 février au 3 mai. Le fichier n’a pas été produit pour raconter quoi que ce soit ; il le fait quand même.

PériodeJoursDemandesMoyenne par jour
13 → 29 février 202017392,3
1er → 16 mars 202016332,1
17 → 31 mars 202015171,1
avril 20203070,23
1er → 3 mai 2020341,3

Le 17 mars est le premier jour du confinement en France. Le flux se divise par deux dans la quinzaine qui suit, puis par dix en avril — et il remonte dès les premiers jours de mai. Rien de mystérieux : un constat suppose un déplacement sur les lieux, et l’activité qui le motive — chantiers, états des lieux, livraisons de logements — s’était arrêtée. Une plateforme de mise en relation n’a aucune prise sur ce genre de rupture : elle n’a pas de stock à écouler, pas d’offre à transformer, rien à faire d’autre qu’attendre que les deux côtés du marché reviennent.

Enseignements

  1. Rendre la main tout de suite et laisser la crontab faire le reste était le bon choix : le formulaire ne dépend d’aucun service tiers, et une panne de SMTP ou de passerelle SMS ne perd pas la demande, elle la retarde.
  2. Séparer le verrou de l’envoi des coordonnées — deux colonnes plutôt qu’une — évite le cas où une demande se retrouve attribuée sans que personne n’ait été prévenu.
  3. Raisonner en listes de communes plutôt qu’en distances collait à la façon dont les partenaires décrivent leur secteur. La bonne modélisation est celle du métier, pas celle de la géométrie.
  4. Un lien de capture sans authentification n’est pas une simplification d’ergonomie, c’est un contrôle d’accès manquant : un identifiant séquentiel suffisait à s’attribuer les demandes des autres.
  5. Deux notes A au GTmetrix au-dessus d’un chargement de 7,8 s : un audit mesure la conformité à des règles, jamais l’expérience. Le seul chiffre qui comptait était le poids de l’image de fond.
  6. Le back-office montrait à chaque partenaire son volume, jamais son taux de capture. La métrique manquante était celle qui décidait s’il restait.

Constat Minute a tenu quatre ans, avec un socle technique qu’on peut qualifier de rustique et qui n’a jamais été le facteur limitant. Ce qui a manqué était ailleurs : une densité suffisante d’huissiers partenaires pour que la promesse de proximité soit vraie partout, et une mesure honnête de ce que la plateforme apportait à chacun d’eux. Le confinement de mars 2020 n’a pas causé l’arrêt — il a rendu visible, en six semaines, la fragilité d’un modèle qui dépendait entièrement du fait que les deux côtés du marché se présentent en même temps.