Kubas126cz commited on
Commit
696a9b4
·
1 Parent(s): 72473af

JHA_Solarmon_API

Browse files
__pycache__/meteo_functions.cpython-310.pyc ADDED
Binary file (5.93 kB). View file
 
__pycache__/predictions.cpython-310.pyc ADDED
Binary file (3.62 kB). View file
 
__pycache__/preprocessing_functions.cpython-310.pyc ADDED
Binary file (4.19 kB). View file
 
app.py CHANGED
@@ -3,19 +3,28 @@ import pandas as pd
3
  import numpy as np
4
  from datetime import timedelta
5
  import datetime
6
- from meteo_functions import get_meteo_data, get_air_quality_data, get_forecast_meteo_data, get_air_quality_forecast
7
- from predictions import predict
8
  import gc
 
 
 
 
9
 
10
  def clear_memory():
11
  gc.collect()
12
 
13
- st.title("Predikce výkonu FVE ABA")
14
- #t.header("this is a header")
15
- #sst.subheader("subheader")
16
- #st.markdown("This is **Markdown**")
17
- #st.caption("small text")
 
 
 
 
18
 
 
19
  today = datetime.date.today()
20
  max_date = today + datetime.timedelta(days=4)
21
 
@@ -25,30 +34,45 @@ with st.form(key="sample_form"):
25
 
26
  if submit_button:
27
  previous_day = date_utc - timedelta(days=1)
28
- if date_utc < today - datetime.timedelta(days=1):
29
- # st.subheader(f"Predikce výkonu pro: {date_utc}:")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
- df_meteo = get_meteo_data(previous_day, date_utc)
32
- df_air_quality = get_air_quality_data(previous_day, date_utc)
33
-
34
- data = df_meteo.merge(df_air_quality, on="DT", how="inner")
35
- st.write("Meteorologická data:")
36
- st.dataframe(data)
37
- predict(data)
38
- clear_memory()
39
 
40
- elif previous_day < date_utc <= today + datetime.timedelta(days=4):
41
- # st.header(f"Data pro: {date_utc}:")
 
 
42
  df_meteo = get_forecast_meteo_data(previous_day, date_utc)
43
  df_air_quality = get_air_quality_forecast(previous_day, date_utc)
44
  data = df_meteo.merge(df_air_quality, on="DT", how="inner")
 
45
  #st.write("Budouci data:")
46
  #st.dataframe(df_meteo)
47
  #st.dataframe(df_air_quality)
48
- st.write("Meteorologická data:")
 
49
  st.dataframe(data)
50
  predict(data)
51
  clear_memory()
52
 
53
  else:
54
- st.warning("Predikce je dostupná pouze pro následujících 5 dnů.")
 
3
  import numpy as np
4
  from datetime import timedelta
5
  import datetime
6
+ from meteo_functions import *
7
+ from predictions import predict, predict_solarmon_history
8
  import gc
9
+ import os
10
+
11
+ username = os.environ.get("USERNAME")
12
+ password = os.environ.get("PASSWORD")
13
 
14
  def clear_memory():
15
  gc.collect()
16
 
17
+ def use_history_model(previous_day, date_utc):
18
+ # st.subheader(f"Predikce výkonu pro: {date_utc}:")
19
+ df_meteo = get_meteo_data(previous_day, date_utc)
20
+ df_air_quality = get_air_quality_data(previous_day, date_utc)
21
+ data = df_meteo.merge(df_air_quality, on="DT", how="inner")
22
+ st.write("Meteorologická data:")
23
+ st.dataframe(data)
24
+ predict(data)
25
+ clear_memory()
26
 
27
+ st.title("Predikce výkonu FVE ABA")
28
  today = datetime.date.today()
29
  max_date = today + datetime.timedelta(days=4)
30
 
 
34
 
35
  if submit_button:
36
  previous_day = date_utc - timedelta(days=1)
