ebm 0.99.5__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.5.dist-info/METADATA +212 -0
  76. ebm-0.99.5.dist-info/RECORD +80 -0
  77. ebm-0.99.5.dist-info/WHEEL +5 -0
  78. ebm-0.99.5.dist-info/entry_points.txt +3 -0
  79. ebm-0.99.5.dist-info/licenses/LICENSE +21 -0
  80. ebm-0.99.5.dist-info/top_level.txt +1 -0
ebm/validators.py ADDED
@@ -0,0 +1,565 @@
1
+ """
2
+ Pandera validators for ebm input files.
3
+ """
4
+ import itertools
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+ import pandera as pa
9
+
10
+ from ebm.model.building_category import BuildingCategory, RESIDENTIAL, NON_RESIDENTIAL
11
+ from ebm.model.building_condition import BuildingCondition
12
+ from ebm.model.column_operations import explode_unique_columns, explode_column_alias
13
+ from ebm.model.data_classes import YearRange
14
+ from ebm.model.energy_purpose import EnergyPurpose
15
+ from ebm.model.heating_systems import HeatingSystems
16
+
17
+
18
+ def check_building_category(value: pd.Series) -> pd.Series:
19
+ """
20
+ Makes sure that the series value contains values that are corresponding to a BuildingCategory
21
+
22
+ Parameters
23
+ ----------
24
+ value: pd.Series
25
+ A series of str that will be checked against BuildingCategory
26
+
27
+ Returns
28
+ -------
29
+ pd.Series of bool values
30
+
31
+ """
32
+ return value.isin(iter(BuildingCategory))
33
+
34
+ def check_default_building_category(value: pd.Series) -> pd.Series:
35
+ """
36
+ Makes sure that the series value contains values that are corresponding to a BuildingCategory or default
37
+
38
+ Parameters
39
+ ----------
40
+ value: pd.Series
41
+ A series of str that will be checked against BuildingCategory and 'default'
42
+
43
+ Returns
44
+ -------
45
+ pd.Series of bool values
46
+
47
+ """
48
+ return value.isin(list(BuildingCategory) + ['default'])
49
+
50
+ def check_default_building_category_with_group(value: pd.Series) -> pd.Series:
51
+ """
52
+ Makes sure that the series value contains values that are corresponding to a BuildingCategory,
53
+ BuildingCategory group (RESIDENTIAL or NON_RESIDENTIAL) or 'default'
54
+
55
+ Parameters
56
+ ----------
57
+ value: pd.Series
58
+ A series of str that will be checked against BuildingCategory, RESIDENTIAL, NON_RESIDENTIAL and 'default'
59
+
60
+ Returns
61
+ -------
62
+ pd.Series of bool values
63
+ """
64
+ return value.isin(list(BuildingCategory) + ['default'] + [RESIDENTIAL, NON_RESIDENTIAL])
65
+
66
+
67
+ def check_building_condition(value: pd.Series) -> pd.Series:
68
+ """
69
+ Makes sure that the series value contains values that are corresponding to a BuildingCondition
70
+
71
+ Parameters
72
+ ----------
73
+ value: pd.Series
74
+ A series of str that will be checked against BuildingCondition
75
+
76
+ Returns
77
+ -------
78
+ pd.Series of bool values
79
+
80
+ """
81
+ return value.isin(iter(BuildingCondition))
82
+
83
+
84
+ def check_existing_building_conditions(value: pd.Series) -> pd.Series:
85
+ """
86
+ Makes sure that the series contains values that are corresponding to 'existing' building conditions.
87
+
88
+ Existing building conditions are all members (conditions) of BuildingCondition, except of DEMOLITION.
89
+
90
+ Parameters
91
+ ----------
92
+ value: pd.Series
93
+ A series of str that will be checked against 'existing' BuildingCondition members
94
+
95
+ Returns
96
+ -------
97
+ pd.Series of bool values
98
+
99
+ """
100
+ return value.isin(iter(BuildingCondition.existing_conditions()))
101
+
102
+
103
+ def check_all_existing_building_conditions_present(df: pd.DataFrame):
104
+ """
105
+ Ensures that all 'existing' building conditions are present in the 'building_conditions' column for
106
+ each unique combination of 'building_category', 'building_code', and 'purpose'.
107
+
108
+ Existing building conditions are all members (conditions) of BuildingCondition, except of DEMOLITION.
109
+
110
+ Parameters
111
+ ----------
112
+ df: pd.Dataframe
113
+ """
114
+ grouped = df.groupby(['building_category', 'building_code', 'purpose'])['building_condition']
115
+ existing_conditions = set(BuildingCondition.existing_conditions())
116
+ for _, conditions in grouped:
117
+ if set(conditions) != existing_conditions:
118
+ return False
119
+ return True
120
+
121
+
122
+ def check_energy_purpose(value: pd.Series) -> pd.Series:
123
+ """
124
+ Makes sure that the value contains one of the valid purpose values: 'Cooling', 'Electrical equipment', 'Fans and pumps', 'HeatingDHW', 'HeatingRV', or 'Lighting'
125
+
126
+ Args:
127
+ value: Input value to check against the valid purpose values
128
+
129
+ Returns:
130
+ pd.Series: Series of bool values indicating if each value matches a valid purpose
131
+ """
132
+ return value.isin(iter(EnergyPurpose))
133
+
134
+
135
+ def check_default_energy_purpose(value: pd.Series) -> pd.Series:
136
+ """
137
+ Makes sure that the value contains one of the default or purpose values: 'Cooling', 'Electrical equipment', 'Fans and pumps', 'HeatingDHW', 'HeatingRV', or 'Lighting'
138
+
139
+ Args:
140
+ value: Input value to check against the valid purpose values
141
+
142
+ Returns:
143
+ pd.Series: Series of bool values indicating if each value matches a valid purpose
144
+ """
145
+ return value.isin(list(EnergyPurpose) + ['default'])
146
+
147
+
148
+ def check_building_code(value: str) -> bool:
149
+ """
150
+ A crude check to determine if value is a 'building_code'
151
+
152
+ Args:
153
+ value (str): A string to check if it's a building_code
154
+
155
+ Returns:
156
+ bool: True when the function thinks that value might be a building_code
157
+ """
158
+ return 'TEK' in value
159
+
160
+
161
+ def check_default_building_code(value: str) -> bool:
162
+ """
163
+ A crude check to determine if value is a 'building_code' or default
164
+
165
+ Args:
166
+ value (str): A string to check if it's a TEK or default
167
+
168
+ Returns:
169
+ bool: True when the function thinks that value might be a TEK
170
+ """
171
+ return check_building_code(value) or value == 'default'
172
+
173
+ #TODO: edge cases?
174
+ def check_overlapping_building_code_periods(df: pd.DataFrame) -> pd.Series:
175
+ """
176
+ """
177
+ df = df.sort_values(["period_end_year"])
178
+ end_years = df['period_end_year'] + 1
179
+ start_years = df["period_start_year"].shift(-1)
180
+
181
+ end_years = end_years.iloc[:-1]
182
+ start_years = start_years.iloc[:-1]
183
+ check = end_years == start_years
184
+ checked_series = pd.Series(check.to_list() +[True])
185
+ return checked_series
186
+
187
+
188
+ def check_building_category_share(values: pd.DataFrame) -> pd.Series:
189
+ """
190
+ Makes sure that the sum of values in values.new_house_share + values.new_apartment_block_share is 1.0
191
+
192
+ Args:
193
+ values (pd.DataFrame): A dataframe with new_house_share and new_apartment_block_share
194
+
195
+ Returns:
196
+ pd.Series: A series of bool with the truth value of new_house_share + new_apartment_block_share equals 1.0
197
+ """
198
+ return values.new_house_share + values.new_apartment_block_share == 1.0
199
+
200
+
201
+ def create_residential_area_checks():
202
+ """
203
+ Creates a list of checks used for house and apartment_block categories.
204
+ - Checks that the first two rows are not empty
205
+ - Checks that the next (3) rows are empty
206
+ - Checks that non-empty rows are not negative
207
+
208
+ Returns
209
+ -------
210
+ List[pa.Check]
211
+ """
212
+ return [
213
+ pa.Check(lambda s: s.iloc[:2].notnull().all(),
214
+ element_wise=False,
215
+ error='Expects number in first two rows for house'),
216
+ pa.Check(lambda s: s.iloc[2:].isnull().all(),
217
+ element_wise=False,
218
+ error='Expects empty in the last four years for house'),
219
+ pa.Check.greater_than_or_equal_to(0.0)]
220
+
221
+
222
+ def check_heating_systems(value: pd.Series) -> pd.Series:
223
+ """
224
+ Makes sure that the series contains values that corresponds to a HeatingSystems
225
+
226
+ Parameters
227
+ ----------
228
+ value: pd.Series
229
+ A series of str that will be checked against HeatingSystems
230
+
231
+ Returns
232
+ -------
233
+ pd.Series of bool values
234
+
235
+ """
236
+ return value.isin(iter(HeatingSystems))
237
+
238
+
239
+ def check_sum_of_heating_system_shares_equal_1(df: pd.DataFrame):
240
+ """
241
+ """
242
+ precision = 4
243
+ df = df.groupby(by=['building_category', 'building_code'])[['heating_system_share']].sum()
244
+ df['heating_system_share'] = round(df['heating_system_share'] * 100, precision)
245
+ return_series = df["heating_system_share"] == 100.0
246
+ return return_series
247
+
248
+
249
+ def make_building_purpose(years: YearRange | None = None) -> pd.DataFrame:
250
+ """
251
+ Returns a dataframe of all combinations building_categories, building_codes, original_condition, purposes
252
+ and optionally years.
253
+
254
+ Parameters
255
+ ----------
256
+ years : YearRange, optional
257
+
258
+ Returns
259
+ -------
260
+ pd.DataFrame
261
+ """
262
+ data = []
263
+ columns = [list(BuildingCategory),
264
+ ['PRE_TEK49', 'TEK49', 'TEK69', 'TEK87', 'TEK97', 'TEK07', 'TEK10', 'TEK17'],
265
+ EnergyPurpose]
266
+
267
+ column_headers = ['building_category', 'building_code', 'building_condition', 'purpose']
268
+ if years:
269
+ columns.append(years)
270
+ column_headers.append('year')
271
+
272
+ for bc, building_code, purpose, *year in itertools.product(*columns):
273
+ row = [bc, building_code, 'original_condition', purpose]
274
+ if years:
275
+ row.append(year[0])
276
+ data.append(row)
277
+
278
+ return pd.DataFrame(data=data, columns=column_headers)
279
+
280
+
281
+ def behaviour_factor_parser(df: pd.DataFrame) -> pd.DataFrame:
282
+ model_years = YearRange(2020, 2050)
283
+ all_combinations = make_building_purpose(years=model_years)
284
+
285
+ if 'start_year' not in df.columns:
286
+ df=df.assign(**{'start_year': model_years.start})
287
+ if 'end_year' not in df.columns:
288
+ df=df.assign(**{'end_year': model_years.end})
289
+ if 'function' not in df.columns:
290
+ df=df.assign(function='noop')
291
+ else:
292
+ df['function'] = df.function.fillna('noop')
293
+ if 'parameter' not in df.columns:
294
+ df=df.assign(parameter=0.0)
295
+
296
+ df['start_year'] = df.start_year.fillna(model_years.start).astype(int)
297
+ df['end_year'] = df.end_year.fillna(model_years.end).astype(int)
298
+
299
+ unique_columns = ['building_category', 'building_code', 'purpose', 'start_year', 'end_year']
300
+ behaviour_factor = explode_unique_columns(df,
301
+ unique_columns=unique_columns)
302
+
303
+ behaviour_factor = explode_column_alias(behaviour_factor,
304
+ column='purpose',
305
+ values=[p for p in EnergyPurpose],
306
+ alias='default',
307
+ de_dup_by=unique_columns)
308
+
309
+ behaviour_factor['year'] = behaviour_factor.apply(
310
+ lambda row: range(row.start_year, row.end_year+1), axis=1)
311
+ behaviour_factor['interpolation'] = behaviour_factor.apply(
312
+ lambda row: np.linspace(row.behaviour_factor, row.parameter, num=row.end_year+1-row.start_year), axis=1)
313
+
314
+ behaviour_factor = behaviour_factor.explode(['year', 'interpolation'])
315
+
316
+ behaviour_factor['year'] = behaviour_factor['year'].astype(int)
317
+
318
+ interpolation_slice = (behaviour_factor.function == 'improvement_at_end_year') & (~behaviour_factor.interpolation.isna())
319
+ behaviour_factor.loc[interpolation_slice, 'behaviour_factor'] = behaviour_factor.loc[
320
+ interpolation_slice, 'interpolation'].astype(float)
321
+
322
+ behaviour_factor.sort_values(['building_category', 'building_code', 'purpose', 'year'])
323
+
324
+ behaviour_factor = calculate_yearly_reduction(behaviour_factor)
325
+
326
+ behaviour_factor=behaviour_factor.set_index(['building_category', 'building_code', 'purpose', 'year'], drop=True)
327
+ all_combinations=all_combinations.set_index(['building_category', 'building_code', 'purpose', 'year'], drop=True)
328
+
329
+ joined = all_combinations.join(behaviour_factor, how='left')
330
+ joined.behaviour_factor = joined.behaviour_factor.fillna(1.0)
331
+ return joined.reset_index()
332
+
333
+
334
+ def calculate_yearly_reduction(df):
335
+ reduction_slice = df[df['function'] == 'yearly_reduction'].index
336
+ df.loc[reduction_slice, 'behaviour_factor'] = df.loc[reduction_slice].behaviour_factor * ((1.0 - df.loc[
337
+ reduction_slice].parameter) ** (df.loc[reduction_slice].year - df.loc[reduction_slice].start_year))
338
+ return df
339
+
340
+
341
+ energy_need_behaviour_factor = pa.DataFrameSchema(
342
+ parsers=pa.Parser(behaviour_factor_parser),
343
+ columns={
344
+ "building_category": pa.Column(str),
345
+ 'building_code': pa.Column(str), #
346
+ "purpose": pa.Column(str),
347
+ 'year': pa.Column(int, required=False),
348
+ 'behaviour_factor': pa.Column(float)
349
+ }
350
+ )
351
+
352
+ area = pa.DataFrameSchema(
353
+ columns={
354
+ "building_category": pa.Column(str, checks=[pa.Check(check_building_category)]),
355
+ 'building_code': pa.Column(str, checks=[pa.Check(check_building_code, element_wise=True)]),
356
+ "area": pa.Column(float, checks=[pa.Check.greater_than(0)], coerce=True)},
357
+ name='area_parameters'
358
+ )
359
+
360
+ building_code_parameters = pa.DataFrameSchema(columns={
361
+ 'building_code': pa.Column(str, unique=True, checks=[pa.Check(check_building_code, element_wise=True)]),
362
+ 'building_year': pa.Column(int, checks=[
363
+ pa.Check.greater_than_or_equal_to(1940),
364
+ pa.Check.less_than_or_equal_to(2070)]),
365
+ 'period_start_year': pa.Column(int, checks=[
366
+ pa.Check.greater_than_or_equal_to(0),
367
+ pa.Check.less_than_or_equal_to(2070),
368
+ ]),
369
+ 'period_end_year': pa.Column(int, checks=[
370
+ pa.Check.greater_than_or_equal_to(1940),
371
+ pa.Check.less_than_or_equal_to(2070, error='period_end_year should be 2070 or lower'),
372
+ pa.Check.between(1940, 2070, error='period_end_year should be between 1940 and 2070')])},
373
+ checks=[pa.Check(lambda df: df["period_end_year"] > df["period_start_year"],
374
+ error="period_end_year should be greater than period_start_year"),
375
+ pa.Check(check_overlapping_building_code_periods,
376
+ error="building_code periods do not overlap")],
377
+ name='building_code_parameters'
378
+ )
379
+
380
+
381
+ area_new_residential_buildings = pa.DataFrameSchema(
382
+ columns={
383
+ 'year': pa.Column(int),
384
+ 'house': pa.Column(pa.Float64, nullable=True, checks=create_residential_area_checks()),
385
+ 'apartment_block': pa.Column(pa.Float64, nullable=True, checks=create_residential_area_checks())
386
+ },
387
+ name='construction_building_category_yearly'
388
+ )
389
+
390
+
391
+ new_buildings_residential = pa.DataFrameSchema(
392
+ columns={
393
+ 'year': pa.Column(int, checks=[pa.Check.between(2010, 2070)]),
394
+ 'new_house_share': pa.Column(float, checks=[pa.Check.between(0.0, 1.0)]),
395
+ 'new_apartment_block_share': pa.Column(float, checks=[pa.Check.between(0.0, 1.0)]),
396
+ 'floor_area_new_house': pa.Column(int, checks=[pa.Check.between(1, 1000)]),
397
+ 'flood_area_new_apartment_block': pa.Column(int, checks=[pa.Check.between(1, 1000)])
398
+ },
399
+ checks=[pa.Check(check_building_category_share,
400
+ error='The sum of new_house_share and new_apartment_block_share should be 1.0 (100%)')],
401
+ name='new_buildings_house_share'
402
+ )
403
+
404
+
405
+ population_forecast = pa.DataFrameSchema(
406
+ columns={
407
+ 'year': pa.Column(int, coerce=True, checks=[pa.Check.between(1900, 2070)]),
408
+ 'population': pa.Column(int, coerce=True, checks=[pa.Check.greater_than_or_equal_to(0)]),
409
+ 'household_size': pa.Column(float, coerce=True, nullable=True, checks=[pa.Check.greater_than_or_equal_to(0)])},
410
+ name='new_buildings_population')
411
+
412
+
413
+ #TODO: evaluete if restrictions on rush and never share make sense (if the program crashes unless they are there)
414
+ s_curve = pa.DataFrameSchema(
415
+ columns={
416
+ 'building_category': pa.Column(str, checks=[pa.Check(check_building_category)]),
417
+ 'condition': pa.Column(str, checks=[pa.Check(check_building_condition)]),
418
+ 'earliest_age_for_measure': pa.Column(int, checks=[pa.Check.greater_than(0)]),
419
+ 'average_age_for_measure': pa.Column(int, checks=[pa.Check.greater_than(0)]),
420
+ 'rush_period_years': pa.Column(int, checks=[pa.Check.greater_than(0)]),
421
+ 'last_age_for_measure': pa.Column(int, checks=[pa.Check.greater_than(0)]),
422
+ 'rush_share': pa.Column(float, checks=[pa.Check.between(min_value=0.0, max_value=1.0, include_min=False)]),
423
+ 'never_share': pa.Column(float, checks=[pa.Check.between(min_value=0.0, max_value=1.0, include_min=False)])
424
+ },
425
+ name='scurve_parameters')
426
+
427
+
428
+ ### TODO: remove strong restrictions on float values and add warnings (should be able to be neg values)
429
+ energy_need_original_condition = pa.DataFrameSchema(
430
+ columns={
431
+ 'building_category': pa.Column(str, checks=[pa.Check(check_default_building_category)]),
432
+ 'building_code': pa.Column(str, checks=[pa.Check(check_default_building_code, element_wise=True)]),
433
+ 'purpose': pa.Column(str, checks=[pa.Check(check_default_energy_purpose)]),
434
+ 'kwh_m2': pa.Column(float, coerce=True, checks=[pa.Check.greater_than_or_equal_to(0)])
435
+ },
436
+ unique=['building_category', 'building_code', 'purpose'],
437
+ report_duplicates='all'
438
+ )
439
+
440
+
441
+ improvement_building_upgrade = pa.DataFrameSchema(
442
+ columns={
443
+ 'building_category': pa.Column(str, checks=pa.Check(check_default_building_category)),
444
+ 'building_code': pa.Column(str, checks=pa.Check(check_default_building_code, element_wise=True)),
445
+ 'purpose': pa.Column(str, checks=pa.Check(check_default_energy_purpose)),
446
+ 'building_condition': pa.Column(str, checks=[pa.Check(check_existing_building_conditions)]),
447
+ 'reduction_share': pa.Column(float, coerce=True, checks=[pa.Check.between(min_value=0.0, include_min=True,
448
+ max_value=1.0, include_max=True)])
449
+ },
450
+ unique=['building_category', 'building_code', 'purpose', 'building_condition'],
451
+ report_duplicates='all'
452
+ )
453
+
454
+
455
+ energy_need_improvements = pa.DataFrameSchema(
456
+ columns={
457
+ 'building_category': pa.Column(str, checks=pa.Check(check_default_building_category)),
458
+ 'building_code': pa.Column(str, checks=pa.Check(check_default_building_code, element_wise=True)),
459
+ 'purpose':pa.Column(str, checks=pa.Check(check_default_energy_purpose)),
460
+ 'value': pa.Column(float, coerce=True,
461
+ checks=[pa.Check.between(min_value=0.0, include_min=True,
462
+ max_value=1.0, include_max=True)])
463
+ },
464
+ unique=['building_category', 'building_code', 'purpose', 'start_year', 'function', 'end_year'],
465
+ report_duplicates='all'
466
+ )
467
+
468
+
469
+ holiday_home_stock = pa.DataFrameSchema(
470
+ columns={
471
+ 'year': pa.Column(int),
472
+ 'Existing buildings Chalet, summerhouses and other holiday houses': pa.Column(int),
473
+ 'Existing buildings Detached houses and farmhouses used as holiday houses': pa.Column(int)
474
+ }
475
+ )
476
+
477
+
478
+ holiday_home_energy_consumption = pa.DataFrameSchema(
479
+ columns={
480
+ 'year': pa.Column(int),
481
+ 'electricity': pa.Column(int),
482
+ 'fuelwood': pa.Column(float, nullable=True)
483
+ }
484
+ )
485
+
486
+ area_per_person = pa.DataFrameSchema(
487
+ columns={
488
+ 'building_category': pa.Column(str, checks=pa.Check(check_building_category)),
489
+ 'area_per_person': pa.Column(float, nullable=True)
490
+ }
491
+ )
492
+
493
+
494
+ heating_system_initial_shares = pa.DataFrameSchema(
495
+ columns={
496
+ 'building_category': pa.Column(str, checks=pa.Check(check_building_category)),
497
+ 'building_code': pa.Column(str, checks=pa.Check(check_default_building_code, element_wise=True)),
498
+ 'heating_systems': pa.Column(str, checks=pa.Check(check_heating_systems)),
499
+ 'year': pa.Column(int, pa.Check(
500
+ lambda year: len(year.unique()) == 1,
501
+ error="All values in the 'year' column must be identical."
502
+ )),
503
+ 'heating_system_share': pa.Column(float, coerce=True,
504
+ checks=[pa.Check.between(min_value=0.0, include_min=True,
505
+ max_value=1.0, include_max=True)])
506
+ },
507
+ #TODO: better warning messages to see where the issues are
508
+ checks=[pa.Check(check_sum_of_heating_system_shares_equal_1, raise_warning=True,
509
+ error="Sum of 'heating_system_share' do not equal 1 for one or more combination of 'building_category' and 'building_code'")],
510
+ name='heating_systems_shares_start_year'
511
+ )
512
+
513
+
514
+ #TODO:
515
+ # - add check on years. Parse to make long format and check years and values? years must be in order, max limit (2070) etc.
516
+ heating_system_forecast = pa.DataFrameSchema(
517
+ columns={
518
+ 'building_category': pa.Column(str, checks=pa.Check(check_default_building_category_with_group)),
519
+ 'building_code': pa.Column(str, checks=pa.Check(check_default_building_code, element_wise=True)),
520
+ 'heating_systems': pa.Column(str, checks=pa.Check(check_heating_systems)),
521
+ 'new_heating_systems': pa.Column(str, checks=pa.Check(check_heating_systems))
522
+ },
523
+ unique=['building_category', 'building_code', 'heating_systems', 'new_heating_systems'],
524
+ report_duplicates='all'
525
+ )
526
+
527
+
528
+ """
529
+ TODO: how to check columns that are heating systems (but not in enum) and 'energivare'. Columns:
530
+ 'Grunnlast': pa.Column(str),
531
+ 'Spisslast': pa.Column(str),
532
+ 'Ekstralast': pa.Column(str),
533
+ 'base_load_energy_product': pa.Column(str),
534
+ 'peak_load_energy_product': pa.Column(str),
535
+ 'tertiary_load_energy_product': pa.Column(str),
536
+ """
537
+ heating_system_efficiencies = pa.DataFrameSchema(
538
+ columns={
539
+ 'heating_systems': pa.Column(str, checks=pa.Check(check_heating_systems)),
540
+ 'base_load_energy_product': pa.Column(str),
541
+ 'peak_load_energy_product': pa.Column(str),
542
+ 'tertiary_load_energy_product': pa.Column(str),
543
+ 'tertiary_load_coverage': pa.Column(float, coerce=True),
544
+ 'base_load_coverage': pa.Column(float, coerce=True),
545
+ 'peak_load_coverage': pa.Column(float, coerce=True),
546
+ 'base_load_efficiency': pa.Column(float, coerce=True),
547
+ 'peak_load_efficiency': pa.Column(float, coerce=True),
548
+ 'tertiary_load_efficiency': pa.Column(float, coerce=True),
549
+ 'domestic_hot_water_energy_product': pa.Column(str),
550
+ 'domestic_hot_water_efficiency': pa.Column(float, coerce=True),
551
+ 'Spesifikt elforbruk': pa.Column(float, coerce=True),
552
+ 'cooling_efficiency': pa.Column(float, coerce=True)
553
+ }
554
+ )
555
+
556
+
557
+ __all__ = [area,
558
+ building_code_parameters,
559
+ area_new_residential_buildings,
560
+ new_buildings_residential,
561
+ population_forecast,
562
+ s_curve,
563
+ new_buildings_residential,
564
+ improvement_building_upgrade]
565
+