Aller au contenu

Structure des tables PT2QE

Vue d'ensemble

PT2QE utilise 4 tables permanentes et 1 table temporaire pour calculer les recommandations de prix. Toutes les tables sont préfixées PT2QE_ et sont recréées (DROP + CREATE) à chaque exécution.

Tables permanentes : - PT2QE_PRICE_OFFERS : Offres actuelles avec historique transactionnel 4Q - PT2QE_PRICE_OFFERS_ENRICHED : Offres enrichies avec corridors PT1CE matchés - PT2QE_CAPPING_CUBES : Cappings par combinaison dimensionnelle - PT2QE_RECOMMENDATIONS : Recommandations finales calculées

Table temporaire : - TEMP_CAPPING_CORRECTIONS : Upload corrections cappings (supprimée après usage)

Principe de gestion : - Chaque table est recréée à chaque run (pas d'historisation automatique) - Options de création : COMPRESS NOLOGGING pour performance - Index créés après insertion des données - Pas de contraintes FK (validation dans le code Python)


Table 1 : PT2QE_PRICE_OFFERS

Description

Table d'extraction des offres de prix actuelles enrichies avec historique de performance transactionnel sur les 4 derniers trimestres fiscaux complets.

Scope temporel : 4 derniers trimestres fiscaux COMPLETS avant la date d'exécution, déterminés dynamiquement via SYS_MD_CALENDRIER_SYSCO.

Périmètre : - ZOOM1 uniquement (filtre appliqué dans la requête) - Offres encore valides (DT_FIN_CONDITION > SYSDATE) - Types de condition : ZFAP, ZFSP, ZIAP, ZISP - ID_SEQ : A305 ou A565 - Exclusions : TYPE_CLIENT 'AUTRES', GMM 'PSN', catégories 'Divers' et 'Freezer'

Création

Module : extract_price_offers.py → méthode extract_current_offers_with_history()

Requête SQL : Créée via une requête complexe avec 5 CTEs principales : 1. PRP_LABELS : Exclusion des prix promo 2. ORIGINE_CDE_UNIQUE : Référentiel origine commande 3. CURRENT_OFFERS : Offres actuelles avec conditions valides 4. LAST_FG_HM : Dernier flag hors mercuriale connu par client×article 5. HISTORICAL_PERFORMANCE : Agrégation des métriques transactionnelles 4Q 6. LAST_QT_UF : Dernière quantité UF connue 7. OFFERS_WITH_UNIVERS : Calcul UNIVERS selon BusinessRules 8. Jointures finales avec PT0CE_TYPE_CLIENT_MAPPING et PT0CE_TYPE_RESTAURANT_MAPPING

Enrichissement dimensions : - Jointure avec PT0CE_TYPE_CLIENT_MAPPING sur (UNIVERS, ID_TC_CG, ID_TC_CIBLE, FG_HM) → produit TYPE_CLIENT - Jointure avec PT0CE_TYPE_RESTAURANT_MAPPING sur LC_SFC_CIBLE → produit TYPE_RESTAURANT

Filtres finaux impératifs :

WHERE ou.UNIVERS = 'ZOOM1'
  AND tc.TYPE_CLIENT IS NOT NULL
  AND tr.TYPE_RESTAURANT IS NOT NULL

Structure détaillée

Colonne Type Oracle Nullable Description Source Commentaires
Identifiants
ID_CLN VARCHAR2(20) NOT NULL Code client SYS_MD_CLIENT.ID_CLN
LC_CLN VARCHAR2(255) NULL Libellé client SYS_MD_CLIENT.LC_CLN
ID_ART VARCHAR2(20) NOT NULL Code article SYS_MD_ARTICLE.ID_ART
LC_ART VARCHAR2(255) NULL Libellé article SYS_MD_ARTICLE.LC_ART
Informations condition
ID_CND VARCHAR2(20) NOT NULL ID condition tarifaire SYS_MD_CONDITION.ID_CND
DT_DEB_CONDITION DATE NULL Date début condition SYS_MD_CONDITION.DT_DEB
DT_FIN_CONDITION DATE NULL Date fin condition SYS_MD_CONDITION.DT_FIN
ID_TYP_CND VARCHAR2(4) NULL Type condition SYS_MD_CONDITION.ID_TYP_CND ZFAP/ZFSP/ZIAP/ZISP
LC_SEQ_CLN VARCHAR2(50) NULL Séquence client SYS_MD_CONDITION.LC_SEQ_CLN CLIENT ou HIERARCHIE CLIENT NX
Dimensions client
ID_TC_CG VARCHAR2(10) NULL Code groupe type client SYS_MD_CLIENT.ID_TC_CG RCI/RSI/RSC/RCC
ID_TC_CIBLE VARCHAR2(10) NULL Code cible type client SYS_MD_CLIENT.ID_TC_CIBLE PINDEP/GINDEP/MP_AO_/etc.
LC_SFC_CIBLE VARCHAR2(100) NULL Surface cible SYS_MD_CLIENT.LC_SFC_CIBLE Pour mapping TYPE_RESTAURANT
GEO VARCHAR2(100) NULL Zone géographique SYS_MD_CLIENT.LC_ZDV_GRV Région/Division
ID_KAM VARCHAR2(50) NULL ID Key Account Manager SYS_MD_CLIENT.ID_KAM 'NO_KAM' si NULL ou vide
Dimensions enrichies (via mappings)
TYPE_CLIENT VARCHAR2(100) NOT NULL Type client enrichi PT0CE_TYPE_CLIENT_MAPPING Résultat du mapping
TYPE_RESTAURANT VARCHAR2(100) NOT NULL Type restaurant enrichi PT0CE_TYPE_RESTAURANT_MAPPING Résultat du mapping
UNIVERS VARCHAR2(20) NOT NULL Univers tarifaire Calculé Toujours 'ZOOM1' (filtre WHERE)
Flags contextuels
FG_HM VARCHAR2(1) NULL Hors mercuriale Historique transactionnel '0'=Mercuriale, '1'=Hors mercuriale
ID_MERC_HM VARCHAR2(50) NULL Type mercuriale HM Historique transactionnel 'HM' ou autre
Hiérarchie article (catégories produit)
HIE_N1 VARCHAR2(255) NULL Catégorie niveau 1 SYS_MD_ARTICLE.LC_HIC_SYSCO_N1
HIE_N2 VARCHAR2(255) NULL Catégorie niveau 2 SYS_MD_ARTICLE.LC_HIC_SYSCO_N2
HIE_N3 VARCHAR2(255) NULL Catégorie niveau 3 SYS_MD_ARTICLE.LC_HIC_SYSCO_N3
HIE_N4 VARCHAR2(255) NULL Catégorie niveau 4 SYS_MD_ARTICLE.LC_HIC_SYSCO_N4
HIE_N5 VARCHAR2(255) NULL Catégorie niveau 5 SYS_MD_ARTICLE.LC_HIC_SYSCO_N5
HIE_N6 VARCHAR2(255) NULL Catégorie niveau 6 SYS_MD_ARTICLE.LC_HIC_SYSCO_N6
HIE_MRQ VARCHAR2(255) NULL Marque SYS_MD_ARTICLE.LC_MRQ
LC_ATTRIBUT VARCHAR2(100) NULL Attribut produit SYS_MD_ARTICLE.LC_ATTRIBUT Ex: 'Basiques'
Prix actuel
PRIX_TARIF_ACTUEL NUMBER NOT NULL Prix offre actuel SYS_TARIF_SIMULATION.PX_OFF_ACTUEL 3 décimales
PRIX_UNITAIRE_ACTUEL NUMBER NULL Prix unitaire actuel = PRIX_TARIF_ACTUEL Identique
Métriques transactionnelles 4Q - Globales (hors Prix promo)
MT_CAB_4Q NUMBER NULL CA total 4 trimestres Agrégation SYS_FACTURE_LIGNE Hors prix promo, valeur par défaut 0
QT_UF_4Q NUMBER NULL Volume UF 4 trimestres Agrégation SYS_FACTURE_LIGNE Hors prix promo, valeur par défaut 0
QT_KG_4Q NUMBER NULL Volume KG 4 trimestres Agrégation SYS_FACTURE_LIGNE Hors prix promo, valeur par défaut 0
Métriques transactionnelles 4Q - Prix fermes uniquement
MT_CAB_4Q_FERMES NUMBER NULL CA prix fermes 4Q Agrégation SYS_FACTURE_LIGNE Où LC_PRP_N1 = 'Prix fermes'
QT_UF_4Q_FERMES NUMBER NULL Volume UF prix fermes 4Q Agrégation SYS_FACTURE_LIGNE Où LC_PRP_N1 = 'Prix fermes'
QT_KG_4Q_FERMES NUMBER NULL Volume KG prix fermes 4Q Agrégation SYS_FACTURE_LIGNE Où LC_PRP_N1 = 'Prix fermes'
Métriques transactionnelles 4Q - Prix indexés uniquement
MT_CAB_4Q_INDEXES NUMBER NULL CA prix indexés 4Q Agrégation SYS_FACTURE_LIGNE Où LC_PRP_N1 = 'Prix indexés'
QT_UF_4Q_INDEXES NUMBER NULL Volume UF prix indexés 4Q Agrégation SYS_FACTURE_LIGNE Où LC_PRP_N1 = 'Prix indexés'
QT_KG_4Q_INDEXES NUMBER NULL Volume KG prix indexés 4Q Agrégation SYS_FACTURE_LIGNE Où LC_PRP_N1 = 'Prix indexés'
Dernière quantité connue
LAST_QT_UF NUMBER NULL Dernière QT_UF connue Dernière facture sur période Défaut 1 si NULL
Métadonnées
EXTRACTION_DATE DATE NOT NULL Date d'extraction SYSDATE Horodatage création

Détail du scope temporel

Période d'analyse historique : 4 derniers trimestres fiscaux COMPLETS

Méthode de détermination : 1. Date de référence = Date d'exécution du script 2. Appel à PeriodManager.get_last_four_complete_fiscal_quarters(reference_date) 3. Interrogation de SYS_MD_CALENDRIER_SYSCO pour identifier les trimestres complets 4. Un trimestre est "complet" si MAX(ID_SEM) < reference_date

Exemple concret : - Date exécution : 15/11/2024 - Dernier trimestre complet : Q3 2024 (terminé le 31/10/2024) - Période retenue : Q4 2023, Q1 2024, Q2 2024, Q3 2024 - Dates calculées : start_date = début Q4 2023, end_date = fin Q3 2024

Application dans la requête :

WHERE f.DT_CDE BETWEEN TO_DATE('start_date', 'YYYY-MM-DD') 
                   AND TO_DATE('end_date', 'YYYY-MM-DD')

Filtres transactionnels appliqués : - ID_TYP_FAC = 'ZF2' (factures standard) - LC_TC_INTRA IN ('Brake', 'D2 hors Brake') - Exclusion des prestations : FG_PRESTA = '0' - Marchandises uniquement : FG_MARCHANDISE IN ('X', '1') - Montants cohérents : MT_GM4 IS NOT NULL AND MT_CAB > 0 AND QT_UF > 0 - Hors prix promo : jointure avec PRP_LABELS qui exclut LC_PRP_N1 = 'Prix promo'

Index créés

CREATE INDEX IX_PT2QE_PRICE_OFFERS_CLN 
  ON PT2QE_PRICE_OFFERS (ID_CLN) COMPRESS;

CREATE INDEX IX_PT2QE_PRICE_OFFERS_ART 
  ON PT2QE_PRICE_OFFERS (ID_ART) COMPRESS;

CREATE INDEX IX_PT2QE_PRICE_OFFERS_MAIN 
  ON PT2QE_PRICE_OFFERS (ID_CLN, ID_ART) COMPRESS;

CREATE INDEX IX_PT2QE_PRICE_OFFERS_TC 
  ON PT2QE_PRICE_OFFERS (ID_TC_CG, ID_TC_CIBLE, FG_HM) COMPRESS;

Statistiques d'enrichissement

Après création, le script affiche : - Total offres extraites - Clients uniques - Articles uniques - % avec TYPE_CLIENT non NULL - % avec TYPE_RESTAURANT non NULL - % avec FG_HM renseigné

Comment modifier

Modifier le périmètre temporel : - Éditer utils/period_manager.py → méthode get_last_four_complete_fiscal_quarters() - Modifier le paramètre FETCH FIRST 4 ROWS ONLY pour changer le nombre de trimestres

Modifier les filtres d'extraction : - Éditer extract_price_offers.py → requête SQL dans extract_current_offers_with_history() - Section WHERE de la CTE CURRENT_OFFERS

Modifier le calcul UNIVERS : - Éditer extract_price_offers.py → CTE OFFERS_WITH_UNIVERS - Section CASE WHEN qui détermine ZOOM1/ZOOM2/ZOOM3

Modifier les mappings appliqués : - Créer/modifier les tables PT0CE_TYPE_CLIENT_MAPPING et PT0CE_TYPE_RESTAURANT_MAPPING - La jointure est automatique dans la requête finale

Ajouter une colonne : 1. Ajouter la colonne dans la requête SQL (section SELECT finale) 2. Si provient de SYS_MD_*, ajouter la table dans les CTEs appropriées 3. Mettre à jour la documentation


Table 2 : PT2QE_PRICE_OFFERS_ENRICHED

Description

Table enrichie par jointure des offres avec les corridors PT1CE optimaux. Contient toutes les offres de PT2QE_PRICE_OFFERS plus les informations des corridors matchés (PAS, PRB, bornes).

Logique de matching : 1. Tentative de match MASTER : jointure exacte sur (ID_ART, TYPE_CLIENT, TYPE_RESTAURANT, GEO) 2. Si échec : fallback NATIONAL : jointure sur (ID_ART) avec TYPE_CLIENT='NATIONAL', TYPE_RESTAURANT='NATIONAL', GEO='NATIONAL' 3. Si échec : NO_MATCH (colonnes corridors à NULL, HAS_CORRIDOR=0)

Création

Module : utils/corridor_matcher.py → méthode enrich_offers_with_corridors()

Requête SQL : Union de 3 CTEs : 1. CORRIDORS_ZOOM1_ONLY : Extraction corridors PT1CE pour ZOOM1 uniquement 2. MATCH_MASTER : Tentative match exact (CUBE_TYPE='MASTER') 3. MATCH_NATIONAL : Fallback national (CUBE_TYPE='NATIONAL') 4. NO_MATCH : Offres sans corridor

Structure détaillée

Colonnes héritées : Toutes les colonnes de PT2QE_PRICE_OFFERS sont présentes

Colonnes ajoutées :

Colonne Type Oracle Nullable Description Source Commentaires
Informations matching
SOURCE_ZOOM VARCHAR2(10) NULL Source corridor PT1CE_OPTIMAL_ZOOM1 Toujours 'ZOOM1' si matché
MATCH_TYPE VARCHAR2(20) NOT NULL Type de matching Calculé 'MASTER'/'NATIONAL'/'NO_MATCH'
CUBE_TYPE VARCHAR2(20) NULL Type de cube PT1CE PT1CE_OPTIMAL_* 'MASTER'/'NATIONAL'
HAS_CORRIDOR NUMBER(1) NOT NULL Flag corridor trouvé Calculé 1=Trouvé, 0=Non trouvé
Prix et bornes ACTUELS (PT0CE - avant recalibrage)
PAS_ACTIF NUMBER NULL PAS actuel PT1CE_OPTIMAL_*.PAS_ACTIF
PRB_RC_ACTIF NUMBER NULL PRB RCI actuel PT1CE_OPTIMAL_*.PRB_RC_ACTIF
PRB_COLL_ACTIF NUMBER NULL PRB Collectivités actuel PT1CE_OPTIMAL_*.PRB_COLL_ACTIF
PRB_TO_USE NUMBER NULL Indicateur PRB à utiliser PT1CE_OPTIMAL_*.PRB_TO_USE 1=RC, 0=COLL
PRB_ACTIF NUMBER NULL PRB actuel final Calculé PRB_RC si PRB_TO_USE=1, sinon PRB_COLL
BORNE_PL1_PL2 NUMBER NULL Borne PL1-PL2 actuelle PT1CE_OPTIMAL_*.BORNE_PL1_PL2
BORNE_PL2_PL3 NUMBER NULL Borne PL2-PL3 actuelle PT1CE_OPTIMAL_*.BORNE_PL2_PL3
BORNE_PL3_PL4 NUMBER NULL Borne PL3-PL4 actuelle PT1CE_OPTIMAL_*.BORNE_PL3_PL4
BORNE_PL4_PL5 NUMBER NULL Borne PL4-PL5 actuelle PT1CE_OPTIMAL_*.BORNE_PL4_PL5
BORNE_PL5_PL6 NUMBER NULL Borne PL5-PL6 actuelle PT1CE_OPTIMAL_*.BORNE_PL5_PL6
BORNE_PL6_PLX NUMBER NULL Borne PL6-PLX actuelle PT1CE_OPTIMAL_*.BORNE_PL6_PLX
Prix et bornes NOUVEAUX (PT1CE - après recalibrage)
NEW_PAS NUMBER NULL Nouveau PAS PT1CE_OPTIMAL_*.NEW_PAS Utilisé pour RECO2
NEW_PRB_RC NUMBER NULL Nouveau PRB RCI PT1CE_OPTIMAL_*.NEW_PRB_RC
NEW_PRB_COLL NUMBER NULL Nouveau PRB Collectivités PT1CE_OPTIMAL_*.NEW_PRB_COLL
NEW_PRB NUMBER NULL Nouveau PRB final Calculé NEW_PRB_RC si PRB_TO_USE=1, sinon NEW_PRB_COLL
NEW_BORNE_PL1_PL2 NUMBER NULL Nouvelle borne PL1-PL2 PT1CE_OPTIMAL_*.NEW_BORNE_PL1_PL2 Utilisée pour RECO1
NEW_BORNE_PL2_PL3 NUMBER NULL Nouvelle borne PL2-PL3 PT1CE_OPTIMAL_*.NEW_BORNE_PL2_PL3 Plancher CHEMIN 2
NEW_BORNE_PL3_PL4 NUMBER NULL Nouvelle borne PL3-PL4 PT1CE_OPTIMAL_*.NEW_BORNE_PL3_PL4
NEW_BORNE_PL4_PL5 NUMBER NULL Nouvelle borne PL4-PL5 PT1CE_OPTIMAL_*.NEW_BORNE_PL4_PL5
NEW_BORNE_PL5_PL6 NUMBER NULL Nouvelle borne PL5-PL6 PT1CE_OPTIMAL_*.NEW_BORNE_PL5_PL6
NEW_BORNE_PL6_PLX NUMBER NULL Nouvelle borne PL6-PLX PT1CE_OPTIMAL_*.NEW_BORNE_PL6_PLX
Contexte produit
PRICE_SENSITIVITY VARCHAR2(20) NULL Sensibilité prix PT1CE_OPTIMAL_*.PRICE_SENSITIVITY 'HIGH'/'MEDIUM'/'LOW'
ECART_TYPE NUMBER NULL Écart-type corridor PT1CE_OPTIMAL_*.ECART_TYPE
Indicateurs calculés
PCT_HAUSSE_PAS NUMBER NULL % hausse du PAS Calculé (NEW_PAS - PAS_ACTIF) / PAS_ACTIF

Logique de matching détaillée

Étape 1 : Construction CORRIDORS_ZOOM1_ONLY

SELECT * FROM (
    SELECT * FROM PT1CE_OPTIMAL_ZOOM1 WHERE STATUS = 'OPTIMAL'
    UNION ALL
    SELECT * FROM PT1CE_OPTIMAL_ZOOM2 WHERE STATUS = 'OPTIMAL'
    UNION ALL
    SELECT * FROM PT1CE_OPTIMAL_ZOOM3 WHERE STATUS = 'OPTIMAL'
) WHERE SOURCE_ZOOM = 'ZOOM1'

Étape 2 : MATCH_MASTER

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

Étape 3 : MATCH_NATIONAL (pour les non-matchés)

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 (
    SELECT 1 FROM MATCH_MASTER m 
    WHERE m.ID_CLN = o.ID_CLN AND m.ID_ART = o.ID_ART
)

Étape 4 : NO_MATCH (toutes colonnes corridor à NULL)

WHERE NOT EXISTS (MATCH_MASTER) 
  AND NOT EXISTS (MATCH_NATIONAL)

Statistiques affichées

Après création : - Comptage par MATCH_TYPE (MASTER/NATIONAL/NO_MATCH) - Nombre d'offres, clients, articles par type de match - Pourcentage global avec corridor

Comment modifier

Modifier la source des corridors : - Éditer corridor_matcher.py → CTE CORRIDORS_ALL - Ajouter/retirer des UNION ALL selon les ZOOMs souhaités

Modifier la logique de fallback : - Éditer corridor_matcher.py → CTE MATCH_NATIONAL - Modifier les conditions de jointure

Désactiver le fallback NATIONAL : - Commenter la CTE MATCH_NATIONAL et son UNION ALL dans la requête finale - Les offres non matchées iront directement en NO_MATCH

Ajouter un niveau de fallback intermédiaire : 1. Créer une nouvelle CTE entre MATCH_MASTER et MATCH_NATIONAL 2. Définir les conditions de matching (ex: sans GEO) 3. Ajouter le UNION ALL dans la requête finale


Table 3 : PT2QE_CAPPING_CUBES

Description

Table des cappings par combinaison dimensionnelle (cube). Générée automatiquement à partir des combinaisons uniques trouvées dans PT2QE_PRICE_OFFERS_ENRICHED, puis mise à jour avec les valeurs du fichier capping_type_client.csv.

Types de cubes : - MASTER : Cubes avec dimensions spécifiques (UNIVERS, TYPE_CLIENT, TYPE_RESTAURANT, GEO) - NATIONAL : Cubes génériques (UNIVERS, 'NATIONAL', 'NATIONAL', 'NATIONAL')

Création

Module : utils/capping_manager.py → méthode generate_cube_capping_table()

Processus de création : 1. Extraction des combinaisons uniques MASTER depuis PT2QE_PRICE_OFFERS_ENRICHED 2. Ajout des cubes NATIONAL (un par UNIVERS) 3. Initialisation avec cappings par défaut 4. Mise à jour avec valeurs du fichier inputs/capping_type_client.csv

Structure détaillée

Colonne Type Oracle Nullable Description Valeurs possibles Utilisation
UNIVERS VARCHAR2(10) NOT NULL Univers tarifaire 'ZOOM1' uniquement Dimension 1
TYPE_CLIENT VARCHAR2(100) NOT NULL Type client ou NATIONAL Valeurs mappées ou 'NATIONAL' Dimension 2
TYPE_RESTAURANT VARCHAR2(100) NOT NULL Type restaurant ou NATIONAL Valeurs mappées ou 'NATIONAL' Dimension 3
GEO VARCHAR2(100) NULL Zone géo ou NATIONAL Régions ou 'NATIONAL' Dimension 4
CUBE_TYPE VARCHAR2(20) NOT NULL Type de cube 'MASTER' ou 'NATIONAL' Indicateur
CAPPING_HIGH NUMBER NOT NULL Capping sensibilité HIGH 0.0 à 1.0 Ex: 0.025 = 2.5%
CAPPING_MEDIUM NUMBER NOT NULL Capping sensibilité MEDIUM 0.0 à 1.0 Ex: 0.05 = 5%
CAPPING_LOW NUMBER NOT NULL Capping sensibilité LOW 0.0 à 1.0 Ex: 0.075 = 7.5%

Valeurs par défaut

Cubes MASTER :

CAPPING_HIGH   = 0.05  (5%)
CAPPING_MEDIUM = 0.15  (15%)
CAPPING_LOW    = 0.20  (20%)

Cubes NATIONAL (plus conservateurs) :

CAPPING_HIGH   = 0.03  (3%)
CAPPING_MEDIUM = 0.10  (10%)
CAPPING_LOW    = 0.15  (15%)

Fichier de paramétrage

Fichier : inputs/capping_type_client.csv

Format attendu :

TYPE_CLIENT;CAPPING_HIGH;CAPPING_MEDIUM;CAPPING_LOW
RCI PI GI;0,025;0,05;0,075
RSI HM;0,025;0,05;0,075
RSC HM;0,025;0,05;0,075
NATIONAL;0,025;0,05;0,075

Règles de lecture : - Séparateur : ; (point-virgule) - Décimale : , (virgule) - Encoding : CP1252 - Obligatoire : colonnes TYPE_CLIENT, CAPPING_HIGH, CAPPING_MEDIUM, CAPPING_LOW

Processus de mise à jour

Étape 1 : Chargement fichier

df = pd.read_csv('inputs/capping_type_client.csv', 
                 sep=';', decimal=',', encoding='cp1252')

Étape 2 : Application aux cubes MASTER

UPDATE PT2QE_CAPPING_CUBES
SET CAPPING_HIGH = {valeur},
    CAPPING_MEDIUM = {valeur},
    CAPPING_LOW = {valeur}
WHERE TYPE_CLIENT = '{type_client}'
  AND CUBE_TYPE = 'MASTER'

Étape 3 : Application aux cubes NATIONAL (si présent dans le fichier)

UPDATE PT2QE_CAPPING_CUBES
SET CAPPING_HIGH = {valeur},
    CAPPING_MEDIUM = {valeur},
    CAPPING_LOW = {valeur}
WHERE CUBE_TYPE = 'NATIONAL'

Utilisation dans le calcul

Les cappings sont appliqués dans PT2QE_RECOMMENDATIONS via jointure :

LEFT JOIN PT2QE_CAPPING_CUBES c
    ON e.UNIVERS = c.UNIVERS
    AND e.TYPE_CLIENT = c.TYPE_CLIENT
    AND e.TYPE_RESTAURANT = c.TYPE_RESTAURANT
    AND NVL(e.GEO, 'NULL') = NVL(c.GEO, 'NULL')

Cascade de capping appliquée : 1. Sensibilité prix : Selon PRICE_SENSITIVITY (HIGH/MEDIUM/LOW) 2. Basiques : Capping fixe 50% si LC_ATTRIBUT = 'Basiques' 3. PRB final : Plafond absolu LEAST(prix, NEW_PRB)

Comment modifier

Modifier les cappings globaux : 1. Éditer inputs/capping_type_client.csv 2. Relancer l'étape 1 (calcul recommandations)

Modifier les cappings d'un cube spécifique : 1. Analyser outputs/run_*/capping_cubes_generated.csv 2. Copier dans corrections/capping_cubes_corrections.csv 3. Modifier les valeurs souhaitées 4. Lancer l'étape 2 (ajustement cappings)

Ajouter un nouveau TYPE_CLIENT : 1. Créer/modifier PT0CE_TYPE_CLIENT_MAPPING avec le nouveau type 2. Ajouter une ligne dans inputs/capping_type_client.csv avec les cappings 3. Relancer le traitement complet

Modifier les valeurs par défaut dans le code : - Éditer capping_manager.py → méthode generate_cube_capping_table() - Section CREATE TABLE → valeurs initiales


Table 4 : PT2QE_RECOMMENDATIONS

Description

Table centrale contenant les recommandations de prix calculées avec l'arbre de décision à 3 chemins. Contient l'intégralité du calcul : RECO1 (avec cascade de capping), RECO2, décision finale, positions dans les corridors.

Création

Module : calculate_recommandations.py → méthode _calculate_recommendations()

Requête SQL : Pipeline complexe avec 6 CTEs imbriquées : 1. ENRICHED_DATA : Jointure offres enrichies + cappings + calcul flags décision 2. RECO_CALC : Calcul RECO1_BASE et RECO2 bruts 3. RECO_WITH_CAPPING_SENSITIVITY : Application capping sensibilité sur RECO1 4. RECO_WITH_CAPPING_BASIQUES : Application capping basiques en cascade 5. DECISION_TREE : Arbre de décision à 3 chemins + sélection finale 6. FINAL_PRICES : Application capping PRB final 7. FINAL_PRICES_WITH_CAPPING : Détermination CAPPING_APPLIED + calcul positions

Arbre de décision (3 chemins)

CHEMIN 1 : PAS_BAISSE_GEL_PRIX - Condition : NEW_PAS < PAS_ACTIF - Action : PRIX_RECOMMANDE = PRIX_TARIF_ACTUEL (gel total) - Rationale : Ne pas répercuter les baisses de coût d'achat

CHEMIN 2 : PL1_CONSERVATION_PREMIUM - Condition : Prix actuel dans PL1 des ANCIENNES bornes (PRB_ACTIF ≥ PRIX > BORNE_PL1_PL2) - Action : PRIX_RECOMMANDE = GREATEST(PRIX_TARIF_ACTUEL, NEW_BORNE_PL2_PL3) - Puis : PRIX_RECOMMANDE = LEAST(PRIX_RECOMMANDE, NEW_PRB) - Rationale : Maintenir clients premium en position haute avec garde-fou

CHEMIN 3 : OPTIMISATION_STANDARD - Condition : Tous les autres cas - Action : PRIX_RECOMMANDE = GREATEST(RECO1_AVEC_CAPPING, RECO2) - Puis : PRIX_RECOMMANDE = LEAST(PRIX_RECOMMANDE, NEW_PRB) - Rationale : Optimisation classique avec double recommandation

Cascade de capping (CHEMIN 3 uniquement)

Étape 1 : Capping sensibilité

CASE
    WHEN PRICE_SENSITIVITY = 'HIGH' THEN
        LEAST(RECO1_BASE, PRIX_TARIF_ACTUEL * (1 + CAPPING_HIGH))
    WHEN PRICE_SENSITIVITY = 'MEDIUM' THEN
        LEAST(RECO1_BASE, PRIX_TARIF_ACTUEL * (1 + CAPPING_MEDIUM))
    WHEN PRICE_SENSITIVITY = 'LOW' THEN
        LEAST(RECO1_BASE, PRIX_TARIF_ACTUEL * (1 + CAPPING_LOW))
    ELSE RECO1_BASE
END as RECO1_APRES_CAPPING_SENSIBILITE

Étape 2 : Capping basiques (écrase sensibilité si plus restrictif)

CASE
    WHEN LC_ATTRIBUT = 'Basiques' THEN
        LEAST(RECO1_APRES_CAPPING_SENSIBILITE, PRIX_TARIF_ACTUEL * 1.50)
    ELSE RECO1_APRES_CAPPING_SENSIBILITE
END as RECO1_AVEC_CAPPING

Étape 3 : Capping PRB final (tous chemins)

LEAST(PRIX_AVANT_CAPPING_PRB, NEW_PRB) as PRIX_RECOMMANDE

Calcul des recommandations

RECO1_BASE (repositionnement paliers) :

Règles paramétrables via config/pt2qe_config.json :

{
  "recommendations": {
    "reco1_rules": [
      {
        "position": "ABOVE_PL1",
        "condition": "PRIX_TARIF_ACTUEL > NEW_BORNE_PL1_PL2",
        "action": "NO_CHANGE",
        "target": "PRIX_TARIF_ACTUEL"
      },
      {
        "position": "PL1_PL2",
        "condition": "PRIX_TARIF_ACTUEL > NEW_BORNE_PL2_PL3",
        "action": "TO_PL1",
        "target": "NEW_BORNE_PL1_PL2"
      },
      ...
    ]
  }
}

Logique par défaut : - Prix dans PL1_PL2 ou PL2_PL3 → Remonter à PL1 (NEW_BORNE_PL1_PL2) - Prix dans PL3_PL4 → Remonter à PL2 (NEW_BORNE_PL2_PL3) - Prix dans PL4_PL5 → Remonter à PL3 (NEW_BORNE_PL3_PL4) - Prix dans PL5_PL6 → Remonter à PL5 (NEW_BORNE_PL5_PL6) - Prix dans PL6_PLX → Remonter à PL6 (NEW_BORNE_PL6_PLX) - Prix < PAS → Remonter au PAS (NEW_PAS)

RECO2 (hausse proportionnelle au PAS) :

PRIX_TARIF_ACTUEL * (1 + (NEW_PAS - PAS_ACTIF) / PAS_ACTIF)

Structure détaillée

Colonnes héritées : Toutes les colonnes de PT2QE_PRICE_OFFERS_ENRICHED sont présentes

Colonnes calculées principales :

Colonne Type Oracle Description Valeurs possibles Commentaires
Flags de diagnostic
IS_PAS_BAISSE NUMBER(1) Flag baisse PAS 0 ou 1 1 si NEW_PAS < PAS_ACTIF
IS_PL1_ANCIENNES NUMBER(1) Flag PL1 anciennes 0 ou 1 1 si prix dans PL1 anciennes bornes
Position 1 : Prix actuel dans ANCIENNES bornes (PT0CE)
POSITION_TARIF_ACTUEL_DANS_ANCIENNES_BORNES VARCHAR2(50) Position en paliers ABOVE_PRB, PL1, PL2, PL3, PL4, PL5, PL6, PLX, BELOW_PAS Calcul via CASE WHEN sur anciennes bornes
Position 2 : Prix actuel dans NOUVELLES bornes (PT1CE)
PALIER_TARIF_ACTUEL_VS_NOUVELLES_BORNES VARCHAR2(50) Position en paliers ABOVE_PRB, PL1, PL2, PL3, PL4, PL5, PL6, PLX, BELOW_PAS Calcul via CASE WHEN sur nouvelles bornes
Cascade de calcul RECO1
RECO1_BASE NUMBER Reco1 brute Repositionnement selon règles config
RECO1_APRES_CAPPING_SENSIBILITE NUMBER Reco1 après étape 1 Après capping sensibilité
RECO1_AVEC_CAPPING NUMBER Reco1 après étape 2 Après cascade complète (sensibilité + basiques)
RECO2
RECO2 NUMBER Hausse proportionnelle PRIX_ACTUEL × (1 + pct_hausse_PAS)
Arbre de décision
DECISION_PATH VARCHAR2(50) Chemin emprunté PAS_BAISSE_GEL_PRIX, PL1_CONSERVATION_PREMIUM, OPTIMISATION_STANDARD Déterminé par conditions
RECO_TYPE VARCHAR2(50) Type reco (court) GEL_PRIX, CONSERVATION_PREMIUM, REPOSITIONNEMENT_PALIERS, HAUSSE_PROPORTIONNELLE_PAS Description courte
RECO_SELECTIONNEE VARCHAR2(100) Reco sélectionnée (explicite) GEL_PRIX, CONSERVATION_PREMIUM, RECO1_REPOSITIONNEMENT_PALIERS, RECO2_HAUSSE_PROPORTIONNELLE_PAS Nom explicite avec préfixe
CAPPING_APPLIED VARCHAR2(50) Capping appliqué GEL_PAS, PRB_FINAL, PLANCHER_PL2_PL3, BASIQUES_50PCT, SENSIBILITE, NONE Capping le plus contraignant
Prix final
PRIX_RECOMMANDE NUMBER Prix final calculé Résultat de l'arbre de décision
PCT_HAUSSE_FINALE NUMBER % de hausse 0.0 à 1.0 (PRIX_RECOMMANDE - PRIX_ACTUEL) / PRIX_ACTUEL
Position 3 : Prix recommandé dans NOUVELLES bornes (PT2QE)
POSITION_NOUVEAU_PRIX_DANS_NOUVELLES_BORNES VARCHAR2(50) Position finale en paliers ABOVE_PRB, PL1, PL2, PL3, PL4, PL5, PL6, PLX, BELOW_PAS Objectif : paliers supérieurs
Métadonnées
CALCULATION_DATE DATE Date de calcul SYSDATE
LAST_UPDATE DATE Dernière mise à jour SYSDATE

Logique CAPPING_APPLIED

Ordre de priorité (du plus au moins contraignant) :

  1. GEL_PAS : Gel total (CHEMIN 1 - PAS en baisse)
  2. PRB_FINAL : Plafond absolu PRB dépassé
  3. PLANCHER_PL2_PL3 : Plancher de sécurité appliqué (CHEMIN 2)
  4. BASIQUES_50PCT : Capping produits basiques à +50%
  5. SENSIBILITE : Capping sensibilité prix actif
  6. NONE : Aucun capping appliqué

Calcul :

CASE
    WHEN DECISION_PATH = 'PAS_BAISSE_GEL_PRIX' THEN 'GEL_PAS'
    WHEN PRIX_AVANT_CAPPING_PRB > NEW_PRB THEN 'PRB_FINAL'
    WHEN DECISION_PATH = 'PL1_CONSERVATION_PREMIUM'
         AND PRIX_TARIF_ACTUEL < NEW_BORNE_PL2_PL3 THEN 'PLANCHER_PL2_PL3'
    WHEN LC_ATTRIBUT = 'Basiques' 
         AND RECO1_APRES_CAPPING_SENSIBILITE > PRIX_ACTUEL * 1.50 
         THEN 'BASIQUES_50PCT'
    WHEN capping_sensibilité_actif THEN 'SENSIBILITE'
    ELSE 'NONE'
END

Filtre appliqué

Seules les offres avec corridor sont calculées :

WHERE e.HAS_CORRIDOR = 1
  AND e.UNIVERS = 'ZOOM1'

Les offres NO_MATCH ne sont pas présentes dans cette table.

Comment modifier

Modifier les règles RECO1 : 1. Éditer config/pt2qe_config.json → section recommendations.reco1_rules 2. Modifier les condition et target selon besoins 3. Relancer le traitement

Modifier le capping basiques (actuellement 50%) : - Éditer calculate_recommandations.py → rechercher 1.50 - Remplacer par la nouvelle valeur (ex: 1.30 pour 30%) - OU ajouter dans config JSON et paramétrer

Ajouter un 4ème chemin dans l'arbre de décision : 1. Éditer calculate_recommandations.py → CTE DECISION_TREE 2. Ajouter une condition CASE dans le calcul DECISION_PATH 3. Définir la logique de calcul prix pour ce chemin 4. Mettre à jour RECO_TYPE et RECO_SELECTIONNEE

Désactiver un chemin (ex: CHEMIN 2 conservation premium) : - Éditer la condition dans DECISION_PATH - Remplacer la condition par 1=0 (toujours faux) - Les offres iront dans CHEMIN 3 (OPTIMISATION_STANDARD)

Modifier l'ordre de priorité des cappings : - Éditer le calcul CAPPING_APPLIED dans CTE FINAL_PRICES_WITH_CAPPING - Réorganiser l'ordre des CASE WHEN


Table 5 : TEMP_CAPPING_CORRECTIONS (temporaire)

Description

Table temporaire créée lors de l'étape 2 (ajustement cappings) pour uploader le fichier de corrections. Supprimée automatiquement après traitement.

Création

Module : apply_capping_rules.py → méthode _upload_corrections()

Cycle de vie : 1. Création au début de l'étape 2 2. Insertion des données du CSV 3. Utilisation dans MERGE sur PT2QE_RECOMMENDATIONS 4. Suppression à la fin de l'étape 2

Structure

Colonne Type Oracle Description Obligatoire
UNIVERS VARCHAR2(20) Univers OUI
TYPE_CLIENT VARCHAR2(100) Type client OUI
TYPE_RESTAURANT VARCHAR2(100) Type restaurant OUI
GEO VARCHAR2(100) Zone géo NON
CAPPING_HIGH NUMBER Nouveau capping HIGH NON
CAPPING_MEDIUM NUMBER Nouveau capping MEDIUM NON
CAPPING_LOW NUMBER Nouveau capping LOW NON

Fichier source

Fichier : corrections/capping_cubes_corrections.csv

Préparation : 1. Copier outputs/run_*/capping_cubes_generated.csv 2. Renommer en capping_cubes_corrections.csv 3. Placer dans le dossier corrections/ 4. Modifier les valeurs CAPPING_HIGH/MEDIUM/LOW souhaitées 5. Conserver uniquement les lignes à modifier (optionnel)

Format :

UNIVERS;TYPE_CLIENT;TYPE_RESTAURANT;GEO;CAPPING_HIGH;CAPPING_MEDIUM;CAPPING_LOW
ZOOM1;RCI PI GI;Traditionnel;NORD;0,03;0,06;0,09
ZOOM1;RSI HM;Restauration rapide;SUD;0,025;0,055;0,08

Processus de mise à jour

MERGE sur PT2QE_RECOMMENDATIONS :

MERGE INTO PT2QE_RECOMMENDATIONS r
USING TEMP_CAPPING_CORRECTIONS c
ON (
    r.UNIVERS = c.UNIVERS
    AND r.TYPE_CLIENT = c.TYPE_CLIENT
    AND r.TYPE_RESTAURANT = c.TYPE_RESTAURANT
    AND NVL(r.GEO, 'NULL') = NVL(c.GEO, 'NULL')
)
WHEN MATCHED THEN UPDATE SET
    -- Recalcul complet de la cascade
    r.RECO1_APRES_CAPPING_SENSIBILITE = ...
    r.RECO1_AVEC_CAPPING = ...
    r.PRIX_RECOMMANDE = ...
    r.PCT_HAUSSE_FINALE = ...
    r.CAPPING_APPLIED = ...
    r.RECO_SELECTIONNEE = ...
    -- Etc.

Le MERGE recalcule l'intégralité de la cascade de capping pour les offres concernées, en respectant l'arbre de décision existant (DECISION_PATH ne change pas).

Comment utiliser

Workflow complet : 1. Exécuter option 1 (calcul initial) 2. Analyser outputs/run_*/capping_cubes_generated.csv 3. Identifier les cubes nécessitant ajustement 4. Copier le fichier dans corrections/capping_cubes_corrections.csv 5. Modifier les valeurs souhaitées 6. Lancer option 2 (ajustement cappings) 7. Vérifier les nouveaux résultats 8. Répéter si nécessaire (itératif)

Bonnes pratiques : - Modifier seulement les lignes nécessaires (supprimer les autres pour clarté) - Conserver une copie de chaque version pour traçabilité - Tester sur un petit nombre de cubes avant généralisation


Relations entre tables

Flux de données

1. PT2QE_PRICE_OFFERS (Extraction base + historique 4Q)
   [Enrichissement corridors PT1CE]
2. PT2QE_PRICE_OFFERS_ENRICHED (Offres + corridors matchés)
   [Génération cappings] → 3. PT2QE_CAPPING_CUBES
   [Jointure + calcul]
4. PT2QE_RECOMMENDATIONS (Recommandations finales)
   [Optionnel : corrections]
   5. TEMP_CAPPING_CORRECTIONS → MERGE → PT2QE_RECOMMENDATIONS (mise à jour)

Clés de jointure

PRICE_OFFERS → PRICE_OFFERS_ENRICHED :

ON (ID_CLN, ID_ART)
-- Jointure 1:1

PRICE_OFFERS_ENRICHED → PT1CE_OPTIMAL_* :

ON (ID_ART, TYPE_CLIENT, TYPE_RESTAURANT, GEO)
-- Jointure 1:0..1 (peut ne pas matcher → NO_MATCH)

PRICE_OFFERS_ENRICHED → CAPPING_CUBES :

ON (UNIVERS, TYPE_CLIENT, TYPE_RESTAURANT, GEO)
-- Jointure 1:1

TEMP_CAPPING_CORRECTIONS → RECOMMENDATIONS :

ON (UNIVERS, TYPE_CLIENT, TYPE_RESTAURANT, GEO)
-- MERGE N:1 (plusieurs offres par cube)

Dépendances externes

Tables PT1CE obligatoires : - PT1CE_OPTIMAL_ZOOM1 (STATUS='OPTIMAL') - Peut utiliser PT1CE_OPTIMAL_ZOOM2/ZOOM3 si modifié (actuellement ZOOM1 seul)

Tables PT0CE obligatoires : - PT0CE_TYPE_CLIENT_MAPPING (pour enrichissement dimensions) - PT0CE_TYPE_RESTAURANT_MAPPING (pour enrichissement dimensions)

Tables SYS_MD obligatoires : - SYS_MD_CLIENT (référentiel clients) - SYS_MD_CONDITION (conditions tarifaires) - SYS_MD_ARTICLE (référentiel articles) - SYS_TARIF_SIMULATION (prix actuels) - SYS_FACTURE_LIGNE (transactions pour historique 4Q) - SYS_MD_CALENDRIER_SYSCO (périodes fiscales) - SYS_MD_ORIGINE_PRIX (types prix pour filtrage) - SYS_MD_ORIGINE_CDE (canaux de vente)


Index et optimisations

Stratégie d'indexation

Principe : Index créés APRÈS insertion des données (pas pendant CREATE TABLE)

Types d'index utilisés : - Index simples sur clés fréquentes (ID_CLN, ID_ART) - Index composites sur combinaisons (ID_CLN, ID_ART) - Index sur dimensions de filtrage (ID_TC_CG, ID_TC_CIBLE, FG_HM) - Option COMPRESS systématique (économie d'espace)

