01 — Fleet Services Group
Reconstituer la structure capitalistique d’une entreprise — et le prouver
Neuf sources publiques croisées pour répondre à une question simple : qui contrôle vraiment cette entreprise ? Avec 50 cas vérifiés à la main pour prouver que la réponse est juste dans 94 % des cas.
« Cette entreprise appartient à qui ? » est une question qui paraît simple et ne l'est pas. Le registre public donne une raison sociale, pas un actionnaire. Les bases commerciales confondent trois notions différentes. Et la réponse détermine si l'on prospecte un site, une société, ou un groupe entier — donc si une campagne commerciale a du sens ou pas.
Le problème
Trois notions sont systématiquement confondues, y compris par des outils payants : la société mère juridique immédiate (qui a un identifiant légal), la holding consolidante ultime (qui peut être étrangère) et le groupe consolidant — la marque publique, celle que tout le monde connaît, et qui bien souvent n'a aucun identifiant d'entreprise.
Les confondre a des conséquences directes : on démarche trois filiales du même groupe en croyant démarcher trois prospects, on adresse une filiale alors que la décision se prend au siège, ou l'inverse. Et aucune source unique ne permet de trancher : le registre national ne publie pas les liens capitalistiques, les bases internationales d'identifiants ne couvrent que les entités financières déclarantes, et l'encyclopédie collaborative est riche mais non structurée.
Ce que j’ai construit
Un moteur de résolution d'entités qui agrège neuf sources publiques hétérogènes — registre national des entreprises, référentiel international d'identifiants d'entités juridiques, bulletin officiel des annonces civiles et commerciales, encyclopédie collaborative et sa base de données structurée, actes déposés au registre du commerce, flux d'actualité, moteur de recherche, marchés publics européens, registre des agréments financiers — et qui en tire une réponse structurée en 47 colonnes typées, avec une décision, un niveau de confiance, la source dominante et un résumé des preuves.
Le tout est exposé dans une application web interne de quatorze outils où l'on dépose un fichier et récupère un fichier, et dans une interface en ligne de commande qui partage exactement le même moteur.
Décisions structurantes
01Le modèle de langage est la source la moins prioritaire
Chaque source porte une priorité calibrée et justifiée en commentaire : le référentiel international d'identifiants à 100, les actes officiels à 85, les annonces légales à 70, l'encyclopédie à 40… et le modèle de langage à 30, avec la mention « jamais autoritaire seul ». Il ne tranche jamais contre une source déterministe ; il sert à croiser, à relier et à formuler — pas à décider. C'est l'inverse de l'usage habituel, et c'est ce qui rend le résultat défendable devant un client.
02Un seul appel au modèle, avec le dossier complet
Plutôt que d'éparpiller les appels source par source, tous les signaux collectés sont assemblés dans un seul dossier soumis en une fois. Le modèle dispose du contexte complet pour croiser les indices, l'appel coûte trois fois moins cher, et surtout il n'y a plus de logique de fusion à écrire après coup — c'est cette logique de fusion qui produit les incohérences les plus difficiles à déboguer.
03Une garde anti-hallucination adossée aux preuves
Tout nom de groupe produit par le modèle doit être attesté par au moins un signal réel : une raison sociale, un candidat parent, un titre d'article, une mention d'annonce légale. Sinon il est rejeté et remplacé par une valeur dérivée des sources. La règle vient d'un cas concret documenté dans le code : sur des filiales d'un grand groupe énergétique, le modèle inventait un groupe consolidant vu dans l'actualité récente, qu'aucune source ne désignait comme tel.
04Une couche de contrôle qualité qui a le droit de dégrader
Après la synthèse, un validateur indépendant peut poser des alertes, corriger des valeurs manifestement fausses, rétrograder une décision trop confiante en « à revoir », ou baisser la confiance d'un cran. Son principe directeur est écrit en toutes lettres dans le code : « on préfère une ligne à revoir plutôt qu'une ligne fausse présentée avec une confiance élevée ». Une donnée fausse mais confiante coûte bien plus cher qu'une donnée absente.
05Un étage économique avant les sources payantes
Les sources gratuites et rapides partent en parallèle. Les sources lentes ou facturées — téléchargement d'actes, moteur de recherche — ne sont appelées que si le premier étage n'a rien produit de crédible, selon un critère explicite. Sur les groupes bien documentés, cela évite 70 à 80 % du temps de traitement. Une source coûteuse doit justifier son appel, pas figurer par principe dans le pipeline.
06Le lot s’enrichit lui-même, puis converge
Après la collecte, quatre phases de consolidation : les filiales du même groupe présentes dans le lot se transmettent l'identifiant parent ; un nom canonique officiel est imposé à toutes les lignes partageant le même parent, ce qui fait converger trois orthographes vers une seule ; un dictionnaire d'environ 120 grands groupes français sert de source de vérité ; enfin une fermeture transitive remonte la chaîne de propriété de proche en proche, avec détection de cycle et refus explicite qu'un fonds d'investissement non opérationnel serve de relais.
07Distinguer un actionnaire d’un parent opérationnel
Une douzaine de motifs écartent les grands gestionnaires d'actifs, les fonds souverains, les holdings patrimoniales et les fédérations. Ils restent acceptables comme détenteur ultime, mais sont fortement pénalisés comme parent direct — parce qu'un fonds indiciel qui détient 6 % d'un groupe de BTP n'en est pas la maison mère opérationnelle, et qu'un commercial à qui l'on répond ça perd confiance dans l'outil.
Le morceau dont je suis le plus fier
Prouver que ça marche
Sur ce problème il n'existe aucune vérité terrain publique. J'ai donc construit la mienne : 50 entreprises françaises réelles annotées à la main, quatorze champs chacune — parent direct attendu avec son identifiant, détenteur ultime, groupe consolidant, décision métier, et les sources primaires qui justifient l'annotation. Le jeu est stratifié en trois niveaux de difficulté et couvre dix familles de groupes parmi les plus tordues du paysage français.
Le harnais compare six champs par ligne avec un comparateur à quatre valeurs, et rend un verdict à quatre niveaux. Il encode surtout des règles de notation qui acceptent l'incertitude au lieu de la punir : sur un cas structurellement ambigu, répondre « à revoir » est mieux noté qu'une certitude fragile. Un identifiant parent égal à l'entreprise analysée est en revanche une violation critique.
Cinq exécutions complètes horodatées : 82 % → 84 % → 90 % → 94 %, zéro violation critique sur les cinq. Et la sixième est retombée à 84 % — le harnais a détecté ma propre régression et nommé les quatre lignes fautives. C'est exactement ce pour quoi il a été écrit.
Le module de dédoublonnage
Le second module répond à une règle simple — un compte égale une entité légale — et à un piège qui coûte cher. La comparaison de N comptes deux à deux est quadratique ; six index inversés sont donc construits en une seule passe, et les paires ne sont formées qu'à l'intérieur d'un même groupe. Chaque paire est notée sur onze signaux mêlant booléens (identifiants légaux, domaine, téléphone) et mesures continues (similarité de raison sociale, d'adresse).
La normalisation calcule deux formes du nom : une forme stricte qui retire vingt-huit formes juridiques et leurs variantes espacées, et une forme allégée qui retire en plus neuf mots faibles comme « France », « Groupe » ou « Holding ». Les deux sont notées séparément — ce qui permet de distinguer une véritable identité d'une simple appartenance à la même marque.
Et la règle qui sauve la mise : domaine racine identique mais identifiant légal différent → jamais de fusion automatique, envoi systématique en file de revue. Sans elle, un même domaine d'entreprise fusionnerait la branche construction, la branche immobilier et la branche télécoms d'un groupe en un seul compte. C'est le piège classique du B2B français, et il est traité en dur.
Les regroupements passent par une structure union-find avec compression de chemin, l'arbitrage entre valeurs concurrentes se fait par famille de champs et non uniformément, et chaque valeur retenue conserve la ligne dont elle provient. Un journal d'audit horodaté enregistre chaque décision de paire : c'est un artefact de conformité, pas un journal de débogage.
Un bug qui vaut une leçon
Post-mortem écrit dans le code
Le client HTTP centralisé applique une limitation de débit par domaine, calibrée source par source. La première version marquait le créneau consommé après l'attente. Six tâches parallèles lisaient donc le même horodatage, calculaient toutes une attente nulle, et partaient en rafale — l'API saturait, répondait « trop de requêtes », et l'on repartait dans une cascade de réessais. Mesuré dans le code : 6 à 16 secondes pour un seul appel alors qu'on était réglé à 5 par seconde.
Le correctif tient en deux lignes — réserver le prochain créneau avant de dormir, pas après — mais il faut avoir diagnostiqué la course pour l'écrire. Le même fichier documente une seconde décision : l'abandon d'une version « auto-adaptative » qui s'étranglait après quelques rejets et mettait des minutes à récupérer. Retirer de la sophistication qui ne marche pas est une décision d'ingénierie, pas un aveu.
Résultats mesurés
jeu annoté à la main · exécution du 22/04/2026, 15 h 09
5 exécutions horodatées du 22/04/2026
même série de 50 cas
moyenne sur 5 exécutions, écart 1,3 %
0,0109 € relevé pour 50 entreprises, journal d’appels
37 appels pour 50 lignes
20 fichiers, 54 blocs
export du 19/03/2026
L’ingénierie autour
- Un moteur, deux façades. La fonction d'orchestration est pure — mémoire vers mémoire, aucune écriture disque. Une enveloppe l'expose en ligne de commande, une autre en HTTP. Même moteur, deux adaptateurs, zéro duplication de logique métier.
- Les métriques voyagent dans les en-têtes HTTP. Le corps de la réponse reste le fichier binaire pur, directement téléchargeable, tout en portant des statistiques structurées dans des en-têtes dédiés.
- Le système de types impose la documentation. L'aide contextuelle est déclarée comme un dictionnaire indexé sur l'identifiant d'outil : TypeScript refuse de compiler si un nouvel outil n'est pas documenté.
- Un pipeline de langage à coût maîtrisé sur un autre outil : déduplication avant appel, traitement par lots de quarante, pool de tâches parallèles, cascade à deux modèles où seuls les cas limites sont escaladés vers le modèle coûteux, et un estimateur qui modélise jusqu'à la remise de tarif sur le préfixe mis en cache. Le coût est annoncé avant de lancer, puis restitué après.
- Le verdict du modèle est recalculé côté serveur à partir du score qu'il a produit, au lieu d'être cru sur parole. Le modèle ne peut pas rendre une sortie incohérente avec elle-même.
- Déploiement en conteneurs : deux images multi-étapes, routage par priorités devant un reverse proxy avec certificats automatiques, aucun port publié sur l'hôte, et une chaîne de livraison qui vérifie que chaque secret est renseigné sans jamais en journaliser la valeur.
Ce que je ferais évoluerDéplier
- Deux monolithes résiduels hérités de la première version — l'écran principal et le serveur. J'en suis assez conscient pour avoir écrit deux outils de transformation automatique de code qui ont déjà servi à en extraire trois modules.
- La chaîne de livraison ne lance ni les tests, ni la vérification de types. Les 125 tests existent et passent, mais à la main. C'est la première chose que je branche.
- Un mode de regroupement déclaré mais non implémenté, et un indicateur d'usage du modèle câblé de bout en bout mais jamais consommé. Des points d'extension, pas des fonctionnalités — et je préfère le dire que laisser quelqu'un le découvrir.
- Aucun test sur l'interface. Toute la logique déterministe est couverte, l'affichage ne l'est pas.
Stack
Backend
- Node.js
- TypeScript strict
- Express
- Jest
- Zod
Frontend
- React 18
- TypeScript
- Vite
- CSS écrit à la main
- Zéro dépendance UI
Données & IA
- OpenAI
- SQLite (WebAssembly)
- SPARQL
- Levenshtein
- Union-Find
Livraison
- Docker multi-étapes
- Traefik
- Let’s Encrypt
- GitHub Actions
- Benchmark de non-régression