|
16 | 16 | from sklearn.model_selection import train_test_split |
17 | 17 | import shap |
18 | 18 | from flask_mqtt import Mqtt |
| 19 | +from config import (DB_FILE, ADMIN_PASS, LATITUDE, LONGITUDE, MODEL_FILE, MQTT_CONFIG, TRAPEZOID_SQL, GAP_THRESHOLD) |
| 20 | +from utils import (calculate_eur, calculate_sun_elevation, get_historical_avg_temp, get_weather_forecast, trapezoid_wh) |
19 | 21 |
|
20 | 22 | os.environ['TZ'] = 'Europe/Berlin' |
21 | 23 | time.tzset() |
22 | 24 | load_dotenv() |
23 | 25 |
|
24 | 26 | app = Flask(__name__, template_folder='.') |
| 27 | +app.config.update(MQTT_CONFIG) |
25 | 28 | loading_status = {"loading": False} |
26 | 29 |
|
27 | | -# ================= KONFIGURATION ================= |
28 | | -DB_FILE = "solar_data.db" |
29 | | -ADMIN_PASS = os.getenv("ADMIN_PASS") |
30 | | -LATITUDE = float(os.getenv("LATITUDE", 0.0)) |
31 | | -LONGITUDE = float(os.getenv("LONGITUDE", 0.0)) |
32 | | -GAP_THRESHOLD = 45 # 3 x 15 Sekunden Logging |
33 | | -MODEL_FILE = "pv_model.pkl" |
34 | | -app.config['MQTT_BROKER_URL'] = os.getenv("MQTT_BROKER_URL") |
35 | | -app.config['MQTT_BROKER_PORT'] = int(os.getenv("MQTT_BROKER_PORT", 1883)) |
36 | | -app.config['MQTT_USERNAME'] = os.getenv("MQTT_USERNAME") |
37 | | -app.config['MQTT_PASSWORD'] = os.getenv("MQTT_PASSWORD") |
38 | | -app.config['MQTT_TLS_ENABLED'] = False |
39 | | - |
40 | | -TRAPEZOID_SQL = f""" |
41 | | -CASE |
42 | | - WHEN prev_t IS NOT NULL |
43 | | - AND dt > 0 |
44 | | - AND dt <= {GAP_THRESHOLD} |
45 | | - THEN ((prev_w + w) / 2.0) * (dt / 3600.0) |
46 | | - ELSE 0 |
47 | | -END |
48 | | -""" |
49 | | -# ================================================= |
50 | | - |
51 | 30 | mqtt = Mqtt(app) |
52 | 31 |
|
53 | 32 | # Globaler Zwischenspeicher |
@@ -332,60 +311,6 @@ def self_heal_daily_stats(): |
332 | 311 | finalize_day(day) |
333 | 312 | print("Self-Heal abgeschlossen.") |
334 | 313 |
|
335 | | -def trapezoid_wh(prev_w, w, dt): |
336 | | - if prev_w is None or dt is None: |
337 | | - return 0.0 |
338 | | - if 0 < dt <= GAP_THRESHOLD: |
339 | | - return ((prev_w + w) / 2.0) * (dt / 3600.0) |
340 | | - return 0.0 |
341 | | - |
342 | | -def calculate_eur(kwh, date_str, prices): |
343 | | - """ |
344 | | - Einheitliche Euro-Berechnung mit hoher Präzision. |
345 | | - Rundung immer auf 6 Nachkommastellen. |
346 | | - """ |
347 | | - for p in prices: |
348 | | - if date_str >= p["date"]: |
349 | | - return round(kwh * p["price"], 6) |
350 | | - return round(kwh * 0.329, 6) |
351 | | - |
352 | | -def calculate_sun_elevation(date): |
353 | | - day_of_year = date.timetuple().tm_yday |
354 | | - |
355 | | - # vereinfachtes astronomisches Modell |
356 | | - decl = -23.44 * math.cos(math.radians((360/365) * (day_of_year + 10))) |
357 | | - |
358 | | - elevation = 90 - abs(LATITUDE - decl) |
359 | | - |
360 | | - return max(elevation, 0) |
361 | | - |
362 | | -def get_historical_avg_temp(day): |
363 | | - """ |
364 | | - Holt Tagesmitteltemperatur von OpenMeteo Historical API. |
365 | | - """ |
366 | | - try: |
367 | | - url = ( |
368 | | - f"https://archive-api.open-meteo.com/v1/archive" |
369 | | - f"?latitude={LATITUDE}" |
370 | | - f"&longitude={LONGITUDE}" |
371 | | - f"&start_date={day}" |
372 | | - f"&end_date={day}" |
373 | | - f"&daily=temperature_2m_mean" |
374 | | - f"&timezone=Europe/Berlin" |
375 | | - ) |
376 | | - |
377 | | - r = requests.get(url, timeout=5) |
378 | | - data = r.json().get("daily", {}) |
379 | | - temps = data.get("temperature_2m_mean", []) |
380 | | - |
381 | | - if temps: |
382 | | - return float(temps[0]) |
383 | | - |
384 | | - except Exception as e: |
385 | | - print("Historical Temp Error:", e) |
386 | | - |
387 | | - return 0.0 |
388 | | - |
389 | 314 | def build_training_data(): |
390 | 315 | conn = sqlite3.connect(DB_FILE) |
391 | 316 | c = conn.cursor() |
@@ -507,29 +432,6 @@ def load_or_train_model(): |
507 | 432 | train_model() |
508 | 433 | return joblib.load(MODEL_FILE) |
509 | 434 |
|
510 | | -def get_weather_forecast(days=7): |
511 | | - try: |
512 | | - url = ( |
513 | | - f"https://api.open-meteo.com/v1/forecast" |
514 | | - f"?latitude={LATITUDE}" |
515 | | - f"&longitude={LONGITUDE}" |
516 | | - f"&daily=cloud_cover_mean,temperature_2m_mean" |
517 | | - f"&timezone=Europe/Berlin" |
518 | | - ) |
519 | | - |
520 | | - r = requests.get(url, timeout=5) |
521 | | - data = r.json().get("daily", {}) |
522 | | - |
523 | | - dates = data.get("time", []) |
524 | | - clouds = data.get("cloud_cover_mean", []) |
525 | | - temps = data.get("temperature_2m_mean", []) |
526 | | - |
527 | | - return list(zip(dates[:days], clouds[:days], temps[:days])) |
528 | | - |
529 | | - except Exception as e: |
530 | | - print("Forecast Weather Error:", e) |
531 | | - return [] |
532 | | - |
533 | 435 | @app.route('/') |
534 | 436 | def index(): |
535 | 437 | return render_template('templates/index.html') |
|
0 commit comments