Tarifs EDF Tempo Automatisés

Bonjour a tous !

Je suis entrain d’automatiser l’obtention des tarifs EDF Tempo depuis le fichier pdf toujours disponible sur leur page :

https://particulier.edf.fr/content/dam/2-Actifs/Documents/Offres/Grille_prix_Tarif_Bleu.pdf

Ceci me permettra de faire des stats de couts sans avoir a me soucier d’un éventuel changement.

Pour se faire, j’ai utilisé ChatGPT et conçu un script Python qui parse le PDF et m’en extrait un JSON avec tous les tarifs.

L’idée est alors (avec un automatisme ou Node Red) de lire ce JSON et de produire mes statistiques en temps réel.

Ce script fonctionne très bien en l’exécutant depuis un Terminal dans HA OS mais je ne peux l’exécuter depuis un “command_line” … j’aurais besoin d’être conseillé et utiliser la bonne pratique (nouveau je viens de Jeedom)

Le script:

#!/usr/bin/python3
import requests
import pdfplumber
import json
import os

url = "https://particulier.edf.fr/content/dam/2-Actifs/Documents/Offres/Grille_prix_Tarif_Bleu.pdf"
pdf_file = "/config/www/edf_tempo.pdf"
json_file = "/config/www/edf_tempo.json"

# Télécharger le PDF
response = requests.get(url)
with open(pdf_file, "wb") as f:
    f.write(response.content)

result = {}
ignored_lines = []

with pdfplumber.open(pdf_file) as pdf:
    in_tempo = False
    for page in pdf.pages:
        text = page.extract_text()
        if not text:
            continue
        lines = text.split("\n")
        for line in lines:
            line = line.strip()
            # Début de la section Tempo
            if "Option Tempo" in line:
                in_tempo = True
                continue
            if in_tempo:
                # Fin de la section Tempo
                if line.startswith("Majoration") or line.startswith("EDF SA"):
                    in_tempo = False
                    break
                # Ignorer les en-têtes
                if any(word in line for word in ["Bleu", "Blanc", "Rouge", "kVA", "Souscrite"]):
                    continue
                # Traiter uniquement les lignes qui commencent par un chiffre
                if line and line[0].isdigit():
                    parts = line.split()
                    if len(parts) >= 8:
                        try:
                            kVA = parts[0]
                            result[kVA] = {
                                "abonnement": float(parts[1].replace(",", ".")),
                                "bleu": {"HC": float(parts[2].replace(",", ".")), "HP": float(parts[3].replace(",", "."))},
                                "blanc": {"HC": float(parts[4].replace(",", ".")), "HP": float(parts[5].replace(",", "."))},
                                "rouge": {"HC": float(parts[6].replace(",", ".")), "HP": float(parts[7].replace(",", "."))}
                            }
                        except ValueError:
                            ignored_lines.append(line)
                    else:
                        ignored_lines.append(line)
                else:
                    ignored_lines.append(line)

# Sauvegarder le JSON
os.makedirs(os.path.dirname(json_file), exist_ok=True)
with open(json_file, "w") as f:
    json.dump(result, f, indent=2)

# Afficher le JSON et les lignes ignorées
print(json.dumps(result, indent=2))
if ignored_lines:
    print("\nLignes ignorées (non conformes) :", ignored_lines)

Merci,

Sébastien

Oh je viens de voir qu’il y a une intégration existante pour récupérer ces informations…

Mais je suis quand même intéressé de savoir comment exécuter ce type de script python de façon automatique.

Merci !

Sébastien

Bonjour,
Est ce que via AI task, il ne serait pas possible de faire la même chose en une fois? L’IA parse le fichier puis indique les résultats dans des variables. Pas testé mais ça me semble possible…

Salut, je ne sais pas ce qu’est AI Task … vais y jeter un coup d’œil ce soir.

Mais bon savoir manier du script c’est quand même sympa :blush:

