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.

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.
Quatre colonnes de t_demande suffisaient à décrire tout le cycle de vie. Les
lire dans l’ordre revient à lire la machine à états.
| Colonne | Valeurs | Ce qu’elle signifie | Qui l’écrit |
|---|---|---|---|
etat | 0 | Demande ouverte. Jamais repassée à 1 dans le code livré | — |
declencheur | 0 → 1 → 2 | 0 : jamais diffusée · 1 : diffusée une fois · 2 : relancée un quart d’heure après | declencheur.php, declencheur20.php |
locked | 0 → 1 | Le verrou de capture. 1 signifie « un huissier s’est déjà positionné » | capture.php, au premier clic |
sendcapture | 0 → 1 | Les coordonnées du client ont été transmises au capteur | scapture.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.

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.

| Indicateur | Valeur | Ce qu’il faut en conclure |
|---|---|---|
| PageSpeed | A, 100 % | Les règles d’optimisation sont respectées : compression, cache, minification |
| YSlow | A, 97 % | Idem, sur un autre jeu de règles |
| Requêtes | 15 | Peu de fichiers, donc peu d’allers-retours — le bon réflexe de l’époque |
| Poids de la page | 4,72 Mo | Le vrai problème : l’image de fond n’a jamais été redimensionnée |
| Chargement complet | 7,8 s | Ce 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ériode | Jours | Demandes | Moyenne par jour |
|---|---|---|---|
| 13 → 29 février 2020 | 17 | 39 | 2,3 |
| 1er → 16 mars 2020 | 16 | 33 | 2,1 |
| 17 → 31 mars 2020 | 15 | 17 | 1,1 |
| avril 2020 | 30 | 7 | 0,23 |
| 1er → 3 mai 2020 | 3 | 4 | 1,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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.