Aller au contenu

TROUBLESHOOTING - PT2QE

1. ERREURS FRÉQUENTES

1.1. Fichier capping_type_client.csv introuvable

Symptôme :

[ERROR] Fichier capping manquant : inputs/capping_type_client.csv

Diagnostic étape par étape :

  1. Vérifier l'existence du fichier :

    dir inputs\capping_type_client.csv
    

  2. Si le fichier n'existe pas, vérifier le répertoire inputs :

    dir inputs
    

  3. Créer le répertoire si nécessaire :

    mkdir inputs
    

Solution - Création du fichier :

  1. Créer le fichier inputs\capping_type_client.csv avec ce contenu exact :
    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
    RSC HM - SRCNAT;0,025;0,05;0,075
    RSI HM - SRCREG;0,025;0,05;0,075
    RCI autres;0,025;0,05;0,075
    NATIONAL;0,025;0,05;0,075
    

Règles de format obligatoires : - Séparateur : point-virgule (;) - Décimale : virgule (,) - Encodage : CP1252 (Windows) - Pas d'espaces avant/après les valeurs

Vérification du format :

type inputs\capping_type_client.csv

Valeurs attendues : - CAPPING_HIGH : entre 0,01 et 0,10 (1% à 10%) - CAPPING_MEDIUM : entre 0,03 et 0,20 (3% à 20%) - CAPPING_LOW : entre 0,05 et 0,30 (5% à 30%)


1.2. Tables PT1CE_OPTIMAL_* manquantes

Symptôme :

ORA-00942: table or view does not exist
PT1CE_OPTIMAL_ZOOM1

Contexte : PT2QE nécessite impérativement que PT1CE ait été exécuté au préalable car il utilise les corridors optimaux calculés par PT1CE.

Diagnostic SQL :

-- Vérifier les tables PT1CE existantes
SELECT table_name, num_rows, last_analyzed
FROM user_tables 
WHERE table_name LIKE 'PT1CE_OPTIMAL%'
ORDER BY table_name;

Résultat attendu :

TABLE_NAME               NUM_ROWS    LAST_ANALYZED
PT1CE_OPTIMAL_ZOOM1      XXXXX       DATE
PT1CE_OPTIMAL_ZOOM2      XXXXX       DATE
PT1CE_OPTIMAL_ZOOM3      XXXXX       DATE

Si aucune table trouvée :

  1. Cause : PT1CE n'a jamais été exécuté ou les tables ont été supprimées

  2. Solution obligatoire :

  3. Aller dans le répertoire PT1CE
  4. Lancer START.bat
  5. Exécuter dans l'ordre :
    • Option 1 : Application nouveaux PAS/PRB
    • Option 2 : Finalisation corridors
  6. Attendre la fin complète de PT1CE
  7. Vérifier à nouveau les tables

  8. Vérification post-PT1CE :

    SELECT 
        'ZOOM1' as ZOOM,
        COUNT(*) as NB_CORRIDORS,
        COUNT(DISTINCT ID_ART) as NB_ARTICLES,
        COUNT(DISTINCT TYPE_CLIENT) as NB_TYPE_CLIENT
    FROM PT1CE_OPTIMAL_ZOOM1
    WHERE STATUS = 'OPTIMAL'
    UNION ALL
    SELECT 
        'ZOOM2',
        COUNT(*),
        COUNT(DISTINCT ID_ART),
        COUNT(DISTINCT TYPE_CLIENT)
    FROM PT1CE_OPTIMAL_ZOOM2
    WHERE STATUS = 'OPTIMAL'
    UNION ALL
    SELECT 
        'ZOOM3',
        COUNT(*),
        COUNT(DISTINCT ID_ART),
        COUNT(DISTINCT TYPE_CLIENT)
    FROM PT1CE_OPTIMAL_ZOOM3
    WHERE STATUS = 'OPTIMAL';
    

Si les tables existent mais sont vides :

-- Vérifier le contenu
SELECT STATUS, COUNT(*)
FROM PT1CE_OPTIMAL_ZOOM1
GROUP BY STATUS;

Si aucune ligne STATUS = 'OPTIMAL', PT1CE n'a pas été finalisé → Relancer PT1CE Option 2.


1.3. Mapping TYPE_CLIENT incomplet

Symptôme :

[WARNING] 15,234 offres sans TYPE_CLIENT
[INFO] - Avec TYPE_CLIENT: 234,567 (94.0%)

Diagnostic détaillé :

  1. Identifier les combinaisons non mappées :

    SELECT 
        o.ID_TC_CG,
        o.ID_TC_CIBLE,
        o.FG_HM,
        COUNT(*) as NB_OFFRES,
        COUNT(DISTINCT o.ID_CLN) as NB_CLIENTS,
        COUNT(DISTINCT o.ID_ART) as NB_ARTICLES
    FROM PT2QE_PRICE_OFFERS o
    WHERE o.TYPE_CLIENT IS NULL 
       OR o.TYPE_CLIENT = 'Hors référentiel'
    GROUP BY o.ID_TC_CG, o.ID_TC_CIBLE, o.FG_HM
    ORDER BY NB_OFFRES DESC;
    

  2. Vérifier le mapping existant pour ces combinaisons :

    -- Remplacer XXX, YYY, Z par les valeurs trouvées ci-dessus
    SELECT *
    FROM PT0CE_TYPE_CLIENT_MAPPING
    WHERE ID_TC_CG = 'XXX'
      AND ID_TC_CIBLE = 'YYY'
      AND FG_HM = 'Z'
      AND UNIVERS = 'ZOOM1';
    

Causes possibles :

Cas 1 : FG_HM non déterminé

-- Vérifier les offres sans FG_HM
SELECT 
    COUNT(*) as NB_OFFRES_SANS_FG_HM
FROM PT2QE_PRICE_OFFERS
WHERE FG_HM IS NULL;

Explication : FG_HM est récupéré depuis l'historique transactionnel (4 derniers quarters). Si NULL, le client n'a pas d'historique.

Solution : - Accepter ces offres (elles auront FG_HM par défaut) - OU les exclure du périmètre

Cas 2 : Combinaison non présente dans PT0CE_TYPE_CLIENT_MAPPING

Solution - Ajouter le mapping dans PT0CE :

  1. Identifier la bonne valeur de TYPE_CLIENT :

    -- Voir les mappings similaires
    SELECT DISTINCT
        ID_TC_CG,
        ID_TC_CIBLE,
        FG_HM,
        TYPE_CLIENT
    FROM PT0CE_TYPE_CLIENT_MAPPING
    WHERE ID_TC_CG = 'XXX'  -- Votre ID_TC_CG
    ORDER BY ID_TC_CG, ID_TC_CIBLE, FG_HM;
    

  2. Insérer le nouveau mapping dans PT0CE_TYPE_CLIENT_MAPPING :

    INSERT INTO PT0CE_TYPE_CLIENT_MAPPING (
        UNIVERS, ID_TC_CG, ID_TC_CIBLE, FG_HM, TYPE_CLIENT
    ) VALUES (
        'ZOOM1',
        'XXX',      -- ID_TC_CG identifié
        'YYY',      -- ID_TC_CIBLE identifié
        'Z',        -- FG_HM identifié ('0' ou '1')
        'NOM_TYPE_CLIENT'  -- Type client approprié
    );
    COMMIT;
    

  3. Relancer PT2QE après avoir complété les mappings.

Cas 3 : Mapping TYPE_RESTAURANT manquant

Diagnostic :

SELECT 
    o.LC_SFC_CIBLE,
    COUNT(*) as NB_OFFRES
FROM PT2QE_PRICE_OFFERS o
WHERE o.TYPE_RESTAURANT IS NULL 
   OR o.TYPE_RESTAURANT = 'Hors référentiel'
GROUP BY o.LC_SFC_CIBLE
ORDER BY NB_OFFRES DESC;

Vérifier le mapping :

SELECT *
FROM PT0CE_TYPE_RESTAURANT_MAPPING
WHERE LC_SFC_CIBLE = 'XXX';

Solution - Ajouter le mapping :

INSERT INTO PT0CE_TYPE_RESTAURANT_MAPPING (
    LC_SFC_CIBLE, Type_Restaurant
) VALUES (
    'XXX',
    'NOM_TYPE_RESTAURANT'
);
COMMIT;


1.4. Erreur de connexion Oracle

Symptôme :

ORA-01017: invalid username/password; logon denied
ou
ORA-12154: TNS:could not resolve the connect identifier specified

