Coverage for aixweather/project_class.py: 88%
136 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-12-31 11:58 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2025-12-31 11:58 +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 in UTC
48 (sometimes inferred by the inheriting class).
49 end (pd.Timestamp or None): The end date of the project data in UTC.
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, export_in_utc: bool = False) -> 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
156 export_in_utc (bool): Timezone to be used for the export.
157 True (default) to use the core_df timezone, UTC+0,
158 False (default) to use timezone from metadata
160 Returns:
161 str: Path to the exported file.
162 """
163 self.output_data_df, filepath = to_mos(
164 core_df=self.core_data,
165 meta=self.meta_data,
166 start=self.start,
167 stop=self.end,
168 fillna=self.fillna,
169 result_folder=self.abs_result_folder_path,
170 filename=filename,
171 export_in_utc=export_in_utc
172 )
173 return filepath
175 def core_2_epw(self, filename: str = None, export_in_utc: bool = False) -> str:
176 """
177 Convert core data to .epw file
179 filename (str): Name of the file to be saved. The default is constructed
180 based on the meta-data as well as start and stop time
181 export_in_utc (bool): Timezone to be used for the export.
182 True (default) to use the core_df timezone, UTC+0,
183 False (default) to use timezone from metadata
185 Returns:
186 str: Path to the exported file.
187 """
188 self.output_data_df, filepath = to_epw(
189 self.core_data,
190 self.meta_data,
191 self.start,
192 self.end,
193 self.fillna,
194 self.abs_result_folder_path,
195 filename=filename,
196 export_in_utc=export_in_utc
197 )
198 return filepath
200 def core_2_csv(self, filename: str = None) -> str:
201 """
202 Convert core data to .csv file
204 filename (str): Name of the file to be saved. The default is constructed
205 based on the station name.
207 Returns:
208 str: Path to the exported file.
209 """
210 self.output_data_df, filepath = to_csv(
211 self.core_data, self.meta_data, self.abs_result_folder_path,
212 filename=filename
213 )
214 return filepath
216 def core_2_json(self, filename: str = None) -> str:
217 """
218 Convert core data to .json file
220 filename (str): Name of the file to be saved. The default is constructed
221 based on the station name.
223 Returns:
224 str: Path to the exported file.
225 """
226 self.output_data_df, filepath = to_json(
227 self.core_data, self.meta_data, self.abs_result_folder_path,
228 filename=filename
229 )
230 return filepath
232 def core_2_pickle(self, filename: str = None) -> str:
233 """
234 Convert core data pickle file
236 filename (str): Name of the file to be saved. The default is constructed
237 based on the station name.
239 Returns:
240 str: Path to the exported file.
241 """
242 self.output_data_df, filepath = to_pickle(
243 self.core_data, self.meta_data, self.abs_result_folder_path,
244 filename=filename
245 )
246 return filepath
249class ProjectClassDWDHistorical(ProjectClassGeneral):
250 """
251 A class representing a project for importing and processing historical weather data
252 from DWD (Deutscher Wetterdienst).
254 For common attributes, properties, and methods, refer to the base class.
256 Attributes:
257 station (str): The identifier of the DWD weather station associated with the data.
258 """
260 def __init__(self, start: dt.datetime, end: dt.datetime, station: str, **kwargs):
261 super().__init__(start=start, end=end, **kwargs)
262 self.station = str(station)
264 # imports
265 def import_data(self):
266 """override abstract function"""
267 self.meta_data = import_meta_DWD_historical(station=self.station)
268 self.imported_data = import_DWD_historical(
269 self.start - dt.timedelta(days=1), self.station
270 ) # pull more data for better interpolation
272 # transformation_2_core_data_DWD_Historical
273 def data_2_core_data(self):
274 """override abstract function"""
275 self.core_data = DWD_historical_to_core_data(
276 self.imported_data,
277 self.start - dt.timedelta(days=1),
278 self.end + dt.timedelta(days=1),
279 self.meta_data,
280 )
283class ProjectClassDWDForecast(ProjectClassGeneral):
284 """
285 A class representing a project for importing and processing weather forecast data
286 from DWD (Deutscher Wetterdienst).
288 For common attributes, properties, and methods, refer to the base class.
290 Attributes:
291 station (str): The identifier of the KML grid associated with the forecast data.
292 """
294 def __init__(self, station: str, **kwargs):
295 super().__init__(**kwargs)
296 self.station = str(station)
298 # imports
299 def import_data(self):
300 """override abstract function"""
301 self.meta_data = import_meta_DWD_forecast(self.station)
302 self.imported_data = import_DWD_forecast(self.station)
304 def data_2_core_data(self):
305 """override abstract function"""
306 self.core_data = DWD_forecast_2_core_data(self.imported_data, self.meta_data)
307 self.start = self.core_data.index[0]
308 self.end = self.core_data.index[-1]
311class ProjectClassERC(ProjectClassGeneral):
312 """
313 A class representing a project for importing and processing weather data
314 from the ERC (Energy Research Center).
316 For common attributes, properties, and methods, refer to the base class.
318 Attributes:
319 cred (tuple): A tuple containing credentials or authentication information for accessing
320 the data source.
321 """
323 def __init__(
324 self, start: dt.datetime, end: dt.datetime, cred: tuple = None, **kwargs
325 ):
326 super().__init__(start=start, end=end, **kwargs)
327 self.cred = cred
328 self.start_hour_earlier = start - dt.timedelta(hours=2)
329 self.end_hour_later = end + dt.timedelta(hours=2)
331 def import_data(self):
332 """override abstract function"""
333 self.meta_data = import_meta_from_ERC()
334 self.imported_data = import_ERC(
335 self.start_hour_earlier, self.end_hour_later, self.cred
336 )
338 def data_2_core_data(self):
339 """override abstract function"""
340 self.core_data = ERC_to_core_data(self.imported_data, self.meta_data)
343class ProjectClassTRY(ProjectClassGeneral):
344 """
345 A class representing a project for importing and processing weather data
346 from TRY (Test Reference Year) format.
348 For common attributes, properties, and methods, refer to the base class.
350 Attributes:
351 path (str): The absolute file path to the TRY weather data.
352 """
354 def __init__(self, path, **kwargs):
355 super().__init__(**kwargs)
356 self.path = path
358 # imports
359 def import_data(self):
360 """override abstract function"""
361 self.meta_data = load_try_meta_from_file(path=self.path)
362 self.imported_data = load_try_from_file(path=self.path)
364 # transformation_2_core_data_TRY
365 def data_2_core_data(self):
366 """override abstract function"""
367 self.core_data = TRY_to_core_data(self.imported_data, self.meta_data)
368 self.start = self.core_data.index[0]
369 self.end = self.core_data.index[-1]
372class ProjectClassEPW(ProjectClassGeneral):
373 """
374 A class representing a project for importing and processing weather data
375 from EPW (EnergyPlus Weather) format.
377 For common attributes, properties, and methods, refer to the base class.
379 Attributes:
380 path (str): The absolute file path to the EPW weather data.
381 """
383 def __init__(self, path, **kwargs):
384 super().__init__(**kwargs)
385 self.path = path
387 # imports
388 def import_data(self):
389 """override abstract function"""
390 self.meta_data = load_epw_meta_from_file(path=self.path)
391 self.imported_data = load_epw_from_file(path=self.path)
393 # transformation_2_core_data_TRY
394 def data_2_core_data(self):
395 """override abstract function"""
396 self.core_data = EPW_to_core_data(self.imported_data, self.meta_data)
397 self.start = self.core_data.index[0]
398 self.end = self.core_data.index[-1]
401class ProjectClassCustom(ProjectClassGeneral):
402 """
403 A class representing a project for importing and processing custom weather data.
404 Modify this class and its functions to create your own weather data pipeline
405 and consider to create a pull request to add the pipeline to the repository.
407 For common attributes, properties, and methods, refer to the base class.
409 Attributes:
410 path (str): The file path to the custom weather data.
411 """
413 def __init__(self, path, **kwargs):
414 super().__init__(**kwargs)
415 self.path = path
417 # imports
418 def import_data(self):
419 """override abstract function"""
420 self.meta_data = load_custom_meta_data()
421 self.imported_data = load_custom_from_file(path=self.path)
423 # transformation_2_core_data_TRY
424 def data_2_core_data(self):
425 """override abstract function"""
426 self.core_data = custom_to_core_data(self.imported_data, self.meta_data)
427 self.start = self.core_data.index[0] # or define in init
428 self.end = self.core_data.index[-1] # or define in init