Contrat API — Compter juste dans le cockpit (parc, revenus, clients)
Le principe en une phrase : le cockpit doit compter des choses réelles — des équipements, de l’argent encaissé, des exploitants — et jamais des lignes de base.
Ce guide s’adresse au développeur du tableau de bord d’administration. Il fixe les règles de comptage et les champs à afficher. Les écarts cités ont été relevés en production le 2026-08-16 ; les valeurs sont volontairement exprimées en ordres de grandeur.
1. La règle qui prime sur toutes les autres
Trois registres d’argent coexistent, et ils ne s’additionnent JAMAIS.
| Registre | À qui appartient l’argent | Ce qui nous revient |
|---|---|---|
| Licences SaaS | à nous | la totalité |
| Vouchers TiketMOMO | aux tenanciers | les frais uniquement |
| Hub techniciens | aux professionnels | la commission |
Un total fondu gonflait le résultat de 45 %. L’API ne renvoie donc aucun champ « total », et cette absence est intentionnelle : un champ présent finirait affiché.
2. status = 'SUCCESS' ne prouve aucun encaissement
C’est le piège le plus coûteux de la base.
Le statut est écrit à la création de la session de paiement, avant que l’acheteur n’ait
saisi quoi que ce soit. Sur les achats de licence, la quasi-totalité des lignes SUCCESS
portent un identifiant cs_live_… et metadata.claimed = false : ce sont des clics sur
« acheter », jamais payés. Les sommer multipliait le chiffre d’affaires annoncé par ~5,6.
La règle : le revenu licences se calcule sur les licences réellement délivrées
(licenses.source_tx_id), en remontant vers la transaction. On part du fait accompli, jamais
de l’intention d’achat.
"licences": {
"cumule": 0, // somme des transactions liées à une licence existante
"periode": 0,
"payantes_cumule": 0 // nombre de licences réellement payées
}
⚠️ Ne jamais reconstruire ce montant côté client à partir d’une liste de transactions.
3. Vouchers : nos frais d’un côté, le volume des tenanciers de l’autre
"vouchers": {
"nos_frais_cumule": 0,
"nos_frais_periode": 0,
"volume_tenanciers_periode": 0, // ⚠️ PAS un revenu
"ventes_periode": 0
}
volume_tenanciers_periode porte son propriétaire dans son nom, exprès. C’est l’argent des
tenanciers qui transite par notre compte ; l’afficher comme du revenu est la faute décrite
au §1. Seuls les frais des ventes encaissées sur notre caisse
(fee_origin = 'DEFAULT_STORE') nous reviennent.
4. Le parc se compte en matériels, jamais en allocations
L’ancien calcul annonçait ~88 % de routeurs muets, au-dessus du seuil critique tous les jours de l’année. Une alerte toujours allumée n’alerte plus : elle apprend à ignorer l’écran. Le taux réel est d’environ 10 %.
Trois confusions se cumulaient :
- Une allocation n’est pas un routeur. Les deux tiers des lignes n’ont jamais vu de matériel : un port réservé, un pair déclaré, personne au bout.
- Un matériel peut porter plusieurs allocations. Des routeurs étaient comptés deux fois.
- « Pas connecté » n’est pas « en panne ». Un hotspot éteint le soir par son propriétaire n’est pas une avarie.
"parc": {
"materiels": 0, // numéros de série UNIQUES, allocations ACTIVE
"sain": 0,
"muet": 0, // ⚠️ la SEULE catégorie qui appelle une intervention
"instable": 0,
"hors_ligne": 0,
"pct_muets": 0,
"complet": true,
"allocations_brutes": 0 // nombre de LIGNES — ne pas présenter comme un parc
}
Ce que « muet » veut dire
Un tunnel WireGuard se maintient sans adresse IP ni règle de pare-feu. Un routeur peut
donc afficher CONNECTED tout en étant totalement sourd — c’est la panne la plus trompeuse
du parc. Elle se reconnaît à la conjonction tunnel_status = CONNECTED et
health_status = UNHEALTHY, jamais à l’un des deux seul.
Les autres catégories décrivent, elles n’alertent pas.
5. Les clients : un entonnoir, pas un total
Le nombre d’inscrits ne dit rien de l’activité — moins d’un inscrit sur huit a branché un équipement, et une fraction de ceux-là a déjà encaissé une vente.
Afficher les trois étages, dans cet ordre :
inscrits → ont un matériel verrouillé → ont vendu au moins une fois
C’est la chute entre les étages qui est l’information ; les valeurs absolues sont secondaires. Même règle pour les licences : montrer « actives » sans distinguer les payantes donne une image fausse de l’entreprise, le parc étant très majoritairement en promotion ou en essai.
Route dédiée : GET /api/v2/analytics/customers. Elle est séparée exprès — fondue dans un
agrégat, la distinction se rediluerait et le total repasserait pour un nombre de clients.
6. complet: false doit s’afficher
PostgREST plafonne les lectures à 1 000 lignes, en silence. Tout agrégat au-delà serait faux sans le moindre signe.
Les instantanés portent donc un booléen complet. Quand il vaut false, le décompte a été
interrompu : affichez-le. Un total tronqué présenté comme exhaustif est la même faute que
celles corrigées ici, sous une autre forme.
7. Les anomalies méritent une tuile
Elles faussent tous les autres chiffres tant qu’elles existent :
"anomalies": {
"transactions_sans_type": 0, // n'entrent dans AUCUN registre
"en_attente_plus_30j": 0, // paiements qui n'aboutiront jamais
"licences_liens_orphelins": 0 // licence référençant une transaction inexistante
}
Ce ne sont pas des détails techniques : une transaction sans type est de l’argent hors comptabilité, et une licence au lien orphelin est un montant payé définitivement introuvable.
8. À ne pas afficher
| À bannir | Pourquoi |
|---|---|
| nombre d’allocations | ce sont des lignes, pas des équipements |
somme des SUCCESS |
le statut précède l’encaissement |
| CA « toutes activités » | additionne l’argent des tenanciers au nôtre |
| « clients actifs » | compte des inscriptions, pas des exploitants |
| tout indicateur en Go | aucune donnée de trafic n’existe : elle serait inventée |
| taux de résiliation | sous 30 titulaires payants, une résiliation déplace le taux de plusieurs points |
9. Quota — à lire avant d’implémenter le rafraîchissement
100 requêtes / 15 min par IP, partagées par TOUTES les routes /api.
Le cockpit santé est mis en cache 30 secondes côté serveur ; inutile de le solliciter plus vite. Interrogez à l’ouverture de l’écran, rafraîchissez sur action explicite, une seule sonde à la fois.
10. Voir aussi
- Santé et suspensions :
https://live.jmoai.net/docs/CONTRAT-API-ADMIN-COCKPIT - Flux financiers admin :
https://live.jmoai.net/docs/CONTRAT-API-ADMIN-FINANCE - Référence complète :
https://live.jmoai.net/api-docs - Source Markdown :
https://live.jmoai.net/docs/CONTRAT-API-COCKPIT-CHIFFRES/raw