ebm 0.99.3__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.
Files changed (80) hide show
  1. ebm/__init__.py +0 -0
  2. ebm/__main__.py +152 -0
  3. ebm/__version__.py +1 -0
  4. ebm/cmd/__init__.py +0 -0
  5. ebm/cmd/calibrate.py +83 -0
  6. ebm/cmd/calibrate_excel_com_io.py +128 -0
  7. ebm/cmd/heating_systems_by_year.py +18 -0
  8. ebm/cmd/helpers.py +134 -0
  9. ebm/cmd/initialize.py +167 -0
  10. ebm/cmd/migrate.py +92 -0
  11. ebm/cmd/pipeline.py +227 -0
  12. ebm/cmd/prepare_main.py +174 -0
  13. ebm/cmd/result_handler.py +272 -0
  14. ebm/cmd/run_calculation.py +221 -0
  15. ebm/data/area.csv +92 -0
  16. ebm/data/area_new_residential_buildings.csv +3 -0
  17. ebm/data/area_per_person.csv +12 -0
  18. ebm/data/building_code_parameters.csv +9 -0
  19. ebm/data/energy_need_behaviour_factor.csv +6 -0
  20. ebm/data/energy_need_improvements.csv +7 -0
  21. ebm/data/energy_need_original_condition.csv +534 -0
  22. ebm/data/heating_system_efficiencies.csv +13 -0
  23. ebm/data/heating_system_forecast.csv +9 -0
  24. ebm/data/heating_system_initial_shares.csv +1113 -0
  25. ebm/data/holiday_home_energy_consumption.csv +24 -0
  26. ebm/data/holiday_home_stock.csv +25 -0
  27. ebm/data/improvement_building_upgrade.csv +9 -0
  28. ebm/data/new_buildings_residential.csv +32 -0
  29. ebm/data/population_forecast.csv +51 -0
  30. ebm/data/s_curve.csv +40 -0
  31. ebm/energy_consumption.py +307 -0
  32. ebm/extractors.py +115 -0
  33. ebm/heating_system_forecast.py +472 -0
  34. ebm/holiday_home_energy.py +341 -0
  35. ebm/migrations.py +224 -0
  36. ebm/model/__init__.py +0 -0
  37. ebm/model/area.py +403 -0
  38. ebm/model/bema.py +149 -0
  39. ebm/model/building_category.py +150 -0
  40. ebm/model/building_condition.py +78 -0
  41. ebm/model/calibrate_energy_requirements.py +84 -0
  42. ebm/model/calibrate_heating_systems.py +180 -0
  43. ebm/model/column_operations.py +157 -0
  44. ebm/model/construction.py +827 -0
  45. ebm/model/data_classes.py +223 -0
  46. ebm/model/database_manager.py +410 -0
  47. ebm/model/dataframemodels.py +115 -0
  48. ebm/model/defaults.py +30 -0
  49. ebm/model/energy_need.py +6 -0
  50. ebm/model/energy_need_filter.py +182 -0
  51. ebm/model/energy_purpose.py +115 -0
  52. ebm/model/energy_requirement.py +353 -0
  53. ebm/model/energy_use.py +202 -0
  54. ebm/model/enums.py +8 -0
  55. ebm/model/exceptions.py +4 -0
  56. ebm/model/file_handler.py +388 -0
  57. ebm/model/filter_scurve_params.py +83 -0
  58. ebm/model/filter_tek.py +152 -0
  59. ebm/model/heat_pump.py +53 -0
  60. ebm/model/heating_systems.py +20 -0
  61. ebm/model/heating_systems_parameter.py +17 -0
  62. ebm/model/heating_systems_projection.py +3 -0
  63. ebm/model/heating_systems_share.py +28 -0
  64. ebm/model/scurve.py +224 -0
  65. ebm/model/tek.py +1 -0
  66. ebm/s_curve.py +515 -0
  67. ebm/services/__init__.py +0 -0
  68. ebm/services/calibration_writer.py +262 -0
  69. ebm/services/console.py +106 -0
  70. ebm/services/excel_loader.py +66 -0
  71. ebm/services/files.py +38 -0
  72. ebm/services/spreadsheet.py +289 -0
  73. ebm/temp_calc.py +99 -0
  74. ebm/validators.py +565 -0
  75. ebm-0.99.3.dist-info/METADATA +217 -0
  76. ebm-0.99.3.dist-info/RECORD +80 -0
  77. ebm-0.99.3.dist-info/WHEEL +5 -0
  78. ebm-0.99.3.dist-info/entry_points.txt +3 -0
  79. ebm-0.99.3.dist-info/licenses/LICENSE +21 -0
  80. ebm-0.99.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,353 @@
