@@ -117,17 +117,23 @@ def _refine_data(options, data1):
117117 data1 .sort_index (inplace = True )
118118 return {"data" : data1 , "refine_logs" : refine_logs }
119119
120+ def _convert_local_to_utc (dte ):
121+ # datetime obj is converted from local time zone to utc
122+ local_timezone = datetime .now ().astimezone ().tzinfo
123+ return pd .Timestamp (dte ,tz = local_timezone ).tz_convert ('UTC' )
120124
121125def _entsoe_get_actual_generation (options = {"country" : "" , "start" : "" , "end" : "" }):
122126 """Fetches the aggregated actual generation per production type data (16.1.B&C) for the given country within the given start and end date
123127 params: options = {country (2 letter country code),start,end} . Both the dates are in the YYYYMMDDhhmm format and the local time zone
124128 returns : {"data":pd.DataFrame, "duration":duration (in min) of the time series data, "refine_logs":"notes on refinements made" }
125129 """
130+ utc_start = _convert_local_to_utc (options ["start" ])
131+ utc_end = _convert_local_to_utc (options ["end" ])
126132 client1 = entsoePandas (api_key = _get_API_token ())
127133 data1 = client1 .query_generation (
128134 options ["country" ],
129- start = pd . Timestamp ( options [ "start" ], tz = "UTC" ) ,
130- end = pd . Timestamp ( options [ "end" ], tz = "UTC" ) ,
135+ start = utc_start ,
136+ end = utc_end ,
131137 psr_type = None ,
132138 )
133139 # drop columns with actual consumption values (we want actual aggregated generation values)
@@ -159,8 +165,8 @@ def _entsoe_get_total_forecast(options={"country": "", "start": "", "end": ""}):
159165 client = entsoePandas (api_key = _get_API_token ())
160166 data = client .query_generation_forecast (
161167 options ["country" ],
162- start = pd . Timestamp (options ["start" ], tz = "UTC" ) ,
163- end = pd . Timestamp (options ["end" ], tz = "UTC" ),
168+ start = _convert_local_to_utc (options ["start" ]) ,
169+ end = _convert_local_to_utc (options ["end" ])
164170 )
165171 # if the data is a series instead of a dataframe, it will be converted to a dataframe
166172 if isinstance (data , pd .Series ):
@@ -188,8 +194,8 @@ def _entsoe_get_wind_solar_forecast(options={"country": "", "start": "", "end":
188194 client = entsoePandas (api_key = _get_API_token ())
189195 data = client .query_wind_and_solar_forecast (
190196 options ["country" ],
191- start = pd . Timestamp (options ["start" ], tz = "UTC" ) ,
192- end = pd . Timestamp (options ["end" ], tz = "UTC" ),
197+ start = _convert_local_to_utc (options ["start" ]) ,
198+ end = _convert_local_to_utc (options ["end" ])
193199 )
194200 durationMin = (data .index [1 ] - data .index [0 ]).total_seconds () / 60
195201 # refining the data
@@ -225,6 +231,10 @@ def _convert_to_60min_interval(rawData):
225231 # determining how many rows need to be combined to get data in 60 min format.
226232 groupingFactor = int (60 / duration )
227233 oldData = rawData ["data" ]
234+ # check if there is enough data to convert to 60 min
235+ if (len (oldData ) < groupingFactor ):
236+ raise ValueError ("Data cannot be converted into 60 min interval since there is inadequate number of rows in the data" )
237+
228238 oldData ["startTimeUTC" ] = pd .to_datetime (oldData ["startTimeUTC" ])
229239 start_time = oldData ["startTimeUTC" ].min ()
230240 end_time = oldData ["startTimeUTC" ].max ()
@@ -246,9 +256,19 @@ def _convert_to_60min_interval(rawData):
246256
247257
248258def _convert_date_to_entsoe_format (dt : datetime ):
259+ """ rounds the date to nearest hour """
249260 return dt .replace (minute = 0 , second = 0 , microsecond = 0 ).strftime ("%Y%m%d%H%M" )
250261
251262
263+ def _format_energy_data (df ):
264+ start_time_column = df .pop ("startTimeUTC" )
265+ df .insert (0 , "startTime" , start_time_column )
266+ local_timezone = datetime .now ().astimezone ().tzinfo
267+ df ["startTime" ] = pd .to_datetime (df ["startTime" ], format = "%Y%m%d%H%M" ).dt .tz_localize ("UTC" ).dt .tz_convert (local_timezone )
268+ df .insert (1 , "startTimeUTC" , start_time_column )
269+ return df
270+
271+
252272# the main methods
253273
254274
@@ -260,6 +280,7 @@ def get_actual_production_percentage(country, start, end, interval60=False) -> d
260280 :param str country: The 2 alphabet country code.
261281 :param datetime start: The start date for data retrieval. A Datetime object. Note that this date will be rounded to the nearest hour.
262282 :param datetime end: The end date for data retrieval. A datetime object. This date is also rounded to the nearest hour.
283+ :param boolean interval60: To convert the data into 60 min time interval. False by default
263284 :return: A DataFrame containing the hourly energy production mix and percentage of energy generated from renewable and non renewable sources.
264285 :return: A dictionary containing:
265286 - `error`: A string with an error message, empty if no errors.
@@ -269,12 +290,32 @@ def get_actual_production_percentage(country, start, end, interval60=False) -> d
269290 :rtype: dict
270291 """
271292 try :
293+ if not isinstance (country , str ):
294+ raise ValueError ("Invalid country" )
295+ if not isinstance (start , datetime ):
296+ raise ValueError ("Invalid start date" )
297+ if not isinstance (end , datetime ):
298+ raise ValueError ("Invalid end date" )
299+
300+ if start > datetime .now ():
301+ raise ValueError ("Invalid start date. Generation data is only available for the past and not the future. Use the forecast API instead" )
302+
303+ if start > end :
304+ raise ValueError ("Invalid date range. End date must be greater than the start date" )
305+
306+ # if end date is in the future and the start date is in the past , only data till the available moment will be returned.
307+ if end > datetime .now ():
308+ raise ValueError ("Invalid end date. Generation data is only available for the past and not the future. Use the forecast API instead" )
309+ # this is not allowed because the entsoe-py returns error if it's greater than the present
310+ #warnings.warn("End date is in the future. Will fetch data only till the present")
311+
272312 options = {
273313 "country" : country ,
274- "start" : start ,
275- "end" : end ,
314+ "start" : start . replace ( minute = 0 , second = 0 ) ,
315+ "end" : end . replace ( second = 0 , minute = 0 ) ,
276316 "interval60" : interval60 ,
277317 }
318+ # print(options)
278319 # get actual generation data per production type and convert it into 60 min interval if required
279320 totalRaw = _entsoe_get_actual_generation (options )
280321 total = totalRaw ["data" ]
@@ -327,18 +368,18 @@ def get_actual_production_percentage(country, start, end, interval60=False) -> d
327368 table [fieldName ] = table [fieldName ].astype (int )
328369
329370 return {
330- "data" : table ,
371+ "data" : _format_energy_data ( table ) ,
331372 "data_available" : True ,
332- "time_interval" : totalRaw [ " duration" ] ,
373+ "time_interval" : duration ,
333374 }
334375 except Exception as e :
335- print (e )
376+ # print(e)
336377 print (traceback .format_exc ())
337378 return {
338379 "data" : None ,
339380 "data_available" : False ,
340- "error" : Exception ,
341- "time_interval" : totalRaw [ "duration" ] ,
381+ "error" : e ,
382+ "time_interval" : 0 ,
342383 }
343384
344385
@@ -364,6 +405,13 @@ def get_forecast_percent_renewable(
364405 """
365406 try :
366407 # print(country,start,end)
408+ if not isinstance (country , str ):
409+ raise ValueError ("Invalid country" )
410+ if not isinstance (start , datetime ):
411+ raise ValueError ("Invalid start date" )
412+ if not isinstance (end , datetime ):
413+ raise ValueError ("Invalid end date" )
414+
367415 start = _convert_date_to_entsoe_format (start )
368416 end = _convert_date_to_entsoe_format (end )
369417 options = {"country" : country , "start" : start , "end" : end }
@@ -390,7 +438,7 @@ def get_forecast_percent_renewable(
390438 windsolar ["startTimeUTC" ], format = "%Y%m%d%H%M"
391439 )
392440 windsolar ["posix_timestamp" ] = windsolar ["startTimeUTC" ].astype (int ) // 10 ** 9
393- return {"data" : windsolar , "data_available" : True , "time_interval" : 60 }
441+ return {"data" : _format_energy_data ( windsolar ) , "data_available" : True , "time_interval" : 60 }
394442 except Exception as e :
395443 print (e )
396444 print (traceback .format_exc ())
0 commit comments