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 :
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 :
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)
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 :
Cubes NATIONAL (plus conservateurs) :
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
É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)
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) :
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) :
- GEL_PAS : Gel total (CHEMIN 1 - PAS en baisse)
- PRB_FINAL : Plafond absolu PRB dépassé
- PLANCHER_PL2_PL3 : Plancher de sécurité appliqué (CHEMIN 2)
- BASIQUES_50PCT : Capping produits basiques à +50%
- SENSIBILITE : Capping sensibilité prix actif
- 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 :
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 :
PRICE_OFFERS_ENRICHED → PT1CE_OPTIMAL_* :
PRICE_OFFERS_ENRICHED → CAPPING_CUBES :
TEMP_CAPPING_CORRECTIONS → RECOMMENDATIONS :
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 :
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 :
- 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 :
- 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 :
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 :
Option 3 : Copier les tables après exécution
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 :
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;