1
+ import typing
2
+
3
+ from loguru import logger
4
+ import numpy as np
5
+ import pandas as pd
6
+
7
+ from ebm.model.database_manager import DatabaseManager
8
+ from ebm.model.building_category import BuildingCategory
9
+ from ebm.model.building_condition import BuildingCondition
10
+ from ebm.model.data_classes import YearRange
11
+ from ebm.model.energy_purpose import EnergyPurpose
12
+ from ebm.model.file_handler import FileHandler
13
+ from ebm.services.files import make_unique_path
14
+
15
+
16
+ def yearly_reduction(x):
17
+ if x.year < x.period_start_year:
18
+ return 1.0
19
+ if x.year > x.period_end_year:
20
+ return round(1.0 - x.improvement_at_period_end, 15)
21
+ ls = np.linspace(1.0, 1.0 - x.improvement_at_period_end, int(x.period_end_year - x.period_start_year + 1.0))[
22
+ x.year_no]
23
+ return round(ls, 15) # x.year_no.astype(int)
24
+
25
+
26
+
27
+ class EnergyRequirement:
28
+ def __init__(self,
29
+ building_code_list: typing.List[str],
30
+ period: YearRange = YearRange(2010, 2050),
31
+ calibration_year: int = 1999,
32
+ database_manager = None):
33
+ self.building_code_list = building_code_list
34
+ self.period = period
35
+ self.calibration_year = calibration_year
36
+ if calibration_year == period.start:
37
+ logger.trace(f'Calibration year {calibration_year} is same as start year {period.start}')
38
+ elif calibration_year not in period.subset(1):
39
+ logger.debug(f'Calibration year {calibration_year} is outside period {period.start}-{period.end}')
40
+
41
+ self.database_manager = database_manager
42
+
43
+
44
+ def calculate_for_building_category(self, database_manager: DatabaseManager = None) -> pd.DataFrame:
45
+ """
46
+ Calculates energy requirements for a single building category
47
+
48
+ Parameters
49
+ ----------
50
+ database_manager: DatabaseManager
51
+ optional database_manager used to load input parameters
52
+
53
+ Returns
54
+ -------
55
+ Iterable of pd.Series
56
+ indexed by year, building_category, TEK, purpose, building_condition
57
+ column kwh_m2 representing energy requirement
58
+
59
+ """
60
+ database_manager = database_manager if database_manager else self.database_manager
61
+
62
+ all_building_codes = database_manager.get_building_code_list().tolist()
63
+ all_building_categories = list(BuildingCategory)
64
+ all_purpose = list(EnergyPurpose)
65
+ most_conditions = list(BuildingCondition.existing_conditions())
66
+ model_years = YearRange(2020, 2050)
67
+ erq_oc = database_manager.get_energy_req_original_condition()
68
+
69
+ merged = self.calculate_energy_requirement(all_building_categories, all_purpose, all_building_codes, erq_oc, model_years,
70
+ most_conditions, database_manager)
71
+
72
+ merged = merged.drop_duplicates('building_category,building_code,building_condition,year,purpose'.split(','), keep='first')
73
+ return merged[['building_category', 'building_code', 'building_condition','year', 'purpose',
74
+ 'original_kwh_m2', 'reduction_yearly', 'reduction_policy', 'reduction_condition',
75
+ 'reduced_kwh_m2', 'behaviour_factor', 'kwh_m2']]
76
+
77
+ def calculate_energy_requirement(self, all_building_categories, all_purpose, all_building_codes, energy_requirement_original_condition, model_years,
78
+ most_conditions, database_manager) -> pd.DataFrame:
79
+ df_bc = pd.DataFrame(all_building_categories, columns=['building_category'])
80
+ df_building_code = pd.merge(df_bc, pd.DataFrame({'building_code': all_building_codes}), how='cross')
81
+ df_purpose = pd.merge(df_building_code, pd.DataFrame(all_purpose, columns=['purpose']), how='cross')
82
+ df_condition = pd.merge(df_purpose, pd.DataFrame({'building_condition': most_conditions}), how='cross')
83
+ df_years = pd.merge(df_condition, pd.DataFrame({'year': model_years}), how='cross')
84
+
85
+ energy_requirement_original_condition = energy_requirement_original_condition.copy()
86
+
87
+ energy_requirement_original_condition = energy_requirement_original_condition.join(
88
+ pd.DataFrame({'building_condition_r': most_conditions}),
89
+ how='cross',
90
+ )
91
+ energy_requirement_original_condition['building_condition'] = energy_requirement_original_condition.building_condition_r
92
+ energy_requirement_original_condition = energy_requirement_original_condition.drop(columns=['building_condition_r'])
93
+
94
+ erq_all_years = pd.merge(left=df_years, right=energy_requirement_original_condition, how='left')
95
+ energy_requirements = erq_all_years.drop(columns=['index', 'level_0'], errors='ignore')
96
+
97
+ reduction_per_condition = database_manager.get_energy_req_reduction_per_condition()
98
+ policy_improvement = database_manager.get_energy_need_policy_improvement()
99
+ yearly_improvement = database_manager.get_energy_need_yearly_improvements()
100
+
101
+ return self.calculate_energy_reduction(energy_requirements, model_years, policy_improvement,
102
+ reduction_per_condition, yearly_improvement)
103
+
104
+ def calculate_energy_reduction(self, energy_requirements: pd.DataFrame,
105
+ model_years: YearRange,
106
+ policy_improvement: pd.DataFrame,
107
+ reduction_per_condition: pd.DataFrame,
108
+ yearly_improvement: pd.DataFrame) -> pd.DataFrame:
109
+ """
110
+ Calculate and combine all reduction factors for energy needs into a single Dataframe.
111
+
112
+ Parameters
113
+ ----------
114
+ energy_requirements : pd.DataFrame
115
+ model_years : YearRange
116
+ policy_improvement : pd.DataFrame
117
+ reduction_per_condition : pd.DataFrame
118
+ yearly_improvement : pd.DataFrame
119
+
120
+ Returns
121
+ -------
122
+ pd.DataFrame
123
+ """
124
+ reduction_condition = self.calculate_reduction_condition(reduction_per_condition)
125
+ condition_factor = pd.merge(left=energy_requirements, right=reduction_condition,
126
+ on=['building_category', 'building_code', 'building_condition', 'purpose'],
127
+ how='left')
128
+
129
+ reduction_policy = self.calculate_reduction_policy(policy_improvement, energy_requirements)
130
+ reduction_yearly = self.calculate_reduction_yearly(energy_requirements, yearly_improvement)
131
+
132
+ merged = self.merge_energy_requirement_reductions(condition_factor, reduction_yearly, reduction_policy)
133
+
134
+ return merged
135
+
136
+ def merge_energy_requirement_reductions(self, condition_factor, yearly_improvements, reduction_policy):
137
+ m_nrg_yi = pd.merge(left=condition_factor,
138
+ right=yearly_improvements.copy(),
139
+ on=['building_category', 'building_code', 'purpose', 'year'],
140
+ how='left')
141
+ m_nrg_yi = pd.merge(left=m_nrg_yi,
142
+ right=reduction_policy.copy(),
143
+ on=['building_category', 'building_code', 'purpose', 'year'],
144
+ how='left')
145
+ merged = m_nrg_yi.copy()
146
+ merged.loc[:, 'reduction_yearly'] = merged.loc[:, 'reduction_yearly'].fillna(1.0)
147
+
148
+ merged.loc[:, 'reduction_policy'] = merged.loc[:, 'reduction_policy'].fillna(1.0)
149
+ merged['reduction_condition'] = merged['reduction_condition'].fillna(1.0)
150
+ merged['reduced_kwh_m2'] = (merged['kwh_m2'] * merged['reduction_condition'].fillna(1.0) *
151
+ merged['reduction_yearly'].fillna(1.0) * merged['reduction_policy'].fillna(1.0))
152
+ merged['behavior_kwh_m2'] = merged['reduced_kwh_m2'] * merged['behaviour_factor'].fillna(1.0)
153
+ merged = merged.rename(columns={'kwh_m2': 'original_kwh_m2'})
154
+ merged['kwh_m2'] = merged['behavior_kwh_m2']
155
+ return merged
156
+
157
+ def calculate_reduction_yearly(self, df_years: pd.DataFrame, yearly_improvement: pd.DataFrame) -> pd.DataFrame:
158
+ """
159
+ Calculate factor for yearly reduction for each entry in the DataFrame yearly_improvement.
160
+
161
+ This method merges the yearly improvement data with the policy improvement data, adjusts the
162
+ efficiency start year if the period end year is greater, and calculates the yearly reduction
163
+ based on the yearly efficiency improvement.
164
+
165
+ Parameters
166
+ ----------
167
+ df_years : pd.DataFrame
168
+ DataFrame containing all years for which to calculate factors. Must include column 'year'.
169
+ yearly_improvement : pd.DataFrame
170
+ DataFrame containing yearly improvement information. Must include columns 'yearly_efficiency_improvement', and 'efficiency_start_year'.
171
+
172
+ Returns
173
+ -------
174
+ pd.DataFrame
175
+ DataFrame with the calculated 'reduction_yearly' column and updated entries.
176
+ """
177
+ required_in_yearly_improvement = {'yearly_efficiency_improvement', 'start_year', 'end_year'}
178
+ if not required_in_yearly_improvement.issubset(yearly_improvement.columns):
179
+ logger.debug(f'Got columns {", ".join(yearly_improvement.columns)}')
180
+ missing = required_in_yearly_improvement.difference(yearly_improvement.columns)
181
+ raise ValueError('Required column{} not found in yearly_improvement: {}'.format(
182
+ 's' if len(missing) != 1 else '', missing
183
+ ))
184
+ if 'year' not in df_years:
185
+ logger.debug(f'Got columns {", ".join(df_years.columns)}')
186
+ raise ValueError('df_years does not contain column year')
187
+
188
+ years = pd.DataFrame(data=[y for y in df_years.year.unique()], columns=['year'])
189
+
190
+ df = pd.merge(left=yearly_improvement, right=years, how='cross')
191
+ rows_in_range = df[(df.year >= df.start_year) & (df.year <= df.end_year)].index
192
+
193
+ df.loc[rows_in_range, 'yearly_change'] = (1.0 - df.loc[rows_in_range, 'yearly_efficiency_improvement'])
194
+ df.loc[rows_in_range, 'pow'] = (df.loc[rows_in_range, 'year'] - df.loc[rows_in_range, 'start_year']) + 1
195
+ df.loc[rows_in_range, 'reduction_yearly'] = df.loc[rows_in_range, 'yearly_change'] ** df.loc[rows_in_range, 'pow']
196
+
197
+ df.loc[df[df.start_year > df.year].index, 'reduction_yearly'] = df.loc[
198
+ df[df.start_year > df.year].index, 'reduction_yearly'].fillna(1.0)
199
+ df.loc[:, 'reduction_yearly'] = df.loc[:, 'reduction_yearly'].ffill()
200
+
201
+ return df[['building_category', 'building_code', 'purpose', 'year', 'reduction_yearly']]
202
+
203
+
204
+ def calculate_reduction_policy(self, policy_improvement: pd.DataFrame, all_things) -> pd.DataFrame:
205
+ """
206
+ Calculate the reduction policy for each entry in the DataFrame.
207
+
208
+ This method computes the reduction policy by first calculating the number of years since the
209
+ start of the period. It then applies the `yearly_reduction` function to each relevant entry
210
+ to determine the reduction policy.
211
+
212
+ Parameters
213
+ ----------
214
+ policy_improvement : pd.DataFrame
215
+ DataFrame containing policy improvement information. Must include columns 'year' and 'period_start_year'.
216
+ all_things: pd.DataFrame
217
+ DataFrame containing every combination of building_category, TEK, purpose, year
218
+
219
+ Returns
220
+ -------
221
+ pd.DataFrame
222
+ DataFrame with the calculated 'reduction_policy' column and updated entries.
223
+ """
224
+ policy_improvement = policy_improvement.sort_values(
225
+ by=['building_category', 'building_code', 'purpose', 'start_year', 'end_year'])
226
+
227
+ policy_improvement[['building_category_s', 'TEK_s', 'purpose_s', 'start_year_s', 'end_year_s']] = \
228
+ policy_improvement[
229
+ ['building_category', 'building_code', 'purpose', 'start_year', 'end_year']]
230
+
231
+ policy_improvement = policy_improvement.set_index(
232
+ ['building_category', 'building_code', 'purpose', 'start_year', 'end_year'], drop=True)
233
+
234
+ shifted = policy_improvement.shift(1).reset_index()
235
+
236
+ shifted = shifted.query('building_category==building_category_s & building_code==TEK_s & purpose==purpose_s')
237
+ shifted['improvement_at_start_year'] = shifted['improvement_at_end_year']
238
+ shifted = shifted[['building_category', 'building_code', 'purpose', 'start_year', 'end_year', 'improvement_at_start_year']]
239
+
240
+ start_year_from_previous = shifted
241
+
242
+ policy_improvement = pd.merge(left=policy_improvement,
243
+ right=start_year_from_previous,
244
+ left_on=['building_category', 'building_code', 'purpose', 'start_year', 'end_year'],
245
+ right_on=['building_category', 'building_code', 'purpose', 'start_year', 'end_year'],
246
+ how='left'
247
+ )
248
+
249
+ policy_improvement[['start_year', 'end_year']] = policy_improvement[['start_year', 'end_year']].astype(int)
250
+ policy_improvement = policy_improvement.set_index(
251
+ ['building_category', 'building_code', 'purpose', 'start_year', 'end_year'], drop=True)
252
+ policy_improvement['improvement_at_start_year'] = 1.0-policy_improvement['improvement_at_start_year'].fillna(0.0)
253
+
254
+ policy_improvement = policy_improvement[['improvement_at_start_year', 'improvement_at_end_year']].reset_index()
255
+
256
+ df = pd.merge(left=all_things[['building_category', 'building_code', 'purpose', 'year']],
257
+ right=policy_improvement,
258
+ on=['building_category', 'building_code', 'purpose'], how='left')
259
+
260
+ df['num_values'] = df['end_year'] - df['start_year'] + 1.0
261
+ df['n'] = (df.year - df.start_year).clip(upper=df.num_values-1, lower=0)
262
+
263
+ df['step'] = ((1.0-df['improvement_at_end_year']) - df['improvement_at_start_year']) / (df['num_values']-1.0)
264
+
265
+ df['reduction_policy'] = df['improvement_at_start_year'] + (df['n']) * df['step']
266
+ df['reduction_policy'] = df['reduction_policy'].fillna(1.0)
267
+ df['_col_to_filter'] = (df['year'] < df['start_year']) | (df['year'] > df['end_year'])
268
+ df = df.sort_values(by=['building_category', 'building_code', 'purpose', 'year', '_col_to_filter'])
269
+ df = df.drop_duplicates(['building_category', 'building_code', 'purpose', 'year'])
270
+
271
+ return df[['building_category', 'building_code', 'purpose', 'year', 'reduction_policy']]
272
+
273
+
274
+ def calculate_reduction_condition(self, reduction_per_condition: pd.DataFrame) -> pd.DataFrame:
275
+ """
276
+ Calculate the reduction condition for each entry in the DataFrame.
277
+
278
+ This method computes the reduction condition by subtracting the reduction share from 1.0.
279
+ It also fills any NaN values in the 'reduction_condition' column with 1.0.
280
+
281
+ Parameters
282
+ ----------
283
+ reduction_per_condition : pd.DataFrame
284
+ DataFrame containing the reduction share information. Must include columns 'reduction_share' and 'building_code'.
285
+
286
+ Returns
287
+ -------
288
+ pd.DataFrame
289
+ DataFrame with the calculated 'reduction_condition' column and filtered entries.
290
+ """
291
+ reduction_per_condition['reduction_condition'] = 1.0 - reduction_per_condition['reduction_share']
292
+ reduction_per_condition.loc[:, 'reduction_condition'] = reduction_per_condition.loc[:,
293
+ 'reduction_condition'].fillna(1.0)
294
+ return reduction_per_condition
295
+
296
+ def calculate_energy_requirements(
297
+ self,
298
+ building_categories: typing.Iterable[BuildingCategory] = None) -> pd.DataFrame:
299
+ """
300
+ Calculates energy requirements for building categories
301
+
302
+ Parameters
303
+ ----------
304
+ building_categories : Iterable[BuildingCategory]
305
+ Iterable containing building categories on which to calculate energy requirements.
306
+
307
+ Returns
308
+ -------
309
+ Iterable of pd.Series
310
+ indexed by year, building_category, TEK, purpose, building_condition
311
+ column kwh_m2 representing energy requirement
312
+
313
+ """
314
+ building_categories = building_categories if building_categories else iter(BuildingCategory)
315
+
316
+ return self.calculate_for_building_category(self.database_manager)
317
+
318
+ @staticmethod
319
+ def new_instance(period, calibration_year, database_manager=None):
320
+ if period.start != 2010 and calibration_year != calibration_year:
321
+ logger.warning(f'EnergyRequirements {period.start=} {calibration_year=}')
322
+ dm = database_manager if isinstance(database_manager, DatabaseManager) else DatabaseManager()
323
+ instance = EnergyRequirement(building_code_list=dm.get_building_code_list(), period=period, calibration_year=calibration_year,
324
+ database_manager=dm)
325
+ return instance
326
+
327
+
328
+ def main():
329
+ import os
330
+ import sys
331
+ import pathlib
332
+ log_format = """
333
+ <blue>{elapsed}</blue> | <level>{level: <8}</level> | <cyan>{function: <20}</cyan>:<cyan>{line: <3}</cyan> - <level>{message}</level>
334
+ """.strip()
335
+ logger.remove()
336
+ logger.add(sys.stderr, format=log_format, level='WARNING')
337
+
338
+ dm = DatabaseManager(FileHandler(directory='kalibrering'))
339
+ er = EnergyRequirement.new_instance(YearRange(2020, 2050), calibration_year=2020, database_manager=dm)
340
+
341
+ logger.error('Calculating')
342
+ df = er.calculate_energy_requirements()
343
+ logger.error('Writing to file')
344
+
345
+ xlsx_filename = make_unique_path(pathlib.Path('output/er.xlsx'))
346
+ df.to_excel(xlsx_filename)
347
+
348
+ logger.error('DONE')
349
+ os.startfile(xlsx_filename, 'open')
350
+
351
+
352
+ if __name__ == '__main__':
353
+ main()
@@ -0,0 +1,202 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ from ebm import extractors
5
+ from ebm.energy_consumption import HEATING_SYSTEM_SHARE, GRUNNLAST_ANDEL, BASE_LOAD_EFFICIENCY, BASE_LOAD_ENERGY_PRODUCT, \
6
+ PEAK_LOAD_ENERGY_PRODUCT, PEAK_LOAD_COVERAGE, PEAK_LOAD_EFFICIENCY, TERTIARY_LOAD_COVERAGE, TERTIARY_LOAD_EFFICIENCY, \
7
+ TERTIARY_LOAD_ENERGY_PRODUCT, COOLING_EFFICIENCY, DHW_EFFICIENCY, DOMESTIC_HOT_WATER_ENERGY_PRODUCT
8
+ from ebm.model import energy_need as e_n, heating_systems_parameter as h_s_param
9
+ from ebm.model.data_classes import YearRange
10
+ from ebm.model.database_manager import DatabaseManager
11
+ from ebm.s_curve import calculate_s_curves
12
+
13
+
14
+ def base_load(heating_systems_projection: pd.DataFrame) -> pd.DataFrame:
15
+ heating_systems_projection['heating_system'] = '-'
16
+ df = heating_systems_projection[
17
+ ['building_category', 'building_code', 'year', 'heating_systems', HEATING_SYSTEM_SHARE, GRUNNLAST_ANDEL, BASE_LOAD_EFFICIENCY,
18
+ BASE_LOAD_ENERGY_PRODUCT, 'heating_system']].copy()
19
+ df = df.rename(columns={GRUNNLAST_ANDEL: 'load_share', BASE_LOAD_EFFICIENCY: 'load_efficiency',
20
+ BASE_LOAD_ENERGY_PRODUCT: 'energy_product'})
21
+ df.loc[:, 'load'] = 'base'
22
+ df.loc[:, 'purpose'] = 'heating_rv'
23
+ df['heating_system'] = df.heating_systems.apply(lambda s: s.split('-')[0])
24
+ df['heating_system'] = df['heating_system'].str.strip()
25
+ return df
26
+
27
+
28
+ def peak_load(heating_systems_projection:pd.DataFrame) -> pd.DataFrame:
29
+ df = heating_systems_projection[
30
+ ['building_category', 'building_code', 'year', 'heating_systems', HEATING_SYSTEM_SHARE, PEAK_LOAD_COVERAGE, PEAK_LOAD_EFFICIENCY,
31
+ PEAK_LOAD_ENERGY_PRODUCT, 'heating_system']].copy()
32
+ df = df.rename(columns={PEAK_LOAD_COVERAGE: 'load_share', PEAK_LOAD_EFFICIENCY: 'load_efficiency',
33
+ PEAK_LOAD_ENERGY_PRODUCT: 'energy_product'})
34
+ df.loc[:, 'load'] = 'peak'
35
+ df.loc[:, 'purpose'] = 'heating_rv'
36
+ df['heating_system'] = df.heating_systems.apply(lambda s: s.split('-')[1:2]).explode('heating_system')
37
+ df['heating_system'] = df['heating_system'].str.strip()
38
+ return df
39
+
40
+
41
+ def tertiary_load(heating_systems_projection: pd.DataFrame) ->pd.DataFrame:
42
+ df = heating_systems_projection[
43
+ ['building_category', 'building_code', 'year', 'heating_systems', HEATING_SYSTEM_SHARE, TERTIARY_LOAD_COVERAGE, TERTIARY_LOAD_EFFICIENCY,
44
+ TERTIARY_LOAD_ENERGY_PRODUCT, 'heating_system']].copy()
45
+ df = df.rename(columns={TERTIARY_LOAD_COVERAGE: 'load_share', TERTIARY_LOAD_EFFICIENCY: 'load_efficiency',
46
+ TERTIARY_LOAD_ENERGY_PRODUCT: 'energy_product'})
47
+ df.loc[:, 'load'] = 'tertiary'
48
+ df.loc[:, 'purpose'] = 'heating_rv'
49
+ df['heating_system'] = df.heating_systems.apply(lambda s: s.split('-')[2:3]).explode('heating_system')
50
+ df['heating_system'] = df['heating_system'].str.strip()
51
+ return df
52
+
53
+
54
+ def heating_rv(heating_systems_projection: pd.DataFrame) -> pd.DataFrame:
55
+ df = heating_systems_projection.copy()
56
+
57
+ base = base_load(df)
58
+ peak = peak_load(df)
59
+ tertiary = tertiary_load(df)
60
+
61
+ return pd.concat([base, peak, tertiary])
62
+
63
+
64
+ def heating_dhw(heating_systems_projection: pd.DataFrame) ->pd.DataFrame:
65
+ df = heating_systems_projection[
66
+ ['building_category', 'building_code', 'year', 'heating_systems', HEATING_SYSTEM_SHARE, GRUNNLAST_ANDEL, DHW_EFFICIENCY,
67
+ DOMESTIC_HOT_WATER_ENERGY_PRODUCT]].copy()
68
+ df.loc[:, GRUNNLAST_ANDEL] = 1.0
69
+ df = df.rename(columns={GRUNNLAST_ANDEL: 'load_share', DHW_EFFICIENCY: 'load_efficiency',
70
+ DOMESTIC_HOT_WATER_ENERGY_PRODUCT: 'energy_product'})
71
+
72
+ df.loc[:, 'load'] = 'dhw'
73
+ df['purpose'] = 'heating_dhw'
74
+ return df
75
+
76
+
77
+ def cooling(heating_systems_projection: pd.DataFrame) -> pd.DataFrame:
78
+ df = heating_systems_projection[
79
+ ['building_category', 'building_code', 'year', 'heating_systems', HEATING_SYSTEM_SHARE, GRUNNLAST_ANDEL, COOLING_EFFICIENCY,
80
+ BASE_LOAD_ENERGY_PRODUCT]].copy()
81
+ df.loc[:, GRUNNLAST_ANDEL] = 1.0
82
+ df.loc[:, BASE_LOAD_ENERGY_PRODUCT] = 'Electricity'
83
+ df = df.rename(columns={GRUNNLAST_ANDEL: 'load_share', COOLING_EFFICIENCY: 'load_efficiency',
84
+ BASE_LOAD_ENERGY_PRODUCT: 'energy_product'})
85
+
86
+ df.loc[:, 'load'] = 'base'
87
+ df.loc[:, 'purpose'] = 'cooling'
88
+ return df
89
+
90
+
91
+ def other(heating_systems_projection: pd.DataFrame) -> pd.DataFrame:
92
+ df = heating_systems_projection[
93
+ ['building_category', 'building_code', 'year', 'heating_systems', HEATING_SYSTEM_SHARE, GRUNNLAST_ANDEL, BASE_LOAD_EFFICIENCY,
94
+ BASE_LOAD_ENERGY_PRODUCT]].copy()
95
+ df.loc[:, GRUNNLAST_ANDEL] = 1.0
96
+ df.loc[:, BASE_LOAD_EFFICIENCY] = 1.0
97
+ df.loc[:, BASE_LOAD_ENERGY_PRODUCT] = 'Electricity'
98
+ df = df.rename(columns={GRUNNLAST_ANDEL: 'load_share', BASE_LOAD_EFFICIENCY: 'load_efficiency',
99
+ BASE_LOAD_ENERGY_PRODUCT: 'energy_product'})
100
+ df.loc[:, 'load'] = 'base'
101
+ df.loc[:, '_purposes'] = 'electrical_equipment,fans_and_pumps,lighting'
102
+ df = df.assign(**{'purpose': df['_purposes'].str.split(',')}).explode('purpose')
103
+ df = df.drop(columns=['_purposes'], errors='ignore')
104
+
105
+ return df.reset_index().drop(columns=['index'], errors='ignore')
106
+
107
+
108
+ def all_purposes(heating_systems_projection: pd.DataFrame) -> pd.DataFrame:
109
+ return pd.concat([heating_rv(heating_systems_projection), heating_dhw(heating_systems_projection),
110
+ cooling(heating_systems_projection), other(heating_systems_projection)])
111
+
112
+
113
+ def efficiency_factor(heating_systems: pd.DataFrame) -> pd.DataFrame:
114
+ df = heating_systems
115
+ df.loc[:, 'efficiency_factor'] = df.loc[:, 'heating_system_share'] * df.loc[:, 'load_share'] / df.loc[:, 'load_efficiency']
116
+
117
+ return df
118
+
119
+
120
+ def energy_use_kwh(energy_need: pd.DataFrame, efficiency_factor: pd.DataFrame) -> pd.DataFrame:
121
+ nrj = energy_need.copy()
122
+
123
+ df = nrj.reset_index().merge(efficiency_factor,
124
+ left_on=['building_category', 'building_code', 'purpose', 'year'],
125
+ right_on=['building_category', 'building_code', 'purpose', 'year'])
126
+
127
+ df['kwh'] = df['energy_requirement'] * df['heating_system_share'] * df['load_share'] / df['load_efficiency']
128
+ if 'kwh_m2' in df.columns:
129
+ df['kwh_m2'] = df['kwh_m2'] * df['efficiency_factor']
130
+ else:
131
+ df['kwh_m2'] = np.nan
132
+ return df
133
+
134
+
135
+ def building_group_energy_use_kwh(heating_systems_parameter: pd.DataFrame, energy_need: pd.DataFrame) -> pd.DataFrame:
136
+ df = all_purposes(heating_systems_parameter)
137
+ df.loc[:, 'building_group'] = 'yrkesbygg'
138
+ df.loc[df.building_category.isin(['house', 'apartment_block']), 'building_group'] = 'bolig'
139
+
140
+ efficiency_factor_df = efficiency_factor(df)
141
+ df = energy_use_kwh(energy_need=energy_need, efficiency_factor=efficiency_factor_df)
142
+
143
+ return df
144
+
145
+
146
+ def energy_use_gwh_by_building_group(energy_use_kwh: pd.DataFrame) -> pd.DataFrame:
147
+ energy_use_by_building_group = energy_use_kwh[['building_group', 'year', 'energy_product', 'kwh']].groupby(
148
+ by=['building_group', 'energy_product', 'year']).sum() / 1_000_000
149
+ energy_use_wide = energy_use_by_building_group.reset_index().pivot(columns=['year'],
150
+ index=['building_group', 'energy_product'],
151
+ values=['kwh'])
152
+ energy_use_wide = energy_use_wide.reset_index()
153
+ energy_use_wide.columns = ['building_group', 'energy_source'] + [c for c in energy_use_wide.columns.get_level_values(1)[2:]]
154
+ return energy_use_wide
155
+
156
+
157
+ def calculate_energy_use(database_manager: 'DatabaseManager',
158
+ years: YearRange|None=YearRange(2020, 2050),
159
+ area_parameters:pd.DataFrame|None=None,
160
+ scurve_parameters: pd.DataFrame|None=None,
161
+ building_code_parameters:pd.DataFrame|None=None) -> pd.DataFrame:
162
+ """
163
+ calculates energy use in KWh by building_category, TEK, building_condition, year, purpose.
164
+
165
+ The dataframe is index by row index. (subject to change)
166
+
167
+ extra columns
168
+ m2, original_kwh_m2, reduction_yearly, reduction_policy, reduction_condition, reduced_kwh_m2,
169
+ behaviour_factor, kwh_m2, energy_requirement, heating_systems,
170
+ heating_system_share, load_share, load_efficiency, energy_product,
171
+ heating_system, load, building_group, efficiency_factor
172
+
173
+ Parameters
174
+ ----------
175
+ database_manager : pd.DataFrame
176
+ years : YearRange, optional
177
+ area_parameters : pd.DataFrame, optional
178
+ scurve_parameters : pd.DataFrame, optional
179
+ building_code_parameters : pd.DataFrame, optional
180
+
181
+ Returns
182
+ -------
183
+ pd.DataFrame
184
+ energy use in KWh by building_category, TEK, building_condition, year, purpose
185
+ ,
186
+ """
187
+ scurve_parameters = database_manager.get_scurve_params() if scurve_parameters is None else scurve_parameters
188
+ area_parameters = database_manager.get_area_parameters() if area_parameters is None else area_parameters
189
+ area_parameters['year'] = years.start
190
+ building_code_parameters = database_manager.file_handler.get_building_code() if building_code_parameters is None else building_code_parameters
191
+
192
+ s_curves_by_condition = calculate_s_curves(scurve_parameters, building_code_parameters, years) # 📌
193
+ area_forecast = extractors.extract_area_forecast(years, s_curves_by_condition, building_code_parameters, area_parameters, database_manager) # 📍
194
+
195
+ energy_need_kwh_m2 = extractors.extract_energy_need(years, database_manager) # 📍
196
+ total_energy_need = e_n.transform_total_energy_need(energy_need_kwh_m2, area_forecast) # 📌
197
+
198
+ heating_systems_projection = extractors.extract_heating_systems_forecast(years, database_manager) # 📍
199
+ heating_systems_parameter = h_s_param.heating_systems_parameter_from_projection(heating_systems_projection) # 📌
200
+ energy_use_kwh_with_building_group = building_group_energy_use_kwh(heating_systems_parameter, total_energy_need)
201
+
202
+ return energy_use_kwh_with_building_group
ebm/model/enums.py ADDED
@@ -0,0 +1,8 @@
1
+ from enum import Enum
2
+
3
+ class ReturnCode(Enum):
4
+ """Enum used by __main__ for return codes"""
5
+ OK = 0
6
+ FILE_EXISTS = 1
7
+ FILE_NOT_ACCESSIBLE = 2
8
+ MISSING_INPUT_FILES = 3
@@ -0,0 +1,4 @@
1
+
2
+ class AmbiguousDataError(Exception):
3
+ """Custom exception raised when conflicting data is found."""
4
+ pass