Skip to content

Commit 26c06ba

Browse files
author
jacquesbach
committed
Added new ML Features in API Routes
1 parent 6010c7f commit 26c06ba

4 files changed

Lines changed: 98 additions & 45 deletions

File tree

app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
init_db()
2626

2727
# 🔥 EINMAL ausführen, danach wieder auskommentieren!
28-
force_rebuild_daily_stats()
28+
# force_rebuild_daily_stats()
2929

3030
self_heal_daily_stats()
3131

ml_logic.py

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,66 +8,106 @@
88
from sklearn.base import clone
99
from sklearn.metrics import mean_absolute_error
1010
from sklearn.model_selection import train_test_split
11+
from database import get_db_connection
1112
from config import DB_FILE, MODEL_FILE
1213
from utils import calculate_sun_elevation
1314

1415
def build_training_data():
15-
conn = sqlite3.connect(DB_FILE)
16+
conn = get_db_connection()
1617
c = conn.cursor()
17-
c.execute("SELECT day, kwh, avg_clouds, avg_temp FROM daily_stats WHERE kwh IS NOT NULL ORDER BY day")
18+
# SQL erweitert um die neuen Spalten
19+
c.execute("""
20+
SELECT day, kwh, avg_clouds, avg_temp, daylight_duration, sunshine_duration
21+
FROM daily_stats
22+
WHERE kwh IS NOT NULL
23+
ORDER BY day
24+
""")
1825
rows = c.fetchall()
1926
conn.close()
2027

2128
X, y, kwh_history = [], [], []
2229

23-
for day_str, kwh, clouds, temp in rows:
30+
for day_str, kwh, clouds, temp, daylight, sunshine in rows:
2431
date = datetime.datetime.strptime(day_str, "%Y-%m-%d")
2532
day_of_year = date.timetuple().tm_yday
33+
34+
# Zeitliche Features
2635
sin_day = math.sin(2 * math.pi * day_of_year / 365)
2736
cos_day = math.cos(2 * math.pi * day_of_year / 365)
2837
sun_elev = calculate_sun_elevation(date)
38+
39+
# Lag-Features (was war gestern?)
2940
prev_kwh = kwh_history[-1] if kwh_history else 0
3041
rolling_avg = sum(kwh_history[-7:]) / 7 if len(kwh_history) >= 7 else prev_kwh
3142

32-
X.append([sin_day, cos_day, clouds or 0, temp or 0, sun_elev, prev_kwh, rolling_avg])
43+
# X-Vektor mit den neuen Werten (Daylight & Sunshine in Sekunden)
44+
X.append([
45+
sin_day,
46+
cos_day,
47+
clouds or 0,
48+
temp or 0,
49+
sun_elev,
50+
prev_kwh,
51+
rolling_avg,
52+
daylight or 0,
53+
sunshine or 0
54+
])
3355
y.append(kwh)
3456
kwh_history.append(kwh)
3557

3658
return np.array(X), np.array(y)
3759

3860
def train_model():
3961
X, y = build_training_data()
62+
# Da wir mehr Features haben, sollten wir mind. 10-15 Tage haben für ein erstes Training
4063
if len(X) < 8: #15!!!
41-
print("⚠️ Nicht genug Trainingsdaten.")
64+
print(f"⚠️ Nicht genug Trainingsdaten ({len(X)}/8).") #15!!!
4265
return None
4366

44-
feature_names = ["sin_day", "cos_day", "clouds", "temperature", "sun_elevation", "prev_kwh", "rolling_avg"]
67+
# Feature-Liste erweitert
68+
feature_names = [
69+
"sin_day", "cos_day", "clouds", "temperature",
70+
"sun_elevation", "prev_kwh", "rolling_avg",
71+
"daylight", "sunshine"
72+
]
73+
4574
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False)
4675

47-
# 1. RandomForestRegressor für den Erwartungswert)
76+
# 1. Hauptmodell
4877
model = RandomForestRegressor(n_estimators=300, random_state=42)
4978
model.fit(X_train, y_train)
5079

51-
# 2. Unteres Quantil (z.B. 10% Perzentil - "Worst Case") mit GradientBoostingRegressor
52-
model_low = GradientBoostingRegressor(loss='quantile', alpha=0.1, n_estimators=300, random_state=42) # 0.1 entspricht dem 10. Perzentil
80+
# 2. Unteres Quantil (Worst Case)
81+
model_low = GradientBoostingRegressor(loss='quantile', alpha=0.1, n_estimators=300, random_state=42)
5382
model_low.fit(X_train, y_train)
5483

55-
# 3. Oberes Quantil (z.B. 90% Perzentil - "Best Case") mit GradientBoostingRegressor
56-
model_high = GradientBoostingRegressor(loss='quantile', alpha=0.9, n_estimators=300, random_state=42) # 0.9 entspricht dem 90. Perzentil
84+
# 3. Oberes Quantil (Best Case)
85+
model_high = GradientBoostingRegressor(loss='quantile', alpha=0.9, n_estimators=300, random_state=42)
5786
model_high.fit(X_train, y_train)
5887

5988
mae = mean_absolute_error(y_test, model.predict(X_test))
6089

6190
joblib.dump({
62-
"model": model, "model_low": model_low, "model_high": model_high,
63-
"mae": mae, "feature_names": feature_names
91+
"model": model,
92+
"model_low": model_low,
93+
"model_high": model_high,
94+
"mae": mae,
95+
"feature_names": feature_names
6496
}, MODEL_FILE)
6597

