Enrichissement et mapping - PT2QE¶
Vue d'ensemble¶
PT2QE enrichit chaque offre de prix (client × article) avec 5 dimensions clés avant de calculer les recommandations :
- FG_HM : Flag hors mercuriale ('0' ou '1')
- UNIVERS : Périmètre (ZOOM1/ZOOM2/ZOOM3)
- TYPE_CLIENT : Type client normalisé
- TYPE_RESTAURANT : Type restaurant normalisé
- GEO : Zone géographique
Ces dimensions permettent ensuite de matcher chaque offre avec son corridor PT1CE correspondant.
⚠️ IMPORTANT : PT2QE nécessite que PT1CE ait été exécuté AU PRÉALABLE pour que les tables PT1CE_OPTIMAL_* existent.
🔒 PÉRIMÈTRE ACTUEL : PT2QE traite UNIQUEMENT les offres ZOOM1. Les offres ZOOM2 et ZOOM3 sont extraites mais exclues du calcul.
Scope temporel de l'enrichissement¶
Période d'analyse¶
L'enrichissement utilise l'historique transactionnel des 4 derniers trimestres fiscaux COMPLETS :
- Source :
SYS_MD_CALENDRIER_SYSCO - Logique : Trimestres terminés dont la dernière semaine (
MAX(ID_SEM)) est antérieure à la date d'exécution - Module :
utils/period_manager.py→get_last_four_complete_fiscal_quarters()
Exemple d'exécution le 03/11/2025 :
Trimestre en cours : 2025_Q04 (non complet)
→ PT2QE utilise : 2025_Q03, 2025_Q02, 2025_Q01, 2024_Q04
→ Période : du 2024-04-01 au 2025-09-30
📍 Comment vérifier la période utilisée :
-- Derniers trimestres complets
SELECT
ID_EXF || '_Q' || NO_TRF as QUARTER_CODE,
MIN(ID_SEM) as START_DATE,
MAX(ID_SEM) as END_DATE
FROM SYS_MD_CALENDRIER_SYSCO
WHERE MAX(ID_SEM) < SYSDATE
GROUP BY ID_EXF, NO_TRF
ORDER BY ID_EXF DESC, NO_TRF DESC
FETCH FIRST 4 ROWS ONLY;
⚠️ Si erreur "Impossible de trouver 4 trimestres complets" : - Le calendrier fiscal manque de données historiques - Contacter l'équipe data pour charger des trimestres passés
1. Récupération du FG_HM historique¶
Concept¶
FG_HM = Flag Hors Mercuriale
- '0' : Offre mercuriale (tarif catalogue standard)
- '1' : Offre hors mercuriale (conditions spéciales négociées)
Nature : Donnée dynamique récupérée depuis l'historique transactionnel (dernière transaction connue).
Extraction SQL¶
Requête complète (dans extract_price_offers.py) :
WITH LAST_FG_HM AS (
SELECT
ID_CLN_KEY,
ID_CLN,
ID_ART,
FG_HM as LAST_FG_HM,
ID_MERC_HM
FROM (
SELECT
f.ID_CLN_KEY,
c.ID_CLN,
f.ID_ART,
-- Standardisation FG_HM
CASE
WHEN TRIM(f.FG_HM) = 'X' THEN '1'
ELSE '0'
END AS FG_HM,
TRIM(f.ID_MERC_HM) AS ID_MERC_HM,
-- Sélection dernière transaction
ROW_NUMBER() OVER (
PARTITION BY c.ID_CLN, f.ID_ART
ORDER BY f.DT_CDE DESC, f.ID_FAC DESC
) as RN
FROM SYS_FACTURE_LIGNE f
JOIN SYS_MD_CLIENT c ON f.ID_CLN_KEY = c.ID_CLN_KEY
JOIN SYS_MD_ARTICLE a ON f.ID_ART = a.ID_ART
LEFT JOIN PRP_LABELS p ON f.ID_PRP = p.ID_PRP_KEY
WHERE f.DT_CDE BETWEEN {start_date} AND {end_date}
AND UPPER(TRIM(c.ID_TC_CG)) <> 'AUTRES'
AND a.ID_GMM <> 'PSN'
AND a.LC_HIC_SYSCO_N2 NOT IN ('Divers', 'Freezer')
AND COALESCE(TRIM(f.FG_PRESTA), '0') = '0'
AND COALESCE(TRIM(f.FG_MARCHANDISE), ' ') IN ('X', '1')
AND f.MT_GM4 IS NOT NULL
AND f.MT_CAB > 0
AND f.QT_UF > 0
AND p.ID_PRP_KEY IS NOT NULL -- Exclut prix promo
)
WHERE RN = 1 -- Garde uniquement la dernière transaction
)
Filtres appliqués :
| Filtre | Valeur | Raison |
|---|---|---|
ID_TC_CG |
<> 'AUTRES' |
Exclusion clients hors périmètre |
ID_GMM |
<> 'PSN' |
Exclusion gamme Personnel |
LC_HIC_SYSCO_N2 |
NOT IN ('Divers', 'Freezer') |
Exclusion catégories non standards |
FG_PRESTA |
= '0' |
Exclusion prestations |
FG_MARCHANDISE |
IN ('X', '1') |
Marchandise uniquement |
MT_CAB |
> 0 |
CA positif |
QT_UF |
> 0 |
Quantité positive |
| Prix promo | Exclus via PRP_LABELS |
Focus sur prix catalogue |
📍 Comment vérifier le FG_HM d'un client×article :
-- Dernière valeur FG_HM connue
SELECT
c.ID_CLN,
c.LC_CLN,
f.ID_ART,
a.LC_ART,
CASE WHEN TRIM(f.FG_HM) = 'X' THEN '1' ELSE '0' END as FG_HM,
f.DT_CDE,
f.ID_FAC
FROM SYS_FACTURE_LIGNE f
JOIN SYS_MD_CLIENT c ON f.ID_CLN_KEY = c.ID_CLN_KEY
JOIN SYS_MD_ARTICLE a ON f.ID_ART = a.ID_ART
WHERE c.ID_CLN = 'CLIENT123'
AND f.ID_ART = 'ART456'
AND f.DT_CDE >= ADD_MONTHS(SYSDATE, -12)
ORDER BY f.DT_CDE DESC, f.ID_FAC DESC
FETCH FIRST 1 ROW ONLY;
Cas particuliers FG_HM¶
Cas 1 : Client sans historique
- Nouveau client ou client inactif
- LAST_FG_HM reste NULL
- Impact : Offre exclue du périmètre PT2QE (pas de mapping TYPE_CLIENT possible)
Cas 2 : Article jamais commandé par le client
- LAST_FG_HM reste NULL
- Impact : Offre exclue du périmètre PT2QE
Cas 3 : Changement de statut HM - Client passe de mercuriale ('0') à hors mercuriale ('1') - Comportement : PT2QE prend la valeur la plus récente - Effet : Le TYPE_CLIENT peut changer entre 2 exécutions
📍 Comment forcer un FG_HM spécifique :
PT2QE ne permet pas de forcer manuellement. La valeur provient TOUJOURS de l'historique.
Workaround si nécessaire :
1. Créer une transaction factice dans SYS_FACTURE_LIGNE avec le bon FG_HM
2. Relancer PT2QE
2. Calcul de l'UNIVERS¶
Définition¶
UNIVERS détermine le niveau de granularité du corridor :
- ZOOM1 : Granularité maximale (4 dimensions : TYPE_CLIENT × TYPE_RESTAURANT × GEO × ID_ART)
- ZOOM2 : Granularité intermédiaire
- ZOOM3 : Granularité minimale (agrégation nationale)
⚠️ PT2QE VERSION ACTUELLE : Traite UNIQUEMENT ZOOM1
Règles de calcul¶
Module : extract_price_offers.py → CTE OFFERS_WITH_UNIVERS
Règles implémentées :
CASE
-- ZOOM1 : RCI PI avec ou sans KAM
WHEN ID_TC_CG = 'RCI' AND ID_TC_CIBLE = 'PINDEP' THEN 'ZOOM1'
-- ZOOM1 : RCI GI SANS KAM
WHEN ID_TC_CG = 'RCI' AND ID_TC_CIBLE = 'GINDEP'
AND ID_KAM = 'NO_KAM' THEN 'ZOOM1'
-- ZOOM2 : RCI GI AVEC KAM
WHEN ID_TC_CG = 'RCI' AND ID_TC_CIBLE = 'GINDEP'
AND ID_KAM <> 'NO_KAM' THEN 'ZOOM2'
-- ZOOM1 : FG_HM = '1' (hors mercuriale) - HORS RSC
WHEN FG_HM = '1' AND (
(ID_TC_CG = 'RSI' AND ID_TC_CIBLE IN ('MP_AO_', 'GR_A_G', 'MPMAPA', 'SRCREG', 'CA_REG', 'CR_REG'))
OR (ID_TC_CG = 'RCI' AND ID_TC_CIBLE IN ('PERSYS', 'PERCLI', 'AS_AUT'))
) THEN 'ZOOM1'
-- ZOOM1 : RSC avec ID_MERC_HM = 'HM'
WHEN ID_TC_CG = 'RSC' AND ID_TC_CIBLE IN ('CR_NAT', 'SRCNAT', 'MP_NAT', 'CA_NAT')
AND TRIM(ID_MERC_HM) = 'HM' THEN 'ZOOM1'
-- ZOOM2 : FG_HM = '0' (mercuriale)
WHEN FG_HM = '0' AND (
(ID_TC_CG = 'RSI' AND ID_TC_CIBLE IN ('MP_AO_', 'SRCREG', 'MPMAPA', 'CA_REG', 'CR_REG'))
OR (ID_TC_CG = 'RCI' AND ID_TC_CIBLE = 'GC_REG')
) THEN 'ZOOM2'
-- ZOOM2 : RCI GC_REG avec FG_HM = '1'
WHEN ID_TC_CG = 'RCI' AND ID_TC_CIBLE = 'GC_REG'
AND FG_HM = '1' THEN 'ZOOM2'
-- ZOOM3 : RSC avec ID_MERC_HM <> 'HM'
WHEN ID_TC_CG = 'RSC' AND ID_TC_CIBLE IN ('SRCNAT', 'CR_NAT', 'MP_NAT', 'CA_NAT')
AND TRIM(ID_MERC_HM) <> 'HM' THEN 'ZOOM3'
-- ZOOM3 : RCC
WHEN ID_TC_CG = 'RCC' AND ID_TC_CIBLE IN ('RCHNAT', 'GCN3PL', 'GCNDIR') THEN 'ZOOM3'
ELSE 'HORS PERIMETRE'
END as UNIVERS
📍 Comment vérifier l'UNIVERS d'une offre :
SELECT
c.ID_CLN,
c.LC_CLN,
c.ID_TC_CG,
c.ID_TC_CIBLE,
fh.FG_HM,
c.ID_KAM,
fh.ID_MERC_HM,
-- Calcul UNIVERS
CASE
WHEN c.ID_TC_CG = 'RCI' AND c.ID_TC_CIBLE = 'PINDEP' THEN 'ZOOM1'
-- [autres règles...]
ELSE 'HORS PERIMETRE'
END as UNIVERS_CALCULE
FROM SYS_MD_CLIENT c
LEFT JOIN LAST_FG_HM fh ON c.ID_CLN = fh.ID_CLN
WHERE c.ID_CLN = 'CLIENT123';
📍 Comment ajouter une nouvelle règle UNIVERS :
- Éditer
extract_price_offers.py - Localiser la CTE
OFFERS_WITH_UNIVERS - Ajouter une clause
WHENdans leCASE
Exemple - ajouter RCI NOUVEAU_TYPE en ZOOM1 :
- Relancer PT2QE Option 1
3. Enrichissement TYPE_CLIENT¶
Table de mapping¶
Table : PT0CE_TYPE_CLIENT_MAPPING
Structure :
| Colonne | Type | Description |
|---|---|---|
TYPE_CLIENT |
VARCHAR2(100) | Type client normalisé (ex: "RCI PI GI") |
UNIVERS |
VARCHAR2(10) | ZOOM1/ZOOM2/ZOOM3 |
ID_TC_CG |
VARCHAR2(10) | Code groupe client SAP |
ID_TC_CIBLE |
VARCHAR2(10) | Code cible client SAP |
FG_HM |
VARCHAR2(1) | '0' ou '1' |
PRB |
NUMBER(1) | 1=PRB_RC, 2=PRB_COLL |
📍 Comment consulter les mappings existants :
-- Liste complète
SELECT
TYPE_CLIENT,
UNIVERS,
ID_TC_CG,
ID_TC_CIBLE,
FG_HM,
PRB
FROM PT0CE_TYPE_CLIENT_MAPPING
ORDER BY UNIVERS, TYPE_CLIENT, FG_HM;
-- Compter par UNIVERS
SELECT
UNIVERS,
COUNT(*) as NB_MAPPINGS,
COUNT(DISTINCT TYPE_CLIENT) as NB_TYPE_CLIENT
FROM PT0CE_TYPE_CLIENT_MAPPING
GROUP BY UNIVERS
ORDER BY UNIVERS;
Processus de jointure¶
Module : extract_price_offers.py → jointure dans le SELECT final
SELECT
ou.*,
COALESCE(tc.TYPE_CLIENT, 'Hors référentiel') AS TYPE_CLIENT,
-- [autres colonnes...]
FROM OFFERS_WITH_UNIVERS ou
LEFT JOIN PT0CE_TYPE_CLIENT_MAPPING tc
ON tc.UNIVERS = ou.UNIVERS
AND tc.ID_TC_CG = ou.ID_TC_CG
AND tc.ID_TC_CIBLE = ou.ID_TC_CIBLE
AND tc.FG_HM = ou.FG_HM
WHERE ou.UNIVERS = 'ZOOM1' -- Filtre ZOOM1 immédiat
AND tc.TYPE_CLIENT IS NOT NULL -- Exclut offres non mappées
⚠️ Clé de jointure :
- UNIVERS (calculé dynamiquement)
- ID_TC_CG
- ID_TC_CIBLE
- FG_HM (depuis historique)
📍 Comment tester une jointure TYPE_CLIENT :
-- Test de jointure pour un client
SELECT
c.ID_CLN,
c.LC_CLN,
c.ID_TC_CG,
c.ID_TC_CIBLE,
fh.FG_HM,
-- UNIVERS calculé (simpllifié ici)
CASE
WHEN c.ID_TC_CG = 'RCI' AND c.ID_TC_CIBLE = 'PINDEP' THEN 'ZOOM1'
ELSE 'AUTRE'
END as UNIVERS,
-- Résultat mapping
tc.TYPE_CLIENT,
tc.PRB
FROM SYS_MD_CLIENT c
LEFT JOIN LAST_FG_HM fh ON c.ID_CLN = fh.ID_CLN AND fh.ID_ART = 'ART123'
LEFT JOIN PT0CE_TYPE_CLIENT_MAPPING tc
ON tc.UNIVERS = CASE WHEN c.ID_TC_CG = 'RCI' AND c.ID_TC_CIBLE = 'PINDEP' THEN 'ZOOM1' ELSE 'AUTRE' END
AND tc.ID_TC_CG = c.ID_TC_CG
AND tc.ID_TC_CIBLE = c.ID_TC_CIBLE
AND tc.FG_HM = fh.FG_HM
WHERE c.ID_CLN = 'CLIENT123';
Modifier les mappings TYPE_CLIENT¶
📍 Ajouter un nouveau mapping :
INSERT INTO PT0CE_TYPE_CLIENT_MAPPING (
TYPE_CLIENT,
UNIVERS,
ID_TC_CG,
ID_TC_CIBLE,
FG_HM,
PRB
) VALUES (
'NOUVEAU TYPE CLIENT',
'ZOOM1',
'RCI',
'NOUVEAU',
'0', -- Mercuriale
1 -- PRB_RC
);
COMMIT;
📍 Modifier un mapping existant :
UPDATE PT0CE_TYPE_CLIENT_MAPPING
SET TYPE_CLIENT = 'TYPE CLIENT MODIFIÉ'
WHERE UNIVERS = 'ZOOM1'
AND ID_TC_CG = 'RCI'
AND ID_TC_CIBLE = 'PINDEP'
AND FG_HM = '0';
COMMIT;
📍 Supprimer un mapping :
DELETE FROM PT0CE_TYPE_CLIENT_MAPPING
WHERE UNIVERS = 'ZOOM1'
AND ID_TC_CG = 'RCI'
AND ID_TC_CIBLE = 'OBSOLETE';
COMMIT;
⚠️ Après modification : Relancer PT2QE Option 1 pour recalculer les recommandations.
Cas non mappés¶
Comportement :
- Offres avec TYPE_CLIENT IS NULL ou 'Hors référentiel'
- Exclues du périmètre PT2QE via le filtre WHERE tc.TYPE_CLIENT IS NOT NULL
📍 Identifier les offres non mappées :
-- Avant exclusion
SELECT
c.ID_TC_CG,
c.ID_TC_CIBLE,
fh.FG_HM,
COUNT(*) as NB_OFFRES
FROM SYS_MD_CONDITION cnd
JOIN SYS_MD_CLIENT c ON cnd.ID_CLN_KEY = c.ID_CLN_KEY
LEFT JOIN LAST_FG_HM fh ON c.ID_CLN = fh.ID_CLN
LEFT JOIN PT0CE_TYPE_CLIENT_MAPPING tc
ON tc.UNIVERS = 'ZOOM1' -- Exemple ZOOM1
AND tc.ID_TC_CG = c.ID_TC_CG
AND tc.ID_TC_CIBLE = c.ID_TC_CIBLE
AND tc.FG_HM = fh.FG_HM
WHERE tc.TYPE_CLIENT IS NULL
AND cnd.FG_CND_VLD = 'X'
GROUP BY c.ID_TC_CG, c.ID_TC_CIBLE, fh.FG_HM
ORDER BY NB_OFFRES DESC;
4. Enrichissement TYPE_RESTAURANT¶
Table de mapping¶
Table : PT0CE_TYPE_RESTAURANT_MAPPING
Structure :
| Colonne | Type | Description |
|---|---|---|
LC_SFC_CIBLE |
VARCHAR2(100) | Code sous-famille client SAP |
Type_Restaurant |
VARCHAR2(100) | Type restaurant normalisé |
Exemples de valeurs :
LC_SFC_CIBLE |
Type_Restaurant |
|---|---|
REST_TRAD |
Restaurant Traditionnel |
REST_RAPIDE |
Restauration Rapide |
CAFETERIA |
Cafétéria |
BRASSERIE |
Brasserie |
HOTEL |
Hôtellerie |
📍 Comment consulter les mappings TYPE_RESTAURANT :
-- Liste complète
SELECT
LC_SFC_CIBLE,
Type_Restaurant
FROM PT0CE_TYPE_RESTAURANT_MAPPING
ORDER BY Type_Restaurant;
-- Statistiques
SELECT
COUNT(*) as NB_TOTAL,
COUNT(DISTINCT Type_Restaurant) as NB_TYPE_UNIQUES
FROM PT0CE_TYPE_RESTAURANT_MAPPING;
Processus de jointure¶
Jointure simple sur LC_SFC_CIBLE :
SELECT
ou.*,
COALESCE(tr.Type_Restaurant, 'Hors référentiel') AS TYPE_RESTAURANT
FROM OFFERS_WITH_UNIVERS ou
LEFT JOIN PT0CE_TYPE_RESTAURANT_MAPPING tr
ON tr.LC_SFC_CIBLE = ou.LC_SFC_CIBLE
WHERE ou.UNIVERS = 'ZOOM1'
AND tr.Type_Restaurant IS NOT NULL; -- Exclut offres non mappées
📍 Comment tester une jointure TYPE_RESTAURANT :
-- Test pour un client
SELECT
c.ID_CLN,
c.LC_CLN,
c.LC_SFC_CIBLE,
tr.Type_Restaurant
FROM SYS_MD_CLIENT c
LEFT JOIN PT0CE_TYPE_RESTAURANT_MAPPING tr
ON tr.LC_SFC_CIBLE = c.LC_SFC_CIBLE
WHERE c.ID_CLN = 'CLIENT123';
Modifier les mappings TYPE_RESTAURANT¶
📍 Ajouter un nouveau mapping :
INSERT INTO PT0CE_TYPE_RESTAURANT_MAPPING (
LC_SFC_CIBLE,
Type_Restaurant
) VALUES (
'NOUVEAU_CODE',
'Nouveau Type Restaurant'
);
COMMIT;
📍 Modifier un mapping existant :
UPDATE PT0CE_TYPE_RESTAURANT_MAPPING
SET Type_Restaurant = 'Type Restaurant Modifié'
WHERE LC_SFC_CIBLE = 'REST_TRAD';
COMMIT;
📍 Supprimer un mapping :
⚠️ Après modification : Relancer PT2QE Option 1.
Cas non mappés¶
Comportement :
- TYPE_RESTAURANT reste NULL ou 'Hors référentiel'
- Exclues du périmètre PT2QE
📍 Identifier les LC_SFC_CIBLE non mappés :
SELECT
c.LC_SFC_CIBLE,
COUNT(DISTINCT c.ID_CLN) as NB_CLIENTS
FROM SYS_MD_CLIENT c
LEFT JOIN PT0CE_TYPE_RESTAURANT_MAPPING tr
ON tr.LC_SFC_CIBLE = c.LC_SFC_CIBLE
WHERE c.LC_SFC_CIBLE IS NOT NULL
AND tr.Type_Restaurant IS NULL
GROUP BY c.LC_SFC_CIBLE
ORDER BY NB_CLIENTS DESC;
5. Enrichissement géographique (GEO)¶
Source de données¶
Table : SYS_MD_CLIENT.LC_ZDV_GRV
Extraction : Jointure directe, aucune table de mapping.
Valeurs GEO standards¶
| Code GEO | Région |
|---|---|
IDF |
Île-de-France |
NO |
Nord-Ouest |
SO |
Sud-Ouest |
SE |
Sud-Est |
EST |
Est |
RA |
Rhône-Alpes |
NATIONAL |
National (agrégé) |
📍 Comment consulter les GEO :
-- Distribution des GEO
SELECT
LC_ZDV_GRV as GEO,
COUNT(DISTINCT ID_CLN) as NB_CLIENTS
FROM SYS_MD_CLIENT
WHERE LC_ZDV_GRV IS NOT NULL
GROUP BY LC_ZDV_GRV
ORDER BY NB_CLIENTS DESC;
Impact sur le matching corridors¶
ZOOM1/ZOOM2 :
- GEO requis pour match MASTER
- Si GEO IS NULL → fallback vers corridor NATIONAL
ZOOM3 : - GEO ignoré (corridors nationaux par défaut)
📍 Comment vérifier le GEO d'un client :
6. Matching avec les corridors PT1CE¶
Prérequis : Exécution PT1CE¶
⚠️ OBLIGATOIRE : PT1CE doit avoir été exécuté pour créer les tables suivantes :
PT1CE_OPTIMAL_ZOOM1PT1CE_OPTIMAL_ZOOM2PT1CE_OPTIMAL_ZOOM3
📍 Comment vérifier la présence des tables PT1CE :
-- Via PT2QE Menu - Option 5
-- Ou manuellement :
SELECT table_name
FROM user_tables
WHERE table_name LIKE 'PT1CE_OPTIMAL_%'
ORDER BY table_name;
Si tables manquantes : 1. Lancer PT1CE (menu START.bat dans dossier PT1CE) 2. Exécuter PT1CE Option 1 (Application nouveaux PAS/PRB) 3. Exécuter PT1CE Option 2 (Finalisation corridors) 4. Revenir à PT2QE
Stratégie de matching en cascade¶
Module : utils/corridor_matcher.py → enrich_offers_with_corridors()
2 tentatives de matching :
- MASTER : Match exact sur 4 dimensions
- NATIONAL : Fallback sur
ID_ARTuniquement
Implémentation SQL :
WITH CORRIDORS_ZOOM1_ONLY AS (
-- Filtre ZOOM1 explicite (PT2QE actuel)
SELECT * FROM PT1CE_OPTIMAL_ZOOM1
WHERE STATUS = 'OPTIMAL'
),
-- TENTATIVE 1 : MASTER
MATCH_MASTER AS (
SELECT
o.*,
c.*,
'MASTER' as MATCH_TYPE
FROM PT2QE_PRICE_OFFERS o
LEFT JOIN CORRIDORS_ZOOM1_ONLY c
ON o.ID_ART = c.ID_ART
AND o.TYPE_CLIENT = c.TYPE_CLIENT
AND o.TYPE_RESTAURANT = c.TYPE_RESTAURANT
AND NVL(o.GEO, 'NULL') = NVL(c.GEO, 'NULL')
AND c.CUBE_TYPE = 'MASTER'
WHERE c.ID_ART IS NOT NULL -- Garde uniquement matchs trouvés
),
-- TENTATIVE 2 : NATIONAL (fallback)
MATCH_NATIONAL AS (
SELECT
o.*,
c.*,
'NATIONAL' as MATCH_TYPE
FROM PT2QE_PRICE_OFFERS o
JOIN CORRIDORS_ZOOM1_ONLY c
ON o.ID_ART = c.ID_ART
AND c.TYPE_CLIENT = 'NATIONAL'
AND c.TYPE_RESTAURANT = 'NATIONAL'
AND (c.GEO = 'NATIONAL' OR c.GEO IS NULL)
AND c.CUBE_TYPE = 'NATIONAL'
WHERE NOT EXISTS (
-- Uniquement si pas matché avec MASTER
SELECT 1 FROM MATCH_MASTER m
WHERE m.ID_CLN = o.ID_CLN AND m.ID_ART = o.ID_ART
)
),
-- Offres sans corridor
NO_MATCH AS (
SELECT
o.*,
CAST(NULL AS VARCHAR2(10)) as SOURCE_ZOOM,
-- [autres colonnes NULL...]
0 as HAS_CORRIDOR,
'NO_MATCH' as MATCH_TYPE
FROM PT2QE_PRICE_OFFERS o
WHERE NOT EXISTS (SELECT 1 FROM MATCH_MASTER m WHERE m.ID_CLN = o.ID_CLN AND m.ID_ART = o.ID_ART)
AND NOT EXISTS (SELECT 1 FROM MATCH_NATIONAL n WHERE n.ID_CLN = o.ID_CLN AND n.ID_ART = o.ID_ART)
)
-- Union finale
SELECT * FROM MATCH_MASTER
UNION ALL
SELECT * FROM MATCH_NATIONAL
UNION ALL
SELECT * FROM NO_MATCH;
📍 Comment vérifier le matching d'une offre :
-- Voir le type de match pour un client×article
SELECT
ID_CLN,
LC_CLN,
ID_ART,
LC_ART,
TYPE_CLIENT,
TYPE_RESTAURANT,
GEO,
MATCH_TYPE,
HAS_CORRIDOR,
SOURCE_ZOOM
FROM PT2QE_PRICE_OFFERS_ENRICHED
WHERE ID_CLN = 'CLIENT123'
AND ID_ART = 'ART456';
Statistiques de matching¶
Module : corridor_matcher.py → _log_enhanced_matching_stats()
Métriques calculées :
SELECT
MATCH_TYPE,
COUNT(*) as NB_OFFRES,
COUNT(DISTINCT ID_CLN) as NB_CLIENTS,
COUNT(DISTINCT ID_ART) as NB_ARTICLES
FROM PT2QE_PRICE_OFFERS_ENRICHED
GROUP BY MATCH_TYPE
ORDER BY MATCH_TYPE;
Résultats typiques :
MATCH_TYPE |
Description | % attendu |
|---|---|---|
MASTER |
Match exact 4D | 85-90% |
NATIONAL |
Fallback agrégé | 8-12% |
NO_MATCH |
Aucun corridor | 1-3% |
📍 Comment afficher les stats après extraction :
Logs PT2QE Option 1 :
→ Résultats du matching :
- MASTER : 45,230 offres, 1,250 clients, 3,420 articles
- NATIONAL : 5,120 offres, 180 clients, 890 articles
- NO_MATCH : 450 offres, 25 clients, 120 articles
→ Total : 50,800/50,800 offres avec corridor (100.0%)
7. Données récupérées depuis les corridors¶
Champs enrichis¶
Après matching, chaque offre hérite des données du corridor :
Anciennes bornes (PT0CE) :
- PAS_ACTIF
- PRB_RC_ACTIF / PRB_COLL_ACTIF
- PRB_ACTIF (calculé selon PRB_TO_USE)
- BORNE_PL1_PL2 → BORNE_PL6_PLX
Nouvelles bornes (PT1CE) :
- NEW_PAS
- NEW_PRB_RC / NEW_PRB_COLL
- NEW_PRB (calculé selon PRB_TO_USE)
- NEW_BORNE_PL1_PL2 → NEW_BORNE_PL6_PLX
Métriques :
- PRICE_SENSITIVITY : HIGH/MEDIUM/LOW
- ECART_TYPE : Écart-type des prix
📍 Comment consulter les données d'un corridor :
-- Corridor MASTER
SELECT
ID_ART,
TYPE_CLIENT,
TYPE_RESTAURANT,
GEO,
PAS_ACTIF,
NEW_PAS,
PRB_ACTIF,
NEW_PRB,
PRICE_SENSITIVITY
FROM PT1CE_OPTIMAL_ZOOM1
WHERE ID_ART = 'ART456'
AND TYPE_CLIENT = 'RCI PI GI'
AND CUBE_TYPE = 'MASTER';
8. Cas particuliers et gestion d'erreurs¶
Cas 1 : Nouveau client sans historique¶
Symptôme : Client créé récemment, aucune transaction.
Impact :
- LAST_FG_HM reste NULL
- Pas de mapping TYPE_CLIENT possible
- Offres exclues du périmètre PT2QE
Diagnostic :
-- Clients sans historique transactionnel
SELECT
c.ID_CLN,
c.LC_CLN,
c.ID_TC_CG,
c.ID_TC_CIBLE,
cnd.NB_CONDITIONS
FROM SYS_MD_CLIENT c
JOIN (
SELECT ID_CLN_KEY, COUNT(*) as NB_CONDITIONS
FROM SYS_MD_CONDITION
WHERE FG_CND_VLD = 'X'
GROUP BY ID_CLN_KEY
) cnd ON c.ID_CLN_KEY = cnd.ID_CLN_KEY
LEFT JOIN LAST_FG_HM fh ON c.ID_CLN = fh.ID_CLN
WHERE fh.LAST_FG_HM IS NULL
ORDER BY cnd.NB_CONDITIONS DESC;
Solution : - Attendre que le client passe des commandes - PT2QE inclura automatiquement ses offres après historique disponible
Cas 2 : Client réactivé après inactivité¶
Symptôme : Client inactif pendant plusieurs trimestres, puis réactivé.
Comportement :
- LAST_FG_HM = valeur de la dernière transaction connue (peut être ancienne)
- Si transaction > 4 trimestres → FG_HM utilisé quand même
Diagnostic :
-- Clients avec historique ancien
SELECT
c.ID_CLN,
c.LC_CLN,
fh.FG_HM,
fh.LAST_TRANSACTION_DATE,
TRUNC(MONTHS_BETWEEN(SYSDATE, fh.LAST_TRANSACTION_DATE)) as MOIS_INACTIVITE
FROM SYS_MD_CLIENT c
JOIN (
SELECT
ID_CLN,
FG_HM,
MAX(DT_CDE) as LAST_TRANSACTION_DATE
FROM LAST_FG_HM
GROUP BY ID_CLN, FG_HM
) fh ON c.ID_CLN = fh.ID_CLN
WHERE MONTHS_BETWEEN(SYSDATE, fh.LAST_TRANSACTION_DATE) > 12
ORDER BY MOIS_INACTIVITE DESC;
Impact : PT2QE utilise le FG_HM historique même ancien.
Cas 3 : Article sans corridor MASTER¶
Symptôme : Article nouveau ou rare, pas de corridor spécifique.
Comportement : 1. Tentative match MASTER → échec 2. Fallback vers corridor NATIONAL → recherche
Diagnostic :
-- Articles avec fallback NATIONAL
SELECT
ID_ART,
LC_ART,
COUNT(*) as NB_OFFRES_NATIONAL
FROM PT2QE_PRICE_OFFERS_ENRICHED
WHERE MATCH_TYPE = 'NATIONAL'
GROUP BY ID_ART, LC_ART
ORDER BY NB_OFFRES_NATIONAL DESC;
Solutions : - Option A : Attendre que PT1CE crée un corridor MASTER (plus de transactions nécessaires) - Option B : Utiliser le corridor NATIONAL existant (PT2QE le fait automatiquement)
Cas 4 : Enrichissement partiel¶
Symptôme : Certaines dimensions manquantes (TYPE_CLIENT NULL, GEO NULL, etc.)
Impact : Offre exclue du périmètre PT2QE.
Diagnostic :
-- Offres avec enrichissement incomplet
SELECT
'TYPE_CLIENT' as DIMENSION_MANQUANTE,
COUNT(*) as NB_OFFRES
FROM PT2QE_PRICE_OFFERS
WHERE TYPE_CLIENT IS NULL OR TYPE_CLIENT = 'Hors référentiel'
UNION ALL
SELECT
'TYPE_RESTAURANT' as DIMENSION_MANQUANTE,
COUNT(*) as NB_OFFRES
FROM PT2QE_PRICE_OFFERS
WHERE TYPE_RESTAURANT IS NULL OR TYPE_RESTAURANT = 'Hors référentiel'
UNION ALL
SELECT
'GEO' as DIMENSION_MANQUANTE,
COUNT(*) as NB_OFFRES
FROM PT2QE_PRICE_OFFERS
WHERE GEO IS NULL
AND UNIVERS IN ('ZOOM1', 'ZOOM2');
Solutions :
1. TYPE_CLIENT manquant : Ajouter un mapping dans PT0CE_TYPE_CLIENT_MAPPING
2. TYPE_RESTAURANT manquant : Ajouter un mapping dans PT0CE_TYPE_RESTAURANT_MAPPING
3. GEO manquant : Corriger LC_ZDV_GRV dans SYS_MD_CLIENT
Cas 5 : Aucun corridor PT1CE disponible¶
Symptôme : Tables PT1CE_OPTIMAL_* vides ou inexistantes.
Erreur :
Solution : 1. Lancer PT1CE (START.bat dans dossier PT1CE) 2. Exécuter PT1CE Option 1 (Application nouveaux PAS/PRB) 3. Exécuter PT1CE Option 2 (Finalisation corridors) 4. Vérifier : PT2QE Menu → Option 5 (Vérifier prérequis) 5. Relancer PT2QE Option 1
Vérification manuelle :
9. Procédures de maintenance¶
Mettre à jour les mappings TYPE_CLIENT¶
Fréquence recommandée : Avant chaque exécution PT2QE si nouveaux types clients SAP.
Étapes :
- Identifier les nouveaux types clients non mappés :
-- Types clients SAP présents mais non mappés
SELECT DISTINCT
c.ID_TC_CG,
c.ID_TC_CIBLE,
COUNT(DISTINCT c.ID_CLN) as NB_CLIENTS
FROM SYS_MD_CLIENT c
LEFT JOIN PT0CE_TYPE_CLIENT_MAPPING tc
ON tc.ID_TC_CG = c.ID_TC_CG
AND tc.ID_TC_CIBLE = c.ID_TC_CIBLE
WHERE tc.TYPE_CLIENT IS NULL
AND c.ID_TC_CG <> 'AUTRES'
GROUP BY c.ID_TC_CG, c.ID_TC_CIBLE
ORDER BY NB_CLIENTS DESC;
- Ajouter les nouveaux mappings :
-- Pour chaque combinaison trouvée
INSERT INTO PT0CE_TYPE_CLIENT_MAPPING (
TYPE_CLIENT, UNIVERS, ID_TC_CG, ID_TC_CIBLE, FG_HM, PRB
) VALUES (
'NOUVEAU TYPE', -- À déterminer
'ZOOM1', -- Selon règles UNIVERS
'RCI', -- ID_TC_CG trouvé
'NOUVEAU', -- ID_TC_CIBLE trouvé
'0', -- '0' pour mercuriale
1 -- 1=PRB_RC, 2=PRB_COLL
);
-- Ajouter aussi la variante FG_HM='1' si nécessaire
INSERT INTO PT0CE_TYPE_CLIENT_MAPPING (
TYPE_CLIENT, UNIVERS, ID_TC_CG, ID_TC_CIBLE, FG_HM, PRB
) VALUES (
'NOUVEAU TYPE HM',
'ZOOM1',
'RCI',
'NOUVEAU',
'1', -- Hors mercuriale
1
);
COMMIT;
- Relancer PT2QE Option 1
Mettre à jour les mappings TYPE_RESTAURANT¶
Fréquence recommandée : Avant chaque exécution PT2QE si nouveaux codes SFC.
Étapes :
- Identifier les LC_SFC_CIBLE non mappés :
SELECT DISTINCT
c.LC_SFC_CIBLE,
COUNT(DISTINCT c.ID_CLN) as NB_CLIENTS
FROM SYS_MD_CLIENT c
LEFT JOIN PT0CE_TYPE_RESTAURANT_MAPPING tr
ON tr.LC_SFC_CIBLE = c.LC_SFC_CIBLE
WHERE c.LC_SFC_CIBLE IS NOT NULL
AND tr.Type_Restaurant IS NULL
GROUP BY c.LC_SFC_CIBLE
ORDER BY NB_CLIENTS DESC;
- Ajouter les nouveaux mappings :
INSERT INTO PT0CE_TYPE_RESTAURANT_MAPPING (
LC_SFC_CIBLE,
Type_Restaurant
) VALUES (
'NOUVEAU_SFC',
'Nouveau Type Restaurant'
);
COMMIT;
- Relancer PT2QE Option 1
Valider les mappings avant exécution PT2QE¶
Script : validate_mappings.py
Utilisation :
Vérifications effectuées : - Existence des tables de mapping - Distribution FG_HM - Test de jointure avec conditions actuelles - Test de jointure TYPE_RESTAURANT - Présence des tables PT1CE_OPTIMAL
Sortie attendue :
=== VALIDATION DES MAPPINGS PT2QE ===
1. Vérification des tables de mapping...
✓ PT0CE_TYPE_CLIENT_MAPPING : 45 lignes
✓ PT0CE_TYPE_RESTAURANT_MAPPING : 120 lignes
2. Vérification des mappings FG_HM = '0'...
Distribution FG_HM :
- FG_HM = '0' : 25 lignes, 8 types client, 1 univers
- FG_HM = '1' : 20 lignes, 7 types client, 1 univers
[...]
=== FIN DE LA VALIDATION ===
10. Index et performance¶
Index recommandés sur tables de mapping¶
PT0CE_TYPE_CLIENT_MAPPING :
CREATE INDEX IDX_PT0CE_TC_MAP
ON PT0CE_TYPE_CLIENT_MAPPING (ID_TC_CG, ID_TC_CIBLE, UNIVERS, FG_HM)
COMPRESS;
PT0CE_TYPE_RESTAURANT_MAPPING :
Index sur table enrichie¶
PT2QE_PRICE_OFFERS_ENRICHED :
-- Index composite pour matching corridors
CREATE INDEX IDX_PT2QE_ENRICHED_DIMS
ON PT2QE_PRICE_OFFERS_ENRICHED (ID_ART, TYPE_CLIENT, TYPE_RESTAURANT, GEO)
COMPRESS;
-- Index sur client
CREATE INDEX IDX_PT2QE_ENRICHED_CLN
ON PT2QE_PRICE_OFFERS_ENRICHED (ID_CLN)
COMPRESS;
📍 Comment vérifier les index :
-- Index sur une table
SELECT
index_name,
column_name,
column_position
FROM user_ind_columns
WHERE table_name = 'PT0CE_TYPE_CLIENT_MAPPING'
ORDER BY index_name, column_position;
11. Diagrammes de flux¶
Flux global enrichissement¶
┌─────────────────────────┐
│ SYS_TARIF_SIMULATION │
│ (Offres de prix) │
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ Extraction │
│ extract_price_offers.py│
└────────────┬────────────┘
│
├──────────────────────────┐
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ Historique FG_HM │ │ Calcul UNIVERS │
│ LAST_FG_HM │ │ (Business Rules) │
│ (4 derniers Q) │ │ │
└──────┬───────────────┘ └───────┬──────────────┘
│ │
└───────────┬───────────────┘
│
▼
┌──────────────────────────────────────┐
│ Jointure Mappings │
│ - PT0CE_TYPE_CLIENT_MAPPING │
│ - PT0CE_TYPE_RESTAURANT_MAPPING │
│ - SYS_MD_CLIENT (GEO) │
└──────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Filtre ZOOM1 │
│ WHERE UNIVERS = 'ZOOM1' │
│ AND TYPE_CLIENT IS NOT NULL │
│ AND TYPE_RESTAURANT IS NOT NULL │
└──────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ PT2QE_PRICE_OFFERS │
│ (Offres enrichies) │
└──────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Matching Corridors │
│ corridor_matcher.py │
│ - Tentative MASTER (4D) │
│ - Fallback NATIONAL (ID_ART) │
└──────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ PT2QE_PRICE_OFFERS_ENRICHED │
│ (Offres + Corridors) │
└──────────────────────────────────────┘
Flux matching TYPE_CLIENT¶
┌─────────────┐
│ ID_TC_CG │────┐
└─────────────┘ │
│
┌─────────────┐ │
│ ID_TC_CIBLE │────┼───┐
└─────────────┘ │ │
│ │
┌─────────────┐ │ │
│ UNIVERS │────┼───┼───┐
│ (calculé) │ │ │ │
└─────────────┘ │ │ │
│ │ │
┌─────────────┐ │ │ │
│ FG_HM │────┼───┼───┼───┐
│ (historique)│ │ │ │ │
└─────────────┘ │ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────┐
│ PT0CE_TYPE_CLIENT_MAPPING │
│ Clé composite (4 colonnes) │
└──────────────┬──────────────┘
│
▼
┌─────────────┐
│TYPE_CLIENT │
└─────────────┘
12. Checklist avant exécution PT2QE¶
À vérifier SYSTÉMATIQUEMENT :
- [ ] PT1CE exécuté : Tables
PT1CE_OPTIMAL_*présentes et remplies - [ ] Mappings TYPE_CLIENT :
PT0CE_TYPE_CLIENT_MAPPINGà jour - [ ] Mappings TYPE_RESTAURANT :
PT0CE_TYPE_RESTAURANT_MAPPINGà jour - [ ] Fichier capping :
inputs/capping_type_client.csvprésent - [ ] Calendrier fiscal :
SYS_MD_CALENDRIER_SYSCOà jour (4 trimestres complets) - [ ] Espace disque : Suffisant pour tables PT2QE_*
- [ ] Validation :
python validate_mappings.py TARIFAIREexécuté sans erreur
Commande de vérification rapide :
-- Checklist SQL complète
SELECT 'PT1CE ZOOM1' as CHECK_NAME, COUNT(*) as VALUE FROM PT1CE_OPTIMAL_ZOOM1 WHERE STATUS = 'OPTIMAL'
UNION ALL
SELECT 'MAPPING TYPE_CLIENT', COUNT(*) FROM PT0CE_TYPE_CLIENT_MAPPING
UNION ALL
SELECT 'MAPPING TYPE_RESTAURANT', COUNT(*) FROM PT0CE_TYPE_RESTAURANT_MAPPING
UNION ALL
SELECT 'CALENDRIER FISCAL', COUNT(DISTINCT ID_EXF || '_Q' || NO_TRF) FROM SYS_MD_CALENDRIER_SYSCO;
Seuils attendus :
| Check | Valeur minimum |
|---|---|
| PT1CE ZOOM1 | > 10,000 |
| MAPPING TYPE_CLIENT | > 20 |
| MAPPING TYPE_RESTAURANT | > 50 |
| CALENDRIER FISCAL | > 8 trimestres |
13. FAQ Technique¶
Q : Pourquoi FG_HM = 'X' devient '1' ?
R : Standardisation des valeurs. Dans SYS_FACTURE_LIGNE, le flag hors mercuriale peut être :
- 'X' ou '1' → Hors mercuriale
- NULL ou '0' → Mercuriale
PT2QE standardise en '0' ou '1' pour simplifier les jointures.
Q : Que se passe-t-il si je modifie un mapping pendant que PT2QE tourne ?
R : Aucun impact. PT2QE lit les mappings au moment de l'extraction (Option 1). Les modifications ne seront prises en compte qu'à la prochaine exécution complète.
Q : Pourquoi UNIVERS est calculé AVANT la jointure TYPE_CLIENT ?
R : Car UNIVERS est une dimension nécessaire pour la jointure. Le mapping TYPE_CLIENT dépend de l'UNIVERS :
Sans UNIVERS, la jointure échoue.
Q : Peut-on avoir plusieurs TYPE_CLIENT pour une même combinaison ID_TC_CG/ID_TC_CIBLE ?
R : Oui, si FG_HM diffère :
Mais pour un UNIVERS donné, la clé (UNIVERS, ID_TC_CG, ID_TC_CIBLE, FG_HM) doit être unique.
Q : Que signifie MATCH_TYPE = 'NATIONAL' ?
R : L'offre n'a pas trouvé de corridor MASTER (spécifique à ses dimensions), donc PT2QE utilise un corridor agrégé au niveau TYPE_CLIENT='NATIONAL'.
Impact : Recommandations moins précises (corridor moyen).
Q : Comment forcer un TYPE_CLIENT spécifique pour un client ?
R : Impossible directement. Le TYPE_CLIENT provient du mapping sur (UNIVERS, ID_TC_CG, ID_TC_CIBLE, FG_HM).
Solutions :
1. Modifier le mapping dans PT0CE_TYPE_CLIENT_MAPPING
2. Corriger ID_TC_CG ou ID_TC_CIBLE dans SYS_MD_CLIENT (côté SAP)
Q : Les offres ZOOM2/ZOOM3 sont-elles perdues ?
R : Non. Elles sont extraites mais exclues du calcul PT2QE actuel.
Pour les inclure :
1. Retirer le filtre WHERE UNIVERS = 'ZOOM1' dans extract_price_offers.py
2. Adapter le matching corridors pour utiliser PT1CE_OPTIMAL_ZOOM2 et PT1CE_OPTIMAL_ZOOM3
14. Dépannage rapide¶
| Symptôme | Cause probable | Solution |
|---|---|---|
TYPE_CLIENT IS NULL |
Mapping manquant ou FG_HM NULL | Ajouter mapping ou vérifier historique |
MATCH_TYPE = 'NO_MATCH' |
Article sans corridor PT1CE | Exécuter PT1CE ou attendre plus de transactions |
UNIVERS = 'HORS PERIMETRE' |
Règles UNIVERS ne couvrent pas ce cas | Ajouter règle dans extract_price_offers.py |
GEO IS NULL |
Client sans zone géographique SAP | Corriger LC_ZDV_GRV dans SYS_MD_CLIENT |
FG_HM toujours = '0' |
Client uniquement mercuriale | Attendre transactions HM ou vérifier historique |
15. Commandes utiles¶
Réinitialiser les tables PT2QE¶
-- ATTENTION : Supprime toutes les données PT2QE
BEGIN
FOR t IN (SELECT table_name FROM user_tables WHERE table_name LIKE 'PT2QE_%') LOOP
EXECUTE IMMEDIATE 'DROP TABLE ' || t.table_name || ' PURGE';
END LOOP;
END;
/
Exporter les mappings¶
# TYPE_CLIENT
sqlplus user/pass@TARIFAIRE <<EOF
SET COLSEP ';'
SET PAGESIZE 0
SET TRIMSPOOL ON
SET HEADSEP OFF
SET LINESIZE 300
SPOOL mappings_type_client.csv
SELECT TYPE_CLIENT||';'||UNIVERS||';'||ID_TC_CG||';'||ID_TC_CIBLE||';'||FG_HM||';'||PRB
FROM PT0CE_TYPE_CLIENT_MAPPING
ORDER BY UNIVERS, TYPE_CLIENT;
SPOOL OFF
EXIT;
EOF
Comparer 2 runs PT2QE¶
-- Différences de TYPE_CLIENT entre 2 runs
SELECT
r1.ID_CLN,
r1.ID_ART,
r1.TYPE_CLIENT as TYPE_CLIENT_RUN1,
r2.TYPE_CLIENT as TYPE_CLIENT_RUN2
FROM PT2QE_PRICE_OFFERS_RUN1 r1
JOIN PT2QE_PRICE_OFFERS_RUN2 r2
ON r1.ID_CLN = r2.ID_CLN
AND r1.ID_ART = r2.ID_ART
WHERE r1.TYPE_CLIENT <> r2.TYPE_CLIENT;