37
+ if date_utc < today - datetime.timedelta(days=60): # delsi nez dva mesice - data z API k dispozici jen 2 mesice zpetne
38
+ use_history_model(previous_day= previous_day, date_utc = date_utc)
39
+ elif date_utc < today:
40
+ data_solarmon = load_and_check_data_solarmon(date_utc- timedelta(days=1), date_utc, username, password) # solarmon-api
41
+ if data_solarmon.isnull().values.any():
42
+ st.warning("Nepodařilo se načíst data ze systému SOLARMON.")
43
+ use_history_model(previous_day= previous_day, date_utc = date_utc)
44
+ else:
45
+ df_meteo = get_meteo_data(previous_day, date_utc)
46
+ df_air_quality = get_air_quality_data(previous_day, date_utc)
47
+ predpoved_meteo = create_time_cycles(df_meteo.merge(df_air_quality, on="DT", how="inner"))
48
+
49
+ # st.write("Data - Solarmon:")
50
+ # st.dataframe(data_solarmon)
51
+
52
+ # st.write("Předpověď - OpenMeteo:")
53
+ # st.dataframe(predpoved_meteo)
54
 
55
+ predpoved_meteo['DT'] = pd.to_datetime(predpoved_meteo['DT']).dt.tz_localize('UTC')
56
+ data = predpoved_meteo.merge(data_solarmon, on="DT", how="inner")
57
+ st.write("Dataset:")
58
+ st.dataframe(data)
 
 
 
 
59
 
60
+ predict_solarmon_history(data)
61
+ clear_memory()
62
+ elif previous_day < date_utc < today + datetime.timedelta(days=5):
63
+ # st.header(f"Data pro: {date_utc}:")
64
  df_meteo = get_forecast_meteo_data(previous_day, date_utc)
65
  df_air_quality = get_air_quality_forecast(previous_day, date_utc)
66
  data = df_meteo.merge(df_air_quality, on="DT", how="inner")
67
+
68
  #st.write("Budouci data:")
69
  #st.dataframe(df_meteo)
70
  #st.dataframe(df_air_quality)
71
+
72
+ st.write("Meteorologická předpověď:")
73
  st.dataframe(data)
74
  predict(data)
75
  clear_memory()
76
 
77
  else:
78
+ st.warning("Predikce je dostupná pouze pro následujících 5 dnů.")
input_preprocessor.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8c2fcafee3a50a9029417ae11ba929ac8af042978438e334b858dcc2831f01bd
3
+ size 5908
meteo_functions.py CHANGED
@@ -2,6 +2,9 @@
2
  import pandas as pd
3
  import requests
4
  import numpy as np
 
 
 
5
 
6
  lat = 49.13114
7
  lon = 15.18067
@@ -136,7 +139,93 @@ def get_air_quality_forecast(start_date, end_date):
136
 
137
  return df_air_quality
138
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
 
 
 
 
 
 
140
 
141
 
142
 
 
2
  import pandas as pd
3
  import requests
4
  import numpy as np
5
+ import streamlit as st
6
+ import json
7
+ from datetime import date, timedelta
8
 
9
  lat = 49.13114
10
  lon = 15.18067
 
139
 
140
  return df_air_quality
141
 
