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,2616 @@
|
|
1
|
+
class BTAPCosting
|
2
|
+
def ventilation_costing(model, prototype_creator, template_type, mech_room, cond_spaces)
|
3
|
+
# Set up reporting hash
|
4
|
+
@costing_report['ventilation'] = {system_1: [], system_2: [], system_3: [], system_4: [], system_5: [], system_6: [], system_7: [], mech_to_roof: [], trunk_duct: [], floor_trunk_ducts: [], tz_distribution: [], hrv_return_ducting: [], natural_ventilation: [], demand_controlled_ventilation: []}
|
5
|
+
# Get mechanical sizing for costing information from mech_sizing.json
|
6
|
+
mech_sizing_info = read_mech_sizing()
|
7
|
+
# Find the mechanical room in the model and conditioned spaces - moved to btap_costing.rb
|
8
|
+
# mech_room, cond_spaces = prototype_creator.find_mech_room(model)
|
9
|
+
# Find the center of the highest roof in the model (this will be surrounded by roof top mechancial equipment and is where utility lines will be sent)
|
10
|
+
roof_cent = prototype_creator.find_highest_roof_centre(model)
|
11
|
+
# Find the lowest space in the building (trunk duct runs from here to the highest space).
|
12
|
+
min_space = get_lowest_space(spaces: cond_spaces)
|
13
|
+
vent_cost = 0
|
14
|
+
# Start ventilation costing
|
15
|
+
vent_cost += ahu_costing(model: model, prototype_creator: prototype_creator, template_type: template_type, mech_room: mech_room, roof_cent: roof_cent, mech_sizing_info: mech_sizing_info, min_space: min_space)
|
16
|
+
# natural ventilation costing
|
17
|
+
nv_total_cost = cost_audit_nv(model: model, prototype_creator: prototype_creator)
|
18
|
+
# demand-controlled ventilation costing
|
19
|
+
dcv_cost_total = cost_audit_dcv(model: model, prototype_creator: prototype_creator)
|
20
|
+
# total ventilation cost
|
21
|
+
vent_cost += nv_total_cost + dcv_cost_total
|
22
|
+
return vent_cost
|
23
|
+
end
|
24
|
+
|
25
|
+
def ahu_costing(model:, prototype_creator:, template_type:, mech_room:, roof_cent:, mech_sizing_info:, min_space:)
|
26
|
+
ahu_cost = 0
|
27
|
+
hrv_total_cost = 0
|
28
|
+
heat_type = {
|
29
|
+
'HP' => 0,
|
30
|
+
'elec' => 0,
|
31
|
+
'Gas' => 0,
|
32
|
+
'HW' => 0,
|
33
|
+
}
|
34
|
+
cool_type = {
|
35
|
+
'DX' => 0,
|
36
|
+
'CHW' => 0,
|
37
|
+
}
|
38
|
+
|
39
|
+
rt_unit_num = 0
|
40
|
+
total_vent_flow_m3_per_s = 0
|
41
|
+
sys_1_4 = true
|
42
|
+
hvac_floors = []
|
43
|
+
# Go through each air loop in the model and cost it
|
44
|
+
model.getAirLoopHVACs.sort.each do |airloop|
|
45
|
+
@airloop_info = nil
|
46
|
+
airloop_name = airloop.nameString
|
47
|
+
# Look for the system type from the name of the air loop
|
48
|
+
sys_name_loc = airloop_name.to_s.upcase.index("SYS_")
|
49
|
+
if sys_name_loc.nil?
|
50
|
+
puts "The name of airloop #{airloop_name} does not start with a valid NECB system type described as \"Sys_\" and then an NECB system number."
|
51
|
+
puts "Please rename the airloop appropriately or do not cost the ventilation system until ventilation costing can handle non-NECB ventilation systems."
|
52
|
+
next
|
53
|
+
else
|
54
|
+
sys_type = airloop_name[(sys_name_loc+4)].to_i
|
55
|
+
sys_type_real = sys_type
|
56
|
+
# For costing, treat system types 1 and 4 the same (treat both as system 1)
|
57
|
+
sys_type = 1 if sys_type == 4
|
58
|
+
next if sys_type == 2
|
59
|
+
end
|
60
|
+
ahu_tags = [
|
61
|
+
"ventilation",
|
62
|
+
airloop_name,
|
63
|
+
"system #{sys_type_real}"
|
64
|
+
]
|
65
|
+
rt_unit_num += 1
|
66
|
+
|
67
|
+
@airloop_info = {sys_type: sys_type}
|
68
|
+
@airloop_info[:name] = airloop_name
|
69
|
+
|
70
|
+
# Get the air loop supply airflow rate (used for sizing the ahu for costing)
|
71
|
+
if airloop.isDesignSupplyAirFlowRateAutosized
|
72
|
+
airloop_flow_m3_per_s = airloop.autosizedDesignSupplyAirFlowRate.to_f
|
73
|
+
else
|
74
|
+
airloop_flow_m3_per_s = airloop.designSupplyAirFlowRate.to_f
|
75
|
+
end
|
76
|
+
airloop_flow_cfm = (OpenStudio.convert(airloop_flow_m3_per_s, 'm^3/s', 'cfm').get)
|
77
|
+
airloop_flow_lps = (OpenStudio.convert(airloop_flow_m3_per_s, 'm^3/s', 'L/s').get)
|
78
|
+
total_vent_flow_m3_per_s += airloop_flow_m3_per_s
|
79
|
+
# Set up hash to record heating and cooling capacities. If more than one heating or cooling source is present this will be used to determine which is predominant one since ahu costing is done based on one heating fuel and cooling type
|
80
|
+
heat_cap = {
|
81
|
+
'HP' => 0,
|
82
|
+
'elec' => 0,
|
83
|
+
'Gas' => 0,
|
84
|
+
'HW' => 0,
|
85
|
+
'CCASHP' => 0
|
86
|
+
}
|
87
|
+
cool_cap = {
|
88
|
+
'DX' => 0,
|
89
|
+
'CHW' => 0,
|
90
|
+
}
|
91
|
+
@airloop_info[:airloop_flow_m3_per_s] = airloop_flow_m3_per_s.round(3)
|
92
|
+
total_heat_cool_cost = 0
|
93
|
+
airloop_equipment = []
|
94
|
+
#@airloop_info[:equipment_info] = []
|
95
|
+
# Find HRVs in the air loop so they can be costed if present
|
96
|
+
hrv_info = get_hrv_info(airloop: airloop, model: model)
|
97
|
+
# Sort through all of the supply components in the air loop and collect heating and cooling equipment
|
98
|
+
airloop.supplyComponents.sort.each do |supplycomp|
|
99
|
+
# Get the OS object type of the supply component
|
100
|
+
obj_type = supplycomp.iddObjectType.valueName.to_s
|
101
|
+
mech_capacity = 0
|
102
|
+
heating_fuel = 'none'
|
103
|
+
cooling_type = 'none'
|
104
|
+
adv_dx_clg_eqpt = false
|
105
|
+
cat_search = nil
|
106
|
+
# Based on the object type determine how to handle it.
|
107
|
+
case obj_type
|
108
|
+
# Determine what to do (if anything) with a piece of air loop heating/cooling equipment. Note the comment for the first type applies to the rest.
|
109
|
+
when /OS_Coil_Heating_DX_VariableSpeed/
|
110
|
+
# Get the object and make sure it is cast correctly
|
111
|
+
suppcomp = supplycomp.to_CoilHeatingDXVariableSpeed.get
|
112
|
+
# Determine the size of the object if either autosized or manualy sized
|
113
|
+
if suppcomp.isRatedHeatingCapacityAtSelectedNominalSpeedLevelAutosized
|
114
|
+
mech_capacity = suppcomp.autosizedRatedHeatingCapacityAtSelectedNominalSpeedLevel.to_f/1000.0
|
115
|
+
else
|
116
|
+
mech_capacity = suppcomp.ratedHeatingCapacityAtSelectedNominalSpeedLevel.to_f/1000.0
|
117
|
+
end
|
118
|
+
# Determine from the name if it is a CCASHP
|
119
|
+
if suppcomp.name.to_s.upcase.include?("CCASHP")
|
120
|
+
# Set the heating equipment type (used to determine how to cost the equipment)
|
121
|
+
heating_fuel = 'CCASHP'
|
122
|
+
# Set the term used to search the 'hvac_costing' sheet in the costing spreadsheet to get costing information
|
123
|
+
cat_search = 'coils'
|
124
|
+
# Set the heating capacity (used to determine the predominant heating type for the air loop)
|
125
|
+
heat_cap['CCASHP'] += mech_capacity
|
126
|
+
else
|
127
|
+
heating_fuel = 'HP'
|
128
|
+
cat_search = 'ashp'
|
129
|
+
heat_cap['HP'] += mech_capacity
|
130
|
+
end
|
131
|
+
when /OS_Coil_Heating_DX_SingleSpeed/
|
132
|
+
suppcomp = supplycomp.to_CoilHeatingDXSingleSpeed.get
|
133
|
+
if suppcomp.isRatedTotalHeatingCapacityAutosized
|
134
|
+
mech_capacity = suppcomp.autosizedRatedTotalHeatingCapacity.to_f/1000.0
|
135
|
+
else
|
136
|
+
mech_capacity = suppcomp.ratedTotalHeatingCapacity.to_f/1000.0
|
137
|
+
end
|
138
|
+
if suppcomp.name.to_s.upcase.include?("CCASHP")
|
139
|
+
heating_fuel = 'CCASHP'
|
140
|
+
# There is a separate method which costs additional CCASHP cost information. The 'coils' category is only
|
141
|
+
# one of the pieces of equipment that goes into CCASHP costing.
|
142
|
+
cat_search = 'coils'
|
143
|
+
heat_cap['CCASHP'] += mech_capacity
|
144
|
+
else
|
145
|
+
heating_fuel = 'HP'
|
146
|
+
cat_search = 'ashp'
|
147
|
+
heat_cap['HP'] += mech_capacity
|
148
|
+
end
|
149
|
+
when 'OS_Coil_Heating_Electric'
|
150
|
+
heating_fuel = 'elec'
|
151
|
+
suppcomp = supplycomp.to_CoilHeatingElectric.get
|
152
|
+
if suppcomp.isNominalCapacityAutosized
|
153
|
+
mech_capacity = suppcomp.autosizedNominalCapacity.to_f/1000.0
|
154
|
+
else
|
155
|
+
mech_capacity = suppcomp.nominalCapacity.to_f/1000.0
|
156
|
+
end
|
157
|
+
cat_search = 'elecheat'
|
158
|
+
heat_cap['elec'] += mech_capacity
|
159
|
+
when /OS_Coil_Heating_Gas/
|
160
|
+
heating_fuel = 'Gas'
|
161
|
+
suppcomp = supplycomp.to_CoilHeatingGas.get
|
162
|
+
if suppcomp.isNominalCapacityAutosized
|
163
|
+
mech_capacity = suppcomp.autosizedNominalCapacity.to_f/1000.0
|
164
|
+
else
|
165
|
+
mech_capacity = suppcomp.nominalCapacity.to_f/1000.0
|
166
|
+
end
|
167
|
+
cat_search = 'FurnaceGas'
|
168
|
+
heat_cap['Gas'] += mech_capacity
|
169
|
+
when /OS_Coil_Heating_Water/
|
170
|
+
heating_fuel = 'HW'
|
171
|
+
suppcomp = supplycomp.to_CoilHeatingWater.get
|
172
|
+
if suppcomp.isRatedCapacityAutosized
|
173
|
+
mech_capacity = suppcomp.autosizedRatedCapacity.to_f/1000.0
|
174
|
+
else
|
175
|
+
suppcomp.ratedCapacity.to_f/1000.0
|
176
|
+
end
|
177
|
+
cat_search = 'coils'
|
178
|
+
heat_cap['HW'] += mech_capacity
|
179
|
+
when /OS_Coil_Cooling_DX_SingleSpeed/
|
180
|
+
suppcomp = supplycomp.to_CoilCoolingDXSingleSpeed.get
|
181
|
+
if suppcomp.isRatedTotalCoolingCapacityAutosized
|
182
|
+
mech_capacity = suppcomp.autosizedRatedTotalCoolingCapacity.to_f/1000.0
|
183
|
+
else
|
184
|
+
mech_capacity = suppcomp.ratedTotalCoolingCapacity.to_f/1000.0
|
185
|
+
end
|
186
|
+
if suppcomp.name.to_s.upcase.include?('DX-ADV')
|
187
|
+
cooling_type = 'DX-adv'
|
188
|
+
cat_search = 'coils'
|
189
|
+
cool_cap['DX'] += mech_capacity
|
190
|
+
else
|
191
|
+
cooling_type = 'DX'
|
192
|
+
cat_search = 'coils'
|
193
|
+
cool_cap['DX'] += mech_capacity
|
194
|
+
end
|
195
|
+
when /OS_Coil_Cooling_DX_VariableSpeed/
|
196
|
+
suppcomp = supplycomp.to_CoilCoolingDXVariableSpeed.get
|
197
|
+
if suppcomp.isGrossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevelAutosized
|
198
|
+
mech_capacity = suppcomp.autosizedGrossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevel.to_f/1000.0
|
199
|
+
else
|
200
|
+
mech_capacity = suppcomp.grossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevel.to_f/1000.0
|
201
|
+
end
|
202
|
+
if suppcomp.name.to_s.upcase.include?('DX-ADV')
|
203
|
+
cooling_type = 'DX-adv'
|
204
|
+
cat_search = 'coils'
|
205
|
+
cool_cap['DX'] += mech_capacity
|
206
|
+
else
|
207
|
+
cooling_type = 'DX'
|
208
|
+
cat_search = 'coils'
|
209
|
+
cool_cap['DX'] += mech_capacity
|
210
|
+
end
|
211
|
+
when /Coil_Cooling_Water/
|
212
|
+
cooling_type = 'CHW'
|
213
|
+
suppcomp = supplycomp.to_CoilCoolingWater.get
|
214
|
+
mech_capacity = suppcomp.autosizedDesignCoilLoad.to_f/1000.0
|
215
|
+
cat_search = 'coils'
|
216
|
+
cool_cap['CHW'] += mech_capacity
|
217
|
+
when /OS_AirLoopHVAC_UnitaryHeatPump_AirToAir/
|
218
|
+
suppcomp = supplycomp.to_AirLoopHVACUnitaryHeatPumpAirToAir.get
|
219
|
+
htg_coil = suppcomp.heatingCoil
|
220
|
+
if htg_coil.to_CoilHeatingDXSingleSpeed.is_initialized
|
221
|
+
htg_coil = htg_coil.to_CoilHeatingDXSingleSpeed.get
|
222
|
+
if htg_coil.isRatedTotalHeatingCapacityAutosized
|
223
|
+
mech_capacity = htg_coil.autosizedRatedTotalHeatingCapacity.to_f/1000.0
|
224
|
+
else
|
225
|
+
mech_capacity = htg_coil.ratedTotalHeatingCapacity.to_f/1000.0
|
226
|
+
end
|
227
|
+
heating_fuel = 'HP'
|
228
|
+
cat_search = 'ashp'
|
229
|
+
heat_cap['HP'] += mech_capacity
|
230
|
+
end
|
231
|
+
clg_coil = suppcomp.coolingCoil
|
232
|
+
if clg_coil.to_CoilCoolingDXSingleSpeed.is_initialized
|
233
|
+
clg_coil = clg_coil.to_CoilCoolingDXSingleSpeed.get
|
234
|
+
if clg_coil.isRatedTotalCoolingCapacityAutosized
|
235
|
+
mech_capacity = clg_coil.autosizedRatedTotalCoolingCapacity.to_f/1000.0
|
236
|
+
else
|
237
|
+
mech_capacity = clg_coil.ratedTotalCoolingCapacity.to_f/1000.0
|
238
|
+
end
|
239
|
+
cooling_type = 'DX'
|
240
|
+
cat_search = 'coils'
|
241
|
+
cool_cap['DX'] += mech_capacity
|
242
|
+
end
|
243
|
+
supp_htg_coil = suppcomp.supplementalHeatingCoil
|
244
|
+
if supp_htg_coil.to_CoilHeatingElectric.is_initialized
|
245
|
+
supp_htg_coil = supp_htg_coil.to_CoilHeatingElectric.get
|
246
|
+
elsif supp_htg_coil.to_CoilHeatingGas.is_initialized
|
247
|
+
supp_htg_coil = supp_htg_coil.to_CoilHeatingGas.get
|
248
|
+
end
|
249
|
+
if supp_htg_coil.isNominalCapacityAutosized
|
250
|
+
mech_capacity = supp_htg_coil.autosizedNominalCapacity.to_f/1000.0
|
251
|
+
else
|
252
|
+
mech_capacity = supp_htg_coil.nominalCapacity.to_f/1000.0
|
253
|
+
end
|
254
|
+
if supp_htg_coil.class.name.include? 'CoilHeatingElectric'
|
255
|
+
cat_search = 'elecheat'
|
256
|
+
heat_cap['elec'] += mech_capacity
|
257
|
+
elsif supp_htg_coil.class.name.include? 'CoilHeatingGas'
|
258
|
+
cat_search = 'FurnaceGas'
|
259
|
+
heat_cap['Gas'] += mech_capacity
|
260
|
+
end
|
261
|
+
end
|
262
|
+
# This hash contains all of the pertinent information required for costing a piece of air loop heating/cooling equipment
|
263
|
+
equipment_info = {
|
264
|
+
sys_type: sys_type,
|
265
|
+
obj_type: obj_type,
|
266
|
+
supply_comp: supplycomp,
|
267
|
+
heating_fuel: heating_fuel,
|
268
|
+
cooling_type: cooling_type,
|
269
|
+
adv_dx_clg_eqpt: adv_dx_clg_eqpt,
|
270
|
+
mech_capacity_kw: mech_capacity,
|
271
|
+
cat_search: cat_search
|
272
|
+
}
|
273
|
+
unless equipment_info[:mech_capacity_kw].to_f <= 0
|
274
|
+
# Add the piece of air loop equipment to an array for costing if the equipment does something (that is has a size larger than 0)
|
275
|
+
airloop_equipment << equipment_info
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# Determine the predominant heating and cooling fuel type.
|
280
|
+
ahu_heat_cool_info = determine_ahu_htg_clg_fuel(heat_cap: heat_cap, cool_cap: cool_cap, heat_type: heat_type, cool_type: cool_type)
|
281
|
+
heat_type = ahu_heat_cool_info[:heat_type]
|
282
|
+
cool_type = ahu_heat_cool_info[:cool_type]
|
283
|
+
# Cost rooftop ventilation unit.
|
284
|
+
costed_ahu_info = cost_ahu(sys_type: sys_type, airloop_flow_lps: airloop_flow_lps, airloop_flow_cfm: airloop_flow_cfm, mech_sizing_info: mech_sizing_info, heating_fuel: ahu_heat_cool_info[:heating_fuel], cooling_type: ahu_heat_cool_info[:cooling_type], airloop_name: airloop_name, vent_tags: ahu_tags)
|
285
|
+
# Get ventilation heating and cooling equipment costs.
|
286
|
+
air_loop_equip_return_info = airloop_equipment_costing(airloop_equipment: airloop_equipment, ahu_mult: costed_ahu_info[:mult].to_f, vent_tags: ahu_tags)
|
287
|
+
# Get the air loop equipment reporting information from the air loop equipment costing method return hash
|
288
|
+
al_eq_reporting_info = air_loop_equip_return_info[:al_eq_reporting_info]
|
289
|
+
# Add the air loop equipment costing to the total air loop cost
|
290
|
+
total_heat_cool_cost += air_loop_equip_return_info[:heat_cool_cost]
|
291
|
+
|
292
|
+
# Determine information about thermal zones supplied by this air loop and sort it by building floor
|
293
|
+
hvac_floors = gen_hvac_info_by_floor(hvac_floors: hvac_floors, model: model, prototype_creator: prototype_creator, airloop: airloop, sys_type: sys_type, hrv_info: hrv_info)
|
294
|
+
sys_1_4 = false unless (sys_type == 1 || sys_type == 4)
|
295
|
+
|
296
|
+
reheat_cost, reheat_array = reheat_recool_cost(airloop: airloop, prototype_creator: prototype_creator, model: model, roof_cent: roof_cent, mech_sizing_info: mech_sizing_info, vent_tags: ahu_tags, report_mult: 1.0)
|
297
|
+
|
298
|
+
if hrv_info[:hrv_present]
|
299
|
+
hrv_rep = hrv_cost(hrv_info: hrv_info, airloop: airloop, vent_tags: ahu_tags, report_mult: 1.0)
|
300
|
+
hrv_total_cost += hrv_rep[:revised_hrv_cost].to_f
|
301
|
+
else
|
302
|
+
hrv_rep = {}
|
303
|
+
end
|
304
|
+
|
305
|
+
@airloop_info[:hrv] = hrv_rep
|
306
|
+
ahu_cost += costed_ahu_info[:adjusted_base_ahu_cost] + reheat_cost + total_heat_cool_cost
|
307
|
+
@airloop_info[:equipment_info] = al_eq_reporting_info
|
308
|
+
@airloop_info[:reheat_recool] = reheat_array
|
309
|
+
@costing_report['ventilation'].each {|key, value| value << @airloop_info if key.to_s == ('system_' + sys_type.to_s)}
|
310
|
+
end
|
311
|
+
if total_vent_flow_m3_per_s == 0 || total_vent_flow_m3_per_s.nil?
|
312
|
+
puts "No ventilation system is present which can currently be costed."
|
313
|
+
@costing_report['ventilation'] = {
|
314
|
+
error: "No ventilation system is present which can currently be costed."
|
315
|
+
}
|
316
|
+
return 0
|
317
|
+
end
|
318
|
+
@costing_report['ventilation'][:hrv_total_cost] = hrv_total_cost.round(2)
|
319
|
+
mech_roof_cost, mech_roof_rep = mech_to_roof_cost(heat_type: heat_type, cool_type: cool_type, mech_room: mech_room, roof_cent: roof_cent, rt_unit_num: rt_unit_num)
|
320
|
+
@costing_report['ventilation'][:mech_to_roof] = mech_roof_rep
|
321
|
+
trunk_duct_cost, trunk_duct_info = vent_trunk_duct_cost(tot_air_m3pers: total_vent_flow_m3_per_s, min_space: min_space, roof_cent: roof_cent, mech_sizing_info: mech_sizing_info, sys_1_4: sys_1_4)
|
322
|
+
@costing_report['ventilation'][:trunk_duct] << trunk_duct_info
|
323
|
+
floor_dist_cost, build_floor_trunk_info = floor_vent_dist_cost(hvac_floors: hvac_floors, prototype_creator: prototype_creator, roof_cent: roof_cent, mech_sizing_info: mech_sizing_info)
|
324
|
+
@costing_report['ventilation'][:floor_trunk_ducts] << build_floor_trunk_info
|
325
|
+
tz_dist_cost, duct_dist_rep = tz_vent_dist_cost(hvac_floors: hvac_floors, mech_sizing_info: mech_sizing_info)
|
326
|
+
@costing_report['ventilation'][:tz_distribution] << duct_dist_rep
|
327
|
+
hrv_ducting_cost, hrv_ret_duct_report = hrv_duct_cost(prototype_creator: prototype_creator, roof_cent: roof_cent, mech_sizing_info: mech_sizing_info, hvac_floors: hvac_floors)
|
328
|
+
@costing_report['ventilation'][:hrv_return_ducting] = hrv_ret_duct_report
|
329
|
+
ahu_cost += tz_dist_cost + trunk_duct_cost + floor_dist_cost + hrv_ducting_cost + hrv_total_cost + mech_roof_cost
|
330
|
+
return ahu_cost.round(2)
|
331
|
+
end
|
332
|
+
|
333
|
+
# This method determines the main heating fuel and cooling type used by an air handling unit (a given model's air
|
334
|
+
# loop). The method also determines the ahu's supplementary heating type (if any) if the primary heater is a heat
|
335
|
+
# pump. All capacities are in KW.
|
336
|
+
#
|
337
|
+
# Inputs:
|
338
|
+
#
|
339
|
+
# heat_cap: The capacity of heaters in the supply side of the air loop. This is used to determine the main heating
|
340
|
+
# type used by the ahu. They can be the following types:
|
341
|
+
# HP (Heat Pump)
|
342
|
+
# elec (Electricity)
|
343
|
+
# Gas
|
344
|
+
# HW (Hot Water)
|
345
|
+
# CCASHP (Cold Climate Air Source Heat Pump)
|
346
|
+
# cool_cap: The capacity of cooling units in the supply side of the air loop. This is used to determine the main
|
347
|
+
# cooling type used by the ahu. They can be the following types:
|
348
|
+
# DX (Direct Expansion)
|
349
|
+
# CHW (Chilled Water)
|
350
|
+
# Note that HP and CCASHP are not inculded. If the the main heating type is a HP or CCASHP and the main
|
351
|
+
# cooling type is DX then the the main cooling type will be reported as being the same as the main heating
|
352
|
+
# type.
|
353
|
+
# heat_type: This is a hash of counters used to determine what services (electrical lines, hot water pipes, chilled
|
354
|
+
# water pipes, etc.) need to be run from the main mechanical room (where they are assumed to originate) to
|
355
|
+
# the roof of the building (where the ahu's are located). The following tpes are used:
|
356
|
+
# HP: Heat pump (also used for CCASHP, esentially just an electircal line is needed which is always
|
357
|
+
# inculded anyway)
|
358
|
+
# elec: Electricity (an electrical line is needed which is always inculded anyway)
|
359
|
+
# Gas: Gas (a gas line is needed)
|
360
|
+
# HW: Hot water (a hot water line is needed)
|
361
|
+
# cool_type: This is the same as heat_type only for cooling. This is really just used to determine if a chilled water
|
362
|
+
# line is needed since an electrical line is always inculded.
|
363
|
+
# DX: Direct Exchange (also used for HP and CCASHP since only the defaul electrical line is needed)
|
364
|
+
# CHW: Chilled water (a chilled water pipe is required)
|
365
|
+
#
|
366
|
+
# Outputs:
|
367
|
+
# heat_cool_info: This is a hash that contains the return information which inculdes:
|
368
|
+
# heating_fuel: The primary heating fuel used by the ahu (and supplemental heating fuel if used by a HP
|
369
|
+
# or CCASHP). This is used when searching the 'hvac_vent_ahu' sheet in the costing
|
370
|
+
# spreasheet when costing the ahu.
|
371
|
+
# cooling_type: The primary cooling type used by the ahu. This is used when searching the
|
372
|
+
# 'hvac_vent_ahu' sheet in the costing spreadsheet when costing the ahu.
|
373
|
+
# heat_type: See above (only counters adjusted)
|
374
|
+
# cool_type: See above (only counters adjusted)
|
375
|
+
#
|
376
|
+
def determine_ahu_htg_clg_fuel(heat_cap:, cool_cap:, heat_type:, cool_type:)
|
377
|
+
# Determine the predominant heating and cooling type by looking for the key associated with the largest value in the
|
378
|
+
# heat_cap and cool_cap hashes. For heating it returns HP, elec, Gas, HW or CCASHP and for cooling it returns CHW
|
379
|
+
# or DX.
|
380
|
+
heating_fuel = heat_cap.max_by{|key, value| value}[0]
|
381
|
+
cooling_type = cool_cap.max_by{|key, value| value}[0]
|
382
|
+
|
383
|
+
# Increase the counter of the associated cooling type by 1
|
384
|
+
cool_type[cooling_type] += 1
|
385
|
+
|
386
|
+
|
387
|
+
# If a variety of heat pump (regular HP or CCASHP) is present then, for costing, it is assumed to be the primary
|
388
|
+
# heating type for the ahu.
|
389
|
+
if heat_cap['HP'] > 0 || heat_cap['CCASHP'] > 0
|
390
|
+
# Increase the heat_type counter for heat pump by 1.
|
391
|
+
heat_type['HP'] += 1
|
392
|
+
|
393
|
+
# Get the capacities of just the HP and CCASHP.
|
394
|
+
pri_hp_type = {
|
395
|
+
'HP' => heat_cap['HP'],
|
396
|
+
'CCASHP' => heat_cap['CCASHP']
|
397
|
+
}
|
398
|
+
# Use the same technique for heating_fuel and cooling_type to determine which type of heat pump has the largest
|
399
|
+
# capacity. This is used in the off chance that more than one heat pump type is present (I'm not even sure that
|
400
|
+
# is possible in OpenStudio air loops but I include this little bit of edge case handling anyway).
|
401
|
+
hp_type = pri_hp_type.max_by{|key, value| value}[0].to_s
|
402
|
+
heating_fuel = hp_type
|
403
|
+
# It is possible to heat your building with an ASHP and use chilled water to cool your building. I don't know why
|
404
|
+
# you would do that but we can cost the ahu if you do. If the main cooling type is DX (which is highly likely
|
405
|
+
# if you are heating your air loop with an ASHP) then the main cooling type is set to be the main heating type
|
406
|
+
# of the air loop (either regular HP or fancy CCAHP).
|
407
|
+
if cooling_type == 'DX'
|
408
|
+
cooling_type = hp_type
|
409
|
+
end
|
410
|
+
# This determines if supplemental heating is used with your heat pump (very likely in most of Canada if you heat
|
411
|
+
# with a HP).
|
412
|
+
unless (heat_cap['elec'] == 0) && (heat_cap['Gas'] == 0) && (heat_cap['HW'] == 0)
|
413
|
+
# Create a hash of just the fuel heating in the air loop and change the hash key to match what we will look for
|
414
|
+
# in the hvac_vent_ahu sheet in the costing speadsheet
|
415
|
+
hp_supp_cap = {
|
416
|
+
'-e' => heat_cap['elec'],
|
417
|
+
'-g' => heat_cap['Gas'],
|
418
|
+
'-hw' => heat_cap['HW'],
|
419
|
+
}
|
420
|
+
# Look for the key (which is the fuel type) with the largest associated value (which is the capacity).
|
421
|
+
hp_supp = hp_supp_cap.max_by{|key, value| value}[0].to_s
|
422
|
+
# Increase the heat_type count for the associated supplement heat type. This is necessary since if gas heating
|
423
|
+
# is used as supplemental heatnig for a heat pump then a gas line will be required between the mechanical room
|
424
|
+
# and the roof.
|
425
|
+
case hp_supp
|
426
|
+
when '-e'
|
427
|
+
heat_type['elec'] += 1
|
428
|
+
when '-g'
|
429
|
+
heat_type['Gas'] += 1
|
430
|
+
when 'hw'
|
431
|
+
heat_type['HW'] += 1
|
432
|
+
end
|
433
|
+
end
|
434
|
+
# Get the heating fuel by appending the supplementary heating type just determined (if any) to the heat pump type
|
435
|
+
heating_fuel = hp_type
|
436
|
+
heating_fuel += hp_supp unless hp_supp.nil?
|
437
|
+
else
|
438
|
+
# If you do not use a heat pump then increase the heat_type counter for whatever fuel you use to heat the air loop
|
439
|
+
# by 1.
|
440
|
+
heat_type[heating_fuel] += 1
|
441
|
+
end
|
442
|
+
# Create the hash with the results and return it (I use a hash to return a bunch of results because it seems
|
443
|
+
# cleaner).
|
444
|
+
heat_cool_info = {
|
445
|
+
heating_fuel: heating_fuel,
|
446
|
+
cooling_type: cooling_type,
|
447
|
+
heat_type: heat_type,
|
448
|
+
cool_type: cool_type,
|
449
|
+
}
|
450
|
+
return heat_cool_info
|
451
|
+
end
|
452
|
+
# This method tokes in:
|
453
|
+
# ids: The list of material ids to look for in the 'material_id' column of the materials_hvac sheet.
|
454
|
+
# The number of ids should match the number of id_quants (this is checked earlier).
|
455
|
+
# id_quants: The number of the piece of equipment defined by the ids above required. Like ids this should be an
|
456
|
+
# array taken from the 'id_layers_quantity_multipliers' column of the 'hvac_vent_ahu' sheet for the air handler that
|
457
|
+
# matches the required criteria. The number of ids should match the number of id_quants (this is checked earlier).
|
458
|
+
# overall_mult: An multiplier to apply to all ids and id_quants (I'm not sure if this is used anymore).
|
459
|
+
# This method cycles through each of the ids and searches for it in the 'material_id' column of the 'material_hvac'
|
460
|
+
# sheet in the costing spreadsheet. The equipment information found in the 'materials_hvac' is then costed. The cost
|
461
|
+
# is then multiplied by the associated id_quants. For example, if the ids contains 5 elements the method searches for
|
462
|
+
# each one. In our example, when we get ot the 4th element of the ids we multiply its associated cost by the 4th
|
463
|
+
# element of the id_quants array. The total cost is then summed and multiplied by the 'overall_mult' and returned.
|
464
|
+
def vent_assembly_cost(ids:, id_quants:, overall_mult: 1.0, vent_tags: [], report_mult: 1.0)
|
465
|
+
assembly_tags = vent_tags.clone
|
466
|
+
total_cost = 0
|
467
|
+
# Cycle through each of the ids. The index is used to select the correct element of the id_quants array.
|
468
|
+
ids.each_with_index do |id, index|
|
469
|
+
# Get the equipment information from the costing spreadsheet's 'material_hvac' sheet whose 'material_id' matches
|
470
|
+
# the id.
|
471
|
+
mat_cost_info = @costing_database['raw']['materials_hvac'].select {|data|
|
472
|
+
data['material_id'].to_f.round(0) == id.to_f.round(0)
|
473
|
+
}.first
|
474
|
+
# If it cannot find it there is an issue with either the 'materials_hvac' sheet or the 'hvac_vent_ahu' sheet which
|
475
|
+
# the user has to deal with.
|
476
|
+
if mat_cost_info.nil?
|
477
|
+
raise "Error: no assembly information available for material id #{id}!"
|
478
|
+
end
|
479
|
+
# Get the cost for the piece of equipment, multiply it by the associated id_quants element and add to the total
|
480
|
+
total_cost += get_vent_mat_cost(mat_cost_info: mat_cost_info, report_mult: (overall_mult*id_quants[index].to_f*report_mult), vent_tags: assembly_tags)*id_quants[index].to_f
|
481
|
+
end
|
482
|
+
# multiply the total by the overal_mult (which is probably always 1.0 now but I'm not sure) and return the cost.
|
483
|
+
return (total_cost*overall_mult)
|
484
|
+
end
|
485
|
+
|
486
|
+
# This method finds how many pieces of costed equipment are required to meet a given load if no one piece of costed
|
487
|
+
# equipment can do it. It takes in two hashes:
|
488
|
+
# mult_floor: This should probably be mult_ceiling. It is the maximum size of mechanical equipment that should be
|
489
|
+
# selected. This is used if you really want to make sure that a given piece of mechanical equipment does not exceed
|
490
|
+
# this size. It is normally not used.
|
491
|
+
# loop_equip: This hash must have the following information in it:
|
492
|
+
# cat_search: The category or type of the mechanical equipment that is being searched for in the 'Material' column
|
493
|
+
# of the 'materials_hvac' sheet in the costing spreadsheet.
|
494
|
+
# supply_comp: This is the oir loop supply component from the OpenStudio model. It is really just used to give a
|
495
|
+
# name in any error messages.
|
496
|
+
# mech_capacity: This is the capacity of the piece of the supply component being costed.
|
497
|
+
#
|
498
|
+
# The method first looks for all of the items in the 'materials_hvac' sheet whose 'Material' match the 'cat_search'
|
499
|
+
# criteria. If none are found then something has gone wrong so an error is generated telling the user what happened.
|
500
|
+
# Assuming it found some items it then finds the largest one (or the largest one that does not exceed the mult_floor
|
501
|
+
# category). It then divides the mech_capacity by the size of the costed equipment it found to determine the minimum
|
502
|
+
# number of pieces of costed equipment meets the model equipment capacity (the multiplier). With this information it
|
503
|
+
# then rounds the multiplier to the next largest whole number and divides the modeled equipment capacity by this size
|
504
|
+
# to determine the revised size of equipment (this may be smaller than the largest piece of equipment). It then looks
|
505
|
+
# for the smallest piece of costed equipment that meets this requirement and returns the result.
|
506
|
+
def get_vent_system_mult(loop_equip:, mult_floor: nil)
|
507
|
+
# Look for all of the equipment in the materials_hvac sheet that has a 'Material' that matches the cat_search
|
508
|
+
# criteria.
|
509
|
+
heat_cool_cost = @costing_database['raw']['materials_hvac'].select {|data|
|
510
|
+
data['Material'].to_s.upcase == loop_equip[:cat_search].to_s.upcase
|
511
|
+
}
|
512
|
+
|
513
|
+
# In some cases loop_equip[:supply_comp] may not be an object but a string. If this is the case then the string
|
514
|
+
# should be given rather than a message that nameString does not exist.
|
515
|
+
equip_name = loop_equip[:supply_comp].nameString rescue equip_name = loop_equip[:supply_comp].to_s
|
516
|
+
|
517
|
+
# If it cannot find any then return an error telling the user what happened. This is likely the result of a
|
518
|
+
# spelling mistake somewhere but it is something the user will have to deal with.
|
519
|
+
if heat_cool_cost.nil? || heat_cool_cost.empty?
|
520
|
+
raise "Error: no equipment could be found whose type matches the name #{loop_equip[:cat_search]} for the #{equip_name} air loop supply component!"
|
521
|
+
end
|
522
|
+
# Set the maximum size to be a really large number if it is not defined.
|
523
|
+
mult_floor.nil? ? max_eq_size = 99999999999999999999.0 : max_eq_size = mult_floor.to_f
|
524
|
+
# Find the largest piece of equipment that is smaller than the size ceiling.
|
525
|
+
max_size = heat_cool_cost.select {|element| element['Size'].to_f <= max_eq_size}.max_by{|data| data['Size'].to_f}
|
526
|
+
# If you cannot find any then the size ceiling is too small. Raise an error telling the user
|
527
|
+
if max_size.nil? || max_size.empty?
|
528
|
+
raise "Error no equipment of the type #{loop_equip[:cat_search]} could be found with a size less than #{max_eq_size} for the #{equip_name} air loop supply component!"
|
529
|
+
end
|
530
|
+
# Make sure the piece of equipment has a capacity larger than 0.
|
531
|
+
if max_size['Size'].to_f <= 0
|
532
|
+
raise "Error: #{loop_equip[:cat_search]} has a size of 0 or less. Please check that the correct costing_database.json file is being used or check the costing spreadsheet!"
|
533
|
+
end
|
534
|
+
# Find the revised number of pieces of equipment and round to the next largest whole number.
|
535
|
+
mult = (loop_equip[:mech_capacity_kw].to_f) / (max_size['Size'].to_f)
|
536
|
+
# This is to handle the small possibility that the revised capacity is a whole number.
|
537
|
+
mult > (mult.to_i).to_f.round(0) ? multiplier = (mult.to_i).to_f.round(0) + 1 : multiplier = mult.round(0)
|
538
|
+
# Find the new capacity of the pieces of equipment
|
539
|
+
new_cap = loop_equip[:mech_capacity_kw].to_f/multiplier.to_f
|
540
|
+
# Find the smallest piece of costed equimpent that meets the new size requirement.
|
541
|
+
return_equip = heat_cool_cost.select{|data| data['Size'].to_f >= new_cap}.min_by{|element| element['Size'.to_f]}
|
542
|
+
# If no costed equipment can be found that matches this new size then something is wrong and use the largest piece
|
543
|
+
# you found before.
|
544
|
+
return_equip = max_size if (return_equip.nil? || return_equip.empty?)
|
545
|
+
return return_equip, multiplier.to_f
|
546
|
+
end
|
547
|
+
|
548
|
+
# This method finds ahu with the largest supply air capacity based on the heating and cooling characteristics defined
|
549
|
+
# by loop_equip. Loop_equip is a hash which includes:
|
550
|
+
# sys_type: the NECB HVAC system type (one of 1, 3, 4, or 6)
|
551
|
+
# heating_fuel: The predominant heating fuel
|
552
|
+
# cooling_type: The predominant cooling type
|
553
|
+
# airloop_flow_lps: The air loop flow rate (L/s)
|
554
|
+
# airloop_name: The name of the air loop (used in an error message)
|
555
|
+
#
|
556
|
+
# If no air handler is found that meets the above requirements raise an error telling the user that something is
|
557
|
+
# wrong. If one or more air handlers are found choose the one with the larges 'Supply_air'. This defines the largest
|
558
|
+
# air handler of the given type. Then divide the air loop air flow by the maximum air flow available. Round up and
|
559
|
+
# this number defines how many air handlers are required to meet the load.
|
560
|
+
#
|
561
|
+
# In some cases, the air loop flow rate is only a little larger than that available by the largest air handler. For
|
562
|
+
# example, an air loop may have a flow rate of 16000 L/s but the largest available air handler is 15000 L/s. Rather
|
563
|
+
# than costing two 15000 L/s air handlers it would be cheaper to cost two 8000 L/s air handlers. To do this, the
|
564
|
+
# method divides the air_loop_flow_lps by the number of required air handlers. It then looks for air handlers which
|
565
|
+
# meet the required characteristics and revised air flow rate. If more than one are found it selects the smallest one
|
566
|
+
# available. It then returns this new air handler along with the raw number of air handlens (which may be a fraction)
|
567
|
+
# and the maximum number (which will be an integer).
|
568
|
+
def get_ahu_mult(loop_equip:)
|
569
|
+
# Look for the largest air handler that matches the system type, heating fuel, and cooling type requirements
|
570
|
+
ahu = @costing_database['raw']['hvac_vent_ahu'].select {|data|
|
571
|
+
data['Sys_type'].to_f.round(0) == loop_equip[:sys_type].to_f.round(0) and
|
572
|
+
data['Htg'].to_s.upcase == loop_equip[:heating_fuel].to_s.upcase and
|
573
|
+
data['Clg'].to_s.upcase == loop_equip[:cooling_type].to_s.upcase
|
574
|
+
}.max_by {|element| element['Supply_air'].to_f}
|
575
|
+
# If none are found something has gone wrong. Tell the user.
|
576
|
+
if ahu.nil? || ahu.empty?
|
577
|
+
raise "Error: no ahu information available for equipment #{loop_equip[:airloop_name]}!"
|
578
|
+
end
|
579
|
+
# I probably don't need to check this but make sure that the air handler has a size larger than 0.
|
580
|
+
if ahu['Supply_air'].to_f <= 0
|
581
|
+
raise "Error: #{loop_equip[:airloop_name]} has a size of 0 or less. Please check that the correct costing_database.json file is being used or check the costing spreadsheet!"
|
582
|
+
end
|
583
|
+
# Determine the number of air handlers to be the air loop flow rate divided by the maximum air handler size. This
|
584
|
+
# will likely not be a whole number.
|
585
|
+
mult = (loop_equip[:airloop_flow_lps].to_f) / (ahu['Supply_air'].to_f)
|
586
|
+
# Since air handlers only come in integer numbers (half and air handler would not be too useful) round up to the
|
587
|
+
# next whole number (the if statement is for the off chance that the required number ended up being an integer).
|
588
|
+
mult > (mult.to_i).to_f.round(0) ? multiplier = (mult.to_i).to_f.round(0) + 1 : multiplier = mult.round(0)
|
589
|
+
# Get the revised required air flow rate by dividing the air loop air flow by the number of air handlers
|
590
|
+
rev_air_flow = loop_equip[:airloop_flow_lps].to_f / multiplier
|
591
|
+
# Find air handlers that can meet that air flow and choose the smallest one that meets the requirement.
|
592
|
+
rev_ahu = @costing_database['raw']['hvac_vent_ahu'].select {|data|
|
593
|
+
data['Sys_type'].to_f.round(0) == loop_equip[:sys_type].to_f.round(0) and
|
594
|
+
data['Htg'].to_s.upcase == loop_equip[:heating_fuel].to_s.upcase and
|
595
|
+
data['Clg'].to_s.upcase == loop_equip[:cooling_type].to_s.upcase and
|
596
|
+
data['Supply_air'].to_f >= rev_air_flow
|
597
|
+
}.min_by{|info| info['Supply_air'].to_f}
|
598
|
+
# If none are found something weird is happening so keep the one you already found.
|
599
|
+
if rev_ahu.nil? || rev_ahu.empty?
|
600
|
+
# Something weird happened, keep the ahu you found before.
|
601
|
+
else
|
602
|
+
ahu = rev_ahu
|
603
|
+
end
|
604
|
+
return ahu, multiplier, rev_air_flow
|
605
|
+
end
|
606
|
+
|
607
|
+
# This method costs a piece of mechanical equipment. The mat_cost_info is a hash that contains the information for the
|
608
|
+
# piece of equipment from the 'materials_hvac' sheet of the costing spreadsheet. It contains:
|
609
|
+
# material_id: An index sometimes used to refer to find a specific piece of equipment
|
610
|
+
# material: The type of equipment.
|
611
|
+
# description: A description of the piece of equipment.
|
612
|
+
# Size: The size of the piece of equipment (see units for the unit this is in).
|
613
|
+
# Fuel: Sometimes this is indicates the fuel type, sometimes it is an additional size criteria.
|
614
|
+
# source: The source to look for the costing information. This can be placeholder or custom.
|
615
|
+
# id: The unique id of the costing information associated with this piece of equipment.
|
616
|
+
# unit: The units of the given Size (can be one of many units).
|
617
|
+
# province_state: For custom costing data, this is the province or state that the costing data is given for (used to
|
618
|
+
# adjust the costing data so it can be used nationally).
|
619
|
+
# city: For custom costing data, this is the city that the costing data is given for (used to adjust the costing so
|
620
|
+
# it can be used nationally).
|
621
|
+
# year: The year the costing information is provided for (it should be the same for everything but some costs are
|
622
|
+
# only available in some years and not others).
|
623
|
+
# material_cost: The custom cost for material (e.g. the cost of a pipe). Not used for placeholder costs.
|
624
|
+
# labour_cost: The custom cost for labour (e.g. the labour to install the pipe). Not used for placeholder costs.
|
625
|
+
# equipment_cost: The custom cost of equipment required (e.g. the cost of any machinery required to install the pipe,
|
626
|
+
# often this is 0). This is not used for placeholder costs.
|
627
|
+
# material_op_factor: Ask Mike or Phylroy. Probably not for placeholder costs.
|
628
|
+
# labour_op_factor: Ask Mike or Phylroy. Probably not for placeholder costs.
|
629
|
+
# equipment_op_factor: Ask Mike or Phylroy. Probably not for placeholder costs.
|
630
|
+
# comments: comments.
|
631
|
+
# material_mult: A fixed multiplier to multiply the material cost by.
|
632
|
+
# labour_mult: A fixed multiplier to multiply the labour costs by.
|
633
|
+
# The method uses the id from 'mat_cost_info' to find costing information for the piece of equipment in the costing
|
634
|
+
# database. It then adjusts the material and equipment cost by the regional cost factor for the location the model
|
635
|
+
# is supposed to be in. The resulting adjusted equipment and material costs are then multiplied by any associated
|
636
|
+
# multipliers and the total amount is returned.
|
637
|
+
def get_vent_mat_cost(mat_cost_info:, vent_tags: [], report_mult: 1.0)
|
638
|
+
cost_tags =vent_tags.clone
|
639
|
+
if mat_cost_info.nil?
|
640
|
+
raise("Error: no assembly information available for material!")
|
641
|
+
end
|
642
|
+
# Look for the costing information for the piece of equipment in the costing database.
|
643
|
+
costing_data = @costing_database['costs'].detect {|data| data['id'].to_s.upcase == mat_cost_info['id'].to_s.upcase}
|
644
|
+
# If no costing information is found then return an error.
|
645
|
+
if costing_data.nil?
|
646
|
+
raise "Error: no costing information available for material id #{mat_cost_info['id']}!"
|
647
|
+
elsif costing_data['baseCosts']['materialOpCost'].nil? || costing_data['baseCosts']['laborOpCost'].nil?
|
648
|
+
#This is a stub for some work that needs to be done to account for equipment costing. For now this is zeroed out.
|
649
|
+
# A similar test is done on reading the data from the database and collected in the error file when the
|
650
|
+
# costing database is generated.
|
651
|
+
puts("Error: costing information for material id #{mat_cost_info['id']} is nil. Please check costing data.")
|
652
|
+
return 0.0
|
653
|
+
end
|
654
|
+
# The costs from the costing database are US national average costs (for placeholder costs) or whatever is in the
|
655
|
+
# 'province_state' and 'city' fieleds (for custom costs). These costs need to be adjusted to reflect the costs
|
656
|
+
# expected in the location of interest. The 'get_regional_cost_factors' method finds the appropriate cost
|
657
|
+
# adjustment factors.
|
658
|
+
mat_mult, inst_mult = get_regional_cost_factors(@costing_report['province_state'], @costing_report['city'], mat_cost_info)
|
659
|
+
if mat_mult.nil? || inst_mult.nil?
|
660
|
+
raise("Error: no localization information available for material id #{id}!")
|
661
|
+
end
|
662
|
+
# Get any associated material or labour multiplier for the equipment present in the 'materials_hvac' sheet in the
|
663
|
+
# costing spreadsheet.
|
664
|
+
mat_cost_info['material_mult'].to_f == 0 ? mat_quant = 1.0 : mat_quant = mat_cost_info['material_mult'].to_f
|
665
|
+
mat_cost_info['labour_mult'].to_f == 0 ? lab_quant = 1.0 : lab_quant = mat_cost_info['labour_mult'].to_f
|
666
|
+
# Calculate the adjusted material and labour costs.
|
667
|
+
mat_cost = costing_data['baseCosts']['materialOpCost']*(mat_mult/100.0)*mat_quant
|
668
|
+
lab_cost = costing_data['baseCosts']['laborOpCost']*(inst_mult/100.0)*lab_quant
|
669
|
+
# Add information to report output if tags provided.
|
670
|
+
unless cost_tags.empty?
|
671
|
+
cost_tags << mat_cost_info['Material'].to_s
|
672
|
+
cost_tags << mat_cost_info['description'].to_s
|
673
|
+
# Add support for equipment_multiplier (if used in the future).
|
674
|
+
mat_cost_info['equipment_mult'].nil? || mat_cost_info['equipment_mult'].to_f == 0 ? equip_quant = 1.0 : equip_quant = mat_cost_info['equipment_mult'].to_f
|
675
|
+
add_costed_item(material_id: mat_cost_info['id'], quantity: report_mult.to_f, material_mult: mat_quant, labour_mult: lab_quant, equip_mult: equip_quant, tags: cost_tags)
|
676
|
+
end
|
677
|
+
# Return the total.
|
678
|
+
return (mat_cost+lab_cost)
|
679
|
+
end
|
680
|
+
|
681
|
+
def cost_heat_cool_equip(equipment_info:, vent_tags: [], report_mult: 1.0)
|
682
|
+
equip_tags = vent_tags.clone
|
683
|
+
total_cost = 0
|
684
|
+
multiplier, heat_cool_cost_info = get_vent_cost_data(equipment_info: equipment_info)
|
685
|
+
total_cost += (get_vent_mat_cost(mat_cost_info: heat_cool_cost_info, vent_tags: equip_tags, report_mult: (report_mult*multiplier)))*multiplier
|
686
|
+
if equipment_info[:cooling_type] == 'DX' || equipment_info[:cooling_type] == 'DX-adv'
|
687
|
+
equipment_info[:cooling_type].include?("-adv") ? search_suff = "-adv" : search_suff = ""
|
688
|
+
equipment_info[:cat_search] = "CondensingUnit" + search_suff
|
689
|
+
equip_tags << equipment_info[:cat_search] unless equip_tags.empty?
|
690
|
+
multiplier, heat_cool_cost_info = get_vent_cost_data(equipment_info: equipment_info)
|
691
|
+
total_cost += get_vent_mat_cost(mat_cost_info: heat_cool_cost_info, vent_tags: equip_tags, report_mult: (report_mult*multiplier))*multiplier
|
692
|
+
equip_tags << "piping" unless equip_tags.empty?
|
693
|
+
piping_search = []
|
694
|
+
|
695
|
+
piping_search << {
|
696
|
+
mat: 'SteelPipe',
|
697
|
+
unit: 'L.F.',
|
698
|
+
size: 1.25,
|
699
|
+
mult: 32.8
|
700
|
+
}
|
701
|
+
piping_search << {
|
702
|
+
mat: 'PipeInsulationsilica',
|
703
|
+
unit: 'L.F.',
|
704
|
+
size: 1.25,
|
705
|
+
mult: 32.8
|
706
|
+
}
|
707
|
+
piping_search << {
|
708
|
+
mat: 'SteelPipeElbow',
|
709
|
+
unit: 'each',
|
710
|
+
size: 1.25,
|
711
|
+
mult: 8
|
712
|
+
}
|
713
|
+
total_cost += get_comp_cost(cost_info: piping_search, vent_tags: equip_tags, report_mult: (report_mult*multiplier))*multiplier
|
714
|
+
return total_cost
|
715
|
+
end
|
716
|
+
# This needs to be revised as currently the costing spreadsheet may not inculde heating and cooling coil costs in
|
717
|
+
# the ahu definition sheet. This is commented out for now but will need to be revisited. See btap_tasks issue 156.
|
718
|
+
=begin
|
719
|
+
if equipment_info[:heating_fuel] == 'HP'
|
720
|
+
if sys_type == 3 || sys_type == 6
|
721
|
+
# Remove the DX cooling unit for ashp in type 3 and 6 systems
|
722
|
+
heat_cool_cost = @costing_database['raw']['materials_hvac'].select {|data|
|
723
|
+
data['Material'].to_s.upcase == 'DX' and
|
724
|
+
data['Size'].to_f.round(8) >= equipment_info[:mech_capacity_kw].to_f
|
725
|
+
}.first
|
726
|
+
if heat_cool_cost.nil?
|
727
|
+
heat_cool_cost, multiplier = get_vent_system_mult(loop_equip: equipment_info)
|
728
|
+
end
|
729
|
+
total_cost -= (get_vent_mat_cost(mat_cost_info: heat_cool_cost))*multiplier
|
730
|
+
|
731
|
+
# Remove the heating coil for ashp in type 3 and 6 systems
|
732
|
+
heat_cool_cost = @costing_database['raw']['materials_hvac'].select {|data|
|
733
|
+
data['Material'].to_s.upcase == 'COILS' and
|
734
|
+
data['Size'].to_f.round(8) >= equipment_info[:mech_capacity_kw].to_f
|
735
|
+
}.first
|
736
|
+
if heat_cool_cost.nil?
|
737
|
+
heat_cool_cost, multiplier = get_vent_system_mult(loop_equip: equipment_info)
|
738
|
+
end
|
739
|
+
total_cost -= (get_vent_mat_cost(mat_cost_info: heat_cool_cost))*multiplier
|
740
|
+
puts 'hello'
|
741
|
+
end
|
742
|
+
# Add pre-heat for ashp in all cases
|
743
|
+
# This needs to be refined as well. Only add the cost of an electric heat if a heater (presumably of any type) if
|
744
|
+
# one is not already explicitly modeled in the air loop (and thus costed already as part of this method). This is
|
745
|
+
# also part of btap_tasks issue 156.
|
746
|
+
heat_cool_cost = @costing_database['raw']['materials_hvac'].select {|data|
|
747
|
+
data['Material'].to_s.upcase == 'ELECHEAT' and
|
748
|
+
data['Size'].to_f.round(8) >= equipment_info[:mech_capacity_kw].to_f
|
749
|
+
}.first
|
750
|
+
if heat_cool_cost.nil?
|
751
|
+
heat_cool_cost, multiplier = get_vent_system_mult(loop_equip: equipment_info)
|
752
|
+
end
|
753
|
+
total_cost += (get_vent_mat_cost(mat_cost_info: heat_cool_cost))*multiplier
|
754
|
+
end
|
755
|
+
=end
|
756
|
+
return total_cost
|
757
|
+
end
|
758
|
+
|
759
|
+
# This method collects information related a piece of equipment from the 'materials_hvac' sheet in the costing
|
760
|
+
# spreadsheet. This information is then used to determine the cost of a piece of equipment. It takes in the
|
761
|
+
# equipment_info hash. This hash contains the following information:
|
762
|
+
# equipment_info = {
|
763
|
+
# cat_search: This is the category or type of mechanical equipment that is being costed. It is used to match items in
|
764
|
+
# the 'Material' column of the 'materials_hvac' sheet.
|
765
|
+
# mech_capacity_kw: This is the capacity of the piece of mechanical equipment being costed. Although it has kw in
|
766
|
+
# the name this is not always the case. It is compared against information in the 'Size' column of the
|
767
|
+
# 'materials_hvac' sheet.
|
768
|
+
# supply_comp: This is the OpenStudio object being costed. If there is an error this is used to tell which piece
|
769
|
+
# of the model had the issue.
|
770
|
+
#
|
771
|
+
# The method tries to find the smallest piece of equipment that matches the equipment type and that can satisfy the
|
772
|
+
# capacity requirements. If it cannot find one then it then it assumes that the largest matching piece of equipment
|
773
|
+
# cannot meet the required capacity and tries to determine how many would be need to meet the required capacity. It
|
774
|
+
# then returns the information it found in the costing spreadsheet and the number of piece of equipment would be
|
775
|
+
# required (if applicable)
|
776
|
+
def get_vent_cost_data(equipment_info:)
|
777
|
+
# Assume one piece of equipment is enough.
|
778
|
+
multiplier = 1.0
|
779
|
+
# Find the smallest piece of equipment in 'materials_hvac' sheet that matches the equipment type and meets the
|
780
|
+
# capacity requirement.
|
781
|
+
heat_cool_cost_data = @costing_database['raw']['materials_hvac'].select {|data|
|
782
|
+
data['Material'].to_s.upcase == equipment_info[:cat_search].to_s.upcase and
|
783
|
+
data['Size'].to_f.round(8) >= equipment_info[:mech_capacity_kw].to_f
|
784
|
+
}.min_by{|heat_cool| heat_cool[:mech_capcity_kw].to_f}
|
785
|
+
# If it cannot find any then assume the largest piece of equipment in the costing spreadsheet is too small and
|
786
|
+
# figure out how many of a smaller piece of equipment are required and what the smaller piece of equipment would be.
|
787
|
+
if heat_cool_cost_data.nil? || heat_cool_cost_data.empty?
|
788
|
+
heat_cool_cost_data, multiplier = get_vent_system_mult(loop_equip: equipment_info)
|
789
|
+
end
|
790
|
+
# Return the number of equipment necessary and the informatino required to find the piece of equipment in the
|
791
|
+
# costing database.
|
792
|
+
return multiplier, heat_cool_cost_data
|
793
|
+
end
|
794
|
+
|
795
|
+
def gas_burner_cost(heating_fuel:, sys_type:, airloop_flow_cfm:, mech_sizing_info:, costed_ahu_info:, vent_tags: [], report_mult: 1.0)
|
796
|
+
ahu_airflow_lps = costed_ahu_info[:ahu]["Supply_air"].to_f
|
797
|
+
report_mult_mod = report_mult*(-1.0)
|
798
|
+
burner_tags = vent_tags.clone
|
799
|
+
if (sys_type == 3 || sys_type == 6)
|
800
|
+
return 0
|
801
|
+
mech_table = get_mech_table(mech_size_info: mech_sizing_info, table_name: 'ahu_airflow')
|
802
|
+
coil_sizing_info = mech_table.select{|data| (data['ahu_airflow_range_lps'][0].to_f <= ahu_airflow_lps) && (data['ahu_airflow_range_lps'][1].to_f > ahu_airflow_lps) }
|
803
|
+
if coil_sizing_info.empty?
|
804
|
+
coil_sizing_kW = mech_table.max_by{|data| data['ahu_airflow_range_lps'][1]}
|
805
|
+
else
|
806
|
+
coil_sizing_kW = coil_sizing_info[0]
|
807
|
+
end
|
808
|
+
heating_kw = coil_sizing_kW['htg_coil_sizing_kW'].to_f
|
809
|
+
cooling_kw = coil_sizing_kW['DX_coil_sizing_kW'].to_f
|
810
|
+
heat_mech_eq_mult, heat_cost_info = get_vent_cost_data(equipment_info: {cat_search: 'coils', mech_capacity_kw: heating_kw})
|
811
|
+
cool_mech_eq_mult, cool_cost_info = get_vent_cost_data(equipment_info: {cat_search: 'coils', mech_capacity_kw: cooling_kw})
|
812
|
+
heating_coil_cost = heat_mech_eq_mult*get_vent_mat_cost(mat_cost_info: heat_cost_info, vent_tags: burner_tags, report_mult: (report_mult_mod*heat_mech_eq_mult))
|
813
|
+
dx_coil_cost = cool_mech_eq_mult*get_vent_mat_cost(mat_cost_info: cool_cost_info, vent_tags: burner_tags, report_mult: (report_mult_mod*cool_mech_eq_mult))
|
814
|
+
return heating_coil_cost + dx_coil_cost
|
815
|
+
else
|
816
|
+
if airloop_flow_cfm >= 1000 && airloop_flow_cfm <= 1500
|
817
|
+
mult, mech_info = get_vent_cost_data(equipment_info: {cat_search: 'DuctFurGasExt', mech_capacity_kw: 88})
|
818
|
+
return get_vent_mat_cost(mat_cost_info: mech_info, vent_tags: burner_tags, report_mult: (report_mult_mod*mult))*mult
|
819
|
+
elsif airloop_flow_cfm > 1500
|
820
|
+
mult, mech_info = get_vent_cost_data(equipment_info: {cat_search: 'DuctFurGasExt', mech_capacity_kw: 132})
|
821
|
+
return get_vent_mat_cost(mat_cost_info: mech_info, vent_tags: burner_tags, report_mult: (report_mult_mod*mult))*mult
|
822
|
+
end
|
823
|
+
end
|
824
|
+
return 0.0
|
825
|
+
end
|
826
|
+
|
827
|
+
# This method looks for an air handler in the 'hvac_vent_ahu' sheet of the costing spreadsheet. The inputs it uses
|
828
|
+
# to find the air handler are:
|
829
|
+
# sys_type: HVAC system type (can handle NECB systems 1, 3, 4 or 6)
|
830
|
+
# airloop_flow_lps: Air loop design air flow rate (L/s)
|
831
|
+
# heating_fuel: The predominant heating fuel used by the air loop (HP, CCASHP, HW, Gas, Propane, Oil)
|
832
|
+
# cooling_type: The predominant cooling type used by the air loop (DX, HP, CCASHP, CHW)
|
833
|
+
# airloop_name: The name of the air loop (only used for error messages)
|
834
|
+
#
|
835
|
+
# If no air handler with matching characteristics are found it assumes that all of the ones in the 'hvac_vent_ahu' ore
|
836
|
+
# too small. I then calls get_ahu_mult to find the largest air handler with the appropriate characteristics and finds
|
837
|
+
# how many of those are required to meet the load (see get_ahu_mult for more information). Once the appropriate air
|
838
|
+
# handler is selected from the 'hvac_vent_ahu' the method then reads the numbers in column K (id_layers) and column N
|
839
|
+
# (id_layers_quantity_multipliers). The numbers in 'id_layers' are indexes that match column A (material_id) in the
|
840
|
+
# 'material_hvac' costing spreadsheet sheet. The numbers in 'id_layers_quantity_multipliers' define how many pieces
|
841
|
+
# of equipment defined in the id_layer. The method then calls the 'vent_assembly_cost' method which takes the set of
|
842
|
+
# id_layers, the 'id_layers_quantity_multipliers' and the overall_mult. This costs each item in 'id_layers',
|
843
|
+
# multiplies the cost by the number in 'id_layers_quantity_multipliers' and multiplies everything by 'overall_mult'.
|
844
|
+
# The returned cast is then multiplied by the number of air handlers present (mult) and returns the cost.
|
845
|
+
#
|
846
|
+
# The method now also also includes the call to the 'gas_burner_cost' method to adjust for burner costs. It also
|
847
|
+
# includes the ahu size adjustement previously done in the main 'ahu_costing' method.
|
848
|
+
def cost_ahu(sys_type:, airloop_flow_lps:, airloop_flow_cfm:, mech_sizing_info:, heating_fuel:, cooling_type:, airloop_name:, vent_tags: [])
|
849
|
+
# Assmue one air handler to start
|
850
|
+
mult = 1.0
|
851
|
+
# Find an air handler in the 'hvac_vent_ahu' sheet that matches the system_type, air flow rate, heating type and
|
852
|
+
# cooling type.
|
853
|
+
ahu = @costing_database['raw']['hvac_vent_ahu'].select {|data|
|
854
|
+
data['Sys_type'].to_f.round(0) == sys_type.to_f.round(0) and
|
855
|
+
data['Supply_air'].to_f >= airloop_flow_lps and
|
856
|
+
data['Htg'].to_s == heating_fuel and
|
857
|
+
data['Clg'].to_s == cooling_type
|
858
|
+
}.min_by{|info| info['Supply_air'].to_f}
|
859
|
+
# If none are there assume that none had a big enough air flow rate. Create a data structure with the pertinent
|
860
|
+
# air handler information.
|
861
|
+
if ahu.nil? || ahu.empty?
|
862
|
+
loop_equip = {
|
863
|
+
sys_type: sys_type,
|
864
|
+
heating_fuel: heating_fuel,
|
865
|
+
cooling_type: cooling_type,
|
866
|
+
airloop_flow_lps: airloop_flow_lps,
|
867
|
+
airloop_name: airloop_name
|
868
|
+
}
|
869
|
+
# Find send the air handler information to the 'get_ahu_mult' method which returns the air handler information and
|
870
|
+
# the number which will meet the supply air rate.
|
871
|
+
ahu, mult, rev_airloop_flow_lps = get_ahu_mult(loop_equip: loop_equip)
|
872
|
+
# If one air handler which meets the requirements is found then use that one.
|
873
|
+
else
|
874
|
+
rev_airloop_flow_lps = airloop_flow_lps
|
875
|
+
end
|
876
|
+
# set the number of air hondlers in @airloop_info which is included in the ventilation costing report.
|
877
|
+
@airloop_info[:num_rooftop_units] = mult.to_i
|
878
|
+
# Calculate the ahu cost modifier for systems other than the largest (recreation of modifier originally applied in
|
879
|
+
# the 'ahu_costing' method).
|
880
|
+
ahu['Supply_air'].to_f.round(0) == 15000 ? ahu_cost_mod = 1.0 : ahu_cost_mod = (rev_airloop_flow_lps/(ahu['Supply_air'].to_f))
|
881
|
+
# Get the 'id_layers' from the 'hvac_vent_ahu' sheet and put them into an array
|
882
|
+
ids = ahu['id_layers'].to_s.split(',')
|
883
|
+
# Get the quantity of each of the preceding 'id_layers'. To do this, get the 'id_layers_quantity_multipliers'
|
884
|
+
# numbers from the 'hvac_vent_ahu' and convert them into an array
|
885
|
+
id_quants = ahu['Id_layers_quantity_multipliers'].to_s.split(',')
|
886
|
+
# Check that the number of ids is the same as the number of id_quants. If it isn't something is wrong and raise an
|
887
|
+
# error.
|
888
|
+
raise "The number of id_layers does not match the number of id_layer_quantity_multipliers in the hvac_vent_auh sheet of the costing spreadsheet. Please look for the air handler in the costing spreadsheet and check the appropriate columns. The air handler characteristics are: #{ahu}" if ids.size != id_quants.size
|
889
|
+
# Get the overall_mult. This used to be used but does not seem to be used anymore. I left it in just in case
|
890
|
+
# (probably a bad idea).
|
891
|
+
overall_mult = ahu['material_mult'].to_f
|
892
|
+
overall_mult = 1.0 if overall_mult == 0
|
893
|
+
|
894
|
+
# Create tags that will be added to the cost list output
|
895
|
+
new_tags = vent_tags.clone
|
896
|
+
new_tags << heating_fuel
|
897
|
+
new_tags << cooling_type
|
898
|
+
new_tags << "Required Air Flow (L/s): #{airloop_flow_lps.to_f.round(2)}"
|
899
|
+
new_tags << "Total AHU Air Flow with Multipliers(L/s): #{(ahu['Supply_air'].to_f*mult).to_f.round(2)}"
|
900
|
+
new_tags << "AHU Equipment"
|
901
|
+
|
902
|
+
# Cost the ids (multiplied by the number associated id_quants) and maltiply everything by the number of air handlers
|
903
|
+
# (if one was too small).
|
904
|
+
ind_ahu_cost = vent_assembly_cost(ids: ids, id_quants: id_quants, overall_mult: overall_mult, vent_tags: new_tags, report_mult: (overall_mult*ahu_cost_mod*mult))
|
905
|
+
# This is the total ahu cost without adjusting cost with airflow
|
906
|
+
calc_ahu_cost = ind_ahu_cost*mult
|
907
|
+
# Create the start of the return hash (done here because it is used in the 'gas_burner_cost' method)
|
908
|
+
costed_ahu_info = {
|
909
|
+
ahu: ahu,
|
910
|
+
mult: mult,
|
911
|
+
air_loop_flow_lps: airloop_flow_lps,
|
912
|
+
ind_ahu_cost: ind_ahu_cost
|
913
|
+
}
|
914
|
+
new_tags.pop
|
915
|
+
# Remove gas burner cost from ahu cost because it is accounted for in the heating and cooling equipment calculated later.
|
916
|
+
new_tags << "AHU Cost Adjustment"
|
917
|
+
ahu_mech_adj = gas_burner_cost(heating_fuel: heating_fuel, sys_type: sys_type, airloop_flow_cfm: airloop_flow_cfm, mech_sizing_info: mech_sizing_info, costed_ahu_info: costed_ahu_info, vent_tags: new_tags, report_mult: ahu_cost_mod)
|
918
|
+
base_ahu_cost = calc_ahu_cost - ahu_mech_adj
|
919
|
+
# Caclculate the adjusted ahu cost
|
920
|
+
adj_ahu_cost = (ind_ahu_cost*mult- ahu_mech_adj)*ahu_cost_mod
|
921
|
+
# Add costs to costing output
|
922
|
+
@airloop_info[:ind_ahu_max_airflow_l_per_s] = ahu['Supply_air'].to_f.round(0)
|
923
|
+
@airloop_info[:base_ahu_cost] = base_ahu_cost.round(2)
|
924
|
+
@airloop_info[:revised_base_ahu_cost] = adj_ahu_cost.round(2)
|
925
|
+
|
926
|
+
# Add ahu costs to return hash
|
927
|
+
costed_ahu_info[:base_ahu_cost] = base_ahu_cost
|
928
|
+
costed_ahu_info[:adjusted_base_ahu_cost] = adj_ahu_cost
|
929
|
+
|
930
|
+
return costed_ahu_info
|
931
|
+
end
|
932
|
+
|
933
|
+
def mech_to_roof_cost(heat_type:, cool_type:, mech_room:, roof_cent:, rt_unit_num:)
|
934
|
+
mech_to_roof_rep = {
|
935
|
+
Gas_Line_m: 0.0,
|
936
|
+
HW_Line_m: 0.0,
|
937
|
+
CHW_Line_m: 0.0,
|
938
|
+
Elec_Line_m: 0.0,
|
939
|
+
Total_cost: 0.0
|
940
|
+
}
|
941
|
+
mech_dist = [(roof_cent[:roof_centroid][0] - mech_room['space_centroid'][0]), (roof_cent[:roof_centroid][1] - mech_room['space_centroid'][1]), (roof_cent[:roof_centroid][2] - mech_room['space_centroid'][2])]
|
942
|
+
utility_dist = 0
|
943
|
+
ut_search = []
|
944
|
+
rt_roof_dist = OpenStudio.convert(10, 'm', 'ft').get
|
945
|
+
mech_dist.each{|dist| utility_dist+= dist.abs}
|
946
|
+
utility_dist = OpenStudio.convert(utility_dist, 'm', 'ft').get
|
947
|
+
heat_type.each do |key, value|
|
948
|
+
if value >= 1
|
949
|
+
case key
|
950
|
+
when 'HP'
|
951
|
+
next
|
952
|
+
when 'elec'
|
953
|
+
next
|
954
|
+
when 'Gas'
|
955
|
+
ut_search << {
|
956
|
+
mat: 'GasLine',
|
957
|
+
unit: 'L.F.',
|
958
|
+
size: 0,
|
959
|
+
mult: utility_dist + rt_roof_dist*value
|
960
|
+
}
|
961
|
+
heat_type['Gas'] = 0
|
962
|
+
mech_to_roof_rep[:Gas_Line_m] == (utility_dist + rt_roof_dist*value).round(1)
|
963
|
+
when 'HW'
|
964
|
+
ut_search << {
|
965
|
+
mat: 'SteelPipe',
|
966
|
+
unit: 'L.F.',
|
967
|
+
size: 4,
|
968
|
+
mult: 2*utility_dist + 2*rt_roof_dist*value
|
969
|
+
}
|
970
|
+
mech_to_roof_rep[:HW_Line_m] = (2*utility_dist + 2*rt_roof_dist*value).round(1)
|
971
|
+
ut_search << {
|
972
|
+
mat: 'PipeInsulation',
|
973
|
+
unit: 'none',
|
974
|
+
size: 4,
|
975
|
+
mult: 2*utility_dist + 2*rt_roof_dist*value
|
976
|
+
}
|
977
|
+
ut_search << {
|
978
|
+
mat: 'PipeJacket',
|
979
|
+
unit: 'none',
|
980
|
+
size: 4,
|
981
|
+
mult: 2*utility_dist + 2*rt_roof_dist*value
|
982
|
+
}
|
983
|
+
end
|
984
|
+
end
|
985
|
+
end
|
986
|
+
|
987
|
+
cool_type.each do |key, value|
|
988
|
+
if value >= 1
|
989
|
+
case key
|
990
|
+
when 'DX'
|
991
|
+
next
|
992
|
+
when 'CHW'
|
993
|
+
ut_search << {
|
994
|
+
mat: 'SteelPipe',
|
995
|
+
unit: 'L.F.',
|
996
|
+
size: 4,
|
997
|
+
mult: 2*utility_dist + 2*rt_roof_dist*value
|
998
|
+
}
|
999
|
+
mech_to_roof_rep[:CHW_Line_m] = (2*utility_dist + 2*rt_roof_dist*value).round(1)
|
1000
|
+
ut_search << {
|
1001
|
+
mat: 'PipeInsulation',
|
1002
|
+
unit: 'none',
|
1003
|
+
size: 4,
|
1004
|
+
mult: 2*utility_dist + 2*rt_roof_dist*value
|
1005
|
+
}
|
1006
|
+
ut_search << {
|
1007
|
+
mat: 'PipeJacket',
|
1008
|
+
unit: 'none',
|
1009
|
+
size: 4,
|
1010
|
+
mult: 2*utility_dist + 2*rt_roof_dist*value
|
1011
|
+
}
|
1012
|
+
end
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
mech_to_roof_rep[:Elec_Line_m] = (utility_dist + rt_unit_num*rt_roof_dist).round(1)
|
1016
|
+
ut_search << {
|
1017
|
+
mat: 'Wiring',
|
1018
|
+
unit: 'CLF',
|
1019
|
+
size: 10,
|
1020
|
+
mult: (utility_dist + rt_unit_num*rt_roof_dist)/100
|
1021
|
+
}
|
1022
|
+
ut_search << {
|
1023
|
+
mat: 'Conduit',
|
1024
|
+
unit: 'L.F.',
|
1025
|
+
size: 0,
|
1026
|
+
mult: utility_dist + rt_unit_num*rt_roof_dist
|
1027
|
+
}
|
1028
|
+
total_comp_cost = get_comp_cost(cost_info: ut_search)
|
1029
|
+
mech_to_roof_rep[:Total_cost] = total_comp_cost.round(2)
|
1030
|
+
return total_comp_cost, mech_to_roof_rep
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
def reheat_recool_cost(airloop:, prototype_creator:, model:, roof_cent:, mech_sizing_info:, vent_tags: [], report_mult: 1.0)
|
1034
|
+
reheat_recool_tags = vent_tags.clone
|
1035
|
+
heat_cost = 0
|
1036
|
+
out_reheat_array = []
|
1037
|
+
airloop.thermalZones.sort.each do |thermalzone|
|
1038
|
+
tz_mult = thermalzone.multiplier.to_f
|
1039
|
+
thermalzone.equipment.sort.each do |eq|
|
1040
|
+
tz_eq_cost = 0
|
1041
|
+
terminal, box_name = get_airloop_terminal_type(eq: eq)
|
1042
|
+
next if box_name.nil?
|
1043
|
+
if terminal.isMaximumAirFlowRateAutosized.to_bool
|
1044
|
+
query = "SELECT Value FROM ComponentSizes WHERE CompName='#{eq.name.to_s.upcase}' AND Description='Design Size Maximum Air Flow Rate'"
|
1045
|
+
air_m3_per_s = model.sqlFile().get().execAndReturnFirstDouble(query).to_f/tz_mult
|
1046
|
+
else
|
1047
|
+
air_m3_per_s = terminal.maximumAirFlowRate.to_f/tz_mult
|
1048
|
+
end
|
1049
|
+
tz_centroids = prototype_creator.thermal_zone_get_centroid_per_floor(thermalzone)
|
1050
|
+
reheat_recool_tags << thermalzone.name.to_s
|
1051
|
+
if box_name == 'CVMixingBoxes'
|
1052
|
+
reheat_recool_tags << "Contant Volume Mixing Box" unless vent_tags.empty?
|
1053
|
+
tz_eq_cost, box_info = reheat_coil_costing(terminal: terminal, tz_centroids: tz_centroids, model: model, tz: thermalzone, roof_cent: roof_cent, tz_mult: tz_mult, mech_sizing_info: mech_sizing_info, air_m3_per_s: air_m3_per_s, box_name: box_name, vent_tags: reheat_recool_tags, report_mult: (tz_mult*report_mult))
|
1054
|
+
reheat_recool_tags.pop()
|
1055
|
+
else
|
1056
|
+
reheat_recool_tags << "VAV" unless vent_tags.empty?
|
1057
|
+
tz_eq_cost, box_info = vav_cost(terminal: terminal, tz_centroids: tz_centroids, tz: thermalzone, roof_cent: roof_cent, mech_sizing_info: mech_sizing_info, air_flow_m3_per_s: air_m3_per_s, box_name: box_name, vent_tags: reheat_recool_tags, report_mult: (tz_mult*report_mult))
|
1058
|
+
reheat_recool_tags.pop()
|
1059
|
+
end
|
1060
|
+
reheat_recool_tags.pop()
|
1061
|
+
heat_cost += tz_mult*tz_eq_cost
|
1062
|
+
out_reheat_array << {
|
1063
|
+
terminal: (terminal.iddObjectType.valueName.to_s)[3..-1],
|
1064
|
+
zone_mult: tz_mult,
|
1065
|
+
box_type: box_name,
|
1066
|
+
box_name: terminal.nameString,
|
1067
|
+
unit_info: box_info,
|
1068
|
+
cost: tz_eq_cost.round(2)
|
1069
|
+
}
|
1070
|
+
end
|
1071
|
+
end
|
1072
|
+
return heat_cost, out_reheat_array
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
def get_airloop_terminal_type(eq:)
|
1076
|
+
case eq.iddObject.name
|
1077
|
+
when /OS:AirTerminal:SingleDuct:ConstantVolume:Reheat/
|
1078
|
+
terminal = eq.to_AirTerminalSingleDuctConstantVolumeReheat.get
|
1079
|
+
box_name = 'CVMixingBoxes'
|
1080
|
+
when /OS:AirTerminal:SingleDuct:VAV:NoReheat/
|
1081
|
+
terminal = eq.to_AirTerminalSingleDuctVavNoReheat.get
|
1082
|
+
box_name = 'VAVFanMixingBoxesClg'
|
1083
|
+
when /OS:AirTerminal:SingleDuct:VAV:Reheat/
|
1084
|
+
terminal = eq.to_AirTerminalSingleDuctVAVReheat.get
|
1085
|
+
box_name = 'VAVFanMixingBoxesHtg'
|
1086
|
+
when /OS:AirTerminal:SingleDuct:ConstantVolume:NoReheat/
|
1087
|
+
terminal = eq.to_AirTerminalSingleDuctConstantVolumeNoReheat.get
|
1088
|
+
box_nam = nil
|
1089
|
+
else
|
1090
|
+
terminal = nil
|
1091
|
+
box_name = nil
|
1092
|
+
end
|
1093
|
+
return terminal, box_name
|
1094
|
+
end
|
1095
|
+
|
1096
|
+
def reheat_coil_costing(terminal:, tz_centroids:, model:, tz:, roof_cent:, tz_mult:, mech_sizing_info:, air_m3_per_s:, box_name:, vent_tags: [], report_mult: 1.0)
|
1097
|
+
coil_tags = vent_tags.clone
|
1098
|
+
coil_mat = 'none'
|
1099
|
+
coil_cost = 0
|
1100
|
+
coil = terminal.reheatCoil
|
1101
|
+
case coil.iddObject.name
|
1102
|
+
when /Water/
|
1103
|
+
coil = coil.to_CoilHeatingWater.get
|
1104
|
+
if coil.isRatedCapacityAutosized
|
1105
|
+
capacity = coil.autosizedRatedCapacity.to_f/(1000.0*tz_mult)
|
1106
|
+
else
|
1107
|
+
capacity = coil.ratedCapacity.to_f/(1000.0*tz_mult)
|
1108
|
+
end
|
1109
|
+
coil_mat = 'Coils'
|
1110
|
+
coil_tags << "water coil" unless coil_tags.empty?
|
1111
|
+
when /Electric/
|
1112
|
+
coil = coil.to_CoilHeatingElectric.get
|
1113
|
+
if coil.isNominalCapacityAutosized.to_bool
|
1114
|
+
capacity = (coil.autosizedNominalCapacity.to_f)/(1000.0*tz_mult)
|
1115
|
+
else
|
1116
|
+
capacity = (coil.nominalCapacity.to_f)/(1000.0*tz_mult)
|
1117
|
+
end
|
1118
|
+
coil_mat = 'ElecDuct'
|
1119
|
+
coil_tags << "electric duct heater" unless coil_tags.empty?
|
1120
|
+
end
|
1121
|
+
return 0, {size_kw: 0.0, air_flow_m3_per_s: 0.0, pipe_dist_m: 0.0, elect_dist_m: 0.0, num_units: 0} if coil_mat == 'none'
|
1122
|
+
pipe_length_m = 0
|
1123
|
+
elect_length_m = 0
|
1124
|
+
num_coils = 0
|
1125
|
+
tz_centroids.sort.each do |tz_cent|
|
1126
|
+
coil_tags << tz_cent[:story_name]
|
1127
|
+
story_floor_area = 0
|
1128
|
+
num_coils += 1
|
1129
|
+
tz_cent[:spaces].each { |space| story_floor_area += space.floorArea.to_f }
|
1130
|
+
floor_area_frac = (story_floor_area/tz.floorArea).round(2)
|
1131
|
+
floor_cap = floor_area_frac*capacity
|
1132
|
+
coil_cost += get_mech_costing(mech_name: coil_mat, size: floor_cap, terminal: terminal, vent_tags: coil_tags, report_mult: report_mult)
|
1133
|
+
coil_cost += get_mech_costing(mech_name: box_name, size: floor_area_frac*(OpenStudio.convert(air_m3_per_s, 'm^3/s', 'cfm').get), terminal: terminal, vent_tags: coil_tags, report_mult: report_mult)
|
1134
|
+
ut_dist = (tz_cent[:centroid][0].to_f - roof_cent[:roof_centroid][0].to_f).abs + (tz_cent[:centroid][1].to_f - roof_cent[:roof_centroid][1].to_f).abs
|
1135
|
+
if coil_mat == 'Coils'
|
1136
|
+
pipe_length_m += ut_dist
|
1137
|
+
coil_cost += piping_cost(pipe_dist_m: ut_dist, mech_sizing_info: mech_sizing_info, air_m3_per_s: air_m3_per_s, vent_tags: coil_tags, report_mult: report_mult)
|
1138
|
+
end
|
1139
|
+
elect_length_m += ut_dist
|
1140
|
+
coil_cost += vent_box_elec_cost(cond_dist_m: ut_dist, vent_tags: coil_tags, report_mult: report_mult)
|
1141
|
+
coil_tags.pop()
|
1142
|
+
end
|
1143
|
+
box_info = {size_kw: capacity.round(3), air_flow_m3_per_s: air_m3_per_s.round(3), pipe_dist_m: pipe_length_m.round(1), elect_dist_m: elect_length_m.round(1), num_units: num_coils}
|
1144
|
+
return coil_cost, box_info
|
1145
|
+
end
|
1146
|
+
|
1147
|
+
def vav_cost(terminal:, tz_centroids:, tz:, roof_cent:, mech_sizing_info:, air_flow_m3_per_s:, box_name:, vent_tags: [], report_mult: 1.0)
|
1148
|
+
cost = 0
|
1149
|
+
pipe_length_m = 0
|
1150
|
+
elect_length_m = 0
|
1151
|
+
num_coils = 0
|
1152
|
+
tz_centroids.sort.each do |tz_cent|
|
1153
|
+
vav_tags = vent_tags.clone
|
1154
|
+
vav_tags << tz_cent[:story_name] unless vav_tags.empty?
|
1155
|
+
num_coils += 1
|
1156
|
+
story_floor_area = 0
|
1157
|
+
tz_cent[:spaces].each { |space| story_floor_area += space.floorArea.to_f }
|
1158
|
+
floor_area_frac = (story_floor_area/tz.floorArea).round(2)
|
1159
|
+
cost += get_mech_costing(mech_name: box_name, size: floor_area_frac*(OpenStudio.convert(air_flow_m3_per_s, 'm^3/s', 'cfm').get), terminal: terminal, vent_tags: vav_tags, report_mult: report_mult)
|
1160
|
+
ut_dist = (tz_cent[:centroid][0].to_f - roof_cent[:roof_centroid][0].to_f).abs + (tz_cent[:centroid][1].to_f - roof_cent[:roof_centroid][1].to_f).abs
|
1161
|
+
if /Htg/.match(box_name)
|
1162
|
+
pipe_length_m += ut_dist
|
1163
|
+
cost += piping_cost(pipe_dist_m: ut_dist, mech_sizing_info: mech_sizing_info, air_m3_per_s: floor_area_frac*air_flow_m3_per_s, vent_tags: vav_tags, report_mult: report_mult)
|
1164
|
+
end
|
1165
|
+
elect_length_m += ut_dist
|
1166
|
+
cost += vent_box_elec_cost(cond_dist_m: ut_dist, vent_tags: vav_tags, report_mult: report_mult)
|
1167
|
+
end
|
1168
|
+
box_info = {size_kw: 0.0, air_flow_m3_per_s: air_flow_m3_per_s.round(3), pipe_dist_m: pipe_length_m.round(1), elect_dist_m: elect_length_m.round(1), num_units: num_coils}
|
1169
|
+
return cost, box_info
|
1170
|
+
end
|
1171
|
+
|
1172
|
+
# This method gets the cost of a piece of equipment. I takes the following in:
|
1173
|
+
# mech_name: The category or type of equipment that is being searched for in the 'Material' column of the
|
1174
|
+
# 'materials_hvac' sheet of the costing spreadsheet.
|
1175
|
+
# size: The size of the piece of equipment being searched for.
|
1176
|
+
# terminal: The openstudio object being costed (used to let the user know if there is an issue finding costing info).
|
1177
|
+
# mult: A switch which is used to determine if you want to cost multiple pieces of equipment. If it is set to true
|
1178
|
+
# (the default) then if a piece of equipment is too large to be costed, then multiple smaller pieces of equipment will
|
1179
|
+
# be costed. If it is set to false, then only 1 of the largest piece of equipment will be costed.
|
1180
|
+
def get_mech_costing(mech_name:, size:, terminal:, use_mult: true, vent_tags: [], report_mult: 1.0)
|
1181
|
+
mech_cost_tags = vent_tags.clone
|
1182
|
+
# Turn the input into something that the get_vent_cost_data method can use.
|
1183
|
+
mech_info = {
|
1184
|
+
cat_search: mech_name,
|
1185
|
+
mech_capacity_kw: size,
|
1186
|
+
supply_component: terminal
|
1187
|
+
}
|
1188
|
+
# Get the costing information and multiplier (if the piece of equipment is too large) for the equipment.
|
1189
|
+
mech_mult, cost_info = get_vent_cost_data(equipment_info: mech_info)
|
1190
|
+
# Use only one piece of equipment if use_mult is set to false
|
1191
|
+
mech_mult = 1.0 unless use_mult
|
1192
|
+
# Return the total cost for the piece of equipment.
|
1193
|
+
return get_vent_mat_cost(mat_cost_info: cost_info, vent_tags: mech_cost_tags, report_mult: (mech_mult*report_mult))*mech_mult
|
1194
|
+
end
|
1195
|
+
|
1196
|
+
def piping_cost(pipe_dist_m:, mech_sizing_info:, air_m3_per_s:, is_cool: false, vent_tags: [], report_mult: 1.0)
|
1197
|
+
piping_tags = vent_tags.clone
|
1198
|
+
piping_tags << "piping" unless piping_tags.nil?
|
1199
|
+
pipe_dist = OpenStudio.convert(pipe_dist_m, 'm', 'ft').get
|
1200
|
+
air_flow = (OpenStudio.convert(air_m3_per_s, 'm^3/s', 'L/s').get)
|
1201
|
+
air_flow = 15000 if air_flow > 15000
|
1202
|
+
mech_table = get_mech_table(mech_size_info: mech_sizing_info, table_name: 'piping')
|
1203
|
+
pipe_sz_info = mech_table.select {|pipe_choice|
|
1204
|
+
pipe_choice['ahu_airflow_range_Literpers'][0].to_f.round(0) < air_flow.round(0) and
|
1205
|
+
pipe_choice['ahu_airflow_range_Literpers'][1].to_f.round(0) >= air_flow.round(0)
|
1206
|
+
}.first
|
1207
|
+
pipe_dia = pipe_sz_info['heat_valve_pipe_dia_inch'].to_f.round(2)
|
1208
|
+
pipe_dia = pipe_sz_info['cool_valve_pipe_dia_inch'].to_f.round(2) if is_cool == true
|
1209
|
+
pipe_cost_search = []
|
1210
|
+
pipe_cost_search << {
|
1211
|
+
mat: 'Steelpipe',
|
1212
|
+
unit: 'L.F.',
|
1213
|
+
size: pipe_dia,
|
1214
|
+
mult: 2*pipe_dist
|
1215
|
+
}
|
1216
|
+
pipe_cost_search << {
|
1217
|
+
mat: 'SteelPipeElbow',
|
1218
|
+
unit: 'none',
|
1219
|
+
size: pipe_dia,
|
1220
|
+
mult: 2
|
1221
|
+
}
|
1222
|
+
pipe_cost_search << {
|
1223
|
+
mat: 'SteelPipeTee',
|
1224
|
+
unit: 'none',
|
1225
|
+
size: pipe_dia,
|
1226
|
+
mult: 2
|
1227
|
+
}
|
1228
|
+
pipe_cost_search << {
|
1229
|
+
mat: 'SteelPipeTeeRed',
|
1230
|
+
unit: 'none',
|
1231
|
+
size: pipe_dia,
|
1232
|
+
mult: 2
|
1233
|
+
}
|
1234
|
+
pipe_cost_search << {
|
1235
|
+
mat: 'SteelPipeRed',
|
1236
|
+
unit: 'none',
|
1237
|
+
size: pipe_dia,
|
1238
|
+
mult: 2
|
1239
|
+
}
|
1240
|
+
pipe_dia > 3 ? pipe_dia_union = 3 : pipe_dia_union = pipe_dia
|
1241
|
+
pipe_cost_search << {
|
1242
|
+
mat: 'SteelPipeUnion',
|
1243
|
+
unit: 'none',
|
1244
|
+
size: pipe_dia_union,
|
1245
|
+
mult: 2
|
1246
|
+
}
|
1247
|
+
return get_comp_cost(cost_info: pipe_cost_search, vent_tags: piping_tags, report_mult: report_mult)
|
1248
|
+
end
|
1249
|
+
|
1250
|
+
def vent_box_elec_cost(cond_dist_m:, vent_tags: [], report_mult: 1.0)
|
1251
|
+
elec_tags = vent_tags.clone
|
1252
|
+
elec_tags << "electrical" unless elec_tags.empty?
|
1253
|
+
cond_dist = OpenStudio.convert(cond_dist_m, 'm', 'ft').get
|
1254
|
+
elec_cost_search = []
|
1255
|
+
elec_cost_search << {
|
1256
|
+
mat: 'Wiring',
|
1257
|
+
unit: 'CLF',
|
1258
|
+
size: 14,
|
1259
|
+
mult: cond_dist/100
|
1260
|
+
}
|
1261
|
+
elec_cost_search << {
|
1262
|
+
mat: 'Conduit',
|
1263
|
+
unit: 'L.F.',
|
1264
|
+
size: 0,
|
1265
|
+
mult: cond_dist
|
1266
|
+
}
|
1267
|
+
elec_cost_search << {
|
1268
|
+
mat: 'Box',
|
1269
|
+
unit: 'none',
|
1270
|
+
size: 4,
|
1271
|
+
mult: 1
|
1272
|
+
}
|
1273
|
+
elec_cost_search << {
|
1274
|
+
mat: 'Box',
|
1275
|
+
unit: 'none',
|
1276
|
+
size: 1,
|
1277
|
+
mult: 1
|
1278
|
+
}
|
1279
|
+
return get_comp_cost(cost_info: elec_cost_search, vent_tags: elec_tags, report_mult: report_mult)
|
1280
|
+
end
|
1281
|
+
|
1282
|
+
def get_comp_cost(cost_info:, vent_tags: [], report_mult: 1.0)
|
1283
|
+
vent_comp_tags = vent_tags.clone
|
1284
|
+
cost = 0
|
1285
|
+
cost_info.each do |comp|
|
1286
|
+
comp_info = nil
|
1287
|
+
if comp[:unit].to_s == 'none'
|
1288
|
+
comp_info = @costing_database['raw']['materials_hvac'].select {|data|
|
1289
|
+
data['Material'].to_s.upcase == comp[:mat].to_s.upcase and
|
1290
|
+
data['Size'].to_f.round(2) == comp[:size].to_f.round(2)
|
1291
|
+
}.first
|
1292
|
+
elsif comp[:size].to_f == 0
|
1293
|
+
comp_info = @costing_database['raw']['materials_hvac'].select {|data|
|
1294
|
+
data['Material'].to_s.upcase == comp[:mat].to_s.upcase and
|
1295
|
+
data['unit'].to_s.upcase == comp[:unit].to_s.upcase
|
1296
|
+
}.first
|
1297
|
+
else
|
1298
|
+
comp_info = @costing_database['raw']['materials_hvac'].select {|data|
|
1299
|
+
data['Material'].to_s.upcase == comp[:mat].to_s.upcase and
|
1300
|
+
data['Size'].to_f.round(2) == comp[:size].to_f.round(2) and
|
1301
|
+
data['unit'].to_s.upcase == comp[:unit].to_s.upcase
|
1302
|
+
}.first
|
1303
|
+
end
|
1304
|
+
if comp_info.nil?
|
1305
|
+
puts("No data found for #{comp}!")
|
1306
|
+
raise
|
1307
|
+
end
|
1308
|
+
# report_mult included for cost list output.
|
1309
|
+
cost += get_vent_mat_cost(mat_cost_info: comp_info, vent_tags: vent_comp_tags, report_mult: (comp[:mult].to_f*report_mult))*(comp[:mult].to_f)
|
1310
|
+
end
|
1311
|
+
return cost
|
1312
|
+
end
|
1313
|
+
|
1314
|
+
def get_mech_table(mech_size_info:, table_name:)
|
1315
|
+
table = mech_size_info.select {|hash|
|
1316
|
+
hash['component'].to_s.upcase == table_name.to_s.upcase
|
1317
|
+
}.first
|
1318
|
+
return table['table']
|
1319
|
+
end
|
1320
|
+
|
1321
|
+
# This method finds the centroid of the ceiling line on a given story furthest from the specified point. It only
|
1322
|
+
# takes into account ceilings that above conditioned spaces that are not plenums. A line can be defined between the
|
1323
|
+
# supplied point (we'll call it point O) and the ceiling line centroid furthest from that point(we'll call it point A).
|
1324
|
+
# We will call this line AO. If the full_length input argument is set to true the method will also return the point
|
1325
|
+
# where line AO intercepts the ceiling line on the other side of the building. Note that the method only looks at x
|
1326
|
+
# and y coordinates and ignores the z coordinate of the point you pass it. The method assumes that the ceilings of
|
1327
|
+
# all of the spaces on the floor you pass it are flat so generally ignores their z components as well. This was done
|
1328
|
+
# to avoid further complicating things with 3D geometry. If the ceilings of all of the spaces in the building story
|
1329
|
+
# you pass the method are not flat it will still work but pretend as though the ceilings are flat by ignoring the z
|
1330
|
+
# coordinate.
|
1331
|
+
#
|
1332
|
+
# The method works by going through each space in the supplied building story and finding the ones which are
|
1333
|
+
# conditioned (either heated or cooled) and which are not considered plenums. It then goes through the surfaces of
|
1334
|
+
# the conditioned spaces and finds the ones which have an OpenStudio SurfaceType of 'RoofCeiling'. It then goes
|
1335
|
+
# through each point on that surface and makes lines going from the current point (CP) to the previous point (PP). It
|
1336
|
+
# calculates the centroid (LC) of the line formed between PP and CP by averaging each coordinate of PP and CP. It then
|
1337
|
+
# determines which LC is furthest from the supplied point (point O) and this becomes point A. Note that point A is not
|
1338
|
+
# necessarily on the outside of a building since no checks are made on where line P lies in the building (only that it
|
1339
|
+
# is on a RoofCeiling above a conditioned space that is not a plenum). For example in the LargeOffice building
|
1340
|
+
# archetype point P generally lies on one of the short edges of the trapezoids forming the perimeter spaces. This is
|
1341
|
+
# if this reference point (O) is the center of the building.
|
1342
|
+
#
|
1343
|
+
# The inputs arguments are are:
|
1344
|
+
# building_story: OpenStudio BuildingStory object. A building story defined in OpenStudio.
|
1345
|
+
# prototype_creator: The Openstudio-standards object, containing all of the methods etc. in the nrcan branch of
|
1346
|
+
# Openstudio-standards.
|
1347
|
+
# target_cent: Array. The point you supply from which you want to find the furthest ceiling line centroid (point O
|
1348
|
+
# in the description above). This point should be a one dimensional array containing at least two
|
1349
|
+
# elements target_cent[0] = x, target_cent[1] = y. The array can have more points but they will be
|
1350
|
+
# ignored. This point should be inside the building.
|
1351
|
+
# tol: Float. The tolerence used by the method when rounding geometry (default is 8 digits after decimal).
|
1352
|
+
# full_length: Boolean true/false
|
1353
|
+
# The switch which tells the method whether or not it should find, and supply, the point where line AO (
|
1354
|
+
# as defined above) intercepts the other side of the building. It is defaulted to false, meaning it
|
1355
|
+
# will only return points A and O. If it set to 'true' it will return the point where line AO
|
1356
|
+
# intercepts the other side of the building. It does this by going through all of the ceiling lines
|
1357
|
+
# in the specified building story and determining if any intercept line AO (let us call each intercepts
|
1358
|
+
# point C). It then runs through each intercept (point C) and determines which C makes line AOC the
|
1359
|
+
# longest.
|
1360
|
+
#
|
1361
|
+
# The output is the following hash.
|
1362
|
+
#
|
1363
|
+
# {
|
1364
|
+
# start_point: Hash. A hash which defines point A and provides a bunch of other information (see below),
|
1365
|
+
# mid_point: Hash. This is a hash containing the array defining the point you passed the method in the first
|
1366
|
+
# place.,
|
1367
|
+
# end_point: Hash. If full_length was set to true then this defines point C and provides a bunch of other
|
1368
|
+
# information (see below). If full_length was not set to false or undefined then this is set to nil.
|
1369
|
+
#
|
1370
|
+
# The structure of the hashes start_point and end_point are identical. I will only define the hash start_point below
|
1371
|
+
# noting differences for end_point.
|
1372
|
+
#
|
1373
|
+
# start_point: {
|
1374
|
+
# space: OpenStudio Space object. The space that contains point A (or point C if in the end_point hash).,
|
1375
|
+
# surface: OpenStudio Surface object. The surface in space that contains point A (should have a RoofCeiling
|
1376
|
+
# SpaceType). In the case of the end_point hash this is the surface that contains point C.,
|
1377
|
+
# verts: Two dimmensional array. The points defining ':surface'. These points are in the building coordinate
|
1378
|
+
# system (rather than the space coordinate system). These points are ordered clockwise when viewed with the
|
1379
|
+
# surface normal pointed towards the viewer. The array would be structured as follows:
|
1380
|
+
# [1st point, 2nd point, ..., last point]. Each point is an array as follows: [x coord, y coord, z coord].
|
1381
|
+
# The points are in meters.,
|
1382
|
+
# line: Hash. A hash defining the line containing point A (point C if this is in the 'end_point' hash). See
|
1383
|
+
# definition below.
|
1384
|
+
# }
|
1385
|
+
#
|
1386
|
+
# 'line' has the identical structure in the start_point and end_point hashes. I will define it once but note any
|
1387
|
+
# differences for when it is containing in the start_point and end_point hashes.
|
1388
|
+
#
|
1389
|
+
# line: {
|
1390
|
+
# verta: Array. The end point of the line containing point A (when in the start_point hash) or point C (when in
|
1391
|
+
# the end_point hash). It is formed as [x, y, z]. It is in the building coordinate system, in meters.
|
1392
|
+
# ventb: Array. The start point of the line containing point A (when in the start_point hash) or point C (when in
|
1393
|
+
# the end_point hash). It is formed as [x, y, z]. It is in the building coordinate system, in meters.
|
1394
|
+
# int: Array. If this is in the start_point hash then this is the centre of the line from vertb to verta. If this
|
1395
|
+
# is in the end_point hash then this is the intercept of the line AO with the line starting with vertb and
|
1396
|
+
# ending with verta. It is formed as [x, y, z]. It is in the building coordinate system, in meters. If in
|
1397
|
+
# the start_point hash then the z coordinate is the average of the z coordinates of verta and vertb. If in
|
1398
|
+
# the end_point hash then the z coordinate is calculated by first determining of the distance of the line
|
1399
|
+
# between vertb and verta when only using their x and y coordinates (we will call it the xy_dist). Then the
|
1400
|
+
# distance from just the x and y coordinates of ventb to the x and y coordinates (the only ones provided) of
|
1401
|
+
# point C is determined (we will call it the c_dist). The fraction c_dist/xy_dist is then found and added to
|
1402
|
+
# the z coordinate of ventb thus providing the z coordinate of point C.
|
1403
|
+
# i: Integer. The index of verta in the verts array.
|
1404
|
+
# ip: Integer. The index of vertb in the verts array.
|
1405
|
+
# dist: If in the start_point hash this is the distance between point A and point O using only the x and y
|
1406
|
+
# coordinates of the respective points. If in the end_point hash this is the distance between point A and
|
1407
|
+
# point C using only the x and y coordinates of the respective points. In meters.
|
1408
|
+
# }
|
1409
|
+
#
|
1410
|
+
def get_story_cent_to_edge(building_story:, prototype_creator:, target_cent:, tol: 8, full_length: false)
|
1411
|
+
ceiling_start = []
|
1412
|
+
space_mod = OpenstudioStandards::Space
|
1413
|
+
building_story.spaces.sort.each do |space|
|
1414
|
+
if (space_mod.space_heated?(space) || space_mod.space_cooled?(space)) && !space_mod.space_plenum?(space)
|
1415
|
+
origin = [space.xOrigin.to_f, space.yOrigin.to_f, space.zOrigin.to_f]
|
1416
|
+
space.surfaces.each do |surface|
|
1417
|
+
if surface.surfaceType.to_s.upcase == 'ROOFCEILING'
|
1418
|
+
verts = surface.vertices
|
1419
|
+
dists = []
|
1420
|
+
surf_verts = []
|
1421
|
+
for index in 1..verts.length
|
1422
|
+
index == verts.length ? i = 0 : i = index
|
1423
|
+
i == 0 ? ip = verts.length - 1 : ip = i - 1
|
1424
|
+
verta = [verts[i].x.to_f + origin[0], verts[i].y.to_f + origin[1], verts[i].z.to_f + origin[2]]
|
1425
|
+
vertb = [verts[ip].x.to_f + origin[0], verts[ip].y.to_f + origin[1], verts[ip].z.to_f + origin[2]]
|
1426
|
+
cent = [(verta[0] + vertb[0])/2.0 , (verta[1] + vertb[1])/2.0, (verta[2] + vertb[2])/2.0]
|
1427
|
+
dist = Math.sqrt((target_cent[0].to_f - cent[0])**2 + (target_cent[1].to_f - cent[1])**2)
|
1428
|
+
dists << {
|
1429
|
+
verta: verta,
|
1430
|
+
vertb: vertb,
|
1431
|
+
int: cent,
|
1432
|
+
i: i,
|
1433
|
+
ip: ip,
|
1434
|
+
dist: dist
|
1435
|
+
}
|
1436
|
+
surf_verts << vertb
|
1437
|
+
end
|
1438
|
+
max_dist = dists.max_by{|dist_el| dist_el[:dist].to_f}
|
1439
|
+
ceiling_start << {
|
1440
|
+
space: space,
|
1441
|
+
surface: surface,
|
1442
|
+
verts: surf_verts,
|
1443
|
+
line: max_dist
|
1444
|
+
}
|
1445
|
+
end
|
1446
|
+
end
|
1447
|
+
end
|
1448
|
+
end
|
1449
|
+
|
1450
|
+
return nil if ceiling_start.empty?
|
1451
|
+
|
1452
|
+
furthest_line = ceiling_start.max_by{|wall| wall[:line][:dist].to_f}
|
1453
|
+
|
1454
|
+
return {start_point: furthest_line, mid_point: target_cent, end_point: nil} unless full_length
|
1455
|
+
|
1456
|
+
x_dist_ref = (furthest_line[:line][:int][0].round(tol) - target_cent[0].round(tol))
|
1457
|
+
x_dist_ref == 1 if x_dist_ref == 0
|
1458
|
+
y_dist_ref = (furthest_line[:line][:int][1].round(tol) - target_cent[1].round(tol))
|
1459
|
+
y_dist_ref == 1 if y_dist_ref == 0
|
1460
|
+
x_side_ref = x_dist_ref/x_dist_ref.abs
|
1461
|
+
y_side_ref = y_dist_ref/y_dist_ref.abs
|
1462
|
+
linea_eq = get_line_eq(a: target_cent, b: furthest_line[:line][:int], tol: tol)
|
1463
|
+
ints = []
|
1464
|
+
ceiling_start.each do |side|
|
1465
|
+
verts = side[:verts]
|
1466
|
+
for index in 1..(verts.length)
|
1467
|
+
index == verts.length ? i = 0 : i = index
|
1468
|
+
i == 0 ? ip = verts.length-1 : ip = i - 1
|
1469
|
+
lineb = [verts[i], verts[ip]]
|
1470
|
+
int = line_int(line_seg: lineb, line: linea_eq, tol: tol)
|
1471
|
+
next if int.nil?
|
1472
|
+
x_dist = (int[0].round(tol) - target_cent[0].round(tol))
|
1473
|
+
x_dist = 1 if x_dist == 0
|
1474
|
+
y_dist = (int[1].round(tol) - target_cent[1].round(tol))
|
1475
|
+
y_dist = 1 if y_dist == 0
|
1476
|
+
x_side = x_dist/x_dist.abs
|
1477
|
+
y_side = y_dist/y_dist.abs
|
1478
|
+
next if x_side == x_side_ref && y_side == y_side_ref
|
1479
|
+
ceil_dist = Math.sqrt((furthest_line[:line][:int][0] - int[0])**2 + (furthest_line[:line][:int][1] - int[1])**2)
|
1480
|
+
int_dist = Math.sqrt((int[0] - verts[ip][0])**2 + (int[1] - verts[ip][1])**2)
|
1481
|
+
line_dist = Math.sqrt((verts[i][0] - verts[ip][0])**2 + (verts[i][1] - verts[ip][1])**2)
|
1482
|
+
z_coord = verts[ip][2] + ((verts[i][2] - verts[ip][2])*int_dist/line_dist)
|
1483
|
+
ints << {
|
1484
|
+
ceiling_info: side,
|
1485
|
+
line: lineb,
|
1486
|
+
int: [int[0], int[1], z_coord],
|
1487
|
+
i: i,
|
1488
|
+
ip: ip,
|
1489
|
+
dist: ceil_dist
|
1490
|
+
}
|
1491
|
+
end
|
1492
|
+
end
|
1493
|
+
|
1494
|
+
return nil if ints.empty?
|
1495
|
+
end_wall = ints.max_by{|wall| wall[:dist].to_f}
|
1496
|
+
return {
|
1497
|
+
start_point: furthest_line,
|
1498
|
+
mid_point: target_cent,
|
1499
|
+
end_point: {
|
1500
|
+
space: end_wall[:ceiling_info][:space],
|
1501
|
+
surface: end_wall[:ceiling_info][:surface],
|
1502
|
+
verts: end_wall[:ceiling_info][:verts],
|
1503
|
+
line: {
|
1504
|
+
verta: end_wall[:line][0],
|
1505
|
+
vertb: end_wall[:line][1],
|
1506
|
+
int: end_wall[:int],
|
1507
|
+
i: end_wall[:i],
|
1508
|
+
ip: end_wall[:ip],
|
1509
|
+
dist: end_wall[:dist]
|
1510
|
+
},
|
1511
|
+
}
|
1512
|
+
}
|
1513
|
+
end
|
1514
|
+
|
1515
|
+
def get_line_eq(a:, b:, tol: 8)
|
1516
|
+
if a[0].round(tol) == b[0].round(tol) and a[1].round(tol) == b[1].round(tol)
|
1517
|
+
return {
|
1518
|
+
slope: 0,
|
1519
|
+
int: 0,
|
1520
|
+
inf: true
|
1521
|
+
}
|
1522
|
+
elsif a[0].round(tol) == b[0].round(tol)
|
1523
|
+
return {
|
1524
|
+
slope: a[0].round(tol),
|
1525
|
+
int: 1,
|
1526
|
+
inf: true
|
1527
|
+
}
|
1528
|
+
else
|
1529
|
+
slope = (b[1].round(tol) - a[1].round(tol))/(b[0].round(tol) - a[0].round(tol))
|
1530
|
+
int = a[1].round(tol) - (slope*a[0].round(tol))
|
1531
|
+
end
|
1532
|
+
return {
|
1533
|
+
slope: slope,
|
1534
|
+
int: int,
|
1535
|
+
inf: false
|
1536
|
+
}
|
1537
|
+
end
|
1538
|
+
|
1539
|
+
def line_int(line_seg:, line:, tol: 8)
|
1540
|
+
line[:inf] == true && line[:int] == 1 ? x_cross = line[:slope] : x_cross = nil
|
1541
|
+
if line_seg[0][0].round(tol) == line_seg[1][0].round(tol) && line_seg[0][1].round(tol) == line_seg[1][1].round(tol)
|
1542
|
+
if x_cross.nil?
|
1543
|
+
y_val = line[:slope]*line_seg[0][0] + line[:int]
|
1544
|
+
y_val.round(tol) == line_seg[0][1].round(tol) ? (return line_seg[0]) : (return nil)
|
1545
|
+
else
|
1546
|
+
x_cross.round(tol) == line_seg[0][0].round(tol) ? (return line_seg[0]) : (return nil)
|
1547
|
+
end
|
1548
|
+
elsif line_seg[0][0].round(tol) == line_seg[1][0]
|
1549
|
+
if x_cross.nil?
|
1550
|
+
y_val = line[:slope]*line_seg[0][0] + line[:int]
|
1551
|
+
if (line_seg[0][1].round(tol) >= y_val.round(tol) && y_val.round(tol) >= line_seg[1][1].round(tol)) ||
|
1552
|
+
(line_seg[0][1].round(tol) <= y_val.round(tol) && y_val.round(tol) <= line_seg[1][1].round(tol))
|
1553
|
+
return [line_seg[0][0] , y_val, line_seg[0][2]]
|
1554
|
+
else
|
1555
|
+
return nil
|
1556
|
+
end
|
1557
|
+
else
|
1558
|
+
if x_cross.round(tol) == line_seg[0][0]
|
1559
|
+
y_val = (line_seg[0][1] + line_seg[1][1])/2
|
1560
|
+
return [line_seg[0][0] , y_val, line_seg[0][2]]
|
1561
|
+
else
|
1562
|
+
return nil
|
1563
|
+
end
|
1564
|
+
end
|
1565
|
+
end
|
1566
|
+
lineb = get_line_eq(a: line_seg[0], b: line_seg[1], tol: tol)
|
1567
|
+
if lineb[:slope].round(tol) == 0 && line[:slope].round(tol) == 0
|
1568
|
+
if x_cross.nil?
|
1569
|
+
if lineb[:int].round(tol) == line[:int].round(tol)
|
1570
|
+
x_val = (line_seg[0][0] + line_seg[1][0])/2
|
1571
|
+
return [x_val, lineb[:slope], line_seg[0][2]]
|
1572
|
+
else
|
1573
|
+
return nil
|
1574
|
+
end
|
1575
|
+
else
|
1576
|
+
if (line_seg[0][0].round(tol) <= x_cross.round(tol) && x_cross.round(tol) <= line_seg[1][0].round(tol)) ||
|
1577
|
+
(line_seg[0][0].round(tol) >= x_cross.round(tol) && x_cross.round(tol) >= line_seg[1][0].round(tol))
|
1578
|
+
[x_cross, lineb[:slope]]
|
1579
|
+
else
|
1580
|
+
return nil
|
1581
|
+
end
|
1582
|
+
end
|
1583
|
+
end
|
1584
|
+
unless x_cross.nil?
|
1585
|
+
if (line_seg[0][0].round(tol) <= x_cross.round(tol) && x_cross.round(tol) <= line_seg[1][0].round(tol)) ||
|
1586
|
+
(line_seg[0][0].round(tol) >= x_cross.round(tol) && x_cross.round(tol) >= line_seg[1][0].round(tol))
|
1587
|
+
y_val = lineb[:slope]*x_cross + lineb[:int]
|
1588
|
+
return [x_cross , y_val, line_seg[0][2]]
|
1589
|
+
else
|
1590
|
+
return nil
|
1591
|
+
end
|
1592
|
+
end
|
1593
|
+
if lineb[:inf] == true && lineb[:int] == 1
|
1594
|
+
x_int = lineb[:slope]
|
1595
|
+
y_int = line[:slope].to_f*x_int + line[:int].to_f
|
1596
|
+
else
|
1597
|
+
x_int = (lineb[:int].to_f - line[:int].to_f)/(line[:slope].to_f - lineb[:slope].to_f)
|
1598
|
+
y_int = lineb[:slope].to_f*x_int + lineb[:int].to_f
|
1599
|
+
end
|
1600
|
+
if (line_seg[0][0].round(tol) <= x_int.round(tol) && x_int.round(tol) <= line_seg[1][0].round(tol)) ||
|
1601
|
+
(line_seg[0][0].round(tol) >= x_int.round(tol) && x_int.round(tol) >= line_seg[1][0].round(tol))
|
1602
|
+
if (line_seg[0][1].round(tol) >= y_int.round(tol) && y_int.round(tol) >= line_seg[1][1].round(tol)) ||
|
1603
|
+
(line_seg[0][1].round(tol) <= y_int.round(tol) && y_int.round(tol) <= line_seg[1][1].round(tol))
|
1604
|
+
return [x_int, y_int, line_seg[0][2]]
|
1605
|
+
end
|
1606
|
+
end
|
1607
|
+
return nil
|
1608
|
+
end
|
1609
|
+
|
1610
|
+
def line_seg_int(linea:, lineb:, tol: 8)
|
1611
|
+
if linea[0][0].round(tol) == lineb[0][0].round(tol) && linea[0][1].round(tol) == lineb[0][1].round(tol) &&
|
1612
|
+
linea[1][0].round(tol) == lineb[1][0].round(tol) && linea[1][1].round(tol) == lineb[1][1].round(tol)
|
1613
|
+
return [(linea[0][0] + linea[1][0])/2 , (linea[0][1] + linea[1][1])/2]
|
1614
|
+
elsif linea[0][0].round(tol) == linea[1][0].round(tol) && linea[0][1].round(tol) == linea[1][1].round(tol)
|
1615
|
+
return linea[0]
|
1616
|
+
elsif lineb[0][0].round(tol) == lineb[1][0].round(tol) && lineb[0][1].round(tol) == lineb[1][1].round(tol)
|
1617
|
+
return lineb[0]
|
1618
|
+
end
|
1619
|
+
|
1620
|
+
o1 = get_orient(p: linea[0], q: linea[1], r: lineb[0], tol: tol)
|
1621
|
+
o2 = get_orient(p: linea[0], q: linea[1], r: lineb[1], tol: tol)
|
1622
|
+
o3 = get_orient(p: lineb[0], q: lineb[1], r: linea[0], tol: tol)
|
1623
|
+
o4 = get_orient(p: lineb[0], q: lineb[1], r: linea[1], tol: tol)
|
1624
|
+
|
1625
|
+
int_sect = 0
|
1626
|
+
int_sect = 1 if o1 != o2 && o3 != o4
|
1627
|
+
return lineb[0] if o1 == 0 && point_on_line(p: linea[0], q: lineb[0], r: linea[1], tol: tol)
|
1628
|
+
return lineb[1] if o2 == 0 && point_on_line(p: linea[0], q: lineb[1], r: linea[1], tol: tol)
|
1629
|
+
return linea[0] if o3 == 0 && point_on_line(p: lineb[0], q: linea[0], r: lineb[1], tol: tol)
|
1630
|
+
return linea[1] if o4 == 0 && point_on_line(p: lineb[0], q: linea[1], r: lineb[1], tol: tol)
|
1631
|
+
|
1632
|
+
return nil if int_sect == 0
|
1633
|
+
|
1634
|
+
eq_linea = get_line_eq(a: linea[0], b: linea[1], tol: tol)
|
1635
|
+
eq_lineb = get_line_eq(a: lineb[0], b: lineb[1], tol: tol)
|
1636
|
+
if eq_linea[:inf] == true && eq_linea[:slope].to_f == 1
|
1637
|
+
x_int = linea[0][0]
|
1638
|
+
y_int = eq_lineb[:slope].to_f*x_int + eq_lineb[:int].to_f
|
1639
|
+
return [x_int, y_int]
|
1640
|
+
elsif eq_lineb[:inf] == true && eq_lineb[:slope].to_f == 1
|
1641
|
+
x_int = lineb[0][0]
|
1642
|
+
y_int = eq_linea[:slope].to_f*x_int + eq_linea[:int].to_f
|
1643
|
+
return [x_int, y_int]
|
1644
|
+
else
|
1645
|
+
x_int = (eq_lineb[:int].to_f - eq_linea[:int].to_f) / (eq_linea[:slope].to_f - eq_lineb[:slope].to_f)
|
1646
|
+
y_int = eq_lineb[:slope].to_f*x_int + eq_lineb[:int].to_f
|
1647
|
+
return [x_int, y_int]
|
1648
|
+
end
|
1649
|
+
end
|
1650
|
+
|
1651
|
+
def get_orient(p:, q:, r:, tol: 8)
|
1652
|
+
orient = (q[1].round(tol) - p[1].round(tol))*(r[0].round(tol) - q[0].round(tol)) - (q[0].round(tol) - p[0].round(tol))*(r[1].round(tol) - q[1].round(tol))
|
1653
|
+
return 0 if orient == 0
|
1654
|
+
orient > 0 ? (return 1) : (return 2)
|
1655
|
+
end
|
1656
|
+
|
1657
|
+
def point_on_line(p:, q:, r:, tol: 8)
|
1658
|
+
q[0].round(tol) <= [p[0].round(tol), r[0].round(tol)].max ? crita = true : crita = false
|
1659
|
+
q[0].round(tol) >= [p[0].round(tol), r[0].round(tol)].min ? critb = true : critb = false
|
1660
|
+
q[1].round(tol) <= [p[1].round(tol), r[1].round(tol)].max ? critc = true : critc = false
|
1661
|
+
q[1].round(tol) >= [p[1].round(tol), r[1].round(tol)].min ? critd = true : critd = false
|
1662
|
+
return true if crita && critb && critc && critd
|
1663
|
+
return false
|
1664
|
+
end
|
1665
|
+
|
1666
|
+
def get_lowest_space(spaces:)
|
1667
|
+
cents = []
|
1668
|
+
spaces.each do |space|
|
1669
|
+
test = space['space']
|
1670
|
+
origin = [space['space'].xOrigin.to_f, space['space'].yOrigin.to_f, space['space'].zOrigin.to_f]
|
1671
|
+
space['space'].surfaces.each do |surface|
|
1672
|
+
if surface.surfaceType.to_s.upcase == 'ROOFCEILING'
|
1673
|
+
cents <<{
|
1674
|
+
space: space['space'],
|
1675
|
+
roof_cent: [surface.centroid.x.to_f + origin[0], surface.centroid.y.to_f + origin[1], surface.centroid.z.to_f + origin[2]]
|
1676
|
+
}
|
1677
|
+
end
|
1678
|
+
end
|
1679
|
+
end
|
1680
|
+
min_space = cents.min_by{|cent| cent[:roof_cent][2]}
|
1681
|
+
return min_space
|
1682
|
+
end
|
1683
|
+
|
1684
|
+
def vent_trunk_duct_cost(tot_air_m3pers:, min_space:, roof_cent:, mech_sizing_info:, sys_1_4:)
|
1685
|
+
sys_1_4 ? overall_mult = 1 : overall_mult = 2
|
1686
|
+
duct_cost_search = []
|
1687
|
+
mech_table = get_mech_table(mech_size_info: mech_sizing_info, table_name: 'trunk')
|
1688
|
+
max_trunk_line = mech_table.max_by {|entry| entry['max_flow_range_m3pers'][0]}
|
1689
|
+
tot_air_m3pers = max_trunk_line['max_flow_range_m3pers'][0].to_f.round(2) if tot_air_m3pers.round(2) > max_trunk_line['max_flow_range_m3pers'][1].to_f.round(2)
|
1690
|
+
trunk_sz_info = mech_table.select {|trunk_choice|
|
1691
|
+
trunk_choice['max_flow_range_m3pers'][0].to_f.round(2) < tot_air_m3pers.round(2) and
|
1692
|
+
trunk_choice['max_flow_range_m3pers'][1].to_f.round(2) >= tot_air_m3pers.round(2)
|
1693
|
+
}.first
|
1694
|
+
duct_dia = trunk_sz_info['duct_dia_inch']
|
1695
|
+
duct_length_m = (roof_cent[:roof_centroid][2].to_f - min_space[:roof_cent][2].to_f).abs
|
1696
|
+
duct_length = (OpenStudio.convert(duct_length_m, 'm', 'ft').get)
|
1697
|
+
duct_cost_search << {
|
1698
|
+
mat: 'Ductwork-S',
|
1699
|
+
unit: 'L.F.',
|
1700
|
+
size: duct_dia,
|
1701
|
+
mult: duct_length*overall_mult
|
1702
|
+
}
|
1703
|
+
duct_area = (duct_dia/12)*Math::PI*duct_length*overall_mult
|
1704
|
+
duct_cost_search << {
|
1705
|
+
mat: 'Ductinsulation',
|
1706
|
+
unit: 'ft2',
|
1707
|
+
size: 1.5,
|
1708
|
+
mult: duct_area
|
1709
|
+
}
|
1710
|
+
duct_cost = get_comp_cost(cost_info: duct_cost_search)
|
1711
|
+
trunk_duct_info = {
|
1712
|
+
DuctSize_in: duct_dia.round(1),
|
1713
|
+
DuctLength_m: duct_length_m.round(1),
|
1714
|
+
NumberRuns: overall_mult,
|
1715
|
+
DuctCost: duct_cost.round(2)
|
1716
|
+
}
|
1717
|
+
return duct_cost, trunk_duct_info
|
1718
|
+
end
|
1719
|
+
|
1720
|
+
def gen_hvac_info_by_floor(hvac_floors:, model:, prototype_creator:, airloop:, sys_type:, hrv_info:)
|
1721
|
+
airloop.thermalZones.sort.each do |tz|
|
1722
|
+
tz.equipment.sort.each do |eq|
|
1723
|
+
tz_mult = tz.multiplier.to_f
|
1724
|
+
terminal, box_name = get_airloop_terminal_type(eq: eq)
|
1725
|
+
next if terminal.nil?
|
1726
|
+
if terminal.isMaximumAirFlowRateAutosized.to_bool
|
1727
|
+
query = "SELECT Value FROM ComponentSizes WHERE CompName='#{eq.name.to_s.upcase}' AND Description='Design Size Maximum Air Flow Rate'"
|
1728
|
+
tz_air = model.sqlFile().get().execAndReturnFirstDouble(query).to_f/tz_mult
|
1729
|
+
else
|
1730
|
+
tz_air = terminal.maximumAirFlowRate.to_f/tz_mult
|
1731
|
+
end
|
1732
|
+
tz_cents = prototype_creator.thermal_zone_get_centroid_per_floor(tz)
|
1733
|
+
tz_cents.each do |tz_cent|
|
1734
|
+
story_floor_area = 0
|
1735
|
+
tz_outdoor_air_m3ps = 0
|
1736
|
+
tz_cent[:spaces].each do |space|
|
1737
|
+
# Note that space.floorArea gets the floor area for the space only and does not include a thermal zone multiplier.
|
1738
|
+
# Thus the outdoor air flow rate totaled here will be for only one thermal zone and will not include thermal zone multipliers.
|
1739
|
+
story_floor_area += space.floorArea.to_f
|
1740
|
+
outdoor_air_obj = space.designSpecificationOutdoorAir
|
1741
|
+
outdoor_air_obj.is_initialized ? outdoor_air_m3ps = (outdoor_air_obj.get.outdoorAirFlowperFloorArea)*(space.floorArea.to_f) : outdoor_air_m3ps = 0
|
1742
|
+
tz_outdoor_air_m3ps += outdoor_air_m3ps
|
1743
|
+
end
|
1744
|
+
story_obj = tz_cent[:spaces][0].buildingStory.get
|
1745
|
+
floor_area_frac = (story_floor_area/tz.floorArea).round(2)
|
1746
|
+
tz_floor_air = floor_area_frac*tz_air
|
1747
|
+
(sys_type == 1 || sys_type == 4) ? tz_floor_return = 0 : tz_floor_return = tz_floor_air
|
1748
|
+
tz_floor_system = {
|
1749
|
+
story_name: tz_cent[:story_name],
|
1750
|
+
story: story_obj,
|
1751
|
+
sys_name: airloop.nameString,
|
1752
|
+
sys_type: sys_type,
|
1753
|
+
sys_info: airloop,
|
1754
|
+
tz: tz,
|
1755
|
+
tz_mult: tz_mult,
|
1756
|
+
terminal: terminal,
|
1757
|
+
floor_area_frac: floor_area_frac,
|
1758
|
+
tz_floor_area: story_floor_area,
|
1759
|
+
tz_floor_supp_air_m3ps: tz_floor_air,
|
1760
|
+
tz_floor_ret_air_m3ps: tz_floor_return,
|
1761
|
+
tz_floor_outdoor_air_m3ps: tz_outdoor_air_m3ps,
|
1762
|
+
hrv_info: hrv_info,
|
1763
|
+
tz_cent: tz_cent
|
1764
|
+
}
|
1765
|
+
hvac_floors = add_floor_sys(hvac_floors: hvac_floors, tz_floor_sys: tz_floor_system)
|
1766
|
+
end
|
1767
|
+
end
|
1768
|
+
end
|
1769
|
+
return hvac_floors
|
1770
|
+
end
|
1771
|
+
|
1772
|
+
def add_floor_sys(hvac_floors:, tz_floor_sys:)
|
1773
|
+
if hvac_floors.empty?
|
1774
|
+
hvac_floors << {
|
1775
|
+
story_name: tz_floor_sys[:story_name],
|
1776
|
+
story: tz_floor_sys[:story],
|
1777
|
+
supply_air_m3ps: tz_floor_sys[:tz_floor_supp_air_m3ps],
|
1778
|
+
return_air_m3ps: tz_floor_sys[:tz_floor_ret_air_m3ps],
|
1779
|
+
tz_mult: tz_floor_sys[:tz_mult],
|
1780
|
+
tz_num: 1,
|
1781
|
+
floor_tz: [tz_floor_sys]
|
1782
|
+
}
|
1783
|
+
else
|
1784
|
+
found_story = false
|
1785
|
+
hvac_floors.each do |hvac_floor|
|
1786
|
+
if hvac_floor[:story_name].to_s.upcase == tz_floor_sys[:story_name].to_s.upcase
|
1787
|
+
hvac_floor[:supply_air_m3ps] += tz_floor_sys[:tz_floor_supp_air_m3ps]
|
1788
|
+
hvac_floor[:return_air_m3ps] += tz_floor_sys[:tz_floor_ret_air_m3ps]
|
1789
|
+
hvac_floor[:tz_mult] += tz_floor_sys[:tz_mult]
|
1790
|
+
hvac_floor[:tz_num] += 1
|
1791
|
+
hvac_floor[:floor_tz] << tz_floor_sys
|
1792
|
+
found_story = true
|
1793
|
+
end
|
1794
|
+
end
|
1795
|
+
if found_story == false
|
1796
|
+
hvac_floors << {
|
1797
|
+
story_name: tz_floor_sys[:story_name],
|
1798
|
+
story: tz_floor_sys[:story],
|
1799
|
+
supply_air_m3ps: tz_floor_sys[:tz_floor_supp_air_m3ps],
|
1800
|
+
return_air_m3ps: tz_floor_sys[:tz_floor_ret_air_m3ps],
|
1801
|
+
tz_mult: tz_floor_sys[:tz_mult],
|
1802
|
+
tz_num: 1,
|
1803
|
+
floor_tz: [tz_floor_sys]
|
1804
|
+
}
|
1805
|
+
end
|
1806
|
+
end
|
1807
|
+
return hvac_floors
|
1808
|
+
end
|
1809
|
+
|
1810
|
+
def floor_vent_dist_cost(hvac_floors:, prototype_creator:, roof_cent:, mech_sizing_info:)
|
1811
|
+
floor_duct_cost = 0
|
1812
|
+
build_floor_trunk_info = []
|
1813
|
+
mech_table = get_mech_table(mech_size_info: mech_sizing_info, table_name: 'vel_prof')
|
1814
|
+
hvac_floors.each do |hvac_floor|
|
1815
|
+
next if hvac_floor[:tz_num] < 2 && hvac_floor[:floor_tz][0][:sys_type] == 3
|
1816
|
+
tz_floor_mult = (hvac_floor[:tz_mult].to_f)/(hvac_floor[:tz_num].to_f)
|
1817
|
+
floor_trunk_line = get_story_cent_to_edge(building_story: hvac_floor[:story], prototype_creator: prototype_creator, target_cent: roof_cent[:roof_centroid], full_length: true)
|
1818
|
+
current_floor_duct_cost, floor_trunk_info = get_floor_trunk_cost(mech_table: mech_table, hvac_floor: hvac_floor, prototype_creator: prototype_creator, floor_trunk_dist_m: floor_trunk_line[:end_point][:line][:dist])
|
1819
|
+
floor_duct_cost += current_floor_duct_cost*tz_floor_mult
|
1820
|
+
floor_trunk_info[:Floor] = hvac_floor[:story_name]
|
1821
|
+
floor_trunk_info[:Multiplier] = tz_floor_mult
|
1822
|
+
build_floor_trunk_info << floor_trunk_info
|
1823
|
+
end
|
1824
|
+
return floor_duct_cost, build_floor_trunk_info
|
1825
|
+
end
|
1826
|
+
|
1827
|
+
def get_floor_trunk_cost(mech_table:, hvac_floor:, prototype_creator:, floor_trunk_dist_m:, fric_allow: 1)
|
1828
|
+
floor_trunk_info = {
|
1829
|
+
Floor: '',
|
1830
|
+
Predominant_space_type: 0,
|
1831
|
+
SupplyDuctSize_in: 0,
|
1832
|
+
SupplyDuctLength_m: 0,
|
1833
|
+
ReturnDuctSize_in: 0,
|
1834
|
+
ReturnDuctLength_m: 0,
|
1835
|
+
TotalDuctCost: 0,
|
1836
|
+
Multiplier: 1
|
1837
|
+
}
|
1838
|
+
floor_trunk_cost = 0
|
1839
|
+
duct_comp_search = []
|
1840
|
+
floor_trunk_dist = (OpenStudio.convert(floor_trunk_dist_m, 'm', 'ft').get)
|
1841
|
+
space_type = get_predominant_floor_space_type_area(hvac_floor: hvac_floor, prototype_creator: prototype_creator)
|
1842
|
+
floor_trunk_info[:Predominant_space_type] = space_type[:space_type]
|
1843
|
+
loor_vel_fpm = nil
|
1844
|
+
mech_table.each do |vel_prof|
|
1845
|
+
spc_type_name = nil
|
1846
|
+
spc_type_name = vel_prof['space_types'].select {|spc_type|
|
1847
|
+
spc_type.to_s.upcase == space_type[:space_type].to_s.upcase
|
1848
|
+
}.first
|
1849
|
+
floor_vel_fpm = vel_prof['vel_fpm'].to_f unless spc_type_name.nil?
|
1850
|
+
end
|
1851
|
+
floor_vel_fpm = mech_table[mech_table.size - 1]['vel_fpm'].to_f if floor_vel_fpm.nil?
|
1852
|
+
supply_flow_cfm = (OpenStudio.convert(hvac_floor[:supply_air_m3ps], 'm^3/s', 'cfm').get)
|
1853
|
+
sup_cross_in2 = ((supply_flow_cfm*fric_allow)/floor_vel_fpm)*144
|
1854
|
+
sup_dia_in = 2*Math.sqrt(sup_cross_in2/Math::PI)
|
1855
|
+
duct_cost_search = {
|
1856
|
+
mat: 'Ductwork-S',
|
1857
|
+
unit: 'L.F.',
|
1858
|
+
size: sup_dia_in,
|
1859
|
+
mult: floor_trunk_dist
|
1860
|
+
}
|
1861
|
+
duct_cost, comp_info = get_duct_cost(cost_info: duct_cost_search)
|
1862
|
+
floor_trunk_info[:SupplyDuctSize_in] = sup_dia_in.round(2)
|
1863
|
+
floor_trunk_info[:SupplyDuctLength_m] = floor_trunk_dist_m.round(1)
|
1864
|
+
floor_trunk_cost += duct_cost
|
1865
|
+
sup_area_sqrft = (comp_info['Size'].to_f/12)*Math::PI*floor_trunk_dist
|
1866
|
+
duct_comp_search << {
|
1867
|
+
mat: 'Ductinsulation',
|
1868
|
+
unit: 'ft2',
|
1869
|
+
size: 1.5,
|
1870
|
+
mult: sup_area_sqrft
|
1871
|
+
}
|
1872
|
+
if hvac_floor[:return_air_m3ps] == hvac_floor[:supply_air_m3ps]
|
1873
|
+
floor_trunk_cost += duct_cost
|
1874
|
+
duct_comp_search[0][:mult] = sup_area_sqrft*2
|
1875
|
+
floor_trunk_info[:ReturnDuctSize_in] = floor_trunk_info[:SupplyDuctSize_in]
|
1876
|
+
floor_trunk_info[:ReturnDuctLength_m] = floor_trunk_info[:SupplyDuctLength_m]
|
1877
|
+
elsif hvac_floor[:return_air_m3ps].to_f > 0
|
1878
|
+
return_flow_cfm = (OpenStudio.convert(hvac_floor[:return_air_m3ps], 'm^3/s', 'cfm').get)
|
1879
|
+
ret_cross_in2 = ((return_flow_cfm*fric_allow)/floor_vel_fpm)*144
|
1880
|
+
ret_dia_in = 2*Math.sqrt(ret_cross_in2/Math::PI)
|
1881
|
+
duct_cost_search = {
|
1882
|
+
mat: 'Ductwork-S',
|
1883
|
+
unit: 'L.F.',
|
1884
|
+
size: ret_dia_in,
|
1885
|
+
mult: floor_trunk_dist
|
1886
|
+
}
|
1887
|
+
duct_cost, comp_info = get_duct_cost(cost_info: duct_cost_search)
|
1888
|
+
floor_trunk_cost += duct_cost
|
1889
|
+
ret_area_sqrft = (comp_info['Size'].to_f/12)*Math::PI*floor_trunk_dist
|
1890
|
+
duct_comp_search << {
|
1891
|
+
mat: 'Ductinsulation',
|
1892
|
+
unit: 'ft2',
|
1893
|
+
size: 1.5,
|
1894
|
+
mult: ret_area_sqrft
|
1895
|
+
}
|
1896
|
+
floor_trunk_info[:ReturnDuctSize_in] = ret_dia_in.round(2)
|
1897
|
+
floor_trunk_info[:ReturnDuctLength_m] = floor_trunk_dist_m.round(1)
|
1898
|
+
end
|
1899
|
+
floor_trunk_cost += get_comp_cost(cost_info: duct_comp_search)
|
1900
|
+
floor_trunk_info[:TotalDuctCost] = floor_trunk_cost.round(2)
|
1901
|
+
return floor_trunk_cost, floor_trunk_info
|
1902
|
+
end
|
1903
|
+
|
1904
|
+
def get_duct_cost(cost_info:)
|
1905
|
+
comp_info = nil
|
1906
|
+
comp_info_all = @costing_database['raw']['materials_hvac'].select {|data|
|
1907
|
+
data['Material'].to_s.upcase == cost_info[:mat].to_s.upcase and
|
1908
|
+
data['Size'].to_f.round(1) >= cost_info[:size].to_f.round(1) and
|
1909
|
+
data['unit'].to_s.upcase == cost_info[:unit].to_s.upcase
|
1910
|
+
}
|
1911
|
+
if comp_info_all.nil? || comp_info_all.empty?
|
1912
|
+
max_size_info = @costing_database['raw']['materials_hvac'].select {|data|
|
1913
|
+
data['Material'].to_s.upcase == cost_info[:mat].to_s.upcase
|
1914
|
+
}
|
1915
|
+
if max_size_info.nil?
|
1916
|
+
puts("No data found for #{cost_info}!")
|
1917
|
+
raise
|
1918
|
+
end
|
1919
|
+
comp_info = max_size_info.max_by {|element| element['Size'].to_f}
|
1920
|
+
elsif comp_info_all.size == 1
|
1921
|
+
comp_info = comp_info_all[0]
|
1922
|
+
else
|
1923
|
+
comp_info = comp_info_all.min_by{|data| data['Size'].to_f}
|
1924
|
+
end
|
1925
|
+
cost = get_vent_mat_cost(mat_cost_info: comp_info)*cost_info[:mult].to_f
|
1926
|
+
return cost, comp_info
|
1927
|
+
end
|
1928
|
+
|
1929
|
+
def get_predominant_floor_space_type_area(hvac_floor:, prototype_creator:)
|
1930
|
+
spaces = hvac_floor[:story].spaces
|
1931
|
+
space_list = []
|
1932
|
+
space_mod = OpenstudioStandards::Space
|
1933
|
+
spaces.sort.each do |space|
|
1934
|
+
if (space_mod.space_cooled?(space) || space_mod.space_heated?(space)) && !space_mod.space_plenum?(space)
|
1935
|
+
space_type = space.spaceType.get.nameString[15..-1]
|
1936
|
+
if space_list.empty?
|
1937
|
+
space_list << {
|
1938
|
+
space_type: space_type,
|
1939
|
+
floor_area: space.floorArea
|
1940
|
+
}
|
1941
|
+
else
|
1942
|
+
new_space = nil
|
1943
|
+
space_list.each do |spc_lst|
|
1944
|
+
if space_type.upcase == spc_lst[:space_type]
|
1945
|
+
spc_lst[:floor_area] += space.floorArea
|
1946
|
+
else
|
1947
|
+
new_space = {
|
1948
|
+
space_type: space_type,
|
1949
|
+
floor_area: space.floorArea
|
1950
|
+
}
|
1951
|
+
end
|
1952
|
+
end
|
1953
|
+
unless new_space.nil?
|
1954
|
+
space_list << new_space
|
1955
|
+
end
|
1956
|
+
end
|
1957
|
+
end
|
1958
|
+
end
|
1959
|
+
max_space_type = space_list.max_by {|spc_lst| spc_lst[:floor_area]}
|
1960
|
+
return max_space_type
|
1961
|
+
end
|
1962
|
+
|
1963
|
+
def tz_vent_dist_cost(hvac_floors:, mech_sizing_info:)
|
1964
|
+
dist_reporting = []
|
1965
|
+
vent_dist_cost = 0
|
1966
|
+
mech_table = get_mech_table(mech_size_info: mech_sizing_info, table_name: 'tz_dist_info')
|
1967
|
+
flexduct_table = get_mech_table(mech_size_info: mech_sizing_info, table_name: 'flex_duct')
|
1968
|
+
hvac_floors.each_with_index do |hvac_floor, index|
|
1969
|
+
dist_reporting << {
|
1970
|
+
Story: hvac_floor[:story_name],
|
1971
|
+
thermal_zones: []
|
1972
|
+
}
|
1973
|
+
hvac_floor[:floor_tz].each do |floor_tz|
|
1974
|
+
floor_vent_cost = 0
|
1975
|
+
airflow_m3ps = []
|
1976
|
+
airflow_m3ps << floor_tz[:tz_floor_supp_air_m3ps]*floor_tz[:floor_area_frac]
|
1977
|
+
airflow_m3ps << floor_tz[:tz_floor_ret_air_m3ps]*floor_tz[:floor_area_frac] if floor_tz[:tz_floor_ret_air_m3ps].to_f.round(6) > 0.0
|
1978
|
+
airflow_m3ps.each_with_index do |max_air_m3ps, flow_index|
|
1979
|
+
# Using max supply air flow rather than breathing zone outdoor airflow. Keep breathing zone outdoor airflow in
|
1980
|
+
# case we change our minds.
|
1981
|
+
# breathing_zone_outdoor_airflow_vbz= model.sqlFile().get().execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='Standard62.1Summary' AND ReportForString='Entire Facility' AND TableName='Zone Ventilation Parameters' AND ColumnName='Breathing Zone Outdoor Airflow - Vbz' AND Units='m3/s' AND RowName='#{tz.nameString.to_s.upcase}' ")
|
1982
|
+
# bz_outdoor_airflow_m3_s = breathing_zone_outdoor_airflow_vbz.get unless breathing_zone_outdoor_airflow_vbz.empty?
|
1983
|
+
tz_dist_sz = mech_table.select {|size_range|
|
1984
|
+
max_air_m3ps > size_range['airflow_m3ps'][0] && max_air_m3ps <= size_range['airflow_m3ps'][1]
|
1985
|
+
}
|
1986
|
+
if tz_dist_sz.empty?
|
1987
|
+
size_range = mech_table[mech_table.size - 1]
|
1988
|
+
diffusers = (max_air_m3ps/size_range["diffusers"]).round(0)
|
1989
|
+
tz_dist_sz << {
|
1990
|
+
"airflow_m3ps" => size_range['airflow_m3ps'],
|
1991
|
+
"diffusers" => diffusers,
|
1992
|
+
"ducting_lbs" => (diffusers*size_range["ducting_lbs"]).round(0),
|
1993
|
+
"duct_insulation_ft2" => (diffusers*size_range["duct_insulation_ft2"]).round(0),
|
1994
|
+
"flex_duct_ft" => (diffusers*size_range["flex_duct_ft"]).round(0)
|
1995
|
+
}
|
1996
|
+
elsif tz_dist_sz[0] == mech_table[mech_table.size - 1]
|
1997
|
+
diffusers = (max_air_m3ps/tz_dist_sz[0]['diffusers']).round(0)
|
1998
|
+
tz_dist_sz[0] = {
|
1999
|
+
"airflow_m3ps" => tz_dist_sz[0]['airflow_m3ps'],
|
2000
|
+
"diffusers" => diffusers,
|
2001
|
+
"ducting_lbs" => (diffusers*tz_dist_sz[0]['ducting_lbs']).round(0),
|
2002
|
+
"duct_insulation_ft2" => (diffusers*tz_dist_sz[0]['duct_insulation_ft2']).round(0),
|
2003
|
+
"flex_duct_ft" => (diffusers*tz_dist_sz[0]['flex_duct_ft']).round(0)
|
2004
|
+
}
|
2005
|
+
end
|
2006
|
+
duct_cost_search = []
|
2007
|
+
duct_cost_search << {
|
2008
|
+
mat: 'Diffusers',
|
2009
|
+
unit: 'each',
|
2010
|
+
size: 36,
|
2011
|
+
mult: tz_dist_sz[0]['diffusers']
|
2012
|
+
}
|
2013
|
+
if tz_dist_sz[0]["ducting_lbs"] < 200
|
2014
|
+
duct_cost_search << {
|
2015
|
+
mat: 'Ductwork',
|
2016
|
+
unit: 'lb.',
|
2017
|
+
size: 199,
|
2018
|
+
mult: tz_dist_sz[0]['ducting_lbs']
|
2019
|
+
}
|
2020
|
+
else
|
2021
|
+
duct_cost_search << {
|
2022
|
+
mat: 'Ductwork',
|
2023
|
+
unit: 'lb.',
|
2024
|
+
size: 200,
|
2025
|
+
mult: tz_dist_sz[0]['ducting_lbs']
|
2026
|
+
}
|
2027
|
+
end
|
2028
|
+
duct_cost_search << {
|
2029
|
+
mat: 'DuctInsulation',
|
2030
|
+
unit: 'ft2',
|
2031
|
+
size: 1.5,
|
2032
|
+
mult: tz_dist_sz[0]['duct_insulation_ft2']
|
2033
|
+
}
|
2034
|
+
floor_vent_cost = get_comp_cost(cost_info: duct_cost_search)*floor_tz[:tz_mult]
|
2035
|
+
flex_duct_sz = flexduct_table.select {|flex_duct|
|
2036
|
+
max_air_m3ps > flex_duct['airflow_m3ps'][0] && max_air_m3ps <= flex_duct['airflow_m3ps'][1]
|
2037
|
+
}
|
2038
|
+
flex_duct_sz << flexduct_table[flexduct_table.size-1] if flex_duct_sz.empty?
|
2039
|
+
duct_cost_search = {
|
2040
|
+
mat: 'Ductwork-M',
|
2041
|
+
unit: 'L.F.',
|
2042
|
+
size: flex_duct_sz[0]['diameter_in'],
|
2043
|
+
mult: tz_dist_sz[0]['flex_duct_ft']
|
2044
|
+
}
|
2045
|
+
duct_cost, comp_info = get_duct_cost(cost_info: duct_cost_search)
|
2046
|
+
floor_vent_cost += duct_cost*floor_tz[:tz_mult]
|
2047
|
+
vent_dist_cost += floor_vent_cost
|
2048
|
+
if flow_index == 0
|
2049
|
+
flow_dir = 'Supply'
|
2050
|
+
else
|
2051
|
+
flow_dir = 'Return'
|
2052
|
+
end
|
2053
|
+
dist_reporting[index][:thermal_zones] << {
|
2054
|
+
ThermalZone: floor_tz[:tz].nameString,
|
2055
|
+
ducting_direction: flow_dir,
|
2056
|
+
tz_mult: floor_tz[:tz_mult],
|
2057
|
+
airflow_m3ps: max_air_m3ps.round(3),
|
2058
|
+
num_diff: tz_dist_sz[0]['diffusers'],
|
2059
|
+
ducting_lbs: tz_dist_sz[0]['ducting_lbs'],
|
2060
|
+
duct_insulation_ft2: tz_dist_sz[0]['duct_insulation_ft2'],
|
2061
|
+
flex_duct_sz_in: flex_duct_sz[0]['diameter_in'],
|
2062
|
+
flex_duct_length_ft: tz_dist_sz[0]['flex_duct_ft'],
|
2063
|
+
cost: floor_vent_cost.round(2)
|
2064
|
+
}
|
2065
|
+
end
|
2066
|
+
end
|
2067
|
+
end
|
2068
|
+
return vent_dist_cost, dist_reporting
|
2069
|
+
end
|
2070
|
+
|
2071
|
+
def get_hrv_info(airloop:, model:)
|
2072
|
+
hrv_present = false
|
2073
|
+
hrv_data = nil
|
2074
|
+
hrv_design_flow_m3ps = 0
|
2075
|
+
airloop.oaComponents.each do |oaComp|
|
2076
|
+
if oaComp.iddObjectType.valueName.to_s == 'OS_HeatExchanger_AirToAir_SensibleAndLatent'
|
2077
|
+
hrv_present = true
|
2078
|
+
hrv_data = oaComp.to_HeatExchangerAirToAirSensibleAndLatent.get
|
2079
|
+
if hrv_data.isNominalSupplyAirFlowRateAutosized
|
2080
|
+
hrv_design_flow_m3ps = hrv_data.autosizedNominalSupplyAirFlowRate.to_f
|
2081
|
+
else
|
2082
|
+
hrv_design_flow_m3ps = hrv_data.nominalSupplyAirFlowRate.to_f
|
2083
|
+
end
|
2084
|
+
end
|
2085
|
+
end
|
2086
|
+
return {
|
2087
|
+
hrv_present: hrv_present,
|
2088
|
+
hrv_data: hrv_data,
|
2089
|
+
hrv_size_m3ps: hrv_design_flow_m3ps,
|
2090
|
+
supply_cap_m3ps: 0,
|
2091
|
+
return_cap_m3ps: 0
|
2092
|
+
} unless hrv_present
|
2093
|
+
airloop.supplyFan.is_initialized ? supply_fan_cap = get_fan_cap(fan: airloop.supplyFan.get, model: model) : supply_fan_cap = 0
|
2094
|
+
airloop.returnFan.is_initialized ? return_fan_cap = get_fan_cap(fan: airloop.returnFan.get, model: model) : return_fan_cap = 0
|
2095
|
+
return {
|
2096
|
+
hrv_present: hrv_present,
|
2097
|
+
hrv_data: hrv_data,
|
2098
|
+
hrv_size_m3ps: hrv_design_flow_m3ps,
|
2099
|
+
supply_cap_m3ps: supply_fan_cap,
|
2100
|
+
return_cap_m3ps: return_fan_cap
|
2101
|
+
}
|
2102
|
+
end
|
2103
|
+
|
2104
|
+
def get_fan_cap(fan:, model:)
|
2105
|
+
fan_type = fan.iddObjectType.valueName.to_s
|
2106
|
+
case fan_type
|
2107
|
+
when /OS_Fan_VariableVolume/
|
2108
|
+
fan_obj = fan.to_FanVariableVolume.get
|
2109
|
+
if fan_obj.isMaximumFlowRateAutosized
|
2110
|
+
fan_cap_m3ps = fan_obj.autosizedMaximumFlowRate.to_f
|
2111
|
+
else
|
2112
|
+
fan_cap_m3ps = fan_obj.maximumFlowRate.to_f
|
2113
|
+
end
|
2114
|
+
when /OS_Fan_ConstantVolume/
|
2115
|
+
fan_obj = fan.to_FanConstantVolume.get
|
2116
|
+
if fan_obj.isMaximumFlowRateAutosized
|
2117
|
+
fan_cap_m3ps = fan_obj.autosizedMaximumFlowRate.to_f
|
2118
|
+
else
|
2119
|
+
fan_cap_m3ps = fan_obj.maximumFlowRate.to_f
|
2120
|
+
end
|
2121
|
+
else
|
2122
|
+
fan_cap_m3ps = 0
|
2123
|
+
end
|
2124
|
+
return fan_cap_m3ps
|
2125
|
+
end
|
2126
|
+
|
2127
|
+
def hrv_duct_cost(prototype_creator:, roof_cent:, mech_sizing_info:, hvac_floors:)
|
2128
|
+
hrv_cost_tot = 0
|
2129
|
+
mech_table = get_mech_table(mech_size_info: mech_sizing_info, table_name: 'trunk')
|
2130
|
+
air_system_totals = []
|
2131
|
+
hrv_dist_rep = []
|
2132
|
+
hvac_floors.each_with_index do |hvac_floor, floor_index|
|
2133
|
+
hrv_dist_rep << {
|
2134
|
+
floor: hvac_floor[:story_name],
|
2135
|
+
air_systems: []
|
2136
|
+
}
|
2137
|
+
floor_systems = sort_tzs_by_air_system(hvac_floor: hvac_floor)
|
2138
|
+
floor_systems.each_with_index do |air_system, air_index|
|
2139
|
+
next if air_system[:sys_hrv_flow_m3ps].round(2) == 0.0 || air_system[:hrv_info][:hrv_present] == false
|
2140
|
+
floor_trunk_line = nil
|
2141
|
+
floor_air_sys = {
|
2142
|
+
air_system: air_system[:air_sys].nameString,
|
2143
|
+
hrv: air_system[:hrv_info][:hrv_data].nameString,
|
2144
|
+
floor_mult: 1,
|
2145
|
+
hrv_ret_trunk: {},
|
2146
|
+
tz_dist: [],
|
2147
|
+
}
|
2148
|
+
if air_system[:num_tz] > 1
|
2149
|
+
sys_floor_mult = air_system[:tz_mult]/(air_system[:num_tz])
|
2150
|
+
floor_trunk_line = get_story_cent_to_edge(building_story: hvac_floor[:story], prototype_creator: prototype_creator, target_cent: roof_cent[:roof_centroid], full_length: true)
|
2151
|
+
hrv_trunk_cost, floor_air_sys[:hrv_ret_trunk] = get_hrv_floor_trunk_cost(mech_table: mech_table, air_system: air_system, floor_trunk_dist_m: floor_trunk_line[:end_point][:line][:dist])
|
2152
|
+
hrv_cost_tot += hrv_trunk_cost*sys_floor_mult
|
2153
|
+
floor_air_sys[:floor_mult] = sys_floor_mult
|
2154
|
+
end
|
2155
|
+
air_system[:floor_tz].each do |floor_tz|
|
2156
|
+
floor_tz[:tz_floor_ret_air_m3ps] >= floor_tz[:tz_floor_outdoor_air_m3ps] ? hrv_air = 0 : hrv_air = (floor_tz[:tz_floor_outdoor_air_m3ps] - floor_tz[:tz_floor_ret_air_m3ps]).abs
|
2157
|
+
next if hrv_air.round(2) == 0.0
|
2158
|
+
air_system_total = {
|
2159
|
+
dist_to_roof_m: (roof_cent[:roof_centroid][2] - floor_tz[:tz_cent][:centroid][2]).abs,
|
2160
|
+
hrv_air_m3ps: hrv_air*floor_tz[:tz_mult],
|
2161
|
+
num_systems: floor_tz[:tz_mult]
|
2162
|
+
}
|
2163
|
+
if floor_trunk_line.nil?
|
2164
|
+
floor_duct_coords = [roof_cent[:roof_centroid][0] - floor_tz[:tz_cent][:centroid][0], roof_cent[:roof_centroid][1] - floor_tz[:tz_cent][:centroid][1], roof_cent[:roof_centroid][2] - floor_tz[:tz_cent][:centroid][2]]
|
2165
|
+
floor_duct_dist_m = floor_duct_coords[0].abs + floor_duct_coords[1].abs
|
2166
|
+
else
|
2167
|
+
line = {
|
2168
|
+
start: floor_trunk_line[:start_point][:line][:int],
|
2169
|
+
end: floor_trunk_line[:end_point][:line][:int]
|
2170
|
+
}
|
2171
|
+
floor_duct_dist_m = short_dist_point_and_line(point: floor_tz[:tz_cent][:centroid], line: line).abs
|
2172
|
+
if floor_duct_dist_m.nil?
|
2173
|
+
floor_duct_dist_m = (line[:start][0] - floor_tz[:tz_cent][:centroid][0]).abs + (line[:start][1] - floor_tz[:tz_cent][:centroid][1]).abs
|
2174
|
+
end
|
2175
|
+
end
|
2176
|
+
if floor_duct_dist_m.round(2) > 0.1
|
2177
|
+
floor_duct_dist_ft = (OpenStudio.convert(floor_duct_dist_m, 'm', 'ft').get)
|
2178
|
+
branch_duct_sz = mech_table.select {|sz_range|
|
2179
|
+
hrv_air > sz_range['max_flow_range_m3pers'][0] && hrv_air <= sz_range['max_flow_range_m3pers'][1]
|
2180
|
+
}
|
2181
|
+
branch_duct_sz << mech_table[mech_table.size-1] if branch_duct_sz.empty?
|
2182
|
+
duct_comp_search = []
|
2183
|
+
duct_dia_in = branch_duct_sz[0]['duct_dia_inch']
|
2184
|
+
duct_surface_area = floor_duct_dist_ft*(duct_dia_in.to_f/12)*Math::PI
|
2185
|
+
duct_comp_search << {
|
2186
|
+
mat: 'Ductinsulation',
|
2187
|
+
unit: 'ft2',
|
2188
|
+
size: 1.5,
|
2189
|
+
mult: duct_surface_area
|
2190
|
+
}
|
2191
|
+
duct_comp_search << {
|
2192
|
+
mat: 'Ductwork-S',
|
2193
|
+
unit: 'L.F.',
|
2194
|
+
size: duct_dia_in,
|
2195
|
+
mult: floor_duct_dist_ft
|
2196
|
+
}
|
2197
|
+
hrv_branch_cost = get_comp_cost(cost_info: duct_comp_search)
|
2198
|
+
hrv_cost_tot += hrv_branch_cost*floor_tz[:tz_mult]
|
2199
|
+
floor_air_sys[:tz_dist] << {
|
2200
|
+
tz: floor_tz[:tz].nameString,
|
2201
|
+
tz_mult: floor_tz[:tz_mult],
|
2202
|
+
hrv_ret_dist_m: floor_duct_dist_m.round(1),
|
2203
|
+
hrv_ret_size_in: duct_dia_in.round(2),
|
2204
|
+
cost: hrv_branch_cost.round(2)
|
2205
|
+
}
|
2206
|
+
end
|
2207
|
+
air_system_totals = add_tz_to_air_sys(air_system: air_system, air_system_total: air_system_total, air_system_totals: air_system_totals, floor_tz: floor_tz)
|
2208
|
+
end
|
2209
|
+
hrv_dist_rep[floor_index][:air_systems] << floor_air_sys
|
2210
|
+
end
|
2211
|
+
end
|
2212
|
+
unless air_system_totals.empty?
|
2213
|
+
air_system_totals.each do |air_system|
|
2214
|
+
next if air_system[:hrv_air_m3ps].round(2) == 0
|
2215
|
+
# In addition to distance from floor to roof add 20' of duct from roof centre to box
|
2216
|
+
main_trunk_dist_ft = (OpenStudio.convert(air_system[:dist_to_roof_m], 'm', 'ft').get) + 20
|
2217
|
+
main_trunk_sz = mech_table.select {|sz_range|
|
2218
|
+
air_system[:hrv_air_m3ps] > sz_range['max_flow_range_m3pers'][0] && air_system[:hrv_air_m3ps] <= sz_range['max_flow_range_m3pers'][1]
|
2219
|
+
}
|
2220
|
+
main_trunk_sz << mech_table[mech_table.size-1] if main_trunk_sz.empty?
|
2221
|
+
duct_comp_search = []
|
2222
|
+
duct_dia_in = main_trunk_sz[0]['duct_dia_inch']
|
2223
|
+
duct_surf_area_ft2 = main_trunk_dist_ft*(duct_dia_in.to_f/12)*Math::PI
|
2224
|
+
duct_comp_search << {
|
2225
|
+
mat: 'Ductinsulation',
|
2226
|
+
unit: 'ft2',
|
2227
|
+
size: 1.5,
|
2228
|
+
mult: duct_surf_area_ft2
|
2229
|
+
}
|
2230
|
+
duct_comp_search << {
|
2231
|
+
mat: 'Ductwork-S',
|
2232
|
+
unit: 'L.F.',
|
2233
|
+
size: duct_dia_in,
|
2234
|
+
mult: main_trunk_dist_ft
|
2235
|
+
}
|
2236
|
+
main_trunk_cost = get_comp_cost(cost_info: duct_comp_search)
|
2237
|
+
hrv_cost_tot += main_trunk_cost
|
2238
|
+
hrv_dist_rep << {
|
2239
|
+
air_system: air_system[:air_system].nameString,
|
2240
|
+
hrv: air_system[:hrv_info][:hrv_data].nameString,
|
2241
|
+
hrv_building_trunk_length_m: air_system[:dist_to_roof_m].round(1),
|
2242
|
+
hrv_building_trunk_dia_in: duct_dia_in.round(2),
|
2243
|
+
cost: main_trunk_cost.round(2)
|
2244
|
+
}
|
2245
|
+
end
|
2246
|
+
end
|
2247
|
+
return hrv_cost_tot, hrv_dist_rep
|
2248
|
+
end
|
2249
|
+
|
2250
|
+
def sort_tzs_by_air_system(hvac_floor:)
|
2251
|
+
floor_systems = []
|
2252
|
+
hvac_floor[:floor_tz].each do |floor_tz|
|
2253
|
+
air_sys = floor_tz[:sys_info]
|
2254
|
+
next if floor_tz[:hrv_info][:hrv_present] == false
|
2255
|
+
floor_tz[:tz_floor_ret_air_m3ps] >= floor_tz[:tz_floor_outdoor_air_m3ps] ? hrv_ret_air_m3ps = 0 : hrv_ret_air_m3ps = (floor_tz[:tz_floor_outdoor_air_m3ps] - floor_tz[:tz_floor_ret_air_m3ps]).abs
|
2256
|
+
if floor_systems.empty?
|
2257
|
+
floor_systems << {
|
2258
|
+
air_sys: air_sys,
|
2259
|
+
sys_hrv_flow_m3ps: hrv_ret_air_m3ps,
|
2260
|
+
num_tz: 1,
|
2261
|
+
tz_mult: floor_tz[:tz_mult],
|
2262
|
+
hrv_info: floor_tz[:hrv_info],
|
2263
|
+
floor_tz: [floor_tz]
|
2264
|
+
}
|
2265
|
+
else
|
2266
|
+
current_sys = floor_systems.select {|floor_sys| floor_sys[:air_sys] == air_sys}
|
2267
|
+
if current_sys.empty?
|
2268
|
+
floor_systems << {
|
2269
|
+
air_sys: air_sys,
|
2270
|
+
sys_hrv_flow_m3ps: hrv_ret_air_m3ps,
|
2271
|
+
num_tz: 1,
|
2272
|
+
tz_mult: floor_tz[:tz_mult],
|
2273
|
+
hrv_info: floor_tz[:hrv_info],
|
2274
|
+
floor_tz: [floor_tz]
|
2275
|
+
}
|
2276
|
+
else
|
2277
|
+
current_sys[0][:sys_hrv_flow_m3ps] += hrv_ret_air_m3ps
|
2278
|
+
current_sys[0][:num_tz] += 1
|
2279
|
+
current_sys[0][:tz_mult] += floor_tz[:tz_mult]
|
2280
|
+
current_sys[0][:floor_tz] << floor_tz
|
2281
|
+
end
|
2282
|
+
end
|
2283
|
+
end
|
2284
|
+
return floor_systems
|
2285
|
+
end
|
2286
|
+
|
2287
|
+
def add_tz_to_air_sys(air_system:, air_system_total:, air_system_totals:, floor_tz:)
|
2288
|
+
if air_system_totals.empty?
|
2289
|
+
air_system_totals << {
|
2290
|
+
air_system: air_system[:air_sys],
|
2291
|
+
hrv_air_m3ps: air_system_total[:hrv_air_m3ps],
|
2292
|
+
dist_to_roof_m: air_system_total[:dist_to_roof_m],
|
2293
|
+
num_systems: air_system_total[:num_systems],
|
2294
|
+
hrv_info: air_system[:hrv_info],
|
2295
|
+
floor_tz: [floor_tz]
|
2296
|
+
}
|
2297
|
+
else
|
2298
|
+
curr_air_sys = air_system_totals.select {|air_sys| air_sys[:air_system] == air_system[:air_sys]}
|
2299
|
+
if curr_air_sys.empty?
|
2300
|
+
air_system_totals << {
|
2301
|
+
air_system: air_system[:air_sys],
|
2302
|
+
hrv_air_m3ps: air_system_total[:hrv_air_m3ps],
|
2303
|
+
dist_to_roof_m: air_system_total[:dist_to_roof_m],
|
2304
|
+
num_systems: air_system_total[:num_systems],
|
2305
|
+
hrv_info: air_system[:hrv_info],
|
2306
|
+
floor_tz: [floor_tz]
|
2307
|
+
}
|
2308
|
+
else
|
2309
|
+
curr_air_sys[0][:hrv_air_m3ps] += air_system_total[:hrv_air_m3ps]
|
2310
|
+
curr_air_sys[0][:dist_to_roof_m] = [curr_air_sys[0][:dist_to_roof_m], air_system_total[:dist_to_roof_m]].max
|
2311
|
+
curr_air_sys[0][:num_systems] += air_system_total[:num_systems]
|
2312
|
+
curr_air_sys[0][:floor_tz] << floor_tz
|
2313
|
+
end
|
2314
|
+
end
|
2315
|
+
return air_system_totals
|
2316
|
+
end
|
2317
|
+
|
2318
|
+
def get_hrv_floor_trunk_cost(mech_table:, air_system:, floor_trunk_dist_m:)
|
2319
|
+
return 0 if air_system[:sys_hrv_flow_m3ps].round(2) == 0.0
|
2320
|
+
hrv_trunk_cost = 0
|
2321
|
+
duct_comp_search = []
|
2322
|
+
floor_trunk_dist = (OpenStudio.convert(floor_trunk_dist_m, 'm', 'ft').get)
|
2323
|
+
trunk_duct_sz = mech_table.select {|sz_range|
|
2324
|
+
air_system[:sys_hrv_flow_m3ps] > sz_range['max_flow_range_m3pers'][0] && air_system[:sys_hrv_flow_m3ps] <= sz_range['max_flow_range_m3pers'][1]
|
2325
|
+
}
|
2326
|
+
trunk_duct_sz << mech_table[mech_table.size-1] if trunk_duct_sz.empty?
|
2327
|
+
trunk_dia_in = (trunk_duct_sz[0]['duct_dia_inch'])
|
2328
|
+
duct_comp_search << {
|
2329
|
+
mat: 'Ductwork-S',
|
2330
|
+
unit: 'L.F.',
|
2331
|
+
size: trunk_dia_in,
|
2332
|
+
mult: floor_trunk_dist
|
2333
|
+
}
|
2334
|
+
trunk_area_sqrft = (trunk_dia_in.to_f/12)*Math::PI*floor_trunk_dist
|
2335
|
+
duct_comp_search << {
|
2336
|
+
mat: 'Ductinsulation',
|
2337
|
+
unit: 'ft2',
|
2338
|
+
size: 1.5,
|
2339
|
+
mult: trunk_area_sqrft
|
2340
|
+
}
|
2341
|
+
hrv_trunk_cost += get_comp_cost(cost_info: duct_comp_search)
|
2342
|
+
hrv_trunk_cost_rep = {
|
2343
|
+
duct_length_m: floor_trunk_dist_m.round(1),
|
2344
|
+
dia_in: trunk_dia_in.round(2),
|
2345
|
+
cost: hrv_trunk_cost.round(2)
|
2346
|
+
}
|
2347
|
+
return hrv_trunk_cost, hrv_trunk_cost_rep
|
2348
|
+
end
|
2349
|
+
|
2350
|
+
def short_dist_point_and_line(point:, line:)
|
2351
|
+
line_eq = get_line_eq(a: line[:start], b: line[:end])
|
2352
|
+
if line_eq[:int] == 1 and line_eq[:inf] == true
|
2353
|
+
dist = point[0] - line_eq[:slope]
|
2354
|
+
elsif line_eq[:int] == 0 and line_eq[:inf] == true
|
2355
|
+
dist = nil
|
2356
|
+
else
|
2357
|
+
# Turn equation of line as: y = slope*x + intercept
|
2358
|
+
# into: a*x + b*y + c = 0
|
2359
|
+
# a = slope, b = -1, c = intercept
|
2360
|
+
a = line_eq[:slope]
|
2361
|
+
b = -1
|
2362
|
+
c = line_eq[:int]
|
2363
|
+
# Use dot product to get shortest distance from point to line
|
2364
|
+
dist = (a*point[0] + b*point[1] + c) / Math.sqrt(a**2 + b**2)
|
2365
|
+
end
|
2366
|
+
return dist
|
2367
|
+
end
|
2368
|
+
|
2369
|
+
# This method consumes the following:
|
2370
|
+
# hrv_info: (hash) Information about the modeled HRV.
|
2371
|
+
# airloop: (OpenStudio Object) The OpenStudio air loop object.
|
2372
|
+
# vent_tags: (array of strings) Tags used to associate the costing output list with whichever component of the
|
2373
|
+
# building is being costed.
|
2374
|
+
# report_mult: (float) When recreating the cost of items from the costing output list this multiplier is used to
|
2375
|
+
# multiply the total of the localized material and labour costs.
|
2376
|
+
def hrv_cost(hrv_info:, airloop:, vent_tags: [], report_mult: 1.0)
|
2377
|
+
hrv_tags = vent_tags.clone
|
2378
|
+
hrv_tags << "ERV duct cost"
|
2379
|
+
hrv_cost_tot = 0
|
2380
|
+
number_zones = 0
|
2381
|
+
duct_comp_search = []
|
2382
|
+
# Calculate the number of thermal zones served by the ERV
|
2383
|
+
airloop.thermalZones.each do |tz|
|
2384
|
+
number_zones += tz.multiplier
|
2385
|
+
end
|
2386
|
+
|
2387
|
+
# Get additional ductwork costs
|
2388
|
+
duct_comp_search << {
|
2389
|
+
mat: 'Ductwork-Fitting',
|
2390
|
+
unit: 'each',
|
2391
|
+
size: 8,
|
2392
|
+
mult: number_zones
|
2393
|
+
}
|
2394
|
+
hrv_cost_tot += get_comp_cost(cost_info: duct_comp_search, vent_tags: hrv_tags, report_mult: report_mult)
|
2395
|
+
hrv_tags.pop
|
2396
|
+
|
2397
|
+
# Get the return air fan cost (if applicable)
|
2398
|
+
hrv_info[:return_cap_m3ps] >= hrv_info[:hrv_size_m3ps] ? hrv_return_flow_m3ps = 0.0 : hrv_return_flow_m3ps = hrv_info[:hrv_size_m3ps] - hrv_info[:return_cap_m3ps]
|
2399
|
+
hrv_tags << "ERV return air fan"
|
2400
|
+
unless hrv_return_flow_m3ps.round(2) == 0
|
2401
|
+
hrv_return_flow_cfm = (OpenStudio.convert(hrv_return_flow_m3ps, 'm^3/s', 'cfm').get)
|
2402
|
+
if hrv_return_flow_cfm < 800
|
2403
|
+
hrv_cost_tot += get_mech_costing(mech_name: 'FansDD-LP', size: hrv_return_flow_cfm, terminal: hrv_info[:hrv_data], use_mult: true, vent_tags: hrv_tags, report_mult: report_mult)
|
2404
|
+
else
|
2405
|
+
hrv_cost_tot += get_mech_costing(mech_name: 'FansBelt', size: hrv_return_flow_cfm, terminal: hrv_info[:hrv_data], use_mult: true, vent_tags: hrv_tags, report_mult: report_mult)
|
2406
|
+
end
|
2407
|
+
end
|
2408
|
+
|
2409
|
+
|
2410
|
+
hrv_tags.pop
|
2411
|
+
hrv_tags << "ERV with adjustment factor"
|
2412
|
+
|
2413
|
+
hrv_size_cfm = (OpenStudio.convert(hrv_info[:hrv_size_m3ps], 'm^3/s', 'cfm').get)
|
2414
|
+
# Turn the HRV information into something the 'get_vent_cost_data' method expects.
|
2415
|
+
hrv_requirements = {
|
2416
|
+
cat_search: 'ERV',
|
2417
|
+
mech_capacity_kw: hrv_size_cfm, # This key really should just be called mech_capacity since the units vary.
|
2418
|
+
supply_component: hrv_info[:hrv_data]
|
2419
|
+
}
|
2420
|
+
# Get the HRV costing information
|
2421
|
+
hrv_mult, hrv_cost_info = get_vent_cost_data(equipment_info: hrv_requirements)
|
2422
|
+
# Calculate the HRV cost adjustment factor
|
2423
|
+
hrv_cost_adj = hrv_size_cfm*hrv_mult/(hrv_cost_info['Size'].to_f)
|
2424
|
+
ind_hrv_cost = get_vent_mat_cost(mat_cost_info: hrv_cost_info, vent_tags: hrv_tags, report_mult: hrv_cost_adj)
|
2425
|
+
|
2426
|
+
ind_hrv_cost_rep = hrv_cost_tot + ind_hrv_cost
|
2427
|
+
hrv_cost_tot += ind_hrv_cost*hrv_cost_adj
|
2428
|
+
hrv_rep = {
|
2429
|
+
hrv_type: (hrv_info[:hrv_data].iddObjectType.valueName.to_s)[3..-1],
|
2430
|
+
hrv_name: hrv_info[:hrv_data].nameString,
|
2431
|
+
hrv_size_m3ps: hrv_info[:hrv_size_m3ps].round(3),
|
2432
|
+
hrv_return_fan_size_m3ps: hrv_return_flow_m3ps.round(3),
|
2433
|
+
hrv_cost: ind_hrv_cost_rep.round(2),
|
2434
|
+
revised_hrv_cost: hrv_cost_tot.round(2)
|
2435
|
+
}
|
2436
|
+
|
2437
|
+
return hrv_rep
|
2438
|
+
end
|
2439
|
+
|
2440
|
+
# This method collects air loop heating and cooling costing information into the al_eq_reporting_info hash. This hash
|
2441
|
+
# will be included in the ventilation costing report. It collects air loops by system type.
|
2442
|
+
def add_heat_cool_to_report(equipment_info:, heat_cool_cost:, al_eq_reporting_info:)
|
2443
|
+
# If there is no air loop heating or cooling equipment casting information add it to the hash.
|
2444
|
+
if al_eq_reporting_info.empty?
|
2445
|
+
al_eq_reporting_info << {
|
2446
|
+
eq_category: equipment_info[:obj_type][3..-1],
|
2447
|
+
heating_fuel: equipment_info[:heating_fuel],
|
2448
|
+
cooling_type: equipment_info[:cooling_type],
|
2449
|
+
total_modeled_capacity_kw: equipment_info[:mech_capacity_kw].round(3),
|
2450
|
+
cost: heat_cool_cost.round(2)
|
2451
|
+
}
|
2452
|
+
else
|
2453
|
+
# look for an air loop with the appropriate system type.
|
2454
|
+
ahu_heat_cool = al_eq_reporting_info.select {|aloop|
|
2455
|
+
aloop[:eq_category] == equipment_info[:obj_type][3..-1]
|
2456
|
+
}
|
2457
|
+
# If air loops with that system type are present add a new one.
|
2458
|
+
if ahu_heat_cool.empty?
|
2459
|
+
al_eq_reporting_info << {
|
2460
|
+
eq_category: equipment_info[:obj_type][3..-1],
|
2461
|
+
heating_fuel: equipment_info[:heating_fuel],
|
2462
|
+
cooling_type: equipment_info[:cooling_type],
|
2463
|
+
total_modeled_capacity_kw: equipment_info[:mech_capacity_kw].round(3),
|
2464
|
+
cost: heat_cool_cost.round(2)
|
2465
|
+
}
|
2466
|
+
else
|
2467
|
+
# If there is an air loop with the appropriate system type add the capacity and cost to the hash.
|
2468
|
+
ahu_heat_cool[0][:total_modeled_capacity_kw] += equipment_info[:mech_capacity_kw].round(3)
|
2469
|
+
ahu_heat_cool[0][:cost] += heat_cool_cost.round(2)
|
2470
|
+
end
|
2471
|
+
end
|
2472
|
+
end
|
2473
|
+
|
2474
|
+
# This method oversees the costing of heating and cooling equipment in an air loop. It takes in:
|
2475
|
+
# airloop_equipment: A hash containing all heating and cooling supply equipment in the air loop
|
2476
|
+
# The method retruns the airloop_equip_return_info hash which contains:
|
2477
|
+
# al_eq_reporting_info: A hash containing information that will be included in the ventilation costing report
|
2478
|
+
# heat_cool_cost: The total cost of heating and cooling equipment in the air loop
|
2479
|
+
def airloop_equipment_costing(airloop_equipment:, ahu_mult:, vent_tags: [])
|
2480
|
+
# Initialize return data
|
2481
|
+
ret_heat_cool_cost = 0
|
2482
|
+
al_eq_reporting_info = []
|
2483
|
+
ccashp_cost = 0
|
2484
|
+
vent_equip_tags = vent_tags.clone
|
2485
|
+
vent_equip_tags << "air loop equipment"
|
2486
|
+
|
2487
|
+
# Look for a heat pump. Heat pump air loop equipment costing is treated differently.
|
2488
|
+
heat_pumps = airloop_equipment.select{|airloop_eq| airloop_eq[:heating_fuel].to_s.include?('HP')}
|
2489
|
+
unless heat_pumps.empty?
|
2490
|
+
cool_eq = airloop_equipment.select{|airloop_eq| airloop_eq[:cooling_type].to_s.include?("DX")}
|
2491
|
+
unless cool_eq.empty?
|
2492
|
+
heat_pumps[0][:mech_capacity_kw] = cool_eq[0][:mech_capacity_kw].to_f if cool_eq[0][:mech_capacity_kw].to_f > heat_pumps[0][:mech_capacity_kw].to_f
|
2493
|
+
heat_pumps[0][:cooling_type] = heat_pumps[0][:heating_fuel]
|
2494
|
+
airloop_equipment.delete_if{|data| data[:cooling_type].to_s.include?("DX")}
|
2495
|
+
end
|
2496
|
+
if heat_pumps[0][:heating_fuel].to_s == "CCASHP"
|
2497
|
+
ccashp_cost = cost_ccashp_additional_components(ahu_mult: ahu_mult, heat_pump: heat_pumps[0], vent_tags: vent_equip_tags)
|
2498
|
+
end
|
2499
|
+
elec_eq = airloop_equipment.select{|airloop_eq| airloop_eq[:heating_fuel] == 'elec'}
|
2500
|
+
# If a backup electric heating coil is present look for a different item in the 'hvac_materials' costing sheet
|
2501
|
+
# than if the coil where part of an air loop without a heat pump.
|
2502
|
+
elec_eq.each do |el_eq|
|
2503
|
+
el_eq[:cat_search] = 'elecduct'
|
2504
|
+
end
|
2505
|
+
#airloop_equipment.select.with_index{|airloop_eq, index| airloop_eq[:cooling_type] == 'DX' || airloop_eq[:cooling_type] == 'CCASHP'}
|
2506
|
+
end
|
2507
|
+
|
2508
|
+
# Cost all of the heating and cooling equipment in the air loop
|
2509
|
+
airloop_equipment.each do |airloop_eq|
|
2510
|
+
# Costing of air loop equipment should be done on a per air handler basis. Thus, divide the total capacity of the
|
2511
|
+
# piece of air loop equipment by the number of air handlers required.
|
2512
|
+
total_modeled_capacity = airloop_eq[:mech_capacity_kw].to_f
|
2513
|
+
airloop_eq[:mech_capacity_kw] = total_modeled_capacity / ahu_mult
|
2514
|
+
# Get ventilation heating and cooling equipment costs.
|
2515
|
+
heat_cool_cost = cost_heat_cool_equip(equipment_info: airloop_eq, vent_tags: vent_equip_tags, report_mult: ahu_mult) * ahu_mult
|
2516
|
+
heat_cool_cost += ccashp_cost if airloop_eq[:heating_fuel].to_s == "CCASHP"
|
2517
|
+
# Add the equipment cost to the total air loop equipment cost
|
2518
|
+
ret_heat_cool_cost += heat_cool_cost
|
2519
|
+
# Only the total modeled capacity of the piece of air loop equipment should be reported to the user rather than
|
2520
|
+
# the capacity per air handler.
|
2521
|
+
airloop_eq[:mech_capacity_kw] = total_modeled_capacity
|
2522
|
+
# Add the air loop hetaing/cooling equipment information to the total air loop heating/cooling equipment report hash
|
2523
|
+
al_eq_reporting_info = add_heat_cool_to_report(equipment_info: airloop_eq, heat_cool_cost: heat_cool_cost, al_eq_reporting_info: al_eq_reporting_info)
|
2524
|
+
end
|
2525
|
+
|
2526
|
+
# Create the return hash and return it.
|
2527
|
+
airloop_equip_return_info = {
|
2528
|
+
al_eq_reporting_info: al_eq_reporting_info,
|
2529
|
+
heat_cool_cost: ret_heat_cool_cost
|
2530
|
+
}
|
2531
|
+
return airloop_equip_return_info
|
2532
|
+
end
|
2533
|
+
|
2534
|
+
# This method calculates the costs of CCASHP equipment beyond the coil cost and any backup heating costs. It takes in
|
2535
|
+
# ahu_mult: The number of air handlers required to meet the model air loop flow rate, cooling type, heating type and
|
2536
|
+
# system type.
|
2537
|
+
# heat_pumps: The heat pump hash for the ccashp which contains the OpenStudio heat pump object and the size of the
|
2538
|
+
# heat pump in kW.
|
2539
|
+
# The method uses a number of different costing methods to get equipment costs. The methods used depend on what best
|
2540
|
+
# suits the costing. For example evaporator costing is found by size and material so the get_vent_cost_data method
|
2541
|
+
# is most appropriate. Wiring has a material and size but the size should be an exact match so the get_comp_cost
|
2542
|
+
# method is used. Finally, a number of pieces of equipment with no size are costed. The esiest way to cost these
|
2543
|
+
# items was to refer to their 'materials_hvac' sheet 'material_id' column numbers and associated quantities and use
|
2544
|
+
# the vent_assembly_cost method.
|
2545
|
+
def cost_ccashp_additional_components(ahu_mult:, heat_pump:, vent_tags: [], report_mult: 1.0)
|
2546
|
+
ccashp_tags = vent_tags.clone
|
2547
|
+
# Initialize the ccashp additional equipment cost.
|
2548
|
+
ccashp_add_cost = 0
|
2549
|
+
# Set a variable to represent the capacity of each heat pump per air handler
|
2550
|
+
cap = heat_pump[:mech_capacity_kw].to_f/ahu_mult
|
2551
|
+
# Set a variable to represent the capacity in tons of cooling (for costing the refrigerent line).
|
2552
|
+
# cap_tonc = (OpenStudio.convert(cap.to_f, 'kW', 'kBtu/hr').get)/12.0 # No longer needed but keeping for future reference
|
2553
|
+
|
2554
|
+
# This variable holds the number of condensing units.
|
2555
|
+
cond_mult = 1.0
|
2556
|
+
|
2557
|
+
# An array of hashes containing the information required to cost the heat pump evaporator valve and condenser.
|
2558
|
+
ccashp_lrg_equips = []
|
2559
|
+
ccashp_lrg_equips << {
|
2560
|
+
supply_comp: heat_pump[:supply_comp],
|
2561
|
+
mech_capacity_kw: cap,
|
2562
|
+
cat_search: "EV_valve"
|
2563
|
+
}
|
2564
|
+
ccashp_lrg_equips << {
|
2565
|
+
supply_comp: heat_pump[:supply_comp],
|
2566
|
+
mech_capacity_kw: cap,
|
2567
|
+
cat_search: "ccashp_condensor"
|
2568
|
+
}
|
2569
|
+
|
2570
|
+
# Cost the heat pump evaporator valve and condenser.
|
2571
|
+
ccashp_lrg_equips.each do |ccashp_lrg_equip|
|
2572
|
+
equip_mult, cost_info = get_vent_cost_data(equipment_info: ccashp_lrg_equip)
|
2573
|
+
ccashp_add_cost += get_vent_mat_cost(mat_cost_info: cost_info, vent_tags: ccashp_tags, report_mult: (report_mult*equip_mult*ahu_mult)) * equip_mult * ahu_mult
|
2574
|
+
# cond_mult is supposed to be the number of condensors there are. It is set to be the multiplier if one condensor
|
2575
|
+
# is not enough. It should be set to the number of condesors because the condensors should be the last item in
|
2576
|
+
# this loop to be costed.
|
2577
|
+
cond_mult = equip_mult
|
2578
|
+
end
|
2579
|
+
|
2580
|
+
# Cost the wiring per heat pump condenser. Correcting to use 20 ft rather than 20 m.
|
2581
|
+
#ccashp_wiring_dist = (OpenStudio.convert(20, 'm', 'ft').get)/100.0
|
2582
|
+
ccashp_add_equip = [
|
2583
|
+
{
|
2584
|
+
mat: "Wiring",
|
2585
|
+
unit: "CLF",
|
2586
|
+
size: 10,
|
2587
|
+
mult: 0.2 * ahu_mult * cond_mult
|
2588
|
+
}
|
2589
|
+
]
|
2590
|
+
# Get the Wiring costs.
|
2591
|
+
ccashp_add_cost += get_comp_cost(cost_info: ccashp_add_equip, vent_tags: ccashp_tags)
|
2592
|
+
|
2593
|
+
# Set an array containing the equipment 'material_id' references to search in the costing spreadsheet
|
2594
|
+
# 'materials_hvac' sheet.
|
2595
|
+
ids = [
|
2596
|
+
#1307, #Low Temperature Kit this belongs with the air handlers not the equipment
|
2597
|
+
1295, # Remote Condensor Controller
|
2598
|
+
1662, # Refrigerant tubing-large, 20' of 0.5" supply and 1-1/8" return
|
2599
|
+
30, # 1.25" pipe insulation for refrigerant tubing
|
2600
|
+
1415 # Safety Switch
|
2601
|
+
]
|
2602
|
+
|
2603
|
+
# Set the quantities associated with the above ids. Note that ahu_mult is included when getting the cost.
|
2604
|
+
id_quants = [
|
2605
|
+
#1.0,
|
2606
|
+
cond_mult,
|
2607
|
+
cond_mult,
|
2608
|
+
cond_mult * 20 * 2, # 20' of supply and return pipe insulation for refrigerant tubing
|
2609
|
+
cond_mult
|
2610
|
+
]
|
2611
|
+
|
2612
|
+
# Get the costs for equipment in the ids with id_quants quantities above.
|
2613
|
+
ccashp_add_cost += vent_assembly_cost(ids: ids, id_quants: id_quants, overall_mult: ahu_mult, vent_tags: ccashp_tags)
|
2614
|
+
return ccashp_add_cost
|
2615
|
+
end
|
2616
|
+
end
|