Diagnostic :

  1. Vérifier le fichier tnsnames.ora :

    echo %ORACLE_HOME%
    type %ORACLE_HOME%\network\admin\tnsnames.ora | find "TARIFAIRE"
    

  2. Tester la connexion avec SQL*Plus :

    sqlplus username/password@TARIFAIRE
    

  3. Vérifier les variables d'environnement :

    echo %ORACLE_HOME%
    echo %TNS_ADMIN%
    echo %PATH% | find "Oracle"
    

Solutions selon l'erreur :

ORA-01017 (Mauvais identifiant/mot de passe) : - Vérifier les credentials - Vérifier que le compte n'est pas verrouillé - Contacter l'administrateur Oracle

ORA-12154 (TNS non trouvé) : 1. Vérifier que TARIFAIRE existe dans tnsnames.ora 2. Définir TNS_ADMIN si nécessaire :

set TNS_ADMIN=%ORACLE_HOME%\network\admin

ORA-12514 (Listener ne connaît pas le service) : 1. Vérifier le service Oracle :

tnsping TARIFAIRE


1.5. Module Python manquant

Symptôme :

ModuleNotFoundError: No module named 'pandas'
ou
ModuleNotFoundError: No module named 'oracledb'

Diagnostic :

python -c "import pandas; print(pandas.__version__)"
python -c "import oracledb; print(oracledb.__version__)"
python -c "import openpyxl; print(openpyxl.__version__)"

Solution - Installation des dépendances :

  1. Vérifier pip :

    python -m pip --version
    

  2. Installer les modules manquants :

    pip install pandas --break-system-packages
    pip install oracledb --break-system-packages
    pip install openpyxl --break-system-packages
    pip install python-dateutil --break-system-packages
    pip install scipy --break-system-packages
    pip install tqdm --break-system-packages
    

Note importante : Le flag --break-system-packages est obligatoire dans l'environnement Sysco.

  1. Vérifier l'installation :
    pip list | find "pandas"
    pip list | find "oracledb"
    

2. PROBLÈMES DE DONNÉES

2.1. Taux de matching corridors faible

Symptôme :

[INFO] → Résultats du matching :
[INFO]   - MASTER : 145,678 offres
[INFO]   - NATIONAL : 89,234 offres  
[INFO]   - NO_MATCH : 234,567 offres (50%)

Contexte : PT2QE fonctionne en ZOOM1 EXCLUSIF. Le matching tente d'abord une jointure MASTER (dimensions spécifiques), puis fallback vers NATIONAL si pas de match.

Diagnostic SQL détaillé :

  1. Analyser les offres non matchées :

    SELECT 
        CASE
            WHEN c.ID_ART IS NULL THEN 'Article sans corridor PT1CE'
            WHEN o.TYPE_CLIENT IS NULL THEN 'TYPE_CLIENT manquant'
            WHEN o.TYPE_RESTAURANT IS NULL THEN 'TYPE_RESTAURANT manquant'
            WHEN o.UNIVERS != 'ZOOM1' THEN 'UNIVERS non ZOOM1'
            ELSE 'Autre raison'
        END as RAISON_NON_MATCH,
        COUNT(*) as NB_OFFRES,
        COUNT(DISTINCT o.ID_CLN) as NB_CLIENTS,
        COUNT(DISTINCT o.ID_ART) as NB_ARTICLES
    FROM PT2QE_PRICE_OFFERS o
    LEFT JOIN (
        SELECT DISTINCT ID_ART, TYPE_CLIENT, TYPE_RESTAURANT, GEO
        FROM PT1CE_OPTIMAL_ZOOM1
        WHERE STATUS = 'OPTIMAL'
    ) c ON o.ID_ART = c.ID_ART
    WHERE NOT EXISTS (
        SELECT 1 FROM PT2QE_PRICE_OFFERS_ENRICHED e
        WHERE e.ID_CLN = o.ID_CLN 
          AND e.ID_ART = o.ID_ART
          AND e.HAS_CORRIDOR = 1
    )
    GROUP BY CASE
        WHEN c.ID_ART IS NULL THEN 'Article sans corridor PT1CE'
        WHEN o.TYPE_CLIENT IS NULL THEN 'TYPE_CLIENT manquant'
        WHEN o.TYPE_RESTAURANT IS NULL THEN 'TYPE_RESTAURANT manquant'
        WHEN o.UNIVERS != 'ZOOM1' THEN 'UNIVERS non ZOOM1'
        ELSE 'Autre raison'
    END
    ORDER BY NB_OFFRES DESC;
    

  2. Vérifier les articles sans corridor :

    -- Articles présents dans les offres mais absents de PT1CE
    SELECT 
        o.ID_ART,
        o.LC_ART,
        COUNT(DISTINCT o.ID_CLN) as NB_CLIENTS_CONCERNES,
        COUNT(*) as NB_OFFRES
    FROM PT2QE_PRICE_OFFERS o
    WHERE NOT EXISTS (
        SELECT 1 
        FROM PT1CE_OPTIMAL_ZOOM1 c
        WHERE c.ID_ART = o.ID_ART
          AND c.STATUS = 'OPTIMAL'
    )
    GROUP BY o.ID_ART, o.LC_ART
    ORDER BY NB_OFFRES DESC
    FETCH FIRST 50 ROWS ONLY;
    

Solutions selon la cause :

Cas 1 : Articles nouveaux sans corridor

Ces articles n'ont pas de corridor PT1CE car : - Nouveaux dans le référentiel - Pas assez d'historique transactionnel - Exclus du périmètre PT1CE

Options : 1. Accepter le fallback NATIONAL : PT2QE utilise automatiquement les corridors NATIONAL 2. Exclure ces articles : Modifier l'extraction pour les filtrer 3. Créer des corridors manuellement dans PT1CE : Voir documentation PT1CE

Cas 2 : Dimensions non mappées

Voir section 1.3 pour compléter les mappings.

Cas 3 : UNIVERS != ZOOM1

Diagnostic :

SELECT 
    UNIVERS,
    COUNT(*) as NB_OFFRES
FROM PT2QE_PRICE_OFFERS
GROUP BY UNIVERS
ORDER BY UNIVERS;

Solution : Par design, PT2QE ne traite que ZOOM1. Les offres ZOOM2/ZOOM3 sont filtrées dès l'extraction.

Si vous voyez des offres non-ZOOM1, c'est une anomalie → Vérifier la logique d'attribution d'UNIVERS dans extract_price_offers.py.


2.2. Hausses anormalement élevées

Symptôme :

[WARNING] 1,234 offres avec hausse > 30%

Diagnostic SQL - Identifier les cas extrêmes :

SELECT 
    r.ID_CLN,
    r.LC_CLN,
    r.ID_ART,
    r.LC_ART,
    r.TYPE_CLIENT,
    r.TYPE_RESTAURANT,
    r.UNIVERS,

    -- Prix et hausses
    r.PRIX_TARIF_ACTUEL,
    r.PRIX_RECOMMANDE,
    r.PCT_HAUSSE_FINALE * 100 as PCT_HAUSSE,
    (r.PRIX_RECOMMANDE - r.PRIX_TARIF_ACTUEL) as HAUSSE_EUROS,

    -- Positions
    r.POSITION_TARIF_ACTUEL_DANS_ANCIENNES_BORNES as POS_AVANT_ANCIENNES,
    r.PALIER_TARIF_ACTUEL_VS_NOUVELLES_BORNES as POS_AVANT_NOUVELLES,
    r.POSITION_NOUVEAU_PRIX_DANS_NOUVELLES_BORNES as POS_APRES,

    -- Arbre de décision
    r.DECISION_PATH,
    r.RECO_SELECTIONNEE,
    r.CAPPING_APPLIED,

    -- Contexte corridor
    r.NEW_PAS,
    r.NEW_PRB,
    r.PRICE_SENSITIVITY,

    -- Recommandations intermédiaires
    r.RECO1_BASE,
    r.RECO1_APRES_CAPPING_SENSIBILITE,
    r.RECO1_AVEC_CAPPING,
    r.RECO2

FROM PT2QE_RECOMMENDATIONS r
WHERE r.PCT_HAUSSE_FINALE > 0.30
ORDER BY r.PCT_HAUSSE_FINALE DESC
FETCH FIRST 100 ROWS ONLY;

Analyse des causes fréquentes :

Cause 1 : Prix actuel très bas (sous le PAS)