142
+ def get_data_for_day(day: date, username: str, password: str) -> list[dict]:
143
+ """
144
+ Stáhne data z API pomocí POST požadavku pro daný den.
145
+
146
+ :param day: Datum typu `datetime.date`
147
+ :param username: Uživatelské jméno pro API
148
+ :param password: Heslo pro API
149
+ :return: Seznam měření (dictů) pro daný den
150
+ """
151
+ url = f"https://aba.solarmon.eu/rest-server/?q=getDataPredMod&date={day.strftime('%Y-%m-%d')}"
152
+ payload = {
153
+ 'username': username,
154
+ 'password': password
155
+ }
156
+
157
+ try:
158
+ response = requests.post(url, data=payload)
159
+ response.raise_for_status()
160
+ data = response.json()
161
+ data = json.loads(data)
162
+ return data.get("data", [])
163
+ except (requests.RequestException, ValueError) as e:
164
+ st.error(f"Chyba při načítání dat pro {day}: {e}")
165
+ return []
166
+
167
+ def load_and_check_data_solarmon(start_date: date, end_date: date, username: str, password: str) -> pd.DataFrame:
168
+ current_date = start_date
169
+ all_data = []
170
+
171
+ while current_date <= end_date + timedelta(days=1):# pridame dalsi den kvuli poslednim 2 hodinam
172
+ daily_data = pd.DataFrame(get_data_for_day(current_date, username, password))
173
+ all_data.append(daily_data)
174
+ current_date += timedelta(days=1)
175
+
176
+ solarmon_meteo = pd.concat(all_data).sort_index()
177
+
178
+ solarmon_meteo = pd.DataFrame(solarmon_meteo)
179
+ solarmon_meteo["time"] = pd.to_datetime(solarmon_meteo["time"]) # + pd.Timedelta(minutes=5) # experimentalne zjistit zda je potreba
180
+ solarmon_meteo["time"] = solarmon_meteo["time"].dt.tz_localize("Europe/Prague").dt.tz_convert("UTC")
181
+ solarmon_meteo = solarmon_meteo.set_index("time")
182
+
183
+ # převody typů a kontrola
184
+ solarmon_meteo["int_sol_irr"] = pd.to_numeric(solarmon_meteo["int_sol_irr"], errors='coerce')
185
+ solarmon_meteo["tmp_module"] = pd.to_numeric(solarmon_meteo["tmp_module"], errors='coerce')
186
+ solarmon_meteo["energy"] = pd.to_numeric(solarmon_meteo["energy"], errors='coerce')
187
+
188
+ # Resample na hodinové intervaly
189
+ solarmon_meteo = solarmon_meteo.resample('1h').agg({
190
+ 'int_sol_irr': 'mean',
191
+ 'wind_vel': 'mean',
192
+ 'tmp_amb': 'mean',
193
+ 'tmp_module': 'mean',
194
+ 'energy': 'sum'
195
+ })
196
+ solarmon_meteo['energy'] = solarmon_meteo['energy'] / 1000
197
+
198
+ solarmon_meteo.rename_axis("DT", inplace=True)
199
+ solarmon_meteo.rename(
200
+ columns={
201
+ 'int_sol_irr': 'Me',
202
+ 'tmp_module': 'tp',
203
+ 'energy': 'output',
204
+ 'tmp_amb': 'to'
205
+ },
206
+ inplace=True
207
+ )
208
+ solarmon_meteo.drop(['wind_vel'], axis=1, inplace=True)
209
+
210
+ solarmon_meteo.index = pd.to_datetime(solarmon_meteo.index)
211
+ start_datetime = pd.to_datetime(start_date).tz_localize('UTC')
212
+ end_datetime = pd.to_datetime(end_date).tz_localize('UTC') + pd.Timedelta(days=1)
213
+ solarmon_meteo = solarmon_meteo[(solarmon_meteo.index >= start_datetime) & (solarmon_meteo.index < end_datetime)]
214
+
215
+ # solarmon_meteo.isnull().values.any() kontroluje zda neni NaN hodnota
216
+ solarmon_meteo = solarmon_meteo.reset_index(drop=False) # resetovani indexu
217
+
218
+ # df_meteo["DT"] = df_meteo["DT"].dt.tz_localize("UTC")
219
+ #df_meteo = get_meteo_data(previous_date, selected_date)
220
+ #df_air_quality = get_air_quality_data(previous_date, selected_date)
221
+ #df_meteo = df_meteo.merge(df_air_quality, on="DT", how="inner")
222
 
223
+
224
+
225
+ #df = df.merge(df_meteo, on="DT", how="inner")
226
+ #df = create_time_cycles(df)
227
+ return solarmon_meteo
228
+ # return df[2:26]
229
 
230
 
231
 
model_hist/input_preprocessor.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9bd687f252949c2015c68b81ae16b337d61ead28e6ec1f7bbb934fedf1f1bbfe
3
+ size 5756
model_hist/output_preprocessor.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a21576128cba609962c3396a6e7397f4a2765ede43a3e4c89d7481b6269ed1e5
3
+ size 1705
output_preprocessor.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a21576128cba609962c3396a6e7397f4a2765ede43a3e4c89d7481b6269ed1e5
3
+ size 1705
predictions.py CHANGED
@@ -3,7 +3,7 @@ import pandas as pd
3
  import numpy as np
