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/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(24), columns=col_names, dtype=float)
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
- url = "https://api.solcast.com.au/rooftop_sites/"+self.retrieve_hass_conf['solcast_rooftop_id']+"/forecasts?hours=24"
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'] / 'src/emhass/data/cec_modules.pbz2', "rb")
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'] / 'src/emhass/data/cec_inverters.pbz2', "rb")
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(self.retrieve_hass_conf['var_load'], load_negative = self.retrieve_hass_conf['load_negative'],
643
- set_zero_min = self.retrieve_hass_conf['set_zero_min'],
644
- var_replace_zero = var_replace_zero,
645
- var_interp = var_interp):
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
- csv_path: Optional[str] = "data_prod_price_forecast.csv",
788
- list_and_perfect: Optional[bool] = False) -> pd.DataFrame:
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(df_final,
818
- forecast_dates_csv,
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__( # noqa: PLR0913
80
- self: MLRegressor,
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
- """Get the base model and parameter grid for the specified regression model.
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
- Args:
165
- ----
166
- self: The instance of the MLRegressor class.
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 # noqa: N806
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
- self.model,
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)