openstudio-standards 0.8.5 → 0.8.7

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 (174) hide show
  1. checksums.yaml +4 -4
  2. data/data/standards/test_performance_expected_dd_results.csv +90 -90
  3. data/lib/openstudio-standards/btap/activity.rb +858 -61
  4. data/lib/openstudio-standards/btap/attributes.rb +297 -103
  5. data/lib/openstudio-standards/btap/bridging.rb +1335 -593
  6. data/lib/openstudio-standards/btap/btap.rb +60 -181
  7. data/lib/openstudio-standards/btap/carbon/btap_carbon.rb +199 -142
  8. data/lib/openstudio-standards/btap/common/constructions.json +1275 -0
  9. data/lib/openstudio-standards/btap/common/costs.csv +1969 -0
  10. data/lib/openstudio-standards/btap/common/costs_local_factors.csv +2315 -0
  11. data/lib/openstudio-standards/btap/common/materials_glazing.csv +61 -0
  12. data/lib/openstudio-standards/btap/{common_resources → common}/materials_hvac.csv +1685 -1685
  13. data/lib/openstudio-standards/btap/{common_resources → common}/materials_lighting.csv +266 -266
  14. data/lib/openstudio-standards/btap/common/materials_opaque.csv +214 -0
  15. data/lib/openstudio-standards/btap/common/necb_building_types.csv +35 -0
  16. data/lib/openstudio-standards/btap/common/necb_space_types.csv +110 -0
  17. data/lib/openstudio-standards/btap/common/thermal_bridging.csv +73 -0
  18. data/lib/openstudio-standards/btap/costing/btap_costing.rb +346 -429
  19. data/lib/openstudio-standards/btap/costing/database.rb +104 -0
  20. data/lib/openstudio-standards/btap/costing/daylighting_sensor_control_costing.rb +356 -344
  21. data/lib/openstudio-standards/btap/costing/dcv_costing.rb +282 -296
  22. data/lib/openstudio-standards/btap/costing/envelope_costing.rb +179 -192
  23. data/lib/openstudio-standards/btap/costing/heating_cooling_costing.rb +2477 -2316
  24. data/lib/openstudio-standards/btap/costing/led_lighting_costing.rb +127 -142
  25. data/lib/openstudio-standards/btap/costing/lighting_costing.rb +216 -198
  26. data/lib/openstudio-standards/btap/costing/nv_costing.rb +522 -523
  27. data/lib/openstudio-standards/btap/costing/pv_ground_costing.rb +649 -649
  28. data/lib/openstudio-standards/btap/costing/shw_costing.rb +626 -625
  29. data/lib/openstudio-standards/btap/costing/thermal_bridging_costing.rb +72 -0
  30. data/lib/openstudio-standards/btap/costing/ventilation_costing.rb +2363 -2361
  31. data/lib/openstudio-standards/btap/deprecated.rb +1033 -0
  32. data/lib/openstudio-standards/btap/envelope.rb +313 -572
  33. data/lib/openstudio-standards/btap/fileio.rb +152 -1070
  34. data/lib/openstudio-standards/btap/geometry.rb +486 -766
  35. data/lib/openstudio-standards/btap/linear_regression.rb +130 -0
  36. data/lib/openstudio-standards/btap/paths.rb +46 -0
  37. data/lib/openstudio-standards/btap/qaqc/analysis.rb +163 -0
  38. data/lib/openstudio-standards/btap/qaqc/data.rb +2293 -0
  39. data/lib/openstudio-standards/btap/qaqc/datapoint.rb +641 -0
  40. data/lib/openstudio-standards/btap/schedules.rb +11 -611
  41. data/lib/openstudio-standards/btap/structure.rb +574 -189
  42. data/lib/openstudio-standards/constructions/materials/information.rb +1 -1
  43. data/lib/openstudio-standards/create_typical/create_typical.rb +1 -1
  44. data/lib/openstudio-standards/exterior_lighting/create.rb +135 -85
  45. data/lib/openstudio-standards/exterior_lighting/data/typical_exterior_lighting.json +20 -0
  46. data/lib/openstudio-standards/exterior_lighting/information.rb +23 -2
  47. data/lib/openstudio-standards/geometry/create.rb +7 -3
  48. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.rb +3 -2
  49. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Model.rb +3 -2
  50. data/lib/openstudio-standards/prototypes/common/data/thermostat_schedule_lookup.json +84 -84
  51. data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +4 -4
  52. data/lib/openstudio-standards/refrigeration/create_walkin.rb +45 -5
  53. data/lib/openstudio-standards/refrigeration/data/refrigerated_walkins.csv +75 -75
  54. data/lib/openstudio-standards/schedules/parametric.rb +6 -0
  55. data/lib/openstudio-standards/service_water_heating/create_typical.rb +10 -3
  56. data/lib/openstudio-standards/service_water_heating/create_water_heating_loop.rb +30 -14
  57. data/lib/openstudio-standards/service_water_heating/information.rb +4 -1
  58. data/lib/openstudio-standards/sql_file/unmet_hours.rb +2 -1
  59. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXSingleSpeed.rb +3 -1
  60. data/lib/openstudio-standards/standards/Standards.CoilHeatingDXSingleSpeed.rb +3 -1
  61. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.boilers.json +12 -0
  62. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.construction_properties.json +18432 -3072
  63. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.boilers.json +26 -0
  64. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.construction_properties.json +8532 -1422
  65. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.boilers.json +26 -0
  66. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.construction_properties.json +8532 -1422
  67. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.boilers.json +16 -0
  68. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.computer_room_acs.json +30 -30
  69. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.construction_properties.json +7560 -1260
  70. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.PumpVariableSpeed.rb +1 -2
  71. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.boilers.json +16 -0
  72. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.construction_properties.json +7560 -1260
  73. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirLoopHVAC.rb +3 -3
  74. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.PumpVariableSpeed.rb +1 -2
  75. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.boilers.json +22 -0
  76. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.construction_properties.json +7236 -1206
  77. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.furnaces.json +223 -19
  78. data/lib/openstudio-standards/standards/ashrae_90_1/data/ashrae_90_1.schedules.json +37 -1
  79. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.boilers.json +3 -0
  80. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.construction_properties.json +6900 -1150
  81. data/lib/openstudio-standards/standards/cbes/data/cbes.schedules.json +1 -1
  82. data/lib/openstudio-standards/standards/deer/data/deer.schedules.json +1 -1
  83. data/lib/openstudio-standards/standards/necb/BTAP1980TO2010/data/surface_thermal_transmittance.json +76 -40
  84. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/surface_thermal_transmittance.json +76 -40
  85. data/lib/openstudio-standards/standards/necb/ECMS/hvac_systems.rb +6 -4
  86. data/lib/openstudio-standards/standards/necb/NECB2011/NECB2011_QAQC.json +2988 -0
  87. data/lib/openstudio-standards/standards/necb/NECB2011/autozone.rb +25 -3
  88. data/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +672 -534
  89. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/Hospital.osm +1791 -1792
  90. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPMultiTower.osm +7 -1
  91. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPTownHouse.osm +7 -1
  92. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LargeHotel.osm +5 -6
  93. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LargeOffice.osm +8 -9
  94. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernEducation.osm +10 -4
  95. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernHealthCare.osm +14 -15
  96. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/Outpatient.osm +7 -1
  97. data/lib/openstudio-standards/standards/necb/NECB2011/data/surface_thermal_transmittance.json +76 -40
  98. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +14 -6
  99. data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +542 -130
  100. data/lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb +30 -63
  101. data/lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb +7 -2
  102. data/lib/openstudio-standards/standards/necb/NECB2015/data/surface_thermal_transmittance.json +90 -0
  103. data/lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb +8 -6
  104. data/lib/openstudio-standards/standards/necb/NECB2017/data/surface_thermal_transmittance.json +76 -40
  105. data/lib/openstudio-standards/standards/necb/NECB2017/necb_2017.rb +2 -1
  106. data/lib/openstudio-standards/standards/necb/NECB2020/building_envelope.rb +0 -117
  107. data/lib/openstudio-standards/standards/necb/NECB2020/data/surface_thermal_transmittance.json +76 -45
  108. data/lib/openstudio-standards/standards/necb/NECB2020/necb_2020.rb +218 -0
  109. data/lib/openstudio-standards/standards/necb/common/construction_defaults.osm +12 -7
  110. data/lib/openstudio-standards/standards/standard.rb +1 -1
  111. data/lib/openstudio-standards/version.rb +1 -1
  112. data/lib/openstudio-standards.rb +9 -4
  113. metadata +59 -66
  114. data/data/inventory/thermal_bridging.csv +0 -90
  115. data/data/standards/OpenStudio_Standards-ashrae_90_1-ALL-comstock(space_types).xlsx +0 -0
  116. data/data/standards/openstudio_standards_duplicates_log.csv +0 -7962
  117. data/lib/openstudio-standards/btap/NECB_building_types.csv +0 -35
  118. data/lib/openstudio-standards/btap/NECB_space_types.csv +0 -109
  119. data/lib/openstudio-standards/btap/WeatherData1.json +0 -2110
  120. data/lib/openstudio-standards/btap/WeatherData1.xlsx +0 -0
  121. data/lib/openstudio-standards/btap/btap_result.rb +0 -1144
  122. data/lib/openstudio-standards/btap/btap_test_helper.rb +0 -86
  123. data/lib/openstudio-standards/btap/common_paths.rb +0 -81
  124. data/lib/openstudio-standards/btap/common_resources/ConstructionProperties.csv +0 -52
  125. data/lib/openstudio-standards/btap/common_resources/Constructions.csv +0 -37
  126. data/lib/openstudio-standards/btap/common_resources/construction_sets.csv +0 -1270
  127. data/lib/openstudio-standards/btap/common_resources/constructions_glazing.csv +0 -121
  128. data/lib/openstudio-standards/btap/common_resources/constructions_opaque.csv +0 -2256
  129. data/lib/openstudio-standards/btap/common_resources/costs.csv +0 -1897
  130. data/lib/openstudio-standards/btap/common_resources/costs_local_factors.csv +0 -2315
  131. data/lib/openstudio-standards/btap/common_resources/materials_glazing.csv +0 -35
  132. data/lib/openstudio-standards/btap/common_resources/materials_opaque.csv +0 -164
  133. data/lib/openstudio-standards/btap/costing/btap_measure_helper.rb +0 -359
  134. data/lib/openstudio-standards/btap/costing/btap_workflow.rb +0 -117
  135. data/lib/openstudio-standards/btap/costing/copy_test_results_files_to_expected_results.rb +0 -11
  136. data/lib/openstudio-standards/btap/costing/cost_building_from_file.rb +0 -136
  137. data/lib/openstudio-standards/btap/costing/costing_database_wrapper.rb +0 -173
  138. data/lib/openstudio-standards/btap/costing/dummy.epw +0 -8768
  139. data/lib/openstudio-standards/btap/costing/dummy.osm +0 -5320
  140. data/lib/openstudio-standards/btap/costing/neb_end_use_prices.csv +0 -42
  141. data/lib/openstudio-standards/btap/costing/necb_2011_spacetype_info.csv +0 -225
  142. data/lib/openstudio-standards/btap/costing/necb_reference_runs.csv +0 -28705
  143. data/lib/openstudio-standards/btap/costing/parallel_tests.rb +0 -92
  144. data/lib/openstudio-standards/btap/costing/test_list.txt +0 -15
  145. data/lib/openstudio-standards/btap/costing/test_run_all_test_locally.rb +0 -27
  146. data/lib/openstudio-standards/btap/costing/test_run_costing_tests.rb +0 -80
  147. data/lib/openstudio-standards/btap/necb_2011_spacetype_info.csv +0 -225
  148. data/lib/openstudio-standards/btap/utilities/Add_template_field_to_json.rb +0 -22
  149. data/lib/openstudio-standards/btap/utilities/convert_surfaces_to_adiabatic_necb_8426.rb +0 -204
  150. data/lib/openstudio-standards/btap/utilities/generate_space_types.rb +0 -8
  151. data/lib/openstudio-standards/btap/utilities/necb_to_epw_map.rb +0 -50
  152. data/lib/openstudio-standards/btap/utilities/os_sim_extract.rb +0 -48
  153. data/lib/openstudio-standards/btap/utilities/rename_surfaces.rb +0 -24
  154. data/lib/openstudio-standards/btap/utilities/round_surf_coords.rb +0 -32
  155. data/lib/openstudio-standards/btap/utilities/sched_create.rb +0 -145
  156. data/lib/openstudio-standards/btap/utilities/set_mult_to_adiabatic.rb +0 -53
  157. data/lib/openstudio-standards/btap/utilities/space_type.rb +0 -78
  158. data/lib/openstudio-standards/btap/utilities/weatherData1_xlsx_to_json.rb +0 -56
  159. data/lib/openstudio-standards/btap/vintagizer.rb +0 -161
  160. data/lib/openstudio-standards/btap/visualization.rb +0 -72
  161. data/lib/openstudio-standards/standards/necb/NECB2011/data/necb_2011_spacetype_info.csv +0 -225
  162. data/lib/openstudio-standards/standards/necb/common/btap_analysis.rb +0 -79
  163. data/lib/openstudio-standards/standards/necb/common/btap_data.rb +0 -2656
  164. data/lib/openstudio-standards/standards/necb/common/btap_datapoint.rb +0 -653
  165. data/lib/openstudio-standards/standards/necb/common/necb_reference_runs.csv +0 -28705
  166. data/lib/openstudio-standards/standards/necb/common/utility_pricing_2025-02-20.csv +0 -92
  167. /data/lib/openstudio-standards/btap/{common_resources → common}/carbon_frame.csv +0 -0
  168. /data/lib/openstudio-standards/btap/{common_resources → common}/carbon_glazing.csv +0 -0
  169. /data/lib/openstudio-standards/btap/{common_resources → common}/carbon_opaque.csv +0 -0
  170. /data/lib/openstudio-standards/btap/{common_resources → common}/hvac_vent_ahu.csv +0 -0
  171. /data/lib/openstudio-standards/btap/{common_resources → common}/lighting.csv +0 -0
  172. /data/lib/openstudio-standards/btap/{common_resources → common}/lighting_sets.csv +0 -0
  173. /data/lib/openstudio-standards/btap/{common_resources → common}/locations.csv +0 -0
  174. /data/lib/openstudio-standards/btap/{costing → common}/mech_sizing.json +0 -0
