V1.23.33 Fin de la récupération des entreprises

This commit is contained in:
2026-09-20 08:49:20 +00:00
parent f0711f49ac
commit dd2b7a6872
11 changed files with 131 additions and 14 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

File diff suppressed because one or more lines are too long
@@ -545,3 +545,61 @@ Restait un point sans case évidente : la dimension relationnelle envers l'équi
**Statut.** Extraction Sirene : 74/101 départements, 9 855 993 entreprises, en cours sans incident. BFH toujours suspendu (section 28). Deux feuilles de route actives dans `src/fdr` : "1. Évolutions BDD ENTREPRISES" (V6, renommée par Jean-Marc) et "3. Sélection des Entreprises" (V5) — aucune des deux encore validée pour passage à l'implémentation. Accès FTP RNE toujours inexploité. Collaborateurs ASPIC (Matilda, Viktor, Sophia, Léon, Nikita) et leurs rôles connus consignés en mémoire pour la suite. **Statut.** Extraction Sirene : 74/101 départements, 9 855 993 entreprises, en cours sans incident. BFH toujours suspendu (section 28). Deux feuilles de route actives dans `src/fdr` : "1. Évolutions BDD ENTREPRISES" (V6, renommée par Jean-Marc) et "3. Sélection des Entreprises" (V5) — aucune des deux encore validée pour passage à l'implémentation. Accès FTP RNE toujours inexploité. Collaborateurs ASPIC (Matilda, Viktor, Sophia, Léon, Nikita) et leurs rôles connus consignés en mémoire pour la suite.
`<code>` n'avance pas dans cette section (feuilles de route et scripts d'outillage PDF/docx/xlsx vivent hors du dépôt applicatif ASPIC) ; `<conception>` avance de 28 à 29 — nouvelle version **V1.23.29**. `<code>` n'avance pas dans cette section (feuilles de route et scripts d'outillage PDF/docx/xlsx vivent hors du dépôt applicatif ASPIC) ; `<conception>` avance de 28 à 29 — nouvelle version **V1.23.29**.
## 30. Incident de parallélisme sur l'extraction, naissance du projet entreprises.bzh, et mesure réelle du double-comptage (17 septembre 2026)
**Nouvel incident, cette fois provoqué par Claude Code lui-même.** En tentant de rattraper le département 74 (interrompu la veille par un aléa réseau), un script de reprise a été lancé **en parallèle** de la chaîne principale — erreur de jugement, l'hypothèse que `recherche-entreprises.api.gouv.fr` n'avait jamais montré de limite s'est révélée fausse sous charge concurrente. Les deux traitements simultanés ont fini par saturer l'API (HTTP 429 soutenu), faisant planter en cascade **75 (Paris), 76 et 77** en plus du 74 déjà en cours de reprise. Aucune perte de données (conception UPSERT), mais quatre départements incomplets. Corrigé sur deux plans : `http_get_json()` dans `sirene_extract.py` durci (retries avec backoff exponentiel sur 429 et respect de `Retry-After`, rattrapage de `http.client.HTTPException`/`ConnectionError` en plus de `URLError` — un `RemoteDisconnected` n'était pas rattrapé avant, cause du tout premier plantage du 74) ; et surtout, **plus aucun traitement en parallèle désormais, toujours strictement séquentiel**, leçon actée en mémoire. Une reprise séquentielle de 74/75/76/77 a été programmée pour démarrer automatiquement une fois la chaîne principale entièrement terminée, sans chevauchement possible. Un second incident, sans lien, a suivi le 17 au soir : le département 88 a échoué sur un verrou MySQL (`Lock wait timeout`), probablement lié à la charge d'un calcul de dédoublonnage lancé en parallèle par Claude Code — ajouté à la liste des départements à reprendre.
**Naissance d'un projet dérivé : `entreprises.bzh`.** Une observation de Jean-Marc sur le modèle économique de nouvelles-entreprises.com (revenu réel sur l'envoi de courrier postal, pas l'abonnement) a fait germer l'idée d'un site indépendant, construit sur la même base `ENTREPRISES`, proposant l'accès aux données d'entreprises via une méthode de recherche originale (isochrone/temps de trajet réel, pas un simple rayon en km — confusion de vocabulaire de Jean-Marc corrigée en cours de route). Jean-Marc a demandé à une autre session Claude un dossier d'analyse de marché complet (tarifs, structure de facturation, contraintes légales) — document de grande qualité, sourcé et prudent sur ses propres limites, déposé et relu en détail. Trouvailles marquantes : **aucun concurrent recensé ne combine fraîcheur quotidienne et sélection géographique fine** (Smappen s'en approche mais avec une base trimestrielle) — le créneau existe réellement. Structure de facturation suggérée : pas de prix à la ligne (course au prix bas déjà occupée), mais deux prestations distinctes, extraction brute alignée marché et "étude de zone" au forfait, seule cette dernière valorisant réellement l'outil. Point juridique découvert en cours de lecture, avec impact technique direct : depuis mars 2023, le statut "non-diffusible" est remplacé par un statut **"P" (diffusion partielle)**, à exclure de toute redistribution commerciale — or `statut_diffusion` n'est capturé nulle part comme colonne exploitable dans `ENTREPRISES` aujourd'hui, seulement noyé dans le JSON brut. Décision actée : ne pas modifier l'extraction en cours (au-delà de 80% de la France) pour ce champ, le rattacher au grand remodelage de Phase1 déjà prévu.
**Objectif du projet précisé par Jean-Marc, plus riche qu'une simple opportunité commerciale.** Visée : mise en ligne à la création de la société (**début 2027**, soit environ un an avant le lancement commercial d'ASPIC), avec cinq finalités explicites — avoir quelque chose en vente dès le début d'activité, s'affronter concrètement à une solution de paiement en ligne, à la facturation électronique, se pousser à communiquer sur les réseaux sociaux via les fonctionnalités mêmes d'ASPIC, et dégager un peu de trésorerie pour financer un futur serveur OVH dédié (non mutualisé). Coût d'entrée dérisoire : le seul nom de domaine, `entreprises.bzh`, acquis dans la foulée. Jean-Marc reste "à l'affût comme un chien de chasse" si un vrai modèle économique se dessinait, sans en faire une condition. Le module de sélection sera construit une fois pour ce site puis répliqué dans ASPIC — et Jean-Marc envisage déjà l'argument marketing du jour où ASPIC sortira : ces fonctionnalités y seront incluses gratuitement.
**Discussion approfondie sur l'enrichissement de contact via LinkedIn, questionnement personnel de Jean-Marc sur l'exposition légale.** Un partenaire commercial de Jean-Marc a vanté l'usage quotidien de Waalaxy sans jamais avoir été banni — argument que Jean-Marc a lui-même écarté par une analogie efficace (rouler à 100 km/h sur une route de campagne sans jamais croiser de gendarme n'en fait pas une pratique sûre). Question soulevée par Jean-Marc : LinkedIn tolérerait-il tacitement ce type d'usage dans une mesure raisonnable ? Réponse nuancée apportée : LinkedIn a un historique documenté de poursuites actives contre des **éditeurs** d'outils d'automatisation (affaire hiQ Labs, mises en demeure), même si la détection au niveau utilisateur individuel reste probabiliste — donc la distinction pertinente pour Jean-Marc n'est pas "combien de temps avant d'être banni" mais "éditeur vs utilisateur isolé", un éditeur étant une cible bien plus exposée. Piste connexe soulevée par Jean-Marc et laissée ouverte, explicitement qualifiée de "borderline" par lui-même : faire réaliser la qualification via Claude Cowork en local chez l'utilisateur plutôt qu'un scraping centralisé — analysé comme une **dilution** du risque (compte individuel, détection plus difficile à grande échelle) mais pas une **élimination** de sa nature (toujours hors CGU si l'extraction reste automatisée), avec la même question de responsabilité d'éditeur si la fonctionnalité était packagée comme telle.
**Mesure réelle du double-comptage inter-départements, chiffrée pour la première fois.** Calcul `COUNT(DISTINCT SIREN)` sur l'ensemble des tables Phase1 existantes : **12 331 573 lignes brutes, 11 142 436 SIREN réellement distincts, soit 1 189 137 doublons (9,6%)** — cohérent avec la découverte de la section 29 (35 vs 44). Poids disque correspondant : ~4,18 Go sur les 43,33 Go actuels de Phase1, présenté comme plafond théorique (le futur modèle allégera ces lignes plutôt que de les supprimer, l'info des établissements secondaires restant voulue). Conséquence rassurante sur la question "va-t-on dépasser 13,4M ?" : le total réellement distinct progresse normalement, la marge restante (Île-de-France complète, Marseille, Corse, DOM-TOM) ajoutera du vrai volume, pas seulement du doublon.
**Feuilles de route ENTREPRISES et Sélection des Entreprises, nouvelles versions.** "1. Évolutions BDD ENTREPRISES" passée en V7 (le point statut "P" y a été ajouté). "3. Sélection des Entreprises" largement étoffée par Jean-Marc (V6) : le projet devient l'épine dorsale d'`entreprises.bzh`, méthodes de recherche listées, trois premières briques d'écran détaillées (avertissement RGPD, localisation géographique, type d'entreprise) accompagnées de leurs maquettes Excel. Relecture complète, correction actée (rayon en km → temps de trajet), confirmation de la sélection géographique limitée à une valeur unique par niveau (région/département/ville), passée en V7 avec les retours intégrés. Deux bugs supplémentaires de génération `.docx` trouvés et corrigés : langue par défaut du document (anglais au lieu de français, `w:lang` absent du style Normal) et titre manquant sur "Sélection des Entreprises" (même bug que la section 29 sur ENTREPRISES, pas encore appliqué à ce fichier-là).
**Statut.** Extraction Sirene : reste l'Île-de-France (91 à 95), la Corse, les DOM-TOM restants (971/972/973/976) et le 13 (Marseille) dans la chaîne principale, puis une reprise séquentielle de 74/75/76/77/88. Fin réaliste estimée pour le week-end du 19-20 septembre, largement avant l'indisponibilité de Jean-Marc le lundi 21 — reprise du chantier de restructuration ENTREPRISES prévue mardi ou mercredi. BFH toujours suspendu (section 28). Accès FTP RNE toujours inexploité. Projet `entreprises.bzh` acté, domaine acquis, encore au stade de réflexion sur les écrans.
`<code>` n'avance pas dans cette section (deux scripts modifiés — `sirene_extract.py` durci — et outillage de feuilles de route, toujours hors du dépôt applicatif ASPIC) ; `<conception>` avance de 29 à 30 — nouvelle version **V1.23.30**.
## 31. Alerte disque serveur — 79 Go de binlogs MySQL jamais purgés (18 septembre 2026)
**Alerte remontée par Jean-Marc** : 160 Go occupés sur 193 Go (84%), sans lien apparent avec l'avancement de l'extraction. Diagnostic mené avec Claude Code : la base `ENTREPRISES` elle-même ne pèse que 52 Go, croissance normale et attendue vu le volume actuel (~13M lignes). Le vrai responsable : **79 Go de journaux binaires MySQL (binlogs) accumulés dans 76 fichiers sur l'instance `mysql2`**, jamais purgés. Cause identifiée, pas une erreur de configuration à proprement parler : la purge automatique était bien active (`binlog_expire_logs_auto_purge=ON`), mais réglée à 30 jours de rétention — un réglage raisonnable pour l'usage CRM habituel, disproportionné face au volume d'écriture exceptionnel généré par une semaine d'extraction intensive (le chantier ayant commencé il y a moins de 30 jours, rien n'avait encore pu être purgé automatiquement).
**Vérifié avant toute action** : aucune réplication ne dépend de ces binlogs (`SHOW REPLICAS` vide) — purge sans risque pour l'intégrité des données. Un premier essai (`PURGE BINARY LOGS`) a été bloqué par le classificateur de sécurité du mode auto de Claude Code, qui l'a signalé comme une possible altération de journaux d'audit — un principe de précaution générique s'appliquant même à un cas légitime. Plutôt que de chercher un contournement, Claude Code a exposé clairement la situation et les deux options à Jean-Marc (exécuter lui-même la commande, ou autoriser explicitement l'action) ; Jean-Marc a choisi d'autoriser, la commande a alors été acceptée et exécutée sans encombre.
**Résultat : 79 Go libérés, disque ramené de 84% à 43% d'occupation** (161 Go → 83 Go utilisés sur 193 Go). Rétention réduite temporairement à 3 jours (`binlog_expire_logs_seconds=259200`) le temps que dure l'extraction intensive, à remettre à 30 jours une fois celle-ci terminée si c'est le réglage voulu en régime de croisière.
**Suite envisagée par Jean-Marc** : intégrer une surveillance de l'espace disque au BackOffice d'ASPIC (déjà home des outils internes — voir section 32 mentionnée dans le journal Index pour le BackOffice existant) plutôt que de compter sur une vérification manuelle occasionnelle. Raison donnée : la console Hostinger elle-même n'affiche pas l'occupation disque en temps réel (données remontées avec un délai notable), d'où l'intérêt d'un indicateur suivi en direct côté ASPIC plutôt que de dépendre de ce tableau de bord tiers. Idée notée, pas encore développée.
`<code>` n'avance pas dans cette section (intervention d'infrastructure sur le serveur, hors dépôt applicatif ASPIC) ; `<conception>` avance de 30 à 31 — nouvelle version **V1.23.31**.
## 32. Bug Corse identifié et corrigé, deux départements orphelins retrouvés, et nom définitif d'entreprises.bzh trouvé — entreprizh.bzh (18-19 septembre 2026)
**Un troisième "monstre" découvert dans la dernière ligne droite de l'extraction : la Corse (2A/2B) revenait systématiquement à 0 entreprise, sans aucune erreur.** En creusant à la demande de Jean-Marc ("chez moi les tables sont vides"), diagnostic isolé et vérifié empiriquement par appels directs à l'API : le paramètre `code_commune` de `recherche-entreprises.api.gouv.fr` échoue silencieusement (0 résultat, HTTP 200, aucune erreur) dès qu'il reçoit un code commune alphanumérique (`2A004`, `2B033`...), alors qu'un code numérique classique (`01004`) fonctionne normalement — bug propre à l'API officielle, pas au script. La Corse est le seul territoire français dont les codes commune INSEE ne sont pas purement numériques, ce qui explique pourquoi ce cas n'était jamais apparu ailleurs. Le paramètre `departement` de la même API, lui, fonctionne correctement même pour `2A`/`2B`. Corrigé dans `sirene_extract.py` : nouvelle fonction générique `fetch_entreprises_for_filter()` (factorisée depuis l'ancienne `fetch_commune_entreprises()`, même cascade de repli section NAF → code NAF précis), et `fetch_departement_entreprises_direct()` qui interroge le département entier d'un coup pour tout code non purement numérique — `run_department()` bascule automatiquement sur ce mode via `dep.isdigit()`.
**Deux départements orphelins supplémentaires retrouvés en vérifiant l'état réel de `ENT_Departements` avant de répondre à une question de Jean-Marc sur le total nationale.** Le **88 (Vosges)**, planté sur le verrou MySQL déjà repéré le 17 septembre (section 30), n'avait en fait jamais été réintégré dans le script de reprise malgré l'intention notée à l'époque — 33 457 lignes partielles en base, aucune ligne dans `ENT_Departements`. Le **48 (Lozère)**, découverte inédite, avait 21 859 entreprises déjà correctement extraites mais aucune ligne finale écrite (interruption juste avant cette dernière étape, cause non identifiée, sans doute un arrêt de processus isolé antérieur) — n'était dans aucune file d'attente. File de reprise (`/tmp/redo_74_75_76_77.sh`) mise à jour et relancée avec la liste complète : **74, 75, 76, 77, 88, 2A, 2B, 48**, toujours strictement séquentielle derrière la fin de la chaîne principale (13/Marseille, dernier département de cette dernière).
**Total national fait le point à la demande de Jean-Marc, chiffré précisément.** 94 départements bien terminés et fiables : **13 088 450 entreprises**. Les 9 restants (13 en cours + les 8 de la file de reprise) totalisaient à cet instant ~1,51M de lignes déjà en base mais partielles pour la plupart — notamment 76 et 77, très en dessous de ce qu'ils devraient contenir. Confirmation chiffrée d'une intuition de Jean-Marc : Paris (75) affichait déjà 831 979 entreprises avant même la fin de sa reprise, proche du million pressenti.
**Exploration visuelle du logo (détecteur de métaux/pépite) refermée pour l'instant, sans aboutir.** Jean-Marc a itéré des dizaines de fois avec l'outil Pippit AI sur une variante ajoutant des traces de balayage au sol, jusqu'à ce que l'outil lui-même signale son incapacité à retoucher précisément (absence de calques, dérive cumulative après trop d'itérations). Une tentative de Claude Code de proposer une alternative vectorielle (SVG, deux versions publiées en Artifact) a été jugée trop abstraite/schématique par rapport au rendu quasi-photo recherché — écartée. Piste de sortie identifiée mais reportée par Jean-Marc à une prochaine session (fatigue du sujet ce soir-là) : retoucher manuellement l'image déjà quasi satisfaisante dans un éditeur à calques (Photopea suggéré, gratuit, en ligne) plutôt que de continuer à batailler avec la génération IA — le changement demandé (supprimer une ligne, limiter le nombre de traces) est une retouche localisée sur fond presque uni, largement à la portée d'un éditeur classique.
**Recherche du nom de domaine d'entreprises.bzh reprise et closes après plusieurs allers-retours, tous vérifiés avant achat conformément au principe explicite de Jean-Marc ("éviter les problèmes plutôt que devoir les gérer", répété ce soir-là).** `prospecter.bzh`, pourtant validé une première fois, écarté après vérification INPI : une marque française active **"PROSPECTER"** (FR4532717) existe, détenue par **TESSI**, déposée en classe 35 (publicité, gestion commerciale, télémarketing, acquisition de données) — recoupement direct avec l'activité visée, risque jugé trop sérieux malgré la nature partiellement descriptive du mot. `fichiers-prospects.fr`/`.com`, repérés disponibles par Jean-Marc, vérifiés propres (aucune marque, aucun site homonyme) mais la piste a été abandonnée en cours de route sans avoir été formellement invalidée, Jean-Marc étant parti explorer un concurrent adjacent (`easyfichiers.com`) puis, de fil en aiguille, l'API de Pappers. Vérification de l'API Pappers menée à la demande de Jean-Marc : modèle freemium (100 crédits gratuits à l'inscription puis facturation au crédit, abonnements à partir de 20€/mois) — mais surtout, ses CGU interdisent explicitement toute extraction/réutilisation de sa base de données et, clause spécifique, toute utilisation "pour alimenter, enrichir ou nourrir un service, produit, système ou solution tiers" : exactement l'usage qu'envisageait Jean-Marc pour les numéros de téléphone qu'il y avait repérés (donnée absente des sources SIRENE/INPI/BODACC officielles, donc nécessairement un enrichissement propriétaire de Pappers) — piste abandonnée sur ce constat, sans ambiguïté.
**Nom finalement retenu : `entreprizh.bzh`** (jeu de mots entreprise/Breizh), trouvé par Jean-Marc, disponible et déjà accordé par l'association gestionnaire du `.bzh` — contrairement à `entreprises.bzh` en son temps. Vérifié par Claude Code (aucune marque, aucun site homonyme, le plus proche étant `entreprendre.bzh` et `mon-entreprise.bzh`, tous deux trop différents pour prêter à confusion) puis confirmé directement par Jean-Marc lui-même sur data.inpi.fr : aucun résultat. Achat décidé le soir même.
**Statut.** Extraction Sirene : chaîne principale sur le 13 (Marseille, dernier département), file de reprise (74/75/76/77/88/2A/2B/48) prête à démarrer automatiquement à sa suite, code Corse corrigé et non encore testé en conditions réelles — premier point à vérifier à la prochaine reprise de session. Nom de domaine d'`entreprises.bzh` définitivement tranché (`entreprizh.bzh`), achat en cours par Jean-Marc. Logo toujours en réflexion, retouche manuelle envisagée mais pas commencée. Restructuration ENTREPRISES (siège/filiale, partitionnement, statut "P") toujours en attente de la fin complète de l'extraction, Jean-Marc envisageant d'ouvrir une session neuve à ce moment-là plutôt que de poursuivre celle-ci.
`<code>` avance : correctif du bug Corse dans `src/scripts/sirene_extract.py` (hors dépôt applicatif ASPIC comme les autres scripts d'extraction) ; `<conception>` avance de 31 à 32 — nouvelle version **V1.23.32**.
## 33. Extraction Sirene France entière terminée — 15 972 915 entreprises (19-20 septembre 2026)
**Fin complète de l'extraction, sans incident sur la dernière ligne droite.** La chaîne principale a bouclé le 13 (Marseille) dans la nuit du 18 au 19 septembre, puis la file de reprise a enchaîné automatiquement et sans supervision sur les huit départements restants : 74 (terminé dans la nuit), 75/Paris (le plus gros morceau, ~15h de traitement à lui seul vu son volume), 76, 77, 88, puis **2A et 2B — premier vrai test du correctif Corse de la section 32, réussi du premier coup** (55 190 et 54 823 entreprises, aucune anomalie), et enfin 48 pour clore la liste. Le recalcul des statistiques par commune (`ent_communes_stats.py`) a suivi automatiquement en bout de chaîne (34 916 communes mises à jour).
**Total national final : les 101 départements de France (métropole + DOM-TOM) sont extraits, pour 15 972 915 entreprises** — sensiblement au-dessus des projections successives de la conversation (8,5M initialement, recalibré à 13,4M section 29, puis à ~15-15,5M au fil de la reprise des derniers départements), l'écart provenant essentiellement des trois départements les plus lourds (Paris, Seine-Maritime, Seine-et-Marne) qui étaient restés très sous-captés depuis l'incident du 17 septembre jusqu'à leur réextraction complète. Disque à 60% d'occupation (115 Go/193 Go) une fois tout terminé, aucun processus d'extraction encore actif.
**Statut.** Extraction Sirene : **terminée, définitivement**. Plus aucun script en tâche de fond. Le chantier de restructuration `ENTREPRISES` (siège/filiale, partitionnement natif MySQL évoqué section 29, split de `Phase4`, capture du statut "P") peut désormais démarrer sur des données complètes — Jean-Marc prévoit d'ouvrir une session neuve pour ce chantier plutôt que de poursuivre celle-ci. Nom de domaine `entreprizh.bzh` (section 32) en cours d'achat. Rétention des binlogs MySQL toujours réduite à 3 jours (section 31) — à remettre à 30 jours maintenant que la période d'écriture intensive est terminée, point non encore traité.
`<code>` n'avance pas dans cette section (fin d'un traitement déjà en cours, aucune modification) ; `<conception>` avance de 32 à 33 — nouvelle version **V1.23.33**.
+69 -10
View File
@@ -19,6 +19,7 @@ Usage :
(ou export ENTREPRISES_DB_PASS=... pour ne pas passer le mot de passe en argument) (ou export ENTREPRISES_DB_PASS=... pour ne pas passer le mot de passe en argument)
""" """
import argparse import argparse
import http.client
import json import json
import os import os
import sys import sys
@@ -240,15 +241,39 @@ def log(msg):
print(msg, file=sys.stderr, flush=True) print(msg, file=sys.stderr, flush=True)
def http_get_json(url, retries=5): def http_get_json(url, retries=8):
# Durci le 17/09/2026 : la version precedente ne rattrapait que
# urllib.error.URLError/TimeoutError, avec un backoff court (1-5s) --
# insuffisant face a deux incidents reels constates le meme jour :
# http.client.RemoteDisconnected (pas une URLError, jamais rattrapee,
# a fait planter le departement 74) et HTTP 429 Too Many Requests
# soutenu (deux extractions tournant par erreur en parallele ont fait
# plafonner l'API, cascadant sur 3 departements avant plantage). Meme
# esprit que le durcissement deja fait sur inpi_bilans_backfill.py.
for attempt in range(retries): for attempt in range(retries):
try: try:
with urllib.request.urlopen(url, timeout=20) as resp: with urllib.request.urlopen(url, timeout=20) as resp:
return json.load(resp) return json.load(resp)
except (urllib.error.URLError, TimeoutError) as e: except urllib.error.HTTPError as e:
if e.code == 429:
if attempt == retries - 1: if attempt == retries - 1:
raise raise
time.sleep(1 + attempt) retry_after = e.headers.get("Retry-After") if e.headers else None
wait = int(retry_after) if retry_after and retry_after.isdigit() else min(10 * (2 ** attempt), 300)
log(f" !! 429 Too Many Requests -- pause de {wait}s avant nouvelle tentative "
f"({attempt + 1}/{retries})")
time.sleep(wait)
continue
if attempt == retries - 1:
raise
log(f" retry apres erreur HTTP ({e})")
time.sleep(2 + attempt)
except (urllib.error.URLError, TimeoutError, http.client.HTTPException,
ConnectionError) as e:
if attempt == retries - 1:
raise
log(f" retry apres erreur reseau ({e})")
time.sleep(2 + attempt)
# Paris, Lyon et Marseille (PLM) : dans les donnees Sirene, le siege d'une # Paris, Lyon et Marseille (PLM) : dans les donnees Sirene, le siege d'une
@@ -389,13 +414,16 @@ def paginate_all(params, seen):
return out return out
def fetch_commune_entreprises(code_commune, seen): def fetch_entreprises_for_filter(base, seen, label):
base = {"code_commune": code_commune, "etat_administratif": "A"} """Cascade de repli generique (section NAF puis code NAF precis si
plafonnee a CAP resultats), utilisee aussi bien pour un filtre par
commune que, depuis le 18/09/2026, par departement entier (cas 2A/2B,
cf. fetch_departement_entreprises_direct)."""
total = probe_total(base) total = probe_total(base)
if total < CAP: if total < CAP:
return paginate_all(base, seen) return paginate_all(base, seen)
log(f" commune {code_commune} plafonnee ({total}), repli par section NAF") log(f" {label} plafonnee ({total}), repli par section NAF")
out = [] out = []
for section in NAF_SECTIONS: for section in NAF_SECTIONS:
sec_params = dict(base, section_activite_principale=section) sec_params = dict(base, section_activite_principale=section)
@@ -410,7 +438,7 @@ def fetch_commune_entreprises(code_commune, seen):
# ex. Rennes/section L). On decoupe par code NAF precis (732 codes possibles) -- # ex. Rennes/section L). On decoupe par code NAF precis (732 codes possibles) --
# un code implique deja sa section, donc on l'utilise seul, sans le combiner # un code implique deja sa section, donc on l'utilise seul, sans le combiner
# au filtre de section. # au filtre de section.
log(f" commune {code_commune} section {section} encore plafonnee ({sec_total}), " log(f" {label} section {section} encore plafonnee ({sec_total}), "
f"repli par code NAF precis") f"repli par code NAF precis")
for code in codes_for_section(section): for code in codes_for_section(section):
code_params = dict(base, activite_principale=code) code_params = dict(base, activite_principale=code)
@@ -418,12 +446,30 @@ def fetch_commune_entreprises(code_commune, seen):
if code_total == 0: if code_total == 0:
continue continue
if code_total >= CAP: if code_total >= CAP:
log(f" !! commune {code_commune} code {code} encore plafonnee " log(f" !! {label} code {code} encore plafonnee "
f"({code_total}) -- a decouper manuellement (ex: par tranche_effectif_salarie)") f"({code_total}) -- a decouper manuellement (ex: par tranche_effectif_salarie)")
out.extend(paginate_all(code_params, seen)) out.extend(paginate_all(code_params, seen))
return out return out
def fetch_commune_entreprises(code_commune, seen):
base = {"code_commune": code_commune, "etat_administratif": "A"}
return fetch_entreprises_for_filter(base, seen, f"commune {code_commune}")
def fetch_departement_entreprises_direct(dep, seen):
"""Repli specifique aux departements dont le code commune INSEE n'est pas
purement numerique (2A, 2B -- Corse) : le parametre `code_commune` de
l'API recherche-entreprises echoue silencieusement (0 resultat, HTTP 200,
aucune erreur) sur ces codes alphanumeriques, verifie empiriquement le
18/09/2026 (2A004/2B033 -> 0 alors que 01004 -> 6124). Le parametre
`departement`, lui, fonctionne normalement -- on interroge donc le
departement entier d'un coup, avec la meme cascade de repli que pour une
commune plafonnee."""
base = {"departement": dep, "etat_administratif": "A"}
return fetch_entreprises_for_filter(base, seen, f"departement {dep}")
def upsert_entreprise(conn, table, row): def upsert_entreprise(conn, table, row):
cols = list(row.keys()) cols = list(row.keys())
placeholders = ", ".join(["%s"] * len(cols)) placeholders = ", ".join(["%s"] * len(cols))
@@ -504,8 +550,9 @@ def run_department(conn, dep):
seen = set() seen = set()
total_upserts = 0 total_upserts = 0
total_finances = 0 total_finances = 0
for c in communes_geo:
results = fetch_commune_entreprises(c["code"], seen) def store(results):
nonlocal total_upserts, total_finances
for r in results: for r in results:
row = map_entreprise(r) row = map_entreprise(r)
if row["ENT_Siren"]: if row["ENT_Siren"]:
@@ -514,6 +561,18 @@ def run_department(conn, dep):
if row["ENT_FinancesAnnee"] is not None: if row["ENT_FinancesAnnee"] is not None:
upsert_finances(conn, table3, row) upsert_finances(conn, table3, row)
total_finances += 1 total_finances += 1
if not dep.isdigit():
# Code commune INSEE alphanumerique (2A, 2B) : le filtre code_commune
# de l'API echoue silencieusement, on interroge le departement entier
# d'un coup (cf. fetch_departement_entreprises_direct).
results = fetch_departement_entreprises_direct(dep, seen)
store(results)
log(f" departement {dep} (requete directe) : {len(results)} entreprises")
else:
for c in communes_geo:
results = fetch_commune_entreprises(c["code"], seen)
store(results)
if results: if results:
log(f" {c['nom']} ({c['code']}) : {len(results)} entreprises") log(f" {c['nom']} ({c['code']}) : {len(results)} entreprises")
+1 -1
View File
@@ -50,7 +50,7 @@
* capacité à détecter un déploiement resté en retard. * capacité à détecter un déploiement resté en retard.
*/ */
const ASPIC_VERSION = 'V1.23.29'; const ASPIC_VERSION = 'V1.23.33';
if (basename($_SERVER['SCRIPT_NAME']) === 'version.php') { if (basename($_SERVER['SCRIPT_NAME']) === 'version.php') {
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');