⚡ Swarm Architecture

FR EPI « White Space » — Design Spec

# FR EPI « White Space » — Design Spec

Date: 2026-05-29 Branch: feat/basket-bundle-rules Author: Pierre Samson (+ Claude) Status: Approved params, design blind (Docker DBs offline at authoring time — numbers wired later)

1. Objectif

Répondre à trois questions sur la base clients FR de Lyréco, KPI centrés workwear = EPI :

1. KPIs EPI par secteur — time-to-purchase + taux de conversion (CR) sur l'EPI, plus fréquence × panier moyen (incl. #SKU). 2. White space — quels secteurs devraient acheter de l'EPI mais n'achètent pas, et quels comptes FR concrets ciblér. 3. Comportement digital — profiler le comportement web des comptes identifiés.

2. Décisions verrouillées

| Paramètre | Choix | Note | |---|---|---| | Dimension secteur | A1 — US SIC 1987, niveau communauté | Pas de NAF FR en base ; pas de SIC par compte. Secteur hérité via la communauté firmographique. | | Définition workwear | W1 — Section 003 "Santé, Sécurité, EPI" | Filtre catalogue actif appliqué. | | Fenêtre | YTD 2026 : 2026-01-01 → 2026-05-29 | Toutes les agrégations bornées à cette fenêtre. | | « Compte actif » | ≥ 3 commandes OU ≥ 500 € CA sur la fenêtre | | | « White-space » | 0 € EPI sur la fenêtre | Option future : < 1 % du CA. | | Secteur « should buy » | secteurs attendus-EPI (Construction, Manufacturing & Industry, Healthcare & Social Care) — par nature du métier, quelle que soit la pénétration | Révisé 2026-06-01 (avant : lift ≥ 1.0). Le lift reste affiché pour montrer la sous-pénétration. | | Tri de la cible | score opportunité (cf. §6) | | | Livrable | Excel dans exports/ | Pas de panel dashboard. |