Index par table

PT2QE_PRICE_OFFERS :

IX_PT2QE_PRICE_OFFERS_CLN (ID_CLN)
IX_PT2QE_PRICE_OFFERS_ART (ID_ART)
IX_PT2QE_PRICE_OFFERS_MAIN (ID_CLN, ID_ART)
IX_PT2QE_PRICE_OFFERS_TC (ID_TC_CG, ID_TC_CIBLE, FG_HM)

PT2QE_PRICE_OFFERS_ENRICHED :

-- Pas d'index créé (table intermédiaire)
-- Accès séquentiel dans la requête suivante

PT2QE_CAPPING_CUBES :

-- Pas d'index créé (table de référence petite)
-- Jointure sur clé composite (UNIVERS, TYPE_CLIENT, TYPE_RESTAURANT, GEO)

PT2QE_RECOMMENDATIONS :

-- Pas d'index créé par le script
-- Possibilité d'en ajouter selon usage analytique
-- Candidats : ID_CLN, ID_ART, PCT_HAUSSE_FINALE, DECISION_PATH

Options de performance

Création de tables :

CREATE TABLE {nom} COMPRESS NOLOGGING AS ...

  • COMPRESS : Compression des données (économie d'espace disque)
  • NOLOGGING : Pas de génération de redo logs (rapidité d'insertion)

Requêtes de calcul :

/*+ PARALLEL(8) */

  • Parallélisation sur 8 threads (configurable dans pt2qe_config.json)

Batch size : - Valeur par défaut : 100 000 lignes (configurable dans pt2qe_config.json)

Comment ajouter des index

Sur PT2QE_RECOMMENDATIONS (usage analytique) :

CREATE INDEX IX_PT2QE_REC_CLN 
  ON PT2QE_RECOMMENDATIONS (ID_CLN) COMPRESS;

CREATE INDEX IX_PT2QE_REC_ART 
  ON PT2QE_RECOMMENDATIONS (ID_ART) COMPRESS;

CREATE INDEX IX_PT2QE_REC_HAUSSE 
  ON PT2QE_RECOMMENDATIONS (PCT_HAUSSE_FINALE) COMPRESS;

CREATE INDEX IX_PT2QE_REC_PATH 
  ON PT2QE_RECOMMENDATIONS (DECISION_PATH) COMPRESS;

Dans le code : 1. Éditer calculate_recommandations.py → méthode _calculate_recommendations() 2. Après la requête CREATE TABLE, ajouter :

indexes = [
    ("IX_PT2QE_REC_CLN", "ID_CLN"),
    ("IX_PT2QE_REC_ART", "ID_ART"),
]

for idx_name, columns in indexes:
    try:
        q = self.db.create_query(f"""
            CREATE INDEX {idx_name} ON {output_table} ({columns}) COMPRESS
        """)
        q.execute()
        q.close()
    except:
        pass


Gestion du cycle de vie

Pattern de création

Toutes les tables suivent ce pattern :

# 1. Drop sécurisé
try:
    q = self.db.create_query(f"DROP TABLE {table_name} PURGE")
    q.execute()
    q.close()
except:
    pass  # Table n'existe pas, OK

# 2. Création avec données
q = self.db.create_query(f"""
    CREATE TABLE {table_name} COMPRESS NOLOGGING AS
    SELECT ...
""")
q.execute()
q.close()

# 3. Création des index (si applicable)
for idx_name, columns in indexes:
    q = self.db.create_query(f"CREATE INDEX {idx_name} ON {table_name} ({columns}) COMPRESS")
    q.execute()
    q.close()

# 4. Commit
self.db.commit()

# 5. Optionnel : Stats Oracle
if row_count > 100000:
    q = self.db.create_query(f"""
        BEGIN
            DBMS_STATS.GATHER_TABLE_STATS(
                ownname => USER,
                tabname => '{table_name}',
                estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
                method_opt => 'FOR ALL COLUMNS SIZE AUTO'
            );
        END;
    """)
    q.execute()
    q.close()

Purge et nettoyage

Tables temporaires :

try:
    q = self.db.create_query(f"DROP TABLE {temp_table} PURGE")
    q.execute()
    q.close()
except:
    pass

Tables permanentes : - Écrasées à chaque run (DROP + CREATE) - Pas de mécanisme d'historisation intégré - Sauvegarde manuelle possible via export CSV

Conservation des résultats

Structure des dossiers de sortie :

outputs/
├── run_20241115_143052/          # Timestamp du run
│   ├── recommendations_detail.csv
│   ├── impact_analysis.csv
│   ├── capping_cubes_generated.csv
│   └── ...
├── run_20241115_153428/
│   └── ...
└── corrections_20241115_154512/   # Run avec corrections
    └── ...

Exports CSV générés : - recommendations_detail.csv : Export complet de PT2QE_RECOMMENDATIONS - capping_cubes_generated.csv : Export de PT2QE_CAPPING_CUBES - Fichiers d'analyse (impact, distribution, etc.)

Pas d'historisation automatique dans Oracle : Les tables sont détruites et recréées à chaque exécution. Seuls les exports CSV sont conservés.

Procédure de rollback

Impossible au niveau SQL (tables écrasées)

Solution : Utiliser les exports CSV 1. Identifier le dossier du run précédent dans outputs/ 2. Relire les CSV pour récupérer les données 3. Optionnel : Recharger dans une table temporaire pour comparaison

Comment conserver un historique

Option 1 : Renommer les tables avant nouveau run

RENAME PT2QE_RECOMMENDATIONS TO PT2QE_RECOMMENDATIONS_20241115;
RENAME PT2QE_CAPPING_CUBES TO PT2QE_CAPPING_CUBES_20241115;
-- Etc.

Option 2 : Modifier le code pour ajouter timestamp 1. Éditer pt2qe_main.py 2. Ajouter timestamp dans TABLE_PREFIX :

timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
TABLE_PREFIX = f'PT2QE_{timestamp}_'

Option 3 : Copier les tables après exécution

CREATE TABLE PT2QE_RECOMMENDATIONS_HISTORY_20241115 
AS SELECT * FROM PT2QE_RECOMMENDATIONS;


Maintenance et monitoring

Vérification de l'intégrité

Après chaque étape, vérifier :

-- Compter les lignes
SELECT COUNT(*) FROM PT2QE_PRICE_OFFERS;
SELECT COUNT(*) FROM PT2QE_PRICE_OFFERS_ENRICHED;
SELECT COUNT(*) FROM PT2QE_CAPPING_CUBES;
SELECT COUNT(*) FROM PT2QE_RECOMMENDATIONS;

-- Vérifier les NULL critiques
SELECT COUNT(*) FROM PT2QE_PRICE_OFFERS 
WHERE TYPE_CLIENT IS NULL OR TYPE_RESTAURANT IS NULL;

SELECT COUNT(*) FROM PT2QE_PRICE_OFFERS_ENRICHED 
WHERE HAS_CORRIDOR = 0;

SELECT COUNT(*) FROM PT2QE_RECOMMENDATIONS 
WHERE PRIX_RECOMMANDE IS NULL;

-- Vérifier les incohérences
SELECT COUNT(*) FROM PT2QE_RECOMMENDATIONS 
WHERE PRIX_RECOMMANDE < NEW_PAS;

SELECT COUNT(*) FROM PT2QE_RECOMMENDATIONS 
WHERE PRIX_RECOMMANDE > NEW_PRB;

Logs et diagnostics

Logs Python affichés : - Nombre de lignes extraites/calculées à chaque étape - Statistiques de matching (MASTER/NATIONAL/NO_MATCH) - Distribution des cappings appliqués - Warnings sur incohérences

Vérification de la cascade de capping :

SELECT
    CAPPING_APPLIED,
    COUNT(*) as NB_OFFRES,
    AVG(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MOY_PCT
FROM PT2QE_RECOMMENDATIONS
GROUP BY CAPPING_APPLIED
ORDER BY CAPPING_APPLIED;

Vérification de l'arbre de décision :

SELECT
    DECISION_PATH,
    RECO_SELECTIONNEE,
    COUNT(*) as NB_OFFRES,
    AVG(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MOY_PCT
FROM PT2QE_RECOMMENDATIONS
GROUP BY DECISION_PATH, RECO_SELECTIONNEE
ORDER BY DECISION_PATH;

Requêtes d'analyse utiles

Top 10 hausses les plus fortes :

SELECT
    ID_CLN, LC_CLN, ID_ART, LC_ART,
    PRIX_TARIF_ACTUEL,
    PRIX_RECOMMANDE,
    PCT_HAUSSE_FINALE * 100 as PCT_HAUSSE,
    DECISION_PATH,
    RECO_SELECTIONNEE,
    CAPPING_APPLIED
FROM PT2QE_RECOMMENDATIONS
ORDER BY PCT_HAUSSE_FINALE DESC
FETCH FIRST 10 ROWS ONLY;

Distribution des hausses par TYPE_CLIENT :

SELECT
    TYPE_CLIENT,
    COUNT(*) as NB_OFFRES,
    AVG(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MOY,
    MEDIAN(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MEDIANE,
    MAX(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MAX
FROM PT2QE_RECOMMENDATIONS
GROUP BY TYPE_CLIENT
ORDER BY HAUSSE_MOY DESC;

Offres bloquées par capping PRB :

SELECT
    TYPE_CLIENT,
    TYPE_RESTAURANT,
    COUNT(*) as NB_OFFRES,
    AVG(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MOY
FROM PT2QE_RECOMMENDATIONS
WHERE CAPPING_APPLIED = 'PRB_FINAL'
GROUP BY TYPE_CLIENT, TYPE_RESTAURANT
ORDER BY NB_OFFRES DESC;

Points d'attention

Performance : - Tables volumineuses (>1M lignes) : temps de création ~5-20 min selon serveur - Indexes COMPRESS : économie d'espace mais légère pénalité en écriture - NOLOGGING : pas de possibilité de recovery après crash (acceptable pour données recalculables)

Intégrité : - Pas de contraintes FK : validation dans le code Python - Risque : données incohérentes si modification manuelle des tables - Solution : toujours passer par les scripts Python

Maintenance : - Tables recréées = pas de fragmentation - Stats Oracle mises à jour automatiquement si >100k lignes - Monitoring volumétrie à faire manuellement (pas d'alerte automatique)


Troubleshooting tables

Table PT2QE_PRICE_OFFERS vide ou très peu de lignes

Causes possibles : 1. Aucune offre valide dans le périmètre (vérifier DT_FIN > SYSDATE) 2. Problème de mapping TYPE_CLIENT ou TYPE_RESTAURANT 3. Filtre UNIVERS trop restrictif (vérifie ZOOM1 uniquement) 4. Tables PT0CE_TYPE_CLIENT_MAPPING ou PT0CE_TYPE_RESTAURANT_MAPPING vides

Actions correctives :

-- Vérifier les offres brutes sans filtres
SELECT COUNT(*) FROM SYS_MD_CONDITION 
WHERE FG_CND_VLD = 'X' AND DT_FIN > SYSDATE;

-- Vérifier les mappings
SELECT COUNT(*) FROM PT0CE_TYPE_CLIENT_MAPPING;
SELECT COUNT(*) FROM PT0CE_TYPE_RESTAURANT_MAPPING;

-- Tester la jointure manuellement
SELECT COUNT(*)
FROM SYS_MD_CLIENT c
JOIN PT0CE_TYPE_CLIENT_MAPPING tc
  ON tc.ID_TC_CG = c.ID_TC_CG
  AND tc.ID_TC_CIBLE = c.ID_TC_CIBLE;

Table PT2QE_PRICE_OFFERS_ENRICHED : Beaucoup de NO_MATCH

Cause : Tables PT1CE_OPTIMAL_* manquantes ou incomplètes

Vérifications :

-- Vérifier l'existence
SELECT table_name FROM user_tables 
WHERE table_name LIKE 'PT1CE_OPTIMAL_%';

-- Compter les corridors OPTIMAL
SELECT COUNT(*) FROM PT1CE_OPTIMAL_ZOOM1 WHERE STATUS = 'OPTIMAL';

-- Compter les combinaisons dimensions uniques
SELECT 
    COUNT(DISTINCT ID_ART || TYPE_CLIENT || TYPE_RESTAURANT || GEO) as NB_CUBES
FROM PT1CE_OPTIMAL_ZOOM1 
WHERE STATUS = 'OPTIMAL';

Action corrective : 1. Exécuter PT1CE en amont (obligatoire) 2. Vérifier que PT1CE Option 2 (Finalisation corridors) a été lancée

Table PT2QE_CAPPING_CUBES : Valeurs incohérentes

Symptôme : Cappings à 0 ou très élevés

Vérifications :

-- Identifier les cappings anormaux
SELECT * FROM PT2QE_CAPPING_CUBES
WHERE CAPPING_HIGH = 0 
   OR CAPPING_MEDIUM = 0 
   OR CAPPING_LOW = 0
   OR CAPPING_HIGH > 0.5;

-- Vérifier le fichier source
SELECT * FROM PT2QE_CAPPING_CUBES 
ORDER BY UNIVERS, TYPE_CLIENT, CUBE_TYPE;

Action corrective : 1. Vérifier le format du fichier inputs/capping_type_client.csv 2. Vérifier séparateur ; et décimale , 3. Vérifier encoding CP1252 4. Relancer avec un fichier corrigé

Table PT2QE_RECOMMENDATIONS : PRIX_RECOMMANDE = NULL

Cause : Offres sans corridor (HAS_CORRIDOR=0) non filtrées

Vérification :

SELECT COUNT(*) FROM PT2QE_RECOMMENDATIONS 
WHERE PRIX_RECOMMANDE IS NULL;

Action corrective : - Normalement impossible (filtre WHERE HAS_CORRIDOR = 1 dans la requête) - Si présent, bug dans le code → vérifier la requête de création

Table PT2QE_RECOMMENDATIONS : PRIX_RECOMMANDE < NEW_PAS

Cause : Bug dans la logique de calcul (ne devrait jamais arriver)

Vérification :

SELECT 
    ID_CLN, ID_ART,
    PRIX_TARIF_ACTUEL,
    NEW_PAS,
    PRIX_RECOMMANDE,
    DECISION_PATH,
    RECO_SELECTIONNEE
FROM PT2QE_RECOMMENDATIONS
WHERE PRIX_RECOMMANDE < NEW_PAS;

Action corrective : - Analyser les lignes concernées - Vérifier la logique de l'arbre de décision dans le code - Reporter le bug avec exemples

Erreur ORA-01652 : unable to extend temp segment

Cause : Manque d'espace dans TEMP tablespace

Action corrective :

-- Vérifier l'espace TEMP
SELECT tablespace_name, SUM(bytes)/1024/1024/1024 as GB
FROM dba_temp_files
GROUP BY tablespace_name;

-- Augmenter TEMP (DBA)
ALTER TABLESPACE TEMP ADD TEMPFILE SIZE 10G;

Erreur ORA-00942 : table or view does not exist

Tables manquantes fréquentes : - PT1CE_OPTIMAL_ZOOM1 → Lancer PT1CE avant PT2QE - PT0CE_TYPE_CLIENT_MAPPING → Créer le mapping - PT0CE_TYPE_RESTAURANT_MAPPING → Créer le mapping - SYS_MD_CALENDRIER_SYSCO → Vérifier accès à la base

Vérification globale :

SELECT 'PT1CE_OPTIMAL_ZOOM1' as table_name, COUNT(*) as exists 
FROM user_tables WHERE table_name = 'PT1CE_OPTIMAL_ZOOM1'
UNION ALL
SELECT 'PT0CE_TYPE_CLIENT_MAPPING', COUNT(*) 
FROM user_tables WHERE table_name = 'PT0CE_TYPE_CLIENT_MAPPING'
UNION ALL
SELECT 'PT0CE_TYPE_RESTAURANT_MAPPING', COUNT(*) 
FROM user_tables WHERE table_name = 'PT0CE_TYPE_RESTAURANT_MAPPING';


Annexes

Diagramme entité-relation

┌─────────────────────────┐
│  PT2QE_PRICE_OFFERS     │
│  (Offres + historique)  │
│  - ID_CLN [PK]          │
│  - ID_ART [PK]          │
│  - TYPE_CLIENT          │
│  - TYPE_RESTAURANT      │
│  - GEO                  │
│  - UNIVERS = 'ZOOM1'    │
│  - MT_CAB_4Q            │
│  - QT_UF_4Q             │
└───────────┬─────────────┘
            │ 1:1
┌─────────────────────────────┐
│ PT2QE_PRICE_OFFERS_ENRICHED │
│ (+ corridors PT1CE)         │
│ - ID_CLN [PK]               │
│ - ID_ART [PK]               │
│ - MATCH_TYPE                │
│ - HAS_CORRIDOR              │
│ - NEW_PAS                   │
│ - NEW_PRB                   │
│ - NEW_BORNE_PL*             │
└───────────┬─────────────────┘
            │ N:1
            ├────────────────┐
            │                │
            ▼                ▼
┌──────────────────┐  ┌─────────────────────┐
│ PT2QE_CAPPING    │  │ PT2QE_RECOMMENDATIONS│
│ _CUBES           │  │ (Calcul final)       │
│ - UNIVERS [PK]   │  │ - ID_CLN [PK]        │
│ - TYPE_CLIENT    │  │ - ID_ART [PK]        │
│ - TYPE_RESTO     │  │ - DECISION_PATH      │
│ - GEO            │  │ - RECO1_BASE         │
│ - CAPPING_HIGH   │  │ - RECO1_AVEC_CAPPING │
│ - CAPPING_MEDIUM │  │ - RECO2              │
│ - CAPPING_LOW    │  │ - PRIX_RECOMMANDE    │
└──────────────────┘  │ - PCT_HAUSSE_FINALE  │
                      │ - CAPPING_APPLIED     │
                      └─────────────────────┘
                                │ MERGE (optionnel)
                      ┌─────────────────────────┐
                      │ TEMP_CAPPING_CORRECTIONS│
                      │ (Temporaire)            │
                      │ - UNIVERS               │
                      │ - TYPE_CLIENT           │
                      │ - TYPE_RESTO            │
                      │ - GEO                   │
                      │ - CAPPING_HIGH (new)    │
                      │ - CAPPING_MEDIUM (new)  │
                      │ - CAPPING_LOW (new)     │
                      └─────────────────────────┘

Requête de test complète

Test de bout en bout :

-- 1. Vérifier extraction
SELECT COUNT(*) as offres_extraites FROM PT2QE_PRICE_OFFERS;

-- 2. Vérifier enrichissement
SELECT 
    MATCH_TYPE,
    COUNT(*) as nb_offres,
    ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 1) as pct
FROM PT2QE_PRICE_OFFERS_ENRICHED
GROUP BY MATCH_TYPE;

-- 3. Vérifier cappings
SELECT COUNT(*) as nb_cubes FROM PT2QE_CAPPING_CUBES;

-- 4. Vérifier recommandations
SELECT 
    DECISION_PATH,
    RECO_SELECTIONNEE,
    COUNT(*) as nb_offres,
    ROUND(AVG(PCT_HAUSSE_FINALE) * 100, 2) as hausse_moy_pct
FROM PT2QE_RECOMMENDATIONS
GROUP BY DECISION_PATH, RECO_SELECTIONNEE
ORDER BY DECISION_PATH;

-- 5. Vérifier cappings appliqués
SELECT 
    CAPPING_APPLIED,
    COUNT(*) as nb_offres
FROM PT2QE_RECOMMENDATIONS
GROUP BY CAPPING_APPLIED
ORDER BY nb_offres DESC;

-- 6. Vérifier incohérences (doit retourner 0)
SELECT COUNT(*) FROM PT2QE_RECOMMENDATIONS 
WHERE PRIX_RECOMMANDE < NEW_PAS 
   OR PRIX_RECOMMANDE > NEW_PRB * 1.01; -- tolérance 1%

Script de nettoyage manuel

Si besoin de supprimer toutes les tables 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;
/

Exemple de création manuelle de table

Si besoin de recréer une table individuellement :

-- Recréer PT2QE_CAPPING_CUBES manuellement
DROP TABLE PT2QE_CAPPING_CUBES PURGE;

CREATE TABLE PT2QE_CAPPING_CUBES (
    UNIVERS VARCHAR2(10) NOT NULL,
    TYPE_CLIENT VARCHAR2(100) NOT NULL,
    TYPE_RESTAURANT VARCHAR2(100) NOT NULL,
    GEO VARCHAR2(100),
    CUBE_TYPE VARCHAR2(20) NOT NULL,
    CAPPING_HIGH NUMBER NOT NULL,
    CAPPING_MEDIUM NUMBER NOT NULL,
    CAPPING_LOW NUMBER NOT NULL
) COMPRESS;

-- Insérer valeurs par défaut
INSERT INTO PT2QE_CAPPING_CUBES
SELECT DISTINCT
    'ZOOM1',
    TYPE_CLIENT,
    TYPE_RESTAURANT,
    GEO,
    'MASTER',
    0.05,
    0.15,
    0.20
FROM PT2QE_PRICE_OFFERS_ENRICHED
WHERE HAS_CORRIDOR = 1;

COMMIT;