|
from fastapi import FastAPI |
|
from apscheduler.schedulers.background import BackgroundScheduler |
|
import pandas as pd |
|
import requests |
|
import xgboost as xgb |
|
from datetime import datetime, timedelta |
|
import os |
|
import matplotlib.pyplot as plt |
|
|
|
app = FastAPI() |
|
|
|
|
|
scheduler = BackgroundScheduler() |
|
scheduler.start() |
|
|
|
|
|
model = xgb.XGBRegressor() |
|
model.load_model("electricity_price_model.json") |
|
|
|
|
|
WEATHER_API = "https://api.open-meteo.com/v1/forecast" |
|
ELECTRICITY_PRICE_API = "https://www.elprisetjustnu.se/api/v1/prices" |
|
ENERGY_CHARTS_API = "https://api.energy-charts.info/public_power?" |
|
PREDICTIONS_FILE = "predicted_prices.csv" |
|
|
|
|
|
def fetch_weather_data_for_tomorrow(): |
|
tomorrow = (datetime.now() + timedelta(days=1)).strftime('%Y-%m-%d') |
|
params = { |
|
"latitude": 59.3293, |
|
"longitude": 18.0686, |
|
"daily": "temperature_2m_mean,precipitation_sum,wind_speed_10m_max,wind_direction_10m_dominant", |
|
"start_date": tomorrow, |
|
"end_date": tomorrow, |
|
"timezone": "Europe/Stockholm" |
|
} |
|
response = requests.get(WEATHER_API, params=params) |
|
response.raise_for_status() |
|
data = response.json()["daily"] |
|
return pd.DataFrame(data) |
|
|
|
|
|
def fetch_energy_production_data(): |
|
today = datetime.now().strftime('%Y-%m-%d') |
|
params = {"country": "se", "start": today, "end": today} |
|
response = requests.get(ENERGY_CHARTS_API, params=params) |
|
response.raise_for_status() |
|
data = response.json() |
|
|
|
if "production_types" in data: |
|
production_data = { |
|
"unix_seconds": data["unix_seconds"], |
|
**{ptype["name"]: ptype["data"] for ptype in data["production_types"]} |
|
} |
|
energy_df = pd.DataFrame(production_data) |
|
energy_df = energy_df.rename(columns={"unix_seconds": "time"}) |
|
energy_df["time"] = pd.to_datetime(energy_df["time"], unit="s", errors="coerce").dt.tz_localize(None) |
|
return energy_df |
|
else: |
|
return pd.DataFrame() |
|
|
|
|
|
def fetch_current_electricity_prices(): |
|
today = datetime.now().strftime('%Y/%m-%d') |
|
url = f"{ELECTRICITY_PRICE_API}/{today}_SE3.json" |
|
response = requests.get(url) |
|
response.raise_for_status() |
|
data = response.json() |
|
|
|
electricity_df = pd.DataFrame(data) |
|
electricity_df = electricity_df.rename(columns={"time_start": "time"}) |
|
electricity_df["time"] = pd.to_datetime(electricity_df["time"], errors="coerce").dt.tz_localize(None) |
|
return electricity_df |
|
|
|
|
|
def prepare_prediction_data(): |
|
energy_data = fetch_energy_production_data() |
|
electricity_data = fetch_current_electricity_prices() |
|
weather_data = fetch_weather_data_for_tomorrow() |
|
|
|
dataset = pd.merge(energy_data, electricity_data, on="time", how="inner") |
|
dataset = pd.merge(dataset, weather_data, on="time", how="outer") |
|
dataset = dataset.dropna() |
|
return dataset |
|
|
|
|
|
def predict_next_day_price(): |
|
dataset = prepare_prediction_data() |
|
X = dataset.drop(["SEK_per_kWh", "time"], axis=1, errors="ignore") |
|
predictions = model.predict(X) |
|
dataset["predicted_price"] = predictions |
|
dataset.to_csv(PREDICTIONS_FILE, index=False) |
|
generate_dashboard(dataset) |
|
print("Predictions saved to 'predicted_prices.csv'.") |
|
|
|
|
|
def generate_dashboard(data): |
|
plt.figure(figsize=(10, 6)) |
|
plt.plot(data["time"], data["predicted_price"], label="Predicted Price", linestyle="--") |
|
if "SEK_per_kWh" in data.columns: |
|
plt.plot(data["time"], data["SEK_per_kWh"], label="Actual Price") |
|
plt.xlabel("Time") |
|
plt.ylabel("Electricity Price (SEK/kWh)") |
|
plt.title("Electricity Prices: Predicted vs Actual") |
|
plt.legend() |
|
plt.grid() |
|
plt.savefig("dashboard.png") |
|
plt.close() |
|
|
|
|
|
scheduler.add_job(predict_next_day_price, "cron", hour=23, minute=59) |
|
|
|
|
|
@app.get("/predictions") |
|
def get_predictions(): |
|
if not os.path.exists(PREDICTIONS_FILE): |
|
return {"error": "Predictions not available"} |
|
predictions = pd.read_csv(PREDICTIONS_FILE) |
|
return predictions.to_dict() |
|
|
|
|
|
@app.get("/dashboard") |
|
def get_dashboard(): |
|
if not os.path.exists("dashboard.png"): |
|
return {"error": "Dashboard not available"} |
|
return {"dashboard_url": "/dashboard.png"} |
|
|