Limites assumées (à écrire dans le README de la sortie)

  • Le secteur est de l'US SIC 1987 (libellés métier US, ex. 80=Hôpitaux, 73=Services aux entreprises), pas du NAF français.
  • Le secteur est au niveau du cluster (communauté), pas le code exact de chaque entreprise : un compte reçoit le SIC dominant de sa communauté.
  • Fenêtre YTD courte (~5 mois) : les KPIs de volume (CR, fréquence, panier, #SKU) sont bornés YTD. Le time-to-purchase EPI est l'exception — il est calculé en latence réelle all-time (hors fenêtre, cf. §5) pour ne pas être tronqué.

3. Architecture & données

Script unique : scripts/analyze_fr_epi_whitespace.py (pattern dual-DB des scripts existants — export_prospect_sector_rollup.py, export_first_purchase_top_sku.py).

Règle cross-DB : jamais de jointure inter-DB. Toujours 2 étapes (pull IDs dans une DB → ANY(%s) dans l'autre).

| Donnée | Table | DB | |---|---|---| | Comptes FR | ecom_accounts (source_country='FR') | PG :5433 | | Compte → communauté | account_firmographic_community (FR) | PG :5433 | | Communauté → SIC | firmographic_communities.dominant_sic_group | PG :5433 | | SIC → méta-secteur | sic_meta_sectors (volet us_sic_1987) | PG :5433 | | Produits / section | ecom_products.section_code | PG :5433 | | Lignes de commande FR | ecom_order_lines (source_country='FR') | TS :5434 | | Lignes de facture FR | ecom_invoice_lines (source_country='FR') | TS :5434 | | Time-to-purchase online | visitor_journey.days_first_session_to_order | PG :5433 | | Comportement web | ga4_sessions, ga4_product_events, ga4_search_events, customer_timeline | TS :5434 |

Source de vérité transactionnelle : ecom_invoice_lines (facturé = réel). On utilise order_date/invoice_date selon la table ; choix par défaut = invoice_lines pour le CA réalisé, cohérent avec la session passée.

4. Définition de l'ensemble SKU EPI (W1)

` SELECT product_reference FROM ecom_products WHERE section_code = '003' AND item_category_code = 'NORM' AND status_code NOT IN ('98','99') AND not_salable_flag = false AND not_visible_flag = false ` → EPI_SKUS (liste Python), bridge vers TS via product_reference = ANY(%s).

5. KPIs — définitions précises

Fenêtre = YTD (invoice_date BETWEEN '2026-01-01' AND '2026-05-29'), source_country='FR'.

  • CR EPI (secteur) = (# comptes du secteur avec ≥1 ligne EPI) / (# comptes actifs du secteur). Actif = ≥1 commande toutes catégories dans la fenêtre.
  • Time-to-purchase EPI (médiane par secteur, sur les acheteurs EPI) = première facture EPI (all-time) − première facture toutes cat. (all-time) du compte. Calculé hors fenêtre YTD (latence réelle, option (b)) : on requête MIN(invoice_date) global et MIN(invoice_date WHERE product_reference = ANY(EPI_SKUS)) global, sans borne basse. Seul ce KPI ignore la fenêtre YTD ; tous les autres restent bornés. Colonne secondaire : visitor_journey.days_first_session_to_order (lens online) quand le bridge visiteur existe.
  • Fréquence (par compte) = # commandes distinctes (invoice_number distincts) sur la fenêtre.
  • Panier moyen (par compte) = SUM(sales_amount) / # commandes.
  • #SKU moyen / commande = moyenne, par commande, du COUNT(DISTINCT product_reference).
  • Ces 3 dernières metrics sont calculées global (toutes cat.) et EPI seul.

6. Bloc « should buy but don't »

1. Secteurs attendus-EPI (révisé 2026-06-01) = EPI_EXPECTED_SECTORS = {Construction, Manufacturing & Industry, Healthcare & Social Care} : secteurs où l'EPI est requis par nature du métier, indépendamment de la pénétration. Le lift (CR_EPI(secteur) / CR_EPI(base FR connue)) reste calculé et affiché pour montrer où ces secteurs sous-achètent (ex. Construction lift 0.86 = sous-pénétré, donc gisement). (Définition initiale, abandonnée : lift ≥ 1.0 — excluait la Construction, contre-intuitif.) 2. Comptes white-space = comptes actifs (≥3 cmd OU ≥500 €) dans un secteur attendu-EPI, avec 0 € EPI sur la fenêtre. 3. Score opportunité par compte : ` score = pénétration_EPI_secteur × CA_YTD_compte × (1 + intention_digitale) `

  • pénétration_EPI_secteur = CR EPI du secteur (0–1).
  • CA_YTD_compte = CA total toutes catégories sur la fenêtre.
  • intention_digitale = boost ∈ {0, 0.5, 1.0} selon signal web EPI (cf. §7) : 0 = aucun signal, 0.5 = recherche EPI, 1.0 = vue page produit EPI.

7. Bloc comportement digital

Pour chaque compte white-space, bridge account_number → web (TS). Métriques sur la fenêtre :

  • Vue page produit EPI : présence dans ga4_product_events sur un SKU de EPI_SKUS → flag viewed_epi.
  • Recherche EPI : termes EPI dans ga4_search_events (mots-clés : EPI, sécurité, gant, casque, chaussure, masque, haute visibilité, lunette, protection…) → flag searched_epi.
  • Engagement : n_sessions, n_engaged_sessions, n_checkout_sessions (depuis ga4_sessions), add-to-cart EPI (depuis customer_timeline event_type ADD_TO_CART sur SKU EPI).
  • Tier : A = viewed_epi (regarde mais n'achète pas — très actionnable) ; B = signal digital nul (besoin outbound/notoriété).

8. Livrable Excel — exports/fr_epi_whitespace_FR_2026-05-29.xlsx

| Feuille | Contenu | |---|---| | README | Méthodo, fenêtre YTD, définitions KPI, limites A1/W1. | | Sector EPI KPIs | Benchmark par méta-secteur : CR EPI, lift, time-to-purchase (médiane), fréq, panier, #SKU (global + EPI). | | White-space accounts | Liste cible (compte, secteur, CA YTD, fréq, panier, score opportunité, tier), triée par score décroissant. | | Digital behavior | Par compte cible : viewed_epi, searched_epi, n_sessions, engaged, checkout, add-to-cart EPI. | | Action list | Top comptes = score opportunité, Tier A en tête (highlight). |

Conventions : noms de feuilles sans / (utiliser — / ·) ; strip tzinfo avant écriture openpyxl ; PYTHONIOENCODING=utf-8.

9. Hors scope (YAGNI)

  • Pas d'import NAF externe (INSEE/Sirene) — repoussé.
  • Pas de panel dashboard.
  • Pas de variante W2/W3/W4/W5.
  • Pas de A/B split (≠ session précédente).