Coverage for aixweather/project_class.py: 88%
136 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-01-06 16:01 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2025-01-06 16:01 +0000
1"""This module contains the central project classes which are used by the user."""
3from abc import ABC, abstractmethod
4import datetime as dt
5import pandas as pd
7from aixweather.imports.DWD import (
8 import_DWD_historical,
9 import_DWD_forecast,
10 import_meta_DWD_historical,
11 import_meta_DWD_forecast,
12)
13from aixweather.imports.ERC import import_ERC, import_meta_from_ERC
14from aixweather.imports.TRY import load_try_from_file, load_try_meta_from_file
15from aixweather.imports.EPW import load_epw_from_file, load_epw_meta_from_file
16from aixweather.imports.custom_file import load_custom_meta_data, load_custom_from_file
17from aixweather.transformation_to_core_data.DWD import (
18 DWD_historical_to_core_data,
19 DWD_forecast_2_core_data,
20)
21from aixweather.transformation_to_core_data.ERC import ERC_to_core_data
22from aixweather.transformation_to_core_data.TRY import TRY_to_core_data
23from aixweather.transformation_to_core_data.EPW import EPW_to_core_data
24from aixweather.transformation_to_core_data.custom_file import custom_to_core_data
25from aixweather.core_data_format_2_output_file.unconverted_to_x import (
26 to_pickle,
27 to_json,
28 to_csv,
29)
30from aixweather.core_data_format_2_output_file.to_mos_TMY3 import to_mos
31from aixweather.core_data_format_2_output_file.to_epw_energyplus import to_epw
34# pylint-disable: R0902
35class ProjectClassGeneral(ABC):
36 """
37 An abstract base class representing a general project.
39 For each source of weather data, a project class should inherit from this class
40 and implement specific methods for data import and transformation.
42 Attributes:
43 fillna (bool): A flag indicating whether NaN values should be filled
44 in the output formats.
45 abs_result_folder_path (str): Optionally define the absolute path to
46 the desired export location.
47 start (pd.Timestamp or None): The start date of the project data
48 (sometimes inferred by the inheriting class).
49 end (pd.Timestamp or None): The end date of the project data.
51 Properties:
52 imported_data (pd.DataFrame): The imported weather data.
53 core_data (pd.DataFrame): The weather data in a standardized core
54 format.
55 output_df_<outputformat> (pd.DataFrame): The output data frame
56 (name depends on output format).
57 meta_data: Metadata associated with weather data origin.
59 Methods:
60 import_data(): An abstract method to import data from the specific
61 source.
62 data_2_core_data(): An abstract method to transform imported data into
63 core data format.
64 core_2_mos(): Convert core data to MOS format.
65 core_2_epw(): Convert core data to EPW format.
66 core_2_csv(): Convert core data to CSV format.
67 core_2_json(): Convert core data to JSON format.
68 core_2_pickle(): Convert core data to Pickle format.
69 """
71 def __init__(self, **kwargs):
72 # User-settable attributes
73 self.fillna = kwargs.get(
74 "fillna", True
75 ) # defines whether nan should be filled in the output formats
76 self.abs_result_folder_path = kwargs.get("abs_result_folder_path", None)
78 # User-settable or placeholder depending on data origin
79 self.start = kwargs.get("start", None)
80 self.end = kwargs.get("end", None)
82 # Placeholder attributes (set during execution)
83 self.imported_data: pd.DataFrame = None
84 self.core_data: pd.DataFrame = None
85 self.output_data_df: pd.DataFrame = None
86 self.meta_data = None
88 # checks too wordy with getter and setters
89 self.start_end_checks()
91 def start_end_checks(self):
92 """Make sure start and end are of type datetime and end is after start."""
93 if self.start is not None and self.end is not None:
94 if not isinstance(self.start, dt.datetime) or not isinstance(self.end, dt.datetime):
95 raise ValueError("Time period for pulling data: start and end must be of "
96 "type datetime")
97 # make sure end is after start
98 if self.end < self.start:
99 raise ValueError("Time period for pulling data: end must be after start")
101 @property
102 def imported_data(self):
103 """Get imported data"""
104 return self._imported_data
106 @imported_data.setter
107 def imported_data(self, data):
108 """If the imported data is empty, the program should be stopped with clear error
109 description."""
110 if isinstance(data, pd.DataFrame) and data.empty:
111 raise ValueError(
112 "Imported data cannot be an empty DataFrame. No weather data "
113 "has been found. Possible reasons are: "
114 "The station id is wrong or the station does not exist/measure "
115 "anymore, the data is currently not available or the weather "
116 "provider can not be accessed.\n"
117 f"The so far pulled meta data is: {self.meta_data.__dict__}"
118 )
119 self._imported_data = data
121 @property
122 def core_data(self):
123 """Get core data"""
124 return self._core_data
126 @core_data.setter
127 def core_data(self, value: pd.DataFrame):
128 """Makes sure the core-data data types are correct."""
129 if value is not None:
130 for column in value.columns:
131 # only real pd.NA values
132 # force strings to be NaN
133 value[column] = pd.to_numeric(value[column], errors="coerce")
134 # round floats for unit test compatibility across different machines
135 digits_2_round = 5
136 if value[column].dtype == "float":
137 value[column] = value[column].round(digits_2_round)
139 self._core_data = value
141 @abstractmethod
142 def import_data(self):
143 """Abstract function to import weather data."""
145 @abstractmethod
146 def data_2_core_data(self):
147 """Abstract function to convert the imported data to core data."""
149 # core_data_format_2_output_file
150 def core_2_mos(self, filename: str = None) -> str:
151 """
152 Convert core data to .mos file
154 filename (str): Name of the file to be saved. The default is constructed
155 based on the meta-data as well as start and stop time
157 Returns:
158 str: Path to the exported file.
159 """
160 self.output_data_df, filepath = to_mos(
161 self.core_data,
162 self.meta_data,
163 self.start,
164 self.end,
165 self.fillna,
166 self.abs_result_folder_path,
167 filename=filename
168 )
169 return filepath
171 def core_2_epw(self, filename: str = None) -> str:
172 """
173 Convert core data to .epw file
175 filename (str): Name of the file to be saved. The default is constructed
176 based on the meta-data as well as start and stop time
178 Returns:
179 str: Path to the exported file.
180 """
181 self.output_data_df, filepath = to_epw(
182 self.core_data,
183 self.meta_data,
184 self.start,
185 self.end,
186 self.fillna,
187 self.abs_result_folder_path,
188 filename=filename
189 )
190 return filepath
192 def core_2_csv(self, filename: str = None) -> str:
193 """
194 Convert core data to .csv file
196 filename (str): Name of the file to be saved. The default is constructed
197 based on the station name.
199 Returns:
200 str: Path to the exported file.
201 """
202 self.output_data_df, filepath = to_csv(
203 self.core_data, self.meta_data, self.abs_result_folder_path,
204 filename=filename
205 )
206 return filepath
208 def core_2_json(self, filename: str = None) -> str:
209 """
210 Convert core data to .json file
212 filename (str): Name of the file to be saved. The default is constructed
213 based on the station name.
215 Returns:
216 str: Path to the exported file.
217 """
218 self.output_data_df, filepath = to_json(
219 self.core_data, self.meta_data, self.abs_result_folder_path,
220 filename=filename
221 )
222 return filepath
224 def core_2_pickle(self, filename: str = None) -> str:
225 """
226 Convert core data pickle file
228 filename (str): Name of the file to be saved. The default is constructed
229 based on the station name.
231 Returns:
232 str: Path to the exported file.
233 """
234 self.output_data_df, filepath = to_pickle(
235 self.core_data, self.meta_data, self.abs_result_folder_path,
236 filename=filename
237 )
238 return filepath
241class ProjectClassDWDHistorical(ProjectClassGeneral):
242 """
243 A class representing a project for importing and processing historical weather data
244 from DWD (Deutscher Wetterdienst).
246 For common attributes, properties, and methods, refer to the base class.
248 Attributes:
249 station (str): The identifier of the DWD weather station associated with the data.
250 """
252 def __init__(self, start: dt.datetime, end: dt.datetime, station: str, **kwargs):
253 super().__init__(start=start, end=end, **kwargs)
254 self.station = str(station)
256 # imports
257 def import_data(self):
258 """override abstract function"""
259 self.meta_data = import_meta_DWD_historical(station=self.station)
260 self.imported_data = import_DWD_historical(
261 self.start - dt.timedelta(days=1), self.station
262 ) # pull more data for better interpolation
264 # transformation_2_core_data_DWD_Historical
265 def data_2_core_data(self):
266 """override abstract function"""
267 self.core_data = DWD_historical_to_core_data(
268 self.imported_data,
269 self.start - dt.timedelta(days=1),
270 self.end + dt.timedelta(days=1),
271 self.meta_data,
272 )
275class ProjectClassDWDForecast(ProjectClassGeneral):
276 """
277 A class representing a project for importing and processing weather forecast data
278 from DWD (Deutscher Wetterdienst).
280 For common attributes, properties, and methods, refer to the base class.
282 Attributes:
283 station (str): The identifier of the KML grid associated with the forecast data.
284 """
286 def __init__(self, station: str, **kwargs):
287 super().__init__(**kwargs)
288 self.station = str(station)
290 # imports
291 def import_data(self):
292 """override abstract function"""
293 self.meta_data = import_meta_DWD_forecast(self.station)
294 self.imported_data = import_DWD_forecast(self.station)
296 def data_2_core_data(self):
297 """override abstract function"""
298 self.core_data = DWD_forecast_2_core_data(self.imported_data, self.meta_data)
299 self.start = self.core_data.index[0]
300 self.end = self.core_data.index[-1]
303class ProjectClassERC(ProjectClassGeneral):
304 """
305 A class representing a project for importing and processing weather data
306 from the ERC (Energy Research Center).
308 For common attributes, properties, and methods, refer to the base class.
310 Attributes:
311 cred (tuple): A tuple containing credentials or authentication information for accessing
312 the data source.
313 """
315 def __init__(
316 self, start: dt.datetime, end: dt.datetime, cred: tuple = None, **kwargs
317 ):
318 super().__init__(start=start, end=end, **kwargs)
319 self.cred = cred
320 self.start_hour_earlier = start - dt.timedelta(hours=2)
321 self.end_hour_later = end + dt.timedelta(hours=2)
323 def import_data(self):
324 """override abstract function"""
325 self.meta_data = import_meta_from_ERC()
326 self.imported_data = import_ERC(
327 self.start_hour_earlier, self.end_hour_later, self.cred
328 )
330 def data_2_core_data(self):
331 """override abstract function"""
332 self.core_data = ERC_to_core_data(self.imported_data, self.meta_data)
335class ProjectClassTRY(ProjectClassGeneral):
336 """
337 A class representing a project for importing and processing weather data
338 from TRY (Test Reference Year) format.
340 For common attributes, properties, and methods, refer to the base class.
342 Attributes:
343 path (str): The absolute file path to the TRY weather data.
344 """
346 def __init__(self, path, **kwargs):
347 super().__init__(**kwargs)
348 self.path = path
350 # imports
351 def import_data(self):
352 """override abstract function"""
353 self.meta_data = load_try_meta_from_file(path=self.path)
354 self.imported_data = load_try_from_file(path=self.path)
356 # transformation_2_core_data_TRY
357 def data_2_core_data(self):
358 """override abstract function"""
359 self.core_data = TRY_to_core_data(self.imported_data, self.meta_data)
360 self.start = self.core_data.index[0]
361 self.end = self.core_data.index[-1]
364class ProjectClassEPW(ProjectClassGeneral):
365 """
366 A class representing a project for importing and processing weather data
367 from EPW (EnergyPlus Weather) format.
369 For common attributes, properties, and methods, refer to the base class.
371 Attributes:
372 path (str): The absolute file path to the EPW weather data.
373 """
375 def __init__(self, path, **kwargs):
376 super().__init__(**kwargs)
377 self.path = path
379 # imports
380 def import_data(self):
381 """override abstract function"""
382 self.meta_data = load_epw_meta_from_file(path=self.path)
383 self.imported_data = load_epw_from_file(path=self.path)
385 # transformation_2_core_data_TRY
386 def data_2_core_data(self):
387 """override abstract function"""
388 self.core_data = EPW_to_core_data(self.imported_data, self.meta_data)
389 self.start = self.core_data.index[0]
390 self.end = self.core_data.index[-1]
393class ProjectClassCustom(ProjectClassGeneral):
394 """
395 A class representing a project for importing and processing custom weather data.
396 Modify this class and its functions to create your own weather data pipeline
397 and consider to create a pull request to add the pipeline to the repository.
399 For common attributes, properties, and methods, refer to the base class.
401 Attributes:
402 path (str): The file path to the custom weather data.
403 """
405 def __init__(self, path, **kwargs):
406 super().__init__(**kwargs)
407 self.path = path
409 # imports
410 def import_data(self):
411 """override abstract function"""
412 self.meta_data = load_custom_meta_data()
413 self.imported_data = load_custom_from_file(path=self.path)
415 # transformation_2_core_data_TRY
416 def data_2_core_data(self):
417 """override abstract function"""
418 self.core_data = custom_to_core_data(self.imported_data, self.meta_data)
419 self.start = self.core_data.index[0] # or define in init
420 self.end = self.core_data.index[-1] # or define in init