import pandas as pd import numpy as np import xgboost as xgb import mysql.connector from mysql.connector import Error from datetime import datetime, timedelta import holidays # --- NIEUW: Imports voor grafiek en e-mail --- import matplotlib.pyplot as plt import matplotlib.dates as mdates import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.image import MIMEImage # --- NIEUW: Zorgt dat matplotlib werkt zonder GUI (CRUCIAAL op Linux) --- plt.switch_backend('Agg') # --- CONFIGURATIE --- PROJECT_MAP = '/volume1/web/energy_price_model_prediction' MODEL_FILE = f'{PROJECT_MAP}/price_forecast_model_1.5.linux.json' # Linux-specifiek model TARGET = 'gemiddelde_prijs' AANTAL_UUR_VOORSPELLEN = 72 GRAFIEK_BESTAND = f'{PROJECT_MAP}/prijs_voorspelling_graph.png' # Linux-specifiek bestand # --- NIEUW: E-mail Configuratie --- # (Overgenomen uit je Windows-voorbeeld) EMAIL_CONFIG = { 'smtp_server': 'localhost', 'smtp_port': 587, 'sender': 'sftpuser@markkors.nl', 'password': 'Aae8-G9yFU5j', # Wachtwoord is ingevuld 'receiver': 'mark@markkors.nl' } DB_CONFIG = { 'host': 'localhost', 'user': 'energy_prices_user', 'password': 'kS9R*xp17ZwCD@CV&E^N', 'database': 'energy_prices', 'port': 3307 } # --- Model laden --- try: print(f"Laden van model: {MODEL_FILE}...") model = xgb.XGBRegressor() model.load_model(MODEL_FILE) FEATURES = model.feature_names_in_ print(f"āœ… Model succesvol geladen (verwacht {len(FEATURES)} features).") except FileNotFoundError: print(f"āŒ Fout: Model bestand '{MODEL_FILE}' niet gevonden.") print(" Heb je het 'v1_5 (linux)' model al getraind en opgeslagen op Linux?") exit() except Exception as e: print(f"āŒ Fout bij laden model: {e}") exit() nl_holidays = holidays.Netherlands(years=[datetime.now().year, datetime.now().year + 1]) # --- Functie: haal_data_uit_database (onveranderd) --- def haal_data_uit_database(conn): print("šŸ’¾ Data ophalen uit MySQL...") query_hist = """ SELECT w.datum_tijd, w.temperatuur, w.gevoelstemperatuur, w.neerslag, w.wind_richting, w.wind_snelheid, w.bewolking, w.luchtdruk, w.luchtvochtigheid, p_avg.gemiddelde_prijs FROM amersfoort_weer_uurlijks AS w LEFT JOIN (SELECT datetime, AVG(price) AS gemiddelde_prijs FROM dynamic_price_data GROUP BY datetime) AS p_avg ON w.datum_tijd = p_avg.datetime WHERE w.datum_tijd BETWEEN (UTC_TIMESTAMP() - INTERVAL 30 HOUR) AND UTC_TIMESTAMP() ORDER BY w.datum_tijd; """ query_toekomst = f""" SELECT datum_tijd, temperatuur, gevoelstemperatuur, neerslag, wind_richting, wind_snelheid, bewolking, luchtdruk, luchtvochtigheid, NULL AS gemiddelde_prijs FROM amersfoort_weer_uurlijks WHERE datum_tijd BETWEEN UTC_TIMESTAMP() AND (UTC_TIMESTAMP() + INTERVAL {AANTAL_UUR_VOORSPELLEN} HOUR) ORDER BY datum_tijd; """ try: hist_df = pd.read_sql(query_hist, conn, index_col='datum_tijd', parse_dates=['datum_tijd']) toekomst_df = pd.read_sql(query_toekomst, conn, index_col='datum_tijd', parse_dates=['datum_tijd']) print(f"āœ… {len(hist_df)} uur historie geladen.") print(f"āœ… {len(toekomst_df)} uur toekomstig weer geladen.") hist_df['gemiddelde_prijs'] = hist_df['gemiddelde_prijs'].ffill() hist_df['gemiddelde_prijs'] = hist_df['gemiddelde_prijs'].bfill() combined_df = pd.concat([hist_df, toekomst_df]) return combined_df.sort_index() except Exception as e: print(f"āŒ Fout bij ophalen data: {e}") return None # --- Functie: maak_features_voor_uur (onveranderd) --- def maak_features_voor_uur(df, timestamp): features = {} data_nu = df.loc[timestamp] features['maand'] = timestamp.month features['dag_van_het_jaar'] = timestamp.dayofyear features['is_feestdag'] = 1 if timestamp in nl_holidays else 0 weer_cols = ['temperatuur', 'gevoelstemperatuur', 'neerslag', 'wind_richting', 'wind_snelheid', 'bewolking', 'luchtdruk', 'luchtvochtigheid'] for col in weer_cols: features[col] = data_nu[col] features['prijs_1u_geleden'] = df.loc[timestamp - timedelta(hours=1)]['gemiddelde_prijs'] features['prijs_24u_geleden'] = df.loc[timestamp - timedelta(hours=24)]['gemiddelde_prijs'] features['temp_avg_3u'] = df.loc[timestamp - timedelta(hours=2) : timestamp]['temperatuur'].mean() features['prijs_avg_6u'] = df.loc[timestamp - timedelta(hours=5) : timestamp]['gemiddelde_prijs'].mean() for dag in range(7): features[f'dag_{dag}'] = 1 if timestamp.dayofweek == dag else 0 for uur in range(24): features[f'uur_{uur}'] = 1 if timestamp.hour == uur else 0 return pd.DataFrame([features], columns=FEATURES) # --- NIEUW: Functie om de e-mail te bouwen en te versturen --- def send_email_with_graph(image_path, result_df): print("\nšŸ“¬ E-mail opstellen...") msg = MIMEMultipart() msg['Subject'] = f"Energie Prijsvoorspelling [energy_prediction] {datetime.now().strftime('%d-%m-%Y')}" msg['From'] = EMAIL_CONFIG['sender'] msg['To'] = EMAIL_CONFIG['receiver'] laagste_prijs = result_df['Voorspelde_Prijs'].min() hoogste_prijs = result_df['Voorspelde_Prijs'].max() # en wanneer deze voorkomen laagste_moment = result_df['Voorspelde_Prijs'].idxmin() hoogste_moment = result_df['Voorspelde_Prijs'].idxmax() body = f""" De prijsvoorspelling voor de komende {len(result_df)} uur. Laagste prijs: {laagste_prijs:.4f} (op {laagste_moment}) Hoogste prijs: {hoogste_prijs:.4f} (op {hoogste_moment}) Volledige voorspelling: {result_df.to_string()} """ msg.attach(MIMEText(body, 'plain')) try: with open(image_path, 'rb') as f: img_attach = MIMEImage(f.read(), name='voorspelling_linux.png') msg.attach(img_attach) print("āœ… Grafiek bijgevoegd.") except Exception as e: print(f"āŒ Kon grafiek-bestand niet lezen: {e}") return try: print(f"Verbinding maken met SMTP server: {EMAIL_CONFIG['smtp_server']}...") server = smtplib.SMTP(EMAIL_CONFIG['smtp_server'], EMAIL_CONFIG['smtp_port']) # Probeer STARTTLS (encryptie) # Veel servers (zelfs lokaal) vereisen dit server.starttls() # Probeer in te loggen server.login(EMAIL_CONFIG['sender'], EMAIL_CONFIG['password']) print("Inloggen succesvol. E-mail verzenden...") server.sendmail(EMAIL_CONFIG['sender'], EMAIL_CONFIG['receiver'], msg.as_string()) server.quit() print("āœ… E-mail succesvol verzonden.") except Exception as e: print(f"āŒ Fout bij verzenden e-mail: {e}") print(" Controleer je EMAIL_CONFIG (server, poort, e-mail en wachtwoord)") # --- START VAN HET SCRIPT (Aangepast) --- try: conn = mysql.connector.connect(**DB_CONFIG) werk_df = haal_data_uit_database(conn) if werk_df is not None: te_voorspellen_tijden = werk_df[werk_df['gemiddelde_prijs'].isnull()].index if len(te_voorspellen_tijden) == 0: print("\nDatabase is al up-to-date. Geen voorspelling nodig.") else: print(f"\n🧠 Start iteratieve voorspelling voor {len(te_voorspellen_tijden)} uur...") voorspellingen = [] for timestamp in te_voorspellen_tijden: features_nu = maak_features_voor_uur(werk_df, timestamp) voorspelde_prijs = model.predict(features_nu)[0] werk_df.loc[timestamp, 'gemiddelde_prijs'] = voorspelde_prijs voorspellingen.append(voorspelde_prijs) print("\n" + "="*70) pd.set_option('display.max_rows', None) print(f"--- VOORSPELDE PRIJZEN (komende {len(te_voorspellen_tijden)} uur) ---") resultaat_df = pd.DataFrame({ 'Voorspelde_Prijs': voorspellingen }, index=te_voorspellen_tijden) print(resultaat_df) print("="*70) # Grafiek maken (met 2 Y-assen en NL opmaak) en e-mailen --- print("\nšŸ“Š Grafiek genereren (met temperatuur en wind)...") try: # Maak de basis-figuur en de EERSTE Y-as (ax1) voor de prijs fig, ax1 = plt.subplots(figsize=(15, 8)) # Plot de Prijs op de linker-as (ax1) ax1.plot( resultaat_df.index, resultaat_df['Voorspelde_Prijs'], color='blue', marker='.', linestyle='-', label='Voorspelde Prijs' ) ax1.set_ylabel('Voorspelde Prijs (€)', color='blue') ax1.tick_params(axis='y', labelcolor='blue') ax1.set_xlabel('Datum en Tijd') ax1.grid(True, which='major', axis='x') # Maak de TWEEDE Y-as (ax2) die de X-as deelt ax2 = ax1.twinx() weer_toekomst = werk_df.loc[te_voorspellen_tijden] # Plot Temperatuur op de rechter-as (ax2) ax2.plot( weer_toekomst.index, weer_toekomst['temperatuur'], color='red', marker='.', linestyle='--', label='Temperatuur (°C)' ) # Plot Windsnelheid op de rechter-as (ax2) ax2.plot( weer_toekomst.index, weer_toekomst['wind_snelheid'], color='green', marker='x', linestyle=':', label='Windsnelheid (km/u)' ) ax2.set_ylabel('Temperatuur / Windsnelheid', color='black') ax2.tick_params(axis='y', labelcolor='black') # Titel en gecombineerde legenda plt.title(f'Energieprijs & Weer Voorspelling ({len(resultaat_df)} uur)', fontsize=16) lines1, labels1 = ax1.get_legend_handles_labels() lines2, labels2 = ax2.get_legend_handles_labels() ax2.legend(lines1 + lines2, labels1 + labels2, loc='upper left') # --- NIEUW: X-as opmaken (Nederlands formaat) --- # Definieer het formaat: Dag-Maand Uur:Minuut (bv: 13-11 22:00) date_format = mdates.DateFormatter('%d-%m %H:%M') # Pas het formaat toe op de X-as ax1.xaxis.set_major_formatter(date_format) # Roteer de labels voor betere leesbaarheid plt.setp(ax1.get_xticklabels(), rotation=30, ha='right') # --- EINDE NIEUWE CODE --- # --- AANGEPAST: Gebruik fig.tight_layout() --- # Zorgt dat de geroteerde labels netjes in de afbeelding passen fig.tight_layout() # Sla de grafiek op plt.savefig(GRAFIEK_BESTAND) plt.close(fig) # Geheugen vrijgeven print(f"āœ… Grafiek opgeslagen als: {GRAFIEK_BESTAND}") # Stuur de e-mail (deze functie is onveranderd) send_email_with_graph(GRAFIEK_BESTAND, resultaat_df) except Exception as e: print(f"āŒ Fout bij genereren van grafiek: {e}") # --- EINDE BLOK --- except Error as e: print(f"āŒ Fout met MySQL verbinding: {e}") except Exception as e: print(f"āŒ Een onverwachte fout is opgetreden: {e}") finally: if 'conn' in locals() and conn.is_connected(): conn.close() print("\nVerbinding met MySQL gesloten.")