V1.23.28 Mise à jour de la BDD des entreprises et stratégies

This commit is contained in:
2026-09-15 21:45:02 +00:00
parent e2de83ad41
commit 2825430bf4
9 changed files with 871 additions and 7 deletions
@@ -467,3 +467,63 @@ Restait un point sans case évidente : la dimension relationnelle envers l'équi
**Statut.** Migration du 56 terminée et vérifiée (167 468 lignes, structure `ENT_`). Extraction du 29 en cours au moment de la rédaction ; 48 traité comme département témoin pour les tests (21 859 entreprises). Restent à traiter : 22 et 35 (Bretagne), puis 17 et 64 évoqués pour l'usage "déplacement personnel". `ENT_Communes` alimentée pour 56/48/29. La synchronisation quotidienne (INPI/INSEE) reste à concevoir et construire — rien engagé au-delà de l'identification des bons endpoints. Aucune ligne de code applicatif ASPIC touchée ce soir : tout le travail décrit ici vit hors du dépôt (base `ENTREPRISES` + script autonome), toujours volontairement tenu à l'écart de l'application elle-même.
`<code>` n'avance pas dans cette section (aucune ligne du dépôt applicatif ASPIC touchée) ; `<conception>` avance de 24 à 25 — nouvelle version **V1.23.25**.
## 26. Backfill historique des comptes annuels, dimensionnement disque national, et un incident sérieux (12-13 septembre 2026)
**Origine.** Suite directe de la section 25 : les 4 départements bretons extraits, restait à peupler Phase3 avec l'historique complet des comptes annuels (2017 à aujourd'hui), pas seulement la dernière année déjà captée à l'extraction.
**Traduire la liasse fiscale brute en CA/résultat net — le vrai travail de cette session.** L'endpoint INPI `/api/bilans-saisis` ne renvoie pas un CA/résultat net direct mais une liasse fiscale complète (codes CERFA type "FJ", "HN"...), dont la signification dépend du type de bilan (`codeTypeBilan` : C, S, K, AC, AS, B). Table de correspondance établie à partir de la documentation officielle INPI (v5, juin 2025) puis **vérifiée empiriquement sur un vrai bilan (EDF, SIREN 552081317)** pour confirmer la bonne colonne (m3 = exercice en cours quand elle existe, sinon m1). Bug détecté et corrigé en cours de route : une même entreprise dépose souvent **à la fois** ses comptes individuels (C) et consolidés de groupe (K) pour la même année — sans règle de priorité, l'un écrasait l'autre au hasard de l'ordre de traitement. Priorité retenue : toujours préférer les comptes propres de l'entité (C/AC/AS/S) à un chiffre de groupe consolidé (K), qui ne représente plus l'entreprise précise. Les bilans en confidentialité non publique ne sont jamais exploités, par respect du choix déclaré par l'entreprise. Script autonome : `src/scripts/inpi_bilans_backfill.py`, surnommé **BFH** ("backfill historique") dans les échanges avec Jean-Marc — désormais systématiquement enchaîné après `sirene_extract.py` pour tout nouveau département, sans qu'il ait à le redemander (Phase2 reste exclue de cet automatisme, elle demeure un traitement manuel à part).
**Tables de pilotage ajoutées à la demande de Jean-Marc** : `ENT_Departements` (un département géré = une ligne, avec compteur d'entreprises mis en cache pour éviter des `COUNT(*)` répétés sur des tables qui grossissent) et `ENT_Config` (clé/valeur générique pour les réglages globaux, ex. date de la prochaine synchro quotidienne des comptes annuels, qui n'est pas filtrable par département). Remplace l'idée initiale de Claude (découvrir les départements via `SHOW TABLES`) — Jean-Marc a jugé une vraie table plus simple à interroger depuis ASPIC, avec raison.
**Dimensionnement national anticipé par Jean-Marc, avant d'avoir mal.** Question posée de façon proactive : sachant qu'il y a ~8,5M d'entreprises en France (chiffre INSEE cité par Jean-Marc), la base tiendra-t-elle le choc ? Calcul fait sur les tailles réelles mesurées (~7,3 Ko/entreprise en moyenne sur 5 départements) : projection nationale ≈ 70 Go, soit 36-40% du disque du VPS (193 Go, 154 Go libres à ce moment) — un chiffre que Jean-Marc a justement qualifié de "pas négligeable", refusant de le balayer d'un revers de main. Jean-Marc a lui-même relevé que la région parisienne (75 : 484 011 entreprises, 92 : 265 707, chiffres INSEE vérifiés) serait un point chaud disproportionné, pas une simple extrapolation linéaire depuis la Bretagne. Solution appliquée : compression native InnoDB (`ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8`) sur la table Phase1 (dominée par le JSON brut de l'API, qui compresse très bien) — gain réel mesuré de **-60%** sur les 5 tables existantes, ramenant la projection nationale à ~28-30 Go (15% du disque). Intégré au gabarit de création de table de `sirene_extract.py`, donc acquis automatiquement pour tous les départements à venir sans y repenser.
**Incident sérieux dans la nuit : le BFH a fait tomber le serveur en OOM.** Le script a été tué par le noyau après avoir atteint 9,33 Go de RAM — et le OOM killer a également tué **systemd lui-même** au passage (le serveur s'est remis seul, sans perte de service durable, 10 jours d'uptime conservés). Cause identifiée : la boucle de pagination par curseur (`searchAfter`) de l'appel `/api/bilans-saisis` n'avait aucun garde-fou — un lot bloqué (curseur qui n'avance plus, ou volume de résultats anormal pour un gros groupe) pouvait boucler en accumulant des résultats en mémoire indéfiniment, sans jamais lever d'erreur propre ni s'arrêter. Corrigé par un double garde-fou : plafond de 100 pages par lot, et détection explicite d'un curseur qui stagne (comparaison avec la valeur précédente). **Nouvelle règle de méthode actée pour la suite : tout lancement d'un script de récupération de données volumineuses doit désormais être encadré par `ulimit -v` (4 Go utilisé ici) avant le `nohup`, pour qu'un futur bug fasse planter le script, jamais le serveur entier.** Aucune perte de données : la conception en `UPSERT` (jamais un `INSERT` seul) fait que reprendre depuis zéro après un crash ne recrée aucun doublon, juste un peu de travail réseau redondant.
**Statut.** BFH relancé avec succès après correctif (mémoire redescendue de plusieurs Go à 62 Mo au même point de progression — le vrai bug semble bien corrigé, pas seulement contenu par le plafond de sécurité). Jean-Marc parti se coucher avec la suite actée pour cette nuit, sans supervision : une fois le BFH du 56 terminé, extraction + BFH enchaînés pour 44, 49, 85, 17, 40, 64 et 65, chaque étape protégée par le même `ulimit`. Mise à jour du dépôt remise au lendemain par Jean-Marc lui-même, comme toujours.
`<code>` n'avance pas dans cette section (aucune ligne du dépôt applicatif ASPIC touchée — tout le travail vit dans `src/scripts/` et la base `ENTREPRISES`) ; `<conception>` avance de 25 à 26 — nouvelle version **V1.23.26**.
## 27. Statistiques par commune, premières esquisses de carte de prospection géographique, et un correctif de fond sur le repli NAF (13 septembre 2026)
**Suite de la nuit du 12 au 13.** La chaîne sans supervision (44, 49, 85, 17, 40, 64, 65) s'est terminée sans incident, `ulimit -v` et garde-fou de pagination tenant leurs promesses. Une seconde chaîne a été lancée dans la foulée pour 31, 32, 33, 46, 47, 09, 13 et 66 (Occitanie/PACA), avec un décalage de départ pour ne jamais faire tourner deux extractions Sirene en même temps.
**Nouveau champ `ENT_CodeCommune` sur Phase1**, backfillé sans nouvel appel API (déjà présent dans le JSON brut stocké de chaque entreprise) sur les 7 départements de la première chaîne — nécessaire pour la fonctionnalité suivante.
**Statistiques par taille d'entreprise, par commune.** À la demande de Jean-Marc : cinq nouveaux champs sur `ENT_Communes` (`COM_EI`, `COM_TPE`, `COM_PME`, `COM_ETI`, `COM_GE`), alimentés par un script quotidien dédié (`ent_communes_stats.py`), pour que le futur module de recherche géographique n'ait jamais à compter en temps réel sur des tables Phase1 qui grossissent. Règle de classification : `ENT_CategorieEntreprise` prioritaire quand l'INSEE la renseigne (GE/ETI/PME) ; sinon répartition EI/TPE selon le premier chiffre de `ENT_NatureJuridique` (entrepreneur individuel = '1'). Deux bugs trouvés et corrigés avant mise en production : `SUM()` renvoie `NULL` (pas 0) sur un ensemble vide, faussant l'agrégat d'une commune sans aucune entreprise d'une catégorie — corrigé par `COALESCE(..., 0)` ; et surtout, une commune pouvait voir ses compteurs écrasés par un département qui ne la possède pas réellement (établissement secondaire présent dans la table Phase1 d'un département voisin) — corrigé en restreignant le calcul aux communes réellement rattachées au département traité via une jointure sur `ENT_Communes`, révélant au passage un conflit de collation (`utf8mb4_unicode_ci` vs `utf8mb4_0900_ai_ci`) entre tables créées à des moments différents, réglé par un `COLLATE` explicite dans la jointure.
**Première brique d'écran : la carte de prospection par géographie personnelle**, idée actée en section 25 (l'isochrone IGN), construite ce jour sous forme de prototypes interactifs (Claude Artifacts) plutôt que dans l'application : une commune de départ au centre, les communes accessibles en 30 minutes de route autour (isochrone réel + position géographique relative, projection locale corrigée du cosinus de la latitude), chacune avec son temps de trajet réel (API IGN `itineraire`) et ses effectifs EI/TPE/PME/ETI/GE réels tirés de `ENT_Communes`. Trois itérations avec retour de Jean-Marc avant d'aboutir à une version jugée satisfaisante : espacement des cartes pour éviter le chevauchement (algorithme de répulsion par paires, position géographique réelle conservée via un point + ligne de rappel quand une carte est déplacée pour la lisibilité), puis remplacement de la navigation par ascenseur natif (peu fiable dans le rendu de la fenêtre d'Artifact à certains niveaux de zoom navigateur) par un cliquer-glisser JavaScript, au clic gauche **et** au clic droit — Jean-Marc ayant signalé une habitude de navigation au clic droit. Quatre versions produites : Lorient (démonstration initiale), Lanester (ville de résidence de Jean-Marc, test de cohérence des distances), puis Concarneau et Beuzec-Cap-Sizun (Finistère) à la demande de Jean-Marc pour deux amis, comme simple partage d'un lien — pas encore une fonctionnalité ASPIC, mais une preuve que la donnée collectée a une valeur de démonstration immédiate.
**Correctif de fond sur le repli NAF à 3 niveaux — la vraie panne de la journée.** Le 3e niveau de repli (par code NAF précis), ajouté le 12 septembre pour débloquer Rennes (section 26 — un seul cas rencontré alors, section L), s'est révélé mal conçu à l'échelle : au lieu de ne tester que les codes NAF appartenant à la section en cours, il retestait les 732 codes en entier **à chaque section qui restait plafonnée**. Sur une grande ville où plusieurs sections dépassent le plafond simultanément (Toulouse : au moins 6 sections concernées), ce défaut a bloqué l'extraction du département 31 pendant plusieurs heures sans jamais planter ni progresser de façon visible — seule la répétition suspecte des mêmes codes (68.20A/68.20B, immobilier) à chaque section a permis de repérer le problème. Corrigé par une table de correspondance division→section NAF (standard, 21 sections, vérifiée exhaustive sur les 732 codes), limitant désormais le 3e niveau aux seuls codes réellement concernés par la section (6 codes pour la section L, contre 732 avant). Département 31 relancé depuis le début après correctif (pas de mécanisme de reprise par commune dans le script) : Toulouse traitée en quelques minutes au second passage, chaîne 2 poursuivie normalement.
**Cap symbolique franchi en cours de journée : plus de 2 millions d'entreprises cumulées** (2 227 587 exactement, sur 15 départements traités, le 33 encore en cours) — 31 (Haute-Garonne, avec Toulouse) est à lui seul le département le plus lourd traité jusqu'ici (312 359 entreprises).
**Premières réflexions sur la refonte du module Contacts, notées mais pas construites.** Jean-Marc a acté un changement de sens par rapport à la conception initiale des contacts : le lien entreprise↔contact ne sera plus exclu multiple (une entreprise peut avoir plusieurs contacts, un contact peut être lié à plusieurs entreprises), et la qualification (statut, score, canal) portera sur l'**entreprise**, pas sur le contact — la fiche contact ne conservant de l'entreprise que ce qui sert à garder le lien. Trois étapes de prospection distinguées : qualification des entreprises sélectionnées, recherche du ou des contacts, démarche commerciale classique — avec la précision que toute prospection ne partira pas nécessairement d'une qualification d'entreprise (d'autres approches resteront possibles). En préparant les écrans, Jean-Marc a demandé le détail du contenu du JSON brut Sirene stocké (structure décrite : `siege`, `dirigeants`, `finances`, `complements`) et relevé l'absence du capital social, confirmée après vérification (absent de cette API, disponible seulement côté INPI RNE, jamais interrogé à ce jour) — Jean-Marc a d'ores et déjà indiqué que ce champ relèverait de la qualification locale (donc jamais mutualisée), cohérent avec la séparation Phase1/Phase2 actée section 25. Sujet remis à plus tard dans son ensemble, quand ce chantier sera formellement ouvert.
**Statut.** 15 départements avec extraction et historique financier complets (17, 22, 29, 35, 40, 44, 48, 49, 56, 64, 65, 85 + 31, 32 achevés dans la chaîne 2, 33 en cours). Restent 46, 47, 09, 13, 66 en extraction puis BFH pour l'ensemble des 8 nouveaux départements. `ent_communes_stats.py` opérationnel mais pas encore automatisé quotidiennement (à brancher sur n8n avec la synchronisation incrémentale, toujours non construite). Quatre prototypes de carte de prospection publiés en Artifacts, partagés par Jean-Marc à deux amis — aucune intégration à l'application elle-même.
`<code>` n'avance pas dans cette section (tout le travail vit dans `src/scripts/`, la base `ENTREPRISES`, et des prototypes hors dépôt) ; `<conception>` avance de 26 à 27 — nouvelle version **V1.23.27**.
## 28. Suspension du BFH, découverte des arrondissements PLM, extraction France entière et refonte de la feuille de route ENTREPRISES (14-15 septembre 2026)
**Deux bugs de fond supplémentaires trouvés sur le BFH, le lendemain du précédent.** Le curseur de pagination de `bilans-saisis` utilisait le SIREN du dernier résultat comme `searchAfter` — or un SIREN a souvent plusieurs bilans (plusieurs années), donc revient plusieurs fois dans les résultats ; le garde-fou anti-boucle de la veille prenait ça à tort pour un blocage et abandonnait le lot en cours, perdant des bilans déjà partiellement récupérés (~14% des lots du département 31 touchés). Vérifié empiriquement que le champ `id` (identifiant unique par bilan, présent dans chaque réponse) donne une pagination propre sans doublon — corrigé. Puis, une fois relancé, l'API INPI a commencé à renvoyer des 401 (jeton expiré en cours d'exécution) après plusieurs heures d'appels continus — le script retentait 8 fois avec le même jeton mort, ce qui ne pouvait jamais réussir. Corrigé par une reconnexion automatique (`auth` devient un dict mutable partagé, se régénère tout seul sur un 401).
**Puis un vrai mur, cette fois non contournable par du code : un quota INPI dépassé** (`"errorCode":"quota_exceeded"`, `"webserviceCode":"QUOTA_SERVICE"`, confirmé par inspection directe du corps de réponse HTTP 429). Contrairement à une limitation de débit passagère, retenter ne sert à rien tant que le quota ne s'est pas régénéré (vraisemblablement journalier). Correctif appliqué par prudence (une `QuotaExceededError` dédiée fait interrompre immédiatement tout le traitement plutôt que de s'acharner lot après lot), mais **décision de Jean-Marc, plus radicale et plus sage : suspendre entièrement le BFH pour l'instant**, risque de bannissement du compte INPI jugé disproportionné par rapport au bénéfice — l'historique financier pourra être rattrapé "petit à petit dans les mois à venir", l'essentiel étant d'abord de disposer de l'identité de toutes les entreprises françaises pour commencer à travailler sur la structure des données et les écrans.
**Découverte majeure et indépendante, sur l'extraction Sirene elle-même : Paris, Lyon et Marseille (PLM) étaient massivement sous-captées.** Repéré à l'œil par Jean-Marc, qui a trouvé étonnant que le département 33 (Gironde, 416 980 entreprises) dépasse le 13 (Bouches-du-Rhône, alors 330 238) alors que Marseille est économiquement plus lourde que Bordeaux. Vérifié : dans les données Sirene, une entreprise marseillaise n'est quasiment jamais rattachée au code commune englobant (13055, ~10 résultats seulement) mais à son arrondissement (13201 à 13216) — or la liste des communes utilisée pour l'extraction ne renvoyait que le code englobant, jamais les 16 arrondissements, chacun pourtant déjà plafonné ou proche du plafond de 10 000 à lui seul. Corrigé (`sirene_extract.py` détecte désormais les trois villes à arrondissements et interroge `geo.api.gouv.fr?type=arrondissement-municipal` à la place du code englobant), `ENT_Communes` corrigée à la main pour le 13 (13055 supprimée, ses 16 arrondissements ajoutés), réextraction du 13 replanifiée en fin de chaîne. Paris et Lyon n'avaient pas encore été traités à ce stade, donc le correctif s'applique nativement dès leur premier passage.
**Décision de méthode pour la suite : terminer l'aspiration avec la méthode Sirene actuelle jusqu'au bout, sans la mélanger à une autre source en cours de route** (pour ne pas risquer d'hétérogénéiser les données), **mais reconcevoir entièrement la stratégie de mise à jour a posteriori**. Chaîne dédiée relancée (extraction seule, aucun appel INPI) pour l'intégralité de la France restante — 81 départements, métropole **et DOM-TOM inclus** (Jean-Marc : "Ne te limite pas à la métropole ! J'ai des amis à la Réunion, j'y passerai bien volontiers du temps !"), La Réunion placée en tête de liste pour cette raison personnelle. Rythme observé : environ 1h/département pour un département rural, plusieurs heures pour une grande ville nécessitant le repli NAF (Nice : 3h05). **Statut à la rédaction : 55 départements extraits, 7 036 958 entreprises** (~83% de l'estimation nationale de 8,5M) — la trajectoire confirme l'hypothèse de Jean-Marc que les 24 premiers départements traités (Bretagne puis Occitanie/PACA) étaient disproportionnellement denses, et que la moyenne va continuer de se rapprocher du national à mesure que les départements ruraux restants sont traités.
**Vérification croisée de plusieurs ratios avec Jean-Marc, à chaque fois confirmée par de vraies mesures plutôt qu'une simple extrapolation théorique** : le nombre d'entreprises (dépassement des 50% national constaté en direct) ; l'espace disque (10,69 Go réels pour 3,63M entreprises à ce moment, soit ~3 133 octets/entreprise compressé, projection nationale ~26,6 Go — cohérente avec l'estimation théorique de la section 26) ; et la proportion d'entrepreneurs individuels, mesurée sur les données déjà collectées plutôt que supposée : **41,3% d'EI (sans capital social), 58,7% de non-EI**, dont 3,27M déjà catégorisées GE/ETI/PME par l'INSEE.
**Piste ouverte par un ami de Jean-Marc, consultant senior, vérifiée et confirmée réelle : l'INPI met à disposition un export en masse du RNE, en FTP/SFTP, séparé de l'API paginée.** Recherche effectuée (dont un projet open-source `etalab/rncs_worker_api_entreprise` en confirmant l'usage réel) : structure stock + flux quotidien, format CSV, capital social explicitement présent comme champ. Demande d'accès faite par Jean-Marc le 15 septembre, accordée le jour même. Connexion testée (lecture seule) : `ftp://www.inpi.net` (FTP simple, pas du vrai SFTP malgré l'intitulé de la demande — source probable de la première tentative infructueuse de Jean-Marc sous FileZilla, configuré en SFTP). Quatre fichiers trouvés à la racine, deux jeux (formalités/comptes annuels) × deux versions chacun, aucun flux quotidien visible à ce niveau : `stock_RNE_formalites_NIVEAU1_20260320_1400.zip` (15,1 Go, mars 2026 — vraisemblablement la source du capital social), `stock_RNE_comptes_annuels_NIVEAU1_20260320_1400.zip` (4,3 Go). Dernier stock à 6 mois d'écart de la date du jour — jugé acceptable pour le capital social (donnée peu volatile) mais insuffisant en l'état pour les comptes annuels. Identifiants stockés dans `.env` (`INPI_RNE_FTP_*`), rien téléchargé, sujet à approfondir sans urgence.
**Réouverture et correction consciente d'une décision antérieure sur le capital social.** Jean-Marc était revenu sur ce qu'il avait dit trois jours plus tôt (section 27) : le capital social avait été classé à tort comme donnée de qualification privée par compte, alors que c'est une donnée factuelle de l'entreprise au même titre que le NAF ou l'effectif — elle doit vivre dans `ENTREPRISES`, mutualisée. Jean-Marc a également tranché explicitement pour une exception assumée au principe "jamais d'écriture dans `ENTREPRISES` déclenchée par un utilisateur" : la mise à jour du capital social pourra être initiée par une action utilisateur, précisément parce que c'est une information structurante — la conception du mécanisme (file d'attente, appel synchrone, etc.) reste ouverte, dépendante de limites de l'API RNE non encore connues (débit, temps de réponse), sans urgence vu le lancement commercial d'ASPIC visé "seulement" début 2028.
**Feuille de route formelle déposée par Jean-Marc** (`src/fdr/1. ENTREPRISES V1.0`), première utilisation de ce nouveau circuit de transmission (dépôt de fichier dans un répertoire dédié plutôt que description en conversation). Relecture complète effectuée à la demande explicite de Jean-Marc ("pour avoir ton retour... pas pour passer à l'action"). Points de retour marquants : découverte, en vérifiant une question de Jean-Marc sur les champs JSON, que `matching_etablissements[]` (jamais exploité) contient probablement l'adresse propre de chaque établissement secondaire ayant fait matcher une recherche — invalidant l'hypothèse de départ de Jean-Marc qu'on ne pourrait pas la connaître, et permettant potentiellement de retrouver un établissement secondaire par ville de recherche ; ~30% des entreprises ont plusieurs dirigeants dans le JSON, un seul capturé actuellement ; signalement d'une collision de nommage évitée de justesse (la feuille de route proposait de recycler le nom "Phase2", déjà porteur d'un sens différent et déjà documenté — Jean-Marc a tranché : `Phase2` disparaît purement et simplement de `ENTREPRISES`, son rôle originel renaît dans la base de chaque utilisateur sous un nom encore provisoire, `ASP_ENTQualif`).
**Clarification du trombinoscope des collaborateurs ASPIC, à l'occasion d'une coquille apparente.** La feuille de route mentionnait "la voix de Viktor" pour une future compétence de qualification — pas une erreur : les cinq collaborateurs nommés d'ASPIC sont **Matilda, Viktor, Sophia, Léon et Nikita**, et Jean-Marc a confirmé une répartition claire pour au moins deux d'entre eux : **Léon** porte la gestion (dont la garde d'`ENTREPRISES`), **Viktor** porte le commercial (dont la future qualification et son suivi temps/coût/réussite). Viktor n'a pas encore d'interface vocale prévue, "mais pourquoi pas quand je n'aurai plus que ça à faire".
**Statut.** BFH suspendu partout, à reprendre progressivement et sans urgence dans les mois à venir, avec une stratégie de mise à jour entièrement à reconcevoir (API paginée vs flux RNE en masse). Extraction Sirene seule en cours sans interruption depuis le 14 septembre, 55 départements sur 101 (métropole + DOM-TOM), 7 036 958 entreprises. Feuille de route ENTREPRISES V1.0 relue et commentée, pas encore amendée par Jean-Marc (en cours). Accès FTP RNE obtenu et testé en lecture seule, non exploité.
`<code>` n'avance pas dans cette section (deux scripts modifiés — `inpi_bilans_backfill.py`, `sirene_extract.py` — mais toujours hors du dépôt applicatif ASPIC lui-même) ; `<conception>` avance de 27 à 28 — nouvelle version **V1.23.28**.