emhass 0.9.0__py3-none-any.whl → 0.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- emhass/command_line.py +392 -286
- emhass/forecast.py +21 -21
- emhass/machine_learning_regressor.py +14 -53
- emhass/optimization.py +261 -82
- emhass/retrieve_hass.py +76 -101
- emhass/utils.py +70 -128
- emhass/web_server.py +32 -7
- {emhass-0.9.0.dist-info → emhass-0.10.0.dist-info}/METADATA +125 -19
- {emhass-0.9.0.dist-info → emhass-0.10.0.dist-info}/RECORD +13 -13
- {emhass-0.9.0.dist-info → emhass-0.10.0.dist-info}/LICENSE +0 -0
- {emhass-0.9.0.dist-info → emhass-0.10.0.dist-info}/WHEEL +0 -0
- {emhass-0.9.0.dist-info → emhass-0.10.0.dist-info}/entry_points.txt +0 -0
- {emhass-0.9.0.dist-info → emhass-0.10.0.dist-info}/top_level.txt +0 -0
emhass/forecast.py
CHANGED
@@ -186,7 +186,7 @@ class Forecast(object):
|
|
186
186
|
self.logger.info("Retrieving weather forecast data using method = "+method)
|
187
187
|
self.weather_forecast_method = method # Saving this attribute for later use to identify csv method usage
|
188
188
|
if method == 'scrapper':
|
189
|
-
freq_scrap = pd.to_timedelta(60, "minutes") # The scrapping time step is 60min
|
189
|
+
freq_scrap = pd.to_timedelta(60, "minutes") # The scrapping time step is 60min on clearoutside
|
190
190
|
forecast_dates_scrap = pd.date_range(start=self.start_forecast,
|
191
191
|
end=self.end_forecast-freq_scrap,
|
192
192
|
freq=freq_scrap).round(freq_scrap, ambiguous='infer', nonexistent='shift_forward')
|
@@ -204,7 +204,7 @@ class Forecast(object):
|
|
204
204
|
col_names = [list_names[i].get_text() for i in selected_cols]
|
205
205
|
list_tables = [list_tables[i] for i in selected_cols]
|
206
206
|
# Building the raw DF container
|
207
|
-
raw_data = pd.DataFrame(index=range(
|
207
|
+
raw_data = pd.DataFrame(index=range(len(forecast_dates_scrap)), columns=col_names, dtype=float)
|
208
208
|
for count_col, col in enumerate(col_names):
|
209
209
|
list_rows = list_tables[count_col].find_all('li')
|
210
210
|
for count_row, row in enumerate(list_rows):
|
@@ -235,7 +235,8 @@ class Forecast(object):
|
|
235
235
|
"Authorization": "Bearer " + self.retrieve_hass_conf['solcast_api_key'],
|
236
236
|
"content-type": "application/json",
|
237
237
|
}
|
238
|
-
|
238
|
+
days_solcast = int(len(self.forecast_dates)*self.freq.seconds/3600)
|
239
|
+
url = "https://api.solcast.com.au/rooftop_sites/"+self.retrieve_hass_conf['solcast_rooftop_id']+"/forecasts?hours="+str(days_solcast)
|
239
240
|
response = get(url, headers=headers)
|
240
241
|
'''import bz2 # Uncomment to save a serialized data for tests
|
241
242
|
import _pickle as cPickle
|
@@ -263,7 +264,11 @@ class Forecast(object):
|
|
263
264
|
self.retrieve_hass_conf['solar_forecast_kwp'] = 5
|
264
265
|
if self.retrieve_hass_conf['solar_forecast_kwp'] == 0:
|
265
266
|
self.logger.warning("The solar_forecast_kwp parameter is set to zero, setting to default 5")
|
266
|
-
self.retrieve_hass_conf['solar_forecast_kwp'] = 5
|
267
|
+
self.retrieve_hass_conf['solar_forecast_kwp'] = 5
|
268
|
+
if self.optim_conf['delta_forecast'].days > 1:
|
269
|
+
self.logger.warning("The free public tier for solar.forecast only provides one day forecasts")
|
270
|
+
self.logger.warning("Continuing with just the first day of data, the other days are filled with 0.0.")
|
271
|
+
self.logger.warning("Use the other available methods for delta_forecast > 1")
|
267
272
|
headers = {
|
268
273
|
"Accept": "application/json"
|
269
274
|
}
|
@@ -289,7 +294,8 @@ class Forecast(object):
|
|
289
294
|
mask_down_data_df = data_tmp.copy(deep=True).fillna(method = "bfill").isnull()
|
290
295
|
data_tmp.loc[data_tmp.index[mask_up_data_df['yhat']==True],:] = 0.0
|
291
296
|
data_tmp.loc[data_tmp.index[mask_down_data_df['yhat']==True],:] = 0.0
|
292
|
-
data_tmp.interpolate(inplace=True)
|
297
|
+
data_tmp.interpolate(inplace=True, limit=1)
|
298
|
+
data_tmp = data_tmp.fillna(0.0)
|
293
299
|
if len(data) == 0:
|
294
300
|
data = copy.deepcopy(data_tmp)
|
295
301
|
else:
|
@@ -417,9 +423,9 @@ class Forecast(object):
|
|
417
423
|
# Setting the main parameters of the PV plant
|
418
424
|
location = Location(latitude=self.lat, longitude=self.lon)
|
419
425
|
temp_params = TEMPERATURE_MODEL_PARAMETERS['sapm']['close_mount_glass_glass']
|
420
|
-
cec_modules = bz2.BZ2File(self.emhass_conf['root_path'] / '
|
426
|
+
cec_modules = bz2.BZ2File(self.emhass_conf['root_path'] / 'data/cec_modules.pbz2', "rb")
|
421
427
|
cec_modules = cPickle.load(cec_modules)
|
422
|
-
cec_inverters = bz2.BZ2File(self.emhass_conf['root_path'] / '
|
428
|
+
cec_inverters = bz2.BZ2File(self.emhass_conf['root_path'] / 'data/cec_inverters.pbz2', "rb")
|
423
429
|
cec_inverters = cPickle.load(cec_inverters)
|
424
430
|
if type(self.plant_conf['module_model']) == list:
|
425
431
|
P_PV_forecast = pd.Series(0, index=df_weather.index)
|
@@ -639,10 +645,10 @@ class Forecast(object):
|
|
639
645
|
days_list = get_days_list(days_min_load_forecast)
|
640
646
|
if not rh.get_data(days_list, var_list):
|
641
647
|
return False
|
642
|
-
if not rh.prepare_data(
|
643
|
-
|
644
|
-
|
645
|
-
|
648
|
+
if not rh.prepare_data(
|
649
|
+
self.retrieve_hass_conf['var_load'], load_negative = self.retrieve_hass_conf['load_negative'],
|
650
|
+
set_zero_min = self.retrieve_hass_conf['set_zero_min'],
|
651
|
+
var_replace_zero = var_replace_zero, var_interp = var_interp):
|
646
652
|
return False
|
647
653
|
df = rh.df_final.copy()[[self.var_load_new]]
|
648
654
|
if method == 'naive': # using a naive approach
|
@@ -747,7 +753,6 @@ class Forecast(object):
|
|
747
753
|
|
748
754
|
"""
|
749
755
|
csv_path = self.emhass_conf['data_path'] / csv_path
|
750
|
-
|
751
756
|
if method == 'hp_hc_periods':
|
752
757
|
df_final[self.var_load_cost] = self.optim_conf['load_cost_hc']
|
753
758
|
list_df_hp = []
|
@@ -780,12 +785,11 @@ class Forecast(object):
|
|
780
785
|
else:
|
781
786
|
self.logger.error("Passed method is not valid")
|
782
787
|
return False
|
783
|
-
|
784
788
|
return df_final
|
785
789
|
|
786
790
|
def get_prod_price_forecast(self, df_final: pd.DataFrame, method: Optional[str] = 'constant',
|
787
|
-
|
788
|
-
|
791
|
+
csv_path: Optional[str] = "data_prod_price_forecast.csv",
|
792
|
+
list_and_perfect: Optional[bool] = False) -> pd.DataFrame:
|
789
793
|
|
790
794
|
r"""
|
791
795
|
Get the unit power production price for the energy injected to the grid.\
|
@@ -807,16 +811,13 @@ class Forecast(object):
|
|
807
811
|
:rtype: pd.DataFrame
|
808
812
|
|
809
813
|
"""
|
810
|
-
|
811
814
|
csv_path = self.emhass_conf['data_path'] / csv_path
|
812
|
-
|
813
815
|
if method == 'constant':
|
814
816
|
df_final[self.var_prod_price] = self.optim_conf['prod_sell_price']
|
815
817
|
elif method == 'csv':
|
816
818
|
forecast_dates_csv = self.get_forecast_days_csv(timedelta_days=0)
|
817
|
-
forecast_out = self.get_forecast_out_from_csv_or_list(
|
818
|
-
|
819
|
-
csv_path)
|
819
|
+
forecast_out = self.get_forecast_out_from_csv_or_list(
|
820
|
+
df_final, forecast_dates_csv, csv_path)
|
820
821
|
df_final[self.var_prod_price] = forecast_out
|
821
822
|
elif method == 'list': # reading a list of values
|
822
823
|
# Loading data from passed list
|
@@ -837,6 +838,5 @@ class Forecast(object):
|
|
837
838
|
else:
|
838
839
|
self.logger.error("Passed method is not valid")
|
839
840
|
return False
|
840
|
-
|
841
841
|
return df_final
|
842
842
|
|
@@ -76,16 +76,8 @@ class MLRegressor:
|
|
76
76
|
|
77
77
|
"""
|
78
78
|
|
79
|
-
def __init__(
|
80
|
-
|
81
|
-
data: pd.DataFrame,
|
82
|
-
model_type: str,
|
83
|
-
regression_model: str,
|
84
|
-
features: list,
|
85
|
-
target: str,
|
86
|
-
timestamp: str,
|
87
|
-
logger: logging.Logger,
|
88
|
-
) -> None:
|
79
|
+
def __init__(self: MLRegressor, data: pd.DataFrame, model_type: str, regression_model: str,
|
80
|
+
features: list, target: str, timestamp: str, logger: logging.Logger) -> None:
|
89
81
|
r"""Define constructor for the forecast class.
|
90
82
|
|
91
83
|
:param data: The data that will be used for train/test
|
@@ -124,11 +116,7 @@ class MLRegressor:
|
|
124
116
|
self.grid_search = None
|
125
117
|
|
126
118
|
@staticmethod
|
127
|
-
def add_date_features(
|
128
|
-
data: pd.DataFrame,
|
129
|
-
date_features: list,
|
130
|
-
timestamp: str,
|
131
|
-
) -> pd.DataFrame:
|
119
|
+
def add_date_features(data: pd.DataFrame, date_features: list, timestamp: str) -> pd.DataFrame:
|
132
120
|
"""Add date features from the input DataFrame timestamp.
|
133
121
|
|
134
122
|
:param data: The input DataFrame
|
@@ -152,23 +140,18 @@ class MLRegressor:
|
|
152
140
|
df["day"] = [i.day for i in df["timestamp"]]
|
153
141
|
if "hour" in date_features:
|
154
142
|
df["hour"] = [i.day for i in df["timestamp"]]
|
155
|
-
|
156
143
|
return df
|
157
144
|
|
158
145
|
def get_regression_model(self: MLRegressor) -> tuple[str, str]:
|
159
|
-
"""
|
160
|
-
|
146
|
+
r"""
|
147
|
+
Get the base model and parameter grid for the specified regression model.
|
161
148
|
Returns a tuple containing the base model and parameter grid corresponding to \
|
162
149
|
the specified regression model.
|
163
150
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
Returns:
|
169
|
-
-------
|
170
|
-
A tuple containing the base model and parameter grid.
|
171
|
-
|
151
|
+
:param self: The instance of the MLRegressor class.
|
152
|
+
:type self: MLRegressor
|
153
|
+
:return: A tuple containing the base model and parameter grid.
|
154
|
+
:rtype: tuple[str, str]
|
172
155
|
"""
|
173
156
|
if self.regression_model == "LinearRegression":
|
174
157
|
base_model = REGRESSION_METHODS["LinearRegression"]["model"]
|
@@ -197,7 +180,7 @@ class MLRegressor:
|
|
197
180
|
return base_model, param_grid
|
198
181
|
|
199
182
|
def fit(self: MLRegressor, date_features: list | None = None) -> None:
|
200
|
-
"""Fit the model using the provided data.
|
183
|
+
r"""Fit the model using the provided data.
|
201
184
|
|
202
185
|
:param date_features: A list of 'date_features' to take into account when \
|
203
186
|
fitting the model.
|
@@ -226,45 +209,24 @@ class MLRegressor:
|
|
226
209
|
"If no timestamp provided, you can't use date_features, going \
|
227
210
|
further without date_features.",
|
228
211
|
)
|
229
|
-
|
230
212
|
y = self.data_exo[self.target]
|
231
213
|
self.data_exo = self.data_exo.drop(self.target, axis=1)
|
232
214
|
if self.timestamp is not None:
|
233
215
|
self.data_exo = self.data_exo.drop(self.timestamp, axis=1)
|
234
|
-
X = self.data_exo
|
235
|
-
|
236
|
-
X_train, X_test, y_train, y_test = train_test_split( # noqa: N806
|
237
|
-
X,
|
238
|
-
y,
|
239
|
-
test_size=0.2,
|
240
|
-
random_state=42,
|
241
|
-
)
|
242
|
-
|
216
|
+
X = self.data_exo
|
217
|
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
243
218
|
self.steps = len(X_test)
|
244
|
-
|
245
219
|
base_model, param_grid = self.get_regression_model()
|
246
|
-
|
247
220
|
self.model = make_pipeline(StandardScaler(), base_model)
|
248
|
-
|
249
221
|
# Create a grid search object
|
250
|
-
self.grid_search = GridSearchCV(
|
251
|
-
|
252
|
-
param_grid,
|
253
|
-
cv=5,
|
254
|
-
scoring="neg_mean_squared_error",
|
255
|
-
refit=True,
|
256
|
-
verbose=0,
|
257
|
-
n_jobs=-1,
|
258
|
-
)
|
259
|
-
|
222
|
+
self.grid_search = GridSearchCV(self.model, param_grid, cv=5, scoring="neg_mean_squared_error",
|
223
|
+
refit=True, verbose=0, n_jobs=-1)
|
260
224
|
# Fit the grid search object to the data
|
261
225
|
self.logger.info("Training a %s model", self.regression_model)
|
262
226
|
start_time = time.time()
|
263
227
|
self.grid_search.fit(X_train.values, y_train.values)
|
264
228
|
self.logger.info("Elapsed time for model fit: %s", time.time() - start_time)
|
265
|
-
|
266
229
|
self.model = self.grid_search.best_estimator_
|
267
|
-
|
268
230
|
# Make predictions
|
269
231
|
predictions = self.model.predict(X_test.values)
|
270
232
|
predictions = pd.Series(predictions, index=X_test.index)
|
@@ -286,5 +248,4 @@ class MLRegressor:
|
|
286
248
|
"""
|
287
249
|
self.logger.info("Performing a prediction for %s", self.model_type)
|
288
250
|
new_values = np.array([new_values])
|
289
|
-
|
290
251
|
return self.model.predict(new_values)
|