-- Vérifier les offres en BELOW_PAS
SELECT 
    COUNT(*) as NB_OFFRES_BELOW_PAS,
    AVG(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MOY_PCT
FROM PT2QE_RECOMMENDATIONS
WHERE POSITION_TARIF_ACTUEL_DANS_ANCIENNES_BORNES = 'BELOW_PAS'
  AND PCT_HAUSSE_FINALE > 0.30;

Explication : Si le prix actuel est sous le PAS et que RECO1 vise à remonter au PAS minimum, la hausse peut être très importante.

Solution - Ajuster le capping LOW pour ces cas :

  1. Créer corrections\capping_cubes_corrections.csv :

    UNIVERS;TYPE_CLIENT;TYPE_RESTAURANT;GEO;CAPPING_HIGH;CAPPING_MEDIUM;CAPPING_LOW
    ZOOM1;TYPE_CLIENT_CONCERNE;TYPE_RESTO_CONCERNE;GEO_CONCERNEE;0,02;0,05;0,15
    

  2. Relancer avec l'option 2 (Ajuster cappings).

Cause 2 : Repositionnement agressif (CHEMIN 3)

-- Analyser les repositionnements forts
SELECT 
    POSITION_TARIF_ACTUEL_DANS_ANCIENNES_BORNES,
    POSITION_NOUVEAU_PRIX_DANS_NOUVELLES_BORNES,
    COUNT(*) as NB_OFFRES,
    AVG(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MOY_PCT,
    MIN(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MIN_PCT,
    MAX(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MAX_PCT
FROM PT2QE_RECOMMENDATIONS
WHERE PCT_HAUSSE_FINALE > 0.30
  AND DECISION_PATH = 'OPTIMISATION_STANDARD'
GROUP BY 
    POSITION_TARIF_ACTUEL_DANS_ANCIENNES_BORNES,
    POSITION_NOUVEAU_PRIX_DANS_NOUVELLES_BORNES
ORDER BY HAUSSE_MOY_PCT DESC;

Explication : Les règles de repositionnement RECO1 peuvent être très agressives si configurées pour sauter plusieurs paliers.

Solution - Modifier les règles RECO1 :

  1. Éditer config\pt2qe_config.json :

    {
      "recommendations": {
        "reco1_rules": [
          {
            "position": "PL5_PL6",
            "condition": "PRIX_TARIF_ACTUEL > NEW_BORNE_PL6_PLX",
            "action": "TO_PL5",
            "target": "NEW_BORNE_PL5_PL6",
            "comment": "Remonter vers PL5 (au lieu de PL6)"
          }
        ]
      }
    }
    

  2. Relancer le calcul complet.

Cause 3 : Capping insuffisant

-- Vérifier les cappings appliqués pour les hausses > 30%
SELECT 
    CAPPING_APPLIED,
    PRICE_SENSITIVITY,
    COUNT(*) as NB_OFFRES,
    AVG(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MOY_PCT
FROM PT2QE_RECOMMENDATIONS
WHERE PCT_HAUSSE_FINALE > 0.30
GROUP BY CAPPING_APPLIED, PRICE_SENSITIVITY
ORDER BY NB_OFFRES DESC;

Si CAPPING_APPLIED = 'NONE', les cappings ne s'appliquent pas → Ajuster les valeurs de capping.


2.3. Données historiques manquantes (4Q)

Symptôme :

[INFO] 123,456 offres sans historique transactionnel

Contexte : PT2QE enrichit chaque offre avec des métriques transactionnelles sur les 4 derniers trimestres fiscaux COMPLETS.

Diagnostic - Vérifier la période d'extraction :

-- Afficher la période utilisée
SELECT DISTINCT
    TO_CHAR(MIN(o.EXTRACTION_DATE), 'YYYY-MM-DD') as DATE_EXTRACTION,
    -- Récupérer la période depuis les logs du run
    NULL as PERIODE_4Q_DEBUT,
    NULL as PERIODE_4Q_FIN
FROM PT2QE_PRICE_OFFERS o;

Note : Les dates exactes de la période 4Q sont affichées au démarrage du traitement dans les logs.

Diagnostic - Offres sans CA :

SELECT 
    COUNT(*) as TOTAL_OFFRES,
    SUM(CASE WHEN MT_CAB_4Q = 0 THEN 1 ELSE 0 END) as NB_SANS_CA,
    SUM(CASE WHEN MT_CAB_4Q = 0 THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as PCT_SANS_CA,
    SUM(CASE WHEN QT_UF_4Q = 0 THEN 1 ELSE 0 END) as NB_SANS_VOLUME,
    SUM(CASE WHEN FG_HM IS NULL THEN 1 ELSE 0 END) as NB_SANS_FG_HM
FROM PT2QE_PRICE_OFFERS;

Diagnostic - Par type client :

SELECT 
    TYPE_CLIENT,
    COUNT(*) as NB_OFFRES,
    SUM(CASE WHEN MT_CAB_4Q = 0 THEN 1 ELSE 0 END) as NB_SANS_CA,
    ROUND(SUM(CASE WHEN MT_CAB_4Q = 0 THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) as PCT_SANS_CA
FROM PT2QE_PRICE_OFFERS
GROUP BY TYPE_CLIENT
ORDER BY PCT_SANS_CA DESC;

Causes et solutions :

Cas 1 : Nouveaux clients sans historique

Normal pour des clients récents. Options : 1. Accepter : Ces offres auront MT_CAB_4Q = 0 et FG_HM sera estimé 2. Exclure : Filtrer ces clients dans l'extraction

Cas 2 : Période 4Q trop ancienne

Si la période 4Q ne couvre pas les transactions récentes : - Vérifier SYS_MD_CALENDRIER_SYSCO pour la cohérence - Les 4Q sont calculés automatiquement par PeriodManager

Diagnostic de la période :

-- Vérifier la semaine en cours selon le calendrier fiscal
SELECT 
    ID_SEM,
    LC_EXF_SEF,
    ID_EXF,
    NO_PEF,
    NO_TRF
FROM SYS_MD_CALENDRIER_SYSCO
WHERE FG_SEM_EN_COURS = 'X';

Cas 3 : Filtres transactionnels trop restrictifs

Les filtres appliqués dans extract_price_offers.py sont : - ID_TYP_FAC = 'ZF2' - LC_TC_INTRA IN ('Brake', 'D2 hors Brake') - COALESCE(TRIM(FG_PRESTA), '0') = '0' - COALESCE(TRIM(FG_MARCHANDISE), ' ') IN ('X', '1') - MT_GM4 IS NOT NULL - MT_CAB > 0 - QT_UF > 0 - Exclusion Prix promo (PRP_LABELS)

Vérifier si trop restrictifs :

-- Comparer volume avec/sans filtres
WITH AVEC_FILTRES AS (
    SELECT COUNT(*) as NB_LIGNES
    FROM SYS_FACTURE_LIGNE f
    JOIN SYS_MD_CLIENT c ON f.ID_CLN_KEY = c.ID_CLN_KEY
    WHERE f.DT_CDE BETWEEN DATE '2024-01-01' AND DATE '2024-12-31'
      AND f.ID_TYP_FAC = 'ZF2'
      AND c.LC_TC_INTRA IN ('Brake', 'D2 hors Brake')
      AND COALESCE(TRIM(f.FG_PRESTA), '0') = '0'
      AND f.MT_CAB > 0
      AND f.QT_UF > 0
),
SANS_FILTRES AS (
    SELECT COUNT(*) as NB_LIGNES
    FROM SYS_FACTURE_LIGNE f
    JOIN SYS_MD_CLIENT c ON f.ID_CLN_KEY = c.ID_CLN_KEY
    WHERE f.DT_CDE BETWEEN DATE '2024-01-01' AND DATE '2024-12-31'
      AND f.ID_TYP_FAC = 'ZF2'
)
SELECT 
    a.NB_LIGNES as AVEC_FILTRES,
    s.NB_LIGNES as SANS_FILTRES,
    ROUND(a.NB_LIGNES * 100.0 / s.NB_LIGNES, 1) as PCT_CONSERVE
FROM AVEC_FILTRES a, SANS_FILTRES s;


2.4. Valeurs NULL dans les positions

Symptôme : Des offres ont POSITION_TARIF_ACTUEL_DANS_ANCIENNES_BORNES = NULL.

Diagnostic :

SELECT 
    COUNT(*) as NB_AVEC_POSITION_NULL
FROM PT2QE_RECOMMENDATIONS
WHERE POSITION_TARIF_ACTUEL_DANS_ANCIENNES_BORNES IS NULL
   OR POSITION_NOUVEAU_PRIX_DANS_NOUVELLES_BORNES IS NULL;

Cause : Bornes manquantes ou prix NULL.

Diagnostic détaillé :

SELECT 
    r.ID_CLN,
    r.ID_ART,
    r.PRIX_TARIF_ACTUEL,
    r.PAS_ACTIF,
    r.PRB_ACTIF,
    r.BORNE_PL1_PL2,
    r.BORNE_PL2_PL3,
    r.BORNE_PL3_PL4,
    r.POSITION_TARIF_ACTUEL_DANS_ANCIENNES_BORNES
FROM PT2QE_RECOMMENDATIONS r
WHERE r.POSITION_TARIF_ACTUEL_DANS_ANCIENNES_BORNES IS NULL
FETCH FIRST 20 ROWS ONLY;

Solution : Vérifier l'intégrité des données PT1CE pour ces articles.


3. PROBLÈMES DE PERFORMANCE

3.1. Phase 1 (Extraction) très lente

Symptôme : Phase 1 dure excessivement longtemps.

Diagnostic - Vérifier les statistiques tables :

SELECT 
    table_name,
    num_rows,
    blocks,
    last_analyzed,
    CASE 
        WHEN last_analyzed IS NULL THEN 'JAMAIS'
        WHEN last_analyzed < SYSDATE - 30 THEN 'OBSOLETE (>30j)'
        WHEN last_analyzed < SYSDATE - 7 THEN 'ANCIEN (>7j)'
        ELSE 'OK'
    END as ETAT_STATS
FROM user_tables
WHERE table_name IN (
    'SYS_TARIF_SIMULATION',
    'SYS_FACTURE_LIGNE',
    'SYS_MD_CLIENT',
    'SYS_MD_ARTICLE',
    'SYS_MD_CONDITION'
)
ORDER BY table_name;

Si ETAT_STATS = 'OBSOLETE' ou 'JAMAIS' :

Solution - Rafraîchir les statistiques :

-- En tant qu'administrateur ou avec droits suffisants
BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(
        ownname => USER,
        tabname => 'SYS_FACTURE_LIGNE',
        estimate_percent => 10,
        method_opt => 'FOR ALL COLUMNS SIZE AUTO',
        cascade => TRUE
    );
END;
/

BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(
        ownname => USER,
        tabname => 'SYS_TARIF_SIMULATION',
        estimate_percent => 10,
        method_opt => 'FOR ALL COLUMNS SIZE AUTO',
        cascade => TRUE
    );
END;
/

BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(
        ownname => USER,
        tabname => 'SYS_MD_CLIENT',
        estimate_percent => 10,
        method_opt => 'FOR ALL COLUMNS SIZE AUTO',
        cascade => TRUE
    );
END;
/

Diagnostic - Vérifier les sessions longues :

Si l'extraction est en cours, vérifier la requête active :

SELECT 
    s.sid,
    s.serial#,
    s.username,
    s.status,
    s.sql_id,
    s.last_call_et as SECONDES_ACTIVES,
    q.sql_text
FROM v$session s
LEFT JOIN v$sql q ON s.sql_id = q.sql_id
WHERE s.username = USER
  AND s.status = 'ACTIVE'
ORDER BY s.last_call_et DESC;

Solution - Optimiser la requête d'extraction :

La requête d'extraction utilise /*+ PARALLEL(8) */. Si trop lent :

  1. Réduire le parallélisme : Éditer extract_price_offers.py, remplacer PARALLEL(8) par PARALLEL(4).

  2. Ajouter des hints supplémentaires :

    /*+ PARALLEL(8) USE_HASH(f c a) */
    


3.2. Phase 2 (Calcul recommandations) très lente

Symptôme : Phase 2 ne progresse pas.

Diagnostic SQL - Vérifier les tables créées :

SELECT 
    table_name,
    num_rows,
    TO_CHAR(last_analyzed, 'YYYY-MM-DD HH24:MI:SS') as last_analyzed
FROM user_tables
WHERE table_name IN (
    'PT2QE_PRICE_OFFERS',
    'PT2QE_PRICE_OFFERS_ENRICHED',
    'PT2QE_CAPPING_CUBES',
    'PT2QE_RECOMMENDATIONS'
)
ORDER BY table_name;

Diagnostic - Session active :

-- Vérifier la requête en cours
SELECT 
    s.sid,
    s.serial#,
    s.sql_id,
    s.last_call_et / 60 as MINUTES_ACTIVES,
    SUBSTR(q.sql_text, 1, 200) as DEBUT_SQL
FROM v$session s
LEFT JOIN v$sql q ON s.sql_id = q.sql_id
WHERE s.username = USER
  AND s.status = 'ACTIVE';

Solution - Optimisation config :

Éditer config\pt2qe_config.json :

{
  "processing": {
    "batch_size": 50000,
    "parallel_degree": 4
  }
}

Solution - Réduire le périmètre pour test :

Modifier temporairement calculate_recommendations.py pour limiter le volume :

-- Ajouter à la fin de la clause WHERE
AND ROWNUM <= 10000


3.3. Mémoire insuffisante (Python)

Symptôme :

MemoryError: Unable to allocate array with shape (XXX, YYY)

Cause : Pandas charge trop de données en mémoire.

Solution 1 - Réduire batch_size :

Éditer config\pt2qe_config.json :

{
  "processing": {
    "batch_size": 25000
  }
}

Solution 2 - Augmenter mémoire Python (si possible) :

set PYTHONMALLOC=malloc

Solution 3 - Traiter par chunks :

Si le problème persiste, contacter le support pour activer le mode chunk.


4. PROBLÈMES DE CALCUL

4.1. Recommandations incohérentes

Symptôme : Prix recommandé < Prix actuel (ne devrait JAMAIS arriver).

Diagnostic SQL :

-- Détecter les anomalies
SELECT 
    r.ID_CLN,
    r.ID_ART,
    r.PRIX_TARIF_ACTUEL,
    r.PRIX_RECOMMANDE,
    r.PCT_HAUSSE_FINALE * 100 as PCT_HAUSSE,
    r.DECISION_PATH,
    r.RECO_SELECTIONNEE,
    r.CAPPING_APPLIED
FROM PT2QE_RECOMMENDATIONS r
WHERE r.PRIX_RECOMMANDE < r.PRIX_TARIF_ACTUEL;

Si des lignes sont retournées, c'est un BUG.

Procédure de remontée :

  1. Exporter le résultat :

    SELECT * 
    FROM PT2QE_RECOMMENDATIONS
    WHERE PRIX_RECOMMANDE < PRIX_TARIF_ACTUEL;
    

  2. Sauvegarder en CSV.

  3. Contacter le support avec :

  4. Le CSV des anomalies
  5. Les logs complets du run
  6. Le fichier capping_type_client.csv utilisé
  7. Le fichier pt2qe_config.json

Solution temporaire - Corriger manuellement :

-- NE PAS FAIRE en production sans validation
UPDATE PT2QE_RECOMMENDATIONS
SET PRIX_RECOMMANDE = PRIX_TARIF_ACTUEL,
    PCT_HAUSSE_FINALE = 0
WHERE PRIX_RECOMMANDE < PRIX_TARIF_ACTUEL;
COMMIT;


4.2. Capping non appliqué

Symptôme : RECO1_AVEC_CAPPING = RECO1_BASE malgré hausse importante.

Diagnostic SQL :

SELECT 
    r.ID_CLN,
    r.ID_ART,
    r.PRICE_SENSITIVITY,
    r.RECO1_BASE,
    r.RECO1_APRES_CAPPING_SENSIBILITE,
    r.RECO1_AVEC_CAPPING,
    r.PRIX_TARIF_ACTUEL,
    (r.RECO1_BASE - r.PRIX_TARIF_ACTUEL) / r.PRIX_TARIF_ACTUEL * 100 as PCT_HAUSSE_BASE,
    r.TYPE_CLIENT,
    r.TYPE_RESTAURANT,
    r.GEO,
    r.CAPPING_APPLIED
FROM PT2QE_RECOMMENDATIONS r
WHERE r.RECO1_BASE = r.RECO1_AVEC_CAPPING
  AND r.RECO1_BASE > r.PRIX_TARIF_ACTUEL * 1.20  -- Hausse > 20%
  AND r.DECISION_PATH = 'OPTIMISATION_STANDARD'
FETCH FIRST 50 ROWS ONLY;

Vérifier les cappings associés :

-- Remplacer par les valeurs trouvées ci-dessus
SELECT *
FROM PT2QE_CAPPING_CUBES
WHERE UNIVERS = 'ZOOM1'
  AND TYPE_CLIENT = 'XXX'
  AND TYPE_RESTAURANT = 'YYY'
  AND NVL(GEO, 'NULL') = NVL('ZZZ', 'NULL');

Causes possibles :

Cas 1 : Valeurs NULL dans PT2QE_CAPPING_CUBES

Si CAPPING_HIGH/MEDIUM/LOW = NULL :

Solution :

-- Mettre à jour avec valeurs par défaut
UPDATE PT2QE_CAPPING_CUBES
SET CAPPING_HIGH = 0.05,
    CAPPING_MEDIUM = 0.15,
    CAPPING_LOW = 0.20
WHERE CAPPING_HIGH IS NULL
   OR CAPPING_MEDIUM IS NULL
   OR CAPPING_LOW IS NULL;
COMMIT;

Relancer avec Option 2 (Ajuster cappings).

Cas 2 : Jointure cube non réalisée

Vérifier la jointure :

SELECT 
    COUNT(*) as TOTAL,
    SUM(CASE WHEN c.TYPE_CLIENT IS NOT NULL THEN 1 ELSE 0 END) as AVEC_CAPPING
FROM PT2QE_PRICE_OFFERS_ENRICHED e
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')
WHERE e.HAS_CORRIDOR = 1;

Si AVEC_CAPPING faible, problème de jointure → Vérifier les dimensions.


4.3. DECISION_PATH incorrect

Symptôme : Arbre de décision ne suit pas la logique attendue.

Diagnostic - Répartition des chemins :

SELECT 
    DECISION_PATH,
    COUNT(*) as NB_OFFRES,
    AVG(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MOY_PCT,
    MIN(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MIN_PCT,
    MAX(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MAX_PCT
FROM PT2QE_RECOMMENDATIONS
GROUP BY DECISION_PATH
ORDER BY NB_OFFRES DESC;

Résultats attendus : - PAS_BAISSE_GEL_PRIX : Hausses = 0% - PL1_CONSERVATION_PREMIUM : Hausses faibles (conservation + plancher) - OPTIMISATION_STANDARD : Hausses variables selon RECO1/RECO2

Diagnostic détaillé CHEMIN 1 :

-- CHEMIN 1 : Vérifier que NEW_PAS < PAS_ACTIF
SELECT 
    ID_CLN,
    ID_ART,
    PAS_ACTIF,
    NEW_PAS,
    PRIX_TARIF_ACTUEL,
    PRIX_RECOMMANDE,
    PCT_HAUSSE_FINALE * 100 as PCT_HAUSSE,
    DECISION_PATH
FROM PT2QE_RECOMMENDATIONS
WHERE DECISION_PATH = 'PAS_BAISSE_GEL_PRIX'
  AND (NEW_PAS >= PAS_ACTIF OR PCT_HAUSSE_FINALE != 0)
FETCH FIRST 20 ROWS ONLY;

Si des lignes retournées → BUG dans la logique du CHEMIN 1.

Diagnostic détaillé CHEMIN 2 :

-- CHEMIN 2 : Vérifier que prix était dans PL1 anciennes bornes
SELECT 
    ID_CLN,
    ID_ART,
    PRIX_TARIF_ACTUEL,
    PRB_ACTIF,
    BORNE_PL1_PL2,
    POSITION_TARIF_ACTUEL_DANS_ANCIENNES_BORNES,
    DECISION_PATH
FROM PT2QE_RECOMMENDATIONS
WHERE DECISION_PATH = 'PL1_CONSERVATION_PREMIUM'
  AND POSITION_TARIF_ACTUEL_DANS_ANCIENNES_BORNES != 'PL1'
FETCH FIRST 20 ROWS ONLY;

Si des lignes retournées → BUG dans la détection PL1 anciennes.


4.4. Les 3 positions incorrectes

Contexte : PT2QE calcule 3 positions : 1. POSITION_TARIF_ACTUEL_DANS_ANCIENNES_BORNES : Prix actuel vs bornes PT0CE 2. PALIER_TARIF_ACTUEL_VS_NOUVELLES_BORNES : Prix actuel vs bornes PT1CE 3. POSITION_NOUVEAU_PRIX_DANS_NOUVELLES_BORNES : Prix recommandé vs bornes PT1CE

Diagnostic - Vérifier la cohérence :

SELECT 
    ID_CLN,
    ID_ART,

    -- Position 1
    PRIX_TARIF_ACTUEL,
    PAS_ACTIF,
    PRB_ACTIF,
    BORNE_PL1_PL2,
    POSITION_TARIF_ACTUEL_DANS_ANCIENNES_BORNES,

    -- Position 2
    NEW_PAS,
    NEW_PRB,
    NEW_BORNE_PL1_PL2,
    PALIER_TARIF_ACTUEL_VS_NOUVELLES_BORNES,

    -- Position 3
    PRIX_RECOMMANDE,
    POSITION_NOUVEAU_PRIX_DANS_NOUVELLES_BORNES

FROM PT2QE_RECOMMENDATIONS
WHERE 
    -- Vérifier incohérence Position 1
    (POSITION_TARIF_ACTUEL_DANS_ANCIENNES_BORNES = 'PL1' 
     AND (PRIX_TARIF_ACTUEL > PRB_ACTIF OR PRIX_TARIF_ACTUEL <= BORNE_PL1_PL2))
    OR
    -- Vérifier incohérence Position 2
    (PALIER_TARIF_ACTUEL_VS_NOUVELLES_BORNES = 'PL1'
     AND (PRIX_TARIF_ACTUEL > NEW_PRB OR PRIX_TARIF_ACTUEL <= NEW_BORNE_PL1_PL2))
    OR
    -- Vérifier incohérence Position 3
    (POSITION_NOUVEAU_PRIX_DANS_NOUVELLES_BORNES = 'PL1'
     AND (PRIX_RECOMMANDE > NEW_PRB OR PRIX_RECOMMANDE <= NEW_BORNE_PL1_PL2))
FETCH FIRST 50 ROWS ONLY;

Si des lignes retournées → Problème dans le calcul des positions.

Vérification manuelle des bornes :

-- Recalculer manuellement la position pour vérifier
SELECT 
    r.ID_CLN,
    r.ID_ART,
    r.PRIX_TARIF_ACTUEL,
    r.NEW_PAS,
    r.NEW_PRB,
    r.NEW_BORNE_PL1_PL2,
    r.NEW_BORNE_PL2_PL3,
    r.NEW_BORNE_PL3_PL4,
    r.NEW_BORNE_PL4_PL5,
    r.NEW_BORNE_PL5_PL6,
    r.NEW_BORNE_PL6_PLX,
    r.PALIER_TARIF_ACTUEL_VS_NOUVELLES_BORNES,
    -- Recalcul manuel
    CASE
        WHEN r.PRIX_TARIF_ACTUEL > r.NEW_PRB THEN 'ABOVE_PRB'
        WHEN r.PRIX_TARIF_ACTUEL >= r.NEW_BORNE_PL1_PL2 THEN 'PL1'
        WHEN r.PRIX_TARIF_ACTUEL >= r.NEW_BORNE_PL2_PL3 THEN 'PL2'
        WHEN r.PRIX_TARIF_ACTUEL >= r.NEW_BORNE_PL3_PL4 THEN 'PL3'
        WHEN r.PRIX_TARIF_ACTUEL >= r.NEW_BORNE_PL4_PL5 THEN 'PL4'
        WHEN r.PRIX_TARIF_ACTUEL >= r.NEW_BORNE_PL5_PL6 THEN 'PL5'
        WHEN r.PRIX_TARIF_ACTUEL >= r.NEW_BORNE_PL6_PLX THEN 'PL6'
        WHEN r.PRIX_TARIF_ACTUEL >= r.NEW_PAS THEN 'PLX'
        ELSE 'BELOW_PAS'
    END as POSITION_RECALCULEE
FROM PT2QE_RECOMMENDATIONS r
WHERE r.PALIER_TARIF_ACTUEL_VS_NOUVELLES_BORNES != 
    CASE
        WHEN r.PRIX_TARIF_ACTUEL > r.NEW_PRB THEN 'ABOVE_PRB'
        WHEN r.PRIX_TARIF_ACTUEL >= r.NEW_BORNE_PL1_PL2 THEN 'PL1'
        WHEN r.PRIX_TARIF_ACTUEL >= r.NEW_BORNE_PL2_PL3 THEN 'PL2'
        WHEN r.PRIX_TARIF_ACTUEL >= r.NEW_BORNE_PL3_PL4 THEN 'PL3'
        WHEN r.PRIX_TARIF_ACTUEL >= r.NEW_BORNE_PL4_PL5 THEN 'PL4'
        WHEN r.PRIX_TARIF_ACTUEL >= r.NEW_BORNE_PL5_PL6 THEN 'PL5'
        WHEN r.PRIX_TARIF_ACTUEL >= r.NEW_BORNE_PL6_PLX THEN 'PL6'
        WHEN r.PRIX_TARIF_ACTUEL >= r.NEW_PAS THEN 'PLX'
        ELSE 'BELOW_PAS'
    END
FETCH FIRST 20 ROWS ONLY;


5. DIAGNOSTIC ET LOGS

5.1. Activer les logs détaillés

Via le menu START.bat :

  1. Lancer START.bat
  2. Choisir [6] CHANGER LE NIVEAU DE LOG
  3. Sélectionner [3] Détaillé (mode debug)

Via ligne de commande :

python pt2qe_main.py TARIFAIRE -vv --capping-file inputs\capping_type_client.csv

Options de log : - -qq : Silencieux (erreurs seulement) - (aucun) : Normal (INFO, WARNING, ERROR) - -vv : Détaillé (DEBUG + tout)


5.2. Requêtes de diagnostic globales

Vue d'ensemble du run :

CREATE OR REPLACE VIEW V_PT2QE_DIAGNOSTIC AS
SELECT 'Offres extraites' as METRIQUE, COUNT(*) as VALEUR
FROM PT2QE_PRICE_OFFERS
UNION ALL
SELECT 'Clients distincts', COUNT(DISTINCT ID_CLN)
FROM PT2QE_PRICE_OFFERS
UNION ALL
SELECT 'Articles distincts', COUNT(DISTINCT ID_ART)
FROM PT2QE_PRICE_OFFERS
UNION ALL
SELECT 'Offres avec TYPE_CLIENT', 
       SUM(CASE WHEN TYPE_CLIENT IS NOT NULL 
                AND TYPE_CLIENT != 'Hors référentiel' 
           THEN 1 ELSE 0 END)
FROM PT2QE_PRICE_OFFERS
UNION ALL
SELECT 'Offres avec TYPE_RESTAURANT',
       SUM(CASE WHEN TYPE_RESTAURANT IS NOT NULL 
                AND TYPE_RESTAURANT != 'Hors référentiel'
           THEN 1 ELSE 0 END)
FROM PT2QE_PRICE_OFFERS
UNION ALL
SELECT 'Offres avec FG_HM',
       SUM(CASE WHEN FG_HM IS NOT NULL THEN 1 ELSE 0 END)
FROM PT2QE_PRICE_OFFERS
UNION ALL
SELECT 'Offres enrichies avec corridor',
       SUM(HAS_CORRIDOR)
FROM PT2QE_PRICE_OFFERS_ENRICHED
UNION ALL
SELECT 'Recommandations calculées',
       COUNT(*)
FROM PT2QE_RECOMMENDATIONS
UNION ALL
SELECT 'Hausse moyenne (%)',
       ROUND(AVG(PCT_HAUSSE_FINALE) * 100, 2)
FROM PT2QE_RECOMMENDATIONS;

-- Utilisation
SELECT * FROM V_PT2QE_DIAGNOSTIC;

Statistiques par étape :

-- Étape 1 : Extraction
SELECT 
    'EXTRACTION' as ETAPE,
    COUNT(*) as NB_LIGNES,
    COUNT(DISTINCT ID_CLN) as NB_CLIENTS,
    COUNT(DISTINCT ID_ART) as NB_ARTICLES,
    TO_CHAR(MAX(EXTRACTION_DATE), 'YYYY-MM-DD HH24:MI:SS') as DATE_EXTRACTION
FROM PT2QE_PRICE_OFFERS

UNION ALL

-- Étape 2 : Enrichissement
SELECT 
    'ENRICHISSEMENT',
    COUNT(*),
    COUNT(DISTINCT ID_CLN),
    COUNT(DISTINCT ID_ART),
    NULL
FROM PT2QE_PRICE_OFFERS_ENRICHED

UNION ALL

-- Étape 3 : Recommandations
SELECT 
    'RECOMMANDATIONS',
    COUNT(*),
    COUNT(DISTINCT ID_CLN),
    COUNT(DISTINCT ID_ART),
    TO_CHAR(MAX(CALCULATION_DATE), 'YYYY-MM-DD HH24:MI:SS')
FROM PT2QE_RECOMMENDATIONS;


5.3. Logs à surveiller

Extraction réussie :

[INFO] ✓ 2,345,678 offres de prix extraites (ZOOM1 uniquement)
[INFO]     Statistiques d'enrichissement:
[INFO]       - Total offres: 2,345,678
[INFO]       - Clients uniques: 45,678
[INFO]       - Articles uniques: 23,456
[INFO]       - Avec TYPE_CLIENT: 2,298,765 (98.0%)
[INFO]       - Avec TYPE_RESTAURANT: 2,312,456 (98.6%)
[INFO]       - Avec FG_HM: 2,156,432 (91.9%)

Matching corridors :

[INFO]       → Résultats du matching :
[INFO]         - MASTER : 1,876,543 offres, 34,567 clients, 18,765 articles
[INFO]         - NATIONAL : 234,567 offres, 5,678 clients, 3,456 articles  
[INFO]         - NO_MATCH : 234,568 offres, 5,433 clients, 1,235 articles
[INFO]       → Total : 2,111,110/2,345,678 offres avec corridor (90.0%)

Calcul recommandations :

[INFO]     ✓ 2,111,110 recommandations calculées
[INFO]       - 1,876,543 clients
[INFO]       - 18,765 articles
[INFO]       - Hausse moyenne: 3.7%

Analyse et export :

[INFO]     → Export détaillé : 2,111,110 lignes
[INFO]     → Statistiques par dimension exportées
[INFO]     → Statistiques par path exportées
[INFO]     → Statistiques des distributions de capping exportées
[INFO]     → Analyse d'impact exportée
[INFO]     → Distribution des hausses exportée


5.4. Historique des exécutions

Lister les runs précédents :

dir /b /a:d outputs\run_*
dir /b /a:d outputs\corrections_*
dir /b /a:d outputs\final_*

Comparer deux runs :

-- Comparer hausse moyenne entre deux runs
WITH RUN1 AS (
    SELECT AVG(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MOY
    FROM PT2QE_RECOMMENDATIONS  -- Run actuel
),
RUN2 AS (
    SELECT AVG(PCT_HAUSSE_FINALE) * 100 as HAUSSE_MOY
    FROM PT2QE_RECOMMENDATIONS_BACKUP  -- Run précédent sauvegardé
)
SELECT 
    r1.HAUSSE_MOY as HAUSSE_RUN_ACTUEL,
    r2.HAUSSE_MOY as HAUSSE_RUN_PRECEDENT,
    r1.HAUSSE_MOY - r2.HAUSSE_MOY as DIFFERENCE
FROM RUN1 r1, RUN2 r2;

Sauvegarder un run pour comparaison :

-- Avant de relancer
CREATE TABLE PT2QE_RECOMMENDATIONS_BACKUP AS
SELECT * FROM PT2QE_RECOMMENDATIONS;


6. SOLUTIONS DE CONTOURNEMENT

6.1. Exclure des offres problématiques

Créer une table d'exclusion :

CREATE TABLE PT2QE_EXCLUSIONS (
    ID_CLN VARCHAR2(20),
    ID_ART VARCHAR2(20),
    RAISON VARCHAR2(255),
    DATE_AJOUT DATE DEFAULT SYSDATE
);

Ajouter des exclusions :

-- Exclure un client×article spécifique
INSERT INTO PT2QE_EXCLUSIONS (ID_CLN, ID_ART, RAISON)
VALUES ('123456', '789012', 'Prix bloqué manuellement');

-- Exclure tous les articles d'un client
INSERT INTO PT2QE_EXCLUSIONS (ID_CLN, ID_ART, RAISON)
SELECT '123456', ID_ART, 'Client en litiges'
FROM SYS_MD_ARTICLE
WHERE ID_GMM = 'XXX';

COMMIT;

Modifier l'extraction pour tenir compte des exclusions :

Éditer extract_price_offers.py, ajouter dans la clause WHERE finale :

AND NOT EXISTS (
    SELECT 1 FROM PT2QE_EXCLUSIONS e
    WHERE e.ID_CLN = o.ID_CLN 
    AND (e.ID_ART = o.ID_ART OR e.ID_ART IS NULL)
)


6.2. Forcer un TYPE_CLIENT par défaut

Si trop d'offres sans TYPE_CLIENT :

Éditer extract_price_offers.py, après l'extraction, ajouter :

-- Après création de la table PT2QE_PRICE_OFFERS
UPDATE PT2QE_PRICE_OFFERS
SET TYPE_CLIENT = 'AUTRES',
    TYPE_RESTAURANT = 'AUTRES'
WHERE (TYPE_CLIENT IS NULL OR TYPE_CLIENT = 'Hors référentiel')
   OR (TYPE_RESTAURANT IS NULL OR TYPE_RESTAURANT = 'Hors référentiel')
AND UNIVERS = 'ZOOM1';
COMMIT;

Note : Nécessite de créer les mappings AUTRES dans PT0CE au préalable.


6.3. Limiter les hausses maximales

Post-traitement pour plafonner à X% :

  1. Après le calcul des recommandations, exécuter :

    -- Plafonner à 25% maximum
    UPDATE PT2QE_RECOMMENDATIONS
    SET PRIX_RECOMMANDE = PRIX_TARIF_ACTUEL * 1.25,
        PCT_HAUSSE_FINALE = 0.25,
        CAPPING_APPLIED = 'POST_PROCESSING_25PCT'
    WHERE PCT_HAUSSE_FINALE > 0.25;
    COMMIT;
    

  2. Régénérer les analyses :

    python -c "from analyze_recommendations import RecommendationAnalyzer; from sysco.db.oracle import SysOraDB; from sysco.application import SysApplication; app = SysApplication('PT2QE', '1.0.0', 'Post-processing'); db = SysOraDB('TARIFAIRE'); analyzer = RecommendationAnalyzer(app, db); analyzer.analyze_and_export('PT2QE_RECOMMENDATIONS', Path('outputs/post_processing'))"
    


6.4. Reprise après échec

Identifier l'étape d'échec :

  1. Vérifier les tables existantes :
    SELECT table_name, num_rows
    FROM user_tables
    WHERE table_name LIKE 'PT2QE_%'
    ORDER BY table_name;
    

Scénarios de reprise :

Scénario 1 : Échec pendant Phase 1 (Extraction) - Tables présentes : Aucune ou PT2QE_PRICE_OFFERS vide - Solution : Relancer normalement (Option 1)

Scénario 2 : Échec après Phase 1 - Tables présentes : PT2QE_PRICE_OFFERS remplie - Solution : Utiliser --skip-extraction

python pt2qe_main.py TARIFAIRE --skip-extraction --capping-file inputs\capping_type_client.csv

Scénario 3 : Échec pendant Phase 2 - Tables présentes : PT2QE_PRICE_OFFERS, PT2QE_PRICE_OFFERS_ENRICHED - Solution : Nettoyer les tables partielles et relancer avec --skip-extraction

-- Nettoyer les tables partielles
DROP TABLE PT2QE_RECOMMENDATIONS PURGE;
DROP TABLE PT2QE_CAPPING_CUBES PURGE;
COMMIT;

Puis :

python pt2qe_main.py TARIFAIRE --skip-extraction --capping-file inputs\capping_type_client.csv


6.5. Mode test sur volume réduit

Pour tester rapidement sans traiter tout le volume :

  1. Créer une table de test :

    CREATE TABLE PT2QE_PRICE_OFFERS_TEST AS
    SELECT * FROM PT2QE_PRICE_OFFERS
    WHERE ROWNUM <= 10000;
    

  2. Modifier temporairement calculate_recommendations.py pour utiliser cette table.

  3. Ou utiliser un fichier de config test avec batch_size réduit.


7. VÉRIFICATIONS AVANT ESCALADE

Avant de contacter le support, effectuer ces vérifications :

Checklist complète

  • [ ] Tables PT1CE : Vérifier que PT1CE_OPTIMAL_* existent et sont remplies (STATUS = 'OPTIMAL')
  • [ ] Fichier capping : Vérifier inputs\capping_type_client.csv existe et format correct
  • [ ] Mappings : Vérifier PT0CE_TYPE_CLIENT_MAPPING et PT0CE_TYPE_RESTAURANT_MAPPING
  • [ ] Connexion Oracle : Tester avec SQL*Plus ou test_connection.py
  • [ ] Espace disque : Vérifier espace disponible sur le serveur
  • [ ] Statistiques Oracle : Vérifier que les tables systèmes ont des stats à jour
  • [ ] Locks Oracle : Vérifier qu'aucun lock ne bloque les tables
  • [ ] Période 4Q : Vérifier que SYS_MD_CALENDRIER_SYSCO est à jour
  • [ ] Logs complets : Sauvegarder les logs du run en échec
  • [ ] Résultats SQL diagnostic : Exécuter les requêtes de diagnostic et sauvegarder les résultats

Informations à fournir au support

  1. Logs complets du run (copier la sortie console complète)
  2. Résultat de la requête diagnostic :
    SELECT * FROM V_PT2QE_DIAGNOSTIC;
    
  3. Configuration utilisée :
  4. inputs\capping_type_client.csv
  5. config\pt2qe_config.json (si modifié)
  6. Période d'exécution (date et heure)
  7. Tables existantes :
    SELECT table_name, num_rows, last_analyzed
    FROM user_tables
    WHERE table_name LIKE 'PT2QE_%' OR table_name LIKE 'PT1CE_OPTIMAL%'
    ORDER BY table_name;
    
  8. Message d'erreur exact (si applicable)
  9. Commande exécutée (ligne de commande ou option menu)

8. ERREURS SPÉCIFIQUES

8.1. ORA-01555: snapshot too old

Symptôme :

ORA-01555: snapshot too old: rollback segment number XX with name "XXX" too small

Cause : Transaction trop longue, espace UNDO insuffisant.

Solution :

  1. Réduire la taille des transactions : Éditer config\pt2qe_config.json :

    {
      "processing": {
        "batch_size": 25000
      }
    }
    

  2. Réexécuter rapidement : Éviter les interruptions pendant le traitement.

  3. Contacter DBA si le problème persiste (augmenter UNDO_RETENTION).


8.2. ORA-01652: unable to extend temp segment

Symptôme :

ORA-01652: unable to extend temp segment by XXX in tablespace TEMP

Cause : Espace temporaire Oracle insuffisant.

Diagnostic :

SELECT 
    tablespace_name,
    ROUND(SUM(bytes)/1024/1024/1024, 2) as SIZE_GB,
    ROUND(SUM(bytes - NVL(free_bytes, 0))/1024/1024/1024, 2) as USED_GB
FROM (
    SELECT tablespace_name, bytes, 0 as free_bytes
    FROM dba_temp_files
    UNION ALL
    SELECT tablespace_name, 0, bytes
    FROM dba_free_space
    WHERE tablespace_name LIKE 'TEMP%'
)
GROUP BY tablespace_name;

Solution :

  1. Réduire le volume traité (voir section 6.5)
  2. Contacter DBA pour étendre le tablespace TEMP
  3. Nettoyer les objets temporaires :
    -- En tant qu'admin
    ALTER SYSTEM KILL SESSION 'XXX,YYY';  -- Tuer les sessions inactives
    

8.3. Timeout Python/Oracle

Symptôme : Programme s'arrête sans message d'erreur après longue attente.

Diagnostic : Vérifier les sessions Oracle actives :

SELECT 
    s.sid,
    s.serial#,
    s.username,
    s.status,
    s.last_call_et / 60 as MINUTES_INACTIVES
FROM v$session s
WHERE s.username = USER
ORDER BY s.last_call_et DESC;

Solution :

  1. Augmenter le timeout Python (si configurable)
  2. Vérifier le réseau entre client et serveur Oracle
  3. Relancer en mode verbose pour voir la progression

8.4. Fichiers de sortie corrompus ou vides

Symptôme : Fichiers CSV générés sont vides ou illisibles.

Diagnostic :

dir outputs\run_*\*.csv
type outputs\run_YYYYMMDD_HHMMSS\recommendations_detail.csv | more

Causes possibles :

  1. Encodage incorrect : Vérifier que encoding='cp1252' est utilisé
  2. Permissions : Vérifier les droits d'écriture sur le dossier outputs
  3. Espace disque plein : Vérifier l'espace disponible

Solution - Régénérer les exports :

Si les tables Oracle sont OK :

# Relancer uniquement les exports
python -c "
from analyze_recommendations import RecommendationAnalyzer
from sysco.db.oracle import SysOraDB
from sysco.application import SysApplication
from pathlib import Path

app = SysApplication('PT2QE_REEXPORT', '1.0.0', 'Reexport')
db = SysOraDB('TARIFAIRE')
analyzer = RecommendationAnalyzer(app, db)
analyzer.analyze_and_export('PT2QE_RECOMMENDATIONS', Path('outputs/reexport'))
"


9. PROBLÈMES DE CONFIGURATION

9.1. Modification des règles RECO1

Fichier à éditer : config\pt2qe_config.json

Structure actuelle :

{
  "recommendations": {
    "reco1_rules": [
      {
        "position": "ABOVE_PL1",
        "condition": "PRIX_TARIF_ACTUEL > NEW_BORNE_PL1_PL2",
        "action": "NO_CHANGE",
        "target": "PRIX_TARIF_ACTUEL",
        "comment": "Prix déjà en PL1, pas de changement"
      },
      {
        "position": "PL1_PL2",
        "condition": "PRIX_TARIF_ACTUEL > NEW_BORNE_PL2_PL3",
        "action": "TO_PL1",
        "target": "NEW_BORNE_PL1_PL2",
        "comment": "Remonter vers PL1"
      }
    ]
  }
}

Modifier une règle existante :

Exemple : Changer PL1_PL2 pour aller vers PL2 au lieu de PL1 :

{
  "position": "PL1_PL2",
  "condition": "PRIX_TARIF_ACTUEL > NEW_BORNE_PL2_PL3",
  "action": "TO_PL2",
  "target": "NEW_BORNE_PL2_PL3",
  "comment": "Remonter vers PL2 (modifié)"
}

Ajouter une règle :

{
  "position": "CUSTOM_RULE",
  "condition": "PRIX_TARIF_ACTUEL > NEW_BORNE_PL5_PL6 AND PRIX_TARIF_ACTUEL <= NEW_BORNE_PL4_PL5",
  "action": "TO_PL4",
  "target": "NEW_BORNE_PL4_PL5",
  "comment": "Règle personnalisée"
}

Ordre des règles : Les règles sont évaluées dans l'ordre du fichier. La première condition vraie est appliquée.

Validation après modification :

  1. Sauvegarder le fichier
  2. Relancer un calcul complet (Option 1)
  3. Vérifier la colonne RECO1_BASE dans les résultats

9.2. Modifier les cappings par défaut

Fichier à éditer : config\pt2qe_config.json

{
  "capping": {
    "default_high": 0.05,
    "default_medium": 0.15,
    "default_low": 0.20,
    "basiques": 0.50,
    "allow_overrides": true
  }
}

Valeurs recommandées : - default_high : 0.02 à 0.10 (produits sensibles) - default_medium : 0.05 à 0.20 (produits standards) - default_low : 0.10 à 0.30 (produits peu sensibles) - basiques : 0.30 à 0.70 (capping spécifique produits basiques)

Note : Ces valeurs par défaut sont utilisées si le fichier capping_type_client.csv ne contient pas de valeur pour un TYPE_CLIENT.


9.3. Problèmes avec le calendrier fiscal (PeriodManager)

Symptôme :

[ERROR] Impossible de trouver 4 trimestres complets avant YYYY-MM-DD

Diagnostic :

-- Vérifier la semaine en cours
SELECT 
    ID_SEM,
    LC_EXF_SEF,
    ID_EXF,
    NO_TRF,
    FG_SEM_EN_COURS
FROM SYS_MD_CALENDRIER_SYSCO
WHERE FG_SEM_EN_COURS = 'X';

Si aucune ligne : La table SYS_MD_CALENDRIER_SYSCO n'est pas à jour → Contacter l'équipe Data.

Vérifier les trimestres disponibles :

SELECT 
    ID_EXF || '_Q' || NO_TRF as QUARTER,
    MIN(ID_SEM) as START_DATE,
    MAX(ID_SEM) as END_DATE,
    COUNT(DISTINCT ID_SEM) as NB_SEMAINES
FROM SYS_MD_CALENDRIER_SYSCO
WHERE ID_EXF >= '2023'
GROUP BY ID_EXF, NO_TRF
ORDER BY ID_EXF DESC, NO_TRF DESC;

Solution temporaire :

Si la table est incomplète, modifier manuellement les dates dans pt2qe_main.py :

# Forcer des dates spécifiques (TEMPORAIRE)
app.start_date = date(2024, 1, 1)
app.end_date = date(2024, 12, 31)
app.fiscal_quarters = ['2024_Q01', '2024_Q02', '2024_Q03', '2024_Q04']

Puis commenter l'appel à period_manager.get_last_four_complete_fiscal_quarters().


10. MAINTENANCE ET NETTOYAGE

10.1. Purger les anciennes tables

Lister les tables PT2QE :

SELECT 
    table_name,
    num_rows,
    TO_CHAR(last_analyzed, 'YYYY-MM-DD') as last_analyzed,
    ROUND(bytes/1024/1024, 2) as SIZE_MB
FROM user_tables t
LEFT JOIN user_segments s ON t.table_name = s.segment_name
WHERE table_name LIKE 'PT2QE_%'
ORDER BY last_analyzed DESC NULLS LAST;

Supprimer les tables obsolètes :

-- ATTENTION : Vérifier avant de supprimer
DROP TABLE PT2QE_PRICE_OFFERS_20240101 PURGE;
DROP TABLE PT2QE_RECOMMENDATIONS_20240101 PURGE;

Script de purge automatique (à exécuter manuellement) :

-- Supprimer les tables PT2QE de plus de 30 jours
DECLARE
    v_sql VARCHAR2(4000);
BEGIN
    FOR t IN (
        SELECT table_name
        FROM user_tables
        WHERE table_name LIKE 'PT2QE_%'
          AND last_analyzed < SYSDATE - 30
    ) LOOP
        v_sql := 'DROP TABLE ' || t.table_name || ' PURGE';
        EXECUTE IMMEDIATE v_sql;
        DBMS_OUTPUT.PUT_LINE('Supprimé : ' || t.table_name);
    END LOOP;
END;
/


10.2. Archiver les résultats

Sauvegarder un run important :

REM Créer un dossier d'archive
mkdir archives\run_YYYYMMDD_description

REM Copier les fichiers CSV
xcopy outputs\run_YYYYMMDD_HHMMSS\*.csv archives\run_YYYYMMDD_description\ /Y

REM Copier la config utilisée
copy config\pt2qe_config.json archives\run_YYYYMMDD_description\
copy inputs\capping_type_client.csv archives\run_YYYYMMDD_description\

Sauvegarder les tables Oracle :

-- Créer des copies permanentes
CREATE TABLE PT2QE_RECOMMENDATIONS_ARCHIVE_YYYYMMDD AS
SELECT * FROM PT2QE_RECOMMENDATIONS;

CREATE TABLE PT2QE_PRICE_OFFERS_ARCHIVE_YYYYMMDD AS
SELECT * FROM PT2QE_PRICE_OFFERS;


10.3. Nettoyer les fichiers temporaires

Supprimer les dossiers de run :

REM Supprimer tous les runs sauf le dernier
for /d %%d in (outputs\run_*) do (
    echo Supprimer %%d ? (Ctrl+C pour annuler)
    pause
    rmdir /s /q "%%d"
)

Nettoyer le dossier corrections :

del corrections\*.csv


11. CONTACT SUPPORT

Si aucune solution de ce document ne résout votre problème :

  1. Rassembler les informations (voir section 7)
  2. Créer un dossier incident avec :
  3. Description détaillée du problème
  4. Logs complets
  5. Résultats des requêtes de diagnostic
  6. Configuration utilisée
  7. Contacter le support technique Pricing

Informations à ne PAS oublier : - Date et heure du run - Version de PT2QE (affichée au démarrage) - Environnement (TARIFAIRE, test, etc.) - Modifications apportées à la configuration standard