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
ebm/model/area.py ADDED
@@ -0,0 +1,403 @@
1
+ from loguru import logger
2
+ import pandas as pd
3
+
4
+ from ebm.model.building_condition import BuildingCondition
5
+ from ebm.model.data_classes import YearRange
6
+ from ebm.model.database_manager import DatabaseManager
7
+ from ebm.model.scurve import SCurve
8
+ from ebm.model.construction import ConstructionCalculator
9
+
10
+
11
+ def transform_area_forecast_to_area_change(area_forecast: pd.DataFrame,
12
+ building_code_parameters: pd.DataFrame | None = None) -> pd.DataFrame:
13
+ """
14
+ Transform area forecast data into yearly area changes due to construction and demolition.
15
+
16
+ This function processes forecasted area data and optional building_code parameters to compute
17
+ the net yearly area change. It distinguishes between construction (positive area change)
18
+ and demolition (negative area change), and returns a combined DataFrame.
19
+
20
+ Parameters
21
+ ----------
22
+ area_forecast : pandas.DataFrame
23
+ A DataFrame containing forecasted building area data, including construction and demolition.
24
+
25
+ building_code_parameters : pandas.DataFrame, optional
26
+ A DataFrame containing building_code-related parameters used to refine construction data.
27
+ If None, construction is assumed to be of TEK17. (transform_construction_by_year)
28
+
29
+ Returns
30
+ -------
31
+ pandas.DataFrame
32
+ A DataFrame with yearly area changes. Columns include:
33
+ - 'building_category': Category of the building.
34
+ - 'building_code': building_code classification.
35
+ - 'year': Year of the area change.
36
+ - 'demolition_construction': Indicates whether the change is due to 'construction' or 'demolition'.
37
+ - 'm2': Area change in square meters (positive for construction, negative for demolition).
38
+
39
+ Notes
40
+ -----
41
+ - Demolition areas are negated to represent area loss.
42
+ - Missing values are filled with 0.0.
43
+ - Assumes helper functions `transform_construction_by_year` and
44
+ `transform_cumulative_demolition_to_yearly_demolition` are defined elsewhere.
45
+ """
46
+ construction_by_year = transform_construction_by_year(area_forecast, building_code_parameters)
47
+ construction_by_year.loc[:, 'demolition_construction'] = 'construction'
48
+
49
+ demolition_by_year = transform_cumulative_demolition_to_yearly_demolition(area_forecast)
50
+ demolition_by_year.loc[:, 'demolition_construction'] = 'demolition'
51
+ demolition_by_year.loc[:, 'm2'] = -demolition_by_year.loc[:, 'm2']
52
+
53
+ area_change = pd.concat([
54
+ demolition_by_year[['building_category', 'building_code', 'year', 'demolition_construction', 'm2']],
55
+ construction_by_year.reset_index()[['building_category', 'building_code', 'year', 'demolition_construction', 'm2']]
56
+ ])
57
+ return area_change.fillna(0.0)
58
+
59
+
60
+ def transform_cumulative_demolition_to_yearly_demolition(area_forecast: pd.DataFrame) -> pd.DataFrame:
61
+ """
62
+ Convert accumulated demolition area data to yearly demolition values.
63
+
64
+ This function filters the input DataFrame for rows where the building condition is demolition,
65
+ and calculates the yearly change in square meters (m2) by computing the difference between
66
+ consecutive years within each group defined by building category and building_code standard.
67
+
68
+ Parameters
69
+ ----------
70
+ area_forecast : pandas.DataFrame
71
+ A DataFrame containing forecasted building area data. Must include the columns:
72
+ 'building_category', 'building_code', 'year', 'building_condition', and 'm2'.
73
+
74
+ Returns
75
+ -------
76
+ pandas.DataFrame
77
+ A DataFrame with columns ['building_category', 'building_code', 'year', 'm2'], where 'm2' represents
78
+ the yearly demolition area (difference from the previous year). Missing values are filled with 0.
79
+
80
+ Notes
81
+ -----
82
+ - The function assumes that the input data is cumulative and sorted by year.
83
+ - The first year in each group will have a demolition value of 0.
84
+ """
85
+ if area_forecast is None:
86
+ raise ValueError('Expected area_forecast of type pandas DataFrame. Got «None» instead.')
87
+ expected_columns = ('building_category', 'building_code', 'building_condition', 'year', 'm2')
88
+ missing_columns = [c for c in expected_columns if c not in area_forecast.columns]
89
+ if missing_columns:
90
+ raise ValueError(f'Column {", ".join(missing_columns)} not found in area_forecast')
91
+
92
+ df = area_forecast[area_forecast['building_condition'] == BuildingCondition.DEMOLITION].copy()
93
+ df = df.set_index(['building_category', 'building_code', 'building_condition', 'year']).sort_index()
94
+ df['m2'] = df['m2'].fillna(0)
95
+ df['diff'] = df.groupby(by=['building_category', 'building_code', 'building_condition']).diff()['m2']
96
+
97
+ return df.reset_index()[['building_category', 'building_code', 'year', 'diff']].rename(columns={'diff': 'm2'})
98
+
99
+
100
+ def transform_construction_by_year(area_forecast: pd.DataFrame,
101
+ building_code_parameters: pd.DataFrame | None = None) -> pd.DataFrame:
102
+ """
103
+ Calculate yearly constructed building area based on building_code parameters.
104
+
105
+ This function filters the input forecast data to include only construction (non-demolition)
106
+ within the building_code-defined construction period. It then calculates the yearly change in constructed
107
+ area (m2) for each combination of building category and building_code standard.
108
+
109
+ Parameters
110
+ ----------
111
+ area_forecast : pandas.DataFrame
112
+ A DataFrame containing forecasted building area data. Must include the columns:
113
+ 'building_category', 'building_code', 'year', 'building_condition', and 'm2'.
114
+
115
+ building_code_parameters : pandas.DataFrame or None, optional
116
+ A DataFrame containing building_code construction period definitions with columns:
117
+ ['building_code', 'building_year', 'period_start_year', 'period_end_year'].
118
+ If None, a default TEK17 period is used (2020–2050 with building year 2025).
119
+
120
+ Returns
121
+ -------
122
+ pandas.DataFrame
123
+ A DataFrame with columns ['building_category', 'building_code', 'year', 'm2'], where 'm2' represents
124
+ the yearly constructed area in square meters.
125
+
126
+ Notes
127
+ -----
128
+ - The function assumes that the input data is cumulative and calculates the difference
129
+ between consecutive years to derive yearly values.
130
+ - Construction is defined as all building conditions except 'demolition'.
131
+ - If no building_codeparameters are provided, a default TEK17 range is used.
132
+ """
133
+ if area_forecast is None:
134
+ raise ValueError('Expected area_forecast of type pandas DataFrame. Got «None» instead.')
135
+
136
+ expected_columns = ('building_category', 'building_code', 'building_condition', 'year', 'm2')
137
+ missing_columns = [c for c in expected_columns if c not in area_forecast.columns]
138
+ if missing_columns:
139
+ raise ValueError(f'Column {", ".join(missing_columns)} not found in area_forecast')
140
+
141
+ building_code_params = building_code_parameters
142
+ if building_code_params is None:
143
+ building_code_params = pd.DataFrame(
144
+ data=[['TEK17', 2025, 2020, 2050]],
145
+ columns=['building_code', 'building_year', 'period_start_year', 'period_end_year'])
146
+ logger.warning('Using default TEK17 for construction')
147
+
148
+ expected_columns = ('building_code', 'building_year', 'period_start_year', 'period_end_year')
149
+ missing_columns = [c for c in expected_columns if c not in building_code_params.columns]
150
+ if missing_columns:
151
+ raise ValueError(f'Column {", ".join(missing_columns)} not found in building_code_parameters')
152
+
153
+ area_forecast = area_forecast.merge(building_code_params, on='building_code', how='left')
154
+ constructed = area_forecast.query(
155
+ 'period_end_year >= year and building_condition!="demolition"').copy()
156
+ constructed = constructed[['building_category', 'building_code', 'year', 'building_condition', 'm2']]
157
+ constructed = constructed.set_index(['building_category', 'building_code', 'year', 'building_condition'])[['m2']].unstack()
158
+
159
+ constructed['total'] = constructed.sum(axis=1)
160
+
161
+ constructed=constructed.groupby(by=['building_category', 'building_code'], as_index=False).diff()
162
+ constructed = constructed.reset_index()[['building_category', 'building_code', 'year', 'total']]
163
+ constructed.columns = ['building_category', 'building_code', 'year', 'm2']
164
+ return constructed
165
+
166
+
167
+ def transform_demolition_construction(energy_use: pd.DataFrame, area_change: pd.DataFrame) -> pd.DataFrame:
168
+ """
169
+ Calculate energy use in GWh for construction and demolition activities based on area changes.
170
+
171
+ This function filters energy use data for renovation and small measures, aggregates it by
172
+ building category, TEK, and year, and merges it with area change data to compute the
173
+ total energy use in GWh.
174
+
175
+ Parameters
176
+ ----------
177
+ energy_use : pandas.DataFrame
178
+ A DataFrame containing energy use data, including columns:
179
+ - 'building_category'
180
+ - 'building_condition'
181
+ - 'building_code'
182
+ - 'year'
183
+ - 'kwh_m2'
184
+
185
+ area_change : pandas.DataFrame
186
+ A DataFrame containing area changes due to construction and demolition, including columns:
187
+ - 'building_category'
188
+ - 'building_code'
189
+ - 'year'
190
+ - 'demolition_construction'
191
+ - 'm2'
192
+
193
+ Returns
194
+ -------
195
+ pandas.DataFrame
196
+ A DataFrame with the following columns:
197
+ - 'year': Year of the activity.
198
+ - 'demolition_construction': Indicates whether the activity is 'construction' or 'demolition'.
199
+ - 'building_category': Category of the building.
200
+ - 'building_code': building_codeclassification.
201
+ - 'm2': Area change in square meters.
202
+ - 'gwh': Energy use in gigawatt-hours (GWh), calculated as (kWh/m² * m²) / 1,000,000.
203
+
204
+ Notes
205
+ -----
206
+ - Only energy use data with 'building_condition' equal to 'renovation_and_small_measure' is considered.
207
+ - The merge is performed on 'building_category', 'building_code', and 'year'.
208
+ """
209
+
210
+ df = energy_use[energy_use['building_condition']=='renovation_and_small_measure']
211
+
212
+ energy_use_m2 = df.groupby(by=['building_category', 'building_condition', 'building_code', 'year'], as_index=False).sum()[['building_category', 'building_code', 'year', 'kwh_m2']]
213
+
214
+ dem_con = pd.merge(left=area_change, right=energy_use_m2, on=['building_category', 'building_code', 'year'])
215
+ dem_con['gwh'] = (dem_con['kwh_m2'] * dem_con['m2']) / 1_000_000
216
+ return dem_con[['year', 'demolition_construction', 'building_category', 'building_code', 'm2', 'gwh']]
217
+
218
+
219
+ def merge_building_code_and_condition(area_forecast: pd.DataFrame) -> pd.DataFrame:
220
+ """
221
+ Add general building_codeand building condition categories to area forecast data.
222
+
223
+ This function creates a copy of the input DataFrame and assigns the value 'all' to both
224
+ the 'building_code' and 'building_condition' columns. This is useful for aggregating or analyzing
225
+ data across all building_codetypes and building conditions.
226
+
227
+ Parameters
228
+ ----------
229
+ area_forecast : pandas.DataFrame
230
+ A DataFrame containing forecasted building area data, including at least the columns
231
+ 'building_code' and 'building_condition'.
232
+
233
+ Returns
234
+ -------
235
+ pandas.DataFrame
236
+ A modified copy of the input DataFrame where:
237
+ - 'building_code' is set to 'all'
238
+ - 'building_condition' is set to 'all'
239
+
240
+ Notes
241
+ -----
242
+ - This function does not modify the original DataFrame in place.
243
+ - Useful for creating aggregate views across all building_codeand condition categories.
244
+ """
245
+
246
+ all_existing_area = area_forecast.copy()
247
+ all_existing_area.loc[:, 'building_code'] = 'all'
248
+ all_existing_area.loc[:, 'building_condition'] = 'all'
249
+
250
+ return all_existing_area
251
+
252
+
253
+ def filter_existing_area(area_forecast: pd.DataFrame) -> pd.DataFrame:
254
+ """
255
+ Filter out demolition entries from area forecast data to retain only existing areas.
256
+
257
+ This function removes rows where the building condition is 'demolition' and returns
258
+ a DataFrame containing only the relevant columns for existing building areas.
259
+
260
+ Parameters
261
+ ----------
262
+ area_forecast : pandas.DataFrame
263
+ A DataFrame containing forecasted building area data, including at least the columns:
264
+ - 'year'
265
+ - 'building_category'
266
+ - 'building_code'
267
+ - 'building_condition'
268
+ - 'm2'
269
+
270
+ Returns
271
+ -------
272
+ pandas.DataFrame
273
+ A filtered DataFrame containing only rows where 'building_condition' is not 'demolition',
274
+ with the following columns:
275
+ - 'year'
276
+ - 'building_category'
277
+ - 'building_code'
278
+ - 'building_condition'
279
+ - 'm2'
280
+
281
+ Notes
282
+ -----
283
+ - The function returns a copy of the filtered DataFrame to avoid modifying the original.
284
+ - Useful for isolating existing building stock from forecast data.
285
+ """
286
+
287
+ existing_area = area_forecast.query('building_condition!="demolition"').copy()
288
+ existing_area = existing_area[['year','building_category','building_code','building_condition']+['m2']]
289
+ return existing_area
290
+
291
+
292
+ def building_condition_scurves(scurve_parameters: pd.DataFrame) -> pd.DataFrame:
293
+ scurves = []
294
+
295
+ for r, v in scurve_parameters.iterrows():
296
+ scurve = SCurve(earliest_age=v.earliest_age_for_measure,
297
+ average_age=v.average_age_for_measure,
298
+ last_age=v.last_age_for_measure,
299
+ rush_years=v.rush_period_years,
300
+ never_share=v.never_share,
301
+ rush_share=v.rush_share)
302
+
303
+ rate = scurve.get_rates_per_year_over_building_lifetime().to_frame().reset_index()
304
+ rate.loc[:, 'building_category'] = v['building_category']
305
+ rate.loc[:, 'building_condition'] = v['condition']
306
+
307
+ scurves.append(rate)
308
+
309
+
310
+ df = pd.concat(scurves).set_index(['building_category', 'age', 'building_condition'])
311
+
312
+ return df
313
+
314
+
315
+ def building_condition_accumulated_scurves(scurve_parameters: pd.DataFrame) -> pd.DataFrame:
316
+ scurves = []
317
+
318
+ for r, v in scurve_parameters.iterrows():
319
+ scurve = SCurve(earliest_age=v.earliest_age_for_measure,
320
+ average_age=v.average_age_for_measure,
321
+ last_age=v.last_age_for_measure,
322
+ rush_years=v.rush_period_years,
323
+ never_share=v.never_share,
324
+ rush_share=v.rush_share)
325
+
326
+ acc = scurve.calc_scurve().to_frame().reset_index()
327
+ acc.loc[:, 'building_category'] = v['building_category']
328
+ acc.loc[:, 'building_condition'] = v['condition'] + '_acc'
329
+
330
+ scurves.append(acc)
331
+
332
+ df = pd.concat(scurves).set_index(['building_category', 'age', 'building_condition'])
333
+
334
+ return df
335
+
336
+
337
+ def multiply_s_curves_with_floor_area(s_curves_by_condition, with_area):
338
+ floor_area_by_condition = s_curves_by_condition.multiply(with_area['area'], axis=0)
339
+ floor_area_forecast = floor_area_by_condition.stack().reset_index()
340
+ floor_area_forecast = floor_area_forecast.rename(columns={'level_3': 'building_condition', 0: 'm2'}) # m²
341
+ return floor_area_forecast
342
+
343
+
344
+ def merge_total_area_by_year(construction_by_building_category_yearly, existing_area):
345
+ total_area_by_year = pd.concat([existing_area.drop(columns=['year_r'], errors='ignore'),
346
+ construction_by_building_category_yearly])
347
+ return total_area_by_year
348
+
349
+
350
+ def calculate_existing_area(area_parameters, building_code_parameters, years):
351
+ # ## Make area
352
+ # Define the range of years
353
+ index = pd.MultiIndex.from_product(
354
+ [area_parameters.index.get_level_values(level='building_category').unique(), building_code_parameters.building_code.unique(),
355
+ years],
356
+ names=['building_category', 'building_code', 'year'])
357
+ # Reindex the DataFrame to include all combinations, filling missing values with NaN
358
+ area = index.to_frame().set_index(['building_category', 'building_code', 'year']).reindex(index).reset_index()
359
+ # Optional: Fill missing values with a default, e.g., 0
360
+ existing_area = pd.merge(left=area_parameters, right=area, on=['building_category', 'building_code'], suffixes=['_r', ''])
361
+ existing_area = existing_area.set_index(['building_category', 'building_code', 'year'])
362
+ return existing_area
363
+
364
+
365
+ def construction_with_building_code(building_category_demolition_by_year: pd.Series,
366
+ building_code: pd.DataFrame,
367
+ construction_floor_area_by_year: pd.Series,
368
+ years:YearRange) -> pd.Series:
369
+ if not years:
370
+ years = YearRange.from_series(building_category_demolition_by_year)
371
+
372
+ building_code_years = years.cross_join(building_code)
373
+
374
+ filtered_building_code_years = building_code_years.query(f'period_end_year>={years.start}')
375
+
376
+ construction_with_building_code = pd.merge(
377
+ left=construction_floor_area_by_year,
378
+ right=filtered_building_code_years[['year', 'building_code', 'period_start_year', 'period_end_year']],
379
+ left_on=['year'], right_on=['year'])
380
+
381
+ query_period_start_end = 'period_start_year > year or period_end_year < year'
382
+ construction_with_building_code.loc[
383
+ construction_with_building_code.query(query_period_start_end).index, 'constructed_floor_area'] = 0.0
384
+
385
+ df = construction_with_building_code.set_index(['building_category', 'building_code', 'year'])[['constructed_floor_area']]
386
+
387
+ s = df.groupby(by=['building_category', 'building_code'])['constructed_floor_area'].cumsum()
388
+ s.name = 'area'
389
+
390
+ return s
391
+
392
+
393
+ def sum_building_category_demolition_by_year(demolition_by_year):
394
+ demolition_by_building_category_year = demolition_by_year.groupby(by=['building_category', 'year']).sum()
395
+ return demolition_by_building_category_year
396
+
397
+
398
+ def calculate_demolition_floor_area_by_year(
399
+ area_parameters: pd.DataFrame, s_curve_cumulative_demolition: pd.Series) -> pd.Series:
400
+ demolition_by_year = area_parameters.loc[:, 'area'] * s_curve_cumulative_demolition.loc[:]
401
+ demolition_by_year.name = 'demolition'
402
+ demolition_by_year = demolition_by_year.to_frame().loc[(slice(None), slice(None), slice(2020, 2050))]
403
+ return demolition_by_year.demolition
ebm/model/bema.py ADDED
@@ -0,0 +1,149 @@
1
+ from types import MappingProxyType
2
+
3
+ from ebm.model.building_category import BuildingCategory, RESIDENTIAL, NON_RESIDENTIAL
4
+ from ebm.model.building_condition import BuildingCondition
5
+
6
+ _building_category_order = {
7
+ BuildingCategory.HOUSE: 101, BuildingCategory.APARTMENT_BLOCK: 102, RESIDENTIAL: 199,
8
+ 'holiday_home': 299,
9
+ BuildingCategory.RETAIL: 301, BuildingCategory.OFFICE: 302, BuildingCategory.KINDERGARTEN: 303, BuildingCategory.SCHOOL: 304,
10
+ BuildingCategory.UNIVERSITY: 305, BuildingCategory.HOSPITAL: 306, BuildingCategory.NURSING_HOME: 307,
11
+ BuildingCategory.HOTEL: 308, BuildingCategory.SPORTS: 309, BuildingCategory.CULTURE: 310,
12
+ BuildingCategory.STORAGE: 312, 'storage': 311, NON_RESIDENTIAL: 399}
13
+
14
+ BUILDING_CATEGORY_ORDER = MappingProxyType(_building_category_order)
15
+ """An immutable dict of BeMa sorting order for building_category"""
16
+
17
+ _building_group_order = {'residential': 199, 'holiday_home': 299, 'non_residential': 399}
18
+
19
+ BUILDING_GROUP_ORDER = MappingProxyType(_building_group_order)
20
+ """An immutable dict of BeMa sorting order for building_group"""
21
+
22
+
23
+ _building_mix_order = _building_group_order| _building_category_order
24
+
25
+
26
+ _building_code_order = {'PRE_TEK49': 1814, 'TEK49': 1949, 'TEK69': 1969, 'TEK87': 1987, 'TEK97': 1997,
27
+ 'TEK07': 2007, 'TEK10': 2010, 'TEK17': 2017,
28
+ 'TEK21': 2021, 'default': 9998, 'all': 9999}
29
+
30
+ TEK_ORDER = MappingProxyType(_building_code_order)
31
+ """A dict of BeMa sorting order for TEK"""
32
+
33
+ _purpose_order = {'heating_rv': 1, 'heating_dhw': 2, 'fans_and_pumps': 3, 'lighting': 4,
34
+ 'electrical_equipment': 5, 'cooling': 6}
35
+
36
+ PURPOSE_ORDER = MappingProxyType(_purpose_order)
37
+ """A dict of BeMa sorting order for purpose"""
38
+
39
+ _building_condition_order = {BuildingCondition.ORIGINAL_CONDITION: 1, BuildingCondition.SMALL_MEASURE: 2,
40
+ BuildingCondition.RENOVATION: 3, BuildingCondition.RENOVATION_AND_SMALL_MEASURE: 4, BuildingCondition.DEMOLITION: 5}
41
+
42
+ BUILDING_CONDITION_ORDER = MappingProxyType(_building_condition_order)
43
+ """A dict of BeMa sorting order for building_condition"""
44
+
45
+ _start_row_building_category_construction = {BuildingCategory.HOUSE: 11, BuildingCategory.APARTMENT_BLOCK: 23,
46
+ BuildingCategory.KINDERGARTEN: 41, BuildingCategory.SCHOOL: 55, BuildingCategory.UNIVERSITY: 69,
47
+ BuildingCategory.OFFICE: 83, BuildingCategory.RETAIL: 97, BuildingCategory.HOTEL: 111,
48
+ BuildingCategory.HOSPITAL: 125, BuildingCategory.NURSING_HOME: 139, BuildingCategory.CULTURE: 153,
49
+ BuildingCategory.SPORTS: 167, BuildingCategory.STORAGE: 182}
50
+
51
+ START_ROWS_CONSTRUCTION_BUILDING_CATEGORY = MappingProxyType(_start_row_building_category_construction)
52
+ """A dict of BeMa sorting order for start row of each building category in the sheet `nybygging`"""
53
+
54
+
55
+ def get_building_category_sheet(building_category: BuildingCategory, area_sheet: bool = True) -> str:
56
+ """
57
+ Returns the appropriate sheet name based on the building category and area sheet type.
58
+
59
+ Parameters
60
+ ----------
61
+ - building_category: An instance of BuildingCategory.
62
+ - area_sheet (bool): Determines whether to return the area sheet ('A') or rates sheet ('R') name. Defaults to True for the area sheet.
63
+
64
+ Returns
65
+ -------
66
+ - sheet (str): The sheet name corresponding to the building category and sheet type.
67
+ """
68
+ building_category_sheets = {BuildingCategory.HOUSE: ['A hus', 'R hus'],
69
+ BuildingCategory.APARTMENT_BLOCK: ['A leil', 'R leil'], BuildingCategory.KINDERGARTEN: ['A bhg', 'R bhg'],
70
+ BuildingCategory.SCHOOL: ['A skole', 'R skole'], BuildingCategory.UNIVERSITY: ['A uni', 'R uni'],
71
+ BuildingCategory.OFFICE: ['A kont', 'R kont'], BuildingCategory.RETAIL: ['A forr', 'R forr'],
72
+ BuildingCategory.HOTEL: ['A hotell', 'R hotell'], BuildingCategory.HOSPITAL: ['A shus', 'R shus'],
73
+ BuildingCategory.NURSING_HOME: ['A shjem', 'R shjem'], BuildingCategory.CULTURE: ['A kult', 'R kult'],
74
+ BuildingCategory.SPORTS: ['A idr', 'R idr'], BuildingCategory.STORAGE: ['A ind', 'R ind']}
75
+
76
+ if area_sheet:
77
+ sheet = building_category_sheets[building_category][0]
78
+ else:
79
+ sheet = building_category_sheets[building_category][1]
80
+
81
+ return sheet
82
+
83
+
84
+ # noinspection PyTypeChecker
85
+ def map_sort_order(column):
86
+ """
87
+ Map the sort order from bema to a DataFrame. The function is meant to be used as the key parameter for
88
+ a pandas DataFrame methods sort_values and sort_index.
89
+
90
+ Example below.
91
+
92
+ Parameters
93
+ ----------
94
+ column : pandas.Series
95
+ A pandas Series whose `name` attribute determines which predefined
96
+ mapping to apply to its values.
97
+
98
+ Returns
99
+ -------
100
+ pandas.Series
101
+ A Series with values mapped to integers according to the corresponding
102
+ sort order. If the column name does not match any predefined mapping,
103
+ the original Series is returned unchanged.
104
+
105
+ Notes
106
+ -----
107
+ The function supports the following mappings:
108
+
109
+ - 'building_category': uses `_building_mix_order`
110
+ - 'building_group': uses `BUILDING_GROUP_ORDER`
111
+ - 'building_condition': uses `BUILDING_CONDITION_ORDER`
112
+ - 'purpose': uses `PURPOSE_ORDER`
113
+ - 'building_code': uses `TEK_ORDER`
114
+
115
+ Examples
116
+ --------
117
+ >>> import pandas as pd
118
+ >>> df = pd.DataFrame(
119
+ >>> data=[('culture', 'PRE_TEK49', 'heating_rv', 2022, 'LAST'),
120
+ >>> ('house', 'TEK07', 'heating_dhw', 2021, 'FIRST')],
121
+ >>> columns=['building_category', 'building_code', 'purpose', 'year', 'value'])
122
+ >>> df.sort_values(by=['building_category', 'building_code', 'purpose', 'year'], key=map_sort_order)
123
+ building_category building_code purpose year value
124
+ 1 house TEK07 heating_dhw 2020 FIRST
125
+ 0 culture PRE_TEK49 heating_rv 2021 LAST
126
+
127
+ >>> from ebm.model.bema import map_sort_order
128
+ >>> import pandas as pd
129
+ >>> df = pd.DataFrame(data=['3', '2', 'last', 'first'],
130
+ >>> index=pd.Index(['non_residential', 'holiday_home', 'all', 'residential'],
131
+ >>> name='building_group'))
132
+ >>> df.sort_index(key=map_sort_order)
133
+ building_group
134
+ residential first
135
+ holiday_home 2
136
+ non_residential 3
137
+ all last
138
+ """
139
+ if column.name=='building_category':
140
+ return column.map(_building_mix_order)
141
+ if column.name=='building_group':
142
+ return column.map(BUILDING_GROUP_ORDER)
143
+ if column.name=='building_condition':
144
+ return column.map(BUILDING_CONDITION_ORDER)
145
+ if column.name=='purpose':
146
+ return column.map(PURPOSE_ORDER)
147
+ if column.name=='building_code':
148
+ return column.map(TEK_ORDER)
149
+ return column