4
  from meteo_functions import create_time_cycles
5
  import joblib
6
- from preprocessing_functions import create_sequences, plot_solar_power_prediction, load_model, load_transformers
7
  from tensorflow import keras
8
  import gc
9
 
@@ -58,3 +58,56 @@ def predict(data):
58
  gc.collect()
59
 
60
  return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import numpy as np
4
  from meteo_functions import create_time_cycles
5
  import joblib
6
+ from preprocessing_functions import create_sequences, create_sequences_solarmon, plot_solar_power_prediction, load_model, load_model_solarmon, load_transformers, load_transformers_solarmon
7
  from tensorflow import keras
8
  import gc
9
 
 
58
  gc.collect()
59
 
60
  return None
61
+
62
+ def predict_solarmon_history(data: pd.DataFrame):
63
+ """
64
+ Jedná se o přesnější predikce pro aktuální a minulé dny s využitím API od Solarmonu
65
+ data:: dataframe s daty za minuly den
66
+ predpoved:: dataframe s predpovedi pro aktualni den
67
+ """
68
+ st.divider()
69
+ # Načtění transformátorů
70
+ input_preprocessor, output_scaler = load_transformers_solarmon()
71
+
72
+ # transformace vstupnich prom
73
+ X_dataset = input_preprocessor.transform(data)
74
+ X_dataset = pd.DataFrame(X_dataset, columns=input_preprocessor.get_feature_names_out(), index=data.index)
75
+ # st.write(data.keys())
76
+ # st.write(input_preprocessor.get_feature_names_out())
77
+ rename_map = {
78
+ "yeo_minmax__RAD": "RAD",
79
+ "yeo_minmax__Relative_Humidity_2m": "Relative_Humidity_2m",
80
+ "yeo_minmax__PM10": "PM10",
81
+ "yeo_minmax__output": "output",
82
+ "yeo_minmax__Me": "Me",
83
+ "yeo_standard__Cloud_Cover": "Cloud_Cover",
84
+ "minmax__Temperature_2m": "Temperature_2m",
85
+ "minmax__wind_u": "wind_u",
86
+ "minmax__wind_v": "wind_v",
87
+ "minmax__Surface_Pressure": "Surface_Pressure",
88
+ "minmax__Ozone": "Ozone",
89
+ "minmax__tp": "tp",
90
+ "remainder__sin_hour": "sin_hour",
91
+ "remainder__cos_hour": "cos_hour",
92
+ "remainder__cos_day_of_year": "cos_day_of_year",
93
+ "remainder__sin_day_of_year": "sin_day_of_year"
94
+ }
95
+ X_dataset = X_dataset.rename(columns=rename_map)
96
+
97
+ # pro open meteo
98
+ features = ["Me", "output","tp","Relative_Humidity_2m", "Surface_Pressure", 'Cloud_Cover',
99
+ 'sin_hour', 'cos_hour', 'cos_day_of_year', "sin_day_of_year", "wind_u", "wind_v", "Ozone", "PM10"]
100
+ future_features = ["Me", "tp", "Relative_Humidity_2m", "Surface_Pressure", 'Cloud_Cover', "wind_u", "wind_v", "Ozone", "PM10", 'sin_hour', 'cos_hour', 'cos_day_of_year', "sin_day_of_year"]
101
+ x = create_sequences_solarmon(X_dataset, window=24, horizon=24, past_features=features, future_features=future_features)
102
+ model = load_model_solarmon()
103
+ y_pred = model.predict(x) # predikce
104
+ y_pred_trans = np.array([output_scaler.inverse_transform(y_pred[:, i].reshape(-1, 1)).flatten() for i in range(y_pred.shape[1])]).T
105
+
106
+ y_pred_trans[y_pred_trans < 1] = 0
107
+ st.write("**Predikované hodnoty výkonu (kW) pro jednotlivé hodiny:**")
108
+ st.write(y_pred_trans, use_container_width=True)
109
+
110
+ my_plt = plot_solar_power_prediction(y_pred_trans, df_true=data[['output']])
111
+ st.pyplot(my_plt)
112
+
113
+ return None
preprocessing_functions.py CHANGED
@@ -8,12 +8,24 @@ import joblib
8
  from pathlib import Path