Edit:Finalement j’ai fait un petit flow Node Red a partir de l’URL utilisé dans l’intégration “Tarifs EDF“ et j’ai rajouté le prix de l’abonnement mensuel. Trop content :slight_smile:

Détails:

// Function node - extraction Tempo EDF depuis CSV (simplifié)
// Puissance demandée, peut être passée via msg.puissance (default 15 kVA)
const puissance = (msg.puissance || "15").toString().trim();

// msg.payload doit être un tableau
if (!Array.isArray(msg.payload)) {
    node.error("msg.payload doit être un tableau (CSV node configuré en 'a single array').");
    return null;
}

// Convertir DD/MM/YYYY => Date
function parseDateDMY(s) {
    if (!s) return null;
    const m = String(s).trim().match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
    if (!m) return null;
    const day = parseInt(m[1],10), month = parseInt(m[2],10)-1, year = parseInt(m[3],10);
    const d = new Date(year, month, day);
    if (d.getFullYear()!==year || d.getMonth()!==month || d.getDate()!==day) return null;
    return d;
}

// Convertir chaîne avec virgule -> float
function toFloat(v) {
    if (v === undefined || v === null || String(v).trim()==="") return null;
    const n = parseFloat(String(v).replace(/\s+/g,"").replace(",","."));
    return Number.isFinite(n) ? n : null;
}

// Filtrer lignes pour la puissance demandée
let lignes = msg.payload.filter(l => String(l.P_SOUSCRITE).trim() === puissance);

if (!lignes.length) {
    node.warn("Aucune donnée trouvée pour " + puissance + " kVA");
    msg.payload = { error:"no_data", puissance };
    return msg;
}

// Trier par date début décroissante
lignes.sort((a,b) => {
    const da = parseDateDMY(a.DATE_DEBUT);
    const db = parseDateDMY(b.DATE_DEBUT);
    if (!da && !db) return 0;
    if (!da) return 1;
    if (!db) return -1;
    return db.getTime() - da.getTime();
});

// Ligne la plus récente
const last = lignes[0];

// Construire JSON simplifié
msg.payload = {
    puissance,
    abonnement_TTC: toFloat(last.PART_FIXE_TTC),
    tarifs: {
        bleu: {
            HC: toFloat(last.PART_VARIABLE_HCBleu_TTC),
            HP: toFloat(last.PART_VARIABLE_HPBleu_TTC)
        },
        blanc: {
            HC: toFloat(last.PART_VARIABLE_HCBlanc_TTC),
            HP: toFloat(last.PART_VARIABLE_HPBlanc_TTC)
        },
        rouge: {
            HC: toFloat(last.PART_VARIABLE_HCRouge_TTC),
            HP: toFloat(last.PART_VARIABLE_HPRouge_TTC)
        }
    },
    date_derniere_maj: last.DATE_DEBUT
};

return msg;
  • 1 autre node “function“ avec 9 sorties pour y relier des nodes “sensors“ et y mettre les valeurs a jour, code du node function:
let data = msg.payload;

let abonnement_mensuel = data.abonnement_TTC / 12;

return [
    { payload: Number(data.puissance) },               // sensor 1 : puissance
    { payload: abonnement_mensuel },                  // sensor 2 : abonnement mensuel
    { payload: data.tarifs.bleu.HC },                // sensor 3 : tarif bleu HC
    { payload: data.tarifs.bleu.HP },                // sensor 4 : tarif bleu HP
    { payload: data.tarifs.blanc.HC },               // sensor 5 : tarif blanc HC
    { payload: data.tarifs.blanc.HP },               // sensor 6 : tarif blanc HP
    { payload: data.tarifs.rouge.HC },               // sensor 7 : tarif rouge HC
    { payload: data.tarifs.rouge.HP },               // sensor 8 : tarif rouge HP
    { payload: data.date_derniere_maj }              // sensor 9 : date dernière maj
];
  • 9 nodes “sensor“ attachés au node function précédent.

Cela donne:

Merci,

Sébastien

Ce sujet a été automatiquement fermé après 60 jours. Aucune réponse n’est permise dorénavant.