From 6a722f6bf2fa2acfabf660ea6087fd04963c21d1 Mon Sep 17 00:00:00 2001 From: Mark Kors Date: Wed, 29 Oct 2025 19:16:07 +0100 Subject: [PATCH] external prices added --- README.md | 213 ++++++++++++++++++++++++++++++++---- steve_transaction_report.py | 168 ++++++++++++++++++++++++---- 2 files changed, 337 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index e78e0c7..9b812e9 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,12 @@ python3 steve_transaction_report.py \ | --transaction | - | Nee | Specifieke transactie ID | | --limit | 10 | Nee | Aantal transacties | | --simple | - | Nee | Zonder uurdetails | +| --provider | NE | Nee | Energie provider code voor prijzen | +| --price-db | alfen | Nee | Database naam voor prijsgegevens | +| --price-host | (zelfde) | Nee | Database host voor prijzen | +| --price-port | (zelfde) | Nee | Database poort voor prijzen | +| --price-user | (zelfde) | Nee | Database gebruiker voor prijzen | +| --price-password | (zelfde) | Nee | Database wachtwoord voor prijzen | ## Rapport Inhoud @@ -123,12 +129,15 @@ python3 steve_transaction_report.py \ - Start en stop tijden - Duur van de laadsessie - Totaal verbruik in kWh -- **Verbruik per uur** met meterwaarden +- **Verbruik en kosten per uur** met echte prijzen - Statistieken: - Aantal actieve laaduren + - Provider informatie - Gemiddeld verbruik per uur - Piekuur (meeste verbruik) - - Geschatte kosten + - Duurste en goedkoopste uur + - **Totale kosten (met echte prijzen!)** + - Gemiddelde prijs per kWh ### Compact overzicht bevat: - Transactie ID @@ -138,6 +147,89 @@ python3 steve_transaction_report.py \ - Laadpaal - RFID tag +## Prijzen Functionaliteit + +Het script gebruikt **echte dynamische prijzen** uit je prijzen database in plaats van geschatte kosten! + +### Prijzen Database +Het script haalt prijzen op uit twee tabellen: +- `dynamic_price_data` - Historische en huidige dagprijzen voor alle providers +- `dynamic_price_data_tommorow` - Prijzen voor de volgende dag (alleen NextEnergy) + +### Providers +Standaard wordt NextEnergy (NE) gebruikt, maar je kunt elke provider kiezen: + +**Beschikbare providers:** +- `NE` - NextEnergy (standaard) +- `AA` - AllAbout +- `AIP` - All in Power +- `ANWB` - ANWB Energie +- `BE` - Budget Energie +- `EE` - Essent Energie +- `EN` - Eneco +- `EVO` - EVO Energie +- `EZ` - Engie +- `FR` - Frank Energie +- `GSL` - Greenchoice +- `MDE` - Mijndomein Energie +- `TI` - Tibber +- `VDB` - Vandebron +- `VON` - Vattenfall +- `WE` - Welkom Energie +- `ZG` - Zonneplan +- `ZP` - Pure Energie + +### Provider instellen +```bash +# Gebruik NextEnergy (standaard) +steve-report --transaction 1 + +# Gebruik een andere provider +steve-report --transaction 1 --provider AA +steve-report --transaction 1 --provider FR +``` + +### Aparte prijzen database credentials +Als je prijzen database andere login credentials heeft: + +```bash +# Zelfde server, andere database en gebruiker +python3 steve_transaction_report.py \ + --host 192.168.178.201 \ + --port 3307 \ + --user steve \ + --password 'steve_wachtwoord' \ + --price-user prijzen_user \ + --price-password 'prijzen_wachtwoord' \ + --transaction 1 + +# Andere server voor prijzen +python3 steve_transaction_report.py \ + --host 192.168.178.201 \ + --port 3307 \ + --user steve \ + --password 'steve_wachtwoord' \ + --price-host 192.168.178.202 \ + --price-port 3306 \ + --price-user prijzen_user \ + --price-password 'prijzen_wachtwoord' \ + --transaction 1 + +# Via alias (voeg prijzen credentials toe aan alias) +alias steve-report='python3 ~/reports/steve_transaction_report.py \ + --host 192.168.178.201 --port 3307 --user steve --password "steve_pass" \ + --price-user prijzen_user --price-password "prijzen_pass"' +``` + +**Let op:** Als je geen prijzen credentials opgeeft, gebruikt het script automatisch dezelfde credentials als voor de SteVe database (alleen de database naam wijzigt naar `alfen`). + +### Hoe het werkt +1. Voor elk uur tijdens het laden wordt de prijs opgehaald +2. De kosten worden berekend: **verbruik (kWh) × prijs (€/kWh)** +3. Het rapport toont prijzen en kosten per uur +4. Het totaal wordt berekend met de werkelijke prijzen +5. Als prijzen ontbreken voor een bepaald uur, wordt dit aangegeven + ## Alias maken (aanbevolen) Voor gemakkelijk gebruik maak je een alias aan: @@ -192,6 +284,18 @@ sudo apt install mailutils 0 8 * * * python3 ~/reports/steve_transaction_report.py --host 192.168.178.201 --port 3307 --password 'XXX' --transaction 1 | mail -s "Laadrapport" jouw@email.nl ``` +### Voorbeeld 6: Aparte prijzen database credentials +```bash +# Prijzen database heeft andere gebruiker +steve-report --price-user prijzen_user --price-password 'geheim2' --transaction 1 + +# Prijzen database op andere server +python3 steve_transaction_report.py \ + --host 192.168.178.201 --port 3307 --password 'pass1' \ + --price-host 192.168.178.202 --price-user prijzen_user --price-password 'pass2' \ + --transaction 1 +``` + ## Output Voorbeeld ``` @@ -212,26 +316,29 @@ Eind meterstand: 5540.121 kWh Totaal geladen: 21.85 kWh ================================================================================ -VERBRUIK PER UUR: --------------------------------------------------------------------------------- -Uur Start (kWh) Eind (kWh) Geladen (kWh) --------------------------------------------------------------------------------- -28-10 18:27 5518.267 5518.796 0.529 -29-10 01:12 5520.027 5522.945 2.918 -29-10 02:12 5525.658 5529.181 3.523 -29-10 03:12 5531.738 5539.881 8.143 --------------------------------------------------------------------------------- -TOTAAL 5518.267 5540.121 21.854 +VERBRUIK EN KOSTEN PER UUR: +---------------------------------------------------------------------------------------------------- +Uur Start (kWh) Eind (kWh) Geladen (kWh) Prijs (€/kWh) Kosten (€) +---------------------------------------------------------------------------------------------------- +28-10 18:27 5518.267 5518.796 0.529 0.29124 0.15 +29-10 01:12 5520.027 5522.945 2.918 0.22685 0.66 +29-10 02:12 5525.658 5529.181 3.523 0.22166 0.78 +29-10 03:12 5531.738 5539.881 8.143 0.21564 1.76 +---------------------------------------------------------------------------------------------------- +TOTAAL 5518.267 5540.121 21.854 €3.35 ================================================================================ STATISTIEKEN: -------------------------------------------------------------------------------- Actieve laaduren: 4 +Provider: NE Gemiddeld per actief uur: 3.78 kWh -Piekuur: 29-10 03:00 (8.14 kWh) +Piekuur verbruik: 29-10 03:00 (8.14 kWh) +Duurste uur: 28-10 18:00 (€0.29124/kWh) +Goedkoopste uur: 29-10 03:00 (€0.21564/kWh) -Geschatte kosten (€0.30/kWh): €6.56 -Geschatte kosten (€0.10/kWh): €2.19 +TOTALE KOSTEN: €3.35 +Gemiddelde prijs: €0.22913/kWh ================================================================================ ``` @@ -264,6 +371,52 @@ Gebruik altijd enkele quotes rond het wachtwoord: --password 'Mijn$Wachtw00rd!' ``` +### Prijzen database problemen +Als je melding krijgt dat prijzen ontbreken of verbinding mislukt: + +**Stap 1: Controleer of prijzen database bestaat** +```bash +mysql -h 192.168.178.201 -P 3307 -u steve -p -e "SHOW DATABASES LIKE 'alfen'" +``` + +**Stap 2: Controleer tabellen** +```bash +mysql -h 192.168.178.201 -P 3307 -u steve -p alfen -e "SHOW TABLES" +``` + +**Stap 3: Test prijzen query** +```bash +mysql -h 192.168.178.201 -P 3307 -u steve -p alfen -e "SELECT COUNT(*) FROM dynamic_price_data WHERE provider_code='NE'" +``` + +**Als prijzen database andere credentials heeft:** +Gebruik de prijzen database parameters: +```bash +python3 steve_transaction_report.py \ + --host 192.168.178.201 --port 3307 --user steve --password 'pass1' \ + --price-user andere_user --price-password 'pass2' \ + --transaction 1 +``` + +**Als prijzen database op andere server staat:** +```bash +python3 steve_transaction_report.py \ + --host 192.168.178.201 --port 3307 --user steve --password 'pass1' \ + --price-host 192.168.178.202 --price-port 3306 \ + --price-user prijzen_user --price-password 'pass2' \ + --transaction 1 +``` + +Als prijzen voor een ander uur ontbreken: +- Het script markeert dit automatisch met "n.v.t." +- Je ziet een waarschuwing: "Let op: Voor X uur ontbreken prijsgegevens" +- De totale kosten zijn dan gebaseerd op de beschikbare prijzen + +### Verkeerde provider +Als je provider code niet werkt: +- Controleer of de provider code bestaat: `mysql -h HOST -P PORT -u USER -p alfen -e "SELECT DISTINCT provider_code FROM dynamic_price_data"` +- Gebruik één van de beschikbare codes (zie README voor lijst) + ## Database Structuur Het script maakt gebruik van de volgende SteVe database tabellen: @@ -275,15 +428,23 @@ Het script maakt gebruik van de volgende SteVe database tabellen: - `ocpp_tag` - RFID tags - `user_ocpp_tag` - Koppeling tussen gebruikers en tags -## Toekomstige Features +## Features -Mogelijke uitbreidingen: -- [ ] Echte prijzen integratie vanuit prijzen database -- [ ] CSV export functionaliteit -- [ ] Grafische weergave van verbruik -- [ ] Vergelijking tussen transacties -- [ ] Maandelijkse/jaarlijkse samenvattingen -- [ ] CO2 besparing berekening +✅ **Geïmplementeerd:** +- Echte dynamische prijzen uit database +- Meerdere energie providers ondersteund +- Verbruik en kosten per uur +- Duurste en goedkoopste laaduren identificeren +- Gemiddelde prijs berekening +- Automatische detectie van ontbrekende prijzen + +🔜 **Toekomstige uitbreidingen:** +- CSV export functionaliteit +- Grafische weergave van verbruik en prijzen +- Vergelijking tussen transacties +- Maandelijkse/jaarlijkse samenvattingen +- CO2 besparing berekening +- Email alerts bij te hoge prijzen ## Support @@ -298,7 +459,11 @@ Open source - vrij te gebruiken en aan te passen voor persoonlijk gebruik. ## Versie -Versie 1.1 - Oktober 2025 +Versie 2.0 - Oktober 2025 +- ✅ **Echte dynamische prijzen uit database** +- ✅ **Meerdere energie providers ondersteund** +- ✅ **Kosten berekening per uur** +- ✅ **Duurste en goedkoopste uren identificeren** - Verbeterde prijsberekening - Standaard gedrag: alle transacties tonen - --limit werkt overal diff --git a/steve_transaction_report.py b/steve_transaction_report.py index aefddfe..8843136 100755 --- a/steve_transaction_report.py +++ b/steve_transaction_report.py @@ -11,7 +11,8 @@ import sys from typing import List, Dict, Optional class SteVeReporter: - def __init__(self, host: str, database: str, user: str, password: str, port: int = 3306): + def __init__(self, host: str, database: str, user: str, password: str, port: int = 3306, + price_host: str = None, price_port: int = None, price_user: str = None, price_password: str = None): """Initialiseer database connectie""" self.config = { 'host': host, @@ -20,8 +21,17 @@ class SteVeReporter: 'password': password, 'port': port } + # Prijzen database config (gebruik SteVe config als fallback) + self.price_config = { + 'host': price_host or host, + 'port': price_port or port, + 'user': price_user or user, + 'password': price_password or password + } self.conn = None self.cursor = None + self.price_conn = None + self.price_cursor = None def connect(self): """Maak verbinding met de database""" @@ -39,6 +49,10 @@ class SteVeReporter: self.cursor.close() if self.conn: self.conn.close() + if hasattr(self, 'price_cursor') and self.price_cursor: + self.price_cursor.close() + if hasattr(self, 'price_conn') and self.price_conn: + self.price_conn.close() def get_transactions(self, limit: Optional[int] = None, transaction_id: Optional[int] = None) -> List[Dict]: """Haal transacties op uit de database""" @@ -114,6 +128,57 @@ class SteVeReporter: self.cursor.execute(query, (id_tag,)) return self.cursor.fetchone() + def get_price(self, timestamp: datetime, provider: str, price_db: str) -> Optional[float]: + """Haal prijs op voor een specifieke timestamp en provider""" + # Maak verbinding met prijzen database indien nodig + if not hasattr(self, 'price_conn') or self.price_conn is None: + try: + # Gebruik price_config met de juiste database naam + price_config = self.price_config.copy() + price_config['database'] = price_db + self.price_conn = mysql.connector.connect(**price_config) + self.price_cursor = self.price_conn.cursor(dictionary=True) + print(f"✓ Verbonden met prijzen database {price_db} op {price_config['host']}") + except mysql.connector.Error as err: + print(f"⚠ Waarschuwing: Kan geen verbinding maken met prijzen database: {err}") + return None + + # Round timestamp naar het uur + hour_timestamp = timestamp.replace(minute=0, second=0, microsecond=0) + + # Probeer eerst dynamic_price_data (voor historische en vandaag) + query = """ + SELECT price + FROM dynamic_price_data + WHERE datetime = %s AND provider_code = %s + LIMIT 1 + """ + try: + self.price_cursor.execute(query, (hour_timestamp, provider)) + result = self.price_cursor.fetchone() + if result: + return float(result['price']) + except mysql.connector.Error: + pass + + # Zo niet, probeer dynamic_price_data_tommorow (alleen NextEnergy) + if provider == 'NE': + query = """ + SELECT price + FROM dynamic_price_data_tommorow + WHERE datetime = %s + LIMIT 1 + """ + try: + self.price_cursor.execute(query, (hour_timestamp,)) + result = self.price_cursor.fetchone() + if result: + return float(result['price']) + except mysql.connector.Error: + pass + + return None + def analyze_hourly_consumption(self, meter_values: List[Dict]) -> List[Dict]: """Analyseer verbruik per uur""" if not meter_values: @@ -154,7 +219,7 @@ class SteVeReporter: return result - def print_transaction_report(self, transaction: Dict, detailed: bool = True): + def print_transaction_report(self, transaction: Dict, detailed: bool = True, provider: str = 'NE', price_db: str = 'alfen'): """Print een geformatteerd rapport van een transactie""" print("\n" + "=" * 80) print(f"LAADSESSIE RAPPORT - Transactie #{transaction['transaction_pk']}") @@ -210,23 +275,44 @@ class SteVeReporter: hourly_data = self.analyze_hourly_consumption(meter_values) if hourly_data: - print("\nVERBRUIK PER UUR:") - print("-" * 80) - print(f"{'Uur':<20} {'Start (kWh)':<15} {'Eind (kWh)':<15} {'Geladen (kWh)':<15}") - print("-" * 80) + print("\nVERBRUIK EN KOSTEN PER UUR:") + print("-" * 100) + print(f"{'Uur':<20} {'Start (kWh)':<15} {'Eind (kWh)':<15} {'Geladen (kWh)':<15} {'Prijs (€/kWh)':<15} {'Kosten (€)':<15}") + print("-" * 100) total_detailed = 0 + total_cost = 0 + hours_with_price = 0 + hours_without_price = 0 + for hour in hourly_data: hour_dt = datetime.strptime(hour['hour'], '%Y-%m-%d %H:00') hour_display = hour_dt.strftime('%d-%m %H:%M') + # Haal prijs op voor dit uur + price = self.get_price(hour_dt, provider, price_db) + + if price is not None: + cost = hour['consumption'] * price + total_cost += cost + hours_with_price += 1 + price_str = f"{price:.5f}" + cost_str = f"{cost:.2f}" + else: + hours_without_price += 1 + price_str = "n.v.t." + cost_str = "n.v.t." + print(f"{hour_display:<20} {hour['start_value']:<15.3f} " - f"{hour['end_value']:<15.3f} {hour['consumption']:<15.3f}") + f"{hour['end_value']:<15.3f} {hour['consumption']:<15.3f} " + f"{price_str:<15} {cost_str:<15}") total_detailed += hour['consumption'] - print("-" * 80) + print("-" * 100) + cost_total_str = f"€{total_cost:.2f}" if hours_without_price == 0 else f"€{total_cost:.2f}*" print(f"{'TOTAAL':<20} {hourly_data[0]['start_value']:<15.3f} " - f"{hourly_data[-1]['end_value']:<15.3f} {total_detailed:<15.3f}") + f"{hourly_data[-1]['end_value']:<15.3f} {total_detailed:<15.3f} " + f"{'':15} {cost_total_str:<15}") # Statistieken active_hours = [h for h in hourly_data if h['consumption'] > 0.01] @@ -235,18 +321,42 @@ class SteVeReporter: print("STATISTIEKEN:") print("-" * 80) print(f"Actieve laaduren: {len(active_hours)}") + print(f"Provider: {provider}") avg_per_hour = sum(h['consumption'] for h in active_hours) / len(active_hours) print(f"Gemiddeld per actief uur: {avg_per_hour:.2f} kWh") max_hour = max(hourly_data, key=lambda x: x['consumption']) if max_hour['consumption'] > 0: max_time = datetime.strptime(max_hour['hour'], '%Y-%m-%d %H:00') - print(f"Piekuur: {max_time.strftime('%d-%m %H:00')} " + print(f"Piekuur verbruik: {max_time.strftime('%d-%m %H:00')} " f"({max_hour['consumption']:.2f} kWh)") - # Bereken geschatte kosten (indicatief) - nu met correcte total_detailed - print(f"\nGeschatte kosten (€0.30/kWh): €{total_detailed * 0.30:.2f}") - print(f"Geschatte kosten (€0.10/kWh): €{total_detailed * 0.10:.2f}") + # Vind duurste en goedkoopste uur + hours_with_costs = [] + for hour in hourly_data: + if hour['consumption'] > 0.01: + hour_dt = datetime.strptime(hour['hour'], '%Y-%m-%d %H:00') + price = self.get_price(hour_dt, provider, price_db) + if price is not None: + hours_with_costs.append((hour_dt, price, hour['consumption'])) + + if hours_with_costs: + most_expensive = max(hours_with_costs, key=lambda x: x[1]) + cheapest = min(hours_with_costs, key=lambda x: x[1]) + print(f"Duurste uur: {most_expensive[0].strftime('%d-%m %H:00')} " + f"(€{most_expensive[1]:.5f}/kWh)") + print(f"Goedkoopste uur: {cheapest[0].strftime('%d-%m %H:00')} " + f"(€{cheapest[1]:.5f}/kWh)") + + # Totale kosten + print(f"\n{'TOTALE KOSTEN:':<30} {cost_total_str}") + if hours_without_price > 0: + print(f"* Let op: Voor {hours_without_price} uur ontbreken prijsgegevens") + + # Gemiddelde prijs + if hours_with_price > 0: + avg_price = total_cost / total_detailed if total_detailed > 0 else 0 + print(f"{'Gemiddelde prijs:':<30} €{avg_price:.5f}/kWh") print("=" * 80) @@ -297,17 +407,21 @@ Voorbeelden: # Toon lijst van laatste 10 transacties python3 steve_transaction_report.py --host 192.168.178.201 --port 3307 --password 'geheim' --list - # Toon gedetailleerd rapport van transactie 1 + # Toon gedetailleerd rapport van transactie 1 met NextEnergy prijzen python3 steve_transaction_report.py --host 192.168.178.201 --port 3307 --password 'geheim' --transaction 1 + # Gebruik aparte credentials voor prijzen database + python3 steve_transaction_report.py --host 192.168.178.201 --port 3307 --password 'geheim' \ + --price-host 192.168.178.201 --price-port 3307 --price-user prijzen --price-password 'geheim2' --transaction 1 + + # Toon rapport met andere provider (bijv. 'AA' voor AllAbout) + python3 steve_transaction_report.py --host 192.168.178.201 --port 3307 --password 'geheim' --transaction 1 --provider AA + # Toon laatste 20 transacties in lijst python3 steve_transaction_report.py --host 192.168.178.201 --port 3307 --password 'geheim' --list --limit 20 # Toon alle transacties gedetailleerd (laatste 10) python3 steve_transaction_report.py --host 192.168.178.201 --port 3307 --password 'geheim' - - # Toon laatste 5 transacties gedetailleerd - python3 steve_transaction_report.py --host 192.168.178.201 --port 3307 --password 'geheim' --limit 5 """ ) @@ -324,6 +438,14 @@ Voorbeelden: parser.add_argument('--limit', type=int, default=10, help='Aantal transacties (default: 10)') parser.add_argument('--simple', action='store_true', help='Eenvoudig rapport zonder uurdetails') + # Prijzen opties + parser.add_argument('--provider', default='NE', help='Energie provider code (default: NE voor NextEnergy)') + parser.add_argument('--price-db', default='alfen', help='Database naam voor prijzen (default: alfen)') + parser.add_argument('--price-host', default=None, help='Database host voor prijzen (default: zelfde als --host)') + parser.add_argument('--price-port', type=int, default=None, help='Database poort voor prijzen (default: zelfde als --port)') + parser.add_argument('--price-user', default=None, help='Database gebruiker voor prijzen (default: zelfde als --user)') + parser.add_argument('--price-password', default=None, help='Database wachtwoord voor prijzen (default: zelfde als --password)') + args = parser.parse_args() # Maak reporter object @@ -332,7 +454,11 @@ Voorbeelden: database=args.database, user=args.user, password=args.password, - port=args.port + port=args.port, + price_host=args.price_host, + price_port=args.price_port, + price_user=args.price_user, + price_password=args.price_password ) try: @@ -342,7 +468,8 @@ Voorbeelden: # Toon specifieke transactie transactions = reporter.get_transactions(transaction_id=args.transaction) if transactions: - reporter.print_transaction_report(transactions[0], detailed=not args.simple) + reporter.print_transaction_report(transactions[0], detailed=not args.simple, + provider=args.provider, price_db=args.price_db) else: print(f"✗ Transactie {args.transaction} niet gevonden") sys.exit(1) @@ -361,7 +488,8 @@ Voorbeelden: for i, trans in enumerate(transactions): if i > 0: print("\n\n") - reporter.print_transaction_report(trans, detailed=not args.simple) + reporter.print_transaction_report(trans, detailed=not args.simple, + provider=args.provider, price_db=args.price_db) except KeyboardInterrupt: print("\n\nAfgebroken door gebruiker")