9
 
10
  MODEL_PATH = "output_predictions_to_meteo_smape_25.keras"
 
 
 
 
 
 
11
 
12
  @st.cache_resource
13
  def load_model():
14
  model = tf.keras.models.load_model(MODEL_PATH)
15
  return model
16
 
 
 
 
 
 
 
17
  @st.cache_resource
18
  def load_transformers():
19
  input_preprocessor = joblib.load('input_preprocessor_meteo_to_smape25.pkl')
@@ -21,6 +33,63 @@ def load_transformers():
21
 
22
  return input_preprocessor, output_scaler
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  def create_sequences(data, window, horizon, past_features, future_features):
25
  """
26
  Vytvoří sekvence vstupních dat a odpovídající cílové hodnoty pro trénování LSTM modelu.
@@ -64,26 +133,36 @@ def create_sequences(data, window, horizon, past_features, future_features):
64
 
65
  return X
66
 
67
- def plot_solar_power_prediction(y_pred_trans):
 
 
 
68
  """
69
  Vytvoří graf predikce výkonu fotovoltaické elektrárny v průběhu dne.
70
 
71
  Args:
72
  y_pred_trans (numpy.ndarray): Pole s predikovanými hodnotami výkonu (kW) ve tvaru (1, 24).
73
-
 
 
74
  Returns:
75
  plt.Figure: Graf pro zobrazení.