66-
print(f"✅ Modell trainiert | MAE: {round(mae,3)}")
98+
print(f"✅ Modell trainiert mit {len(feature_names)} Features | MAE: {round(mae,3)}")
6799
return model
68100

69101
def load_or_train_model():
70102
if os.path.exists(MODEL_FILE):
71-
return joblib.load(MODEL_FILE)
72-
train_model()
73-
return joblib.load(MODEL_FILE)
103+
try:
104+
bundle = joblib.load(MODEL_FILE)
105+
# Kleiner Check, ob die Feature-Anzahl noch stimmt (falls du upgradest)
106+
if len(bundle["feature_names"]) != 9:
107+
print("🔄 Altes Modell erkannt, trainiere neu mit 9 Features...")
108+
return train_model()
109+
return bundle
110+
except:
111+
return train_model()
112+
return train_model()
113+

routes.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -808,8 +808,8 @@ def forecast():
808808
c.execute("SELECT valid_from, price FROM prices ORDER BY valid_from DESC")
809809
prices = [{"date": r[0], "price": r[1]} for r in c.fetchall()]
810810
conn.close()
811-
812-
for date_str, cloud, temp in forecast_data:
811+
812+
for date_str, cloud, temp, daylight, sunshine in forecast_data:
813813

814814
date = datetime.datetime.strptime(date_str, "%Y-%m-%d")
815815
doy = date.timetuple().tm_yday
@@ -820,13 +820,18 @@ def forecast():
820820

821821
prev_kwh = last_rows[-1] if last_rows else 0
822822
rolling_avg = sum(last_rows[-7:]) / 7 if len(last_rows) >= 7 else prev_kwh
823-
824-
X = np.array([[sin_day, cos_day,
825-
cloud or 0,
826-
temp or 0,
827-
sun_elev,
828-
prev_kwh,
829-
rolling_avg]])
823+
824+
X = np.array([[
825+
sin_day,
826+
cos_day,
827+
cloud or 0,
828+
temp or 0,
829+
sun_elev,
830+
prev_kwh,
831+
rolling_avg,
832+
daylight or 0,
833+
sunshine or 0
834+
]])
830835

831836
median = max(float(model.predict(X)[0]), 0)
832837
lower = max(float(model_low.predict(X)[0]), 0)
@@ -927,16 +932,8 @@ def feature_importance():
927932

928933
model_bundle = load_or_train_model()
929934
model = model_bundle["model"]
930-
931-
feature_names = [
932-
"sin_day",
933-
"cos_day",
934-
"clouds",
935-
"temperature",
936-
"sun_elevation",
937-
"prev_kwh",
938-
"rolling_avg"
939-
]
935+
936+
feature_names = model_bundle["feature_names"]
940937

941938
importances = model.feature_importances_
942939

@@ -975,7 +972,7 @@ def shap_values():
975972

976973
results = []
977974

978-
for date_str, cloud, temp in forecast_data:
975+
for date_str, cloud, temp, daylight, sunshine in forecast_data:
979976

980977
date = datetime.datetime.strptime(date_str, "%Y-%m-%d")
981978
doy = date.timetuple().tm_yday
@@ -987,9 +984,19 @@ def shap_values():
987984

988985
prev_kwh = last_rows[-1] if last_rows else 0
989986
rolling_avg = sum(last_rows[-7:]) / 7 if len(last_rows) >= 7 else prev_kwh
990-
991-
X = np.array([[sin_day, cos_day, cloud or 0, temp or 0, sun_elev, prev_kwh, rolling_avg]])
992-
987+
988+
X = np.array([[
989+
sin_day,
990+
cos_day,
991+
cloud or 0,
992+
temp or 0,
993+
sun_elev,
994+
prev_kwh,
995+
rolling_avg,
996+
daylight or 0,
997+
sunshine or 0
998+
]])
999+
9931000
prediction = float(model.predict(X)[0])
9941001

9951002
shap_vals = explainer.shap_values(X)[0]
@@ -1012,7 +1019,6 @@ def shap_values():
10121019

10131020
return jsonify(results)
10141021

1015-
10161022
@api_bp.route('/api/shap-summary')
10171023
def shap_summary():
10181024

utils.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,22 @@ def get_weather_forecast(days=7):
6767
f"https://api.open-meteo.com/v1/forecast"
6868
f"?latitude={LATITUDE}"
6969
f"&longitude={LONGITUDE}"
70-
f"&daily=cloud_cover_mean,temperature_2m_mean"
70+
f"&daily=cloud_cover_mean,temperature_2m_mean,daylight_duration,sunshine_duration"
7171
f"&timezone=Europe/Berlin"
72+
f"&forecast_days={days}"
7273
)
7374
r = requests.get(url, timeout=5)
7475
data = r.json().get("daily", {})
76+
7577
dates = data.get("time", [])
7678
clouds = data.get("cloud_cover_mean", [])
7779
temps = data.get("temperature_2m_mean", [])
78-
return list(zip(dates[:days], clouds[:days], temps[:days]))
80+
daylight = data.get("daylight_duration", [])
81+
sunshine = data.get("sunshine_duration", [])
82+
83+
# Wir geben nun 5 Werte pro Tag zurück
84+
return list(zip(dates, clouds, temps, daylight, sunshine))
85+
7986
except Exception as e:
8087
print("Forecast Weather Error:", e)
81-
return []
88+
return []

0 commit comments

Comments
 (0)