# 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êteMIN(invoice_date)global etMIN(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_numberdistincts) 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_eventssur un SKU deEPI_SKUS→ flagviewed_epi. - Recherche EPI : termes EPI dans
ga4_search_events(mots-clés : EPI, sécurité, gant, casque, chaussure, masque, haute visibilité, lunette, protection…) → flagsearched_epi. - Engagement :
n_sessions,n_engaged_sessions,n_checkout_sessions(depuisga4_sessions), add-to-cart EPI (depuiscustomer_timelineevent_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).