76
  """
77
  hours = np.arange(24)
78
 
79
  plt.figure(figsize=(10, 6))
80
- plt.plot(hours, y_pred_trans.flatten(), marker='o', label='Výkon (kW)')
 
 
 
 
81
  plt.xlabel('Hodiny (UTC)')
82
  plt.ylabel('Výkon (kW)')
83
- plt.title('Predikce výkonu fotovoltaické elektrárny v průběhu dne')
84
- plt.xticks(hours)
85
  plt.grid(True)
86
  plt.legend()
87
 
88
  return plt
89
 
 
 
8
  from pathlib import Path
9
 
10
  MODEL_PATH = "output_predictions_to_meteo_smape_25.keras"
11
+ MODEL_PATH_SOLARMON = "model_24h_base_on_Me_open_meteo_weather_predictions.keras"
12
+
13
+ MODEL_PATH_SOLARMON_HIST = "model_hist\model_24h_predictions_hist.keras"
14
+
15
+ TRANSFORMER_INPUT_PATH_HIST = "model_hist\input_preprocessor.pkl"
16
+ TRANSFORMER_OUTPUT_PATH_HIST = "model_hist\output_preprocessor.pkl"
17
 
18
  @st.cache_resource
19
  def load_model():
20
  model = tf.keras.models.load_model(MODEL_PATH)
21
  return model
22
 
23
+ @st.cache_resource
24
+ def load_model_solarmon():
25
+ # model = tf.keras.models.load_model(MODEL_PATH_SOLARMON)
26
+ model = tf.keras.models.load_model(MODEL_PATH_SOLARMON_HIST)
27
+ return model
28
+
29
  @st.cache_resource
30
  def load_transformers():
31
  input_preprocessor = joblib.load('input_preprocessor_meteo_to_smape25.pkl')
 
33
 
34
  return input_preprocessor, output_scaler
35
 
36
+ @st.cache_resource
37
+ def load_transformers_solarmon():
38
+ #input_preprocessor = joblib.load('input_preprocessor.pkl')
39
+ #output_scaler = joblib.load('output_preprocessor.pkl')
40
+
41
+ input_preprocessor = joblib.load(TRANSFORMER_INPUT_PATH_HIST)
42
+ output_scaler = joblib.load(TRANSFORMER_OUTPUT_PATH_HIST)
43
+ return input_preprocessor, output_scaler
44
+
45
+ import numpy as np
46
+
47
+ def create_sequences_solarmon(data, window, horizon, past_features, future_features):
48
+ """
49
+ Vytvoří sekvence vstupních dat a odpovídající cílové hodnoty pro trénování LSTM modelu.
50
+
51
+ Parametry:
52
+ ----------
53
+ data : pandas.DataFrame
54
+ DataFrame obsahující časové řady.
55
+ window : int
56
+ Počet časových kroků v minulosti.
57
+ horizon : int
58
+ Počet časových kroků do budoucnosti.
59
+ past_features : list
60
+ Seznam sloupců, které budou použity jako vstupní vlastnosti v minulosti.
61
+ future_features : list
62
+ Seznam sloupců, které budou použity jako vstupní vlastnosti v budoucnosti.
63
+ target : str
64
+ Název sloupce, který bude použit jako cílová hodnota.
65
+
66
+ Návratové hodnoty:
67
+ -------------------
68
+ X : numpy.ndarray
69
+ Pole tvaru (vzorky, window, past_features + future_features), obsahující sekvence vstupních dat.
70
+ y : numpy.ndarray
71
+ Pole tvaru (vzorky, horizon), obsahující odpovídající cílové hodnoty.
72
+ """
73
+
74
+ # Vytvoření historických sekvencí (past_features)
75
+ X_past = np.lib.stride_tricks.sliding_window_view(
76
+ data[past_features].values, (window, len(past_features))
77
+ )[:-horizon, :, :]
78
+
79
+ X_past = np.squeeze(X_past, axis=1) # Výstup: (vzorky, window, len(past_features))
80
+
81
+ # Vytvoření budoucích sekvencí (future_features)
82
+ X_future = np.lib.stride_tricks.sliding_window_view(
83
+ data[future_features].values, (window, len(future_features))
84
+ )[horizon-1 : len(X_past) + horizon-1, :, :]
85
+
86
+ X_future = np.squeeze(X_future, axis=1) # Výstup: (vzorky, window, len(future_features))
87
+
88
+ # Spojení historických a budoucích proměnných do jednoho pole
89
+ X = np.concatenate([X_past, X_future], axis=2) # (vzorky, window, past_features + future_features)
90
+
91
+ return X
92
+
93
  def create_sequences(data, window, horizon, past_features, future_features):
94
  """
95
  Vytvoří sekvence vstupních dat a odpovídající cílové hodnoty pro trénování LSTM modelu.
 
133
 
134
  return X
135
 
136
+ import numpy as np
137
+ import matplotlib.pyplot as plt
138
+
139
+ def plot_solar_power_prediction(y_pred_trans, df_true=None):
140
  """
141
  Vytvoří graf predikce výkonu fotovoltaické elektrárny v průběhu dne.
142
 
143
  Args:
144
  y_pred_trans (numpy.ndarray): Pole s predikovanými hodnotami výkonu (kW) ve tvaru (1, 24).
145
+ df_true (pd.DataFrame, optional): DataFrame se skutečnými hodnotami výkonu.
146
+ Musí obsahovat sloupec 'output' s 24 hodnotami.
147
+
148
  Returns:
149
  plt.Figure: Graf pro zobrazení.
150
  """
151
  hours = np.arange(24)
152
 
153
  plt.figure(figsize=(10, 6))
154
+ plt.plot(hours, y_pred_trans.flatten(), marker='o', label='Predikce (kW)', color='tab:blue')
155
+
156
+ if df_true is not None and 'output' in df_true.columns and len(df_true) >= 24:
157
+ plt.plot(hours, df_true['output'].values[23:-1], marker='x', label='Skutečný výkon (kW)', color='tab:orange')
158
+
159
  plt.xlabel('Hodiny (UTC)')
160
  plt.ylabel('Výkon (kW)')
161
+ plt.title('Predikce vs. skutečný výkon FVE v průběhu dne')
162
+ plt.xticks(hours)
163
  plt.grid(True)
164
  plt.legend()
165
 
166
  return plt
167
 
168
+