openstudio-standards 0.8.2 → 0.8.4
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.
- checksums.yaml +4 -4
- data/data/standards/OpenStudio_Standards-ashrae_90_1-ALL-comstock(space_types).xlsx +0 -0
- data/data/standards/openstudio_standards_duplicates_log.csv +7962 -0
- data/lib/openstudio-standards/btap/costing/README.md +502 -0
- data/lib/openstudio-standards/btap/costing/btap_costing.rb +473 -0
- data/lib/openstudio-standards/btap/costing/btap_measure_helper.rb +359 -0
- data/lib/openstudio-standards/btap/costing/btap_workflow.rb +117 -0
- data/lib/openstudio-standards/btap/costing/common_paths.rb +78 -0
- data/lib/openstudio-standards/btap/costing/common_resources/ConstructionProperties.csv +52 -0
- data/lib/openstudio-standards/btap/costing/common_resources/Constructions.csv +37 -0
- data/lib/openstudio-standards/btap/costing/common_resources/construction_sets.csv +1270 -0
- data/lib/openstudio-standards/btap/costing/common_resources/constructions_glazing.csv +61 -0
- data/lib/openstudio-standards/btap/costing/common_resources/constructions_opaque.csv +2256 -0
- data/lib/openstudio-standards/btap/costing/common_resources/costs.csv +1904 -0
- data/lib/openstudio-standards/btap/costing/common_resources/costs_local_factors.csv +2315 -0
- data/lib/openstudio-standards/btap/costing/common_resources/hvac_vent_ahu.csv +925 -0
- data/lib/openstudio-standards/btap/costing/common_resources/lighting.csv +364 -0
- data/lib/openstudio-standards/btap/costing/common_resources/lighting_sets.csv +2667 -0
- data/lib/openstudio-standards/btap/costing/common_resources/locations.csv +75 -0
- data/lib/openstudio-standards/btap/costing/common_resources/materials_glazing.csv +35 -0
- data/lib/openstudio-standards/btap/costing/common_resources/materials_hvac.csv +1699 -0
- data/lib/openstudio-standards/btap/costing/common_resources/materials_lighting.csv +267 -0
- data/lib/openstudio-standards/btap/costing/common_resources/materials_opaque.csv +164 -0
- data/lib/openstudio-standards/btap/costing/copy_test_results_files_to_expected_results.rb +11 -0
- data/lib/openstudio-standards/btap/costing/cost_building_from_file.rb +136 -0
- data/lib/openstudio-standards/btap/costing/costing_database_wrapper.rb +177 -0
- data/lib/openstudio-standards/btap/costing/daylighting_sensor_control_costing.rb +353 -0
- data/lib/openstudio-standards/btap/costing/dcv_costing.rb +314 -0
- data/lib/openstudio-standards/btap/costing/dummy.epw +8768 -0
- data/lib/openstudio-standards/btap/costing/dummy.osm +5320 -0
- data/lib/openstudio-standards/btap/costing/envelope_costing.rb +284 -0
- data/lib/openstudio-standards/btap/costing/heating_cooling_costing.rb +2584 -0
- data/lib/openstudio-standards/btap/costing/led_lighting_costing.rb +155 -0
- data/lib/openstudio-standards/btap/costing/lighting_costing.rb +209 -0
- data/lib/openstudio-standards/btap/costing/mech_sizing.json +502 -0
- data/lib/openstudio-standards/btap/costing/neb_end_use_prices.csv +42 -0
- data/lib/openstudio-standards/btap/costing/necb_2011_spacetype_info.csv +225 -0
- data/lib/openstudio-standards/btap/costing/necb_reference_runs.csv +28705 -0
- data/lib/openstudio-standards/btap/costing/nv_costing.rb +547 -0
- data/lib/openstudio-standards/btap/costing/parallel_tests.rb +92 -0
- data/lib/openstudio-standards/btap/costing/pv_ground_costing.rb +687 -0
- data/lib/openstudio-standards/btap/costing/shw_costing.rb +705 -0
- data/lib/openstudio-standards/btap/costing/test_list.txt +17 -0
- data/lib/openstudio-standards/btap/costing/test_run_all_test_locally.rb +26 -0
- data/lib/openstudio-standards/btap/costing/test_run_costing_tests.rb +80 -0
- data/lib/openstudio-standards/btap/costing/ventilation_costing.rb +2616 -0
- data/lib/openstudio-standards/constructions/modify.rb +2 -1
- data/lib/openstudio-standards/refrigeration/create_case.rb +58 -21
- data/lib/openstudio-standards/refrigeration/create_typical_refrigeration.rb +4 -2
- data/lib/openstudio-standards/refrigeration/create_walkin.rb +57 -22
- data/lib/openstudio-standards/refrigeration/data/refrigerated_cases.csv +31 -31
- data/lib/openstudio-standards/refrigeration/data/refrigerated_walkins.csv +76 -76
- data/lib/openstudio-standards/service_water_heating/create_typical.rb +10 -10
- data/lib/openstudio-standards/service_water_heating/create_water_heater.rb +10 -0
- data/lib/openstudio-standards/service_water_heating/create_water_heating_loop.rb +16 -3
- data/lib/openstudio-standards/service_water_heating/data/convert_data.rb +9 -9
- data/lib/openstudio-standards/service_water_heating/data/typical_water_use_equipment.csv +49 -49
- data/lib/openstudio-standards/service_water_heating/data/typical_water_use_equipment.json +159 -159
- data/lib/openstudio-standards/standards/Standards.CoilCoolingDXMultiSpeed.rb +7 -18
- data/lib/openstudio-standards/standards/Standards.CoilCoolingDXSingleSpeed.rb +10 -20
- data/lib/openstudio-standards/standards/Standards.CoilCoolingDXTwoSpeed.rb +6 -15
- data/lib/openstudio-standards/standards/Standards.CoilCoolingWaterToAirHeatPumpEquationFit.rb +5 -6
- data/lib/openstudio-standards/standards/Standards.CoilDX.rb +93 -43
- data/lib/openstudio-standards/standards/Standards.CoilHeatingDXMultiSpeed.rb +5 -5
- data/lib/openstudio-standards/standards/Standards.CoilHeatingDXSingleSpeed.rb +135 -37
- data/lib/openstudio-standards/standards/Standards.CoilHeatingWaterToAirHeatPumpEquationFit.rb +2 -2
- data/lib/openstudio-standards/standards/Standards.Model.rb +48 -13
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.computer_room_acs.json +302 -140
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.heat_pumps.json +648 -326
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.heat_pumps_heating.json +371 -90
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.water_heaters.json +66 -22
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.computer_room_acs.json +302 -140
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.heat_pumps.json +1012 -296
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.heat_pumps_heating.json +443 -79
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.water_heaters.json +66 -22
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.computer_room_acs.json +302 -140
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.heat_pumps.json +672 -306
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.heat_pumps_heating.json +354 -74
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.water_heaters.json +72 -24
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.computer_room_acs.json +302 -140
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.energy_recovery.json +8 -8
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.heat_pumps.json +930 -604
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.heat_pumps_heating.json +415 -111
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.water_heaters.json +72 -24
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.computer_room_acs.json +602 -140
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.heat_pumps.json +1005 -333
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.heat_pumps_heating.json +642 -88
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.water_heaters.json +78 -26
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.computer_room_acs.json +722 -140
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.heat_pumps.json +1741 -426
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.heat_pumps_heating.json +1108 -111
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.water_heaters.json +186 -62
- data/lib/openstudio-standards/standards/ashrae_90_1/data/ashrae_90_1.schedules.json +62 -232
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXSingleSpeed.rb +2 -3
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXTwoSpeed.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilDX.rb +7 -18
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingDXSingleSpeed.rb +9 -7
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingGas.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb +2 -2
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.heat_pumps.json +154 -69
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.heat_pumps_heating.json +72 -72
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.water_heaters.json +382 -295
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/ashrae_90_1_prm.UserData.rb +6 -1
- data/lib/openstudio-standards/standards/deer/data/deer.schedules.json +62 -232
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb +2 -27
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/heat_pumps.json +16 -0
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/heat_pumps_heating.json +6 -0
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/hvac_system_3_and_8_single_speed.rb +68 -27
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/hvac_system_4.rb +64 -25
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/hvac_system_6.rb +9 -14
- data/lib/openstudio-standards/standards/necb/ECMS/hvac_systems.rb +46 -20
- data/lib/openstudio-standards/standards/necb/NECB2011/autozone.rb +635 -248
- data/lib/openstudio-standards/standards/necb/NECB2011/data/constants.json +43 -7
- data/lib/openstudio-standards/standards/necb/NECB2011/data/fuel_type_sets.json +7 -1
- data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/HighriseApartmentMult.osm +14272 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/data/heat_pumps.json +16 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/data/heat_pumps_heating.json +6 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/data/necb_2015_table_c1.json +1 -1
- data/lib/openstudio-standards/standards/necb/NECB2011/data/space_types.json +437 -437
- data/lib/openstudio-standards/standards/necb/NECB2011/data/systems.json +516 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/data/systems_including_sys5.json +588 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_namer.rb +489 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_single_speed.rb +16 -6
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_2_and_5.rb +48 -5
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_multi_speed.rb +2 -2
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb +35 -27
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_4.rb +34 -23
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_6.rb +8 -6
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +43 -14
- data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +214 -25
- data/lib/openstudio-standards/standards/necb/NECB2011/system_fuels.rb +61 -1
- data/lib/openstudio-standards/standards/necb/NECB2015/data/heat_pumps.json +16 -0
- data/lib/openstudio-standards/standards/necb/NECB2015/data/heat_pumps_heating.json +8 -0
- data/lib/openstudio-standards/standards/necb/NECB2015/data/space_types.json +636 -636
- data/lib/openstudio-standards/standards/necb/NECB2015/data/unitary_acs.json +38 -38
- data/lib/openstudio-standards/standards/necb/NECB2015/hvac_systems.rb +15 -6
- data/lib/openstudio-standards/standards/necb/NECB2017/data/space_types.json +636 -636
- data/lib/openstudio-standards/standards/necb/NECB2020/data/chillers.json +71 -71
- data/lib/openstudio-standards/standards/necb/NECB2020/data/heat_pumps.json +20 -0
- data/lib/openstudio-standards/standards/necb/NECB2020/data/heat_pumps_heating.json +10 -0
- data/lib/openstudio-standards/standards/necb/README.md +343 -0
- data/lib/openstudio-standards/standards/necb/common/btap_data.rb +190 -28
- data/lib/openstudio-standards/standards/necb/common/btap_datapoint.rb +14 -5
- data/lib/openstudio-standards/standards/necb/common/eccc_electric_grid_intensity_20250311.csv +14 -0
- data/lib/openstudio-standards/standards/necb/common/nir_gas_grid_intensity_20250311.csv +14 -0
- data/lib/openstudio-standards/standards/necb/common/system_types.yaml +0 -0
- data/lib/openstudio-standards/utilities/logging.rb +18 -14
- data/lib/openstudio-standards/utilities/simulation.rb +3 -2
- data/lib/openstudio-standards/version.rb +1 -1
- data/lib/openstudio-standards/weather/modify.rb +2 -2
- data/lib/openstudio-standards.rb +12 -0
- metadata +56 -3
@@ -0,0 +1,2584 @@
|
|
1
|
+
class BTAPCosting
|
2
|
+
|
3
|
+
# --------------------------------------------------------------------------------------------------
|
4
|
+
# This function gets all costs associated with boilers (i.e., boilers, pumps, flues, electrical
|
5
|
+
# lines and boxes, fuel lines and distribution piping to zonal heating units)
|
6
|
+
# --------------------------------------------------------------------------------------------------
|
7
|
+
def boiler_costing(model, prototype_creator)
|
8
|
+
|
9
|
+
#Global flag to determine if a GSHP is present
|
10
|
+
@gshp_flag = false
|
11
|
+
|
12
|
+
#Global flag to determine if a AWHP is present
|
13
|
+
@awhp_flag = false
|
14
|
+
|
15
|
+
totalCost = 0.0
|
16
|
+
|
17
|
+
# Get regional cost factors for this province and city
|
18
|
+
materials_hvac = @costing_database["raw"]["materials_hvac"]
|
19
|
+
hvac_material = materials_hvac.select {|data|
|
20
|
+
data['Material'].to_s == "GasBoilers"}.first # Get any row from spreadsheet in case of region error
|
21
|
+
regional_material, regional_installation =
|
22
|
+
get_regional_cost_factors(@costing_report['province_state'], @costing_report['city'], hvac_material)
|
23
|
+
|
24
|
+
# Get regional electric cost factors for this province and city
|
25
|
+
hvac_material = materials_hvac.select {|data|
|
26
|
+
data['Material'].to_s.upcase == "BOX" && data['Size'].to_i == 1}.first
|
27
|
+
reg_mat_elec, reg_lab_elec =
|
28
|
+
get_regional_cost_factors(@costing_report['province_state'], @costing_report['city'], hvac_material)
|
29
|
+
|
30
|
+
# Store some geometry data for use below...
|
31
|
+
util_dist, ht_roof, nom_flr_hght, horz_dist, numAGFlrs, mechRmInBsmt = getGeometryData(model, prototype_creator)
|
32
|
+
|
33
|
+
template_type = prototype_creator.template
|
34
|
+
|
35
|
+
plant_loop_info = {}
|
36
|
+
plant_loop_info[:boilers] = []
|
37
|
+
plant_loop_info[:boilerpumps] = []
|
38
|
+
|
39
|
+
# Iterate through the plant loops to get boiler & pump data...
|
40
|
+
model.getPlantLoops.each do |plant_loop|
|
41
|
+
next unless (plant_loop.name.get.to_s.downcase == "hot water loop") || (plant_loop.name.get.to_s.downcase == "hw plantloop")
|
42
|
+
plant_loop.supplyComponents.each do |supply_comp|
|
43
|
+
if supply_comp.to_BoilerHotWater.is_initialized
|
44
|
+
boiler = supply_comp.to_BoilerHotWater.get
|
45
|
+
boiler_info = {}
|
46
|
+
plant_loop_info[:boilers] << boiler_info
|
47
|
+
boiler_info[:name] = boiler.name.get
|
48
|
+
# 2020-09-01 CK Include efficiency for boiler upgrade costing
|
49
|
+
boiler_info[:efficiency] = boiler.nominalThermalEfficiency.to_f
|
50
|
+
if boiler.fuelType =~ /Electric/i
|
51
|
+
boiler_info[:fueltype] = 'ElecBoilers'
|
52
|
+
elsif boiler.fuelType =~ /NaturalGas/i
|
53
|
+
boiler_info[:fueltype] = 'GasBoilers'
|
54
|
+
#2020-09-01 CK Include modifications for condensing and pulse gas boilers
|
55
|
+
if boiler_info[:efficiency] >= 0.827 && boiler_info[:efficiency] < 0.9
|
56
|
+
boiler_info[:fueltype] = "CondensingBoilers"
|
57
|
+
elsif boiler_info[:efficiency] >= 0.9
|
58
|
+
boiler_info[:fueltype] = "PulseBoilers"
|
59
|
+
end
|
60
|
+
elsif boiler.fuelType =~ /Oil/i # Oil, FuelOil, FuelOil#2
|
61
|
+
boiler_info[:fueltype] = 'OilBoilers'
|
62
|
+
end
|
63
|
+
boiler_info[:nominal_capacity] = boiler.nominalCapacity.to_f / 1000 # kW
|
64
|
+
elsif supply_comp.to_PumpConstantSpeed.is_initialized
|
65
|
+
csPump = supply_comp.to_PumpConstantSpeed.get
|
66
|
+
csPump_info = {}
|
67
|
+
plant_loop_info[:boilerpumps] << csPump_info
|
68
|
+
csPump_info[:name] = csPump.name.get
|
69
|
+
if csPump.isRatedPowerConsumptionAutosized.to_bool
|
70
|
+
csPumpSize = csPump.autosizedRatedPowerConsumption.to_f
|
71
|
+
else
|
72
|
+
csPumpSize = csPump.ratedPowerConsumption.to_f
|
73
|
+
end
|
74
|
+
csPump_info[:size] = csPumpSize.to_f # Watts
|
75
|
+
if csPump.isRatedFlowRateAutosized.to_bool
|
76
|
+
csPump_info[:water_flow_m3_per_s] = csPump.autosizedRatedFlowRate.to_f
|
77
|
+
else
|
78
|
+
csPump_info[:water_flow_m3_per_s] = csPump.ratedFlowRate.to_f
|
79
|
+
end
|
80
|
+
elsif supply_comp.to_PumpVariableSpeed.is_initialized
|
81
|
+
vsPump = supply_comp.to_PumpVariableSpeed.get
|
82
|
+
vsPump_info = {}
|
83
|
+
plant_loop_info[:boilerpumps] << vsPump_info
|
84
|
+
vsPump_info[:name] = vsPump.name.get
|
85
|
+
if vsPump.isRatedPowerConsumptionAutosized.to_bool
|
86
|
+
vsPumpSize = vsPump.autosizedRatedPowerConsumption.to_f
|
87
|
+
else
|
88
|
+
vsPumpSize = vsPump.ratedPowerConsumption.to_f
|
89
|
+
end
|
90
|
+
vsPump_info[:size] = vsPumpSize.to_f # Watts
|
91
|
+
if vsPump.isRatedFlowRateAutosized.to_bool
|
92
|
+
vsPump_info[:water_flow_m3_per_s] = vsPump.autosizedRatedFlowRate.to_f
|
93
|
+
else
|
94
|
+
vsPump_info[:water_flow_m3_per_s] = vsPump.ratedFlowRate.to_f
|
95
|
+
end
|
96
|
+
elsif supply_comp.to_HeatPumpWaterToWaterEquationFitHeating.is_initialized
|
97
|
+
gshp = supply_comp.to_HeatPumpWaterToWaterEquationFitHeating.get
|
98
|
+
gshp_info = {}
|
99
|
+
gshp_info[:fueltype] = 'wshp'
|
100
|
+
gshp_info[:name] = gshp.name.to_s
|
101
|
+
if gshp.isRatedHeatingCapacityAutosized.to_bool
|
102
|
+
gshp_info[:nominal_capacity] = gshp.autosizedRatedHeatingCapacity.to_f/1000.0
|
103
|
+
else
|
104
|
+
gshp_info[:nominal_capacity] = gshp.ratedHeatingCapacity.to_f/1000.0
|
105
|
+
end
|
106
|
+
plant_loop_info[:boilers] << gshp_info
|
107
|
+
@gshp_flag = true
|
108
|
+
elsif supply_comp.to_HeatPumpPlantLoopEIRHeating.is_initialized
|
109
|
+
awhp = supply_comp.to_HeatPumpPlantLoopEIRHeating.get
|
110
|
+
awhp_info = {}
|
111
|
+
awhp_info[:fueltype] = 'Airtowaterhp'
|
112
|
+
awhp_info[:name] = awhp.name.to_s
|
113
|
+
if awhp.isReferenceCapacityAutosized.to_bool
|
114
|
+
awhp_info[:nominal_capacity] = awhp.autosizedReferenceCapacity.to_f/1000.0
|
115
|
+
else
|
116
|
+
awhp_info[:nominal_capacity] = awhp.referenceCapacity.to_f/1000.0
|
117
|
+
end
|
118
|
+
plant_loop_info[:boilers] << awhp_info
|
119
|
+
@awhp_flag = true
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
boilerCost = 0.0 ; thisBoilerCost = 0.0 ; flueCost = 0.0 ; utilCost = 0.0 ; fuelFittingCost = 0.0
|
125
|
+
numBoilers = 0 ; multiplier = 1.0 ; primaryFuel = ''; primaryCap = 0 ; backupBoiler = false
|
126
|
+
|
127
|
+
# Get costs associated with each boiler
|
128
|
+
plant_loop_info[:boilers].each do |boiler|
|
129
|
+
|
130
|
+
# Get primary/secondary/backup boiler cost based on fuel type and capacity for each boiler
|
131
|
+
# 06-Sep-2019 JTB: Added check for no 'Primary' or 'Secondary' label and assume primary.
|
132
|
+
# This boiler prefix name seemed to disappear after the heat pump work was committed.
|
133
|
+
numBoilers += 1
|
134
|
+
if boiler[:name] =~ /primary/i || (boiler[:name] !~ /primary/i && boiler[:name] !~ /secondary/ && numBoilers == 1) || (boiler[:fuel_type] == 'wshp') || (boiler[:fuel_type] == 'Airtowaterhp')
|
135
|
+
primaryFuel = boiler[:fueltype]
|
136
|
+
primaryCap = boiler[:nominal_capacity]
|
137
|
+
matCost, labCost = getHVACCost(boiler[:name], boiler[:fueltype], boiler[:nominal_capacity], false)
|
138
|
+
|
139
|
+
# 2020-09-02 CK: Assume condensing oil boilers cost twice as much as non-condensing oil boilers
|
140
|
+
if boiler[:fueltype] == 'OilBoilers' && boiler[:efficiency] >= 0.9
|
141
|
+
thisBoilerCost = matCost * 2 * regional_material / 100.0 + labCost * regional_installation / 100.0
|
142
|
+
else
|
143
|
+
thisBoilerCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
144
|
+
end
|
145
|
+
|
146
|
+
# Flue and utility component costs (for gas and oil boilers only)
|
147
|
+
# 2020-09-02 CK: Include pulse and condensing gas boilers to those with utility component costs
|
148
|
+
if boiler[:fueltype] == 'GasBoilers' || boiler[:fueltype] == 'OilBoilers' || boiler[:fueltype] == 'CondensingBoilers' || boiler[:fueltype] == 'PulseBoilers'
|
149
|
+
# Calculate flue costs once for all boilers since flues combined by header when multiple boilers
|
150
|
+
# 6 inch diameter flue (#384)
|
151
|
+
materialHash = get_cost_info(mat: 'Venting', size: '6')
|
152
|
+
matCost, labCost = getCost('flue', materialHash, multiplier)
|
153
|
+
flueVentCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
154
|
+
|
155
|
+
#6 inch elbow fitting (#386)
|
156
|
+
materialHash = get_cost_info(mat: 'VentingElbow', size: '6')
|
157
|
+
matCost, labCost = getCost('flue elbow', materialHash, multiplier)
|
158
|
+
flueElbowCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
159
|
+
|
160
|
+
# 6 inch top (#392)
|
161
|
+
materialHash = get_cost_info(mat: 'VentingTop', size: '6')
|
162
|
+
matCost, labCost = getCost('flue top', materialHash, multiplier)
|
163
|
+
flueTopCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
164
|
+
|
165
|
+
# Gas/Oil line piping cost per ft (#1)
|
166
|
+
materialHash = get_cost_info(mat: 'GasLine', unit: 'L.F.')
|
167
|
+
matCost, labCost = getCost('fuel line', materialHash, multiplier)
|
168
|
+
fuelLineCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
169
|
+
|
170
|
+
# Gas/Oil line fitting connection per boiler (#2)
|
171
|
+
materialHash = get_cost_info(mat: 'GasLine', unit: 'each')
|
172
|
+
matCost, labCost = getCost('fuel line fitting connection', materialHash, multiplier)
|
173
|
+
fuelFittingCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
174
|
+
|
175
|
+
# Header cost only non-zero if there is a secondary/backup gas/oil boiler
|
176
|
+
headerCost = 0.0
|
177
|
+
else # Electric has no flue
|
178
|
+
flueVentCost = 0.0 ; flueElbowCost = 0.0 ; flueTopCost = 0.0 ; headerCost = 0.0
|
179
|
+
end
|
180
|
+
|
181
|
+
# Electric utility cost components (i.e., power lines).
|
182
|
+
# Calculate utility cost for primary boiler only since multiple boilers use common utilities
|
183
|
+
|
184
|
+
# elec 600V #14 wire /100 ft (#848)
|
185
|
+
materialHash = get_cost_info(mat: 'Wiring', size: 14)
|
186
|
+
matCost, labCost = getCost('electrical wire - 600V #14', materialHash, multiplier)
|
187
|
+
elecWireCost = matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0
|
188
|
+
|
189
|
+
# 1 inch metal conduit (#851)
|
190
|
+
materialHash = get_cost_info(mat: 'Conduit', unit: 'L.F.')
|
191
|
+
matCost, labCost = getCost('1 inch metal conduit', materialHash, multiplier)
|
192
|
+
metalConduitCost = matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0
|
193
|
+
|
194
|
+
# 2020-09-02 CK Adding Condensing and Pulse boilers to those that need additional connections
|
195
|
+
if boiler[:fueltype] == 'GasBoilers' || boiler[:fueltype] == 'CondensingBoilers' || boiler[:fueltype] == 'PulseBoilers'
|
196
|
+
# Gas boilers require fuel line+valves+connectors and electrical conduit
|
197
|
+
utilCost += (fuelLineCost + metalConduitCost) * util_dist + fuelFittingCost +
|
198
|
+
elecWireCost * util_dist / 100
|
199
|
+
|
200
|
+
elsif boiler[:fueltype] == 'OilBoilers'
|
201
|
+
# Oil boilers require fuel line+valves+connectors and electrical conduit
|
202
|
+
|
203
|
+
# Oil filtering system (#4)
|
204
|
+
materialHash = get_cost_info(mat: 'OilLine', unit: 'each')
|
205
|
+
matCost, labCost = getCost('Oil filtering system', materialHash, multiplier)
|
206
|
+
oilFilterCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
207
|
+
|
208
|
+
# 2000 USG above ground tank (#5)
|
209
|
+
materialHash = get_cost_info(mat: 'OilTanks', size: 2000)
|
210
|
+
matCost, labCost = getCost('Oil tank (2000 USG)', materialHash, multiplier)
|
211
|
+
oilTankCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
212
|
+
|
213
|
+
utilCost += (fuelLineCost + metalConduitCost) * util_dist + fuelFittingCost +
|
214
|
+
elecWireCost * util_dist / 100 + oilFilterCost + oilTankCost
|
215
|
+
|
216
|
+
elsif boiler[:fueltype].to_s.downcase == 'elecboilers' || boiler[:fueltype].to_s.downcase == 'wshp'
|
217
|
+
# Electric boilers require only conduit
|
218
|
+
utilCost += metalConduitCost * util_dist + elecWireCost * util_dist / 100
|
219
|
+
elsif boiler[:fuel_type].to_s.downcase == 'airtowaterhp'
|
220
|
+
# Add heating buffer tank for awhp
|
221
|
+
materialHash = get_cost_info(mat: 'solartank', size: 450)
|
222
|
+
matCost, labCost = getCost('solartank', materialHash, multiplier)
|
223
|
+
utilCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
224
|
+
end
|
225
|
+
|
226
|
+
elsif boiler[:name] =~ /secondary/i || numBoilers > 1
|
227
|
+
if boiler[:nominal_capacity] > 0.1
|
228
|
+
# A secondary boiler exists so use it for costing
|
229
|
+
matCost, labCost = getHVACCost(boiler[:name], boiler[:fueltype], boiler[:nominal_capacity], false)
|
230
|
+
# 2020-09-02 CK: Assume condensing oil boilers cost twice as much as non-condensing oil boilers
|
231
|
+
if boiler[:fueltype] == 'OilBoilers' && boiler[:efficiency] >= 0.9
|
232
|
+
thisBoilerCost = matCost * 2 * regional_material / 100.0 + labCost * regional_installation / 100.0
|
233
|
+
else
|
234
|
+
thisBoilerCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
235
|
+
end
|
236
|
+
else
|
237
|
+
# Use existing value of thisBoilerCost to represent a backup boiler!
|
238
|
+
# This just doubles the cost of the primary boiler.
|
239
|
+
# 2023-04-25: Leaving backup boiler in energy model but no longer costing.
|
240
|
+
backupBoiler = false
|
241
|
+
thisBoilerCost = 0.0
|
242
|
+
numBoilers -= 1
|
243
|
+
end
|
244
|
+
|
245
|
+
# Flue costs set to zero if secondary boiler since already calculated in primary
|
246
|
+
flueVentCost = 0.0; flueElbowCost = 0.0; flueTopCost = 0.0
|
247
|
+
|
248
|
+
# Check if need a flue header (i.e., there are both primary and secondary/backup boilers)
|
249
|
+
if thisBoilerCost > 0.0 && ( (backupBoiler && primaryFuel != 'ElecBoilers') || (boiler[:fueltype] != 'ElecBoilers') || (boiler[:fueltype] != 'wshp') || (boiler[:fueltype] != 'Airtowaterhp'))
|
250
|
+
# 6 inch diameter header (#384)
|
251
|
+
materialHash = get_cost_info(mat: 'Venting', size: 6)
|
252
|
+
matCost, labCost = getCost('flue header', materialHash, multiplier)
|
253
|
+
headerVentCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
254
|
+
|
255
|
+
#6 inch elbow fitting for header (#386)
|
256
|
+
materialHash = get_cost_info(mat: 'VentingElbow', size: 6)
|
257
|
+
matCost, labCost = getCost('flue header elbow', materialHash, multiplier)
|
258
|
+
headerElbowCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
259
|
+
|
260
|
+
# Assume a header length of 20 ft and an elbow fitting for each boiler connected to the header
|
261
|
+
headerCost = (headerVentCost * 20 + headerElbowCost) * numBoilers
|
262
|
+
else
|
263
|
+
headerCost = 0.0
|
264
|
+
end
|
265
|
+
end
|
266
|
+
boilerCost += thisBoilerCost
|
267
|
+
flueCost += flueVentCost * ht_roof + flueElbowCost + flueTopCost + headerCost
|
268
|
+
if numBoilers > 1
|
269
|
+
# Adjust utility cost for extra fuel line fitting cost
|
270
|
+
utilCost += fuelFittingCost * (numBoilers - 1)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Boiler pump costs
|
275
|
+
pumpCost = 0.0; pipingToPumpCost = 0.0; numPumps = 0; pumpName = ''; pumpSize = 0.0 ; pumpFlow = 0.0
|
276
|
+
plant_loop_info[:boilerpumps].each do |pump|
|
277
|
+
numPumps += 1
|
278
|
+
# Cost variable and constant volume pumps the same (the difference is in extra cost for VFD controller)
|
279
|
+
pumpSize = pump[:size]; pumpName = pump[:name]
|
280
|
+
pumpFlow += pump[:water_flow_m3_per_s].to_f
|
281
|
+
matCost, labCost = getHVACCost(pumpName, 'Pumps', pumpSize, false)
|
282
|
+
pumpCost += matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
283
|
+
if pump[:name] =~ /variable/i
|
284
|
+
# Cost the VFD controller for the variable pump
|
285
|
+
matCost, labCost = getHVACCost(pumpName, 'VFD', pumpSize, false)
|
286
|
+
pumpCost += matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0
|
287
|
+
end
|
288
|
+
end
|
289
|
+
if numBoilers > 1 && numPumps < 2
|
290
|
+
# Add pump costing for the backup boiler pump.
|
291
|
+
pumpCost *= 2.0
|
292
|
+
numPumps = 2 # reset the number of pumps for piping costs below
|
293
|
+
end
|
294
|
+
# Double the pump costs to accomodate the costing of a backup pumps for each boiler!
|
295
|
+
# No longer costing backup pumps.
|
296
|
+
# pumpCost *= 2.0
|
297
|
+
|
298
|
+
# Boiler water piping to pumps cost: Add piping elbows, valves and insulation from the boiler(s)
|
299
|
+
# to the pumps(s) assuming a pipe diameter of 1” and a distance of 10 ft per pump
|
300
|
+
|
301
|
+
if numBoilers > 0
|
302
|
+
# 1 inch Steel pipe
|
303
|
+
matCost, labCost = getHVACCost('1 inch steel pipe', 'SteelPipe', 1)
|
304
|
+
pipingToPumpCost += 10.0 * numPumps * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
305
|
+
|
306
|
+
# 1 inch Steel pipe insulation
|
307
|
+
matCost, labCost = getHVACCost('1 inch pipe insulation', 'PipeInsulation', 1)
|
308
|
+
pipingToPumpCost += 10.0 * numPumps * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
309
|
+
|
310
|
+
# 1 inch Steel pipe elbow
|
311
|
+
matCost, labCost = getHVACCost('1 inch steel pipe elbow', 'SteelPipeElbow', 1)
|
312
|
+
pipingToPumpCost += 2.0 * numPumps * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
313
|
+
|
314
|
+
# 1 inch gate valves
|
315
|
+
matCost, labCost = getHVACCost('1 inch gate valves', 'ValvesGate', 1)
|
316
|
+
pipingToPumpCost += 1.0 * numPumps * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
317
|
+
end
|
318
|
+
|
319
|
+
if numBoilers > 0
|
320
|
+
# Double pump piping cost to account for second boiler
|
321
|
+
pipingToPumpCost *= numBoilers
|
322
|
+
|
323
|
+
hdrDistributionCost = getHeaderPipingDistributionCost(numAGFlrs, mechRmInBsmt, regional_material, regional_installation, reg_mat_elec, reg_lab_elec,
|
324
|
+
pumpFlow, horz_dist, nom_flr_hght)
|
325
|
+
else
|
326
|
+
pipingToPumpCost = 0
|
327
|
+
hdrDistributionCost = 0
|
328
|
+
end
|
329
|
+
|
330
|
+
totalCost = boilerCost + flueCost + utilCost + pumpCost + pipingToPumpCost + hdrDistributionCost
|
331
|
+
|
332
|
+
@costing_report['heating_and_cooling']['plant_equipment'] << {
|
333
|
+
'type' => 'boilers',
|
334
|
+
'nom_flr2flr_hght_ft' => nom_flr_hght.round(1),
|
335
|
+
'ht_roof_ft' => ht_roof.round(1),
|
336
|
+
'longest_distance_to_ext_ft' => horz_dist.round(1),
|
337
|
+
'wiring_and_gas_connections_distance_ft' => util_dist.round(1),
|
338
|
+
'equipment_cost' => boilerCost.round(0),
|
339
|
+
'flue_cost' => flueCost.round(0),
|
340
|
+
'wiring_and_gas_connections_cost' => utilCost.round(0),
|
341
|
+
'pump_cost' => pumpCost.round(0),
|
342
|
+
'piping_to_pump_cost' => pipingToPumpCost.round(0),
|
343
|
+
'header_distribution_cost' => hdrDistributionCost.round(0),
|
344
|
+
'total_cost' => totalCost.round(0)
|
345
|
+
}
|
346
|
+
puts "\nHVAC Boiler costing data successfully generated. Total boiler costs: $#{totalCost.round(0)}"
|
347
|
+
|
348
|
+
return totalCost
|
349
|
+
end
|
350
|
+
|
351
|
+
# --------------------------------------------------------------------------------------------------
|
352
|
+
# Chiller costing is similar to boiler costing above
|
353
|
+
# --------------------------------------------------------------------------------------------------
|
354
|
+
def chiller_costing(model, prototype_creator)
|
355
|
+
|
356
|
+
totalCost = 0.0
|
357
|
+
|
358
|
+
# Get regional cost factors for this province and city
|
359
|
+
materials_hvac = @costing_database["raw"]["materials_hvac"]
|
360
|
+
hvac_material = materials_hvac.select {|data|
|
361
|
+
data['Material'].to_s == "GasBoilers"}.first # Get any row from spreadsheet in case of region error
|
362
|
+
regional_material, regional_installation =
|
363
|
+
get_regional_cost_factors(@costing_report['province_state'], @costing_report['city'], hvac_material)
|
364
|
+
|
365
|
+
# Get regional electric cost factors for this province and city
|
366
|
+
hvac_material = materials_hvac.select {|data|
|
367
|
+
data['Material'].to_s.upcase == "BOX" && data['Size'].to_i == 1}.first
|
368
|
+
reg_mat_elec, reg_lab_elec =
|
369
|
+
get_regional_cost_factors(@costing_report['province_state'], @costing_report['city'], hvac_material)
|
370
|
+
|
371
|
+
# Store some geometry data for use below...
|
372
|
+
util_dist, ht_roof, nom_flr_hght, horz_dist, numAGFlrs, mechRmInBsmt = getGeometryData(model, prototype_creator)
|
373
|
+
|
374
|
+
template_type = prototype_creator.template
|
375
|
+
|
376
|
+
chillerCost = 0.0 ; thisChillerCost = 0.0 ; flueCost = 0.0 ; utilCost = 0.0
|
377
|
+
plant_loop_info = {}
|
378
|
+
plant_loop_info[:chillers] = []
|
379
|
+
plant_loop_info[:chillerpumps] = []
|
380
|
+
awhp_chiller = false
|
381
|
+
|
382
|
+
# Iterate through the plant loops to get chiller & pump data...
|
383
|
+
model.getPlantLoops.each do |plant_loop|
|
384
|
+
next unless (plant_loop.name.get.to_s.downcase == "chilled water loop") || (plant_loop.name.get.to_s.downcase == "chw plantloop")
|
385
|
+
plant_loop.supplyComponents.each do |supply_comp|
|
386
|
+
if supply_comp.to_ChillerElectricEIR.is_initialized #|| supply_comp.to_ChillerGasEIR.is_initialized
|
387
|
+
chiller = supply_comp.to_ChillerElectricEIR.get
|
388
|
+
chiller_info = {}
|
389
|
+
plant_loop_info[:chillers] << chiller_info
|
390
|
+
chiller_info[:name] = chiller.name.get
|
391
|
+
if chiller_info[:name] =~ /WaterCooled/i
|
392
|
+
if chiller_info[:name] =~ /Absorption/i
|
393
|
+
chiller_info[:type] = 'HotAbsChiller'
|
394
|
+
chiller_info[:fuel] = 'NaturalGas'
|
395
|
+
elsif chiller_info[:name] =~ /Direct Gas/i
|
396
|
+
chiller_info[:type] = 'GasAbsChiller'
|
397
|
+
chiller_info[:fuel] = 'NaturalGas'
|
398
|
+
elsif chiller_info[:name] =~ /Centrifugal/i
|
399
|
+
chiller_info[:type] = 'CentChillerWater'
|
400
|
+
chiller_info[:fuel] = 'Electric'
|
401
|
+
elsif chiller_info[:name] =~ /Reciprocating/i
|
402
|
+
chiller_info[:type] = 'RecChillerWater'
|
403
|
+
chiller_info[:fuel] = 'Electric'
|
404
|
+
elsif chiller_info[:name] =~ /Scroll/i
|
405
|
+
chiller_info[:type] = 'ScrollChillerWater'
|
406
|
+
chiller_info[:fuel] = 'Electric'
|
407
|
+
elsif chiller_info[:name] =~ /Screw/i
|
408
|
+
chiller_info[:type] = 'ScrewChillerWater'
|
409
|
+
chiller_info[:fuel] = 'Electric'
|
410
|
+
end
|
411
|
+
elsif chiller_info[:name] =~ /AirCooled/i
|
412
|
+
if chiller_info[:name] =~ /Reciprocating/i
|
413
|
+
chiller_info[:type] = 'RecChillerAir'
|
414
|
+
chiller_info[:fuel] = 'Electric'
|
415
|
+
elsif chiller_info[:name] =~ /Scroll/i
|
416
|
+
chiller_info[:type] = 'ScrollChillerAir'
|
417
|
+
chiller_info[:fuel] = 'Electric'
|
418
|
+
elsif chiller_info[:name] =~ /Screw/i
|
419
|
+
chiller_info[:type] = 'ScrewChillerAir'
|
420
|
+
chiller_info[:fuel] = 'Electric'
|
421
|
+
elsif chiller_info[:name] =~ /DX/i
|
422
|
+
chiller_info[:type] = 'DXChiller'
|
423
|
+
chiller_info[:fuel] = 'Electric'
|
424
|
+
end
|
425
|
+
end
|
426
|
+
chiller_info[:reference_capacity] = chiller.referenceCapacity.to_f / 1000 # kW
|
427
|
+
elsif supply_comp.to_HeatPumpPlantLoopEIRCooling.is_initialized
|
428
|
+
chiller = supply_comp.to_HeatPumpPlantLoopEIRCooling.get
|
429
|
+
chiller_info = {}
|
430
|
+
chiller_info[:name] = chiller.name.get
|
431
|
+
chiller_info[:type] = 'Airtowaterhp'
|
432
|
+
chiller_info[:fuel] = 'Electric'
|
433
|
+
if chiller.isReferenceCapacityAutosized
|
434
|
+
chiller_info[:reference_capacity] = chiller.autosizedReferenceCapacity.to_f / 1000 # kW
|
435
|
+
else
|
436
|
+
chiller_info[:reference_capacity] = chiller.referenceCapacity.to_f / 1000 # kW
|
437
|
+
end
|
438
|
+
awhp_chiller = true
|
439
|
+
plant_loop_info[:chillers] << chiller_info
|
440
|
+
elsif supply_comp.to_PumpConstantSpeed.is_initialized
|
441
|
+
csPump = supply_comp.to_PumpConstantSpeed.get
|
442
|
+
csPump_info = {}
|
443
|
+
plant_loop_info[:chillerpumps] << csPump_info
|
444
|
+
csPump_info[:name] = csPump.name.get
|
445
|
+
if csPump.isRatedPowerConsumptionAutosized.to_bool
|
446
|
+
csPumpSize = csPump.autosizedRatedPowerConsumption.to_f
|
447
|
+
else
|
448
|
+
csPumpSize = csPump.ratedPowerConsumption.to_f
|
449
|
+
end
|
450
|
+
csPump_info[:size] = csPumpSize.to_f # Watts
|
451
|
+
if csPump.isRatedFlowRateAutosized.to_bool
|
452
|
+
csPump_info[:water_flow_m3_per_s] = csPump.autosizedRatedFlowRate.to_f
|
453
|
+
else
|
454
|
+
csPump_info[:water_flow_m3_per_s] = csPump.ratedFlowRate.to_f
|
455
|
+
end
|
456
|
+
elsif supply_comp.to_PumpVariableSpeed.is_initialized
|
457
|
+
vsPump = supply_comp.to_PumpVariableSpeed.get
|
458
|
+
vsPump_info = {}
|
459
|
+
plant_loop_info[:chillerpumps] << vsPump_info
|
460
|
+
vsPump_info[:name] = vsPump.name.get
|
461
|
+
if vsPump.isRatedPowerConsumptionAutosized.to_bool
|
462
|
+
vsPumpSize = vsPump.autosizedRatedPowerConsumption.to_f
|
463
|
+
else
|
464
|
+
vsPumpSize = vsPump.ratedPowerConsumption.to_f
|
465
|
+
end
|
466
|
+
vsPump_info[:size] = vsPumpSize.to_f # Watts
|
467
|
+
if vsPump.isRatedFlowRateAutosized.to_bool
|
468
|
+
vsPump_info[:water_flow_m3_per_s] = vsPump.autosizedRatedFlowRate.to_f
|
469
|
+
else
|
470
|
+
vsPump_info[:water_flow_m3_per_s] = vsPump.ratedFlowRate.to_f
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
# Get costs associated with each chiller
|
477
|
+
numChillers = 0 ; multiplier = 1.0
|
478
|
+
primaryFuel = ''; primaryCap = 0
|
479
|
+
|
480
|
+
plant_loop_info[:chillers].each do |chiller|
|
481
|
+
|
482
|
+
# Get primary/secondary/backup chiller cost based on type and capacity for each chiller
|
483
|
+
# 06-Sep-2019 JTB: Added check for no 'Primary' or 'Secondary' label and assume primary.
|
484
|
+
# This chiller prefix name seemed to disappear after the heat pump work was committed.
|
485
|
+
numChillers += 1
|
486
|
+
if chiller[:type].to_s.downcase == 'airtowaterhp'
|
487
|
+
primaryFuel = chiller[:fuel]
|
488
|
+
primaryCap = chiller[:reference_capacity] #kW
|
489
|
+
# Add cooling buffer tank for awhp
|
490
|
+
materialHash = get_cost_info(mat: 'solartank', size: 450)
|
491
|
+
matCost, labCost = getCost('solartank', materialHash, multiplier) #Costing for AWHP only buffer tank, AWHP included in boiler cost
|
492
|
+
thisChillerCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
493
|
+
# Include 2 expansion tanks for awhp
|
494
|
+
materialHash = get_cost_info(mat: 'ExpansionTanks', size: 60)
|
495
|
+
matCost, labCost = getCost('ExpansionTanks', materialHash, multiplier) #Costing for AWHP only buffer tank, AWHP included in boiler cost
|
496
|
+
thisChillerCost += (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * 2
|
497
|
+
# Inclide glycol cost
|
498
|
+
materalHash = get_cost_info(mat: 'glycol')
|
499
|
+
matCost, labCost = getCost('solartank', materialHash, multiplier) #Costing for AWHP only buffer tank, AWHP included in boiler cost
|
500
|
+
thisChillerCost += (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * 2
|
501
|
+
|
502
|
+
flueVentCost = 0.0 ; flueElbowCost = 0.0 ; flueTopCost = 0.0 ; headerCost = 0.0
|
503
|
+
elsif ((chiller[:name].to_s.downcase =~ /primary/i || (chiller[:name] !~ /primary/i && chiller[:name] !~ /secondary/i && numChillers == 1)) || (@gshp_flag))
|
504
|
+
primaryFuel = chiller[:fuel]
|
505
|
+
primaryCap = chiller[:reference_capacity] #kW
|
506
|
+
if not chiller[:name].include?("ChillerElectricEIR_VSDCentrifugalWaterChiller")
|
507
|
+
matCost, labCost = getHVACCost(chiller[:name], chiller[:type], chiller[:reference_capacity], false)
|
508
|
+
thisChillerCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
509
|
+
elsif chiller[:name].include?("ChillerElectricEIR_VSDCentrifugalWaterChiller")
|
510
|
+
thisChillerCost = vsd_chiller_cost(primaryCap: primaryCap)
|
511
|
+
end
|
512
|
+
# Flue cost for gas (absorption) chillers!
|
513
|
+
if chiller[:fuel] == 'NaturalGas'
|
514
|
+
# Calculate flue costs once for all chillets since flues combined by header when multiple chillers
|
515
|
+
# 6 inch diameter flue (#384)
|
516
|
+
materialHash = get_cost_info(mat: 'Venting', size: 6)
|
517
|
+
matCost, labCost = getCost('flue', materialHash, multiplier)
|
518
|
+
flueVentCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
519
|
+
|
520
|
+
#6 inch elbow fitting (#386)
|
521
|
+
materialHash = get_cost_info(mat: 'VentingElbow', size: 6)
|
522
|
+
matCost, labCost = getCost('flue elbow', materialHash, multiplier)
|
523
|
+
flueElbowCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
524
|
+
|
525
|
+
# 6 inch top (#392)
|
526
|
+
materialHash = get_cost_info(mat: 'VentingTop', size: 6)
|
527
|
+
matCost, labCost = getCost('flue top', materialHash, multiplier)
|
528
|
+
flueTopCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
529
|
+
|
530
|
+
# Gas line piping cost per ft (#1)
|
531
|
+
materialHash = get_cost_info(mat: 'GasLine', unit: 'L.F.')
|
532
|
+
matCost, labCost = getCost('fuel line', materialHash, multiplier)
|
533
|
+
fuelLineCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
534
|
+
|
535
|
+
# Gas line fitting connection per boiler (#2)
|
536
|
+
materialHash = get_cost_info(mat: 'GasLine', unit: 'each')
|
537
|
+
matCost, labCost = getCost('fuel line fitting connection', materialHash, multiplier)
|
538
|
+
fuelFittingCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
539
|
+
|
540
|
+
|
541
|
+
# Header cost only non-zero if there is a secondary/backup gas/oil boiler
|
542
|
+
headerCost = 0.0
|
543
|
+
else # Electric
|
544
|
+
flueVentCost = 0.0 ; flueElbowCost = 0.0 ; flueTopCost = 0.0 ; headerCost = 0.0
|
545
|
+
end
|
546
|
+
|
547
|
+
# Electric utility costs (i.e., power lines).
|
548
|
+
# Calculate utility cost components for primary chiller only since multiple chillers use common utilities
|
549
|
+
|
550
|
+
# elec 600V #14 wire /100 ft (#848)
|
551
|
+
materialHash = get_cost_info(mat: 'Wiring', size: 14)
|
552
|
+
matCost, labCost = getCost('electrical wire - 600V #14', materialHash, multiplier)
|
553
|
+
elecWireCost = matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0
|
554
|
+
|
555
|
+
# 1 inch metal conduit (#851)
|
556
|
+
materialHash = get_cost_info(mat: 'Conduit', unit: 'L.F.')
|
557
|
+
matCost, labCost = getCost('1 inch metal conduit', materialHash, multiplier)
|
558
|
+
metalConduitCost = matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0
|
559
|
+
|
560
|
+
if chiller[:fuel] == 'NaturalGas'
|
561
|
+
# Gas chillers require fuel line+valves+connectors and electrical conduit
|
562
|
+
utilCost += (fuelLineCost + metalConduitCost) * util_dist + fuelFittingCost + elecWireCost * util_dist / 100
|
563
|
+
|
564
|
+
else # Electric
|
565
|
+
# Electric chillers require only conduit
|
566
|
+
utilCost += metalConduitCost * util_dist + elecWireCost * util_dist / 100
|
567
|
+
end
|
568
|
+
|
569
|
+
elsif (chiller[:name].to_s.downcase =~ /secondary/i || numChillers > 1)
|
570
|
+
if chiller[:reference_capacity] <= 0.1
|
571
|
+
# Chiller cost is zero!
|
572
|
+
thisChillerCost = 0.0
|
573
|
+
numChillers -= 1
|
574
|
+
else
|
575
|
+
# A secondary chiller exists so use it for costing
|
576
|
+
if not chiller[:name].include?("ChillerElectricEIR_VSDCentrifugalWaterChiller")
|
577
|
+
matCost, labCost = getHVACCost(chiller[:name], chiller[:type], chiller[:reference_capacity], false)
|
578
|
+
thisChillerCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
579
|
+
elsif chiller[:name].include?("ChillerElectricEIR_VSDCentrifugalWaterChiller")
|
580
|
+
thisChillerCost = vsd_chiller_cost(primaryCap: primaryCap)
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
# Flue costs set to zero if secondary chiler since already calculated in primary (if gas absorption)
|
585
|
+
flueVentCost = 0.0; flueElbowCost = 0.0; flueTopCost = 0.0
|
586
|
+
|
587
|
+
# Check if need a flue header (i.e., both primary and secondary chillers are gas absorption)
|
588
|
+
if thisChillerCost > 0.0 && primaryFuel == 'NaturalGas' && chiller[:fuel] == 'NaturalGas'
|
589
|
+
# 6 inch diameter header (#384)
|
590
|
+
materialHash = get_cost_info(mat: 'Venting', size: 6)
|
591
|
+
matCost, labCost = getCost('flue header', materialHash, multiplier)
|
592
|
+
headerVentCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
593
|
+
|
594
|
+
#6 inch elbow fitting for header (#386)
|
595
|
+
materialHash = get_cost_info(mat: 'VentingElbow', size: 6)
|
596
|
+
matCost, labCost = getCost('flue header elbow', materialHash, multiplier)
|
597
|
+
headerElbowCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
598
|
+
|
599
|
+
# Assume a header length of 20 ft and an elbow fitting for each boiler connected to the header
|
600
|
+
headerCost = (headerVentCost * 20 + headerElbowCost) * numChillers
|
601
|
+
else
|
602
|
+
headerCost = 0.0
|
603
|
+
end
|
604
|
+
end
|
605
|
+
chillerCost += thisChillerCost
|
606
|
+
flueCost += flueVentCost * ht_roof + flueElbowCost + flueTopCost + headerCost
|
607
|
+
if numChillers > 1 && primaryFuel == 'NaturalGas'
|
608
|
+
# Adjust utility cost for extra fuel line fitting cost
|
609
|
+
utilCost += fuelFittingCost * (numChillers - 1)
|
610
|
+
end
|
611
|
+
if numChillers < 2
|
612
|
+
# Create a cost for a backup chiller by doubling cost of primary chiller
|
613
|
+
# 2023-04-25: Although backup chillers may be modeled we are no longer counting them.
|
614
|
+
#chillerCost *= 2.0
|
615
|
+
numChillers = 1
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
# Chiller pump costs
|
620
|
+
pumpCost = 0.0; pipingToPumpCost = 0.0; numPumps = 0; pumpName = ''; pumpSize = 0.0 ; pumpFlow = 0.0
|
621
|
+
plant_loop_info[:chillerpumps].each do |pump|
|
622
|
+
numPumps += 1
|
623
|
+
# Cost variable and constant volume pumps the same (the difference is in extra cost for VFD controller)
|
624
|
+
pumpSize = pump[:size]; pumpName = pump[:name]
|
625
|
+
pumpFlow += pump[:water_flow_m3_per_s].to_f
|
626
|
+
matCost, labCost = getHVACCost(pumpName, 'Pumps', pumpSize, false)
|
627
|
+
indpumpCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
628
|
+
if pump[:name] =~ /variable/i
|
629
|
+
# Cost the VFD controller for the variable pump costed above
|
630
|
+
matCost, labCost = getHVACCost(pumpName, 'VFD', pumpSize, false)
|
631
|
+
indpumpCost += matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0
|
632
|
+
end
|
633
|
+
if awhp_chiller
|
634
|
+
pumpCost += indpumpCost * 2
|
635
|
+
else
|
636
|
+
pumpCost += indpumpCost
|
637
|
+
end
|
638
|
+
end
|
639
|
+
if (numChillers > 1 && numPumps < 2)
|
640
|
+
# Add pump costing for additional chillers
|
641
|
+
pumpCost *= 2.0
|
642
|
+
numPumps = 2 # reset the number of pumps for piping costs below
|
643
|
+
numChillers = 2
|
644
|
+
end
|
645
|
+
# Double the pump costs to accomodate the costing of backup pumps for each chiller!
|
646
|
+
# No longer costing backup pump CK 2023-06-23
|
647
|
+
#pumpCost *= 2.0
|
648
|
+
|
649
|
+
# Chiller water piping cost: Add piping elbows, valves and insulation from the chiller(s)
|
650
|
+
# to the pumps(s) assuming a pipe diameter of 1” and a distance of 10 ft per pump
|
651
|
+
if numChillers > 0
|
652
|
+
# 1 inch Steel pipe
|
653
|
+
matCost, labCost = getHVACCost('1 inch steel pipe', 'SteelPipe', 1)
|
654
|
+
pipingToPumpCost = 10.0 * numPumps * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
655
|
+
|
656
|
+
# 1 inch Steel pipe insulation
|
657
|
+
matCost, labCost = getHVACCost('1 inch pipe insulation', 'PipeInsulation', 1)
|
658
|
+
pipingToPumpCost += 10.0 * numPumps * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
659
|
+
|
660
|
+
# 1 inch Steel pipe elbow
|
661
|
+
matCost, labCost = getHVACCost('1 inch steel pipe elbow', 'SteelPipeElbow', 1)
|
662
|
+
pipingToPumpCost += 2.0 * numPumps * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
663
|
+
|
664
|
+
# 1 inch gate valves
|
665
|
+
matCost, labCost = getHVACCost('1 inch gate valves', 'ValvesGate', 1)
|
666
|
+
pipingToPumpCost += 1.0 * numPumps * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
667
|
+
end
|
668
|
+
|
669
|
+
if numChillers > 0
|
670
|
+
# Double pump piping cost to account for second chiller
|
671
|
+
pipingToPumpCost *= 2 if awhp_chiller
|
672
|
+
pipingToPumpCost *= numChillers
|
673
|
+
|
674
|
+
hdrDistributionCost = getHeaderPipingDistributionCost(numAGFlrs, mechRmInBsmt, regional_material, regional_installation, reg_mat_elec, reg_lab_elec,
|
675
|
+
pumpFlow, horz_dist, nom_flr_hght)
|
676
|
+
else
|
677
|
+
pipingToPumpCost = 0
|
678
|
+
hdrDistributionCost = 0
|
679
|
+
end
|
680
|
+
|
681
|
+
totalCost = chillerCost + flueCost + utilCost + pumpCost + pipingToPumpCost + hdrDistributionCost
|
682
|
+
|
683
|
+
@costing_report['heating_and_cooling']['plant_equipment'] << {
|
684
|
+
'type' => 'chillers',
|
685
|
+
'nom_flr2flr_hght_ft' => nom_flr_hght.round(1),
|
686
|
+
'ht_roof_ft' => ht_roof.round(1),
|
687
|
+
'longest_distance_to_ext_ft' => horz_dist.round(1),
|
688
|
+
'wiring_and_gas_connections_distance_ft' => util_dist.round(1),
|
689
|
+
'equipment_cost' => chillerCost.round(0),
|
690
|
+
'flue_cost' => flueCost.round(0),
|
691
|
+
'wiring_and_gas_connections_cost' => utilCost.round(0),
|
692
|
+
'pump_cost' => pumpCost.round(0),
|
693
|
+
'piping_to_pump_cost' => pipingToPumpCost.round(0),
|
694
|
+
'header_distribution_cost' => hdrDistributionCost.round(0),
|
695
|
+
'total_cost' => totalCost.round(0)
|
696
|
+
}
|
697
|
+
|
698
|
+
puts "\nHVAC Chiller costing data successfully generated. Total chiller costs: $#{totalCost.round(0)}"
|
699
|
+
|
700
|
+
return totalCost
|
701
|
+
end
|
702
|
+
|
703
|
+
# ----------------------------------------------------------------------------------------------
|
704
|
+
# Cooling tower (i.e., chiller condensor loop cooling) costing
|
705
|
+
# ----------------------------------------------------------------------------------------------
|
706
|
+
def coolingtower_costing(model, prototype_creator)
|
707
|
+
|
708
|
+
totalCost = 0.0
|
709
|
+
|
710
|
+
# Get regional cost factors for this province and city
|
711
|
+
materials_hvac = @costing_database["raw"]["materials_hvac"]
|
712
|
+
hvac_material = materials_hvac.select {|data|
|
713
|
+
data['Material'].to_s == "GasBoilers"}.first # Get any row from spreadsheet in case of region error
|
714
|
+
regional_material, regional_installation =
|
715
|
+
get_regional_cost_factors(@costing_report['province_state'], @costing_report['city'], hvac_material)
|
716
|
+
|
717
|
+
# Get regional electric cost factors for this province and city
|
718
|
+
hvac_material = materials_hvac.select {|data|
|
719
|
+
data['Material'].to_s.upcase == "BOX" && data['Size'].to_i == 1}.first
|
720
|
+
reg_mat_elec, reg_lab_elec =
|
721
|
+
get_regional_cost_factors(@costing_report['province_state'], @costing_report['city'], hvac_material)
|
722
|
+
|
723
|
+
# Store some geometry data for use below...
|
724
|
+
util_dist, ht_roof, nom_flr_hght, horz_dist, numAGFlrs, mechRmInBsmt = getGeometryData(model, prototype_creator)
|
725
|
+
|
726
|
+
template_type = prototype_creator.template
|
727
|
+
|
728
|
+
cltowerCost = 0.0
|
729
|
+
thisClTowerCost = 0.0
|
730
|
+
utilCost = 0.0
|
731
|
+
plant_loop_info = {}
|
732
|
+
plant_loop_info[:coolingtowers] = []
|
733
|
+
plant_loop_info[:coolingtowerpumps] = []
|
734
|
+
plant_loop_info[:groundloops] = []
|
735
|
+
cltowertype = 'cooling_towers'
|
736
|
+
|
737
|
+
# Iterate through the plant loops to get cooling tower & pump data...
|
738
|
+
model.getPlantLoops.each do |plant_loop|
|
739
|
+
next unless (plant_loop.name.get.to_s =~ /Condenser Water Loop/i) || (plant_loop.name.get.to_s =~ /Condenser PlantLoop GLHX/i)
|
740
|
+
plant_loop.supplyComponents.each do |supply_comp|
|
741
|
+
if supply_comp.to_CoolingTowerSingleSpeed.is_initialized
|
742
|
+
cltower = supply_comp.to_CoolingTowerSingleSpeed.get
|
743
|
+
cltower_info = {}
|
744
|
+
plant_loop_info[:coolingtowers] << cltower_info
|
745
|
+
cltower_info[:name] = cltower.name.get
|
746
|
+
cltower_info[:type] = 'ClgTwr' # Material lookup name
|
747
|
+
cltower_info[:fanPoweratDesignAirFlowRate] = cltower.fanPoweratDesignAirFlowRate.to_f / 1000 # kW
|
748
|
+
cltower_info[:capacity] = model.sqlFile().get().execAndReturnFirstDouble("SELECT Value FROM " +
|
749
|
+
"TabularDataWithStrings WHERE ReportName='EquipmentSummary' AND ReportForString='Entire Facility' AND " +
|
750
|
+
"TableName='Central Plant' AND ColumnName='Nominal Capacity' AND " +
|
751
|
+
"RowName='#{cltower_info[:name].upcase}' ").to_f / 1000 # kW
|
752
|
+
elsif supply_comp.to_PumpConstantSpeed.is_initialized
|
753
|
+
csPump = supply_comp.to_PumpConstantSpeed.get
|
754
|
+
csPump_info = {}
|
755
|
+
plant_loop_info[:coolingtowerpumps] << csPump_info
|
756
|
+
csPump_info[:name] = csPump.name.get
|
757
|
+
if csPump.isRatedPowerConsumptionAutosized.to_bool
|
758
|
+
csPumpSize = csPump.autosizedRatedPowerConsumption.to_f
|
759
|
+
else
|
760
|
+
csPumpSize = csPump.ratedPowerConsumption.to_f
|
761
|
+
end
|
762
|
+
csPump_info[:size] = csPumpSize.to_f # Watts
|
763
|
+
elsif supply_comp.to_PumpVariableSpeed.is_initialized
|
764
|
+
vsPump = supply_comp.to_PumpVariableSpeed.get
|
765
|
+
vsPump_info = {}
|
766
|
+
plant_loop_info[:coolingtowerpumps] << vsPump_info
|
767
|
+
vsPump_info[:name] = vsPump.name.get
|
768
|
+
if vsPump.isRatedPowerConsumptionAutosized.to_bool
|
769
|
+
vsPumpSize = vsPump.autosizedRatedPowerConsumption.to_f
|
770
|
+
else
|
771
|
+
vsPumpSize = vsPump.ratedPowerConsumption.to_f
|
772
|
+
end
|
773
|
+
vsPump_info[:size] = vsPumpSize.to_f # Watts
|
774
|
+
elsif supply_comp.to_DistrictHeating.is_initialized
|
775
|
+
groundLoop = supply_comp.to_DistrictHeating.get
|
776
|
+
groundLoop_info = {}
|
777
|
+
groundLoop_info[:name] = groundLoop.name.to_s
|
778
|
+
if groundLoop.isNominalCapacityAutosized.to_bool
|
779
|
+
groundLoop_info[:nominal_capacity] = groundLoop.autosizedNominalCapacity.to_f/1000.0
|
780
|
+
else
|
781
|
+
groundLoop_info[:nominal_capacity] = groundLoop.nominalCapacity.to_f/1000.0
|
782
|
+
end
|
783
|
+
# Get flow rate to ground loop
|
784
|
+
if plant_loop.isMaximumLoopFlowRateAutosized.to_bool
|
785
|
+
groundLoop_info[:plant_loop_flow_rate_m3ps] = plant_loop.autosizedMaximumLoopFlowRate.to_f
|
786
|
+
else
|
787
|
+
groundLoop_info[:plant_loop_flow_rate_m3ps] = plant_loop.maximumLoopFlowRate.to_f
|
788
|
+
end
|
789
|
+
plant_loop_info[:groundloops] << groundLoop_info
|
790
|
+
elsif supply_comp.to_DistrictCooling.is_initialized
|
791
|
+
groundLoop = supply_comp.to_DistrictCooling.get
|
792
|
+
groundLoop_info = {}
|
793
|
+
groundLoop_info[:name] = groundLoop.name.to_s
|
794
|
+
if groundLoop.isNominalCapacityAutosized.to_bool
|
795
|
+
groundLoop_info[:nominal_capacity] = groundLoop.autosizedNominalCapacity.to_f/1000.0
|
796
|
+
else
|
797
|
+
groundLoop_info[:nominal_capacity] = groundLoop.nominalCapacity.to_f/1000.0
|
798
|
+
end
|
799
|
+
# Get flow rate to ground loop
|
800
|
+
if plant_loop.isMaximumLoopFlowRateAutosized.to_bool
|
801
|
+
groundLoop_info[:plant_loop_flow_rate_m3ps] = plant_loop.autosizedMaximumLoopFlowRate.to_f
|
802
|
+
else
|
803
|
+
groundLoop_info[:plant_loop_flow_rate_m3ps] = plant_loop.maximumLoopFlowRate.to_f
|
804
|
+
end
|
805
|
+
plant_loop_info[:groundloops] << groundLoop_info
|
806
|
+
end
|
807
|
+
end
|
808
|
+
end
|
809
|
+
|
810
|
+
# Get costs associated with each cooling tower
|
811
|
+
numTowers = 0 ; multiplier = 1.0
|
812
|
+
|
813
|
+
plant_loop_info[:coolingtowers].each do |cltower|
|
814
|
+
# Get cooling tower cost based on capacity
|
815
|
+
numTowers += 1
|
816
|
+
if numTowers == 1
|
817
|
+
matCost, labCost = getHVACCost(cltower[:name], cltower[:type], cltower[:capacity], false)
|
818
|
+
thisClTowerCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
819
|
+
else # Multiple cooling towers
|
820
|
+
if cltower[:capacity] <= 0.1
|
821
|
+
# Cooling tower cost is zero!
|
822
|
+
thisClTowerCost = 0.0
|
823
|
+
else
|
824
|
+
# A second cooling tower exists so use it for costing
|
825
|
+
matCost, labCost = getHVACCost(cltower[:name], cltower[:type], cltower[:capacity], false)
|
826
|
+
thisClTowerCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
827
|
+
end
|
828
|
+
end
|
829
|
+
cltowerCost += thisClTowerCost
|
830
|
+
|
831
|
+
# Electric utility costs (i.e., power lines) for cooling tower(s).
|
832
|
+
|
833
|
+
# elec 600V #14 wire /100 ft (#848)
|
834
|
+
materialHash = get_cost_info(mat: 'Wiring', size: 14)
|
835
|
+
matCost, labCost = getCost('electrical wire - 600V #14', materialHash, multiplier)
|
836
|
+
elecWireCost = matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0
|
837
|
+
|
838
|
+
# 1 inch metal conduit (#851)
|
839
|
+
materialHash = get_cost_info(mat: 'Conduit', unit: 'L.F.')
|
840
|
+
matCost, labCost = getCost('1 inch metal conduit', materialHash, multiplier)
|
841
|
+
metalConduitCost = matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0
|
842
|
+
|
843
|
+
utilCost += metalConduitCost * (ht_roof + 20) + elecWireCost * (ht_roof + 20) / 100
|
844
|
+
end
|
845
|
+
|
846
|
+
# Cooling Tower (condensor) pump costs
|
847
|
+
pumpCost = 0.0; pipingCost = 0.0; numPumps = 0; pumpName = ''; pumpSize = 0.0
|
848
|
+
plant_loop_info[:coolingtowerpumps].each do |pump|
|
849
|
+
numPumps += 1
|
850
|
+
# Cost variable and constant volume pumps the same (VFD controller added if variable)
|
851
|
+
pumpSize = pump[:size]; pumpName = pump[:name]
|
852
|
+
matCost, labCost = getHVACCost(pumpName, 'Pumps', pumpSize, false)
|
853
|
+
pumpCost += matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
|
854
|
+
if pump[:name] =~ /variable/i
|
855
|
+
# Cost the VFD controller for the variable pump costed above
|
856
|
+
pumpSize = pump[:size]; pumpName = pump[:name]
|
857
|
+
matCost, labCost = getHVACCost(pumpName, 'VFD', pumpSize, false)
|
858
|
+
pumpCost += matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0
|
859
|
+
end
|
860
|
+
end
|
861
|
+
if numTowers > 1 && numPumps < 2
|
862
|
+
# Add pump costing for the backup pump.
|
863
|
+
# 2023-04-25: Not including backup tower or pump costs
|
864
|
+
# pumpCost *= 2.0
|
865
|
+
end
|
866
|
+
# Double the pump costs to accomodate the costing of a backup pump(s)!
|
867
|
+
# 2023-04-25 No longer including backup pumps
|
868
|
+
#pumpCost *= 2.0
|
869
|
+
#numPumps = 2 # reset the number of pumps for piping costs below
|
870
|
+
|
871
|
+
|
872
|
+
# Chiller water piping cost: Add piping elbows, valves and insulation from the chiller(s)
|
873
|
+
# to the pumps(s) assuming a pipe diameter of 1” and a distance of 10 ft per pump
|
874
|
+
if numTowers > 0
|
875
|
+
# 4 inch Steel pipe (vertical + horizontal)
|
876
|
+
matCost, labCost = getHVACCost('4 inch steel pipe', 'SteelPipe', 4)
|
877
|
+
pipingCost += (ht_roof * 2 + 10 * numPumps) * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
878
|
+
|
879
|
+
# 4 inch Steel pipe insulation (vertical + horizontal)
|
880
|
+
matCost, labCost = getHVACCost('1 inch pipe insulation', 'PipeInsulation', 4)
|
881
|
+
pipingCost += (ht_roof * 2 + 10 * numPumps) * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
882
|
+
|
883
|
+
# 4 inch Steel pipe elbow
|
884
|
+
matCost, labCost = getHVACCost('4 inch steel pipe tee', 'SteelPipeTee', 4)
|
885
|
+
pipingCost += 1.0 * numPumps * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
886
|
+
|
887
|
+
# 4 inch valves
|
888
|
+
matCost, labCost = getHVACCost('4 inch BFly valves', 'ValvesBFly', 4)
|
889
|
+
pipingCost += 1.0 * numPumps * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
890
|
+
end
|
891
|
+
|
892
|
+
# Note: No extra costs for piping for backup condenser pump or multiple cooling towers.
|
893
|
+
|
894
|
+
# Calculate GSHP ground loop cost and interior piping
|
895
|
+
unless plant_loop_info[:groundloops].empty?
|
896
|
+
# GSHP ground loop cost
|
897
|
+
# Not applying localization because costs are currently national estimates only (2023-06-19)
|
898
|
+
cltowertype = 'ground_loop'
|
899
|
+
largest_loop = plant_loop_info[:groundloops].max_by { |groundloop| groundloop[:nominal_capacity] }
|
900
|
+
nominal_capacity = largest_loop[:nominal_capacity].to_f
|
901
|
+
matCost, labCost = getHVACCost('GSHP ground loop', 'gshp_ground_loop', '')
|
902
|
+
cltowerCost = matCost * nominal_capacity
|
903
|
+
|
904
|
+
# GSHP piping from building to bore field cost
|
905
|
+
# Not applying localization because costs are currently national estimates only (2023-06-19)
|
906
|
+
gshp_dist = 50
|
907
|
+
loop_flow = largest_loop[:plant_loop_flow_rate_m3ps]
|
908
|
+
pipe_dia_mm = d = Math.sqrt(1.273*loop_flow/2.0)*1000
|
909
|
+
matCost, labCost = getHVACCost('GSHP outdoor piping cost', 'gshp_buried_pipe', pipe_dia_mm, false)
|
910
|
+
pipingCost = matCost * gshp_dist
|
911
|
+
|
912
|
+
# Interior piping cost
|
913
|
+
# Get mechanical room lacotion (assume this is where the GSHP is)
|
914
|
+
mech_room, cond_spaces = prototype_creator.find_mech_room(model)
|
915
|
+
mech_centroid = mech_room["space_centroid"]
|
916
|
+
# Determine the length to the largest exterior wall
|
917
|
+
ground_spaces = []
|
918
|
+
pipe_dists = []
|
919
|
+
# Determine the exterior ground walls touching the ground
|
920
|
+
# Get the spaces
|
921
|
+
model.getSpaces.sort.each do |space|
|
922
|
+
ground_surf = false
|
923
|
+
# Get the surfaces for the space and determine if any are contacting the ground. If one is add it to the arroy
|
924
|
+
# of spaces
|
925
|
+
space.surfaces.sort.each do |surface|
|
926
|
+
if surface.isGroundSurface
|
927
|
+
ground_surf = true
|
928
|
+
end
|
929
|
+
end
|
930
|
+
ground_spaces << space if ground_surf == true
|
931
|
+
end
|
932
|
+
# Go through all of the spaces contacting the ground
|
933
|
+
ground_spaces.sort.each do |ground_space|
|
934
|
+
# Go through all of the surfaces for the space and determine which are exterior or foundation walls
|
935
|
+
ext_walls = ground_space.surfaces.select{ |surf| surf.surfaceType == 'Wall' && (surf.outsideBoundaryCondition == 'Outdoors' || surf.outsideBoundaryCondition == 'Foundation')}
|
936
|
+
# Get the largest exterior wall and the distance to its centroid to the mech room centroid
|
937
|
+
unless ext_walls.empty?
|
938
|
+
ext_wall = ext_walls.max_by { |ext_wall| ext_wall.grossArea.to_f }
|
939
|
+
pipe_dists << {
|
940
|
+
wall: ext_wall,
|
941
|
+
pipe_dist: ((ext_wall.centroid.x.to_f + ground_space.xOrigin.to_f - mech_centroid[0].to_f).abs + (ext_wall.centroid.y.to_f + ground_space.yOrigin.to_f - mech_centroid[1].to_f).abs + (ext_wall.centroid.z.to_f + ground_space.zOrigin.to_f - mech_centroid[2].to_f).abs)
|
942
|
+
}
|
943
|
+
end
|
944
|
+
end
|
945
|
+
# Find the shortest distance to the 3 largest walls and pick the shortest one
|
946
|
+
largest_walls = pipe_dists.max_by(3) { |wall| wall[:wall].grossArea.to_f }
|
947
|
+
pipe_dist = largest_walls.min_by { |wall| wall[:pipe_dist].to_f }
|
948
|
+
pipe_dist_ft = (OpenStudio.convert(pipe_dist[:pipe_dist], 'm', 'ft').get)
|
949
|
+
pipe_dia_mm = d = Math.sqrt(1.273*loop_flow/2.0)*1000
|
950
|
+
pipe_dia_inch = (OpenStudio.convert(pipe_dia_mm/1000, 'm', 'ft').get)*12.0
|
951
|
+
pipe_dia_inch = 8.0 if pipe_dia_inch >= 8.0
|
952
|
+
|
953
|
+
# Include localization foctors in interior piping and fixtures
|
954
|
+
# Cost the interior pipe
|
955
|
+
matCost, labCost = getHVACCost('GSHP indoor piping cost', 'SteelPipe', pipe_dia_inch, false)
|
956
|
+
pipingCost += (matCost*regional_material + labCost*regional_installation)*pipe_dist_ft*2.0/100.0
|
957
|
+
|
958
|
+
# Cost the pipe insulation
|
959
|
+
pipe_dia_inch = 4.0 if pipe_dia_inch > 4.0
|
960
|
+
matCost, labCost = getHVACCost('GSHP indoor pipe insulation', 'PipeInsulation', pipe_dia_inch, false)
|
961
|
+
pipingCost += (matCost*regional_material + labCost*regional_installation)*pipe_dist_ft*2.0/100.0
|
962
|
+
|
963
|
+
# Cost 1 valve
|
964
|
+
matCost, labCost = getHVACCost('GSHP indoor pipe valve', 'ValvesBig', 4.0)
|
965
|
+
pipingCost += (matCost*regional_material + labCost*regional_installation)/100.0
|
966
|
+
|
967
|
+
# Cost 2 pipe tees
|
968
|
+
matCost, labCost = getHVACCost('GSHP indoor pipe tees', 'SteelPipeTee', 4.0)
|
969
|
+
pipingCost += (matCost*regional_material + labCost*regional_installation)*2.0/100.0
|
970
|
+
|
971
|
+
# Cost 8 pipe tees
|
972
|
+
matCost, labCost = getHVACCost('GSHP indoor pipe elbows', 'SteelPipeElbow', 4.0)
|
973
|
+
pipingCost += (matCost*regional_material + labCost*regional_installation)*8.0/100.0
|
974
|
+
end
|
975
|
+
totalCost = cltowerCost + utilCost + pumpCost + pipingCost
|
976
|
+
|
977
|
+
@costing_report['heating_and_cooling']['plant_equipment'] << {
|
978
|
+
'type' => cltowertype,
|
979
|
+
'nom_flr2flr_hght_ft' => nom_flr_hght.round(1),
|
980
|
+
'ht_roof_ft' => ht_roof.round(1),
|
981
|
+
'longest_distance_to_ext_ft' => horz_dist.round(1),
|
982
|
+
'wiring_and_gas_connections_distance_ft' => util_dist.round(1),
|
983
|
+
'equipment_cost' => cltowerCost.round(0),
|
984
|
+
'wiring_and_gas_connections_cost' => utilCost.round(0),
|
985
|
+
'pump_cost' => pumpCost.round(0),
|
986
|
+
'piping_cost' => pipingCost.round(0),
|
987
|
+
'total_cost' => totalCost.round(0)
|
988
|
+
}
|
989
|
+
|
990
|
+
puts "\nHVAC Cooling Tower costing data successfully generated. Total cooling tower costs: $#{totalCost.round(0)}"
|
991
|
+
|
992
|
+
return totalCost
|
993
|
+
end
|
994
|
+
|
995
|
+
|
996
|
+
# This method determines how many pieces of equipment are required to satisfy the required size if 1 piece is not
|
997
|
+
# enough. It takes in:
|
998
|
+
# materialLookup(String): The name to search for in the 'Material' column of the materials_hvac sheet of the costing
|
999
|
+
# spreadsheet.
|
1000
|
+
# materialSize(Float): The size to search for in the 'Size' column of teh materials_hvac sheet of the costing
|
1001
|
+
# spreadsheet.
|
1002
|
+
# It returns:
|
1003
|
+
# multiplier(Float): Number of materialLookup with the largest size required to meet the materialSize.
|
1004
|
+
def get_HVAC_multiplier(materialLookup, materialSize)
|
1005
|
+
multiplier = 1.0
|
1006
|
+
materials_hvac = @costing_database['raw']['materials_hvac'].select {|data|
|
1007
|
+
data['Material'].to_s.upcase == materialLookup.to_s.upcase
|
1008
|
+
}
|
1009
|
+
if materials_hvac.nil?
|
1010
|
+
puts("Error: no hvac information available for equipment #{materialLookup}!")
|
1011
|
+
raise
|
1012
|
+
elsif materials_hvac.empty?
|
1013
|
+
puts("Error: no hvac information available for equipment #{materialLookup}!")
|
1014
|
+
raise
|
1015
|
+
end
|
1016
|
+
materials_hvac.length == 1 ? max_size = materials_hvac[0] : max_size = materials_hvac.max_by {|d| d['Size'].to_f}
|
1017
|
+
if max_size['Size'].to_f <= 0
|
1018
|
+
puts("Error: #{materialLookup} has a size of 0 or less. Please check that the correct costing_database.json file is being used or check the costing spreadsheet!")
|
1019
|
+
raise
|
1020
|
+
end
|
1021
|
+
mult = materialSize.to_f / (max_size['Size'].to_f)
|
1022
|
+
multiplier = (mult.to_i).to_f.round(0) + 1 # Use next largest integer for multiplier
|
1023
|
+
return multiplier.to_f
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
# This method provides the material and labour cost for a required piece of equimpment. It takes in:
|
1027
|
+
# name(String): The name of a piece of equipment. Is only used for error reporting and is not linked to anything
|
1028
|
+
# else.
|
1029
|
+
# materialLookup(String): The material type used to search hte 'Material' column of the materials_hvac sheet of the
|
1030
|
+
# costing spreadsheet.
|
1031
|
+
# materialSize(float): The size of the equipment in whichever units are required when searching the 'Size' column of
|
1032
|
+
# the costing spreadsheet.
|
1033
|
+
# exactMatch(true/false): A flag to indicate if the hvac equipment must match the size provided exactly or if the
|
1034
|
+
# size is a minimum equipment size.
|
1035
|
+
# It returns the material cost ond labor cost for the equipment including any multipliers.
|
1036
|
+
def getHVACCost(name, materialLookup, materialSize, exactMatch=true)
|
1037
|
+
eqCostInfo = getHVACDBInfo(name: name, materialLookup: materialLookup, materialSize: materialSize, exactMatch: exactMatch)
|
1038
|
+
return getCost(eqCostInfo[:name], eqCostInfo[:hvac_material], eqCostInfo[:multiplier])
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
# This method was originally part of getHVACCOST but was split out because in some cases the information from the
|
1042
|
+
# materials_hvac sheet of the costing spreadsheet was required but not tho cost.
|
1043
|
+
# The method takes in:
|
1044
|
+
# name(String): The name of a piece of equipment. Is only used for error reporting and is not linked to anything
|
1045
|
+
# else.
|
1046
|
+
# materialLookup(String): The material type used to search hte 'Material' column of the materials_hvac sheet of the
|
1047
|
+
# costing spreadsheet.
|
1048
|
+
# materialSize(float): The size of the equipment in whichever units are required when searching the 'Size' column of
|
1049
|
+
# the costing spreadsheet.
|
1050
|
+
# exactMatch(true/false): A flag to indicate if the hvac equipment must match the size provided exactly or if the
|
1051
|
+
# size is a minimum equipment size.
|
1052
|
+
#
|
1053
|
+
# The method returns a hash with the following composition:
|
1054
|
+
# {
|
1055
|
+
# name(string): Same as above.
|
1056
|
+
# hvac_material(hash): The costing spreadsheet information for the hvac equipment being searched for.
|
1057
|
+
# multiplier(float): Default is 1. Will be higher if exactMatch is false, and no materialLookup could be found with
|
1058
|
+
# a large enough materialSize in the costing spreadsheet. In this case, it is assumed that several
|
1059
|
+
# pieced of equipment defined by hvact_material are used to satisfy the required materialSize.
|
1060
|
+
# The multiplier defines the number of hvac_material required to meet the materialSize
|
1061
|
+
# }
|
1062
|
+
def getHVACDBInfo(name:, materialLookup:, materialSize:, exactMatch: true)
|
1063
|
+
multiplier = 1.0
|
1064
|
+
materials_hvac = @costing_database["raw"]["materials_hvac"]
|
1065
|
+
if materialSize == 'nil' || materialSize == '' || materialSize == nil
|
1066
|
+
# When materialSize is blank because there is only one row in the data sheet, the value is nil
|
1067
|
+
hvac_material = materials_hvac.select { |data| data['Material'].to_s.upcase == materialLookup.to_s.upcase }.first
|
1068
|
+
else
|
1069
|
+
if exactMatch
|
1070
|
+
hvac_material = materials_hvac.select {|data|
|
1071
|
+
data['Material'].to_s.upcase == materialLookup.to_s.upcase && data['Size'].to_f == materialSize
|
1072
|
+
}.first
|
1073
|
+
else
|
1074
|
+
hvac_material_info = materials_hvac.select {|data|
|
1075
|
+
data['Material'].to_s.upcase == materialLookup.to_s.upcase && data['Size'].to_f >= materialSize
|
1076
|
+
}
|
1077
|
+
if hvac_material_info.empty?
|
1078
|
+
hvac_material = nil
|
1079
|
+
elsif hvac_material_info.size == 1
|
1080
|
+
hvac_material = hvac_material_info[0]
|
1081
|
+
else
|
1082
|
+
hvac_material = hvac_material_info.min_by{|data| data['Size'].to_f}
|
1083
|
+
end
|
1084
|
+
end
|
1085
|
+
end
|
1086
|
+
if hvac_material.nil?
|
1087
|
+
if exactMatch
|
1088
|
+
puts "HVAC material error! Could not find #{name} in materials_hvac!"
|
1089
|
+
raise
|
1090
|
+
else
|
1091
|
+
# There is no exact match in the costing spreadsheet so redo search for next largest size
|
1092
|
+
hvac_material = materials_hvac.select {|data|
|
1093
|
+
data['Material'].to_s.upcase == materialLookup.to_s.upcase && data['Size'].to_f >= materialSize.to_f
|
1094
|
+
}.min_by{|mat_info| mat_info['Size'].to_f}
|
1095
|
+
if hvac_material.nil?
|
1096
|
+
# The nominal capacity is greater than the maximum value in the API data for this boiler!
|
1097
|
+
# Lookup cost for a capacity divided by the multiple of req'd size/max size.
|
1098
|
+
multiplier = get_HVAC_multiplier( materialLookup, materialSize )
|
1099
|
+
hvac_materials = materials_hvac.select {|data|
|
1100
|
+
data['Material'].to_s.upcase == materialLookup.to_s.upcase && data['Size'].to_f >= materialSize.to_f / multiplier.to_f
|
1101
|
+
}
|
1102
|
+
if hvac_materials.size == 0
|
1103
|
+
puts "HVAC material error! Could not find next largest size for #{name} in #{materials_hvac}"
|
1104
|
+
raise
|
1105
|
+
elsif hvac_materials.size == 1
|
1106
|
+
hvac_material = hvac_materials[0]
|
1107
|
+
else
|
1108
|
+
hvac_material = hvac_materials.min_by{|data| data['Size'].to_f}
|
1109
|
+
end
|
1110
|
+
end
|
1111
|
+
end
|
1112
|
+
end
|
1113
|
+
# Create the return hash.
|
1114
|
+
costDBInfo = {
|
1115
|
+
name: name,
|
1116
|
+
hvac_material: hvac_material,
|
1117
|
+
multiplier: multiplier
|
1118
|
+
}
|
1119
|
+
return costDBInfo
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
|
1123
|
+
def getCost(materialType, materialHash, multiplier)
|
1124
|
+
material_cost = 0.0 ; labour_cost = 0.0
|
1125
|
+
costing_data = @costing_database['costs'].detect do |data|
|
1126
|
+
data['id'].to_s.upcase == materialHash['id'].to_s.upcase
|
1127
|
+
end
|
1128
|
+
if costing_data.nil?
|
1129
|
+
puts "HVAC #{materialType} with id #{materialHash['id']} not found in the costing database. Skipping."
|
1130
|
+
raise
|
1131
|
+
else
|
1132
|
+
# Get cost information from lookup.
|
1133
|
+
# Adjust for material and labour multiplier in costing spreadsheet 'materials_hvac' sheet 'material_mult' and
|
1134
|
+
# 'labour_mult' columns.
|
1135
|
+
(materialHash['material_mult'].nil?) || (materialHash['material_mult'].empty?) ? mat_mult = 1.0 : mat_mult = materialHash['material_mult'].to_f
|
1136
|
+
(materialHash['labour_mult'].nil?) || (materialHash['labour_mult'].empty?) ? lab_mult = 1.0 : lab_mult = materialHash['labour_mult'].to_f
|
1137
|
+
material_cost = costing_data['baseCosts']['materialOpCost'].to_f * multiplier * mat_mult
|
1138
|
+
labour_cost = costing_data['baseCosts']['laborOpCost'].to_f * multiplier * lab_mult
|
1139
|
+
end
|
1140
|
+
return material_cost, labour_cost
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
def getGeometryData(model, prototype_creator)
|
1144
|
+
num_of_above_ground_stories = model.getBuilding.standardsNumberOfAboveGroundStories.to_i
|
1145
|
+
space_mod = OpenstudioStandards::Space
|
1146
|
+
if model.building.get.nominalFloortoFloorHeight().empty?
|
1147
|
+
volume = model.building.get.airVolume()
|
1148
|
+
flrArea = 0.0
|
1149
|
+
if model.building.get.conditionedFloorArea.empty?
|
1150
|
+
model.getThermalZones.sort.each do |tz|
|
1151
|
+
tz.spaces.sort.each do |tz_space|
|
1152
|
+
flrArea += tz_space.floorArea.to_f if ( (space_mod.space_cooled?(tz_space)) || (space_mod.space_heated?(tz_space)) )
|
1153
|
+
end
|
1154
|
+
flrArea += tz.floorArea
|
1155
|
+
end
|
1156
|
+
else
|
1157
|
+
flrArea = model.building.get.conditionedFloorArea().get
|
1158
|
+
end
|
1159
|
+
nominal_flr2flr_height = 0.0
|
1160
|
+
nominal_flr2flr_height = volume / flrArea unless flrArea <= 0.01
|
1161
|
+
else
|
1162
|
+
nominal_flr2flr_height = model.building.get.nominalFloortoFloorHeight.get
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
# Location of mechanical room and utility distances for use below (space_centroid is an array
|
1166
|
+
# in mech_room hash containing the x,y and z coordinates of space centroid). Utility distance
|
1167
|
+
# uses the distance from the mech room centroid to the perimeter of the building.
|
1168
|
+
mech_room, cond_spaces = prototype_creator.find_mech_room(model)
|
1169
|
+
mech_room_story = nil
|
1170
|
+
target_cent = [mech_room['space_centroid'][0], mech_room['space_centroid'][1]]
|
1171
|
+
found = false
|
1172
|
+
model.getBuildingStorys.sort.each do |story|
|
1173
|
+
story.spaces.sort.each do |space|
|
1174
|
+
if space.nameString == mech_room['space_name']
|
1175
|
+
mech_room_story = story
|
1176
|
+
found = true
|
1177
|
+
break
|
1178
|
+
end
|
1179
|
+
end
|
1180
|
+
break if found
|
1181
|
+
end
|
1182
|
+
distance_info_hash = get_story_cent_to_edge( building_story: mech_room_story, prototype_creator: prototype_creator,
|
1183
|
+
target_cent: target_cent, full_length: false )
|
1184
|
+
horizontal_dist = distance_info_hash[:start_point][:line][:dist] # in metres
|
1185
|
+
|
1186
|
+
ht_roof = 0.0
|
1187
|
+
util_dist = 0.0
|
1188
|
+
mechRmInBsmt = false
|
1189
|
+
if mech_room['space_centroid'][2] < 0
|
1190
|
+
# Mechanical room is in the basement (z dimension is negative).
|
1191
|
+
mechRmInBsmt = true
|
1192
|
+
ht_roof = (num_of_above_ground_stories + 1) * nominal_flr2flr_height
|
1193
|
+
util_dist = nominal_flr2flr_height + horizontal_dist
|
1194
|
+
elsif mech_room['space_centroid'][2] == 0
|
1195
|
+
# Mech room on ground floor
|
1196
|
+
ht_roof = num_of_above_ground_stories * nominal_flr2flr_height
|
1197
|
+
util_dist = horizontal_dist
|
1198
|
+
else
|
1199
|
+
# Mech room on some other floor
|
1200
|
+
ht_roof = (num_of_above_ground_stories - (mech_room['space_centroid'][2]/nominal_flr2flr_height).round(0)) * nominal_flr2flr_height
|
1201
|
+
util_dist = ht_roof + horizontal_dist
|
1202
|
+
end
|
1203
|
+
|
1204
|
+
util_dist = OpenStudio.convert(util_dist,"m","ft").get
|
1205
|
+
nominal_flr2flr_height = OpenStudio.convert(nominal_flr2flr_height,"m","ft").get
|
1206
|
+
ht_roof = OpenStudio.convert(ht_roof,"m","ft").get
|
1207
|
+
horizontal_dist = OpenStudio.convert(horizontal_dist,"m","ft").get
|
1208
|
+
|
1209
|
+
return util_dist, ht_roof, nominal_flr2flr_height, horizontal_dist, num_of_above_ground_stories, mechRmInBsmt
|
1210
|
+
end
|
1211
|
+
|
1212
|
+
# --------------------------------------------------------------------------------------------------
|
1213
|
+
# This function gets all costs associated zonal heating and cooling systems
|
1214
|
+
# (i.e., zonal units, pumps, flues & utility costs)
|
1215
|
+
# --------------------------------------------------------------------------------------------------
|
1216
|
+
def zonalsys_costing(model, prototype_creator, mech_room, cond_spaces)
|
1217
|
+
|
1218
|
+
totalCost = 0.0
|
1219
|
+
|
1220
|
+
# Get regional cost factors for this province and city
|
1221
|
+
materials_hvac = @costing_database["raw"]["materials_hvac"]
|
1222
|
+
hvac_material = materials_hvac.select {|data|
|
1223
|
+
data['Material'].to_s == "GasBoilers"}.first # Get any row from spreadsheet in case of region error
|
1224
|
+
regional_material, regional_installation =
|
1225
|
+
get_regional_cost_factors(@costing_report['province_state'], @costing_report['city'], hvac_material)
|
1226
|
+
|
1227
|
+
# Get regional electric cost factors for this province and city
|
1228
|
+
hvac_material = materials_hvac.select {|data|
|
1229
|
+
data['Material'].to_s.upcase == "BOX" && data['Size'].to_i == 1}.first
|
1230
|
+
reg_mat_elec, reg_lab_elec =
|
1231
|
+
get_regional_cost_factors(@costing_report['province_state'], @costing_report['city'], hvac_material)
|
1232
|
+
|
1233
|
+
# Store some geometry data for use below...
|
1234
|
+
util_dist, ht_roof, nom_flr_hght, horz_dist, numAGFlrs, mechRmInBsmt = getGeometryData(model, prototype_creator)
|
1235
|
+
|
1236
|
+
template_type = prototype_creator.template
|
1237
|
+
|
1238
|
+
zone_loop_info = {}
|
1239
|
+
zone_loop_info[:zonesys] = []
|
1240
|
+
numZones = 0; floorNumber = 0
|
1241
|
+
vrfSystemFloors = {
|
1242
|
+
maxCeil: -9999999999999,
|
1243
|
+
lowCeil: 9999999999999,
|
1244
|
+
vrfFloors: []
|
1245
|
+
}
|
1246
|
+
|
1247
|
+
model.getThermalZones.sort.each do |zone|
|
1248
|
+
numZones += 1
|
1249
|
+
zone.equipment.each do |equipment|
|
1250
|
+
obj_type = equipment.iddObjectType.valueName.to_s
|
1251
|
+
if equipment.to_ZoneHVACComponent.is_initialized
|
1252
|
+
# This is a zonal HVAC component
|
1253
|
+
zone_info = {}
|
1254
|
+
zone_loop_info[:zonesys] << zone_info
|
1255
|
+
|
1256
|
+
# Get floor number from zone name string using regexp (Flr-N, where N is the storey number)
|
1257
|
+
zone_info[:zonename] = zone.name.get
|
1258
|
+
zone_info[:zonename].scan(/.*Flr-(\d+).*/) {|num| zone_info[:flrnum] = num[0].to_i}
|
1259
|
+
|
1260
|
+
unless zone.isConditioned.empty?
|
1261
|
+
zone_info[:is_conditioned] = zone.isConditioned.get
|
1262
|
+
else
|
1263
|
+
zone_info[:is_conditioned] = 'N/A'
|
1264
|
+
puts "Warning: zone.isConditioned is empty for #{zone.name.get}!"
|
1265
|
+
end
|
1266
|
+
|
1267
|
+
zone_info[:multiplier] = zone.multiplier
|
1268
|
+
|
1269
|
+
# Get the zone ceiling height value from the sql file...
|
1270
|
+
query = "SELECT CeilingHeight FROM Zones WHERE ZoneName='#{zone_info[:zonename].upcase}'"
|
1271
|
+
ceilHeight = model.sqlFile().get().execAndReturnFirstDouble(query)
|
1272
|
+
zone_info[:ceilingheight] = OpenStudio.convert(ceilHeight.to_f,"m","ft").get # feet
|
1273
|
+
|
1274
|
+
zone_info[:heatcost] = 0.0
|
1275
|
+
zone_info[:coolcost] = 0.0
|
1276
|
+
zone_info[:heatcoolcost] = 0.0
|
1277
|
+
zone_info[:pipingcost] = 0.0
|
1278
|
+
zone_info[:wiringcost] = 0.0
|
1279
|
+
zone_info[:multiplier] = zone.multiplier
|
1280
|
+
zone_info[:sysname] = equipment.name.get
|
1281
|
+
|
1282
|
+
# Get the heat capacity values from the sql file - ZoneSizes table...
|
1283
|
+
query = "SELECT UserDesLoad FROM ZoneSizes WHERE ZoneName='#{zone_info[:zonename].upcase}' AND LoadType='Heating'"
|
1284
|
+
heatCapVal = model.sqlFile().get().execAndReturnFirstDouble(query)
|
1285
|
+
zone_info[:heatcapacity] = heatCapVal.to_f / 1000.0 # Watts -> kW
|
1286
|
+
|
1287
|
+
component = equipment.to_ZoneHVACComponent.get
|
1288
|
+
if component.to_ZoneHVACPackagedTerminalAirConditioner.is_initialized
|
1289
|
+
heating_coil_name = component.to_ZoneHVACPackagedTerminalAirConditioner.get.heatingCoil.name.to_s
|
1290
|
+
query = "SELECT Value FROM TabularDataWithStrings WHERE ReportName='CoilSizingDetails' AND RowName='#{heating_coil_name.upcase}' AND ColumnName='Coil Final Gross Total Capacity'"
|
1291
|
+
zone_info[:heatcapacity] = model.sqlFile.get.execAndReturnFirstDouble(query).to_f/1000.0
|
1292
|
+
cooling_coil_name = component.to_ZoneHVACPackagedTerminalAirConditioner.get.coolingCoil.name.to_s
|
1293
|
+
query = "SELECT Value FROM TabularDataWithStrings WHERE ReportName='CoilSizingDetails' AND RowName='#{cooling_coil_name.upcase}' AND ColumnName='Coil Final Gross Total Capacity'"
|
1294
|
+
zone_info[:coolcapacity] = model.sqlFile.get.execAndReturnFirstDouble(query).to_f/1000.0
|
1295
|
+
elsif component.to_ZoneHVACFourPipeFanCoil.is_initialized # 2PFC & 4PFC
|
1296
|
+
heating_coil_name = component.to_ZoneHVACFourPipeFanCoil.get.heatingCoil.name.to_s
|
1297
|
+
query = "SELECT Value FROM TabularDataWithStrings WHERE ReportName='CoilSizingDetails' AND RowName='#{heating_coil_name.upcase}' AND ColumnName='Coil Final Gross Total Capacity'"
|
1298
|
+
zone_info[:heatcapacity] = model.sqlFile.get.execAndReturnFirstDouble(query).to_f/1000.0
|
1299
|
+
cooling_coil_name = component.to_ZoneHVACFourPipeFanCoil.get.coolingCoil.name.to_s
|
1300
|
+
query = "SELECT Value FROM TabularDataWithStrings WHERE ReportName='CoilSizingDetails' AND RowName='#{cooling_coil_name.upcase}' AND ColumnName='Coil Final Gross Total Capacity'"
|
1301
|
+
zone_info[:coolcapacity] = model.sqlFile.get.execAndReturnFirstDouble(query).to_f/1000.0
|
1302
|
+
elsif component.to_ZoneHVACPackagedTerminalHeatPump.is_initialized
|
1303
|
+
heating_coil_name = component.to_ZoneHVACPackagedTerminalHeatPump.get.heatingCoil.name.to_s
|
1304
|
+
query = "SELECT Value FROM TabularDataWithStrings WHERE ReportName='CoilSizingDetails' AND RowName='#{heating_coil_name.upcase}' AND ColumnName='Coil Final Gross Total Capacity'"
|
1305
|
+
zone_info[:heatcapacity] = model.sqlFile.get.execAndReturnFirstDouble(query).to_f/1000.0
|
1306
|
+
cooling_coil_name = component.to_ZoneHVACPackagedTerminalHeatPump.get.coolingCoil.name.to_s
|
1307
|
+
query = "SELECT Value FROM TabularDataWithStrings WHERE ReportName='CoilSizingDetails' AND RowName='#{cooling_coil_name.upcase}' AND ColumnName='Coil Final Gross Total Capacity'"
|
1308
|
+
zone_info[:coolcapacity] = model.sqlFile.get.execAndReturnFirstDouble(query).to_f/1000.0
|
1309
|
+
elsif component.to_ZoneHVACTerminalUnitVariableRefrigerantFlow.is_initialized
|
1310
|
+
# Use separate method to get zonal VRF system info
|
1311
|
+
zonalSys = component.to_ZoneHVACTerminalUnitVariableRefrigerantFlow.get
|
1312
|
+
vrfSystemFloors = getZonalVRFInfo(zone: zone, model: model, prototype_creator: prototype_creator, zonalSys: zonalSys, vrfSystemFloors: vrfSystemFloors, regMat: regional_material, regLab: regional_installation, numZones: numZones)
|
1313
|
+
# When done, go to the next piece of equipment. Will do VRF costing once all thermal zones are
|
1314
|
+
# investigated.
|
1315
|
+
next
|
1316
|
+
else
|
1317
|
+
cooling_coil_name = 'nil'
|
1318
|
+
end
|
1319
|
+
|
1320
|
+
unless (obj_type.to_s == 'OS_ZoneHVAC_FourPipeFanCoil') || (obj_type.to_s == 'OS_ZoneHVAC_PackagedTerminalHeatPump') || (obj_type.to_s == 'OS_ZoneHVAC_PackagedTerminalAirConditioner')
|
1321
|
+
# Get the cooling total capacity (sen+lat) value from the sql file - ComponentSizes table
|
1322
|
+
query = "SELECT Value FROM ComponentSizes WHERE CompName='#{cooling_coil_name.upcase}' AND Units='W'"
|
1323
|
+
coolCapVal = model.sqlFile().get().execAndReturnFirstDouble(query)
|
1324
|
+
zone_info[:coolcapacity] = coolCapVal.to_f / 1000.0 # Watts -> kW
|
1325
|
+
end
|
1326
|
+
|
1327
|
+
if (zone_info[:sysname] =~ /Baseboard Convective Water/i) or (zone_info[:sysname] =~ /BaseboardConvectiveWater/i)
|
1328
|
+
zone_info[:systype] = 'HW'
|
1329
|
+
# HW convector length based on 0.425 kW/foot
|
1330
|
+
if zone_info[:heatcapacity] > 0
|
1331
|
+
heatCapacity = zone_info[:heatcapacity] / zone.multiplier
|
1332
|
+
convLength = (heatCapacity / 0.425).round(0)
|
1333
|
+
# HW convector 1" copper core pipe cost
|
1334
|
+
matCost, labCost = getHVACCost(zone_info[:sysname], 'ConvectCopper', 1.25, true)
|
1335
|
+
convPipeCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * convLength
|
1336
|
+
# For each convector there will be a shut-off valve, 2 Tee connections and 2 elbows to
|
1337
|
+
# isolate the convector from the hot water loop distribution for servicing and balancing.
|
1338
|
+
# Hot water convectors are manufactured in maximum 8 ft lengths, therefore the number of
|
1339
|
+
# convectors per thermal zone is (rounded up to nearest integer):
|
1340
|
+
ratio = (convLength.to_f / 8.0).to_f
|
1341
|
+
numConvectors = (ratio - ratio.to_i) > 0.10 ? (ratio + 0.5).round(0) : ratio
|
1342
|
+
# Cost of valves:
|
1343
|
+
matCost, labCost = getHVACCost('1.25 inch gate valve', 'ValvesGate', 1.25, true)
|
1344
|
+
convValvesCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * numConvectors
|
1345
|
+
# Cost of tees:
|
1346
|
+
matCost, labCost = getHVACCost('1.25 inch copper tees', 'CopperPipeTee', 1.25, true)
|
1347
|
+
convTeesCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * 2 * numConvectors
|
1348
|
+
# Cost of elbows:
|
1349
|
+
matCost, labCost = getHVACCost('1.25 inch copper elbows', 'CopperPipeElbow', 1.25, true)
|
1350
|
+
convElbowsCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * 2 * numConvectors
|
1351
|
+
# Total convector cost for this zone (excluding distribution piping):
|
1352
|
+
convCost = (convPipeCost + convValvesCost + convTeesCost + convElbowsCost) * zone.multiplier
|
1353
|
+
zone_info[:heatcost] = convCost
|
1354
|
+
zone_info[:num_units] = numConvectors
|
1355
|
+
|
1356
|
+
# Single pipe supply and return
|
1357
|
+
perimPipingCost = getPerimDistPipingCost(zone, nom_flr_hght, regional_material, regional_installation)
|
1358
|
+
zone_info[:pipingcost] = perimPipingCost
|
1359
|
+
|
1360
|
+
totalCost += convCost + perimPipingCost
|
1361
|
+
end
|
1362
|
+
|
1363
|
+
elsif (zone_info[:sysname]=~ /Baseboard Convective Electric/i) or (zone_info[:sysname]=~ /BaseboardConvectiveElectric/i)
|
1364
|
+
zone_info[:systype] = 'BB'
|
1365
|
+
# BB number based on 0.935 kW/unit
|
1366
|
+
if zone_info[:heatcapacity] > 0
|
1367
|
+
heatCapacity = zone_info[:heatcapacity] / zone.multiplier
|
1368
|
+
ratio = (heatCapacity / 0.935).to_f
|
1369
|
+
numConvectors = (ratio - ratio.to_i) > 0.10 ? (ratio + 0.5).round(0) : ratio
|
1370
|
+
# BB electric convector unit cost (Just one in sheet)
|
1371
|
+
matCost, labCost = getHVACCost(zone_info[:sysname], 'ElectricBaseboard', 'nil', true)
|
1372
|
+
elecBBCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * numConvectors
|
1373
|
+
# For each baseboard there will be an electrical junction box
|
1374
|
+
matCost, labCost = getHVACCost('Electrical Outlet Box', 'Box', 1, true)
|
1375
|
+
elecBoxCost = (matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0) * numConvectors
|
1376
|
+
# Total electric basbeboard cost for this zone:
|
1377
|
+
elecConvCost = (elecBBCost + elecBoxCost) * zone.multiplier
|
1378
|
+
zone_info[:heatcost] = elecConvCost
|
1379
|
+
zone_info[:num_units] = numConvectors
|
1380
|
+
|
1381
|
+
perimWiringCost = getPerimDistWiringCost(zone, nom_flr_hght, reg_mat_elec, reg_lab_elec)
|
1382
|
+
zone_info[:wiringcost] = perimWiringCost
|
1383
|
+
|
1384
|
+
totalCost += elecConvCost + perimWiringCost
|
1385
|
+
end
|
1386
|
+
|
1387
|
+
elsif (zone_info[:sysname] =~ /PTAC/i) || (obj_type.to_s == 'OS_ZoneHVAC_PackagedTerminalAirConditioner')
|
1388
|
+
zone_info[:systype] = 'PTAC'
|
1389
|
+
# Heating cost of PTAC is handled by Baseboard Convective Electric Heater entry in Equipment list!
|
1390
|
+
# Cooling cost of PTAC ...
|
1391
|
+
if zone_info[:coolcapacity] > 0
|
1392
|
+
# DX cooling unit
|
1393
|
+
coolCapacity = zone_info[:coolcapacity] / zone.multiplier
|
1394
|
+
numUnits = get_HVAC_multiplier('PTAC', coolCapacity)
|
1395
|
+
# PTAC unit cost (Note that same numUnits multiple applied within getHVACCost())
|
1396
|
+
matCost, labCost = getHVACCost(zone_info[:sysname], 'PTAC', coolCapacity, false)
|
1397
|
+
thePTACUnitCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1398
|
+
# For each PTAC unit there will be an electrical junction box (wiring costed with distribution - not here)
|
1399
|
+
matCost, labCost = getHVACCost('Electrical Outlet Box', 'Box', 1, true)
|
1400
|
+
elecBoxCost = (matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0) * numUnits
|
1401
|
+
# Total PTAC cost for this zone (excluding distribution piping):
|
1402
|
+
thePTACCost = (thePTACUnitCost + elecBoxCost) * zone.multiplier
|
1403
|
+
zone_info[:coolcost] = thePTACCost
|
1404
|
+
zone_info[:num_units] = numUnits
|
1405
|
+
|
1406
|
+
perimWiringCost = getPerimDistWiringCost(zone, nom_flr_hght, reg_mat_elec, reg_lab_elec)
|
1407
|
+
zone_info[:wiringcost] = perimWiringCost
|
1408
|
+
|
1409
|
+
totalCost += thePTACCost + perimWiringCost
|
1410
|
+
end
|
1411
|
+
|
1412
|
+
elsif (zone_info[:sysname] =~ /PTHP/i) || (obj_type.to_s =~ /OS_ZoneHVAC_PackagedTerminalHeatPump/)
|
1413
|
+
zone_info[:systype] = 'HP'
|
1414
|
+
# Cost of PTAC based on heating capacity...
|
1415
|
+
if zone_info[:heatcapacity] > 0
|
1416
|
+
# DX heat pump unit
|
1417
|
+
capacityHPUnit = zone_info[:coolcapacity] > zone_info[:heatcapacity] ?
|
1418
|
+
zone_info[:coolcapacity] / zone.multiplier : zone_info[:heatcapacity] / zone.multiplier
|
1419
|
+
numUnits = get_HVAC_multiplier('ashp', capacityHPUnit)
|
1420
|
+
# HP unit cost (Note that same numUnits multiple applied within getHVACCost())
|
1421
|
+
matCost, labCost = getHVACCost(zone_info[:sysname], 'ashp', capacityHPUnit, false)
|
1422
|
+
theHPUnitCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1423
|
+
# For each HP unit there will be an electrical junction box (wiring costed with distribution - not here)
|
1424
|
+
matCost, labCost = getHVACCost('Electrical Outlet Box', 'Box', 1, true)
|
1425
|
+
elecBoxCost = (matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0) * numUnits
|
1426
|
+
# Total HP cost for this zone (excluding distribution piping):
|
1427
|
+
theHPCost = (theHPUnitCost + elecBoxCost) * zone.multiplier
|
1428
|
+
zone_info[:heatcoolcost] = theHPUnitCost
|
1429
|
+
zone_info[:num_units] = numUnits
|
1430
|
+
|
1431
|
+
perimWiringCost = getPerimDistWiringCost(zone, nom_flr_hght, reg_mat_elec, reg_lab_elec)
|
1432
|
+
zone_info[:wiringcost] = perimWiringCost
|
1433
|
+
|
1434
|
+
totalCost += theHPCost + perimWiringCost
|
1435
|
+
end
|
1436
|
+
|
1437
|
+
elsif zone_info[:sysname] =~ /2-pipe Fan Coil/i
|
1438
|
+
zone_info[:sfurnaceystype] = '2FC'
|
1439
|
+
if zone_info[:heatcapacity] > 0 || zone_info[:coolcapacity] > 0
|
1440
|
+
# Hot water heating and chilled water cooling type fan coil unit
|
1441
|
+
capacityFCUnit = zone_info[:coolcapacity] > zone_info[:heatcapacity] ?
|
1442
|
+
zone_info[:coolcapacity] / zone.multiplier : zone_info[:heatcapacity] / zone.multiplier
|
1443
|
+
numFCUnits = get_HVAC_multiplier('FanCoilHtgClgVent', capacityFCUnit)
|
1444
|
+
# 2PFC unit cost (Note that same numFCUnits multiple applied within getHVACCost())
|
1445
|
+
matCost, labCost = getHVACCost(zone_info[:sysname], 'FanCoilHtgClgVent', capacityFCUnit, false)
|
1446
|
+
fcUnitCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1447
|
+
# For each 2PFC unit there will be a shut-off valve, 2 Tee connections and 2 elbows to
|
1448
|
+
# isolate the convector from the hot water loop distribution for servicing and balancing.
|
1449
|
+
# Assumed unit piping is 1.25 inches in diameter.
|
1450
|
+
# Cost of valves:
|
1451
|
+
matCost, labCost = getHVACCost('1.25 inch gate valve', 'ValvesGate', 1.25, true)
|
1452
|
+
fcValvesCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * numFCUnits
|
1453
|
+
# Cost of tees:
|
1454
|
+
matCost, labCost = getHVACCost('1.25 inch copper tees', 'CopperPipeTee', 1.25, true)
|
1455
|
+
fcTeesCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * 2 * numFCUnits
|
1456
|
+
# Cost of elbows:
|
1457
|
+
matCost, labCost = getHVACCost('1.25 inch copper elbows', 'CopperPipeElbow', 1.25, true)
|
1458
|
+
fcElbowsCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * 2 * numFCUnits
|
1459
|
+
# For each 2PFC unit there will be an electrical junction box (wiring costed with distribution - not here)
|
1460
|
+
matCost, labCost = getHVACCost('Electrical Outlet Box', 'Box', 1, true)
|
1461
|
+
elecBoxCost = (matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0) * numFCUnits
|
1462
|
+
# Total 2PFC cost for this zone (excluding distribution piping):
|
1463
|
+
fcCost = (fcUnitCost + fcValvesCost + fcTeesCost + fcElbowsCost + elecBoxCost) * zone.multiplier
|
1464
|
+
zone_info[:heatcoolcost] = fcCost
|
1465
|
+
zone_info[:num_units] = numFCUnits
|
1466
|
+
|
1467
|
+
# Cost for one set supply/return piping
|
1468
|
+
perimPipingCost = getPerimDistPipingCost(zone, nom_flr_hght, regional_material, regional_installation)
|
1469
|
+
zone_info[:pipingcost] = perimPipingCost
|
1470
|
+
|
1471
|
+
perimWiringCost = getPerimDistWiringCost(zone, nom_flr_hght, reg_mat_elec, reg_lab_elec)
|
1472
|
+
zone_info[:wiringcost] = perimWiringCost
|
1473
|
+
|
1474
|
+
totalCost += fcCost + perimPipingCost + perimWiringCost
|
1475
|
+
end
|
1476
|
+
|
1477
|
+
elsif (zone_info[:sysname] =~ /4-pipe Fan Coil/i) || (obj_type =~ /OS_ZoneHVAC_FourPipeFanCoil/)
|
1478
|
+
zone_info[:systype] = '4FC'
|
1479
|
+
if (zone_info[:heatcapacity] > 0) || (zone_info[:coolcapacity] > 0)
|
1480
|
+
# Hot water heating and chilled water cooling type fan coil unit
|
1481
|
+
capacityFCUnit = zone_info[:coolcapacity] > zone_info[:heatcapacity] ?
|
1482
|
+
zone_info[:coolcapacity] / zone.multiplier : zone_info[:heatcapacity] / zone.multiplier
|
1483
|
+
numFCUnits = get_HVAC_multiplier('FanCoilHtgClgVent', capacityFCUnit)
|
1484
|
+
# 4PFC unit cost (Note that same numFCUnits multiple applied within getHVACCost())
|
1485
|
+
matCost, labCost = getHVACCost(zone_info[:sysname], 'FanCoilHtgClgVent', capacityFCUnit, false)
|
1486
|
+
fcUnitCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1487
|
+
# For each 4PFC unit there will be 2 shut-off valves, 4 Tee connections and 4 elbows to
|
1488
|
+
# isolate the convector from the hot water loop distribution for servicing and balancing.
|
1489
|
+
# Assumed unit piping is 1.25 inches in diameter.
|
1490
|
+
# Cost of valves:
|
1491
|
+
matCost, labCost = getHVACCost('1.25 inch gate valve', 'ValvesGate', 1.25, true)
|
1492
|
+
fcValvesCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * 2 * numFCUnits
|
1493
|
+
# Cost of tees:
|
1494
|
+
matCost, labCost = getHVACCost('1.25 inch copper tees', 'CopperPipeTee', 1.25, true)
|
1495
|
+
fcTeesCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * 4 * numFCUnits
|
1496
|
+
# Cost of elbows:
|
1497
|
+
matCost, labCost = getHVACCost('1.25 inch copper elbows', 'CopperPipeElbow', 1.25, true)
|
1498
|
+
fcElbowsCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * 4 * numFCUnits
|
1499
|
+
# For each 4PFC unit there will be an electrical junction box (wiring costed with distribution - not here)
|
1500
|
+
matCost, labCost = getHVACCost('Electrical Outlet Box', 'Box', 1, true)
|
1501
|
+
elecBoxCost = (matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0) * numFCUnits
|
1502
|
+
# Total 4PFC cost for this zone (excluding distribution piping):
|
1503
|
+
fcCost = (fcUnitCost + fcValvesCost + fcTeesCost + fcElbowsCost + elecBoxCost) * zone.multiplier
|
1504
|
+
zone_info[:heatcoolcost] = fcCost
|
1505
|
+
zone_info[:num_units] = numFCUnits
|
1506
|
+
|
1507
|
+
# Cost for two sets supply/return piping
|
1508
|
+
perimPipingCost = 2 * getPerimDistPipingCost(zone, nom_flr_hght, regional_material, regional_installation)
|
1509
|
+
zone_info[:pipingcost] = perimPipingCost
|
1510
|
+
|
1511
|
+
perimWiringCost = getPerimDistWiringCost(zone, nom_flr_hght, reg_mat_elec, reg_lab_elec)
|
1512
|
+
zone_info[:wiringcost] = perimWiringCost
|
1513
|
+
|
1514
|
+
totalCost += fcCost + perimPipingCost + perimWiringCost
|
1515
|
+
end
|
1516
|
+
|
1517
|
+
elsif zone_info[:sysname] =~ /Unit Heater/i || zone_info[:sysname] =~ /Unitary/i
|
1518
|
+
zone_info[:systype] = 'FUR'
|
1519
|
+
# Two types of unit heaters: electric and gas
|
1520
|
+
unitHeater = component.to_ZoneHVACUnitHeater.get
|
1521
|
+
heatCoil = unitHeater.heatingCoil
|
1522
|
+
if heatCoil.to_CoilHeatingGas.is_initialized # TODO: Need to test this!
|
1523
|
+
# The gas unit heaters are cabinet type with a burner and blower rather than the radiant type
|
1524
|
+
gasCoil = heatCoil.to_CoilHeatingGas.get
|
1525
|
+
if heatCoil.isNominalCapacityAutosized.to_bool
|
1526
|
+
zone_info[:heatcapacity] = gasCoil.autosizedNominalCapacity.to_f/1000.0
|
1527
|
+
else
|
1528
|
+
zone_info[:heatcapacity] = gasCoil.nominalCapacity.to_f/1000.0
|
1529
|
+
end
|
1530
|
+
if zone_info[:heatcapacity] > 0
|
1531
|
+
heatCapacity = zone_info[:heatcapacity] / zone.multiplier
|
1532
|
+
numUnits = get_HVAC_multiplier('gasheater', heatCapacity)
|
1533
|
+
# Unit cost (Note that same unit multiple applied within getHVACCost())
|
1534
|
+
matCost, labCost = getHVACCost(zone_info[:sysname], 'gasheater', heatCapacity, false)
|
1535
|
+
unitCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1536
|
+
# For each unit heater there will be an electrical junction box (wiring costed with distribution - not here)
|
1537
|
+
matCost, labCost = getHVACCost('Electrical Outlet Box', 'Box', 1, true)
|
1538
|
+
elecBoxCost = (matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0) * numUnits
|
1539
|
+
# It is assumed that the gas unit heater(s) are located in the centre of this zone. An 8 in exhaust duct
|
1540
|
+
# must be costed from the unit heater to the exterior via the roof. The centroid of this zone:
|
1541
|
+
if zone_info[:flrnum] > 1
|
1542
|
+
zoneCentroidToRoof_Ft = 10 + nom_flr_hght * zone_info[:flrnum]
|
1543
|
+
else
|
1544
|
+
zoneCentroidToRoof_Ft = 10
|
1545
|
+
end
|
1546
|
+
matCost, labCost = getHVACCost('Unit heater exhaust duct', 'Ductwork-S', 8, true)
|
1547
|
+
exhaustductCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * zoneCentroidToRoof_Ft
|
1548
|
+
zone_info[:heatcost] = (unitCost + elecBoxCost + exhaustductCost) * zone.multiplier
|
1549
|
+
zone_info[:num_units] = numUnits
|
1550
|
+
|
1551
|
+
# Cost of gas line header for zone. Header is located in the centre of this zone's floor
|
1552
|
+
mechRmInBsmt ? numFlrs = numAGFlrs + 1 : numFlrs = numAGFlrs
|
1553
|
+
hdrGasLen = numFlrs * nom_flr_hght
|
1554
|
+
# Gas line - first one in spreadsheet
|
1555
|
+
matCost, labCost = getHVACCost('Central header gas line', 'GasLine', '')
|
1556
|
+
hdrGasLineCost = hdrGasLen * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1557
|
+
|
1558
|
+
# Cost of gas line from header (centre of flr) to unit heater (centre of zone)
|
1559
|
+
#centreOfFloor = get_story_cent_to_edge( building_story: zone_info[:flrnum], prototype_creator: prototype_creator, target_cent: target_cent, full_length: false )
|
1560
|
+
#centreOfSpace = get_space_floor_centroid(space:)
|
1561
|
+
#gasLineLen = ABS(centreOfFloor - centreOfSpace)
|
1562
|
+
|
1563
|
+
# Cost of wiring header for zone
|
1564
|
+
|
1565
|
+
# Cost of wiring from header to unit heater
|
1566
|
+
|
1567
|
+
|
1568
|
+
totalCost += zone_info[:heatcost] + hdrGasLineCost
|
1569
|
+
end
|
1570
|
+
elsif heatCoil.to_CoilHeatingElectric.is_initialized # Electric Unit Heater
|
1571
|
+
elecCoil = heatCoil.to_CoilHeatingElectric.get
|
1572
|
+
if elecCoil.isNominalCapacityAutosized.to_bool
|
1573
|
+
zone_info[:heatcapacity] = elecCoil.autosizedNominalCapacity.to_f/1000.0
|
1574
|
+
else
|
1575
|
+
zone_info[:heatcapacity] = elecCoil.nominalCapacity.to_f/1000.0
|
1576
|
+
end
|
1577
|
+
if zone_info[:heatcapacity] > 0
|
1578
|
+
heatCapacity = zone_info[:heatcapacity] / zone.multiplier
|
1579
|
+
numUnits = get_HVAC_multiplier('elecheat', heatCapacity)
|
1580
|
+
# Unit cost (Note that same unit multiple applied within getHVACCost())
|
1581
|
+
matCost, labCost = getHVACCost(zone_info[:sysname], 'elecheat', heatCapacity, false)
|
1582
|
+
unitCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1583
|
+
# For each unit heater there will be an electrical junction box (wiring costed with distribution - not here)
|
1584
|
+
matCost, labCost = getHVACCost('Electrical Outlet Box', 'Box', 1, true)
|
1585
|
+
elecBoxCost = (matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0) * numUnits
|
1586
|
+
zone_info[:heatcost] = (unitCost + elecBoxCost) * zone.multiplier
|
1587
|
+
zone_info[:num_units] = numUnits
|
1588
|
+
# Cost of wiring to electric unit heater
|
1589
|
+
|
1590
|
+
|
1591
|
+
totalCost += zone_info[:heatcost]
|
1592
|
+
end
|
1593
|
+
elsif heatCoil.to_CoilHeatingWater.is_initialized # Hot water unit heater
|
1594
|
+
waterCoil = heatCoil.to_CoilHeatingWater.get
|
1595
|
+
if waterCoil.isRatedCapacityAutosized.to_bool
|
1596
|
+
zone_info[:heatcapacity] = waterCoil.autosizedRatedCapacity.to_f/1000.0
|
1597
|
+
else
|
1598
|
+
zone_info[:heatcapacity] = waterCoil.ratedCapacity.to_f/1000.0
|
1599
|
+
end
|
1600
|
+
if zone_info[:heatcapacity] > 0
|
1601
|
+
heatCapacity = zone_info[:heatcapacity] / zone.multiplier
|
1602
|
+
# Max capacity for hot water heater is 75300 Watts
|
1603
|
+
numUnits = get_HVAC_multiplier('hotwateruh', heatCapacity)
|
1604
|
+
matCost, labCost = getHVACCost(zone_info[:sysname], 'hotwateruh', heatCapacity, false)
|
1605
|
+
unitHtrCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1606
|
+
# For each unit heater there will be a shut-off valve, 2 Tee connections and 2 elbows to
|
1607
|
+
# isolate the convector from the hot water loop distribution for servicing and balancing.
|
1608
|
+
# Cost of valves:
|
1609
|
+
matCost, labCost = getHVACCost('1.25 inch gate valve', 'ValvesGate', 1.25, true)
|
1610
|
+
unitHtrValvesCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * numUnits
|
1611
|
+
# Cost of tees:
|
1612
|
+
matCost, labCost = getHVACCost('1.25 inch copper tees', 'CopperPipeTee', 1.25, true)
|
1613
|
+
unitHtrTeesCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * 2 * numUnits
|
1614
|
+
# Cost of elbows:
|
1615
|
+
matCost, labCost = getHVACCost('1.25 inch copper elbows', 'CopperPipeElbow', 1.25, true)
|
1616
|
+
unitHtrElbowsCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * 2 * numUnits
|
1617
|
+
# For each unit heater there will be an electrical junction box (wiring costed with distribution - not here)
|
1618
|
+
matCost, labCost = getHVACCost('Electrical Outlet Box', 'Box', 1, true)
|
1619
|
+
elecBoxCost = (matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0) * numUnits
|
1620
|
+
# Total convector cost for this zone (excluding distribution piping):
|
1621
|
+
unitHeaterCost = (unitHtrCost + unitHtrValvesCost + unitHtrTeesCost + unitHtrElbowsCost + elecBoxCost) * zone.multiplier
|
1622
|
+
zone_info[:heatcost] = unitHeaterCost
|
1623
|
+
zone_info[:num_units] = numUnits
|
1624
|
+
# Cost of distribution piping from header to unit heater
|
1625
|
+
|
1626
|
+
|
1627
|
+
totalCost += unitHeaterCost
|
1628
|
+
end
|
1629
|
+
end
|
1630
|
+
elsif zone_info[:sysname] =~ /WindowAC/i
|
1631
|
+
zone_info[:systype] = 'WinAC'
|
1632
|
+
# Cooling cost of WindowAC ...
|
1633
|
+
if cooling_coil_name == 'nil'
|
1634
|
+
# The cooling coil name doesn't exist so must use a different method to determine cooling
|
1635
|
+
# capacity for window AC units!
|
1636
|
+
query = "SELECT Value FROM ComponentSizes WHERE CompName='WindowAC' AND Units='W'"
|
1637
|
+
coolCapVal = model.sqlFile().get().execAndReturnFirstDouble(query)
|
1638
|
+
zone_info[:coolcapacity] = coolCapVal.to_f / 1000.0 # Watts -> kW
|
1639
|
+
end
|
1640
|
+
if zone_info[:coolcapacity] > 0
|
1641
|
+
# DX cooling unit
|
1642
|
+
coolCapacity = zone_info[:coolcapacity] / zone.multiplier
|
1643
|
+
numUnits = get_HVAC_multiplier('WINAC', coolCapacity)
|
1644
|
+
# Window AC unit cost (Note that same numUnits multiple applied within getHVACCost())
|
1645
|
+
matCost, labCost = getHVACCost(zone_info[:sysname], 'WINAC', coolCapacity, false)
|
1646
|
+
unitWinACCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1647
|
+
# For each WinAC unit there will be an electrical junction box (wiring costed with distribution - not here)
|
1648
|
+
matCost, labCost = getHVACCost('Electrical Outlet Box', 'Box', 1, true)
|
1649
|
+
elecBoxCost = (matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0) * numUnits
|
1650
|
+
# Total WinAC cost for this zone:
|
1651
|
+
theWinACCost = (unitWinACCost + elecBoxCost) * zone.multiplier
|
1652
|
+
zone_info[:coolcost] = theWinACCost
|
1653
|
+
zone_info[:num_units] = numUnits
|
1654
|
+
totalCost += theWinACCost
|
1655
|
+
end
|
1656
|
+
elsif zone_info[:sysname] =~ /Split/i
|
1657
|
+
zone_info[:systype] = 'MiniSplit'
|
1658
|
+
# Cooling cost of Mini-split AC ...
|
1659
|
+
if cooling_coil_name == 'nil'
|
1660
|
+
# The cooling coil name doesn't exist so must use a different method to determine cooling
|
1661
|
+
# capacity for mini-spli units!
|
1662
|
+
query = "SELECT Value FROM ComponentSizes WHERE CompName='WindowAC' AND Units='W'"
|
1663
|
+
coolCapVal = model.sqlFile().get().execAndReturnFirstDouble(query)
|
1664
|
+
zone_info[:coolcapacity] = coolCapVal.to_f / 1000.0 # Watts -> kW
|
1665
|
+
end
|
1666
|
+
if zone_info[:coolcapacity] > 0
|
1667
|
+
# Mini-splt cooling unit
|
1668
|
+
coolCapacity = zone_info[:coolcapacity] / zone.multiplier
|
1669
|
+
numUnits = get_HVAC_multiplier('SplitSZWall', coolCapacity)
|
1670
|
+
# PTAC unit cost (Note that same numUnits multiple applied within getHVACCost())
|
1671
|
+
matCost, labCost = getHVACCost(zone_info[:sysname], 'PTAC', coolCapacity, false)
|
1672
|
+
theMiniSplitUnitCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1673
|
+
# For each PTAC unit there will be an electrical junction box (wiring costed with distribution - not here)
|
1674
|
+
matCost, labCost = getHVACCost('Electrical Outlet Box', 'Box', 1, true)
|
1675
|
+
elecBoxCost = (matCost * reg_mat_elec / 100.0 + labCost * reg_lab_elec / 100.0) * numUnits
|
1676
|
+
# Total PTAC cost for this zone (excluding distribution piping):
|
1677
|
+
theMiniSplitCost = (theMiniSplitUnitCost + elecBoxCost) * zone.multiplier
|
1678
|
+
zone_info[:coolcost] = theMiniSplitCost
|
1679
|
+
zone_info[:num_units] = numUnits
|
1680
|
+
totalCost += theMiniSplitCost
|
1681
|
+
end
|
1682
|
+
|
1683
|
+
end
|
1684
|
+
# Add information to zonal costing report.
|
1685
|
+
@costing_report['heating_and_cooling']['zonal_systems'] << {
|
1686
|
+
'systype' => zone_info[:systype],
|
1687
|
+
'zone_number' => numZones,
|
1688
|
+
'zone_name' => zone_info[:zonename],
|
1689
|
+
'zone_multiple' => zone_info[:multiplier],
|
1690
|
+
'heat_capacity(kW)' => zone_info[:heatcapacity].round(1),
|
1691
|
+
'cool_capacity(kW)' => zone_info[:coolcapacity].round(1),
|
1692
|
+
'heat_cost' => zone_info[:heatcost].round(0),
|
1693
|
+
'cool_cost' => zone_info[:coolcost].round(0),
|
1694
|
+
'heatcool_cost' => zone_info[:heatcoolcost].round(0),
|
1695
|
+
'piping_cost' => zone_info[:pipingcost].round(0),
|
1696
|
+
'wiring_cost' => zone_info[:wiringcost].round(0),
|
1697
|
+
'num_units' => zone_info[:num_units],
|
1698
|
+
'cummultive_zonal_cost' => totalCost.round(0)
|
1699
|
+
}
|
1700
|
+
end # End of check of check of if zonal equipment exists
|
1701
|
+
end # End of equipment loop
|
1702
|
+
end # End of zone loop
|
1703
|
+
|
1704
|
+
# Get cost of zonal vrf systems
|
1705
|
+
unless vrfSystemFloors[:vrfFloors].empty?
|
1706
|
+
totalCost += getZonalVRFCosting(vrfSystemFloors: vrfSystemFloors, model: model, prototype_creator: prototype_creator, regMat: regional_material, regLab: regional_installation, cumulCost: totalCost)
|
1707
|
+
end
|
1708
|
+
puts "\nZonal systems costing data successfully generated. Total zonal systems costs: $#{totalCost.round(0)}"
|
1709
|
+
|
1710
|
+
return totalCost
|
1711
|
+
end
|
1712
|
+
|
1713
|
+
def getHeaderPipingDistributionCost(numAGFlrs, mechRmInBsmt, regional_material, regional_installation, reg_elec_mat, reg_elec_inst, pumpFlow, horz_dist, nom_flr_hght)
|
1714
|
+
# Hot water central header piping distribution costs. Note that the piping distribution cost
|
1715
|
+
# of zone piping is done in the zonalsys_costing function
|
1716
|
+
|
1717
|
+
# Central header piping Cost
|
1718
|
+
supHdrCost = 0; retHdrCost = 0
|
1719
|
+
mechRmInBsmt ? numFlrs = numAGFlrs + 1 : numFlrs = numAGFlrs
|
1720
|
+
if numFlrs < 3
|
1721
|
+
# Header pipe is same diameter as distribution pipes to zone floors
|
1722
|
+
supHdrLen = numFlrs * nom_flr_hght
|
1723
|
+
|
1724
|
+
# 1.25 inch Steel pipe
|
1725
|
+
matCost, labCost = getHVACCost('Header 1.25 inch steel pipe', 'SteelPipe', 1.25)
|
1726
|
+
supHdrpipingCost = supHdrLen * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1727
|
+
|
1728
|
+
# 1.25 inch Steel pipe insulation
|
1729
|
+
matCost, labCost = getHVACCost('Header 1.25 inch pipe insulation', 'PipeInsulation', 1.25)
|
1730
|
+
supHdrInsulCost = supHdrLen * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1731
|
+
|
1732
|
+
# 1.25 inch gate valves
|
1733
|
+
matCost, labCost = getHVACCost('Header 1.25 inch gate valves', 'ValvesGate', 1.25)
|
1734
|
+
supHdrValvesCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1735
|
+
|
1736
|
+
# 1.25 inch tee
|
1737
|
+
matCost, labCost = getHVACCost('Header 1.25 inch steel tee', 'SteelPipeTee', 1.25)
|
1738
|
+
supHdrTeeCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1739
|
+
|
1740
|
+
supHdrCost = supHdrpipingCost + supHdrInsulCost + supHdrValvesCost + supHdrTeeCost
|
1741
|
+
retHdrCost = supHdrCost
|
1742
|
+
else # Greater than 3 floors (including basement)
|
1743
|
+
# Use pumpFlow to determine pipe size
|
1744
|
+
if pumpFlow <= 0.0001262
|
1745
|
+
hdrPipeSize = 0.5
|
1746
|
+
elsif pumpFlow > 0.0001262 && pumpFlow <= 0.0002524
|
1747
|
+
hdrPipeSize = 0.75
|
1748
|
+
elsif pumpFlow > 0.0002524 && pumpFlow <= 0.0005047
|
1749
|
+
hdrPipeSize = 1.0
|
1750
|
+
elsif pumpFlow > 0.0005047 && pumpFlow <= 0.0010090
|
1751
|
+
hdrPipeSize = 1.25
|
1752
|
+
elsif pumpFlow > 0.0010090 && pumpFlow <= 0.0015773
|
1753
|
+
hdrPipeSize = 1.5
|
1754
|
+
elsif pumpFlow > 0.0015773 && pumpFlow <= 0.0031545
|
1755
|
+
hdrPipeSize = 2.0
|
1756
|
+
elsif pumpFlow > 0.0031545
|
1757
|
+
hdrPipeSize = 2.5
|
1758
|
+
end
|
1759
|
+
|
1760
|
+
hdrPipeLen = horz_dist + nom_flr_hght * numFlrs
|
1761
|
+
|
1762
|
+
# Steel pipe
|
1763
|
+
matCost, labCost = getHVACCost("Header Steel Pipe - #{hdrPipeSize} inch", 'SteelPipe', hdrPipeSize)
|
1764
|
+
supHdrpipingCost = hdrPipeLen * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1765
|
+
|
1766
|
+
# Steel pipe insulation
|
1767
|
+
matCost, labCost = getHVACCost("Header Pipe Insulation - #{hdrPipeSize} inch", 'PipeInsulation', hdrPipeSize)
|
1768
|
+
supHdrInsulCost = hdrPipeLen * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1769
|
+
|
1770
|
+
# Gate valves
|
1771
|
+
matCost, labCost = getHVACCost("Header Gate Valves - #{hdrPipeSize} inch", 'ValvesGate', hdrPipeSize)
|
1772
|
+
supHdrValvesCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1773
|
+
|
1774
|
+
# Tee
|
1775
|
+
matCost, labCost = getHVACCost("Header Steel Tee - #{hdrPipeSize} inch", 'SteelPipeTee', hdrPipeSize)
|
1776
|
+
supHdrTeeCost = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1777
|
+
|
1778
|
+
supHdrCost = supHdrpipingCost + supHdrInsulCost + supHdrValvesCost + supHdrTeeCost
|
1779
|
+
retHdrCost = supHdrCost
|
1780
|
+
end
|
1781
|
+
|
1782
|
+
hdrPipeCost = supHdrCost + retHdrCost
|
1783
|
+
|
1784
|
+
# Electrical header costs. Central electric header cost for zonal heatingunits
|
1785
|
+
hdrLen = numFlrs * nom_flr_hght
|
1786
|
+
|
1787
|
+
# Conduit - only one spreadsheet entry
|
1788
|
+
matCost, labCost = getHVACCost('Header Metal conduit', 'Conduit', '')
|
1789
|
+
hdrConduitCost = hdrLen * (matCost * reg_elec_mat / 100.0 + labCost * reg_elec_inst / 100.0)
|
1790
|
+
|
1791
|
+
# Wiring - size 10
|
1792
|
+
matCost, labCost = getHVACCost('Header No 10 Wiring', 'Wiring', 10)
|
1793
|
+
hdrWireCost = hdrLen / 100 * (matCost * reg_elec_mat / 100.0 + labCost * reg_elec_inst / 100.0)
|
1794
|
+
|
1795
|
+
# Box - size 4
|
1796
|
+
matCost, labCost = getHVACCost('Header 4 inch deep Box', 'Box', 4)
|
1797
|
+
hdrBoxCost = numFlrs * (matCost * reg_elec_mat / 100.0 + labCost * reg_elec_inst / 100.0)
|
1798
|
+
|
1799
|
+
elecHdrCost = hdrConduitCost + hdrWireCost + hdrBoxCost
|
1800
|
+
|
1801
|
+
# Central gas header cost will be determined in zonalsys_costing function since
|
1802
|
+
# this cost depends on existence of at least one gas-fired unit heater in building.
|
1803
|
+
|
1804
|
+
hdrDistributionCost = hdrPipeCost + elecHdrCost
|
1805
|
+
|
1806
|
+
return hdrDistributionCost
|
1807
|
+
end
|
1808
|
+
|
1809
|
+
def getPerimDistPipingCost(zone, nom_flr_hght, regional_material, regional_installation)
|
1810
|
+
# Get perimeter distribution piping cost
|
1811
|
+
extWallArea = 0.0
|
1812
|
+
perimPipingCost = 0.0
|
1813
|
+
zone.spaces.sort.each do |space|
|
1814
|
+
if space.spaceType.empty? or space.spaceType.get.standardsSpaceType.empty? or space.spaceType.get.standardsBuildingType.empty?
|
1815
|
+
raise ("standards Space type and building type is not defined for space:#{space.name.get}. Skipping this space for costing.")
|
1816
|
+
end
|
1817
|
+
extWallArea += OpenStudio.convert(space.exteriorWallArea.to_f,"m^2","ft^2").get # sq.ft.
|
1818
|
+
end
|
1819
|
+
perimTotal = ( extWallArea / nom_flr_hght ) * zone.multiplier
|
1820
|
+
|
1821
|
+
# 1.25 inch Steel pipe
|
1822
|
+
matCost, labCost = getHVACCost('Perimeter Distribution - 1.25 inch steel pipe', 'SteelPipe', 1.25)
|
1823
|
+
perimPipingCost = perimTotal * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1824
|
+
|
1825
|
+
# 1.25 inch Steel pipe insulation
|
1826
|
+
matCost, labCost = getHVACCost('Perimeter Distribution - 1.25 inch pipe insulation', 'PipeInsulation', 1.25)
|
1827
|
+
perimPipingCost += perimTotal * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1828
|
+
|
1829
|
+
return perimPipingCost
|
1830
|
+
end
|
1831
|
+
|
1832
|
+
def getPerimDistWiringCost(zone, nom_flr_hght, regional_material, regional_installation)
|
1833
|
+
# Get perimeter distribution wiring cost
|
1834
|
+
extWallArea = 0.0
|
1835
|
+
perimWiringCost = 0.0
|
1836
|
+
zone.spaces.sort.each do |space|
|
1837
|
+
if space.spaceType.empty? or space.spaceType.get.standardsSpaceType.empty? or space.spaceType.get.standardsBuildingType.empty?
|
1838
|
+
raise ("standards Space type and building type is not defined for space:#{space.name.get}. Skipping this space for costing.")
|
1839
|
+
end
|
1840
|
+
extWallArea += OpenStudio.convert(space.exteriorWallArea.to_f,"m^2","ft^2").get # sq.ft.
|
1841
|
+
end
|
1842
|
+
perimTotal = ( extWallArea / nom_flr_hght ) * zone.multiplier
|
1843
|
+
|
1844
|
+
# Conduit - only one spreadsheet entry
|
1845
|
+
matCost, labCost = getHVACCost('Perimeter Distribution - Metal conduit', 'Conduit', '')
|
1846
|
+
perimWiringCost = perimTotal * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1847
|
+
|
1848
|
+
# Wiring - size 10
|
1849
|
+
matCost, labCost = getHVACCost('Perimeter Distribution - No 10 Wiring', 'Wiring', 10)
|
1850
|
+
perimWiringCost += perimTotal / 100 * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
|
1851
|
+
|
1852
|
+
return perimWiringCost
|
1853
|
+
end
|
1854
|
+
|
1855
|
+
# Get information on Zonal VRF System in a Thermal Zone
|
1856
|
+
def getZonalVRFInfo(zone:, model:, prototype_creator:, zonalSys:, vrfSystemFloors:, regMat:, regLab:, numZones:)
|
1857
|
+
#Get heating and cooling coil objects
|
1858
|
+
heatingCoil = zonalSys.heatingCoil.get.to_CoilHeatingDXVariableRefrigerantFlow.get
|
1859
|
+
coolingCoil = zonalSys.coolingCoil.get.to_CoilCoolingDXVariableRefrigerantFlow.get
|
1860
|
+
|
1861
|
+
# Get heating capacity
|
1862
|
+
if heatingCoil.isRatedTotalHeatingCapacityAutosized.to_bool
|
1863
|
+
heatingCapkW = heatingCoil.autosizedRatedTotalHeatingCapacity.to_f/1000.0
|
1864
|
+
else
|
1865
|
+
heatingCapkW = (heatingCoil.ratedTotalHeatingCapacity).to_f/1000.0
|
1866
|
+
end
|
1867
|
+
|
1868
|
+
# Get cooling capacity
|
1869
|
+
if coolingCoil.isRatedTotalCoolingCapacityAutosized.to_bool
|
1870
|
+
coolingCapkW = coolingCoil.autosizedRatedTotalCoolingCapacity.to_f/1000.0
|
1871
|
+
else
|
1872
|
+
coolingCapkW = coolingCoil.grossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevel.to_f/1000.0
|
1873
|
+
end
|
1874
|
+
|
1875
|
+
# Get the multiplier for the thermal zone
|
1876
|
+
zoneMult = zone.multiplier
|
1877
|
+
|
1878
|
+
# Set capacity to the highest of the heating or cooling capacity and adjust for the thermal zone multiplier
|
1879
|
+
heatingCapkW >= coolingCapkW ? totalCapkW = heatingCapkW/zoneMult : totalCapkW = coolingCapkW/zoneMult
|
1880
|
+
|
1881
|
+
# Get the thermal zone (TZ) and collect information on:
|
1882
|
+
# The TZ name.
|
1883
|
+
# The TZ capacity.
|
1884
|
+
# The spaces associated with the TZ.
|
1885
|
+
# The floors the spaces occupy.
|
1886
|
+
# The total ceiling area and floor area for each floor the spaces occupy.
|
1887
|
+
# The centroid of all of the spaces on a given floor (this may be outside of the spaces for some geometries)
|
1888
|
+
# The building story names and objects associated with a thermal zone.
|
1889
|
+
# the ceiling and floor area of the spaces associated with it for each floor they occupy
|
1890
|
+
# The capacity of the TZ on a given floor (calculated at total capacity * floor area of TZ spaces on floor/total TZ floor area)
|
1891
|
+
# The VRF ceiling mounts required and associated cost
|
1892
|
+
# The VRF system controllers cost
|
1893
|
+
tzFloorsInfo = prototype_creator.thermal_zone_get_centroid_per_floor(zone)
|
1894
|
+
tzFloorsInfo.each do |tzFloor|
|
1895
|
+
tzStoryFloorArea_m2 = 0
|
1896
|
+
tzSpaceMults = []
|
1897
|
+
tzFloor[:spaces].each do |tz_space|
|
1898
|
+
tzStoryFloorArea_m2 += tz_space.floorArea.to_f
|
1899
|
+
tzSpaceMults << tz_space.multiplier.to_f
|
1900
|
+
end
|
1901
|
+
tzFloorCapkW = totalCapkW * tzStoryFloorArea_m2 / zone.floorArea.to_f
|
1902
|
+
# Get the VRF Ceiling mount information and cost
|
1903
|
+
vrfCeilMountInfo = getHVACDBInfo(name: "VRF Ceiling Mount", materialLookup: "VRF-CeilingMount", materialSize: tzFloorCapkW, exactMatch: false)
|
1904
|
+
# Get VRF Ceiling mount cost.
|
1905
|
+
material, labour = getCost(vrfCeilMountInfo[:name], vrfCeilMountInfo[:hvac_material], vrfCeilMountInfo[:multiplier])
|
1906
|
+
# The multiplier (number of units required to meet the demand) is already included in the material and labour cost.
|
1907
|
+
vrfCeilMountCost = (material*regMat + labour*regLab) / 100.0
|
1908
|
+
# Get the VRF System Controller Cost (for 1 controller)
|
1909
|
+
material, labour = getHVACCost("VRF System Controller", 'VRF-Sys-Controller', nil, true)
|
1910
|
+
# Since material and labour are for 1 controller the multiplier is added here.
|
1911
|
+
vrfSysContCost = (material * regMat + labour * regLab) * vrfCeilMountInfo[:multiplier] / 100.0
|
1912
|
+
|
1913
|
+
# Put the thermal zone information for the floor into a hash.
|
1914
|
+
tzFloorInfo = {
|
1915
|
+
tzName: zone.name.to_s,
|
1916
|
+
tzNum: numZones,
|
1917
|
+
tzFloorName: tzFloor[:story_name],
|
1918
|
+
tzFloorCeilingAream2: tzFloor[:ceiling_area],
|
1919
|
+
tzMult: zoneMult,
|
1920
|
+
tzSpaces: tzFloor[:spaces],
|
1921
|
+
tzSpaceMults: tzSpaceMults,
|
1922
|
+
tzFloorArea_m2: tzStoryFloorArea_m2,
|
1923
|
+
tzFloorCapkW: tzFloorCapkW,
|
1924
|
+
tzCentroid: tzFloor[:centroid],
|
1925
|
+
vrfCeilMountInfo: vrfCeilMountInfo,
|
1926
|
+
vrfCeilMountCost: vrfCeilMountCost,
|
1927
|
+
vrfSysContCost: vrfSysContCost
|
1928
|
+
}
|
1929
|
+
|
1930
|
+
# Add the TZ information for the floor to a hash containing all of the thermal zones.
|
1931
|
+
vrfSystemFloors = compileZonalVRFFloors(vrfSystemFloors: vrfSystemFloors, tzFloor: tzFloorInfo)
|
1932
|
+
end
|
1933
|
+
return vrfSystemFloors
|
1934
|
+
end
|
1935
|
+
|
1936
|
+
# Cost zonal VRF systems including zone equipment (ceiling units and associated tubing and wiring), floor equipment (
|
1937
|
+
# branch distributor on each floor), VRF system condenser (assumed one on rooftop and another every 50m), and piping
|
1938
|
+
# and wiring linking the branch distributors to one another and to the condensers.
|
1939
|
+
#
|
1940
|
+
# Takes in:
|
1941
|
+
# vrfSystemFloors = {
|
1942
|
+
# maxCeil(float): The height of the thermal zone served by a VRF system with the highest ceiling in the
|
1943
|
+
# building. This is referenced to the global origin for the building. The units are m.
|
1944
|
+
# lowCeil(float): The height of the thermal zone served by a VRF system with the lowest ceiling in the
|
1945
|
+
# building. This is referenced to the global origin for the building. The units are m.
|
1946
|
+
# vrfFloors(array): [
|
1947
|
+
# storyName(string): Name of the current floor (story).
|
1948
|
+
# buildStoryObj(Obj): The OpenStudio object associated with the current floor (story).
|
1949
|
+
# floorAream2: Total floor area (m2) of thermal zones on the current floor served by VRF systems. Does not
|
1950
|
+
# include multipliers.
|
1951
|
+
# floorCeillingAream2: The Total ceiling area (m2) of thermal zones on the current floor served by VRF
|
1952
|
+
# systems. Does not include multipliers.
|
1953
|
+
# floorTZs(array): An array containing each tzFloor hash described above for the current floor.
|
1954
|
+
#
|
1955
|
+
# ]
|
1956
|
+
# }
|
1957
|
+
# model(hash): OpenStudio building model.
|
1958
|
+
# prototype_creator(object): OpenStudio-Standards object for whichever version of NECB was used to create the model.
|
1959
|
+
# regMat(float): HVAC regional cost factor for materiel.
|
1960
|
+
# regLab(float): HVAC regional cost factor for labour.
|
1961
|
+
# cumulCost(float): Cumulative zonal system cost.
|
1962
|
+
#
|
1963
|
+
# Output(float): Total cost for VRF system. Also adds information to @costing_report for condenser(s), VRF zonal
|
1964
|
+
# systems, and branch distributors.
|
1965
|
+
def getZonalVRFCosting(vrfSystemFloors:, model:, prototype_creator:, regMat:, regLab:, cumulCost:)
|
1966
|
+
# Include empty array in costing report for branch distributor costs on each floor
|
1967
|
+
@costing_report['heating_and_cooling']['floor_systems'] = []
|
1968
|
+
total_cost = 0
|
1969
|
+
vrfWireInfo = getHVACDBInfo(name: "VRF Wiring", materialLookup: "wiring", materialSize: 10, exactMatch: true)
|
1970
|
+
regMatElec, regLabElec = get_regional_cost_factors(@costing_report['province_state'], @costing_report['city'], vrfWireInfo[:hvac_material])
|
1971
|
+
|
1972
|
+
# Find the center of the highest roof
|
1973
|
+
roof_cent_info = prototype_creator.find_highest_roof_centre(model)
|
1974
|
+
roof_cent = roof_cent_info[:roof_centroid]
|
1975
|
+
# Find the distance between the highest roof and the ceiling of the lowest space served by a VRF system
|
1976
|
+
maxHeightDiff = (roof_cent[2] - vrfSystemFloors[:lowCeil]).to_f.round(8)
|
1977
|
+
# Find the roof height for the condenensate line cost. If the maxHeightDiff includes basement spaces use it,
|
1978
|
+
# otherwise use the height of the roof.
|
1979
|
+
maxHeightDiff > roof_cent[2].to_f.round(8) ? roofHeight = maxHeightDiff : roofHeight = roof_cent[2].to_f.round(8)
|
1980
|
+
# Get the condenser cost
|
1981
|
+
vrfCondenserCost = costVRFCondenser(model: model, maxHeightDiff: maxHeightDiff, regMat: regMat, regLab: regLab, regMatElec: regMatElec, regLabElec:regLabElec, roofHeight: roofHeight)
|
1982
|
+
total_cost += vrfCondenserCost
|
1983
|
+
vrfSystemFloors[:vrfFloors].each do |currFloor|
|
1984
|
+
vrfDistWallInfo = getWallWithLargestArea(currFloor: currFloor)
|
1985
|
+
vrfDistWallCent = vrfDistWallInfo[:wallCent]
|
1986
|
+
totalFloorCapkW = 0
|
1987
|
+
totalFloorCeilUnits = 0
|
1988
|
+
floorMults = []
|
1989
|
+
currFloor[:floorTZs].each do |floorTZ|
|
1990
|
+
zoneWallLengthm = (vrfDistWallCent[0] - floorTZ[:tzCentroid][0]).abs + (vrfDistWallCent[1] - floorTZ[:tzCentroid][1]).abs
|
1991
|
+
zoneWallLengthft = (OpenStudio.convert(zoneWallLengthm, 'm', 'ft').get)
|
1992
|
+
elecLength = zoneWallLengthft + 10.0 * (floorTZ[:vrfCeilMountInfo][:multiplier] - 1)
|
1993
|
+
|
1994
|
+
# Get the zone refrigerant tubing cost (tubing running from the ceiling units to the brancd distributers). A
|
1995
|
+
# Size of 50 is used for interior refrigerant tubing while 10 is used for the exterior tubing used between the
|
1996
|
+
# branch distributors and the condensers.
|
1997
|
+
zoneRefrigTubingMat, zoneRefrigTubingLab = getHVACCost("VRF Zone Refrigerant Tubing", 'Refrig-tubing', 50, true)
|
1998
|
+
# Refrigerant tubing comes in 50 ft rolls
|
1999
|
+
zoneRefrigTubingCost = ((zoneRefrigTubingMat * regMat + zoneRefrigTubingLab * regLab) / 100) * elecLength / 50.0
|
2000
|
+
|
2001
|
+
# Include condensate line tubing cost
|
2002
|
+
zoneCondTubingMat, zoneCondTubingLab = getHVACCost('VRF Zone Condensate Line Tubing', 'PEX_tubing', 0.5, true)
|
2003
|
+
zoneCondTubingCost = ((zoneCondTubingMat * regMat + zoneCondTubingLab * regLab) / 100) * elecLength
|
2004
|
+
|
2005
|
+
# Include coupler cost for condensate line
|
2006
|
+
zoneCondCouplingMat, zoneCondCouplingLab = getHVACCost('VRF Zone Condensate Line Couplers', 'PVC_coupling', 0.5, true)
|
2007
|
+
zoneCondCouplingCost = ((zoneCondCouplingMat * regMat + zoneCondCouplingLab * regLab) / 100) * floorTZ[:vrfCeilMountInfo][:multiplier]
|
2008
|
+
|
2009
|
+
# Include tee cost for condensate line
|
2010
|
+
zoneCondTeeMat, zoneCondTeeLab = getHVACCost('VRF Zone Condensate Line Tees', 'PVC_tee', 0.5, true)
|
2011
|
+
zoneCondTeeCost = ((zoneCondTeeMat * regMat + zoneCondTeeLab * regLab) / 100) * floorTZ[:vrfCeilMountInfo][:multiplier]
|
2012
|
+
|
2013
|
+
# Total condensate line cost
|
2014
|
+
zoneCondLineCost = zoneCondTubingCost + zoneCondCouplingCost + zoneCondTeeCost
|
2015
|
+
|
2016
|
+
# Get the wiring cost
|
2017
|
+
zoneWiringMat, zoneWiringLab = getCost(vrfWireInfo[:name], vrfWireInfo[:hvac_material], vrfWireInfo[:multiplier])
|
2018
|
+
zoneWiringCost = ((zoneWiringMat*regMatElec + zoneWiringLab*regLabElec)/100)*elecLength/100
|
2019
|
+
|
2020
|
+
# Get the conduit cost
|
2021
|
+
zoneConduitMat, zoneConduitLab = getHVACCost("VRF Zone Conduit", 'Conduit', nil, true)
|
2022
|
+
zoneConduitCost = ((zoneConduitMat*regMatElec + zoneConduitLab*regLabElec)/100)*elecLength
|
2023
|
+
|
2024
|
+
# Get the total cost
|
2025
|
+
totalZoneCost = (zoneRefrigTubingCost + zoneCondLineCost + zoneWiringCost + zoneConduitCost + floorTZ[:vrfCeilMountCost] + floorTZ[:vrfSysContCost])*floorTZ[:tzMult]
|
2026
|
+
|
2027
|
+
total_cost += totalZoneCost
|
2028
|
+
|
2029
|
+
cumulCost += total_cost
|
2030
|
+
# Add zonal cost to report
|
2031
|
+
@costing_report['heating_and_cooling']['zonal_systems'] << {
|
2032
|
+
'systype' => 'zonalVRF',
|
2033
|
+
'zone_number' => floorTZ[:tzNum],
|
2034
|
+
'zone_name' => floorTZ[:tzName],
|
2035
|
+
'zone_multiple' => floorTZ[:tzMult],
|
2036
|
+
'heat_capacity(kW)' => floorTZ[:tzFloorCapkW].round(1),
|
2037
|
+
'cool_capacity(kW)' => floorTZ[:tzFloorCapkW].round(1),
|
2038
|
+
'heat_cost' => 0.00,
|
2039
|
+
'cool_cost' => 0.00,
|
2040
|
+
'heatcool_cost' => ((floorTZ[:vrfCeilMountCost] + floorTZ[:vrfSysContCost]) * floorTZ[:tzMult]).round(0),
|
2041
|
+
'piping_cost' => ((zoneRefrigTubingCost + zoneCondLineCost) * floorTZ[:tzMult]).round(0),
|
2042
|
+
'wiring_cost' => ((zoneWiringCost + zoneConduitCost) * floorTZ[:tzMult]).round(0),
|
2043
|
+
'num_units' => floorTZ[:vrfCeilMountInfo][:multiplier],
|
2044
|
+
'cummultive_zonal_cost' => cumulCost.round(0)
|
2045
|
+
}
|
2046
|
+
totalFloorCapkW += floorTZ[:tzFloorCapkW]
|
2047
|
+
totalFloorCeilUnits += floorTZ[:vrfCeilMountInfo][:multiplier]
|
2048
|
+
# Determine the distribution of thermal zone multipliers on the floor. For each thermal zone on the floor get
|
2049
|
+
# the multiplier. If the same multiplier is already in the arry then add 1 to the number of occurrences of that
|
2050
|
+
# multiplier. If the multiplier is not in the array then add the multiplier to the array with an occurrence of
|
2051
|
+
# one.
|
2052
|
+
if floorMults.empty?
|
2053
|
+
floorMults << {
|
2054
|
+
zoneMult: floorTZ[:tzMult],
|
2055
|
+
numMults: 1
|
2056
|
+
}
|
2057
|
+
else
|
2058
|
+
numFloorMult = floorMults.select{|data| data[:zoneMult] == floorTZ[:tzMult]}
|
2059
|
+
if numFloorMult.empty?
|
2060
|
+
floorMults << {
|
2061
|
+
zoneMult: floorTZ[:tzMult],
|
2062
|
+
numMults: 1
|
2063
|
+
}
|
2064
|
+
else
|
2065
|
+
numFloorMult[0][:numMults] += 1
|
2066
|
+
end
|
2067
|
+
end
|
2068
|
+
end
|
2069
|
+
# Find the number of and type of branch distributors which meet the number of ceiling unit criteria and capacity
|
2070
|
+
# criteria. Costing for a separate, smaller, branch distributor may be returned if multiple branch distributors
|
2071
|
+
# are required to meet the connection or load requirements. In this case this smaller branch distributor will
|
2072
|
+
# meet any connection or capacity remaining from the main equipment*(multiplier - 1). In some cases most of the
|
2073
|
+
# requirements may be met by the (multiplier - 1)*equipment and a much smaller piece of equipment can be used for
|
2074
|
+
# the remaining requipments.
|
2075
|
+
vrfBranchDistInfo, vrfBranchDistInfoRed= getHVACMultiSizeDBInfo(name: 'VRF Branch Distributors', materialLookup: 'VRF-Solenoid', materialCap: totalFloorCapkW, materialCon: totalFloorCeilUnits)
|
2076
|
+
# Get the branch distributor cost
|
2077
|
+
vrfBranchDistMat, vrfBranchDistLab = getCost(vrfBranchDistInfo[:name], vrfBranchDistInfo[:hvac_material], vrfBranchDistInfo[:multiplier])
|
2078
|
+
vrfBranchDistCost = (vrfBranchDistMat*regMat + vrfBranchDistLab*regLab)/100
|
2079
|
+
# Using the distribution of zone multipliers on the floor find which appears most often.
|
2080
|
+
initMaxMult = floorMults.max_by{|data| data[:numMults]}
|
2081
|
+
# If several different zone multipliers appear the same number of times then choose the largest zone multiplier
|
2082
|
+
floorMultsMatch = floorMults.select{|data| data[:numMults] == initMaxMult[:numMults]}
|
2083
|
+
if floorMultsMatch.size > 1
|
2084
|
+
maxMult = floorMultsMatch.max_by{|data| data[:zoneMult]}[:zoneMult]
|
2085
|
+
else
|
2086
|
+
maxMult = initMaxMult[:zoneMult]
|
2087
|
+
end
|
2088
|
+
# multiply the branch distributer cost by the floor multiplier
|
2089
|
+
totalVRFBranchDistCost = vrfBranchDistCost*maxMult
|
2090
|
+
|
2091
|
+
# Add branch distributor cost to report
|
2092
|
+
@costing_report['heating_and_cooling']['floor_systems'] << {
|
2093
|
+
'systype' => 'VRFBranchDistributor',
|
2094
|
+
'floor_name' => currFloor[:storyName],
|
2095
|
+
'floor_multiple' => maxMult,
|
2096
|
+
'heat_capacity(kW)' => totalFloorCapkW.round(1),
|
2097
|
+
'cool_capacity(kW)' => totalFloorCapkW.round(1),
|
2098
|
+
'num_ceiling_units' => totalFloorCeilUnits,
|
2099
|
+
'heatcool_cost' => totalVRFBranchDistCost.round(0),
|
2100
|
+
'num_units' => vrfBranchDistInfo[:multiplier],
|
2101
|
+
'total_floor_cost' => totalVRFBranchDistCost.round(0)
|
2102
|
+
}
|
2103
|
+
|
2104
|
+
total_cost += totalVRFBranchDistCost
|
2105
|
+
|
2106
|
+
# Check if a smaller piece of equipment was found to meet the requirements remaining after removing the
|
2107
|
+
# (multiplier-1) requirements.
|
2108
|
+
unless vrfBranchDistInfoRed.nil?
|
2109
|
+
# Get the branch distributor cost
|
2110
|
+
vrfBranchDistRedMat, vrfBranchDistRedLab = getCost(vrfBranchDistInfo[:name], vrfBranchDistInfoRed[:red_ret_hash], 1.0)
|
2111
|
+
vrfBranchDistRedCost = (vrfBranchDistRedMat*regMat + vrfBranchDistRedLab*regLab)/100
|
2112
|
+
|
2113
|
+
# multiply the branch distributer cost by the floor multiplier
|
2114
|
+
totalVRFBranchDistRedCost = vrfBranchDistRedCost*maxMult
|
2115
|
+
|
2116
|
+
# Add branch distributor cost to report
|
2117
|
+
@costing_report['heating_and_cooling']['floor_systems'] << {
|
2118
|
+
'systype' => 'VRFBranchDistributor',
|
2119
|
+
'floor_name' => currFloor[:storyName],
|
2120
|
+
'floor_multiple' => maxMult,
|
2121
|
+
'heat_capacity(kW)' => vrfBranchDistInfoRed[:numCap].to_f.round(1),
|
2122
|
+
'cool_capacity(kW)' => vrfBranchDistInfoRed[:numCap].to_f.round(1),
|
2123
|
+
'num_ceiling_units' => vrfBranchDistInfoRed[:numCon].to_f.round(1),
|
2124
|
+
'heatcool_cost' => totalVRFBranchDistRedCost.round(0),
|
2125
|
+
'num_units' => 1.0,
|
2126
|
+
'total_floor_cost' => totalVRFBranchDistRedCost.round(0)
|
2127
|
+
}
|
2128
|
+
total_cost += totalVRFBranchDistRedCost
|
2129
|
+
end
|
2130
|
+
end
|
2131
|
+
return total_cost
|
2132
|
+
end
|
2133
|
+
|
2134
|
+
# This method takes information about a thermal zone served by a VRF system (tzFloor) and adds it to the collection of
|
2135
|
+
# thermal zones also served by VRF systems on the same floor. The ultimate output (once all thermal zones are read)
|
2136
|
+
# is an array of hashes. Each entry in the array represents a floor of the building. Each floor entry contains
|
2137
|
+
# information about thermal zones served by VRF systems on that floor.
|
2138
|
+
#
|
2139
|
+
# The information on the thermal zone served by a VRF system on a given floor is contained in tzFloor. The overall
|
2140
|
+
# collection of thermal zone information by floors is contained in vrfSystemFloors. This method modifies
|
2141
|
+
# vrfSystemFloors which is why that is an input and output for the method. Both tzFloor and vrfSystemFloors are
|
2142
|
+
# described below:
|
2143
|
+
# Input:
|
2144
|
+
# tzFloor = {
|
2145
|
+
# tzName(string): Name of the thermal zone.
|
2146
|
+
# tzFloorName(string): Name of the floor the thermal zone is on (or if the TZ is on multiple floors the
|
2147
|
+
# name of the current floor being looked at for the thermal zone).
|
2148
|
+
# tzFloorCeilingAream2(float): The ceiling area (m2) of the thermal zone on the given floor (this is for the
|
2149
|
+
# current thermal zone only and does not include multiples).
|
2150
|
+
# tzMult(float): The multiplier for the thermal zone (that is the thermal zone is modeled as tzMult number
|
2151
|
+
# of identical thermal zones).
|
2152
|
+
# tzSpaces(array): An array containing all of the space objects contained by the thermal zone on the given
|
2153
|
+
# floor.
|
2154
|
+
# tzSpaceMults(array): An array containing all of the multipliers for the spaces in tzSpaces (probably all the
|
2155
|
+
# same as tzMult but I added it anyway).
|
2156
|
+
# tzFloorArea_m2(float): The floor area (m2) of all of the spaces in the thermal zone on the given floor (this
|
2157
|
+
# is for the current thermal zone only and does not include multiples).
|
2158
|
+
# tzFloorCapkW(float): The capacity (kW) of the thermal zone VRF system for the current floor. It is the
|
2159
|
+
# highest of the heating and cooling capacities for the VRF system. It dose not include
|
2160
|
+
# multipliers and is only for the current floor. If the same thermal zone spans multiple
|
2161
|
+
# floors then this is the total copacity for the thermal zone times tzFloorArea_m2
|
2162
|
+
# divided by the total floor area for the thermal zone.
|
2163
|
+
# tzCentroid(array): This is an array containing three items. These items ore x, y and z coordinates of the
|
2164
|
+
# centroid of the current thermal zone on the current floor referenced to the global
|
2165
|
+
# origin for the building. The units are in m. Note that, depending on the shape of the
|
2166
|
+
# thermal zone on the current floor, the centroid may not actually lie in the thermal
|
2167
|
+
# zone (e.g. for an L shaped thermal zone the centroid may be outside the L).
|
2168
|
+
# vrfCeilMountInfo(hash): This is a hash containing the costing information, from the costing spreadsheet, for
|
2169
|
+
# the VRF ceiling mounts serving the thermal zone on the current floor. It is only
|
2170
|
+
# for the current floor and does not include tz multipliers.
|
2171
|
+
# vrfCeilMountCost(float): The cost of the VRF ceiling mounts serving the thermal zone on the current floor.
|
2172
|
+
# It is only for the current floor and does not include tz multipliers.
|
2173
|
+
# vrfSysContCost: The cost of the VRF system controllers that are associated with each VRF ceiling mount. It
|
2174
|
+
# is only for the current floor and does not include tz multipliers.
|
2175
|
+
# }
|
2176
|
+
#
|
2177
|
+
# vrfSystemFloors = {
|
2178
|
+
# maxCeil(float): The height of the thermal zone served by a VRF system with the highest ceiling in the
|
2179
|
+
# building. This is referenced to the global origin for the building. The units are m.
|
2180
|
+
# lowCeil(float): The height of the thermal zone served by a VRF system with the lowest ceiling in the
|
2181
|
+
# building. This is referenced to the global origin for the building. The units are m.
|
2182
|
+
# vrfFloors(array): [
|
2183
|
+
# storyName(string): Name of the current floor (story).
|
2184
|
+
# buildStoryObj(Obj): The OpenStudio object associated with the current floor (story).
|
2185
|
+
# floorAream2: Total floor area (m2) of thermal zones on the current floor served by VRF systems. Does not
|
2186
|
+
# include multipliers.
|
2187
|
+
# floorCeillingAream2: The Total ceiling area (m2) of thermal zones on the current floor served by VRF
|
2188
|
+
# systems. Does not include multipliers.
|
2189
|
+
# floorTZs(array): An array containing each tzFloor hash described above for the current floor.
|
2190
|
+
#
|
2191
|
+
# ]
|
2192
|
+
# }
|
2193
|
+
#
|
2194
|
+
def compileZonalVRFFloors(vrfSystemFloors:, tzFloor:)
|
2195
|
+
# Check for the highest ceiling and lowest ceiling.
|
2196
|
+
vrfSystemFloors[:maxCeil] = tzFloor[:tzCentroid][2].to_f if tzFloor[:tzCentroid][2].to_f >= vrfSystemFloors[:maxCeil].to_f
|
2197
|
+
vrfSystemFloors[:lowCeil] = tzFloor[:tzCentroid][2].to_f if tzFloor[:tzCentroid][2].to_f <= vrfSystemFloors[:lowCeil].to_f
|
2198
|
+
# If this is the first time vrfSystemFloors has been used add a new floor and enter the information for tzFloor in
|
2199
|
+
# it.
|
2200
|
+
if vrfSystemFloors[:vrfFloors].empty?
|
2201
|
+
vrfSystemFloors[:vrfFloors] << {
|
2202
|
+
storyName: tzFloor[:tzFloorName],
|
2203
|
+
buildStoryObj: tzFloor[:tzSpaces][0].buildingStory.get,
|
2204
|
+
floorAream2: tzFloor[:tzFloorArea_m2],
|
2205
|
+
floorCeilingAream2: tzFloor[:tzFloorCeilingAream2],
|
2206
|
+
floorTZs: [tzFloor]
|
2207
|
+
}
|
2208
|
+
else
|
2209
|
+
# If vrfSystemFloors has been used check if the the floor that tzFloor is on has an entry already.
|
2210
|
+
vrfFloor = vrfSystemFloors[:vrfFloors].select{|sysFloor| sysFloor[:storyName].to_s.upcase == tzFloor[:tzFloorName].to_s.upcase}
|
2211
|
+
# If no entry has been made for the floor tzFloor is on then add a new floor and include the tzFloor info.
|
2212
|
+
if vrfFloor.empty?
|
2213
|
+
vrfSystemFloors[:vrfFloors] << {
|
2214
|
+
storyName: tzFloor[:tzFloorName],
|
2215
|
+
buildStoryObj: tzFloor[:tzSpaces][0].buildingStory.get,
|
2216
|
+
floorAream2: tzFloor[:tzFloorArea_m2],
|
2217
|
+
floorCeilingAream2: tzFloor[:tzFloorCeilingAream2],
|
2218
|
+
floorTZs: [tzFloor]
|
2219
|
+
}
|
2220
|
+
else
|
2221
|
+
# If the floor that tzFloor is on has already been made then adjust the floor information to include tzFloor.
|
2222
|
+
vrfFloor[0][:floorAream2] += tzFloor[:tzFloorArea_m2]
|
2223
|
+
vrfFloor[0][:floorCeilingAream2] += tzFloor[:tzFloorCeilingAream2]
|
2224
|
+
vrfFloor[0][:floorTZs] << tzFloor
|
2225
|
+
end
|
2226
|
+
end
|
2227
|
+
return vrfSystemFloors
|
2228
|
+
end
|
2229
|
+
|
2230
|
+
# This method finds and returns the outside wall with the largest area on a given building story. It takes in:
|
2231
|
+
# currFloor = {
|
2232
|
+
# storyName(string): Name of the current floor (story).
|
2233
|
+
# buildStoryObj(Obj): The OpenStudio object associated with the current floor (story).
|
2234
|
+
# floorAream2: Total floor area (m2) of thermal zones on the current floor served by VRF systems. Does not
|
2235
|
+
# include multipliers.
|
2236
|
+
# floorCeillingAream2: The Total ceiling area (m2) of thermal zones on the current floor served by VRF
|
2237
|
+
# systems. Does not include multipliers.
|
2238
|
+
# floorTZs(array): An array containing each tzFloor hash described above for the current floor.
|
2239
|
+
#
|
2240
|
+
# }
|
2241
|
+
# It returns a hash containing the following:
|
2242
|
+
# wallRetHash = {
|
2243
|
+
# largestOutsideWallObj(OS Object): OpenStudio surface object with the largest gross area that has a 'wall'
|
2244
|
+
# surface type and an 'Outdoors' outside boundary condition.
|
2245
|
+
# wallCentObj(array of floats): OpenStudio point3d object containing the x, y, z coordinates of the wall above
|
2246
|
+
# wall's centroid. In local coordinate system.
|
2247
|
+
# wallCentOrigin(array of OS Objects): Coordinates of the local wall origin in the absolute building
|
2248
|
+
# coordinate system.
|
2249
|
+
# wallCent(array of floats): Coordinates of the wall's centroid in floats referenced to the building
|
2250
|
+
# coordinate system.
|
2251
|
+
# }
|
2252
|
+
def getWallWithLargestArea(currFloor:)
|
2253
|
+
outsideWalls = []
|
2254
|
+
# Get all the spaces associated with the building story
|
2255
|
+
floorSpaces = currFloor[:buildStoryObj].spaces
|
2256
|
+
# Cycle through each space associated with the building story.
|
2257
|
+
floorSpaces.each do |floorSpace|
|
2258
|
+
# Get the surfaces in the spcae which have a 'Wall' Surface Type and an 'Outdoors' outside boundary condition.
|
2259
|
+
spaceOutWalls = floorSpace.surfaces.select{|surf| surf.surfaceType.to_s.upcase == 'WALL' && ((surf.outsideBoundaryCondition.to_s.upcase == 'OUTDOORS') || (surf.outsideBoundaryCondition.to_s.upcase == 'GROUND') || (surf.outsideBoundaryCondition.to_s.upcase == 'FOUNDATION'))}
|
2260
|
+
# Add these surfaces to the array containing outdoor walls.
|
2261
|
+
spaceOutWalls.each{|outWall| outsideWalls << outWall}
|
2262
|
+
end
|
2263
|
+
# Find and return the outside wall object with the largest gross area.
|
2264
|
+
largestWall = outsideWalls.sort.max_by{|outWall| outWall.grossArea.to_f}
|
2265
|
+
largestWallSpace = largestWall.space.get
|
2266
|
+
largestWallSpaceOrigin = [
|
2267
|
+
largestWallSpace.xOrigin,
|
2268
|
+
largestWallSpace.yOrigin,
|
2269
|
+
largestWallSpace.zOrigin
|
2270
|
+
]
|
2271
|
+
wallCentObj = largestWall.centroid
|
2272
|
+
wallCent = [
|
2273
|
+
wallCentObj.x.to_f + largestWallSpaceOrigin[0].to_f,
|
2274
|
+
wallCentObj.y.to_f + largestWallSpaceOrigin[1].to_f,
|
2275
|
+
wallCentObj.z.to_f + largestWallSpaceOrigin[2].to_f
|
2276
|
+
]
|
2277
|
+
wallRetHash = {
|
2278
|
+
largestOutsideWallObj: largestWall,
|
2279
|
+
wallCentObj: wallCentObj,
|
2280
|
+
wallCentOrigin: largestWallSpaceOrigin,
|
2281
|
+
wallCent: wallCent
|
2282
|
+
}
|
2283
|
+
return wallRetHash
|
2284
|
+
end
|
2285
|
+
|
2286
|
+
# Costing for the VRF Condenser(s) and the wiring and piping conecting the Condenser(s) to the branch distributors
|
2287
|
+
# on each floor of the building with thermal zones served by a VRF system.
|
2288
|
+
#
|
2289
|
+
# Taking in:
|
2290
|
+
# model(hash): OpenStudio building model
|
2291
|
+
# maxHeightDiff(float, m): Difference between height of highest ceiling and ceiling of lowest space served by a VRF
|
2292
|
+
# system.
|
2293
|
+
# regMat(float): HVAC regional cost factor for material.
|
2294
|
+
# regLab(float): HVAC regional cost factor for labour.
|
2295
|
+
# RegMatElec(float): Electrical regional cost factor for material.
|
2296
|
+
# RegLabElec(float): Electrical regional cost factor for labour.
|
2297
|
+
#
|
2298
|
+
# Returns:
|
2299
|
+
# Total VRF condensor cost (also adds information to @costing_report).
|
2300
|
+
def costVRFCondenser(model:, maxHeightDiff:, regMat:, regLab:, regMatElec:, regLabElec:, roofHeight:)
|
2301
|
+
# VRF systems have a maximum height difference of 50m. If the height difference calculated above is greater than
|
2302
|
+
# 50m then determine how many VRF condensers are required (one every 50m)
|
2303
|
+
numVRFheight = 1.0
|
2304
|
+
if maxHeightDiff > 50.0
|
2305
|
+
(maxHeightDiff % 50.0).round(1) > 0.0 ? numVRFheight = (maxHeightDiff / 50.0).to_i.to_f + 1.0 : numVRFheight = (maxHeightDiff / 50.0).to_f.round(0)
|
2306
|
+
end
|
2307
|
+
# Get the VRF condenser and calculate the overall capacity as the largest of the heating or cooling capacities.
|
2308
|
+
vrfCond = model.getAirConditionerVariableRefrigerantFlows[0]
|
2309
|
+
if vrfCond.isGrossRatedHeatingCapacityAutosized.to_bool
|
2310
|
+
heatingCapkW = vrfCond.autosizedGrossRatedHeatingCapacity.to_f/1000.0
|
2311
|
+
else
|
2312
|
+
heatingCapkW = vrfCond.grossRatedHeatingCapacity.to_f/1000.0
|
2313
|
+
end
|
2314
|
+
if vrfCond.isGrossRatedTotalCoolingCapacityAutosized.to_bool
|
2315
|
+
coolingCapkW = vrfCond.autosizedGrossRatedTotalCoolingCapacity.to_f/1000.0
|
2316
|
+
else
|
2317
|
+
coolingCapkW = vrfCond.grossRatedTotalCoolingCapacity.to_f/1000.0
|
2318
|
+
end
|
2319
|
+
heatingCapkW >= coolingCapkW ? vrfCondCapkW = heatingCapkW : vrfCondCapkW = coolingCapkW
|
2320
|
+
# If more than one VRF condenser is present because of a large height difference then assume each VRF condenser will
|
2321
|
+
# serve the same fraction of the load. Divide the revised capacity as the original capacity divided by the number
|
2322
|
+
# of VRF condensers required to compensate for a height difference (the default is 1).
|
2323
|
+
modVRFCondCapkW = vrfCondCapkW / numVRFheight
|
2324
|
+
vrfCondInfo = getHVACDBInfo(name: "VRF Condenser Unit", materialLookup: "VRF-HP-HRV-Outdoor", materialSize: modVRFCondCapkW, exactMatch: false)
|
2325
|
+
vrfSizeMult = vrfCondInfo[:multiplier]
|
2326
|
+
vrfMat, vrfLab = getCost(vrfCondInfo[:name], vrfCondInfo[:hvac_material], vrfCondInfo[:multiplier])
|
2327
|
+
vrfCondCost = (vrfMat * regMat + vrfLab * regLab) / 100
|
2328
|
+
|
2329
|
+
# Cost the refrigerant tubing (assume 20' of 0.5" supply and 1.0833" return tubing)
|
2330
|
+
# vrfCondSizeTon = (OpenStudio.convert(modVRFCondCapkW.to_f, 'kW', 'kBtu/hr').get)/12.0
|
2331
|
+
refrigPipeMat, refrigPipeLab = getHVACCost("Refrigerant Piping", 'refrig-tubing-large', 20, true)
|
2332
|
+
refrigPipingCost = (refrigPipeMat * regMat + refrigPipeLab * regLab) / 100
|
2333
|
+
|
2334
|
+
# Cost insulation for tubing (assume 20' of 1.25" pipe insulation for both the supply and return refrigerant tubing)
|
2335
|
+
refrigInsMat, refrigInsLab = getHVACCost('Refrigerant Insulation', 'pipeinsulation', 1.25, true)
|
2336
|
+
refrigInsulationCost = (refrigInsMat * regMat + refrigInsLab * regLab) * 2 * 20 / 100
|
2337
|
+
|
2338
|
+
# Cost the wiring
|
2339
|
+
vrfWireMat, vrfWireLab = getHVACCost('VRF Wiring', 'wiring', 10, true)
|
2340
|
+
vrfWireLength = 20
|
2341
|
+
vrfWiringCost = ((vrfWireMat*regMatElec + vrfWireLab*regLabElec)/100)*vrfWireLength/100
|
2342
|
+
|
2343
|
+
# Cost the Conduit
|
2344
|
+
vrfConduitMat, vrfConduitLab = getHVACCost('VRF Wiring Conduit', 'Conduit', nil, true)
|
2345
|
+
vrfConduitCost = ((vrfConduitMat*regMatElec + vrfConduitLab*regLabElec)/100)*vrfWireLength
|
2346
|
+
|
2347
|
+
# Cost the disconnect
|
2348
|
+
vrfDiscMat, vrfDiscLab = getHVACCost('VRF Wiring Disconnect', 'Safety_switch', 60, true)
|
2349
|
+
vrfDiscCost = (vrfDiscMat*regMatElec + vrfDiscLab*regLabElec)/100
|
2350
|
+
|
2351
|
+
# Determine the Tubing and wiring between the branch distributors and the condenser unit on the roof. This cost is
|
2352
|
+
# included with the condenser cost since this is for the entire building and only depends on the distance between
|
2353
|
+
# the height difference between the lowest space served by a VRF system and the roof center height. It will be the
|
2354
|
+
# same cost even if there are several condensers because of height restrictions).
|
2355
|
+
#
|
2356
|
+
# Get the refrigerant tubing cost. Exterior refrigerant tubing is given in 10' lengths of 0.5" supply and 1-1/8"
|
2357
|
+
# return tubing. The tubing is assumed to run inside the building so no insulation or all-weather protection is
|
2358
|
+
# provided.
|
2359
|
+
buildRefrigTubingMat, buildRefrigTubingLab = getHVACCost("VRF Building Refrigerant Tubing", 'refrig-tubing-large', 10, true)
|
2360
|
+
# Get distance between lowest floor served by the VRF system and the roof in feet
|
2361
|
+
maxHeightDiffFt = OpenStudio.convert(maxHeightDiff, 'm', 'ft').get
|
2362
|
+
# Tubing cost divided by ten because tubing costing is provided in 10 ft rolls
|
2363
|
+
buildRefrigCost = ((buildRefrigTubingMat * regMat + buildRefrigTubingLab * regLab) / 100) * maxHeightDiffFt / 10.0
|
2364
|
+
|
2365
|
+
# Get the cost of condensate tubing for the whole building. A different height is used than the that for
|
2366
|
+
# refrigerant tubing since the condensate line must extend from the height of the roof (where the condenser is) to
|
2367
|
+
# the ground floor (if maxHeightDiff does not extend the entire building height) or basement ()if maxHeightDiff
|
2368
|
+
# includes a basement).
|
2369
|
+
buildCondMat, buildCondLab = getHVACCost('Building Condensate pipe', 'PEX_tubing', 0.5, true)
|
2370
|
+
buildCondCost = ((buildCondMat * regMat + buildCondLab * regLab) / 100) * maxHeightDiffFt
|
2371
|
+
|
2372
|
+
# Get the wiring cost (note wiring comes in 100 ft lengths)
|
2373
|
+
buildWiringMat, buildWiringLab = getHVACCost('VRF Wiring', 'wiring', 10, true)
|
2374
|
+
buildWiringCost = ((buildWiringMat * regMatElec + buildWiringLab * regLabElec) / 100) * maxHeightDiffFt / 100.0
|
2375
|
+
|
2376
|
+
# Get the conduit cost
|
2377
|
+
buildConduitMat, buildConduitLab = getHVACCost("VRF Building Conduit", 'Conduit', nil, true)
|
2378
|
+
buildConduitCost = ((buildConduitMat * regMatElec + buildConduitLab * regLabElec) / 100) * maxHeightDiffFt
|
2379
|
+
|
2380
|
+
# Find totals
|
2381
|
+
totalVRFCondCost = vrfCondCost * numVRFheight
|
2382
|
+
totalVRFPipingCost = (refrigPipingCost + refrigInsulationCost) * numVRFheight * vrfSizeMult + buildRefrigCost + buildCondCost
|
2383
|
+
totalVRFWiringCost = (vrfWiringCost + vrfConduitCost + vrfDiscCost) * numVRFheight * vrfSizeMult + buildWiringCost + buildConduitCost
|
2384
|
+
totalVRFEquipCost = totalVRFCondCost + totalVRFPipingCost + totalVRFWiringCost
|
2385
|
+
|
2386
|
+
|
2387
|
+
# Add to VRF Condenser cost report. I was not sure where to put this since it was really neither a plant unit or a
|
2388
|
+
# zonal unit. I guess it supplies several zones so that makes it plant equipment.
|
2389
|
+
@costing_report['heating_and_cooling']['plant_equipment'] << {
|
2390
|
+
'type' => 'VRF Zonal System Condenser',
|
2391
|
+
'nom_flr2flr_hght_ft' => 0.0,
|
2392
|
+
'ht_roof_ft' => maxHeightDiffFt.round(1),
|
2393
|
+
'longest_distance_to_ext_ft' => 0.0,
|
2394
|
+
'wiring_and_gas_connections_distance_ft' => (vrfWireLength*numVRFheight*vrfSizeMult + maxHeightDiffFt).round(1),
|
2395
|
+
'equipment_cost' => totalVRFCondCost.round(0),
|
2396
|
+
'wiring_and_gas_connections_cost' => totalVRFWiringCost.round(0),
|
2397
|
+
'pump_cost' => 0.00,
|
2398
|
+
'piping_cost' => totalVRFPipingCost.round(0),
|
2399
|
+
'total_cost' => totalVRFEquipCost.round(0)
|
2400
|
+
}
|
2401
|
+
|
2402
|
+
return totalVRFEquipCost
|
2403
|
+
end
|
2404
|
+
|
2405
|
+
# This method was originally part of getHVACCOST but was split out because in some cases the information from the
|
2406
|
+
# materials_hvac sheet of the costing spreadsheet was required but not tho cost.
|
2407
|
+
# The method takes in:
|
2408
|
+
# name(String): The name of a piece of equipment. Is only used for error reporting and is not linked to anything
|
2409
|
+
# else.
|
2410
|
+
# materialLookup(String): The material type used to search hte 'Material' column of the materials_hvac sheet of the
|
2411
|
+
# costing spreadsheet.
|
2412
|
+
# materialSize(float): The size of the equipment in whichever units are required when searching the 'Size' column of
|
2413
|
+
# the costing spreadsheet.
|
2414
|
+
# exactMatch(true/false): A flag to indicate if the hvac equipment must match the size provided exactly or if the
|
2415
|
+
# size is a minimum equipment size.
|
2416
|
+
#
|
2417
|
+
# The method returns a hash with the following composition:
|
2418
|
+
# {
|
2419
|
+
# name(string): Same as above.
|
2420
|
+
# hvac_material(hash): The costing spreadsheet information for the hvac equipment being searched for.
|
2421
|
+
# multiplier(float): Default is 1. Will be higher if exactMatch is false, and no materialLookup could be found with
|
2422
|
+
# a large enough materialSize in the costing spreadsheet. In this case, it is assumed that several
|
2423
|
+
# pieced of equipment defined by hvact_material are used to satisfy the required materialSize.
|
2424
|
+
# The multiplier defines the number of hvac_material required to meet the materialSize
|
2425
|
+
# }
|
2426
|
+
def getHVACMultiSizeDBInfo(name:, materialLookup:, materialCap:, materialCon:)
|
2427
|
+
multiplier = 1.0
|
2428
|
+
numConLoops = 1.0
|
2429
|
+
# Get the materials_hvac sheet info from the costing spreadsheet
|
2430
|
+
materials_hvac = @costing_database["raw"]["materials_hvac"]
|
2431
|
+
# Find material that meet the materialCon requirement
|
2432
|
+
hvac_material_con_info = materials_hvac.select {|data|
|
2433
|
+
data['Material'].to_s.upcase == materialLookup.to_s.upcase && data['Fuel'].to_f >= materialCon
|
2434
|
+
}
|
2435
|
+
hvac_material = []
|
2436
|
+
unless hvac_material_con_info.empty?
|
2437
|
+
# If the materialCon criteria check if any of the selected material meet the mateterialCap criteria
|
2438
|
+
hvac_material_cap_info = hvac_material_con_info.select {|data| data['Size'].to_f >= materialCap}
|
2439
|
+
if hvac_material_cap_info.nil? || hvac_material_cap_info.empty?
|
2440
|
+
# If none do then select the material with the largest capacity that met the materialCon criteria
|
2441
|
+
hvac_material << hvac_material_con_info.max_by {|data| data['Size'].to_f}
|
2442
|
+
con_per_loop = hvac_material[0]['Fuel'].to_f
|
2443
|
+
else
|
2444
|
+
# If something met both the materialCon and materialCap then return a hash containing the material and other
|
2445
|
+
# information and we are done.
|
2446
|
+
hvac_material = hvac_material_cap_info.min_by {|data| data['Size'].to_f}
|
2447
|
+
ret_hash = {
|
2448
|
+
name: name,
|
2449
|
+
hvac_material: hvac_material,
|
2450
|
+
multiplier: 1
|
2451
|
+
}
|
2452
|
+
return ret_hash
|
2453
|
+
end
|
2454
|
+
end
|
2455
|
+
if hvac_material.empty?
|
2456
|
+
# If no equipment met the materialCon criteria then find all with the material type we want
|
2457
|
+
hvac_material_info = materials_hvac.select {|data| data['Material'].to_s.upcase == materialLookup.to_s.upcase}
|
2458
|
+
# If you cannot find even the material type then something has gone very wrong. Stop everything and tell the user.
|
2459
|
+
raise "HVAC material error! Could not find next largest size for #{name} in the materials_hvac sheet of the costing spreadsheet of #{materialLookup} type." if hvac_material_info.empty?
|
2460
|
+
# Find the equipment with the largest 'Fuel' (this is what defines the materialCon options for this material type)
|
2461
|
+
hvac_material = hvac_material_info.max_by{|data| data['Fuel'].to_f}
|
2462
|
+
# Find the number of pieces of equipment will be needed to meet the materialCon requirement
|
2463
|
+
(((materialCon.to_f) % (hvac_material['Fuel'].to_f)).round(3) > 0.0) ? numConLoops = ((materialCon.to_f/(hvac_material['Fuel'].to_f)).to_i + 1).to_f.round(0) : numConLoops = (materialCon.to_f/(hvac_material['Fuel'].to_f)).round(0)
|
2464
|
+
# Revise the materialCon requirement now that several pieces of equipment are being used
|
2465
|
+
con_per_loop = materialCon / numConLoops
|
2466
|
+
# Find all the appropriate equipment in the costing spreadsheet that meet the revised materialCon requirement
|
2467
|
+
hvac_material = hvac_material_info.select{|data| data['Fuel'].to_f >= con_per_loop}
|
2468
|
+
end
|
2469
|
+
|
2470
|
+
# Now that we have some equipment that meet the required materialCon requirement revise the materialCap requirement
|
2471
|
+
# in case multiple pieces of equipment were to meet the materialCon requirement.
|
2472
|
+
reqMatSize = materialCap/numConLoops
|
2473
|
+
# Of the equipment that met the (modified or original) materialCan requirement select the equipment the meets the
|
2474
|
+
# (modified or original) capacity requirement.
|
2475
|
+
material_cap = hvac_material.select{|data| data['Size'].to_f >= reqMatSize}
|
2476
|
+
if material_cap.empty?
|
2477
|
+
# If none of the selected materials meet the materialCap requirement find the one with the largest capacity
|
2478
|
+
largestMat = hvac_material.max_by{|data| data['Size'].to_f}
|
2479
|
+
maxAvailCap = largestMat['Size'].to_f
|
2480
|
+
# Find out how many are required to meet the materialCap requirement
|
2481
|
+
(reqMatSize%maxAvailCap).to_f.round(3) > 0 ? numCapLoops = (((reqMatSize/maxAvailCap).to_i) + 1).to_f.round(0) : numCapLoops = (reqMatSize/maxAvailCap).to_f.round(0)
|
2482
|
+
# Calculate how many pieces of equipment are now required to meet both the materialCon and materialCap
|
2483
|
+
# requirements
|
2484
|
+
totLoops = numConLoops*numCapLoops
|
2485
|
+
# Revise the materialCap and materialCon requirements to reflect that even more pieces of equipment will be used
|
2486
|
+
# to meet both requirements
|
2487
|
+
modMatCap = (materialCap / totLoops).to_f
|
2488
|
+
modMatCon = (materialCon / totLoops).to_f
|
2489
|
+
# Search for equipment that meet both the modMatCap and modMatCon criteria
|
2490
|
+
hvac_material_info = materials_hvac.select {|data| data['Material'].to_s.upcase == materialLookup.to_s.upcase}
|
2491
|
+
material_cap = hvac_material_info.select{|data| (data['Size'].to_f >= modMatCap) && (data['Fuel'].to_f >= modMatCon)}
|
2492
|
+
if material_cap.empty?
|
2493
|
+
# It should have gotten something. If it didn't then select the one with largest capacity and use that.
|
2494
|
+
ret_mat = largestMat
|
2495
|
+
else
|
2496
|
+
# Find the equipment with the smallest capacity that meets the requirement
|
2497
|
+
ret_mat = material_cap.min_by{|data| data['Size'].to_f}
|
2498
|
+
end
|
2499
|
+
else
|
2500
|
+
# If something now meets the materialCon and materialCap requirement select the one with the smallest capacity.
|
2501
|
+
totLoops = numConLoops
|
2502
|
+
ret_mat = material_cap.min_by{|data| data['Size'].to_f}
|
2503
|
+
end
|
2504
|
+
# If multiple branch distributors are required check if the last one can be smaller than the others and return that
|
2505
|
+
# in addition to the other branch distributors.
|
2506
|
+
if totLoops.round(0) > 1.0
|
2507
|
+
# Check check the remaining size requirements for the last branch distributor
|
2508
|
+
(materialCon - (totLoops - 1.0)*(ret_mat['Fuel'].to_f)) > 0 ? redCon = (materialCon - (totLoops - 1.0)*(ret_mat['Fuel'].to_f)) : redCon = 0.0
|
2509
|
+
(materialCap - (totLoops - 1.0)*(ret_mat['Size'].to_f)) > 0 ? redCap = (materialCap - (totLoops - 1.0)*(ret_mat['Size'].to_f)) : redCap = 0.0
|
2510
|
+
# If either are greater than zero (as should be the case) then look for equipment that can meet the remaining
|
2511
|
+
# connection or capacity requipments.
|
2512
|
+
if (redCon > 0) || (redCap > 0)
|
2513
|
+
# Find material that meet the remaining connection and capacity requirements.
|
2514
|
+
hvac_material_red = materials_hvac.select {|data|
|
2515
|
+
data['Material'].to_s.upcase == materialLookup.to_s.upcase && data['Fuel'].to_f >= redCon && data['Size'].to_f >= redCap
|
2516
|
+
}
|
2517
|
+
if hvac_material_red.size == 0
|
2518
|
+
# If no equipment could be found which meet the remaining connection and capacity requirements then return
|
2519
|
+
# the the number and type of equipment without adjust for a smaller final piece of equipment.
|
2520
|
+
red_ret_hash = nil
|
2521
|
+
else
|
2522
|
+
# If equipment could be found then select the one with the minimum number of connections.
|
2523
|
+
red_ret = hvac_material_red.min_by{|data| data['Fuel'].to_f}
|
2524
|
+
min_hvac_sel = hvac_material_red.select{|data| data['Fuel'].to_f == red_ret['Fuel'].to_f}
|
2525
|
+
# If more than one piece of equipment can meet the minimum connection requirement then select the one with the
|
2526
|
+
# minimum capacity requirement.
|
2527
|
+
if min_hvac_sel.size > 1
|
2528
|
+
red_ret = min_hvac_sel.min_by{|data| data['Size'].to_f}
|
2529
|
+
end
|
2530
|
+
red_ret_hash = {
|
2531
|
+
red_ret_hash: red_ret,
|
2532
|
+
numCon: redCon,
|
2533
|
+
numCap: redCap
|
2534
|
+
}
|
2535
|
+
end
|
2536
|
+
end
|
2537
|
+
end
|
2538
|
+
# If multiple pieces of equipment are required to meet the connection and capacity requirements check if a smaller
|
2539
|
+
# piece of equipment was found to get the final remaining requirements. If one is found then adjust the multiplier
|
2540
|
+
# for the main equipment to be reduced by one and return the smaller remaining piece of equipment.
|
2541
|
+
red_ret_hash.nil? ? retLoops = totLoops : retLoops = totLoops - 1.0
|
2542
|
+
# Create a hash with the results and return it.
|
2543
|
+
ret_hash = {
|
2544
|
+
name: name,
|
2545
|
+
hvac_material: ret_mat,
|
2546
|
+
multiplier: retLoops
|
2547
|
+
}
|
2548
|
+
return ret_hash, red_ret_hash
|
2549
|
+
end
|
2550
|
+
|
2551
|
+
# This method is for the calculation of VSD chiller cost
|
2552
|
+
def vsd_chiller_cost(primaryCap:)
|
2553
|
+
# Gather a list of VSD chillers that exist in the costing spreadsheet
|
2554
|
+
vsd_chiller_sizes = []
|
2555
|
+
vsd_chiller_options = @costing_database['raw']['materials_hvac'].select {|data|
|
2556
|
+
data['Material'].to_s.upcase == 'ChillerElectricEIR_VSDCentrifugalWaterChiller'.upcase
|
2557
|
+
}
|
2558
|
+
vsd_chiller_options[0..-1].each do |a,b|
|
2559
|
+
a.each do |key,value|
|
2560
|
+
vsd_chiller_sizes << value.to_f if key=='Size'
|
2561
|
+
end
|
2562
|
+
end
|
2563
|
+
|
2564
|
+
# Look for a VSD in the list of VSDs that has the closest size, and calculate its cost
|
2565
|
+
vsd_chiller_closet_to_current_kw = vsd_chiller_sizes.sort_by { |item| (primaryCap-item).abs }.first(1)
|
2566
|
+
quantity_chiller_electric_eir = 1.0
|
2567
|
+
search_chiller_electric_eir = {
|
2568
|
+
row_id_1: 'ChillerElectricEIR_VSDCentrifugalWaterChiller',
|
2569
|
+
row_id_2: vsd_chiller_closet_to_current_kw[0].to_s
|
2570
|
+
}
|
2571
|
+
sheet_name = 'materials_hvac'
|
2572
|
+
column_1 = 'Material'
|
2573
|
+
column_2 = 'Size'
|
2574
|
+
tags = ['heating_and_cooling','plant_equipment','chiller']
|
2575
|
+
thisChillerCost = assembly_cost(cost_info:search_chiller_electric_eir,
|
2576
|
+
sheet_name:sheet_name,
|
2577
|
+
column_1:column_1,
|
2578
|
+
column_2:column_2,
|
2579
|
+
quantity:quantity_chiller_electric_eir,
|
2580
|
+
tags: tags)
|
2581
|
+
# puts "thisVSDChillerCost is #{thisChillerCost}"
|
2582
|
+
return thisChillerCost
|
2583
|
+
end
|
2584
|
+
end
|