@@ -1,2656 +0,0 @@
1
- class BTAPData
2
- attr_accessor :osa_file
3
- attr_accessor :osw_file
4
- attr_accessor :osm_file
5
- attr_accessor :sqlite_file
6
- attr_accessor :btap_data
7
-
8
- def initialize(
9
- model:,
10
- runner: nil,
11
- cost_result:,
12
- carbon_result:,
13
- baseline_cost_equipment_total_cost_per_m_sq: -1.0,
14
- baseline_cost_utility_neb_total_cost_per_m_sq: -1.0,
15
- baseline_energy_eui_total_gj_per_m_sq: -1.0,
16
- qaqc:,
17
- npv_start_year: 2022,
18
- npv_end_year: 2041,
19
- npv_discount_rate: 0.03,
20
- npv_discount_rate_carbon: 0.03,
21
- oerd_utility_pricing: nil,
22
- utility_pricing_year: 2020)
23
-
24
- @model = model
25
- @error_warning = []
26
- # sets sql file.
27
- set_sql_file(model.sqlFile)
28
- @standard = Standard.build('NECB2011')
29
- @standards_data = @standard.load_standards_database_new()
30
- @btap_data = {}
31
- @btap_results_version = 1.00
32
- @neb_prices_csv_file_name = File.join(__dir__, 'neb_end_use_prices.csv')
33
- @neb_prices_csv_file_name = File.join(__dir__, 'utility_pricing_2025-02-20.csv') if oerd_utility_pricing.to_bool
34
- @necb_reference_runs_csv_file_name = File.join(__dir__, 'necb_reference_runs.csv')
35
- @eccc_ghg_electricity = File.join(__dir__, 'eccc_electric_grid_intensity_20250311.csv') #REF (Retrieved March 11, 2025) for ECCC's emissions factors for the current and future years for GHG "without Biomass and RNG CO2 emissions": https://data-donnees.az.ec.gc.ca/data/substances/monitor/canada-s-greenhouse-gas-emissions-projections/Current-Projections-Actuelles/Energy-Energie/AM%20Scenario%20AMS/Grid-O%26G-Intensities-Intensites-Reseau-Delectricite-P%26G?lang=en
36
- @nir_ghg_gas = File.join(__dir__, 'nir_gas_grid_intensity_20250311.csv') #REF (Retrieved March 11, 2025) for Year 2022 of "Table A6.1–1 CO2 Emission Factors for Marketable Natural Gas" of National Inventory Report: https://data-donnees.az.ec.gc.ca/api/file?path=%2Fsubstances%2Fmonitor%2Fcanada-s-official-greenhouse-gas-inventory%2FD-Emission-Factors%2FEN_Annex6_Emission_Factors.pdf
37
-
38
- # Conditioned floor area is used so much. May as well make it a object variable.
39
- # setup the queries
40
- command = "SELECT Value
41
- FROM TabularDataWithStrings
42
- WHERE ReportName='AnnualBuildingUtilityPerformanceSummary'
43
- AND ReportForString='Entire Facility'
44
- AND TableName='Building Area'
45
- AND RowName = 'Net Conditioned Building Area'
46
- AND ColumnName='Area'"
47
- area = @sqlite_file.get.execAndReturnFirstDouble(command)
48
- # make sure all the data are available
49
- if area.empty?
50
- @conditioned_floor_area_m_sq = 0.0
51
- else
52
- @conditioned_floor_area_m_sq = area.get
53
- end
54
-
55
-
56
- @btap_data['simulation_btap_data_version'] = '0.1'
57
- # @btap_data["simulation_openstudio_version"] = open("| \"#{OpenStudio.getOpenStudioCLI}\" openstudio_version").read().strip
58
- # @btap_data["simulation_energyplus_version"] = open("| \"#{OpenStudio.getOpenStudioCLI}\" energyplus_version").read().strip
59
- @btap_data['simulation_os_standards_revision'] = OpenstudioStandards.git_revision
60
- @btap_data['simulation_os_standards_version'] = OpenstudioStandards::VERSION
61
- @btap_data['simulation_date'] = Time.now
62
- @btap_data.merge!(building_data)
63
- @btap_data.merge!(building_costing_data(cost_result)) unless cost_result.nil?
64
- @btap_data.merge!(climate_data)
65
- @btap_data.merge!(service_water_heating_data)
66
- @btap_data.merge!(energy_eui_data(model))
67
- @btap_data.merge!(energy_peak_data)
68
- @btap_data.merge!(utility(model: model, utility_pricing_year: utility_pricing_year))
69
- @btap_data.merge!(unmet_hours(model))
70
- @btap_data.merge!outdoor_air_data(model)
71
-
72
- # Data in tables...
73
- @btap_data.merge!('measures_data_table' => measures_data_table(runner)) unless runner.nil?
74
- @btap_data.merge!('envelope_exterior_surface_table' => envelope_exterior_surface_table)
75
- @btap_data.merge!('space_table' => space_table(model, cost_result))
76
- @btap_data.merge!('space_type_table' => space_type_table(model))
77
- # This does not work with the new VRF or CCASHP systems. Commenting it for now.
78
- # @btap_data.merge!({'zone_table' => thermal_zones_table(model, cost_result)['table']})
79
- @btap_data.merge!('zone_equip_table' => thermal_zones_equipment_table(model))
80
- # This does not work with the new VRF or CCASHP systems. Commenting it for now.
81
- # @btap_data.merge!({'air_loop_table' => air_loops_table(model, cost_result)})
82
- # @btap_data.merge!({'sql_raw_data' => sql_data_tables(model)})
83
- @btap_data.merge!('eplusout_err_table' => eplusout_err_table(model))
84
-
85
- # Remainder of costing data in separate tables:
86
- @btap_data.merge!('envelope_construction_cost_table' => cost_result['envelope']['construction_costs']) unless cost_result.nil?
87
- @btap_data.merge!('lighting_fixture_cost_table' => cost_result['lighting']['fixture_report']) unless cost_result.nil?
88
- ideal_air = true
89
- model.getThermalZones.each do |zone|
90
- ideal_air = false if zone.useIdealAirLoads == false
91
- end
92
- unless ideal_air
93
- @btap_data.merge!('h_and_c_plant_equipment_cost_table' => cost_result['heating_and_cooling']['plant_equipment']) unless cost_result.nil?
94
- @btap_data.merge!('h_and_c_plant_zonal_systems_cost_table' => cost_result['heating_and_cooling']['zonal_systems']) unless cost_result.nil?
95
- # This does not work with the new VRF or CCASHP systems. Commenting it for now.
96
- # @btap_data.merge!('system_coils_cost_table' => coil_cost_table(cost_result))
97
- # This does not work with the new VRF or CCASHP systems. Commenting it for now.
98
- # @btap_data.merge!('terminal_VAV_cost_table' => terminal_VAV_cost_table(cost_result))
99
- # This does not work with the new VRF or CCASHP systems. Commenting it for now.
100
- # @btap_data.merge!('trunk_ducts_cost_table' => trunk_ducts_cost_table(cost_result))
101
- end
102
- # calculate energy demands and peak loads calculations as per PHIUS and NECB and compare them
103
- phius_performance_indicators(model)
104
- # The below method calculates energy performance indicators (i.e. TEDI and MEUI) as per BC Energy Step Code
105
- bc_energy_step_code_performance_indicators
106
- # calculates annual peak electricity cost (dollar)
107
- annual_peak_kW_per_m_sq = oerd_electricity_cost(model: model, utility_pricing_year: utility_pricing_year, oerd_utility_pricing: oerd_utility_pricing)
108
- # calculate net present value
109
- net_present_value(npv_start_year: npv_start_year, npv_end_year: npv_end_year, npv_discount_rate: npv_discount_rate, npv_discount_rate_carbon: npv_discount_rate_carbon, oerd_utility_pricing: oerd_utility_pricing, annual_peak_kW_per_m_sq: annual_peak_kW_per_m_sq) unless cost_result.nil?
110
-
111
- measure_metrics(qaqc)
112
- @btap_data
113
- end
114
-
115
- # Oct-2019 JTB: This function must be passed a hash and will flatten mixtures of hashes
116
- # and arrays of hashes. Embedded arrays are enumerated (starting at 1).
117
- def flatten_mix(hash)
118
- hash.each_with_object({}) do |(k, v), h|
119
- if v.is_a?(Hash)
120
- flatten_mix(v).map do |h_k, h_v|
121
- h["#{k}.#{h_k}".to_sym] = h_v
122
- end
123
- elsif v.is_a?(Array)
124
- v.map.with_index do |e, ndx|
125
- if e.is_a?(Hash)
126
- flatten_mix(e).map do |e_k, e_v|
127
- h["#{k}.#{e_k}.#{ndx + 1}".to_sym] = e_v
128
- end
129
- # if there is another array within the array v, flatten more
130
- # but this is as deep as we go with embedded arrays!
131
- elsif e.is_a?(Array)
132
- e.map.with_index do |e1, ndx1|
133
- if e1.is_a?(Hash)
134
- flatten_mix(e1).map do |e1_k, e1_v|
135
- h["#{k}.#{e1_k}.#{ndx1 + 1}".to_sym] = e1_v
136
- end
137
- else
138
- # Stop flattening here!
139
- h[k] = v
140
- end
141
- end
142
- else
143
- h[k] = v
144
- end
145
- end
146
- else
147
- h[k] = v
148
- end
149
- end
150
- end
151
-
152
- # General Building Data that there is alway either zero of 1 of.
153
- def building_data
154
- # Store Building data.
155
- building_data = {}
156
- building_data['bldg_name'] = @model.building.get.name.get
157
- building_data['bldg_conditioned_floor_area_m_sq'] = @conditioned_floor_area_m_sq
158
- building_data['bldg_exterior_area_m_sq'] = @model.building.get.exteriorSurfaceArea # m_sq
159
- building_data['bldg_volume_m_cu'] = @model.building.get.airVolume # m_cu
160
- building_data['bldg_standards_template'] = @model.building.get.standardsTemplate.empty? ? nil : @model.building.get.standardsTemplate.get
161
- building_data['bldg_standards_building_type'] = @model.building.get.standardsBuildingType.empty? ? nil : @model.building.get.standardsBuildingType.get
162
- building_data['bldg_standards_number_of_stories'] = @model.building.get.standardsNumberOfStories.empty? ? nil : @model.building.get.standardsNumberOfStories.get
163
- building_data['bldg_standards_number_of_above_ground_stories'] = @model.building.get.standardsNumberOfAboveGroundStories.empty? ? nil : @model.building.get.standardsNumberOfAboveGroundStories.get
164
- building_data['bldg_standards_number_of_living_units'] = @model.building.get.standardsNumberOfLivingUnits.empty? ? nil : @model.building.get.standardsNumberOfAboveGroundStories.get
165
- building_data['bldg_nominal_floor_to_ceiling_height'] = @model.building.get.nominalFloortoCeilingHeight.empty? ? nil : @model.building.get.nominalFloortoCeilingHeight.get
166
- building_data['bldg_nominal_floor_to_floor_height'] = @model.building.get.nominalFloortoFloorHeight.empty? ? nil : @model.building.get.nominalFloortoFloorHeight.get
167
- building_data['bldg_surface_to_volume_ratio'] = @model.building.get.exteriorSurfaceArea / @model.building.get.airVolume
168
- building_data['bldg_fdwr'] = (BTAP::Geometry.get_fwdr(@model) * 100.0).round(1)
169
- building_data['bldg_srr'] = (BTAP::Geometry.get_srr(@model) * 100.0).round(1)
170
-
171
- return building_data
172
- end
173
-
174
- def building_costing_data(cost_result)
175
- building_data = {}
176
- building_data['cost_province_state'] = cost_result['province_state']
177
- building_data['cost_city'] = cost_result['city']
178
- building_data['cost_equipment_envelope_total_cost_per_m_sq'] = (cost_result['totals']['envelope']) / @conditioned_floor_area_m_sq
179
- building_data['cost_equipment_thermal_bridging_total_cost_per_m_sq'] = (cost_result['totals']['thermal_bridging']) / @conditioned_floor_area_m_sq
180
- building_data['cost_equipment_lighting_total_cost_per_m_sq'] = (cost_result['totals']['lighting']) / @conditioned_floor_area_m_sq
181
- building_data['cost_equipment_heating_and_cooling_total_cost_per_m_sq'] = (cost_result['totals']['heating_and_cooling']) / @conditioned_floor_area_m_sq
182
- building_data['cost_equipment_shw_total_cost_per_m_sq'] = (cost_result['totals']['shw']) / @conditioned_floor_area_m_sq
183
- building_data['cost_equipment_ventilation_total_cost_per_m_sq'] = (cost_result['totals']['ventilation']) / @conditioned_floor_area_m_sq
184
- building_data['cost_equipment_renewables_total_cost_per_m_sq'] = (cost_result['totals']['renewables']) / @conditioned_floor_area_m_sq
185
- building_data['cost_equipment_total_cost_per_m_sq'] = (cost_result['totals']['grand_total']) / @conditioned_floor_area_m_sq
186
- # building_data.merge!(cost_result['envelope'].select{|k,v| k!='construction_costs' && k!='total_envelope_cost'})
187
- # building_data.merge!(cost_result['shw'].select{|k,v| k!='shw_total'})
188
- # building_data.merge!(flatten_mix(cost_result['ventilation'].select{|k,v| k=='mech_to_roof'.to_sym}))
189
- return building_data
190
- end
191
-
192
- def net_present_value(npv_start_year: 2022, npv_end_year: 2041, npv_discount_rate: 0.03, npv_discount_rate_carbon: 0.03, oerd_utility_pricing: false, annual_peak_kW_per_m_sq: nil)
193
-
194
- # Find end year in the neb data
195
- neb_header = CSV.read(@neb_prices_csv_file_name, headers: true).headers
196
- neb_header.delete_if { |item| ["building_type", "province", "fuel_type"].include?(item) } # remove "building_type", "province", "fuel_type" from neb_header in order to have only years in neb_header
197
- neb_header.delete_if { |item| ["rate_class", "units", "references", "links"].include?(item) } if oerd_utility_pricing # remove additional headers if the OERD data is used
198
- neb_header.map(&:to_f) #convert years to float
199
- year_max = neb_header.max
200
-
201
- # Convert a string to a float
202
- if npv_start_year.instance_of?(String) && npv_start_year != 'NECB_Default' && npv_start_year != 'none' && !npv_start_yeay.nil?
203
- npv_start_year = npv_start_year.to_f
204
- end
205
- if npv_end_year.instance_of?(String) && npv_end_year != 'NECB_Default' && npv_end_year != 'none' && !npv_end_year.nil?
206
- npv_end_year = npv_end_year.to_f
207
- end
208
- if npv_discount_rate.instance_of?(String) && npv_discount_rate != 'NECB_Default' && npv_discount_rate != 'none' && !npv_discount_rate.nil?
209
- npv_discount_rate = npv_discount_rate.to_f
210
- end
211
- if npv_discount_rate_carbon.instance_of?(String) && npv_discount_rate_carbon != 'NECB_Default' && npv_discount_rate_carbon != 'none' && !npv_discount_rate_carbon.nil?
212
- npv_discount_rate_carbon = npv_discount_rate_carbon.to_f
213
- end
214
-
215
-
216
- # Set default npv_start_year as 2022, npv_end_year as 2041, npv_discount_rate as 3%
217
- if npv_start_year == 'NECB_Default' || npv_start_year.nil? || npv_start_year == 'none'
218
- npv_start_year = 2022
219
- end
220
- if npv_end_year == 'NECB_Default' || npv_end_year.nil? || npv_end_year == 'none'
221
- npv_end_year = 2041
222
- end
223
- if npv_discount_rate == 'NECB_Default' || npv_discount_rate.nil? || npv_discount_rate == 'none'
224
- npv_discount_rate = 0.03
225
- end
226
- if npv_discount_rate_carbon == 'NECB_Default' || npv_discount_rate_carbon.nil? || npv_discount_rate_carbon == 'none'
227
- npv_discount_rate_carbon = 0.03
228
- end
229
-
230
- # Set npv_end_year as year_max if users' input > neb's end year
231
- if npv_end_year > year_max.to_f
232
- npv_end_year = year_max.to_f
233
- warn "WARNING: Your npv_end_year for the calculation of net present value is larger than that in Canada Energy Regulator (CER) (i.e. #{year_max}). So, npv_end_year has been reset as #{year_max}."
234
- end
235
- # puts "npv_start_year is #{npv_start_year}"
236
- # puts "npv_end_year is #{npv_end_year}"
237
- # puts "npv_discount_rate is #{npv_discount_rate}"
238
-
239
- # Get energy end-use prices (CER data from https://apps.cer-rec.gc.ca/ftrppndc/dflt.aspx?GoCTemplateCulture=en-CA)
240
- # @neb_prices_csv_file_name = "#{File.dirname(__FILE__)}/neb_end_use_prices.csv"
241
-
242
- # Create a hash of the neb data.
243
- neb_data = CSV.parse(File.read(@neb_prices_csv_file_name), headers: true, converters: :numeric).map(&:to_h)
244
-
245
- # Create a hash of the ECCC's electricity emissions factors for the current and future years for GHG "without Biomass and RNG CO2 emissions".
246
- eccc_ghg_elec_emission_factors = CSV.parse(File.read(@eccc_ghg_electricity), headers: true, converters: :numeric).map(&:to_h)
247
-
248
- # Find which province the proposed building is located in
249
- building_type = 'Commercial'
250
- geography_data = climate_data
251
- province_abbreviation = geography_data['location_state_province_region']
252
- province = @standards_data['province_map'][province_abbreviation]
253
-
254
- # Note: If there is on-site energy generation (e.g. PV), it should be considered in the calculation of EUI for the calculation of energy use cost and NPV.
255
- # To do so, it has been assumed that on-site energy generation is only for electricity.
256
- # Electricity EUI of a building is re-calculated for NPV. It will be: ['energy_eui_electricity_gj_per_m_sq' - ('total_site_eui_gj_per_m_sq' - 'net_site_eui_gj_per_m_sq')]
257
- # Note that if there is no on-site energy generation, 'total_site_eui_gj_per_m_sq' and 'net_site_eui_gj_per_m_sq' will be equal.
258
- # Note: 'total_site_eui_gj_per_m_sq' is the gross energy consumed by the building (REF: https://unmethours.com/question/25416/what-is-the-difference-between-site-energy-and-source-energy/)
259
- # Note: 'net_site_eui_gj_per_m_sq' is the final energy consumed by the building after accounting for on-site energy generations (e.g. PV) (REF: https://unmethours.com/question/25416/what-is-the-difference-between-site-energy-and-source-energy/)
260
-
261
- # Calculate npv of electricity
262
- onsite_elec_generation = @btap_data['total_site_eui_gj_per_m_sq'] - @btap_data['net_site_eui_gj_per_m_sq']
263
- if onsite_elec_generation > 0.0
264
- eui_elec = @btap_data['energy_eui_electricity_gj_per_m_sq'] - onsite_elec_generation
265
- else
266
- eui_elec = @btap_data['energy_eui_electricity_gj_per_m_sq']
267
- end
268
- # Find the appropriate row for electricity GHG emmission factors
269
- row_emm_factor = eccc_ghg_elec_emission_factors.detect do |data|
270
- (data['province'] == province)
271
- end
272
- # Find the appropriate row for electricity pricing
273
- row = neb_data.detect do |data|
274
- (data['building_type'] == building_type) && (data['province'] == province) && (data['fuel_type'] == 'Electricity')
275
- end
276
- row_peak = nil
277
- if oerd_utility_pricing && !annual_peak_kW_per_m_sq.nil?
278
- row_peak = neb_data.detect do |data|
279
- (data['building_type'] == building_type) && (data['province'] == province) && (data['fuel_type'] == 'Electricity Peak')
280
- end
281
- end
282
- npv_elec = 0.0
283
- year_index = 1.0
284
- npv_elec_ghg = 0.0
285
- npv_elec_peak = 0.0
286
- emm_factor_conv = 3600.0 #GWH to GJ
287
- if eui_elec > 0.0
288
- for year in npv_start_year.to_int..npv_end_year.to_int
289
- npv_elec += (eui_elec * row[year.to_s]) / (1+npv_discount_rate)**year_index
290
- npv_elec_ghg += (((eui_elec * row_emm_factor[year.to_i.to_s].to_f) / emm_factor_conv) * get_national_ghg_cost(year: year.to_i)) / (1+npv_discount_rate_carbon)**year_index
291
- npv_elec_peak += (annual_peak_kW_per_m_sq * row_peak[year.to_i.to_s]).to_f / (1+npv_discount_rate)**year_index unless row_peak.nil? || annual_peak_kW_per_m_sq.nil?
292
- year_index += 1.0
293
- end
294
- end
295
-
296
- # Calculate npv of natural gas
297
- eui_ngas= @btap_data['energy_eui_natural_gas_gj_per_m_sq']
298
- # Convert to tonnes of ghg/m^2
299
- ghg_ngas = @btap_data['cost_utility_ghg_natural gas_kg_per_m_sq'].to_f/1000.0
300
- row = neb_data.detect do |data|
301
- (data['building_type'] == building_type) && (data['province'] == province) && (data['fuel_type'] == 'Natural Gas')
302
- end
303
- npv_ngas = 0.0
304
- npv_ngas_ghg = 0.0
305
- year_index = 1.0
306
- for year in npv_start_year.to_int..npv_end_year.to_int
307
- npv_ngas += (eui_ngas * row[year.to_s]) / (1+npv_discount_rate)**year_index
308
- npv_ngas_ghg += (ghg_ngas * get_national_ghg_cost(year: year.to_i)) / (1+npv_discount_rate_carbon)**year_index
309
- year_index += 1.0
310
- end
311
- # puts "npv_ngas is #{npv_ngas}"
312
-
313
- # Calculate npv of oil
314
- eui_oil= @btap_data['energy_eui_additional_fuel_gj_per_m_sq']
315
- # Convert to tonnes of ghg/m^2
316
- ghg_oil = @btap_data['cost_utility_ghg_oil_kg_per_m_sq'].to_f/1000.0
317
- row = neb_data.detect do |data|
318
- (data['building_type'] == building_type) && (data['province'] == province) && (data['fuel_type'] == 'Oil')
319
- end
320
- npv_oil = 0.0
321
- npv_oil_ghg = 0.0
322
- year_index = 1.0
323
- for year in npv_start_year.to_int..npv_end_year.to_int
324
- npv_oil += (eui_oil * row[year.to_s]) / (1+npv_discount_rate)**year_index
325
- npv_oil_ghg += (ghg_oil * get_national_ghg_cost(year: year.to_i)) / (1+npv_discount_rate_carbon)**year_index
326
- year_index += 1.0
327
- end
328
- # puts "npv_oil is #{npv_oil}"
329
-
330
- # Calculate total npv
331
- npv_total = @btap_data['cost_equipment_total_cost_per_m_sq'] + npv_elec + npv_ngas + npv_oil
332
- npv_ghg_total = npv_elec_ghg + npv_ngas_ghg + npv_oil_ghg
333
- npv_total_with_ghg_and_peak = npv_ghg_total + npv_total + npv_elec_peak
334
- @btap_data.merge!('npv_total_per_m_sq' => npv_total)
335
- @btap_data.merge!('npv_carbon_pricing_per_m_sq' => npv_ghg_total)
336
- @btap_data.merge!('npv_peak_elec_per_m_sq' => npv_elec_peak)
337
- @btap_data.merge!('npv_with_carbon_and_peak_pricing_total_per_m_sq' => npv_total_with_ghg_and_peak)
338
- end
339
-
340
- def envelope(model)
341
- data = {}
342
- # Get OSM surface information
343
- surfaces = model.getSurfaces.sort
344
- interior_surfaces = BTAP::Geometry::Surfaces.filter_by_boundary_condition(surfaces, ['Surface', 'Adiabatic'])
345
- interior_floors = BTAP::Geometry::Surfaces.filter_by_surface_types(interior_surfaces, 'Floor')
346
- outdoor_surfaces = BTAP::Geometry::Surfaces.filter_by_boundary_condition(surfaces, 'Outdoors')
347
- outdoor_walls = BTAP::Geometry::Surfaces.filter_by_surface_types(outdoor_surfaces, 'Wall')
348
- outdoor_roofs = BTAP::Geometry::Surfaces.filter_by_surface_types(outdoor_surfaces, 'RoofCeiling')
349
- outdoor_floors = BTAP::Geometry::Surfaces.filter_by_surface_types(outdoor_surfaces, 'Floor')
350
- outdoor_subsurfaces = outdoor_surfaces.flat_map(&:subSurfaces)
351
- ground_surfaces = BTAP::Geometry::Surfaces.filter_by_boundary_condition(surfaces, ['Ground', 'Foundation'])
352
- ground_walls = BTAP::Geometry::Surfaces.filter_by_surface_types(ground_surfaces, 'Wall')
353
- ground_roofs = BTAP::Geometry::Surfaces.filter_by_surface_types(ground_surfaces, 'RoofCeiling')
354
- ground_floors = BTAP::Geometry::Surfaces.filter_by_surface_types(ground_surfaces, 'Floor')
355
- windows = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(outdoor_subsurfaces, ['FixedWindow', 'OperableWindow'])
356
- skylights = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(outdoor_subsurfaces, ['Skylight', 'TubularDaylightDiffuser', 'TubularDaylightDome'])
357
- doors = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(outdoor_subsurfaces, ['Door', 'GlassDoor'])
358
- overhead_doors = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(outdoor_subsurfaces, ['OverheadDoor'])
359
-
360
- # Get Areas
361
- data['outdoor_walls_area_m_sq'] = outdoor_walls.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier }
362
- data['outdoor_roofs_area_m_sq'] = outdoor_roofs.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier }
363
- data['outdoor_floors_area_m_sq'] = outdoor_floors.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier }
364
- data['ground_walls_area_m_sq'] = ground_walls.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier }
365
- data['ground_roofs_area_m_sq'] = ground_roofs.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier }
366
- data['ground_floors_area_m_sq'] = ground_floors.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier }
367
- data['interior_floors_area_m_sq'] = interior_floors.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier }
368
-
369
- # Subsurface areas
370
- data['windows_area_m_sq'] = windows.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier * e.multiplier }
371
- data['skylights_area_m_sq'] = skylights.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier * e.multiplier }
372
- data['doors_area_m_sq'] = doors.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier * e.multiplier }
373
- data['overhead_doors_area_m_sq'] = overhead_doors.inject(0) { |sum, e| sum + e.netArea * e.space.get.multiplier * e.multiplier }
374
-
375
- # Total Building Ground Surface Area.
376
- data['total_ground_area_m_sq'] = data['ground_walls_area_m_sq'] +
377
- data['ground_roofs_area_m_sq'] +
378
- data['ground_floors_area_m_sq']
379
- # Total Building Outdoor Surface Area.
380
- data['total_outdoor_area_m_sq'] = data['outdoor_walls_area_m_sq'] +
381
- data['outdoor_roofs_area_m_sq'] +
382
- data['outdoor_floors_area_m_sq'] +
383
- data['windows_area_m_sq'] +
384
- data['skylights_area_m_sq'] +
385
- data['doors_area_m_sq'] +
386
- data['overhead_doors_area_m_sq']
387
-
388
- # Average Conductances by surface Type
389
- data['outdoor_walls_average_conductance_w_per_m_sq_k'] = OpenstudioStandards::Constructions.surfaces_get_conductance(outdoor_walls).round(4) if !outdoor_walls.empty?
390
- data['outdoor_roofs_average_conductance_w_per_m_sq_k'] = OpenstudioStandards::Constructions.surfaces_get_conductance(outdoor_roofs).round(4) if !outdoor_roofs.empty?
391
- data['outdoor_floors_average_conductance_w_per_m_sq_k'] = OpenstudioStandards::Constructions.surfaces_get_conductance(outdoor_floors).round(4) if !outdoor_floors.empty?
392
- data['ground_walls_average_conductance_w_per_m_sq_k'] = OpenstudioStandards::Constructions.surfaces_get_conductance(ground_walls).round(4) if !ground_walls.empty?
393
- data['ground_roofs_average_conductance_w_per_m_sq_k'] = OpenstudioStandards::Constructions.surfaces_get_conductance(ground_roofs).round(4) if !ground_roofs.empty?
394
- data['ground_floors_average_conductance_w_per_m_sq_k'] = OpenstudioStandards::Constructions.surfaces_get_conductance(ground_floors).round(4) if !ground_floors.empty?
395
- data['windows_average_conductance_w_per_m_sq_k'] = OpenstudioStandards::Constructions.surfaces_get_conductance(windows).round(4) if !windows.empty?
396
- data['skylights_average_conductance_w_per_m_sq_k'] = OpenstudioStandards::Constructions.surfaces_get_conductance(skylights).round(4) if !skylights.empty?
397
- data['doors_average_conductance_w_per_m_sq_k'] = OpenstudioStandards::Constructions.surfaces_get_conductance(doors).round(4) if !doors.empty?
398
- data['overhead_doors_average_conductance_w_per_m_sq_k'] = OpenstudioStandards::Constructions.surfaces_get_conductance(overhead_doors).round(4) if !overhead_doors.empty?
399
-
400
- # #Average Conductances for building whole weight factors
401
- !outdoor_walls.empty? ? o_wall_cond_weight = data['outdoor_walls_average_conductance_w_per_m_sq_k'] * data['outdoor_walls_area_m_sq'] : o_wall_cond_weight = 0
402
- !outdoor_roofs.empty? ? o_roof_cond_weight = data['outdoor_roofs_average_conductance_w_per_m_sq_k'] * data['outdoor_roofs_area_m_sq'] : o_roof_cond_weight = 0
403
- !outdoor_floors.empty? ? o_floor_cond_weight = data['outdoor_floors_average_conductance_w_per_m_sq_k'] * data['outdoor_floors_area_m_sq'] : o_floor_cond_weight = 0
404
- !ground_walls.empty? ? g_wall_cond_weight = data['ground_walls_average_conductance_w_per_m_sq_k'] * data['ground_walls_area_m_sq'] : g_wall_cond_weight = 0
405
- !ground_roofs.empty? ? g_roof_cond_weight = data['ground_roofs_average_conductance_w_per_m_sq_k'] * data['ground_roofs_area_m_sq'] : g_roof_cond_weight = 0
406
- !ground_floors.empty? ? g_floor_cond_weight = data['ground_floors_average_conductance_w_per_m_sq_k'] * data['ground_floors_area_m_sq'] : g_floor_cond_weight = 0
407
- !windows.empty? ? win_cond_weight = data['windows_average_conductance_w_per_m_sq_k'] * data['windows_area_m_sq'] : win_cond_weight = 0
408
- # doors.size > 0 ? sky_cond_weight = data["skylights_average_conductance_w_per_m_sq_k"] * data["skylights_area_m_sq"] : sky_cond_weight = 0
409
- if !doors.empty? && !data['skylights_average_conductance_w_per_m_sq_k'].nil? && !data['skylights_area_m_sq'].nil?
410
- sky_cond_weight = data['skylights_average_conductance_w_per_m_sq_k'] * data['skylights_area_m_sq']
411
- else
412
- sky_cond_weight = 0
413
- end
414
- !overhead_doors.empty? ? door_cond_weight = data['doors_average_conductance_w_per_m_sq_k'] * data['doors_area_m_sq'] : door_cond_weight = 0
415
- !overhead_doors.empty? ? overhead_door_cond_weight = data['overhead_doors_average_conductance_w_per_m_sq_k'] * data['overhead_doors_area_m_sq'] : overhead_door_cond_weight = 0
416
-
417
- # Building Average Conductance
418
- data['outdoor_average_conductance_w_per_m_sq_k'] = (
419
- o_floor_cond_weight +
420
- o_roof_cond_weight +
421
- o_wall_cond_weight +
422
- win_cond_weight +
423
- sky_cond_weight +
424
- door_cond_weight +
425
- overhead_door_cond_weight) / data['total_outdoor_area_m_sq']
426
-
427
- # Building Average Ground Conductance
428
- data['ground_average_conductance_w_per_m_sq_k'] = (
429
- g_floor_cond_weight +
430
- g_roof_cond_weight +
431
- g_wall_cond_weight) / data['total_ground_area_m_sq']
432
-
433
- # Building Average Conductance
434
- data['average_conductance_w_per_m_sq_k'] = (
435
- (data['average_conductance_w_per_m_sq_k'] * data['total_ground_area_m_sq']) +
436
- (data['outdoor_average_conductance_w_per_m_sq_k'] * data['total_outdoor_area_m_sq'])
437
- ) /
438
- (data['total_ground_area_m_sq'] + data['total_outdoor_area_m_sq'])
439
- prefix = 'envel_'
440
- return Hash[data.map { |k, v| ["#{prefix}_#{k}", v] }]
441
- end
442
-
443
- def envelope_summary(qaqc)
444
- @btap_data['envelope-outdoor_walls_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:outdoor_walls_average_conductance_w_per_m2_k]
445
- @btap_data['envelope-outdoor_roofs_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:outdoor_roofs_average_conductance_w_per_m2_k]
446
- @btap_data['envelope-outdoor_floors_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:outdoor_floors_average_conductance_w_per_m2_k]
447
- @btap_data['envelope-ground_walls_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:ground_walls_average_conductance_w_per_m2_k]
448
- @btap_data['envelope-ground_roofs_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:ground_roofs_average_conductance_w_per_m2_k]
449
- @btap_data['envelope-ground_floors_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:ground_floors_average_conductance_w_per_m2_k]
450
- @btap_data['envelope-outdoor_windows_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:windows_average_conductance_w_per_m2_k]
451
- @btap_data['envelope-outdoor_doors_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:doors_average_conductance_w_per_m2_k]
452
- @btap_data['envelope-outdoor_overhead_doors_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:overhead_doors_average_conductance_w_per_m2_k]
453
- @btap_data['envelope-skylights_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:skylights_average_conductance_w_per_m2_k]
454
- end
455
-
456
- def envelope_exterior_surface_table
457
- surfaces = @model.getSurfaces.sort
458
- outdoor_surfaces = BTAP::Geometry::Surfaces.filter_by_boundary_condition(surfaces, 'Outdoors')
459
- ground_surfaces = BTAP::Geometry::Surfaces.filter_by_boundary_condition(surfaces, ['Ground', 'Foundation'])
460
- exterior_opaque_surfaces = outdoor_surfaces + ground_surfaces
461
- # outdoor_surfaces.each { |surface| puts surface.name}
462
- # get surface table from sql
463
- table = get_sql_table_to_json(@model, 'EnvelopeSummary', 'Entire Facility', 'Opaque Exterior')
464
- raise('Could not get opaque surface table from E+ sql') if table.empty?
465
-
466
- # add space name to table.
467
- table['table'].each do |row|
468
- surface = exterior_opaque_surfaces.detect { |curr_surface| curr_surface.name.get.downcase == row['name'].downcase }
469
- raise("Could not find surface #{row['name'].downcase} in #{outdoor_surfaces.map { |curr_surface| curr_surface.name.get.downcase }}") if surface.nil?
470
-
471
- row['os_type'] = surface.surfaceType
472
- row['boundary_condition'] = surface.outsideBoundaryCondition
473
- space_includes_surface = @model.getSpaces.detect { |space| space.surfaces.include?(surface) }
474
- row['space_name'] = space_includes_surface.nil? || !space_includes_surface.name.is_initialized ? 'NA' : space_includes_surface.name.get
475
- end
476
- opaque = table
477
-
478
- # Fenestrations
479
- fenestrations = BTAP::Geometry::Surfaces.filter_subsurfaces_by_types(@model.getSubSurfaces.sort, ['GlassDoor', 'FixedWindow', 'OperableWindow', 'Skylight', 'TubularDaylightDiffuser', 'TubularDaylightDome'])
480
- # get surface table from sql
481
- table = get_sql_table_to_json(@model, 'EnvelopeSummary', 'Entire Facility', 'Exterior Fenestration')
482
- # Exclude totals and averages by deleting row with that in their name.
483
- table['table'].delete_if { |row| !!(row['name'] =~ /Total|Average/) }
484
- raise('Could not get fenestration surface table from E+ sql') if table.empty?
485
-
486
- # add space name to table.
487
- table['table'].each do |row|
488
- subsurface = fenestrations.detect { |surface| surface.name.get.downcase == row['name'].downcase }
489
- raise("Could not find surface #{row['name'].downcase} in #{fenestrations.map { |surface| surface.name.get.downcase }}") if subsurface.nil?
490
-
491
- row['os_type'] = subsurface.subSurfaceType
492
- row['boundary_condition'] = subsurface.outsideBoundaryCondition
493
- parent_surface = subsurface.surface.get
494
- space_includes_surface = @model.getSpaces.detect { |space| space.surfaces.include?(parent_surface) }
495
- row['space_name'] = space_includes_surface.nil? || !space_includes_surface.name.is_initialized ? 'NA' : space_includes_surface.name.get
496
- end
497
- glazing = table
498
-
499
- # return as a single table.
500
-
501
- return glazing['table'] + opaque['table']
502
- end
503
-
504
- def space_table(model, cost_result)
505
- # Store Space data.
506
- table = []
507
- model.getSpaces.sort.each do |space|
508
- spaceinfo = {}
509
- table << spaceinfo
510
- spaceinfo['thermal_zone_name'] = space.thermalZone.empty? ? 'None' : space.thermalZone.get.name.get # should be assigned a thermalzone name.
511
- spaceinfo['space_name'] = space.name.get # name should be defined test
512
- spaceinfo['multiplier'] = space.multiplier
513
- spaceinfo['volume'] = space.volume # should be greater than zero
514
- spaceinfo['exterior_wall_area'] = space.exteriorWallArea # just for information.
515
- spaceinfo['space_type_name'] = space.spaceType.get.name.get unless space.spaceType.empty? # should have a space types name defined.
516
- spaceinfo['breathing_zone_outdoor_airflow_vbz'] = -1
517
- spaceinfo['infiltration_flow_per_m_sq'] = space.infiltrationDesignFlowPerExteriorSurfaceArea
518
- spaceinfo['floor_area_m2'] = space.floorArea
519
- spaceinfo['building_type'] = space.spaceType.get.standardsBuildingType.empty? ? 'None' : space.spaceType.get.standardsBuildingType.get
520
- spaceinfo['is_conditioned'] = space.thermalZone.get.isConditioned.get unless space.thermalZone.empty? or space.thermalZone.get.isConditioned.empty?
521
- # shw
522
- spaceinfo['shw_peak_flow_rate_m_cu_per_s'] = 0
523
- spaceinfo['shw_peak_flow_rate_per_floor_area_m_cu_per_s_per_m_sq'] = 0
524
- space.waterUseEquipment.each do |equipment|
525
- spaceinfo['shw_peak_flow_rate_m_cu_per_s'] += equipment.waterUseEquipmentDefinition.peakFlowRate
526
- spaceinfo['shw_peak_flow_rate_per_floor_area_m_cu_per_s_per_m_sq'] += equipment.waterUseEquipmentDefinition.peakFlowRate / space.floorArea
527
- area_per_occ = space.spaceType.get.getFloorAreaPerPerson(space.floorArea)
528
- # Watt per person = m_cu/s/m_cu * 1000W/kW * (specific heat * dT) * m_sq/person
529
- spaceinfo['shw_watts_per_person'] = spaceinfo['shw_peak_flow_rate_per_floor_area_m_cu_per_s_per_m_sq'] * 1000 * (4.19 * 44.4) * 1000 * area_per_occ
530
- end
531
- unless cost_result.nil?
532
- # Including space level lighting costs in the existing space table...
533
- spaceLgtInfo = cost_result['lighting']['space_report'].detect { |curr_spaceLgtInfo| curr_spaceLgtInfo['zone'].downcase == spaceinfo['thermal_zone_name'].downcase }
534
- raise("Could not find zone name \"#{spaceinfo['thermal_zone_name']}\" in lighting space_report") if spaceLgtInfo.nil?
535
-
536
- spaceinfo['space_type'] = spaceLgtInfo['space_type']
537
- spaceinfo['fixture_type'] = spaceLgtInfo['fixture_type']
538
- # Note spelling mistake of "description" in cost_result hash fixed below in copy
539
- spaceinfo['fixture_description'] = spaceLgtInfo['fixture_desciption']
540
- spaceinfo['height_avg_ft'] = spaceLgtInfo['height_avg_ft']
541
- spaceinfo['floor_area_ft2'] = spaceLgtInfo['floor_area_ft2']
542
- spaceinfo['lighting_cost'] = spaceLgtInfo['cost']
543
- spaceinfo['lighting_cost_per_ft2'] = spaceLgtInfo['cost_per_ft2']
544
- spaceinfo['lighting_note'] = spaceLgtInfo['note']
545
- end
546
- end
547
- table.sort_by! { |spaceinfo| [spaceinfo['thermal_zone_name'], spaceinfo['space_name']] }
548
- return table
549
- end
550
-
551
- def climate_data
552
- # Store Geography Data
553
- geography_data = {}
554
- geography_data['location_necb_hdd'] = @standard.get_necb_hdd18(model: @model, necb_hdd: true)
555
- geography_data['location_weather_file'] = File.basename(@model.getWeatherFile.path.get.to_s)
556
- weather_file_path = @model.weatherFile.get.path.get.to_s
557
- stat_file_path = weather_file_path.gsub('.epw', '.stat')
558
- stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path)
559
- geography_data['location_epw_cdd'] = stat_file.cdd18
560
- geography_data['location_epw_hdd'] = stat_file.hdd18
561
- geography_data['location_necb_climate_zone'] = @standard.get_climate_zone_name(geography_data['location_necb_hdd'])
562
- geography_data['location_city'] = @model.getWeatherFile.city
563
- geography_data['location_state_province_region'] = @model.getWeatherFile.stateProvinceRegion
564
- geography_data['location_country'] = @model.getWeatherFile.country
565
- geography_data['location_latitude'] = @model.getWeatherFile.latitude
566
- geography_data['location_longitude'] = @model.getWeatherFile.longitude
567
- return geography_data
568
- end
569
-
570
- ### The 'annual_peak_electricity_cost_dollar' function calculates annual peak electricity cost (dollar)
571
- ### for OEE-Electrification project using the OERD's electricity rates for commercial buildings
572
- ### that Danielle Krauel of OEE provided to CE-O
573
- def oerd_electricity_cost(model:, utility_pricing_year: 2020, oerd_utility_pricing: false)
574
- unless oerd_utility_pricing
575
- @btap_data.merge!('cost_utility_oerd_electricity_peak_annual_cost_per_m_sq' => 0.00) unless oerd_utility_pricing
576
- @btap_data.merge!('cost_utility_oerd_electricity_energy_annual_cost_per_m_sq' => 0.00) unless oerd_utility_pricing
577
- return 0.00
578
- end
579
-
580
- #===================================================================================================================
581
- ### OERD's electricity rates for commercial buildings
582
- neb_data = CSV.parse(File.read(@neb_prices_csv_file_name), headers: true, converters: :numeric).map(&:to_h)
583
- #===================================================================================================================
584
- #===================================================================================================================
585
- #===================================================================================================================
586
- #===================================================================================================================
587
- #===================================================================================================================
588
- ##################### Calculate annual electricity peak cost using OERD's electricity rate #########################
589
- #===================================================================================================================
590
- #===================================================================================================================
591
- ### Find index for hourly data of 'ElectricityNet:Facility' (J)
592
- query = "
593
- SELECT ReportDataDictionaryIndex
594
- FROM ReportDataDictionary
595
- WHERE Name=='ElectricityNet:Facility' AND ReportingFrequency == 'Hourly' AND Units == 'J'
596
- "
597
- index_timestep_electricity = model.sqlFile.get.execAndReturnVectorOfInt(query).get[0]
598
- number_of_timesteps_per_hour = 1
599
- puts "Trying hourly ElectricityNet:Facility"
600
-
601
- ### Find index for timestep data of 'Electricity:Facility' (J) if 'ElectricityNet:Facility' data is not available
602
- if index_timestep_electricity.nil?
603
- query = "
604
- SELECT ReportDataDictionaryIndex
605
- FROM ReportDataDictionary
606
- WHERE Name=='Electricity:Facility' AND ReportingFrequency == 'Zone Timestep' AND Units == 'J'
607
- "
608
- index_timestep_electricity = model.sqlFile.get.execAndReturnVectorOfInt(query).get[0]
609
- number_of_timesteps_per_hour = model.getTimestep.numberOfTimestepsPerHour
610
- puts "Trying timestep Electricity:Facility"
611
- end
612
-
613
- ### Find index for hourly data of 'Electricity:Facility' (J) if timestep data is not available
614
- if index_timestep_electricity.nil?
615
- query = "
616
- SELECT ReportDataDictionaryIndex
617
- FROM ReportDataDictionary
618
- WHERE Name=='Electricity:Facility' AND ReportingFrequency == 'Hourly' AND Units == 'J'
619
- "
620
- index_timestep_electricity = model.sqlFile.get.execAndReturnVectorOfInt(query).get[0]
621
- number_of_timesteps_per_hour = 1
622
- puts "Trying hourly Electricity:Facility"
623
- end
624
-
625
- if index_timestep_electricity.nil?
626
- puts "No hourly or timestep data available. Peak data not available"
627
- @btap_data.merge!('cost_utility_oerd_electricity_peak_annual_cost_per_m_sq' => 0.00) unless oerd_utility_pricing
628
- @btap_data.merge!('cost_utility_oerd_electricity_energy_annual_cost_per_m_sq' => 0.00) unless oerd_utility_pricing
629
- return 0.00
630
- end
631
- ### Get number of timesteps from the model
632
- timestep_second = 3600 / number_of_timesteps_per_hour
633
- #===================================================================================================================
634
- ### Calculate number of timesteps of the whole year, and create dates/times for the whole year
635
- number_of_timesteps_of_year = 365 * 24 * number_of_timesteps_per_hour
636
- timesteps_of_year = []
637
- d = Time.new(2006, 1, 1, 0)
638
- (0...number_of_timesteps_of_year).each do |increment|
639
- timesteps_of_year << (d + (60 * 60 / number_of_timesteps_per_hour) * increment).strftime('%Y-%m-%d %H:%M')
640
- end
641
- #===================================================================================================================
642
-
643
- #===================================================================================================================
644
- ### Get timestep output for Electricity:Facility output.
645
- query = "
646
- Select VariableValue
647
- FROM ReportVariableData
648
- WHERE
649
- ReportVariableDataDictionaryIndex = #{index_timestep_electricity}
650
- "
651
- timestep_values = model.sqlFile.get.execAndReturnVectorOfDouble(query).get
652
- #===================================================================================================================
653
- ### Calculate electricity peak (J) for each month of the year
654
- # Find month of each timestep
655
- keys_month = timesteps_of_year.map{ |timestep| DateTime.parse(timestep).to_date.month }
656
- # Create a hash where its keys are months and values for eahc month are electricity energy use (J) of all timesteps for that month
657
- hash_months_electricity = keys_month.zip(timestep_values).group_by(&:first).map{|key, value| [key, value.map(&:last)]}.to_h
658
- # Find maximum of electricity energy use (J) among all timesteps for each month of the year
659
- monthly_peak_J = hash_months_electricity.map { |key, value| [key, value.max()] }.to_h
660
- #===================================================================================================================
661
- ### Convert J to kW of maximum electricity energy use (J)
662
- monthly_peak_kW = monthly_peak_J.map { |key, value| [key, value * 0.001 / timestep_second] }.to_h
663
- ### Normalize by building floor area
664
- annual_peak_kW_per_m_sq = monthly_peak_kW.values.sum / @btap_data['bldg_conditioned_floor_area_m_sq'].to_f
665
- #===================================================================================================================
666
- ### Get province of the model and find its peak electricity price from the utility_pricing_2025-02-20.csv
667
- province = @btap_data['location_state_province_region']
668
- # Find which province the proposed building is located in
669
- building_type = 'Commercial'
670
- geography_data = climate_data
671
- province_abbreviation = geography_data['location_state_province_region']
672
- province = @standards_data['province_map'][province_abbreviation]
673
- row_peak = neb_data.detect do |data|
674
- (data['building_type'] == building_type) && (data['province'] == province) && (data['fuel_type'] == 'Electricity Peak')
675
- end
676
- province_dollar_kW_month = row_peak[utility_pricing_year.to_i.to_s]
677
- #===================================================================================================================
678
- ### Calculate cost of peak electricity for each month
679
- monthly_peak_cost = monthly_peak_kW.map { |key, value| [key, value * province_dollar_kW_month] }.to_h
680
- #===================================================================================================================
681
- ### Calculate cost of peak electricity for the whole year in dollar
682
- annual_peak_cost_dollar = monthly_peak_cost.values.sum
683
- #===================================================================================================================
684
- ### Calculate cost of peak electricity for the whole year in dollar normalized by floor area
685
- annual_peak_cost_dollar_per_m_sq = annual_peak_cost_dollar / @btap_data['bldg_conditioned_floor_area_m_sq']
686
- #===================================================================================================================
687
- ### Merge the calculated value to @btap_data
688
- @btap_data.merge!('cost_utility_oerd_electricity_peak_annual_cost_per_m_sq' => annual_peak_cost_dollar_per_m_sq.round(2))
689
- #===================================================================================================================
690
- #===================================================================================================================
691
- #===================================================================================================================
692
- #===================================================================================================================
693
- #===================================================================================================================
694
- ################### Calculate annual electricity energy use cost using OERD's electricity rate #####################
695
- #===================================================================================================================
696
- ### Get province of the model and find its peak electricity price from the pricing_2025-02-20.csv
697
- row_elec = neb_data.detect do |data|
698
- (data['building_type'] == building_type) && (data['province'] == province) && (data['fuel_type'] == 'Electricity')
699
- end
700
- province_dollar_kWh = row_elec[utility_pricing_year.to_i.to_s]
701
- #===================================================================================================================
702
- # Calculate cost of energy electricity for the whole year in dollar normalized by floor area
703
- annual_energy_cost_dollar_per_m_sq = @btap_data['energy_eui_electricity_gj_per_m_sq'] * 277.778 * province_dollar_kWh
704
- #===================================================================================================================
705
- ### Merge the calculated value to @btap_data
706
- @btap_data.merge!('cost_utility_oerd_electricity_energy_annual_cost_per_m_sq' => annual_energy_cost_dollar_per_m_sq.round(2))
707
- #===================================================================================================================
708
- return annual_peak_kW_per_m_sq
709
- end
710
-
711
- def utility(model:, utility_pricing_year: 2020)
712
- economics_data = {}
713
- building_type = 'Commercial'
714
- province = @standards_data['province_map'][model.getWeatherFile.stateProvinceRegion]
715
- neb_eplus_fuel_map = {'Natural Gas' => {eplus_fuel_name: 'NaturalGas',
716
- eplus_table_name: 'Annual and Peak Values - Natural Gas',
717
- eplus_row_name: 'NaturalGas:Facility',
718
- eplus_column_name: 'Natural Gas Annual Value'},
719
- 'Electricity' => {eplus_fuel_name: 'Electricity',
720
- eplus_table_name: 'Annual and Peak Values - Electricity',
721
- eplus_row_name: 'Electricity:Facility',
722
- eplus_column_name: 'Electricity Annual Value'},
723
- 'Oil' => {eplus_fuel_name: 'FuelOilNo2',
724
- eplus_table_name: 'Annual and Peak Values - Other',
725
- eplus_row_name: 'FuelOilNo2:Facility',
726
- eplus_column_name: 'Annual Value'}
727
- }
728
- economics_data['cost_utility_neb_total_cost_per_m_sq'] = 0.0
729
- economics_data['cost_utility_ghg_total_kg_per_m_sq'] = 0.0
730
- # Create a hash of the neb data.
731
- neb_data = CSV.parse(File.read(@neb_prices_csv_file_name), headers: true, converters: :numeric).map(&:to_h)
732
-
733
- neb_eplus_fuel_map.each do |neb_fuel, ep_fuel|
734
- next if neb_fuel.to_s.downcase == 'electricity peak'
735
- row = neb_data.detect do |data|
736
- (data['building_type'].to_s == building_type) &&
737
- (data['province'].to_s == province) &&
738
- (data['fuel_type'].to_s == neb_fuel)
739
- end
740
- utility_pricing_year_s = utility_pricing_year.to_i.to_s
741
- neb_fuel_cost = row[utility_pricing_year_s]
742
- fuel_consumption_gj = 0.0
743
- sql_command = "SELECT Value FROM tabulardatawithstrings
744
- WHERE ReportName='EnergyMeters'
745
- AND ReportForString='Entire Facility'
746
- AND TableName='#{ep_fuel[:eplus_table_name]}'
747
- AND RowName='#{ep_fuel[:eplus_row_name]}'
748
- AND ColumnName='#{ep_fuel[:eplus_column_name]}'
749
- AND Units='GJ'"
750
- fuel_consumption_gj = model.sqlFile.get.execAndReturnFirstDouble(sql_command).is_initialized ? model.sqlFile.get.execAndReturnFirstDouble(sql_command).get : 0.0
751
-
752
- # Determine costs in $$
753
- economics_data["cost_utility_neb_#{neb_fuel.downcase}_cost_per_m_sq"] = fuel_consumption_gj * neb_fuel_cost.to_f / @conditioned_floor_area_m_sq
754
- economics_data['cost_utility_neb_total_cost_per_m_sq'] += economics_data["cost_utility_neb_#{neb_fuel.downcase}_cost_per_m_sq"]
755
- # Determine cost in GHG kg of CO2
756
- if neb_fuel.downcase == 'electricity' # Use ECCC data for utility emission factors rather than NEB data.
757
- # Create a hash of the ECCC's electricity emissions factors for the current and future years for GHG "without Biomass and RNG CO2 emissions".
758
- eccc_ghg_elec_emission_factors = CSV.parse(File.read(@eccc_ghg_electricity), headers: true, converters: :numeric).map(&:to_h)
759
- # Find the appropriate row for electricity GHG emmission factors
760
- row_emm_factor = eccc_ghg_elec_emission_factors.detect { |data| (data['province'] == province) }
761
- emm_factor_conv = 3600.0
762
- economics_data["cost_utility_ghg_#{neb_fuel.downcase}_kg_per_m_sq"] = ((fuel_consumption_gj * row_emm_factor[utility_pricing_year.to_i.to_s].to_f) / emm_factor_conv) * 1000 / @conditioned_floor_area_m_sq
763
- else
764
- economics_data["cost_utility_ghg_#{neb_fuel.downcase}_kg_per_m_sq"] = fuel_consumption_gj * get_utility_ghg_kg_per_gj(province: model.getWeatherFile.stateProvinceRegion, fuel_type: ep_fuel[:eplus_fuel_name]) / @conditioned_floor_area_m_sq
765
- end
766
- economics_data['cost_utility_ghg_total_kg_per_m_sq'] += economics_data["cost_utility_ghg_#{neb_fuel.downcase}_kg_per_m_sq"]
767
- end
768
- # Commenting out block charge rates for now....
769
-
770
- # Fuel cost based local utility rates
771
- # sql_command = "SELECT RowName FROM TabularDataWithStrings
772
- # WHERE ReportName='LEEDsummary'
773
- # AND ReportForString='Entire Facility'
774
- # AND TableName='EAp2-7. Energy Cost Summary'
775
- # AND ColumnName='Total Energy Cost'"
776
- # costing_rownames = model.sqlFile().get().execAndReturnVectorOfString(sql_command)
777
-
778
- #==> ["Electricity", "Natural Gas", "Additional", "Total"]
779
- # costing_rownames = validate_optional(costing_rownames, model, "N/A")
780
- # unless costing_rownames == "N/A"
781
- # costing_rownames.each do |rowname|
782
- # sql_command = "SELECT Value FROM TabularDataWithStrings
783
- # WHERE ReportName='LEEDsummary'
784
- # AND ReportForString='Entire Facility'
785
- # AND TableName='EAp2-7. Energy Cost Summary'
786
- # AND ColumnName='Total Energy Cost'
787
- # AND RowName='#{rowname}'"
788
- # case rowname
789
- # when "Electricity"
790
- # economics_data["cost_utility_block_electricity_cost_per_m_sq"] = model.sqlFile().get().execAndReturnFirstDouble(sql_command).get / @conditioned_floor_area_m_sq
791
- # when "Natural Gas"
792
- # economics_data["cost_utility_block_natural_gas_cost_per_m_sq"] = model.sqlFile().get().execAndReturnFirstDouble(sql_command).get / @conditioned_floor_area_m_sq
793
- # when "Additional"
794
- # economics_data["cost_utility_block_additional_cost_per_m_sq"] = model.sqlFile().get().execAndReturnFirstDouble(sql_command).get / @conditioned_floor_area_m_sq
795
- # when "Total"
796
- # economics_data["cost_utility_block_total_cost_per_m_sq"] = model.sqlFile().get().execAndReturnFirstDouble(sql_command).get / @conditioned_floor_area_m_sq
797
- # end
798
- # end
799
- # else
800
- # @error_warning << "costing is unavailable because the sql statement is nil RowName FROM TabularDataWithStrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-7. Energy Cost Summary' AND ColumnName='Total Energy Cost'"
801
- # end
802
- return economics_data
803
- end
804
-
805
- def space_type_table(model)
806
- table = []
807
- model.getSpaceTypes.sort.each do |spaceType|
808
- next if spaceType.floorArea == 0
809
-
810
- # data for space type breakdown
811
- display = spaceType.name.get
812
- floor_area_si = 0
813
- # loop through spaces so I can skip if not included in floor area
814
- spaceType.spaces.sort.each do |space|
815
- next if !space.partofTotalFloorArea
816
-
817
- floor_area_si += space.floorArea * space.multiplier
818
- end
819
- space_type_info = {}
820
- space_type_info['name'] = spaceType.name.get
821
- space_type_info['floor_m_sq'] = floor_area_si
822
- space_type_info['percent_area'] = (floor_area_si / @conditioned_floor_area_m_sq * 100.0).round(2)
823
- space_type_info['occ_per_m_sq'] = !spaceType.peoplePerFloorArea.empty? ? spaceType.peoplePerFloorArea.get : nil
824
- space_type_info['occ_schedule'] = !spaceType.defaultScheduleSet.empty? && !spaceType.defaultScheduleSet.get.numberofPeopleSchedule.empty? ? spaceType.defaultScheduleSet.get.numberofPeopleSchedule.get.name.get : nil
825
- space_type_info['lighting_w_per_m_sq'] = !spaceType.lightingPowerPerFloorArea.empty? ? spaceType.lightingPowerPerFloorArea.get : nil
826
- space_type_info['electric_w_per_m_sq'] = !spaceType.electricEquipmentPowerPerFloorArea.empty? ? spaceType.electricEquipmentPowerPerFloorArea.get : nil
827
- space_type_info['gas_w_per_m_sq'] = !spaceType.gasEquipmentPowerPerFloorArea.empty? ? spaceType.gasEquipmentPowerPerFloorArea.get : nil
828
- table << space_type_info
829
- end
830
- return table
831
- end
832
-
833
- def thermal_zones_table(model, cost_result)
834
- # Get E+ zone table.
835
- zones = @model.getThermalZones
836
- # get surface table from sql
837
- table = get_sql_table_to_json(@model, 'InputVerificationandResultsSummary', 'Entire Facility', 'Zone Summary')
838
- # Get rid of totals and averages.
839
- table['table'].delete_if { |row| !!(row['name'] =~ /Total|Average/) }
840
- raise('Could not get zone table from E+ sql') if table.empty?
841
-
842
- # Go through zone objects
843
- zones.each do |zone|
844
- # get E+ zone row
845
- row = table['table'].detect { |curr_row| zone.name.get.downcase == curr_row['name'].downcase }
846
- raise("Could not find zone #{row['name']} in #{zones.map { |curr_zone| curr_zone.name.get }}") if row.nil?
847
-
848
- row['is_ideal_air_loads'] = zone.useIdealAirLoads
849
- row['heating_sizing_factor'] = zone.sizingZone.zoneHeatingSizingFactor.empty? ? -1.0 : zone.sizingZone.zoneHeatingSizingFactor.get
850
- row['cooling_sizing_factor'] = zone.sizingZone.zoneCoolingSizingFactor.empty? ? -1.0 : zone.sizingZone.zoneCoolingSizingFactor.get
851
- row['zone_heating_design_supply_air_temperature'] = zone.sizingZone.zoneHeatingDesignSupplyAirTemperature
852
- row['zone_cooling_design_supply_air_temperature'] = zone.sizingZone.zoneCoolingDesignSupplyAirTemperature
853
- # Get Air loop that it is connected to if possible
854
- row['air_loop_name'] = nil
855
- model.getAirLoopHVACs.sort.each do |air_loop|
856
- row['air_loop_name'] = air_loop.name.get if air_loop.thermalZones.include?(zone)
857
- end
858
- # Get Breathing zone outdoor air flow.
859
- sql_command = "
860
- SELECT Value FROM TabularDataWithStrings
861
- WHERE ReportName='Standard62.1Summary'
862
- AND ReportForString='Entire Facility'
863
- AND TableName='Zone Ventilation Parameters'
864
- AND ColumnName='Breathing Zone Outdoor Airflow - Vbz'
865
- AND Units='m3/s'
866
- AND RowName='#{row['name']}'
867
- "
868
- breathing_zone_outdoor_airflow_vbz = model.sqlFile.get.execAndReturnFirstDouble(sql_command)
869
- row['breathing_zone_outdoor_airflow_vbz'] = breathing_zone_outdoor_airflow_vbz.empty? ? nil : breathing_zone_outdoor_airflow_vbz.get
870
-
871
- if zone.useIdealAirLoads == false
872
- # Including ventilation tz_distribution cost data into zone_table
873
- zoneName = row['name'].downcase
874
- storyHash = cost_result['ventilation']['tz_distribution'.to_sym][0].detect { |currstoryHash| zoneName.include?(currstoryHash[:Story].to_s.downcase) }
875
- if storyHash
876
- tzHash = storyHash[:thermal_zones].detect { |currtzHash| zoneName == currtzHash[:ThermalZone].to_s.downcase && tzHash[:ducting_direction].to_s.downcase == 'supply' }
877
- if tzHash
878
- row['ducting_direction'] = tzHash[:ducting_direction]
879
- row['tz_mult'] = tzHash[:tz_mult]
880
- row['airflow_m3ps'] = tzHash[:airflow_m3ps]
881
- row['num_diff'] = tzHash[:num_diff]
882
- row['ducting_lbs'] = tzHash[:ducting_lbs]
883
- row['duct_insulation_ft2'] = tzHash[:duct_insulation_ft2]
884
- row['flex_duct_sz_in'] = tzHash[:flex_duct_sz_in]
885
- row['flex_duct_length_ft'] = tzHash[:flex_duct_length_ft]
886
- row['duct_cost'] = tzHash[:cost]
887
- # Check if there is a return duct hash and, if so, modify ducting direction & cost to include
888
- # Return duct. Note that all other return duct costing values are identical to Supply duct
889
- tzHash1 = storyHash[:thermal_zones].detect { |currtzHash1| zoneName == currtzHash1[:ThermalZone].to_s.downcase && currtzHash1[:ducting_direction].to_s.downcase == 'return' }
890
- if !tzHash1.nil?
891
- row['ducting_direction'] = 'Supply & Return'
892
- row['duct_cost'] += tzHash1[:cost]
893
- end
894
- end
895
- end
896
-
897
- # Including thermal zone HRV return ducting distribution cost information
898
- floorHash = cost_result['ventilation']['hrv_return_ducting'.to_sym].detect { |currfloorHash| zoneName.include?(currfloorHash[:floor].to_s.downcase) }
899
- if floorHash
900
- airSysArr = floorHash[:air_systems].select { |airSys| airSys[:air_system].to_s.downcase == row['air_loop_name'].downcase }
901
- if !airSysArr.empty?
902
- airSysArr.each do |airSysHash|
903
- airSys_tz_hash = airSysHash[:tz_dist].detect { |curr_airSys_tz_hash| curr_airSys_tz_hash[:tz].to_s.downcase == zoneName }
904
- if airSys_tz_hash
905
- row['hrv_ret_dist_m'] = airSys_tz_hash[:hrv_ret_dist_m]
906
- row['hrv_ret_size_in'] = airSys_tz_hash[:hrv_ret_size_in]
907
- row['hrv_ret_duct_cost'] = airSys_tz_hash[:cost]
908
- end
909
- end
910
- end
911
- end
912
- end
913
- end
914
-
915
- return table
916
- end
917
-
918
- def thermal_zones_equipment_table(model)
919
- # Store Thermal zone data
920
- table = []
921
- model.getThermalZones.sort.each do |zone|
922
- zone.equipmentInHeatingOrder.each do |equipment|
923
- item = {}
924
- item['air_loop_name'] = nil
925
- model.getAirLoopHVACs.sort.each do |air_loop|
926
- if air_loop.thermalZones.include?(zone)
927
- item['air_loop_name'] = air_loop.name.empty? ? 'None' : air_loop.name.get
928
- else
929
- item['air_loop_name'] = 'None'
930
- end
931
- end
932
- item['thermal_zone_name'] = zone.name.empty? ? 'None' : zone.name.get
933
- item['zone_equipment_name'] = equipment.name.empty? ? 'None' : equipment.name.get
934
- item['type'] = get_actual_child_object(equipment).class.name
935
- table << item
936
- end
937
- end
938
-
939
- table.sort_by! { |item| [item['air_loop_name'], item['thermal_zone_name'], item['zone_equipment_name']] }
940
- return table
941
- end
942
-
943
- def air_loops_table(model, cost_result)
944
- # Store Air Loop Information
945
- table = []
946
- model.getAirLoopHVACs.sort.each do |air_loop|
947
- air_loop_info = {}
948
- air_loop_info['name'] = air_loop.name.get
949
- sql_command = " SELECT Value FROM TabularDataWithStrings
950
- WHERE ReportName='Standard62.1Summary'
951
- AND ReportForString='Entire Facility'
952
- AND TableName='System Ventilation Parameters'
953
- AND ColumnName='Area Outdoor Air Rate - Ra'
954
- AND Units='m3/s-m2'
955
- AND RowName='#{air_loop.name.get.to_s.upcase}' "
956
- air_loop_info['area_outdoor_air_rate_m_cu_per_s_m_sq'] = validate_optional(model.sqlFile.get.execAndReturnFirstDouble(sql_command), model, -1.0)
957
-
958
- air_loop_info['total_floor_area_served'] = 0.0
959
- air_loop_info['total_breathing_zone_outdoor_airflow_vbz'] = 0.0
960
- air_loop.thermalZones.sort.each do |zone|
961
- sql_command = " SELECT Value FROM TabularDataWithStrings
962
- WHERE ReportName='Standard62.1Summary'
963
- AND ReportForString='Entire Facility'
964
- AND TableName='Zone Ventilation Parameters'
965
- AND ColumnName='Breathing Zone Outdoor Airflow - Vbz'
966
- AND Units='m3/s'
967
- AND RowName='#{zone.name.get.to_s.upcase}' "
968
- vbz = validate_optional(model.sqlFile.get.execAndReturnFirstDouble(sql_command), model, 0)
969
- air_loop_info['total_breathing_zone_outdoor_airflow_vbz'] += vbz
970
- air_loop_info['total_floor_area_served'] += zone.floorArea
971
- end
972
- air_loop_info['outdoor_air_l_per_s'] = -1.0
973
- unless air_loop_info['area_outdoor_air_rate_m_cu_per_s_m_sq'] == -1.0
974
- air_loop_info['outdoor_air_l_per_s'] = air_loop_info['area_outdoor_air_rate_m_cu_per_s_m_sq'] * air_loop_info['total_floor_area_served'] * 1000
975
- end
976
-
977
- # SUpply Fan
978
- unless air_loop.supplyFan.empty?
979
- if air_loop.supplyFan.get.to_FanConstantVolume.is_initialized
980
- air_loop_info['supply_fan_type'] = 'CV'
981
- fan = air_loop.supplyFan.get.to_FanConstantVolume.get
982
- elsif air_loop.supplyFan.get.to_FanVariableVolume.is_initialized
983
- air_loop_info['supply_fan_type'] = 'VV'
984
- fan = air_loop.supplyFan.get.to_FanVariableVolume.get
985
- end
986
- air_loop_info['supply_fan_name'] = fan.name.get
987
- air_loop_info['supply_fan_efficiency'] = fan.fanEfficiency
988
- air_loop_info['supply_fan_motor_efficiency'] = fan.motorEfficiency
989
- air_loop_info['supply_fan_pressure_rise'] = fan.pressureRise
990
- air_loop_info['supply_fan_max_air_flow_rate_m_cu_per_s'] = -1.0
991
- sql_command = " SELECT RowName FROM TabularDataWithStrings
992
- WHERE ReportName='EquipmentSummary'
993
- AND ReportForString='Entire Facility'
994
- AND TableName='Fans'
995
- AND ColumnName='Max Air Flow Rate'
996
- AND Units='m3/s' "
997
- max_air_flow_info = model.sqlFile.get.execAndReturnVectorOfString(sql_command)
998
- max_air_flow_info = validate_optional(max_air_flow_info, model, 'N/A')
999
- if max_air_flow_info != 'N/A'
1000
- if max_air_flow_info.include?(air_loop_info['supply_fan_name'].to_s.upcase)
1001
- sql_command = " SELECT Value FROM TabularDataWithStrings
1002
- WHERE ReportName='EquipmentSummary'
1003
- AND ReportForString='Entire Facility'
1004
- AND TableName='Fans'
1005
- AND ColumnName='Max Air Flow Rate'
1006
- AND Units='m3/s'
1007
- AND RowName='#{air_loop_info['supply_fan_name'].upcase}' "
1008
- air_loop_info['supply_fan_max_air_flow_rate_m_cu_per_s'] = model.sqlFile.get.execAndReturnFirstDouble(sql_command).get
1009
- sql_coommand = " SELECT Value FROM TabularDataWithStrings
1010
- WHERE ReportName='EquipmentSummary'
1011
- AND ReportForString='Entire Facility'
1012
- AND TableName='Fans'
1013
- AND ColumnName='Rated Electric Power'
1014
- AND Units='W'
1015
- AND RowName='#{air_loop_info['supply_fan_name'].upcase}' "
1016
- air_loop_info['supply_fan_rated_electric_power_w'] = model.sqlFile.get.execAndReturnFirstDouble(sql_coommand).get
1017
- else
1018
- @error_warning << "#{air_loop_info['supply_fan_name']} does not exist in sql file WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Max Air Flow Rate' AND Units='m3/s'"
1019
- end
1020
- else
1021
- @error_warning << "max_air_flow_info is nil because the following sql statement returned nil: RowName FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Max Air Flow Rate' AND Units='m3/s' "
1022
- end
1023
- end
1024
-
1025
- # Fan
1026
- unless air_loop.returnFan.empty?
1027
- air_loop_info['return_fan'] = {}
1028
- if air_loop.returnFan.get.to_FanConstantVolume.is_initialized
1029
- air_loop_info['return_fan_type'] = 'CV'
1030
- fan = air_loop.returnFan.get.to_FanConstantVolume.get
1031
- elsif air_loop.returnFan.get.to_FanVariableVolume.is_initialized
1032
- air_loop_info['return_fan_type'] = 'VV'
1033
- fan = air_loop.returnFan.get.to_FanVariableVolume.get
1034
- end
1035
- air_loop_info['return_fan_name'] = fan.name.get
1036
- air_loop_info['return_fan_efficiency'] = fan.fanEfficiency
1037
- air_loop_info['return_fan_motor_efficiency'] = fan.motorEfficiency
1038
- air_loop_info['return_fan_pressure_rise'] = fan.pressureRise
1039
- air_loop_info['return_fan_max_air_flow_rate_m_cu_per_s'] = -1.0
1040
- sql_command = " SELECT RowName FROM TabularDataWithStrings
1041
- WHERE ReportName='EquipmentSummary'
1042
- AND ReportForString='Entire Facility'
1043
- AND TableName='Fans'
1044
- AND ColumnName='Max Air Flow Rate'
1045
- AND Units='m3/s' "
1046
- max_air_flow_info = model.sqlFile.get.execAndReturnVectorOfString(sql_command)
1047
- max_air_flow_info = validate_optional(max_air_flow_info, model, 'N/A')
1048
- if max_air_flow_info != 'N/A'
1049
- if max_air_flow_info.include?(air_loop_info['return_fan_name'].to_s.upcase.to_s)
1050
- sql_command = " SELECT Value FROM TabularDataWithStrings
1051
- WHERE ReportName='EquipmentSummary'
1052
- AND ReportForString='Entire Facility'
1053
- AND TableName='Fans'
1054
- AND ColumnName='Max Air Flow Rate'
1055
- AND Units='m3/s'
1056
- AND RowName='#{air_loop_info['return_fan_name'].upcase}' "
1057
- air_loop_info['return_fan_max_air_flow_rate_m_cu_per_s'] = model.sqlFile.get.execAndReturnFirstDouble(sql_command).get
1058
- sql_coommand = " SELECT Value FROM TabularDataWithStrings
1059
- WHERE ReportName='EquipmentSummary'
1060
- AND ReportForString='Entire Facility'
1061
- AND TableName='Fans'
1062
- AND ColumnName='Rated Electric Power'
1063
- AND Units='W'
1064
- AND RowName='#{air_loop_info['return_fan_name'].upcase}' "
1065
- air_loop_info['return_fan_rated_electric_power_w'] = model.sqlFile.get.execAndReturnFirstDouble(sql_coommand).get
1066
- else
1067
- @error_warning << "#{air_loop_info['return_fan_name']} does not exist in sql file WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Max Air Flow Rate' AND Units='m3/s'"
1068
- end
1069
- else
1070
- @error_warning << "max_air_flow_info is nil because the following sql statement returned nil: RowName FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName='Fans' AND ColumnName='Max Air Flow Rate' AND Units='m3/s' "
1071
- end
1072
- end
1073
-
1074
- # economizer
1075
- air_loop_info['economizer_name'] = air_loop.airLoopHVACOutdoorAirSystem.get.getControllerOutdoorAir.name.get
1076
- air_loop_info['economizer_control_type'] = air_loop.airLoopHVACOutdoorAirSystem.get.getControllerOutdoorAir.getEconomizerControlType
1077
-
1078
- # Include air system costs from ventilation costs
1079
- sysNum = air_loop_info['name'][4].to_i
1080
- sysCostInfo = cost_result['ventilation']["system_#{sysNum}".to_s.to_sym].detect { |currsysCostInfo| currsysCostInfo[:name].to_s.downcase == air_loop_info['name'].downcase }
1081
- if sysCostInfo.nil? && sysNum == 4
1082
- # 04-Nov-2019 JTB: CK says that system_4 is handled the same way as system_1 so may see "Sys_4" substrings in the name of a system_1 airloop!
1083
- sysCostInfo = cost_result['ventilation']['system_1'.to_s.to_sym].detect { |currsysCostInfo| currsysCostInfo[:name].to_s.downcase == air_loop_info['name'].downcase }
1084
- raise("System name \"#{air_loop_info['name']}\" not found in ventilation cost info for system_1") if sysCostInfo.nil?
1085
- else
1086
- raise("System name \"#{air_loop_info['name']}\" not found in ventilation cost info for System_#{sysNum}") if sysCostInfo.nil?
1087
- end
1088
- if !sysCostInfo.nil?
1089
- air_loop_info['airloop_flow_m3_per_s'] = sysCostInfo[:airloop_flow_m3_per_s]
1090
- air_loop_info['num_rooftop_units'] = sysCostInfo[:num_rooftop_units]
1091
- # air_loop_info['ahu_counter'] = sysCostInfo[:ahu_counter]
1092
- # air_loop_info['ahu_l_per_s'] = sysCostInfo[:ahu_l_per_s]
1093
- air_loop_info['base_ahu_cost'] = sysCostInfo[:base_ahu_cost]
1094
- air_loop_info['revised_base_ahu_cost'] = sysCostInfo[:revised_base_ahu_cost]
1095
- # Promote hrv data from it's own hash, if it isn't empty!
1096
- if sysCostInfo[:hrv].empty?
1097
- air_loop_info['hrv_type'] = sysCostInfo[:hrv]
1098
- else
1099
- air_loop_info['hrv_type'] = sysCostInfo[:hrv][:hrv_type]
1100
- air_loop_info['hrv_name'] = sysCostInfo[:hrv][:hrv_name]
1101
- air_loop_info['hrv_size_m3ps'] = sysCostInfo[:hrv][:hrv_size_m3ps]
1102
- air_loop_info['hrv_return_fan_size_m3ps'] = sysCostInfo[:hrv][:hrv_return_fan_size_m3ps]
1103
- air_loop_info['hrv_cost'] = sysCostInfo[:hrv][:hrv_cost]
1104
- air_loop_info['revised_hrv_cost'] = sysCostInfo[:hrv][:revised_hrv_cost]
1105
- end
1106
- end
1107
-
1108
- # Also include hrv_return_ducting information from ventilation costs.
1109
- # Note that there can be multiple hrv return duct runs for the same air loop name
1110
- # (on different floors) plus a system level trunk return duct section.
1111
- hrv_retduct_byflr = cost_result['ventilation']['hrv_return_ducting'.to_sym].reject { |arr| arr[:floor].nil? }
1112
- hrv_retduct_bysys = cost_result['ventilation']['hrv_return_ducting'.to_sym].reject { |arr| arr[:air_system].nil? }
1113
-
1114
- # Floor level hrv return ducts...
1115
- hrv_retduct_byflr.each do |arr1|
1116
- flrNum = arr1[:floor].to_s[15].to_i
1117
- airsys_byflr = arr1[:air_systems].select { |arr2| arr2[:air_system].to_s.downcase == air_loop_info['name'].to_s.downcase }
1118
- airsys_byflr.each do |arr3|
1119
- air_loop_info["flr#{flrNum}_floor_mult".to_sym] = arr3[:floor_mult]
1120
- if !arr3[:hrv_ret_trunk].empty?
1121
- # The hrv_ret_trunk embedded hash is not empty -- promote it
1122
- air_loop_info["flr#{flrNum}_hrv_ret_trunk_len_m".to_sym] = arr3[:hrv_ret_trunk][:duct_length_m]
1123
- air_loop_info["flr#{flrNum}_hrv_ret_trunk_dia_in".to_sym] = arr3[:hrv_ret_trunk][:dia_in]
1124
- air_loop_info["flr#{flrNum}_hrv_ret_trunk_cost".to_sym] = arr3[:hrv_ret_trunk][:cost]
1125
- # If not don't include anything!
1126
- end
1127
- # The individual thermal zone (by floor) ret duct distribution (tz_dist) added to zone_table
1128
- end
1129
- end
1130
- # System level trunk hrv return ducts...
1131
- hrv_retduct_bysys.each do |arr1|
1132
- if arr1[:air_system].to_s.downcase == air_loop_info['name'].to_s.downcase
1133
- air_loop_info[:hrv_building_trunk_length_m] = arr1[:hrv_building_trunk_length_m]
1134
- air_loop_info[:hrv_building_trunk_dia_in] = arr1[:hrv_building_trunk_dia_in]
1135
- air_loop_info["sys#{sysNum}_hrv_ret_duct_cost".to_sym] = arr1[:cost]
1136
- end
1137
- end
1138
-
1139
- table << air_loop_info
1140
- end
1141
-
1142
- return table
1143
- end
1144
-
1145
- def coil_table
1146
- table = get_sql_table_to_json(model, 'CoilSizingDetails', 'Entire Facility', 'Coils')['table']
1147
- model.getAirLoopHVACs.sort.each do |air_loop|
1148
- air_loop.supplyComponents.each do |supply_comp|
1149
- object = get_actual_child_object(supply_comp)
1150
- coil_types = [
1151
- 'openstudio::model::CoilCoolingCooledBeam',
1152
- 'openstudio::model::CoilCoolingDXMultiSpeed',
1153
- 'openstudio::model::CoilCoolingDXSingleSpeed',
1154
- 'openstudio::model::CoilCoolingDXTwoSpeed',
1155
- 'openstudio::model::CoilCoolingDXTwoStageWithHumidityControlMode',
1156
- 'openstudio::model::CoilCoolingDXVariableSpeed',
1157
- 'openstudio::model::CoilCoolingFourPipeBeam',
1158
- 'openstudio::model::CoilCoolingLowTempRadiantConstFlow',
1159
- 'openstudio::model::CoilCoolingLowTempRadiantVarFlow',
1160
- 'openstudio::model::CoilHeatingDesuperheater',
1161
- 'openstudio::model::CoilHeatingDXMultiSpeed',
1162
- 'openstudio::model::CoilHeatingDXSingleSpeed',
1163
- 'openstudio::model::CoilHeatingDXVariableSpeed',
1164
- 'openstudio::model::CoilHeatingElectric',
1165
- 'openstudio::model::CoilHeatingFourPipeBeam',
1166
- 'openstudio::model::CoilHeatingGas',
1167
- 'openstudio::model::CoilHeatingGasMultiStage',
1168
- 'openstudio::model::CoilHeatingLowTempRadiantConstFlow',
1169
- 'openstudio::model::CoilHeatingLowTempRadiantVarFlow',
1170
- 'openstudio::model::CoilHeatingWaterBaseboard',
1171
- 'openstudio::model::CoilHeatingWaterBaseboardRadiant',
1172
- 'openstudio::model::CoilSystemCoolingDXHeatExchangerAssisted',
1173
- 'openstudio::model::CoilSystemCoolingWaterHeatExchangerAssisted',
1174
- 'openstudio::model::CoilWaterHeatingDesuperheater'
1175
- ]
1176
- # Is it a heating coil?
1177
- if coil_types.include?(object.class.name) && object.class.name.include?('Heating')
1178
-
1179
- case object.class.name
1180
- when 'CoilHeatingGas'
1181
- coil = {}
1182
- coil['name'] = object.get.name
1183
- coil['type'] = 'Gas'
1184
- coil['efficency'] = gas.gasBurnerEfficiency
1185
- sql_command = "SELECT Value FROM TabularDataWithStrings
1186
- WHERE ReportName='EquipmentSummary'
1187
- AND ReportForString='Entire Facility'
1188
- AND TableName='Heating Coils'
1189
- AND ColumnName='Nominal Total Capacity'
1190
- AND RowName='#{coil['name'].to_s.upcase}'"
1191
- coil['nominal_total_capacity_w'] = validate_optional(@model.sqlFile.get.execAndReturnFirstDouble(sql_command), @model, -1.0)
1192
-
1193
- when 'CoilHeatingElectric'
1194
- when 'CoilHeatingWater'
1195
- end
1196
-
1197
- if supply_comp.to_CoilHeatingGas.is_initialized
1198
- coil = {}
1199
- air_loop_info['heating_coils']['coil_heating_gas'] << coil
1200
- gas = supply_comp.to_CoilHeatingGas.get
1201
- coil['name'] = gas.name.get
1202
- coil['type'] = 'Gas'
1203
- coil['efficency'] = gas.gasBurnerEfficiency
1204
- sql_command = "SELECT Value FROM TabularDataWithStrings
1205
- WHERE ReportName='EquipmentSummary'
1206
- AND ReportForString='Entire Facility'
1207
- AND TableName='Heating Coils'
1208
- AND ColumnName='Nominal Total Capacity'
1209
- AND RowName='#{coil['name'].to_s.upcase}'"
1210
- coil['nominal_total_capacity_w'] = validate_optional(@model.sqlFile.get.execAndReturnFirstDouble(sql_command), @model, -1.0)
1211
- end
1212
- if supply_comp.to_CoilHeatingElectric.is_initialized
1213
- coil = {}
1214
- air_loop_info['heating_coils']['coil_heating_electric'] << coil
1215
- electric = supply_comp.to_CoilHeatingElectric.get
1216
- coil['name'] = electric.name.get
1217
- coil['type'] = 'Electric'
1218
- sql_command = "SELECT Value FROM TabularDataWithStrings
1219
- WHERE ReportName='EquipmentSummary'
1220
- AND ReportForString='Entire Facility'
1221
- AND TableName='Heating Coils'
1222
- AND ColumnName='Nominal Total Capacity'
1223
- AND RowName='#{coil['name'].to_s.upcase}'"
1224
- coil['nominal_total_capacity_w'] = validate_optional(model.sqlFile.get.execAndReturnFirstDouble(sql_command), model, -1.0)
1225
- end
1226
- if supply_comp.to_CoilHeatingWater.is_initialized
1227
- coil = {}
1228
- air_loop_info['heating_coils']['coil_heating_water'] << coil
1229
- water = supply_comp.to_CoilHeatingWater.get
1230
- coil['name'] = water.name.get
1231
- coil['type'] = 'Water'
1232
- sql_command = "SELECT Value FROM TabularDataWithStrings
1233
- WHERE ReportName='EquipmentSummary'
1234
- AND ReportForString='Entire Facility'
1235
- AND TableName='Heating Coils'
1236
- AND ColumnName='Nominal Total Capacity'
1237
- AND RowName='#{coil['name'].to_s.upcase}'"
1238
- coil['nominal_total_capacity_w'] = validate_optional(model.sqlFile.get.execAndReturnFirstDouble(sql_command), model, -1.0)
1239
- end
1240
- end
1241
-
1242
- # I dont think i need to get the type of heating coil from the sql file, because the coils are differentiated by class, and I have hard coded the information
1243
- # model.sqlFile().get().execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND TableName= 'Heating Coils' AND ColumnName='Type' ").get #padmussen to complete #AND RowName='#{air_loop_info["heating_coils"]["name"].upcase}'
1244
- #
1245
- # Collect all the fans into the the array.
1246
- air_loop.supplyComponents.each do |curr_supply_comp|
1247
- if curr_supply_comp.to_CoilCoolingDXSingleSpeed.is_initialized
1248
- coil = {}
1249
- air_loop_info['cooling_coils']['dx_single_speed'] << coil
1250
- single_speed = curr_supply_comp.to_CoilCoolingDXSingleSpeed.get
1251
- coil['name'] = single_speed.name.get
1252
- coil['cop'] = single_speed.getRatedCOP.get
1253
- sql_command = "SELECT Value FROM TabularDataWithStrings
1254
- WHERE ReportName='EquipmentSummary'
1255
- AND ReportForString='Entire Facility'
1256
- AND TableName='Cooling Coils'
1257
- AND ColumnName='Nominal Total Capacity'
1258
- AND RowName='#{coil['name'].to_s.upcase}'"
1259
- coil['nominal_total_capacity_w'] = validate_optional(model.sqlFile.get.execAndReturnFirstDouble(sql_command), model, -1.0)
1260
- end
1261
- if curr_supply_comp.to_CoilCoolingDXTwoSpeed.is_initialized
1262
- coil = {}
1263
- air_loop_info['cooling_coils']['dx_two_speed'] << coil
1264
- two_speed = curr_supply_comp.to_CoilCoolingDXTwoSpeed.get
1265
- coil['name'] = two_speed.name.get
1266
- coil['cop_low'] = two_speed.getRatedLowSpeedCOP.get
1267
- coil['cop_high'] = two_speed.getRatedHighSpeedCOP.get
1268
- sql_command = "SELECT Value FROM TabularDataWithStrings
1269
- WHERE ReportName='EquipmentSummary'
1270
- AND ReportForString='Entire Facility'
1271
- AND TableName='Cooling Coils'
1272
- AND ColumnName='Nominal Total Capacity'
1273
- AND RowName='#{coil['name'].to_s.upcase}'"
1274
- coil['nominal_total_capacity_w'] = validate_optional(model.sqlFile.get.execAndReturnFirstDouble(sql_command), model, -1.0)
1275
- end
1276
- if curr_supply_comp.to_CoilCoolingWater.is_initialized
1277
- coil = {}
1278
- air_loop_info['cooling_coils']['coil_cooling_water'] << coil
1279
- coil_cooling_water = curr_supply_comp.to_CoilCoolingWater.get
1280
- coil['name'] = coil_cooling_water.name.get
1281
- sql_command = "SELECT Value FROM TabularDataWithStrings
1282
- WHERE ReportName='EquipmentSummary'
1283
- AND ReportForString='Entire Facility'
1284
- AND TableName='Cooling Coils'
1285
- AND ColumnName='Nominal Total Capacity'
1286
- AND RowName='#{coil['name'].to_s.upcase}'"
1287
- coil['nominal_total_capacity_w'] = validate_optional(model.sqlFile.get.execAndReturnFirstDouble(sql_command), model, -1.0)
1288
- sql_command = "SELECT Value FROM TabularDataWithStrings
1289
- WHERE ReportName='EquipmentSummary'
1290
- AND ReportForString='Entire Facility'
1291
- AND TableName='Cooling Coils'
1292
- AND ColumnName='Nominal Sensible Heat Ratio'
1293
- AND RowName='#{coil['name'].upcase}' "
1294
- coil['nominal_sensible_heat_ratio'] = validate_optional(model.sqlFile.get.execAndReturnFirstDouble(sql_command), model, -1.0)
1295
- end
1296
- end
1297
- end
1298
- end
1299
- end
1300
-
1301
- def coil_cost_table(cost_result)
1302
- sys_coils_table = []
1303
- (1..7).each do |sysNum|
1304
- sys_table = cost_result['ventilation']["system_#{sysNum}".to_s.to_sym].reject(&:empty?)
1305
- sys_table.each do |sysHash|
1306
- equip_info_table = sysHash[:equipment_info].reject(&:empty?)
1307
- equip_info_table.each do |equipHash|
1308
- sysCoilsInfo = {}
1309
- sys_coils_table << sysCoilsInfo
1310
- sysCoilsInfo[:sys_type] = sysNum
1311
- sysCoilsInfo[:sys_name] = equipHash[:name]
1312
- sysCoilsInfo[:eq_category] = equipHash[:eq_category]
1313
- sysCoilsInfo[:heating_fuel] = equipHash[:heating_fuel]
1314
- sysCoilsInfo[:cooling_type] = equipHash[:cooling_type]
1315
- sysCoilsInfo[:capacity_kw] = equipHash[:capacity_kw]
1316
- sysCoilsInfo[:coil_cost] = equipHash[:cost]
1317
- end
1318
- end
1319
- end
1320
- return sys_coils_table
1321
- end
1322
-
1323
- def terminal_VAV_cost_table(cost_result)
1324
- terminal_VAV_table = []
1325
- (1..7).each do |sysNum|
1326
- ndx = 0
1327
- until cost_result['ventilation']["system_#{sysNum}".to_s.to_sym][ndx].nil?
1328
- ndx1 = 0
1329
- until cost_result['ventilation']["system_#{sysNum}".to_s.to_sym][ndx][:reheat_recool][ndx1].nil?
1330
- sysTerminalInfo = {}
1331
- terminal_VAV_table << sysTerminalInfo
1332
- sysTerminalInfo[:sys_type] = sysNum
1333
- sysTerminalInfo[:sys_name] = cost_result['ventilation']["system_#{sysNum}".to_s.to_sym][ndx][:name]
1334
- sysTerminalInfo[:terminal] = cost_result['ventilation']["system_#{sysNum}".to_s.to_sym][ndx][:reheat_recool][ndx1][:terminal]
1335
- sysTerminalInfo[:zone_mult] = cost_result['ventilation']["system_#{sysNum}".to_s.to_sym][ndx][:reheat_recool][ndx1][:zone_mult]
1336
- sysTerminalInfo[:box_type] = cost_result['ventilation']["system_#{sysNum}".to_s.to_sym][ndx][:reheat_recool][ndx1][:box_type]
1337
- sysTerminalInfo[:box_name] = cost_result['ventilation']["system_#{sysNum}".to_s.to_sym][ndx][:reheat_recool][ndx1][:box_name]
1338
- sysTerminalInfo[:unit_size_kw] = cost_result['ventilation']["system_#{sysNum}".to_s.to_sym][ndx][:reheat_recool][ndx1][:unit_info][:size_kw]
1339
- sysTerminalInfo[:unit_air_flow_m3_per_s] = cost_result['ventilation']["system_#{sysNum}".to_s.to_sym][ndx][:reheat_recool][ndx1][:unit_info][:air_flow_m3_per_s]
1340
- sysTerminalInfo[:unit_pipe_dist_m] = cost_result['ventilation']["system_#{sysNum}".to_s.to_sym][ndx][:reheat_recool][ndx1][:unit_info][:pipe_dist_m]
1341
- sysTerminalInfo[:unit_elect_dist_m] = cost_result['ventilation']["system_#{sysNum}".to_s.to_sym][ndx][:reheat_recool][ndx1][:unit_info][:elect_dist_m]
1342
- sysTerminalInfo[:unit_num_units] = cost_result['ventilation']["system_#{sysNum}".to_s.to_sym][ndx][:reheat_recool][ndx1][:unit_info][:num_units]
1343
- sysTerminalInfo[:cost] = cost_result['ventilation']["system_#{sysNum}".to_s.to_sym][ndx][:reheat_recool][ndx1][:cost]
1344
- ndx1 += 1
1345
- end
1346
- ndx += 1
1347
- end
1348
- end
1349
- return terminal_VAV_table
1350
- end
1351
-
1352
- def trunk_ducts_cost_table(cost_result)
1353
- # Create a trunk ducts cost table that combines the building trunk duct with the floor trunk ducts
1354
- trunk_ducts_table = []
1355
- ndx = 0
1356
- insert_bld_trunk_duct = true
1357
- until cost_result['ventilation']['floor_trunk_ducts'.to_sym][0][ndx].nil?
1358
- if insert_bld_trunk_duct
1359
- trunkDuctsInfo = {}
1360
- trunk_ducts_table << trunkDuctsInfo
1361
- trunkDuctsInfo[:Floor] = 'building_trunk'
1362
- trunkDuctsInfo[:Predominant_space_type] = 'n/a'
1363
- trunkDuctsInfo[:SupplyDuctSize_in] = cost_result['ventilation']['trunk_duct'.to_sym][ndx][:DuctSize_in]
1364
- trunkDuctsInfo[:SupplyDuctLength_m] = cost_result['ventilation']['trunk_duct'.to_sym][ndx][:DuctLength_m]
1365
- if cost_result['ventilation']['trunk_duct'.to_sym][ndx][:NumberRuns] == 2
1366
- trunkDuctsInfo[:ReturnDuctSize_in] = cost_result['ventilation']['trunk_duct'.to_sym][ndx][:DuctSize_in]
1367
- trunkDuctsInfo[:ReturnDuctLength_m] = cost_result['ventilation']['trunk_duct'.to_sym][ndx][:DuctLength_m]
1368
- else
1369
- trunkDuctsInfo[:ReturnDuctSize_in] = 0
1370
- trunkDuctsInfo[:ReturnDuctLength_m] = 0
1371
- end
1372
- trunkDuctsInfo[:TotalDuctCost] = cost_result['ventilation']['trunk_duct'.to_sym][ndx][:DuctCost]
1373
- trunkDuctsInfo[:Multiplier] = 1.0
1374
- insert_bld_trunk_duct = false
1375
- else
1376
- trunkDuctsInfo = {}
1377
- trunk_ducts_table << trunkDuctsInfo
1378
- cost_result['ventilation']['floor_trunk_ducts'.to_sym][0][ndx].each do |k, v|
1379
- trunkDuctsInfo[k] = v
1380
- end
1381
- ndx += 1
1382
- end
1383
- end
1384
- return trunk_ducts_table
1385
- end
1386
-
1387
- def plant_loop_table(model)
1388
- table = []
1389
- model.getPlantLoops.sort.each do |plant_loop|
1390
- plant_loop_info = {}
1391
- table << plant_loop_info
1392
- plant_loop_info['name'] = plant_loop.name.get
1393
-
1394
- sizing = plant_loop.sizingPlant
1395
- plant_loop_info['design_loop_exit_temperature'] = sizing.getDesignLoopExitTemperature.value
1396
- plant_loop_info['loop_design_temperature_difference'] = sizing.getLoopDesignTemperatureDifference.value
1397
-
1398
- # Create Container for plant equipment arrays.
1399
- plant_loop_info['pumps'] = []
1400
- plant_loop_info['boilers'] = []
1401
- plant_loop_info['chiller_electric_eir'] = []
1402
- plant_loop_info['cooling_tower_single_speed'] = []
1403
- plant_loop_info['water_heater_mixed'] = []
1404
- plant_loop.supplyComponents.each do |supply_comp|
1405
- # Collect Constant Speed
1406
- if supply_comp.to_PumpConstantSpeed.is_initialized
1407
- pump = supply_comp.to_PumpConstantSpeed.get
1408
- pump_info = {}
1409
- plant_loop_info['pumps'] << pump_info
1410
- pump_info['name'] = pump.name.get
1411
- pump_info['type'] = 'Pump:ConstantSpeed'
1412
- sql_command = " SELECT Value FROM TabularDataWithStrings
1413
- WHERE ReportName='EquipmentSummary'
1414
- AND ReportForString='Entire Facility'
1415
- AND TableName='Pumps'
1416
- AND ColumnName='Head'
1417
- AND RowName='#{pump_info['name'].upcase}' "
1418
- pump_info['head_pa'] = validate_optional(model.sqlFile.get.execAndReturnFirstDouble(sql_command), model, -1.0)
1419
- sql_command = " SELECT Value FROM TabularDataWithStrings
1420
- WHERE ReportName='EquipmentSummary'
1421
- AND ReportForString='Entire Facility'
1422
- AND TableName='Pumps'
1423
- AND ColumnName='Water Flow'
1424
- AND RowName='#{pump_info['name'].upcase}' "
1425
- pump_info['water_flow_m_cu_per_s'] = validate_optional(model.sqlFile.get.execAndReturnFirstDouble(sql_command), model, -1.0)
1426
- sql_command = " SELECT Value FROM TabularDataWithStrings
1427
- WHERE ReportName='EquipmentSummary'
1428
- AND ReportForString='Entire Facility'
1429
- AND TableName='Pumps'
1430
- AND ColumnName='Electric Power'
1431
- AND RowName='#{pump_info['name'].upcase}' "
1432
- pump_info['electric_power_w'] = validate_optional(model.sqlFile.get.execAndReturnFirstDouble(sql_command), model, -1.0)
1433
- pump_info['motor_efficency'] = pump.getMotorEfficiency.value
1434
- end
1435
-
1436
- # Collect Variable Speed
1437
- if supply_comp.to_PumpVariableSpeed.is_initialized
1438
- pump = supply_comp.to_PumpVariableSpeed.get
1439
- pump_info = {}
1440
- plant_loop_info['pumps'] << pump_info
1441
- pump_info['name'] = pump.name.get
1442
- pump_info['type'] = 'Pump:VariableSpeed'
1443
- sql_command = " SELECT Value FROM TabularDataWithStrings
1444
- WHERE ReportName='EquipmentSummary'
1445
- AND ReportForString='Entire Facility'
1446
- AND TableName='Pumps'
1447
- AND ColumnName='Head'
1448
- AND RowName='#{pump_info['name'].upcase}' "
1449
- pump_info['head_pa'] = validate_optional(model.sqlFile.get.execAndReturnFirstDouble(sql_command), model, -1.0)
1450
- sql_command = " SELECT Value FROM TabularDataWithStrings
1451
- WHERE ReportName='EquipmentSummary'
1452
- AND ReportForString='Entire Facility'
1453
- AND TableName='Pumps'
1454
- AND ColumnName='Water Flow'
1455
- AND RowName='#{pump_info['name'].upcase}' "
1456
- pump_info['water_flow_m_cu_per_s'] = validate_optional(model.sqlFile.get.execAndReturnFirstDouble(sql_command), model, -1.0)
1457
- sql_command = " SELECT Value FROM TabularDataWithStrings
1458
- WHERE ReportName='EquipmentSummary'
1459
- AND ReportForString='Entire Facility'
1460
- AND TableName='Pumps'
1461
- AND ColumnName='Electric Power'
1462
- AND RowName='#{pump_info['name'].upcase}' "
1463
- pump_info['electric_power_w'] = validate_optional(model.sqlFile.get.execAndReturnFirstDouble(sql_command), model, -1.0)
1464
- pump_info['motor_efficency'] = pump.getMotorEfficiency.value
1465
- end
1466
-
1467
- # Collect HotWaterBoilers
1468
- if supply_comp.to_BoilerHotWater.is_initialized
1469
- boiler = supply_comp.to_BoilerHotWater.get
1470
- boiler_info = {}
1471
- plant_loop_info['boilers'] << boiler_info
1472
- boiler_info['name'] = boiler.name.get
1473
- boiler_info['type'] = 'Boiler:HotWater'
1474
- boiler_info['fueltype'] = boiler.fuelType
1475
- boiler_info['nominal_capacity'] = validate_optional(boiler.nominalCapacity, model, -1.0)
1476
- end
1477
-
1478
- # Collect ChillerElectricEIR
1479
- if supply_comp.to_ChillerElectricEIR.is_initialized
1480
- chiller = supply_comp.to_ChillerElectricEIR.get
1481
- chiller_info = {}
1482
- plant_loop_info['chiller_electric_eir'] << chiller_info
1483
- chiller_info['name'] = chiller.name.get
1484
- chiller_info['type'] = 'Chiller:Electric:EIR'
1485
- chiller_info['reference_capacity'] = validate_optional(chiller.referenceCapacity, model, -1.0)
1486
- chiller_info['reference_leaving_chilled_water_temperature'] = chiller.referenceLeavingChilledWaterTemperature
1487
- end
1488
-
1489
- # Collect CoolingTowerSingleSpeed
1490
- if supply_comp.to_CoolingTowerSingleSpeed.is_initialized
1491
- coolingTower = supply_comp.to_CoolingTowerSingleSpeed.get
1492
- coolingTower_info = {}
1493
- plant_loop_info['cooling_tower_single_speed'] << coolingTower_info
1494
- coolingTower_info['name'] = coolingTower.name.get
1495
- coolingTower_info['type'] = 'CoolingTower:SingleSpeed'
1496
- coolingTower_info['fan_power_at_design_air_flow_rate'] = validate_optional(coolingTower.fanPoweratDesignAirFlowRate, model, -1.0)
1497
- end
1498
-
1499
- # Collect WaterHeaterMixed
1500
- if supply_comp.to_WaterHeaterMixed.is_initialized
1501
- waterHeaterMixed = supply_comp.to_WaterHeaterMixed.get
1502
- waterHeaterMixed_info = {}
1503
- plant_loop_info['water_heater_mixed'] << waterHeaterMixed_info
1504
- waterHeaterMixed_info['name'] = waterHeaterMixed.name.get
1505
- waterHeaterMixed_info['type'] = 'WaterHeater:Mixed'
1506
- waterHeaterMixed_info['heater_thermal_efficiency'] = waterHeaterMixed.heaterThermalEfficiency.get unless waterHeaterMixed.heaterThermalEfficiency.empty?
1507
- waterHeaterMixed_info['heater_fuel_type'] = waterHeaterMixed.heaterFuelType
1508
- end
1509
- end
1510
- end
1511
- return table
1512
- end
1513
-
1514
- def eplusout_err_table(model)
1515
- table = []
1516
- warnings = model.sqlFile.get.execAndReturnVectorOfString("SELECT ErrorMessage FROM Errors WHERE ErrorType='0' ")
1517
- warnings = validate_optional(warnings, model, 'N/A')
1518
- unless warnings == 'N/A'
1519
- messages = model.sqlFile.get.execAndReturnVectorOfString("SELECT ErrorMessage FROM Errors WHERE ErrorType='0' ").get
1520
- messages.each do |message|
1521
- table << { 'error_type' => 'warning', 'message' => message }
1522
- end
1523
- messages = model.sqlFile.get.execAndReturnVectorOfString("SELECT ErrorMessage FROM Errors WHERE ErrorType='1' ").get
1524
- messages.each do |message|
1525
- table << { 'error_type' => 'severe', 'message' => message }
1526
- end
1527
- messages = model.sqlFile.get.execAndReturnVectorOfString("SELECT ErrorMessage FROM Errors WHERE ErrorType='2' ").get
1528
- messages.each do |message|
1529
- table << { 'error_type' => 'fatal', 'message' => message }
1530
- end
1531
- end
1532
- return table
1533
- end
1534
-
1535
- def energy_peak_data
1536
- # Primary heaing source
1537
- data = {}
1538
- command = "SELECT Value
1539
- FROM TabularDataWithStrings
1540
- WHERE ReportName='LEEDsummary'
1541
- AND ReportForString='Entire Facility'
1542
- AND TableName='Sec1.1A-General Information'
1543
- AND RowName = 'Principal Heating Source'
1544
- AND ColumnName='Data'"
1545
- value = @sqlite_file.get.execAndReturnFirstString(command)
1546
- # make sure all the data are availalbe
1547
-
1548
- data['energy_principal_heating_source'] = 'unknown'
1549
- unless value.empty?
1550
- data['energy_principal_heating_source'] = value.get
1551
- end
1552
-
1553
- # Peaks
1554
- electric_peak = @model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='EnergyMeters'" \
1555
- " AND ReportForString='Entire Facility' AND TableName='Annual and Peak Values - Electricity' AND RowName='Electricity:Facility'" \
1556
- " AND ColumnName='Electricity Maximum Value' AND Units='W'")
1557
- natural_gas_peak = @model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM tabulardatawithstrings WHERE ReportName='EnergyMeters'" \
1558
- " AND ReportForString='Entire Facility' AND TableName='Annual and Peak Values - Natural Gas' AND RowName='NaturalGas:Facility'" \
1559
- " AND ColumnName='Natural Gas Maximum Value' AND Units='W'")
1560
- data['energy_peak_electric_w_per_m_sq'] = electric_peak.empty? ? 0.0 : electric_peak.get / @conditioned_floor_area_m_sq
1561
- data['energy_peak_natural_gas_w_per_m_sq'] = natural_gas_peak.empty? ? 0.0 : natural_gas_peak.get / @conditioned_floor_area_m_sq
1562
-
1563
- # Peak heating load # @todo IMPORTANT NOTE: Peak heating load must be updated if a combination of fuel types is used in a building model.
1564
- command = "SELECT Value
1565
- FROM TabularDataWithStrings
1566
- WHERE ReportName='EnergyMeters'
1567
- AND ReportForString='Entire Facility'
1568
- AND TableName='Annual and Peak Values - Electricity'
1569
- AND RowName='Heating:Electricity'
1570
- AND ColumnName='Electricity Maximum Value'
1571
- AND Units='W'"
1572
- heating_peak_w_electricity = @sqlite_file.get.execAndReturnFirstDouble(command)
1573
- command = "SELECT Value
1574
- FROM TabularDataWithStrings
1575
- WHERE ReportName='EnergyMeters'
1576
- AND ReportForString='Entire Facility'
1577
- AND TableName='Annual and Peak Values - Natural Gas'
1578
- AND RowName='Heating:NaturalGas'
1579
- AND ColumnName='Natural Gas Maximum Value'
1580
- AND Units='W'"
1581
- heating_peak_w_gas = @sqlite_file.get.execAndReturnFirstDouble(command)
1582
- heating_peak_w = [heating_peak_w_electricity.to_f, heating_peak_w_gas.to_f].max
1583
- data['heating_peak_w_per_m_sq'] = heating_peak_w / @conditioned_floor_area_m_sq
1584
-
1585
- # Peak cooling load # @todo IMPORTANT NOTE: Peak cooling load must be updated if a combination of fuel types is used in a building model.
1586
- command = "SELECT Value
1587
- FROM TabularDataWithStrings
1588
- WHERE ReportName='EnergyMeters'
1589
- AND ReportForString='Entire Facility'
1590
- AND TableName='Annual and Peak Values - Electricity'
1591
- AND RowName='Cooling:Electricity'
1592
- AND ColumnName='Electricity Maximum Value'
1593
- AND Units='W'"
1594
- cooling_peak_w_electricity = @sqlite_file.get.execAndReturnFirstDouble(command)
1595
- command = "SELECT Value
1596
- FROM TabularDataWithStrings
1597
- WHERE ReportName='EnergyMeters'
1598
- AND ReportForString='Entire Facility'
1599
- AND TableName='Annual and Peak Values - Natural Gas'
1600
- AND RowName='Cooling:Electricity'
1601
- AND ColumnName='Electricity Maximum Value'
1602
- AND Units='W'"
1603
- cooling_peak_w_gas = @sqlite_file.get.execAndReturnFirstDouble(command)
1604
- cooling_peak_w = [cooling_peak_w_electricity.to_f, cooling_peak_w_gas.to_f].max
1605
- data['cooling_peak_w_per_m_sq'] = cooling_peak_w / @conditioned_floor_area_m_sq
1606
-
1607
- # Peak water system load # @todo IMPORTANT NOTE: Peak water system load must be updated if a combination of fuel types is used in a building model.
1608
- command = "SELECT Value
1609
- FROM TabularDataWithStrings
1610
- WHERE ReportName='EnergyMeters'
1611
- AND ReportForString='Entire Facility'
1612
- AND TableName='Annual and Peak Values - Electricity'
1613
- AND RowName='WaterSystems:Electricity'
1614
- AND ColumnName='Electricity Maximum Value'
1615
- AND Units='W'"
1616
- water_heating_peak_w_electricity = @sqlite_file.get.execAndReturnFirstDouble(command)
1617
- command = "SELECT Value
1618
- FROM TabularDataWithStrings
1619
- WHERE ReportName='EnergyMeters'
1620
- AND ReportForString='Entire Facility'
1621
- AND TableName='Annual and Peak Values - Natural Gas'
1622
- AND RowName='WaterSystems:NaturalGas'
1623
- AND ColumnName='Natural Gas Maximum Value'
1624
- AND Units='W'"
1625
- water_heating_peak_w_gas = @sqlite_file.get.execAndReturnFirstDouble(command)
1626
- water_heating_peak_w = [water_heating_peak_w_electricity.to_f, water_heating_peak_w_gas.to_f].max
1627
- data['energy_peak_water_systems_w_per_m_sq'] = water_heating_peak_w / @conditioned_floor_area_m_sq
1628
-
1629
- #===================================================================================================================
1630
- # Winter peak values
1631
- ### Get number of timesteps from the model
1632
- number_of_timesteps_per_hour = 1
1633
- timestep_second = 3600 / number_of_timesteps_per_hour
1634
- #===================================================================================================================
1635
- ### Calculate number of timesteps of the whole year, and create dates/times for the whole year
1636
- number_of_timesteps_of_year = 365 * 24 * number_of_timesteps_per_hour
1637
- timesteps_of_year = []
1638
- d = Time.new(2006, 1, 1, 0)
1639
- (0...number_of_timesteps_of_year).each do |increment|
1640
- timesteps_of_year << (d + (60 * 60 / number_of_timesteps_per_hour) * increment).strftime('%Y-%m-%d %H:%M')
1641
- end
1642
- # Find month of each timestep
1643
- keys_month = timesteps_of_year.map{ |timestep| DateTime.parse(timestep).to_date.month }
1644
- # Peak electricity in winter
1645
- query = "
1646
- SELECT ReportDataDictionaryIndex
1647
- FROM ReportDataDictionary
1648
- WHERE Name=='Electricity:Facility' AND ReportingFrequency == 'Hourly' AND Units == 'J'
1649
- "
1650
- index_timestep_electricity = @model.sqlFile.get.execAndReturnVectorOfInt(query).get[0]
1651
- number_of_timesteps_per_hour = 1
1652
- if index_timestep_electricity.nil?
1653
- puts "No hourly or timestep data available. Peak data not available"
1654
- data['energy_peak_electric_w_per_m_sq_winter'] = 0
1655
- else
1656
- ### Get timestep output for Electricity:Facility output.
1657
- query = "
1658
- Select VariableValue
1659
- FROM ReportVariableData
1660
- WHERE
1661
- ReportVariableDataDictionaryIndex = #{index_timestep_electricity}
1662
- "
1663
- timestep_values = @model.sqlFile.get.execAndReturnVectorOfDouble(query).get
1664
- # Create a hash where its keys are months and values for eahc month are electricity energy use (J) of all timesteps for that month
1665
- hash_months_electricity = keys_month.zip(timestep_values).group_by(&:first).map{|key, value| [key, value.map(&:last)]}.to_h
1666
- # Find maximum of electricity energy use (J) among all timesteps for each month of the year
1667
- monthly_peak_J = hash_months_electricity.map { |key, value| [key, value.max()] }.to_h
1668
- ### Convert J to W of maximum electricity energy use (J)
1669
- monthly_peak_W = monthly_peak_J.map { |key, value| [key, value / timestep_second] }.to_h
1670
- # Get winter peak
1671
- winter_peak_W = [monthly_peak_W[12].to_f, monthly_peak_W[1].to_f, monthly_peak_W[2].to_f].max
1672
- ### Normalize by building floor area
1673
- annual_peak_W_per_m_sq = winter_peak_W / @btap_data['bldg_conditioned_floor_area_m_sq'].to_f
1674
- data['energy_peak_electric_w_per_m_sq_winter'] = annual_peak_W_per_m_sq
1675
- end
1676
-
1677
- # Peak electricity heating in winter
1678
- query = "
1679
- SELECT ReportDataDictionaryIndex
1680
- FROM ReportDataDictionary
1681
- WHERE Name=='Heating:Electricity' AND ReportingFrequency == 'Hourly' AND Units == 'J'
1682
- "
1683
- index_timestep_electricity = @model.sqlFile.get.execAndReturnVectorOfInt(query).get[0]
1684
- number_of_timesteps_per_hour = 1
1685
- if index_timestep_electricity.nil?
1686
- puts "No hourly or timestep data available. Peak data not available"
1687
- data['energy_peak_electric_heating_w_per_m_sq_winter'] = 0
1688
- else
1689
- ### Get timestep output for Electricity:Facility output.
1690
- query = "
1691
- Select VariableValue
1692
- FROM ReportVariableData
1693
- WHERE
1694
- ReportVariableDataDictionaryIndex = #{index_timestep_electricity}
1695
- "
1696
- timestep_values = @model.sqlFile.get.execAndReturnVectorOfDouble(query).get
1697
- # Create a hash where its keys are months and values for eahc month are electricity energy use (J) of all timesteps for that month
1698
- hash_months_electricity = keys_month.zip(timestep_values).group_by(&:first).map{|key, value| [key, value.map(&:last)]}.to_h
1699
- # Find maximum of electricity energy use (J) among all timesteps for each month of the year
1700
- monthly_peak_J = hash_months_electricity.map { |key, value| [key, value.max()] }.to_h
1701
- ### Convert J to W of maximum electricity energy use (J)
1702
- monthly_peak_W = monthly_peak_J.map { |key, value| [key, value / timestep_second] }.to_h
1703
- # Get winter peak
1704
- winter_peak_W = [monthly_peak_W[12].to_f, monthly_peak_W[1].to_f, monthly_peak_W[2].to_f].max
1705
- ### Normalize by building floor area
1706
- annual_peak_W_per_m_sq = winter_peak_W / @btap_data['bldg_conditioned_floor_area_m_sq'].to_f
1707
- data['energy_peak_electric_heating_w_per_m_sq_winter'] = annual_peak_W_per_m_sq
1708
- end
1709
-
1710
- # Peak water system electricity heating in winter
1711
- query = "
1712
- SELECT ReportDataDictionaryIndex
1713
- FROM ReportDataDictionary
1714
- WHERE Name=='WaterSystems:Electricity' AND ReportingFrequency == 'Hourly' AND Units == 'J'
1715
- "
1716
- index_timestep_electricity = @model.sqlFile.get.execAndReturnVectorOfInt(query).get[0]
1717
- number_of_timesteps_per_hour = 1
1718
- if index_timestep_electricity.nil?
1719
- puts "No hourly or timestep data available. Peak data not available"
1720
- data['energy_peak_electric_water_systems_w_per_m_sq_winter'] = 0
1721
- else
1722
- ### Get timestep output for Electricity:Facility output.
1723
- query = "
1724
- Select VariableValue
1725
- FROM ReportVariableData
1726
- WHERE
1727
- ReportVariableDataDictionaryIndex = #{index_timestep_electricity}
1728
- "
1729
- timestep_values = @model.sqlFile.get.execAndReturnVectorOfDouble(query).get
1730
- # Create a hash where its keys are months and values for eahc month are electricity energy use (J) of all timesteps for that month
1731
- hash_months_electricity = keys_month.zip(timestep_values).group_by(&:first).map{|key, value| [key, value.map(&:last)]}.to_h
1732
- # Find maximum of electricity energy use (J) among all timesteps for each month of the year
1733
- monthly_peak_J = hash_months_electricity.map { |key, value| [key, value.max()] }.to_h
1734
- ### Convert J to W of maximum electricity energy use (J)
1735
- monthly_peak_W = monthly_peak_J.map { |key, value| [key, value / timestep_second] }.to_h
1736
- # Get winter peak
1737
- winter_peak_W = [monthly_peak_W[12].to_f, monthly_peak_W[1].to_f, monthly_peak_W[2].to_f].max
1738
- ### Normalize by building floor area
1739
- annual_peak_W_per_m_sq = winter_peak_W / @btap_data['bldg_conditioned_floor_area_m_sq'].to_f
1740
- data['energy_peak_electric_water_systems_w_per_m_sq_winter'] = annual_peak_W_per_m_sq
1741
- end
1742
-
1743
- return data
1744
- end
1745
-
1746
- def energy_eui_data(model)
1747
- data = {}
1748
- # default to zero to start.
1749
- ['energy_eui_fans_gj_per_m_sq',
1750
- 'energy_eui_heating_gj_per_m_sq',
1751
- 'energy_eui_cooling_gj_per_m_sq',
1752
- 'energy_eui_interior equipment_gj_per_m_sq',
1753
- 'energy_eui_natural_gas_gj_per_m_sq',
1754
- 'energy_eui_pumps_gj_per_m_sq',
1755
- 'energy_eui_total_gj_per_m_sq',
1756
- 'energy_eui_heat recovery_gj_per_m_sq',
1757
- 'energy_eui_water systems_gj_per_m_sq'].each { |end_use| data[end_use] = 0.0 }
1758
-
1759
- # Check if the HVAC of the model is GSHP
1760
- plant_loops = model.getPlantLoops
1761
- model_has_how_many_GSHP = 0.0
1762
- plant_loops.each do |plantloop|
1763
- if plantloop.name.to_s.upcase.include? "GLHX"
1764
- model_has_how_many_GSHP += 1.0
1765
- end
1766
- end
1767
-
1768
- # Get E+ End use table from sql
1769
- table = get_sql_table_to_json(@model, 'AnnualBuildingUtilityPerformanceSummary', 'Entire Facility', 'End Uses')['table']
1770
- # Get rid of totals and averages rows.. I want just the
1771
- table.delete_if { |row| !!(row['name'] =~ /Total|Average/) }
1772
- table.each do |row|
1773
- # skip name and water_m3 columns
1774
- energy_columns = row.select { |k, v| (k != 'name') && (k != 'water_m3') }
1775
- # Store eui by use name.
1776
- data["energy_eui_#{row['name'].downcase}_gj_per_m_sq"] = energy_columns.inject(0) { |sum, tuple| sum += tuple[1] } / @conditioned_floor_area_m_sq
1777
- end
1778
-
1779
- data['energy_eui_total_gj_per_m_sq'] = 0.0
1780
-
1781
- ['natural_gas_GJ', 'electricity_GJ', 'additional_fuel_GJ', 'district_cooling_GJ', 'district_heating_GJ'].each do |column|
1782
- data["energy_eui_#{column.downcase}_per_m_sq"] = table.inject(0) { |sum, row| sum + (row[column].nil? ? 0.0 : row[column]) } / @conditioned_floor_area_m_sq
1783
- data['energy_eui_total_gj_per_m_sq'] += data["energy_eui_#{column.downcase}_per_m_sq"] unless data["energy_eui_#{column.downcase}_per_m_sq"].nil?
1784
- end
1785
-
1786
- # If the HVAC of the model is GSHP, district heating and cooling must be removed from EUIs for heating and cooling and total EUI
1787
- # NOTE: it has been assumed that if a model has GSHP, that is the only HVAC type in the model. This assumption means that any district heating/cooling in the model is related to GSHP.
1788
- if model_has_how_many_GSHP > 0.0
1789
- data['energy_eui_heating_gj_per_m_sq'] -= data['energy_eui_district_heating_gj_per_m_sq']
1790
- data['energy_eui_cooling_gj_per_m_sq'] -= data['energy_eui_district_cooling_gj_per_m_sq']
1791
- data['energy_eui_total_gj_per_m_sq'] -= (data['energy_eui_district_heating_gj_per_m_sq'] + data['energy_eui_district_cooling_gj_per_m_sq'])
1792
- end
1793
-
1794
- # Get total and net site energy use intensity
1795
- # Note: 'Total Site Energy' is the "gross" energy used by a building.
1796
- # Note: 'Net Site Energy' is the final energy used by the building after considering any on-site energy generation (e.g. PV).
1797
- # Reference: https://unmethours.com/question/25416/what-is-the-difference-between-site-energy-and-source-energy/
1798
- # Reference: https://designbuilder.co.uk/helpv6.0/Content/KPIs.htm
1799
- data['total_site_eui_gj_per_m_sq'] = data['energy_eui_total_gj_per_m_sq'].to_f
1800
- command = "SELECT Value
1801
- FROM TabularDataWithStrings
1802
- WHERE ReportName='AnnualBuildingUtilityPerformanceSummary'
1803
- AND ReportForString='Entire Facility'
1804
- AND TableName='Site and Source Energy'
1805
- AND RowName='Net Site Energy'
1806
- AND ColumnName='Energy Per Conditioned Building Area'
1807
- AND Units='MJ/m2'"
1808
- net_site_eui_mj_per_m_sq = @sqlite_file.get.execAndReturnFirstDouble(command)
1809
- data['net_site_eui_gj_per_m_sq'] = OpenStudio.convert(net_site_eui_mj_per_m_sq.to_f, 'MJ', 'GJ').get
1810
- return data
1811
- end
1812
-
1813
- def unmet_hours(model)
1814
- # Store unmet hour data
1815
- unmet_hours = {}
1816
- unmet_hours['unmet_hours_cooling'] = model.getFacility.hoursCoolingSetpointNotMet.get unless model.getFacility.hoursCoolingSetpointNotMet.empty?
1817
- unmet_hours['unmet_hours_heating'] = model.getFacility.hoursHeatingSetpointNotMet.get unless model.getFacility.hoursHeatingSetpointNotMet.empty?
1818
- command = "SELECT Value
1819
- FROM TabularDataWithStrings
1820
- WHERE ReportName='AnnualBuildingUtilityPerformanceSummary'
1821
- AND ReportForString='Entire Facility'
1822
- AND TableName='Comfort and Setpoint Not Met Summary'
1823
- AND RowName='Time Setpoint Not Met During Occupied Cooling'
1824
- AND ColumnName='Facility'
1825
- AND Units='Hours'"
1826
- unmet_hours['unmet_hours_cooling_during_occupied'] = @sqlite_file.get.execAndReturnFirstDouble(command).to_f
1827
- command = "SELECT Value
1828
- FROM TabularDataWithStrings
1829
- WHERE ReportName='AnnualBuildingUtilityPerformanceSummary'
1830
- AND ReportForString='Entire Facility'
1831
- AND TableName='Comfort and Setpoint Not Met Summary'
1832
- AND RowName='Time Setpoint Not Met During Occupied Heating'
1833
- AND ColumnName='Facility'
1834
- AND Units='Hours'"
1835
- unmet_hours['unmet_hours_heating_during_occupied'] = @sqlite_file.get.execAndReturnFirstDouble(command).to_f
1836
- return unmet_hours
1837
- end
1838
-
1839
- def service_water_heating_data
1840
- service_water_heating = {}
1841
- service_water_heating['shw_total_nominal_occupancy'] = -1
1842
- # service_water_heating["total_nominal_occupancy"]=@model.sqlFile().get().execAndReturnVectorOfDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='OutdoorAirSummary' AND ReportForString='Entire Facility' AND TableName='Average Outdoor Air During Occupied Hours' AND ColumnName='Nominal Number of Occupants'").get.inject(0, :+)
1843
- service_water_heating['shw_total_nominal_occupancy'] = get_total_nominal_capacity(@model)
1844
-
1845
- service_water_heating['shw_electricity_per_year'] = @model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND ColumnName='Electricity' AND RowName='Water Systems'")
1846
- service_water_heating['shw_electricity_per_year'] = validate_optional(service_water_heating['shw_electricity_per_year'], @model, -1)
1847
-
1848
- service_water_heating['shw_electricity_per_day'] = service_water_heating['shw_electricity_per_year'] / 365.5
1849
- service_water_heating['shw_electricity_per_day_per_occupant'] = service_water_heating['shw_electricity_per_day'] / service_water_heating['shw_total_nominal_occupancy']
1850
-
1851
- service_water_heating['shw_natural_gas_per_year'] = @model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND ColumnName='Natural Gas' AND RowName='Water Systems'")
1852
- service_water_heating['shw_natural_gas_per_year'] = validate_optional(service_water_heating['shw_natural_gas_per_year'], @model, -1)
1853
-
1854
- service_water_heating['shw_additional_fuel_per_year'] = @model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND ColumnName='Additional Fuel' AND RowName='Water Systems'")
1855
- service_water_heating['shw_additional_fuel_per_year'] = validate_optional(service_water_heating['shw_additional_fuel_per_year'], @model, -1)
1856
-
1857
- service_water_heating['shw_water_m_cu_per_year'] = @model.sqlFile.get.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='End Uses' AND ColumnName='Water' AND RowName='Water Systems'")
1858
- service_water_heating['shw_water_m_cu_per_year'] = validate_optional(service_water_heating['shw_water_m_cu_per_year'], @model, -1)
1859
-
1860
- service_water_heating['shw_water_m_cu_per_day'] = service_water_heating['shw_water_m_cu_per_year'] / 365.5
1861
- service_water_heating['shw_water_m_cu_per_day_per_occupant'] = service_water_heating['shw_water_m_cu_per_day'] / service_water_heating['shw_total_nominal_occupancy']
1862
- return service_water_heating
1863
- end
1864
-
1865
- # The below method (outdoor_air_data extract a couple of outputs related to outdoor air from the .html output file)
1866
- def outdoor_air_data(model)
1867
- # Store outdoor air data
1868
- outdoor_air_data = {}
1869
- #===============================================================================================================
1870
- airloops_total_outdoor_air_mechanical_ventilation_m3 = 0.0
1871
- airloops_total_outdoor_air_natural_ventilation_m3 = 0.0
1872
- zones_total_outdoor_air_mechanical_ventilation_m3 = 0.0
1873
- zones_total_outdoor_air_natural_ventilation_m3 = 0.0
1874
- zones_total_outdoor_air_infiltration_m3 = 0.0
1875
- #===============================================================================================================
1876
- # Total outdoor air by airLoop
1877
- model.getAirLoopHVACs.sort.each do |air_loop|
1878
- air_loop_name = air_loop.name.get.upcase
1879
-
1880
- # Mechanical ventilation of all airloops
1881
- command = "SELECT Value
1882
- FROM TabularDataWithStrings
1883
- WHERE ReportName='OutdoorAirDetails'
1884
- AND ReportForString='Entire Facility'
1885
- AND TableName='Total Outdoor Air by AirLoop'
1886
- AND RowName='#{air_loop_name}'
1887
- AND ColumnName='Mechanical Ventilation'
1888
- AND Units='m3'"
1889
- airloops_total_outdoor_air_mechanical_ventilation_m3 += @sqlite_file.get.execAndReturnFirstDouble(command).to_f
1890
-
1891
- # Natural ventilation of all airloops
1892
- command = "SELECT Value
1893
- FROM TabularDataWithStrings
1894
- WHERE ReportName='OutdoorAirDetails'
1895
- AND ReportForString='Entire Facility'
1896
- AND TableName='Total Outdoor Air by AirLoop'
1897
- AND RowName='#{air_loop_name}'
1898
- AND ColumnName='Natural Ventilation'
1899
- AND Units='m3'"
1900
- airloops_total_outdoor_air_natural_ventilation_m3 += @sqlite_file.get.execAndReturnFirstDouble(command).to_f
1901
-
1902
- end
1903
-
1904
- # Not-normalized mechanical/natural.
1905
- outdoor_air_data['airloops_total_outdoor_air_mechanical_ventilation_m3'] = airloops_total_outdoor_air_mechanical_ventilation_m3
1906
- outdoor_air_data['airloops_total_outdoor_air_natural_ventilation_m3'] = airloops_total_outdoor_air_natural_ventilation_m3
1907
-
1908
- # Normalized mechanical/natural: ACH (air changes per hour)
1909
- outdoor_air_data['airloops_total_outdoor_air_mechanical_ventilation_ach_1_per_hr'] = airloops_total_outdoor_air_mechanical_ventilation_m3 / (@btap_data['bldg_volume_m_cu'] * 365 * 24)
1910
- outdoor_air_data['airloops_total_outdoor_air_natural_ventilation_ach_1_per_hr'] = airloops_total_outdoor_air_natural_ventilation_m3 / (@btap_data['bldg_volume_m_cu'] * 365 * 24)
1911
-
1912
- # Normalized mechanical/natural: normalized by conditioned floor area
1913
- outdoor_air_data['airloops_total_outdoor_air_mechanical_ventilation_flow_per_conditioned_floor_area_m3_per_s_m2'] = airloops_total_outdoor_air_mechanical_ventilation_m3 / (@conditioned_floor_area_m_sq * 365 * 24 * 3600)
1914
- outdoor_air_data['airloops_total_outdoor_air_natural_ventilation_flow_per_conditioned_floor_area_m3_per_s_m2'] = airloops_total_outdoor_air_natural_ventilation_m3 / (@conditioned_floor_area_m_sq * 365 * 24 * 3600)
1915
-
1916
- # Normalized mechanical/natural: normalized by exterior area
1917
- outdoor_air_data['airloops_total_outdoor_air_mechanical_ventilation_flow_per_exterior_area_m3_per_s_m2'] = airloops_total_outdoor_air_mechanical_ventilation_m3 / (@btap_data['bldg_exterior_area_m_sq'] * 365 * 24 * 3600)
1918
- outdoor_air_data['airloops_total_outdoor_air_natural_ventilation_flow_per_exterior_area_m3_per_s_m2'] = airloops_total_outdoor_air_natural_ventilation_m3 / (@btap_data['bldg_exterior_area_m_sq'] * 365 * 24 * 3600)
1919
-
1920
- #===============================================================================================================
1921
- # Total outdoor air by zone
1922
- total_outdoor_air_mechanical_ventilation_zones_m3 = 0.0
1923
- model.getThermalZones.sort.each do |zone|
1924
- zone_name = zone.name.get.upcase
1925
-
1926
- # Mechanical ventilation of all zones
1927
- command = "SELECT Value
1928
- FROM TabularDataWithStrings
1929
- WHERE ReportName='OutdoorAirDetails'
1930
- AND ReportForString='Entire Facility'
1931
- AND TableName='Total Outdoor Air by Zone'
1932
- AND RowName='#{zone_name}'
1933
- AND ColumnName='Mechanical Ventilation'
1934
- AND Units='m3'"
1935
- zones_total_outdoor_air_mechanical_ventilation_m3 += @sqlite_file.get.execAndReturnFirstDouble(command).to_f
1936
-
1937
- # Natural ventilation of all zones
1938
- command = "SELECT Value
1939
- FROM TabularDataWithStrings
1940
- WHERE ReportName='OutdoorAirDetails'
1941
- AND ReportForString='Entire Facility'
1942
- AND TableName='Total Outdoor Air by Zone'
1943
- AND RowName='#{zone_name}'
1944
- AND ColumnName='Natural Ventilation'
1945
- AND Units='m3'"
1946
- zones_total_outdoor_air_natural_ventilation_m3 += @sqlite_file.get.execAndReturnFirstDouble(command).to_f
1947
-
1948
- # Infiltration of all zones
1949
- command = "SELECT Value
1950
- FROM TabularDataWithStrings
1951
- WHERE ReportName='OutdoorAirDetails'
1952
- AND ReportForString='Entire Facility'
1953
- AND TableName='Total Outdoor Air by Zone'
1954
- AND RowName='#{zone_name}'
1955
- AND ColumnName='Infiltration'
1956
- AND Units='m3'"
1957
- zones_total_outdoor_air_infiltration_m3 += @sqlite_file.get.execAndReturnFirstDouble(command).to_f
1958
-
1959
- end
1960
-
1961
- # Not-normalized mechanical/natural/infiltration.
1962
- outdoor_air_data['zones_total_outdoor_air_mechanical_ventilation_m3'] = zones_total_outdoor_air_mechanical_ventilation_m3
1963
- outdoor_air_data['zones_total_outdoor_air_natural_ventilation_m3'] = zones_total_outdoor_air_natural_ventilation_m3
1964
- outdoor_air_data['zones_total_outdoor_air_infiltration_m3'] = zones_total_outdoor_air_infiltration_m3
1965
-
1966
- # Normalized mechanical/natural/infiltration: ACH (air changes per hour)
1967
- outdoor_air_data['zones_total_outdoor_air_mechanical_ventilation_ach_1_per_hr'] = zones_total_outdoor_air_mechanical_ventilation_m3 / (@btap_data['bldg_volume_m_cu'] * 365 * 24)
1968
- outdoor_air_data['zones_total_outdoor_air_natural_ventilation_ach_1_per_hr'] = zones_total_outdoor_air_natural_ventilation_m3 / (@btap_data['bldg_volume_m_cu'] * 365 * 24)
1969
- outdoor_air_data['zones_total_outdoor_air_infiltration_ach_1_per_hr'] = zones_total_outdoor_air_infiltration_m3 / (@btap_data['bldg_volume_m_cu'] * 365 * 24)
1970
-
1971
- # Normalized mechanical/natural/infiltration: normalized by conditioned floor area
1972
- outdoor_air_data['zones_total_outdoor_air_mechanical_ventilation_flow_per_conditioned_floor_area_m3_per_s_m2'] = zones_total_outdoor_air_mechanical_ventilation_m3 / (@conditioned_floor_area_m_sq * 365 * 24 * 3600)
1973
- outdoor_air_data['zones_total_outdoor_air_natural_ventilation_flow_per_conditioned_floor_area_m3_per_s_m2'] = zones_total_outdoor_air_natural_ventilation_m3 / (@conditioned_floor_area_m_sq * 365 * 24 * 3600)
1974
- outdoor_air_data['zones_total_outdoor_air_infiltration_flow_per_conditioned_floor_area_m3_per_s_m2'] = zones_total_outdoor_air_infiltration_m3 / (@conditioned_floor_area_m_sq * 365 * 24 * 3600)
1975
-
1976
- # Normalized mechanical/natural/infiltration: normalized by exterior area
1977
- outdoor_air_data['zones_total_outdoor_air_mechanical_ventilation_flow_per_exterior_area_m3_per_s_m2'] = zones_total_outdoor_air_mechanical_ventilation_m3 / (@btap_data['bldg_exterior_area_m_sq'] * 365 * 24 * 3600)
1978
- outdoor_air_data['zones_total_outdoor_air_natural_ventilation_flow_per_exterior_area_m3_per_s_m2'] = zones_total_outdoor_air_natural_ventilation_m3 / (@btap_data['bldg_exterior_area_m_sq'] * 365 * 24 * 3600)
1979
- outdoor_air_data['zones_total_outdoor_air_infiltration_flow_per_exterior_area_m3_per_s_m2'] = zones_total_outdoor_air_infiltration_m3 / (@btap_data['bldg_exterior_area_m_sq'] * 365 * 24 * 3600)
1980
- #===============================================================================================================
1981
-
1982
- return outdoor_air_data
1983
- end
1984
-
1985
- def sql_data_tables(model)
1986
- puts 'Getting SQL Data into json...'
1987
- start = Time.now
1988
- sql_data = []
1989
-
1990
- [
1991
- ['AnnualBuildingUtilityPerformanceSummary', 'Entire Facility', 'End Uses']
1992
- # ["AnnualBuildingUtilityPerformanceSummary", "Entire Facility", "Site and Source Energy"],
1993
- # ["AnnualBuildingUtilityPerformanceSummary", "Entire Facility", "On-Site Thermal Sources"],
1994
- # ["AnnualBuildingUtilityPerformanceSummary", "Entire Facility", "Comfort and Setpoint Not Met Summary"],
1995
- # ["InputVerificationandResultsSummary", "Entire Facility", "Window-Wall Ratio"],
1996
- # ["InputVerificationandResultsSummary", "Entire Facility", "Conditioned Window-Wall Ratio"],
1997
- # ["InputVerificationandResultsSummary", "Entire Facility", "Skylight-Roof Ratio"],
1998
- # ["DemandEndUseComponentsSummary", "Entire Facility", "End Uses"],
1999
- # ["ComponentSizingSummary", "Entire Facility", "AirLoopHVAC"],
2000
- # ["EnergyMeters", "Entire Facility", 'Annual and Peak Values - Natural Gas'],
2001
- # ["EnergyMeters", "Entire Facility", 'Annual and Peak Values - Electricity'],
2002
- # ["EnergyMeters", "Entire Facility", 'Annual and Peak Values - FuelOilNo2'],
2003
- # ["EnergyMeters", "Entire Facility", 'Annual and Peak Values - Other'],
2004
- # ["LEEDsummary", "Entire Facility", "EAp2-7. Energy Cost Summary"],
2005
- # ["Standard62.1Summary", "Entire Facility", "Zone Ventilation Parameters"],
2006
- # ["EquipmentSummary", "Entire Facility", "Fans"],
2007
- # ["EquipmentSummary", "Entire Facility", "Heating Coils"],
2008
- # ["EquipmentSummary", "Entire Facility", "Cooling Coils"],
2009
- # ["EquipmentSummary", "Entire Facility", "Pumps"],
2010
- # ["CoilSizingDetails", "Entire Facility", "Coils"] # Do not use! Takes very long to parse.
2011
- ].each do |table|
2012
- start = Time.now
2013
- puts "Parsing #{table[0]}-#{table[1]}-#{table[2]}"
2014
- sql_data << get_sql_table_to_json(model, table[0], table[1], table[2])
2015
- finish = Time.now
2016
- puts "....finish parsing in #{finish - start} seconds and stored in sql_data_tables hash."
2017
- end
2018
- end
2019
-
2020
- # This measure will return an array of hashes with the varialbles used in the previous measures.
2021
- def measures_data_table(runner)
2022
- # Array to store hash row data.
2023
- measure_variables_table = []
2024
- # Go through each workstep.
2025
- runner.workflow.workflowSteps.each do |step|
2026
- # Check if the ws is a measure.
2027
- if step.to_MeasureStep.is_initialized
2028
- measure_step = step.to_MeasureStep.get
2029
- # Set measure name using either the folder name or the measureStep name if possible.
2030
- measure_name = measure_step.name.is_initialized ? measure_step.name.get : measure_step.measureDirName
2031
- measures_to_skip = ['openstudio_results']
2032
- unless measures_to_skip.include?(measure_name.to_s)
2033
- # Check if the 'result' (?) is initialized?
2034
- if measure_step.result.is_initialized
2035
- result = measure_step.result.get
2036
- # Iterate through the result object stepValues to obtain the arg values. I am assuming this is for arg, var. pivot, continous...
2037
- result.stepValues.each do |arg|
2038
- # Store the row /hash for the table.
2039
- units = arg.units.empty? ? nil : arg.units
2040
- value = nil
2041
- case arg.variantType.value
2042
- when 0
2043
- value = arg.valueAsBoolean
2044
- when 1..2
2045
- value = arg.valueAsDouble
2046
- when 3
2047
- value = arg.valueAsString
2048
- end
2049
- measure_variables_table << { 'measure_name' => measure_name, 'arg_name' => arg.name, 'value' => value, 'units' => units, 'type' => arg.variantType.value }
2050
- end
2051
- end
2052
- end
2053
- end
2054
- end
2055
- return measure_variables_table
2056
- end
2057
-
2058
- # This should be done last.
2059
-
2060
- def get_sql_table_to_json(model, report_name, report_for_string, table_name)
2061
- table = []
2062
- query_row_names = "
2063
- SELECT DISTINCT
2064
- RowName
2065
- FROM
2066
- tabulardatawithstrings
2067
- WHERE
2068
- ReportName='#{report_name}'
2069
- AND
2070
- ReportForString='#{report_for_string}'
2071
- AND
2072
- TableName='#{table_name}'"
2073
- row_names = model.sqlFile.get.execAndReturnVectorOfString(query_row_names).get
2074
-
2075
- # get Columns
2076
- query_col_names = "
2077
- SELECT DISTINCT
2078
- ColumnName
2079
- FROM tabulardatawithstrings
2080
- WHERE ReportName='#{report_name}'
2081
- AND ReportForString='#{report_for_string}'
2082
- AND TableName='#{table_name}'"
2083
- col_names = model.sqlFile.get.execAndReturnVectorOfString(query_col_names).get
2084
-
2085
- # get units
2086
- query_unit_names = "
2087
- SELECT DISTINCT
2088
- Units
2089
- FROM tabulardatawithstrings
2090
- WHERE ReportName='#{report_name}'
2091
- AND ReportForString='#{report_for_string}'
2092
- AND TableName='#{table_name}'"
2093
- unit_names = model.sqlFile.get.execAndReturnVectorOfString(query_unit_names).get
2094
-
2095
- row_names.each do |row|
2096
- next if row.nil? || row == ''
2097
-
2098
- row_hash = {}
2099
- row_hash['name'] = row
2100
- col_names.each do |col|
2101
- unit_names.each do |unit|
2102
- query = "
2103
- SELECT
2104
- Value
2105
- FROM
2106
- tabulardatawithstrings
2107
- WHERE
2108
- ReportName='#{report_name}'
2109
- AND
2110
- ReportForString='#{report_for_string}'
2111
- AND
2112
- TableName='#{table_name}'
2113
- AND
2114
- RowName='#{row}'
2115
- AND
2116
- ColumnName='#{col}'
2117
- AND
2118
- Units='#{unit}'
2119
- "
2120
- column_name = col.to_s.gsub(/\s+/, '_').downcase
2121
- column_name += "_#{unit}" if unit != ''
2122
- value = model.sqlFile.get.execAndReturnFirstString(query)
2123
- next if value.empty? || value.get.nil?
2124
-
2125
- value = value.get.strip
2126
- # check is value is a number. The last chunk checks if the string includes an E, if not return true since it
2127
- # is a regular number, if not it checks if it is in the E+ exponent format and returns the bool result of that.
2128
- if (begin
2129
- Float(value)
2130
- rescue StandardError
2131
- false
2132
- end) && value.to_f != 0 && (value.include?('E') || value.include?('e') ? value =~ /\d*\.\d*E[+|-]\d*/ : true)
2133
- row_hash[column_name] = value.to_f
2134
- # Check if value is a date
2135
- elsif unit == '' && value =~ /\d\d-\D\D\D-\d\d:\d\d/
2136
- row_hash[column_name] = DateTime.parse(value)
2137
- # skip if value in an empty string or a zero value
2138
- elsif value != '' && value != '0.00'
2139
- row_hash[column_name] = value
2140
- end
2141
- end
2142
- end
2143
- if row_hash.size > 1
2144
- table << row_hash
2145
- end
2146
- end
2147
- result = { 'report_name' => report_name, 'report_for_string' => report_for_string, 'table_name' => table_name, 'table' => table }
2148
- return result
2149
- end
2150
-
2151
- def merge_recursively(a, b)
2152
- a.merge(b) { |key, a_item, b_item| merge_recursively(a_item, b_item) }
2153
- end
2154
-
2155
- def validate_optional(var, model, return_value = 'N/A')
2156
- return return_value if var.nil? || var.empty?
2157
-
2158
- return var.get
2159
- end
2160
-
2161
- # @todo SQL command units may have been converted wrong.
2162
-
2163
- def get_actual_child_object(object)
2164
- # monkey patch class to have a decendants static method to return all possible subclasses of the object.
2165
- object.class.class_eval do
2166
- def self.descendants
2167
- ObjectSpace.each_object(Class).select { |k| k < self } << self
2168
- end
2169
- end
2170
- # Dont try and match the class that the object is already in! So get decendants that are not of the current object class
2171
- subclass_array = object.class.descendants.map { |classtype| classtype if classtype != object.class }.reject(&:nil?)
2172
- subclass_array.each do |class_type|
2173
- # convert class name to not have prefix.
2174
- matches = class_type.name.match(/OpenStudio::Model::(?<object_name>.*)/)
2175
- new_object = nil
2176
- # Use eval (I know this is the devil) to try to cast to a subclass.
2177
- eval_info = "new_object = object.to_#{matches['object_name']}"
2178
- eval(eval_info)
2179
- # if it does then clean it up and return it.
2180
- if new_object.is_initialized
2181
- new_object = new_object.get
2182
- return get_actual_child_object(new_object)
2183
- end
2184
- end
2185
- # It is not cast-able to any subclass.. so returning original object.
2186
- return object
2187
- end
2188
-
2189
- def set_sql_file(file)
2190
- @sqlite_file = file
2191
- end
2192
-
2193
- def measure_metrics(qaqc)
2194
- # Store mesure metric data that will be used in analysis tools.
2195
- @btap_data['env_outdoor_walls_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:outdoor_walls_average_conductance_w_per_m2_k]
2196
- @btap_data['env_outdoor_roofs_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:outdoor_roofs_average_conductance_w_per_m2_k]
2197
- @btap_data['env_outdoor_floors_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:outdoor_floors_average_conductance_w_per_m2_k]
2198
- @btap_data['env_ground_walls_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:ground_walls_average_conductance_w_per_m2_k]
2199
- @btap_data['env_ground_roofs_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:ground_roofs_average_conductance_w_per_m2_k]
2200
- @btap_data['env_ground_floors_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:ground_floors_average_conductance_w_per_m2_k]
2201
- @btap_data['env_outdoor_windows_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:windows_average_conductance_w_per_m2_k]
2202
- @btap_data['env_outdoor_doors_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:doors_average_conductance_w_per_m2_k]
2203
- @btap_data['env_outdoor_overhead_doors_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:overhead_doors_average_conductance_w_per_m2_k]
2204
- @btap_data['env_skylights_average_conductance-w_per_m_sq_k'] = qaqc[:envelope][:skylights_average_conductance_w_per_m2_k]
2205
- @btap_data['env_fdwr'] = (BTAP::Geometry.get_fwdr(@model) * 100.0).round(1)
2206
- @btap_data['env_srr'] = (BTAP::Geometry.get_srr(@model) * 100.0).round(1)
2207
- unless @btap_data['measures_data_table'].nil?
2208
- if @btap_data['measures_data_table'].detect { |item| item['measure_name'] == 'btap_standard_building_type_geometry' }.nil?
2209
- @btap_data['env_x_scale'] = 1.0
2210
- @btap_data['env_y_scale'] = 1.0
2211
- @btap_data['env_z_scale'] = 1.0
2212
- @btap_data['env_rotation'] = 0.0
2213
- else
2214
- @btap_data['env_x_scale'] = @btap_data['measures_data_table'].detect { |item| item['measure_name'] == 'btap_standard_building_type_geometry' && item['arg_name'] == 'x_scale' }['value']
2215
- @btap_data['env_y_scale'] = @btap_data['measures_data_table'].detect { |item| item['measure_name'] == 'btap_standard_building_type_geometry' && item['arg_name'] == 'y_scale' }['value']
2216
- @btap_data['env_z_scale'] = @btap_data['measures_data_table'].detect { |item| item['measure_name'] == 'btap_standard_building_type_geometry' && item['arg_name'] == 'z_scale' }['value']
2217
- @btap_data['env_rotation'] = @btap_data['measures_data_table'].detect { |item| item['measure_name'] == 'btap_standard_building_type_geometry' && item['arg_name'] == 'relative_building_rotation' }['value']
2218
- end
2219
- end
2220
-
2221
- # This does not work with the new VRF or CCASHP systems. Commenting it for now.
2222
- # Determine dominant system type by air loop
2223
-
2224
- # systems = {}
2225
- # @btap_data["air_loop_table"].each do |loop|
2226
- # Get system name part from regex
2227
- # system_name = loop["name"].match(/(^.{0,6}).*/)[1]
2228
- # systems[system_name] = 0.0 if systems[system_name] == nil
2229
- # systems[system_name] += loop["total_floor_area_served"]
2230
- # end
2231
- # if systems.empty?
2232
- # @btap_data["mm_hvac_dominant_system_type"] = "Unknown/IdealHVAC"
2233
- # else
2234
- # @btap_data["mm_hvac_dominant_system_type"] = systems.key(systems.values.max)
2235
- # end
2236
- return @btap_data
2237
- end
2238
-
2239
- def get_utility_ghg_kg_per_gj(province:, fuel_type:)
2240
- ghg_data = [
2241
- # Obtained from Portfolio Manager https://portfoliomanager.energystar.gov/pdf/reference/Emissions.pdf 10/10/2020
2242
- #{ "province": 'AB', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 53.24, "CO2eq Emissions (g/m3)": 1939.0 },
2243
- #{ "province": 'BC', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 53.19, "CO2eq Emissions (g/m3)": 1937.0 },
2244
- #{ "province": 'MB', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.09, "CO2eq Emissions (g/m3)": 1897.0 },
2245
- #{ "province": 'NB', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.50, "CO2eq Emissions (g/m3)": 1912.0 },
2246
- #{ "province": 'NL', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.50, "CO2eq Emissions (g/m3)": 1912.0 },
2247
- #{ "province": 'NT', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.50, "CO2eq Emissions (g/m3)": 1912.0 },
2248
- #{ "province": 'NS', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.50, "CO2eq Emissions (g/m3)": 1912.0 },
2249
- #{ "province": 'NU', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.50, "CO2eq Emissions (g/m3)": 1912.0 },
2250
- #{ "province": 'ON', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.14, "CO2eq Emissions (g/m3)": 1912.0 },
2251
- #{ "province": 'PE', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.50, "CO2eq Emissions (g/m3)": 1912.0 },
2252
- #{ "province": 'QC', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.12, "CO2eq Emissions (g/m3)": 1898.0 },
2253
- #{ "province": 'SK', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 50.53, "CO2eq Emissions (g/m3)": 1840.0 },
2254
- #{ "province": 'YT', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.50, "CO2eq Emissions (g/m3)": 1912.0 },
2255
-
2256
- #REF (Retrieved March 11, 2025) for Year 2022 of "Table A6.1–1 CO2 Emission Factors for Marketable Natural Gas" of National Inventory Report: https://data-donnees.az.ec.gc.ca/api/file?path=%2Fsubstances%2Fmonitor%2Fcanada-s-official-greenhouse-gas-inventory%2FD-Emission-Factors%2FEN_Annex6_Emission_Factors.pdf
2257
- { "province": 'AB', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 53.87, "CO2eq Emissions (g/m3)": 1962.0 },
2258
- { "province": 'BC', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 53.98, "CO2eq Emissions (g/m3)": 1966.0 },
2259
- { "province": 'MB', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.58, "CO2eq Emissions (g/m3)": 1915.0 },
2260
- { "province": 'NB', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.69, "CO2eq Emissions (g/m3)": 1919.0 },
2261
- { "province": 'NL', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.69, "CO2eq Emissions (g/m3)": 1919.0 },
2262
- { "province": 'NT', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 53.98, "CO2eq Emissions (g/m3)": 1966.0 },
2263
- { "province": 'NS', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.69, "CO2eq Emissions (g/m3)": 1919.0 },
2264
- { "province": 'NU', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 53.98, "CO2eq Emissions (g/m3)": 1966.0 },
2265
- { "province": 'ON', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.75, "CO2eq Emissions (g/m3)": 1921.0 },
2266
- { "province": 'PE', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.69, "CO2eq Emissions (g/m3)": 1919.0 },
2267
- { "province": 'QC', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.88, "CO2eq Emissions (g/m3)": 1926.0 },
2268
- { "province": 'SK', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 52.72, "CO2eq Emissions (g/m3)": 1920.0 },
2269
- { "province": 'YT', "fuel_type": 'NaturalGas', "CO2eq Emissions (kg/MBtu)": 56.98, "CO2eq Emissions (g/m3)": 1966.0 },
2270
-
2271
- # Obtained from Portfolio Manager https://portfoliomanager.energystar.gov/pdf/reference/Emissions.pdf 10/10/2020
2272
- { "province": 'AB', "fuel_type": 'FuelOilNo2', "CO2eq Emissions (kg/MBtu)": 75.13, "CO2eq Emissions (g/m3)": 2763.0 },
2273
- { "province": 'BC', "fuel_type": 'FuelOilNo2', "CO2eq Emissions (kg/MBtu)": 75.13, "CO2eq Emissions (g/m3)": 2763.0 },
2274
- { "province": 'MB', "fuel_type": 'FuelOilNo2', "CO2eq Emissions (kg/MBtu)": 75.13, "CO2eq Emissions (g/m3)": 2763.0 },
2275
- { "province": 'NB', "fuel_type": 'FuelOilNo2', "CO2eq Emissions (kg/MBtu)": 75.13, "CO2eq Emissions (g/m3)": 2763.0 },
2276
- { "province": 'NL', "fuel_type": 'FuelOilNo2', "CO2eq Emissions (kg/MBtu)": 75.13, "CO2eq Emissions (g/m3)": 2763.0 },
2277
- { "province": 'NT', "fuel_type": 'FuelOilNo2', "CO2eq Emissions (kg/MBtu)": 75.13, "CO2eq Emissions (g/m3)": 2763.0 },
2278
- { "province": 'NS', "fuel_type": 'FuelOilNo2', "CO2eq Emissions (kg/MBtu)": 75.13, "CO2eq Emissions (g/m3)": 2763.0 },
2279
- { "province": 'NU', "fuel_type": 'FuelOilNo2', "CO2eq Emissions (kg/MBtu)": 75.13, "CO2eq Emissions (g/m3)": 2763.0 },
2280
- { "province": 'ON', "fuel_type": 'FuelOilNo2', "CO2eq Emissions (kg/MBtu)": 75.13, "CO2eq Emissions (g/m3)": 2763.0 },
2281
- { "province": 'PE', "fuel_type": 'FuelOilNo2', "CO2eq Emissions (kg/MBtu)": 75.13, "CO2eq Emissions (g/m3)": 2763.0 },
2282
- { "province": 'QC', "fuel_type": 'FuelOilNo2', "CO2eq Emissions (kg/MBtu)": 75.13, "CO2eq Emissions (g/m3)": 2763.0 },
2283
- { "province": 'SK', "fuel_type": 'FuelOilNo2', "CO2eq Emissions (kg/MBtu)": 75.13, "CO2eq Emissions (g/m3)": 2763.0 },
2284
- { "province": 'YT', "fuel_type": 'FuelOilNo2', "CO2eq Emissions (kg/MBtu)": 75.13, "CO2eq Emissions (g/m3)": 2763.0 },
2285
-
2286
- { "province": 'AB', "fuel_type": 'Propane', "CO2eq Emissions (kg/MBtu)": 64.25, "CO2eq Emissions (g/m3)": 1548.00 },
2287
- { "province": 'BC', "fuel_type": 'Propane', "CO2eq Emissions (kg/MBtu)": 64.25, "CO2eq Emissions (g/m3)": 1548.00 },
2288
- { "province": 'MB', "fuel_type": 'Propane', "CO2eq Emissions (kg/MBtu)": 64.25, "CO2eq Emissions (g/m3)": 1548.00 },
2289
- { "province": 'NB', "fuel_type": 'Propane', "CO2eq Emissions (kg/MBtu)": 64.25, "CO2eq Emissions (g/m3)": 1548.00 },
2290
- { "province": 'NL', "fuel_type": 'Propane', "CO2eq Emissions (kg/MBtu)": 64.25, "CO2eq Emissions (g/m3)": 1548.00 },
2291
- { "province": 'NT', "fuel_type": 'Propane', "CO2eq Emissions (kg/MBtu)": 64.25, "CO2eq Emissions (g/m3)": 1548.00 },
2292
- { "province": 'NS', "fuel_type": 'Propane', "CO2eq Emissions (kg/MBtu)": 64.25, "CO2eq Emissions (g/m3)": 1548.00 },
2293
- { "province": 'NU', "fuel_type": 'Propane', "CO2eq Emissions (kg/MBtu)": 64.25, "CO2eq Emissions (g/m3)": 1548.00 },
2294
- { "province": 'ON', "fuel_type": 'Propane', "CO2eq Emissions (kg/MBtu)": 64.25, "CO2eq Emissions (g/m3)": 1548.00 },
2295
- { "province": 'PE', "fuel_type": 'Propane', "CO2eq Emissions (kg/MBtu)": 64.25, "CO2eq Emissions (g/m3)": 1548.00 },
2296
- { "province": 'QC', "fuel_type": 'Propane', "CO2eq Emissions (kg/MBtu)": 64.25, "CO2eq Emissions (g/m3)": 1548.00 },
2297
- { "province": 'SK', "fuel_type": 'Propane', "CO2eq Emissions (kg/MBtu)": 64.25, "CO2eq Emissions (g/m3)": 1548.00 },
2298
- { "province": 'YT', "fuel_type": 'Propane', "CO2eq Emissions (kg/MBtu)": 64.25, "CO2eq Emissions (g/m3)": 1548.00 },
2299
-
2300
- { "province": 'AB', "fuel_type": 'Electricity', "CO2eq Emissions (kg/MBtu)": 202.23, "CO2eq Emissions (g/m3)": 690.0 },
2301
- { "province": 'BC', "fuel_type": 'Electricity', "CO2eq Emissions (kg/MBtu)": 3.84, "CO2eq Emissions (g/m3)": 13.1 },
2302
- { "province": 'MB', "fuel_type": 'Electricity', "CO2eq Emissions (kg/MBtu)": 0.41, "CO2eq Emissions (g/m3)": 1.4 },
2303
- { "province": 'NB', "fuel_type": 'Electricity', "CO2eq Emissions (kg/MBtu)": 84.99, "CO2eq Emissions (g/m3)": 290.0 },
2304
- { "province": 'NL', "fuel_type": 'Electricity', "CO2eq Emissions (kg/MBtu)": 7.91, "CO2eq Emissions (g/m3)": 27.0 },
2305
- { "province": 'NT', "fuel_type": 'Electricity', "CO2eq Emissions (kg/MBtu)": 46.89, "CO2eq Emissions (g/m3)": 160.0 },
2306
- { "province": 'NS', "fuel_type": 'Electricity', "CO2eq Emissions (kg/MBtu)": 216.88, "CO2eq Emissions (g/m3)": 740.0 },
2307
- { "province": 'NU', "fuel_type": 'Electricity', "CO2eq Emissions (kg/MBtu)": 260.84, "CO2eq Emissions (g/m3)": 890.0 },
2308
- { "province": 'ON', "fuel_type": 'Electricity', "CO2eq Emissions (kg/MBtu)": 8.79, "CO2eq Emissions (g/m3)": 30.0 },
2309
- { "province": 'PE', "fuel_type": 'Electricity', "CO2eq Emissions (kg/MBtu)": 84.99, "CO2eq Emissions (g/m3)": 290.0 },
2310
- { "province": 'QC', "fuel_type": 'Electricity', "CO2eq Emissions (kg/MBtu)": 0.47, "CO2eq Emissions (g/m3)": 1.6 },
2311
- { "province": 'SK', "fuel_type": 'Electricity', "CO2eq Emissions (kg/MBtu)": 219.81, "CO2eq Emissions (g/m3)": 750.0 },
2312
- { "province": 'YT', "fuel_type": 'Electricity', "CO2eq Emissions (kg/MBtu)": 23.15, "CO2eq Emissions (g/m3)": 79.0 }
2313
- ]
2314
- mbtu_to_gj = 1.05505585
2315
- factor = ghg_data.detect { |item| (item[:province] == province) && (item[:fuel_type] == fuel_type) }
2316
- raise "could not find ghg factor for province name #{province} and fuel_type #{fuel_type}" if factor.nil?
2317
-
2318
- return factor[:"CO2eq Emissions (kg/MBtu)"] / mbtu_to_gj
2319
- end
2320
-
2321
- def bc_energy_step_code_performance_indicators
2322
- # TEDI (Thermal Energy Demand Intensity) [kWh/(m2.year)]
2323
- command = "SELECT Value
2324
- FROM TabularDataWithStrings
2325
- WHERE ReportName='EnergyMeters'
2326
- AND ReportForString='Entire Facility'
2327
- AND TableName='Annual and Peak Values - Other'
2328
- AND RowName='Baseboard:EnergyTransfer'
2329
- AND ColumnName='Annual Value'
2330
- AND Units='GJ'"
2331
- baseboard_energy_transfer_gj = @sqlite_file.get.execAndReturnFirstDouble(command)
2332
- baseboard_energy_transfer_kwh = OpenStudio.convert(baseboard_energy_transfer_gj.to_f, 'GJ', 'kWh')
2333
- command = "SELECT Value
2334
- FROM TabularDataWithStrings
2335
- WHERE ReportName='EnergyMeters'
2336
- AND ReportForString='Entire Facility'
2337
- AND TableName='Annual and Peak Values - Other'
2338
- AND RowName='HeatingCoils:EnergyTransfer'
2339
- AND ColumnName='Annual Value'
2340
- AND Units='GJ'"
2341
- heating_coils_energy_transfer_gj = @sqlite_file.get.execAndReturnFirstDouble(command)
2342
- heating_coils_energy_transfer_kwh = OpenStudio.convert(heating_coils_energy_transfer_gj.to_f, 'GJ', 'kWh')
2343
- tedi_kwh_per_m_sq = (baseboard_energy_transfer_kwh.to_f + heating_coils_energy_transfer_kwh.to_f) / @btap_data['bldg_conditioned_floor_area_m_sq']
2344
- @btap_data.merge!('bc_step_code_tedi_kwh_per_m_sq' => tedi_kwh_per_m_sq)
2345
-
2346
- # MEUI (Mechanical Energy Use Intensity) [kWh/(m2.year)]
2347
- meui_gj_per_m_sq = @btap_data['energy_eui_heating_gj_per_m_sq'].to_f +
2348
- @btap_data['energy_eui_cooling_gj_per_m_sq'].to_f +
2349
- @btap_data['energy_eui_fans_gj_per_m_sq'].to_f +
2350
- @btap_data['energy_eui_pumps_gj_per_m_sq'].to_f +
2351
- @btap_data['energy_eui_water systems_gj_per_m_sq'].to_f
2352
- meui_kwh_per_m_sq = OpenStudio.convert(meui_gj_per_m_sq, 'GJ', 'kWh').to_f
2353
- @btap_data.merge!('bc_step_code_meui_kwh_per_m_sq' => meui_kwh_per_m_sq)
2354
- end
2355
-
2356
- # The below method calculates energy demands and peak loads calculations as per PHIUS and NECB; and compares them to see if NECB meets PHIUS' performance criteria.
2357
- ### References:
2358
- ### (1) PHIUS 2021 Passive Building Standard Standard-Setting Documentation. Available at https://www.phius.org/phius-certification-for-buildings-products/project-certification/phius-2021-emissions-down-scale-up
2359
- ### (2) Wright, L. (2019). Setting the Heating/Cooling Performance Criteria for the PHIUS 2018 Passive Building Standard. In ASHRAE Topical Conference Proceedings, pp. 399-409
2360
- def phius_performance_indicators(model)
2361
- ### Envelope to Floor Area ratio (EnvFlr)
2362
- ### Note: 'Floor Area' has been considered as iCFA (interior conditioned floor area) as per REF: Wright (2019)
2363
- bldg_exterior_area_m_sq = @btap_data['bldg_exterior_area_m_sq']
2364
- bldg_conditioned_floor_area_m_sq = @btap_data['bldg_conditioned_floor_area_m_sq']
2365
- bldg_conditioned_floor_area_ft_sq = OpenStudio.convert(bldg_conditioned_floor_area_m_sq, 'm^2', 'ft^2').get
2366
- envelope_to_floor_area_ratio = bldg_exterior_area_m_sq / bldg_conditioned_floor_area_m_sq
2367
-
2368
- ### UnitDens: Unit density (1/ft2) (inverse of the floor area per unit) in PHIUS, 2021
2369
- # Note: if commercial buildings, set the number of units to 1 and divide by the floor area
2370
- # This is the list of building types considered as some sort of residential buildings when the whole building method is used
2371
- building_type_names_necb_2011 = ['Dormitory', 'Hospital', 'Hotel', 'Motel', 'Multi-unit residential', 'Penitentiary']
2372
- building_type_names_necb_2015 = ['Dormitory', 'Health care clinic', 'Hospital', 'Hotel/Motel', 'Long-term care - dwelling units', 'Long-term care - other', 'Multi-unit residential building', 'Penitentiary']
2373
- building_type_names_necb_2017 = ['Dormitory', 'Health care clinic', 'Hospital', 'Hotel/Motel', 'Long-term care - dwelling units', 'Long-term care - other', 'Multi-unit residential building', 'Penitentiary']
2374
- building_type_names_list = building_type_names_necb_2011 + building_type_names_necb_2015 + building_type_names_necb_2017
2375
- building_type_names_list = building_type_names_list.uniq
2376
- # This is the list of space types considered as some sort of residential spaces when the space type method is used
2377
- space_type_names_necb_2011 = ['Dormitory - living quarters', 'Dwelling Unit(s)', 'Hotel/Motel - rooms', 'Hway lodging - rooms']
2378
- space_type_names_necb_2015 = ['Guest room', 'Dormitory living quarters', 'Dwelling units general', 'Dwelling units long-term', 'Fire station sleeping quarters', 'Health care facility patient room', 'Health care facility recovery room']
2379
- space_type_names_necb_2017 = ['Guest room', 'Dormitory living quarters', 'Dwelling units general', 'Dwelling units long-term', 'Fire station sleeping quarters', 'Health care facility patient room', 'Health care facility recovery room']
2380
- space_type_names_list = space_type_names_necb_2011 + space_type_names_necb_2015 + space_type_names_necb_2017
2381
- space_type_names_list = space_type_names_list.uniq
2382
- sum_handle = 0.0
2383
- number_of_dwelling_units = 0.0
2384
- @btap_data['space_table'].each do |space_info|
2385
- building_type_name = space_info['building_type'].sub! 'building', ''
2386
- building_type_name = building_type_name.strip unless building_type_name.nil?
2387
- space_type_name = space_info['space_type_name']
2388
- if !space_type_name.include?('WholeBuilding')
2389
- # puts "This_is_the_space_type_method"
2390
- space_type_name = space_info['space_type_name'].sub! 'Space Function ', '' # This removes 'Space Function' from space type name
2391
- if space_type_names_list.include?(space_type_name)
2392
- number_of_dwelling_units += 1.0 * space_info['multiplier']
2393
- sum_handle += OpenStudio.convert(space_info['floor_area_m2'], 'm^2', 'ft^2').get * space_info['multiplier']
2394
- end
2395
- elsif space_type_name.include?('WholeBuilding') && building_type_names_list.include?(building_type_name) && space_info['is_conditioned'] == 'Yes'
2396
- # puts "This_is_the_whole_building_method"
2397
- number_of_dwelling_units += 1.0 * space_info['multiplier']
2398
- sum_handle += OpenStudio.convert(space_info['floor_area_m2'], 'm^2', 'ft^2').get * space_info['multiplier']
2399
- end
2400
- end
2401
- # Calculate what percentage of conditioned floor area has space types of the 'space_type_names_list' list.
2402
- # This percentage is used to determine if most of a building model is sort of dwelling type or not.
2403
- # The threshold for this percentage has been set to 60% based on the below reference:
2404
- # GSA (2012), Circulation: Defining and planning, U.S. General Services Administration, Available at Https://Www.Gsa.Gov/
2405
- # The above reference says: 'As a general planning rule of thumb, Circulation Area comprises roughly 25 to 40% of the total Usable Area.'
2406
- # Moreover, ~63% and ~41% of the SmallHotel and LargeHotel archetype, respectively, are guest rooms. So, the threshold has been chosen as 40%.
2407
- percentage_dwelling = 100.0 * sum_handle / bldg_conditioned_floor_area_ft_sq
2408
- # now, calculate UnitDens depending on whether a building model is sort of dwelling type or not
2409
- if percentage_dwelling >= 40.0 && number_of_dwelling_units > 0.0
2410
- unit_density_per_ft_sq = 1.0 / (sum_handle / number_of_dwelling_units)
2411
- else # i.e. if commercial buildings, set the number of units to 1 and divide by the floor area
2412
- unit_density_per_ft_sq = 1.0 / bldg_conditioned_floor_area_ft_sq
2413
- end
2414
-
2415
- ### Get weather file
2416
- weather_file_path = model.weatherFile.get.path.get.to_s
2417
- epw_file = model.weatherFile.get.file.get
2418
- stat_file_path = weather_file_path.gsub('.epw', '.stat')
2419
- stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path)
2420
-
2421
- ### Cooling Degree Days, base 50degF
2422
- cdd10_degree_c_days = stat_file.cdd10
2423
- cdd50_degree_f_days = cdd10_degree_c_days * 9.0 / 5.0
2424
-
2425
- ### Heating Degree Days, base 65degF (note that base temperature of 18degC has been considered)
2426
- hdd18_degree_c_days = stat_file.hdd18
2427
- hdd65_degree_f_days = hdd18_degree_c_days * 9.0 / 5.0
2428
-
2429
- ### Dehumidification degree days
2430
- ### ('Dehumidification degree-days, base 0.010' in REF: Wright (2019))
2431
- dehumidification_degree_days = OpenstudioStandards::Weather.epw_file_get_dehumidification_degree_days(epw_file)
2432
-
2433
- ### annual global horizontal irradiance (GHI)
2434
-
2435
- # Workaround for case when the weather file contains the February from a leap year but that February only has 28
2436
- # days of data.
2437
- has_leap_day = false
2438
-
2439
- # Find the first day in February
2440
- feb_index = epw_file.data.find_index { |entry| entry.date.monthOfYear.value == 2 }
2441
-
2442
- # Find the year for February
2443
- feb_year = epw_file.data[feb_index].year
2444
- # Determine if February's year is a leap year
2445
- leap_year = false
2446
- if (feb_year % 100) > 0
2447
- leap_year = true if (feb_year % 4) == 0
2448
- else
2449
- leap_year = true if (feb_year % 400) == 0
2450
- end
2451
- # If the February is from a leap year determine if it contains a leap day
2452
- if leap_year
2453
-
2454
- day = epw_file.data[feb_index].date.dayOfMonth
2455
- inc = 0
2456
-
2457
- while epw_file.data[feb_index].date.dayOfMonth == day
2458
- feb_index += 1
2459
- inc += 1
2460
- end
2461
-
2462
- has_leap_day = epw_file.data[feb_index + (inc * 28)].date.dayOfMonth == 29
2463
- end
2464
-
2465
- # If the February is from a leap year and there is no leap day then do not use the faulty OpenStudio Epw
2466
- # .getTimeSeries method. Otherwise, use the method.
2467
- if has_leap_day || !leap_year
2468
- ghi_timeseries = epw_file.getTimeSeries('Global Horizontal Radiation').get.values
2469
- else
2470
- # Access the data directly instead of using the OpenStudio API to avoid the faulty OpenStudioEpw
2471
- # .getTimeSeries method.
2472
-
2473
- # Open the weather file
2474
- regex_csv = /[^,]+/
2475
- regex_num = /[0-9]/
2476
- f = File.open(epw_file.path.to_s, 'r')
2477
- i = 0
2478
-
2479
- # Skip the header
2480
- i += 1 until f.readline[0] =~ regex_num
2481
-
2482
- # Get all of the hourly weather data
2483
- lines = IO.readlines(f)[i..-1]
2484
-
2485
- # Get hourly weather data for a specific column
2486
- ghi_timeseries = lines.map {|line| Float(line.scan(regex_csv)[13])}
2487
- end
2488
-
2489
- annual_ghi_kwh_per_m_sq = ghi_timeseries.sum / 1000.0
2490
-
2491
- ### THD-1 Temperature at the colder of the two heating design conditions in PHIUS, 2021
2492
- ### ('Heating design temperature' in REF: Wright (2019))
2493
- thd_degree_c = stat_file.heating_design_info[1]
2494
- thd_degree_f = OpenStudio.convert(thd_degree_c, 'C', 'F').get
2495
-
2496
- ### TCD Temperature at the cooling design condition in PHIUS, 2021
2497
- ### ('Cooling design temperature' in REF: Wright (2019))
2498
- tcd_degree_c = stat_file.cooling_design_info[2]
2499
- tcd_degree_f = OpenStudio.convert(tcd_degree_c.to_f, 'C', 'F').get
2500
-
2501
- ### IGHL (Irradiance, Global, at the heating design condition) (Btu/h.ft2) in PHIUS, 2021
2502
- average_daily_global_irradiance_w_per_m2_array = []
2503
- model.getDesignDays.each do |design_day|
2504
- next unless design_day.dayType == 'WinterDesignDay'
2505
-
2506
- average_daily_global_irradiance_w_per_m2 = OpenstudioStandards::Weather.design_day_average_global_irradiance(design_day)
2507
- average_daily_global_irradiance_w_per_m2_array << average_daily_global_irradiance_w_per_m2
2508
- end
2509
- solar_irradiance_on_heating_design_day_w_per_m_sq = average_daily_global_irradiance_w_per_m2_array.min
2510
- solar_irradiance_on_heating_design_day_btu_per_hr_ft_sq = OpenStudio.convert(solar_irradiance_on_heating_design_day_w_per_m_sq.to_f, 'W/m^2', 'Btu/ft^2*h').get
2511
-
2512
- ### IGCL (Irradiance, Global, at the cooling design condition) (Btu/h.ft2) in PHIUS, 2021
2513
- average_daily_global_irradiance_w_per_m2_array = []
2514
- model.getDesignDays.each do |design_day|
2515
- next unless design_day.dayType == 'SummerDesignDay'
2516
-
2517
- average_daily_global_irradiance_w_per_m2 = OpenstudioStandards::Weather.design_day_average_global_irradiance(design_day)
2518
- average_daily_global_irradiance_w_per_m2_array << average_daily_global_irradiance_w_per_m2
2519
- end
2520
- solar_irradiance_on_cooling_design_day_w_per_m_sq = average_daily_global_irradiance_w_per_m2_array.max
2521
- solar_irradiance_on_cooling_design_day_btu_per_hr_ft_sq = OpenStudio.convert(solar_irradiance_on_cooling_design_day_w_per_m_sq.to_f, 'W/m^2', 'Btu/ft^2*h').get
2522
-
2523
- ### occupant density (persons per ft2 of floor area)
2524
- sum_handle = 0.0
2525
- @btap_data['space_type_table'].each do |space_info|
2526
- unless space_info['occ_per_m_sq'].nil?
2527
- sum_handle += space_info['floor_m_sq'] * space_info['occ_per_m_sq']
2528
- end
2529
- end
2530
- occ_density_person_per_m_sq = sum_handle / bldg_conditioned_floor_area_m_sq
2531
- occ_density_person_per_ft_sq = OpenStudio.convert(occ_density_person_per_m_sq, 'ft^2', 'm^2').get
2532
-
2533
- ### marginal electricity price ($/kWh)
2534
- ### ('Electricity price' in REF: Wright (2019))
2535
- electricity_price_per_gj = @btap_data['cost_utility_neb_electricity_cost_per_m_sq'] / @btap_data['energy_eui_electricity_gj_per_m_sq']
2536
- electricity_price_per_kwh = OpenStudio.convert(electricity_price_per_gj, 'kWh', 'GJ').get # note: this is not GJ to kWh since 1/GJ should be converted to 1/kWh.
2537
-
2538
- ### Calculate annual heating and cooling energy demands based on PHIUS
2539
- # REF: page 27 of PHIUS 2021 Passive Building Standard Standard-Setting Documentation. Available at https://www.phius.org/phius-certification-for-buildings-products/project-certification/phius-2021-emissions-down-scale-up
2540
- annual_heating_demand_kbtu_per_ft_sq_phius = 3.2606827206 +
2541
- 1.1634499236 * envelope_to_floor_area_ratio +
2542
- 904.39163818 * unit_density_per_ft_sq +
2543
- 0.000604853 * hdd65_degree_f_days +
2544
- -0.001645777 * annual_ghi_kwh_per_m_sq +
2545
- -11.87299596 * electricity_price_per_kwh +
2546
- (envelope_to_floor_area_ratio - 1.766) * (envelope_to_floor_area_ratio - 1.766) * 0.8314860529 +
2547
- (envelope_to_floor_area_ratio - 1.766) * (hdd65_degree_f_days - 5860.0833333) * 0.0002310823 +
2548
- (hdd65_degree_f_days - 5860.0833333) * (hdd65_degree_f_days - 5860.0833333) * -5.736435e-8 +
2549
- (hdd65_degree_f_days - 5860.0833333) * (annual_ghi_kwh_per_m_sq - 1451.0633333) * -3.260379e-7 +
2550
- (envelope_to_floor_area_ratio - 1.766) * (electricity_price_per_kwh - -0.2029193333) * -3.851052937 +
2551
- (hdd65_degree_f_days - 5860.0833333) * (electricity_price_per_kwh - -0.2029193333) * -0.001897043
2552
- annual_heating_demand_kwh_per_m_sq_phius = OpenStudio.convert(annual_heating_demand_kbtu_per_ft_sq_phius, 'kBtu/ft^2', 'kWh/m^2').get
2553
- @btap_data.merge!('phius_annual_heating_demand_kwh_per_m_sq' => annual_heating_demand_kwh_per_m_sq_phius)
2554
-
2555
- # REF: page 28 of PHIUS 2021 Passive Building Standard Standard-Setting Documentation. Available at https://www.phius.org/phius-certification-for-buildings-products/project-certification/phius-2021-emissions-down-scale-up
2556
- annual_cooling_demand_kbtu_per_ft_sq_phius = -6.510791255 +
2557
- -0.749993351 * envelope_to_floor_area_ratio +
2558
- 0.0004550801 * cdd50_degree_f_days +
2559
- 0.004990109 * annual_ghi_kwh_per_m_sq +
2560
- 7.9460878688 * dehumidification_degree_days +
2561
- (envelope_to_floor_area_ratio - 1.766) * (envelope_to_floor_area_ratio - 1.766) * 1.6367059356 +
2562
- (cdd50_degree_f_days - 4104.8333333) * (cdd50_degree_f_days - 4104.8333333) * 8.6952014e-8 +
2563
- (envelope_to_floor_area_ratio - 1.766) * (annual_ghi_kwh_per_m_sq - 1451.0633333) * 0.001671947 +
2564
- (cdd50_degree_f_days - 4104.8333333) * (annual_ghi_kwh_per_m_sq - 1451.0633333) * 0.0000013639 +
2565
- (unit_density_per_ft_sq - 0.0008646735) * (dehumidification_degree_days - 0.3233057481) * 5547.7542211 +
2566
- (dehumidification_degree_days - 0.3233057481) * (electricity_price_per_kwh - 0.2029193333) * -15.67511944 +
2567
- 1624.6144639 * unit_density_per_ft_sq
2568
- annual_cooling_demand_kwh_per_m_sq_phius = OpenStudio.convert(annual_cooling_demand_kbtu_per_ft_sq_phius, 'kBtu/ft^2', 'kWh/m^2').get
2569
- @btap_data.merge!('phius_annual_cooling_demand_kwh_per_m_sq' => annual_cooling_demand_kwh_per_m_sq_phius)
2570
-
2571
- ### Calculate peak heating and cooling loads based on PHIUS
2572
- # REF: page 29 of PHIUS 2021 Passive Building Standard Standard-Setting Documentation. Available at https://www.phius.org/phius-certification-for-buildings-products/project-certification/phius-2021-emissions-down-scale-up
2573
- peak_heating_load_btu_per_hr_ft_sq_phius = 4.6700403241 +
2574
- 0.6774809481 * envelope_to_floor_area_ratio +
2575
- 239.08369574 * occ_density_person_per_ft_sq +
2576
- 596.681543 * unit_density_per_ft_sq +
2577
- -0.000177742 * hdd65_degree_f_days +
2578
- -0.076727655 * thd_degree_f +
2579
- -0.03316804 * solar_irradiance_on_heating_design_day_btu_per_hr_ft_sq +
2580
- -4.140193817 * electricity_price_per_kwh +
2581
- (envelope_to_floor_area_ratio - 1.766) * (envelope_to_floor_area_ratio - 1.766) * 0.8449921713 +
2582
- (hdd65_degree_f_days - 5860.0833333) * (hdd65_degree_f_days - 5860.0833333) * 2.8376386e-8 +
2583
- (envelope_to_floor_area_ratio - 1.766) * (thd_degree_f - 14.7102) * -0.013821021 +
2584
- (unit_density_per_ft_sq - 0.0008646735) * (thd_degree_f - 14.7102) * -20.10551451 +
2585
- (hdd65_degree_f_days - 5860.0833333) * (thd_degree_f - 14.7102) * 5.1870203e-6 +
2586
- (thd_degree_f - 14.7102) * (electricity_price_per_kwh - 0.2029193333) * 0.1264922802
2587
- peak_heating_load_w_per_m_sq_phius = OpenStudio.convert(peak_heating_load_btu_per_hr_ft_sq_phius, 'Btu/ft^2*h', 'W/m^2').get
2588
- @btap_data.merge!('phius_peak_heating_load_w_per_m_sq' => peak_heating_load_w_per_m_sq_phius)
2589
-
2590
- # REF: page 30 of PHIUS 2021 Passive Building Standard Standard-Setting Documentation. Available at https://www.phius.org/phius-certification-for-buildings-products/project-certification/phius-2021-emissions-down-scale-up
2591
- peak_cooling_load_btu_per_hr_ft_sq_phius = -7.289806442 +
2592
- 98.245977611 * occ_density_person_per_ft_sq +
2593
- 236.93351876 * unit_density_per_ft_sq +
2594
- 0.0967328928 * tcd_degree_f +
2595
- 0.010777725 * solar_irradiance_on_cooling_design_day_btu_per_hr_ft_sq +
2596
- (cdd50_degree_f_days - 4104.8333333) * (cdd50_degree_f_days - 4104.8333333) * 1.7699655e-8 +
2597
- (cdd50_degree_f_days - 4104.8333333) * (tcd_degree_f - 78.127) * 6.5268802e-6 +
2598
- (tcd_degree_f - 78.127) * (envelope_to_floor_area_ratio - 1.766) * 0.0165401721 +
2599
- (tcd_degree_f - 78.127) * (occ_density_person_per_ft_sq - 0.0027218) * 8.0465528305 +
2600
- (cdd50_degree_f_days - 4104.8333333) * (envelope_to_floor_area_ratio - 1.766) * 0.0000322288 +
2601
- (envelope_to_floor_area_ratio - 1.766) * (envelope_to_floor_area_ratio - 1.766) * 0.6579032913
2602
- peak_cooling_load_w_per_m_sq_phius = OpenStudio.convert(peak_cooling_load_btu_per_hr_ft_sq_phius, 'Btu/ft^2*h', 'W/m^2').get
2603
- @btap_data.merge!('phius_peak_cooling_load_w_per_m_sq' => peak_cooling_load_w_per_m_sq_phius)
2604
-
2605
- ### Gather annual heating and cooling energy demands based on NECB
2606
-
2607
- annual_heating_demand_kwh_per_m_sq_necb = OpenStudio.convert(@btap_data['energy_eui_heating_gj_per_m_sq'], 'GJ', 'kWh') unless @btap_data['energy_eui_heating_gj_per_m_sq'].nil?
2608
- annual_cooling_demand_kwh_per_m_sq_necb = OpenStudio.convert(@btap_data['energy_eui_cooling_gj_per_m_sq'], 'GJ', 'kWh') unless @btap_data['energy_eui_cooling_gj_per_m_sq'].nil?
2609
-
2610
- ### Gather peak heating and cooling loads based on NECB
2611
- peak_heating_load_w_per_m_sq_necb = @btap_data['heating_peak_w_per_m_sq']
2612
- peak_cooling_load_w_per_m_sq_necb = @btap_data['cooling_peak_w_per_m_sq']
2613
- @btap_data.merge!('peak_heating_load_w_per_m_sq_necb' => peak_heating_load_w_per_m_sq_necb)
2614
- @btap_data.merge!('peak_cooling_load_w_per_m_sq_necb' => peak_cooling_load_w_per_m_sq_necb)
2615
-
2616
- ### Compare annual heating and cooling energy demands of NECB with PHIUS to see if NECB meets PHIUS
2617
- if annual_heating_demand_kwh_per_m_sq_necb.to_f <= annual_heating_demand_kwh_per_m_sq_phius.to_f
2618
- @btap_data.merge!('phius_necb_meet_heating_demand' => 'True')
2619
- else
2620
- @btap_data.merge!('phius_necb_meet_heating_demand' => 'False')
2621
- end
2622
- if annual_cooling_demand_kwh_per_m_sq_necb.to_f <= annual_cooling_demand_kwh_per_m_sq_phius.to_f
2623
- @btap_data.merge!('phius_necb_meet_cooling_demand' => 'True')
2624
- else
2625
- @btap_data.merge!('phius_necb_meet_cooling_demand' => 'False')
2626
- end
2627
-
2628
- ### Compare peak heating and cooling loads of NECB with PHIUS to see if NECB meets PHIUS
2629
- if peak_heating_load_w_per_m_sq_necb.to_f <= peak_heating_load_w_per_m_sq_phius.to_f
2630
- @btap_data.merge!('phius_necb_meet_heating_peak_load' => 'True')
2631
- else
2632
- @btap_data.merge!('phius_necb_meet_heating_peak_load' => 'False')
2633
- end
2634
- if peak_cooling_load_w_per_m_sq_necb.to_f <= peak_cooling_load_w_per_m_sq_phius.to_f
2635
- @btap_data.merge!('phius_necb_meet_cooling_peak_load' => 'True')
2636
- else
2637
- @btap_data.merge!('phius_necb_meet_cooling_peak_load' => 'False')
2638
- end
2639
- # def phius_metrics(model)
2640
- end
2641
-
2642
- def get_national_ghg_cost(year:)
2643
- raise "Nil year submitted for national ghg cost" if year.nil?
2644
- year = year.to_i
2645
- if year < 2019
2646
- price = 0.0
2647
- elsif (year >= 2019) && (year <= 2030)
2648
- ghg_price_data = @standards_data["tables"]["canada_national_carbon_pricing"]["table"]
2649
- year_price = ghg_price_data.detect { |item| (item["year"].to_i == year) }
2650
- price = year_price["price_per_tonne"].to_f
2651
- elsif (year > 2030)
2652
- price = 170.0
2653
- end
2654
- return price.to_f
2655
- end
2656
- end