openstudio-standards 0.8.3 → 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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/lib/openstudio-standards/btap/costing/README.md +502 -0
  3. data/lib/openstudio-standards/btap/costing/btap_costing.rb +473 -0
  4. data/lib/openstudio-standards/btap/costing/btap_measure_helper.rb +359 -0
  5. data/lib/openstudio-standards/btap/costing/btap_workflow.rb +117 -0
  6. data/lib/openstudio-standards/btap/costing/common_paths.rb +78 -0
  7. data/lib/openstudio-standards/btap/costing/common_resources/ConstructionProperties.csv +52 -0
  8. data/lib/openstudio-standards/btap/costing/common_resources/Constructions.csv +37 -0
  9. data/lib/openstudio-standards/btap/costing/common_resources/construction_sets.csv +1270 -0
  10. data/lib/openstudio-standards/btap/costing/common_resources/constructions_glazing.csv +61 -0
  11. data/lib/openstudio-standards/btap/costing/common_resources/constructions_opaque.csv +2256 -0
  12. data/lib/openstudio-standards/btap/costing/common_resources/costs.csv +1904 -0
  13. data/lib/openstudio-standards/btap/costing/common_resources/costs_local_factors.csv +2315 -0
  14. data/lib/openstudio-standards/btap/costing/common_resources/hvac_vent_ahu.csv +925 -0
  15. data/lib/openstudio-standards/btap/costing/common_resources/lighting.csv +364 -0
  16. data/lib/openstudio-standards/btap/costing/common_resources/lighting_sets.csv +2667 -0
  17. data/lib/openstudio-standards/btap/costing/common_resources/locations.csv +75 -0
  18. data/lib/openstudio-standards/btap/costing/common_resources/materials_glazing.csv +35 -0
  19. data/lib/openstudio-standards/btap/costing/common_resources/materials_hvac.csv +1699 -0
  20. data/lib/openstudio-standards/btap/costing/common_resources/materials_lighting.csv +267 -0
  21. data/lib/openstudio-standards/btap/costing/common_resources/materials_opaque.csv +164 -0
  22. data/lib/openstudio-standards/btap/costing/copy_test_results_files_to_expected_results.rb +11 -0
  23. data/lib/openstudio-standards/btap/costing/cost_building_from_file.rb +136 -0
  24. data/lib/openstudio-standards/btap/costing/costing_database_wrapper.rb +177 -0
  25. data/lib/openstudio-standards/btap/costing/daylighting_sensor_control_costing.rb +353 -0
  26. data/lib/openstudio-standards/btap/costing/dcv_costing.rb +314 -0
  27. data/lib/openstudio-standards/btap/costing/dummy.epw +8768 -0
  28. data/lib/openstudio-standards/btap/costing/dummy.osm +5320 -0
  29. data/lib/openstudio-standards/btap/costing/envelope_costing.rb +284 -0
  30. data/lib/openstudio-standards/btap/costing/heating_cooling_costing.rb +2584 -0
  31. data/lib/openstudio-standards/btap/costing/led_lighting_costing.rb +155 -0
  32. data/lib/openstudio-standards/btap/costing/lighting_costing.rb +209 -0
  33. data/lib/openstudio-standards/btap/costing/mech_sizing.json +502 -0
  34. data/lib/openstudio-standards/btap/costing/neb_end_use_prices.csv +42 -0
  35. data/lib/openstudio-standards/btap/costing/necb_2011_spacetype_info.csv +225 -0
  36. data/lib/openstudio-standards/btap/costing/necb_reference_runs.csv +28705 -0
  37. data/lib/openstudio-standards/btap/costing/nv_costing.rb +547 -0
  38. data/lib/openstudio-standards/btap/costing/parallel_tests.rb +92 -0
  39. data/lib/openstudio-standards/btap/costing/pv_ground_costing.rb +687 -0
  40. data/lib/openstudio-standards/btap/costing/shw_costing.rb +705 -0
  41. data/lib/openstudio-standards/btap/costing/test_list.txt +17 -0
  42. data/lib/openstudio-standards/btap/costing/test_run_all_test_locally.rb +26 -0
  43. data/lib/openstudio-standards/btap/costing/test_run_costing_tests.rb +80 -0
  44. data/lib/openstudio-standards/btap/costing/ventilation_costing.rb +2616 -0
  45. data/lib/openstudio-standards/constructions/modify.rb +2 -1
  46. data/lib/openstudio-standards/standards/Standards.Model.rb +39 -9
  47. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb +2 -2
  48. data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/ashrae_90_1_prm.UserData.rb +6 -1
  49. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb +2 -27
  50. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/hvac_system_3_and_8_single_speed.rb +68 -27
  51. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/hvac_system_4.rb +64 -25
  52. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/hvac_system_6.rb +9 -14
  53. data/lib/openstudio-standards/standards/necb/ECMS/hvac_systems.rb +46 -20
  54. data/lib/openstudio-standards/standards/necb/NECB2011/autozone.rb +635 -248
  55. data/lib/openstudio-standards/standards/necb/NECB2011/data/constants.json +43 -7
  56. data/lib/openstudio-standards/standards/necb/NECB2011/data/fuel_type_sets.json +7 -1
  57. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/HighriseApartmentMult.osm +14272 -0
  58. data/lib/openstudio-standards/standards/necb/NECB2011/data/necb_2015_table_c1.json +1 -1
  59. data/lib/openstudio-standards/standards/necb/NECB2011/data/space_types.json +437 -437
  60. data/lib/openstudio-standards/standards/necb/NECB2011/data/systems.json +516 -0
  61. data/lib/openstudio-standards/standards/necb/NECB2011/data/systems_including_sys5.json +588 -0
  62. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_namer.rb +489 -0
  63. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_single_speed.rb +16 -6
  64. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_2_and_5.rb +48 -5
  65. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_multi_speed.rb +2 -2
  66. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb +35 -27
  67. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_4.rb +34 -23
  68. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_6.rb +8 -6
  69. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +42 -13
  70. data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +214 -25
  71. data/lib/openstudio-standards/standards/necb/NECB2011/system_fuels.rb +61 -1
  72. data/lib/openstudio-standards/standards/necb/NECB2015/data/space_types.json +636 -636
  73. data/lib/openstudio-standards/standards/necb/NECB2015/data/unitary_acs.json +38 -38
  74. data/lib/openstudio-standards/standards/necb/NECB2015/hvac_systems.rb +15 -6
  75. data/lib/openstudio-standards/standards/necb/NECB2017/data/space_types.json +636 -636
  76. data/lib/openstudio-standards/standards/necb/NECB2020/data/chillers.json +71 -71
  77. data/lib/openstudio-standards/standards/necb/README.md +343 -0
  78. data/lib/openstudio-standards/standards/necb/common/btap_data.rb +190 -28
  79. data/lib/openstudio-standards/standards/necb/common/btap_datapoint.rb +14 -5
  80. data/lib/openstudio-standards/standards/necb/common/eccc_electric_grid_intensity_20250311.csv +14 -0
  81. data/lib/openstudio-standards/standards/necb/common/nir_gas_grid_intensity_20250311.csv +14 -0
  82. data/lib/openstudio-standards/standards/necb/common/system_types.yaml +0 -0
  83. data/lib/openstudio-standards/utilities/logging.rb +18 -14
  84. data/lib/openstudio-standards/version.rb +1 -1
  85. data/lib/openstudio-standards/weather/modify.rb +2 -2
  86. data/lib/openstudio-standards.rb +12 -0
  87. metadata +53 -2
@@ -0,0 +1,705 @@
1
+ class BTAPCosting
2
+
3
+ # --------------------------------------------------------------------------------------------------
4
+ # This function gets all costs associated with SHW/DHW (i.e., tanks, pumps, flues, piping and
5
+ # utility costs)
6
+ # --------------------------------------------------------------------------------------------------
7
+ def shw_costing(model, prototype_creator)
8
+
9
+ @costing_report['shw'] = {}
10
+ totalCost = 0.0
11
+
12
+ # Get regional cost factors for this province and city
13
+ materials_hvac = @costing_database["raw"]["materials_hvac"]
14
+ hvac_material = materials_hvac.select {|data|
15
+ data['Material'].to_s == "WaterGas"}.first # Get any row from spreadsheet in case of region error
16
+ regional_material, regional_installation =
17
+ get_regional_cost_factors(@costing_report['province_state'], @costing_report['city'], hvac_material)
18
+ # Use wiring to get regional cost factors for electrical equipment such as conduit and VFDs
19
+ hvac_material_elec = get_cost_info(mat: 'Wiring', size: 14, unit: nil)
20
+ regional_material_elec, regional_installation_elec =
21
+ get_regional_cost_factors(@costing_report['province_state'], @costing_report['city'], hvac_material_elec)
22
+
23
+ # Store some geometry data for use below...
24
+ util_dist, ht_roof, nominal_flr2flr_height, horizontal_dist = getGeometryData(model, prototype_creator)
25
+
26
+ template_type = prototype_creator.template
27
+
28
+ plant_loop_info = {}
29
+ plant_loop_info[:shwtanks] = []
30
+ plant_loop_info[:shwpumps] = []
31
+ hphw_tank_names = []
32
+
33
+ num_reg_gas_tanks = 0
34
+ num_reg_oil_tanks = 0
35
+ num_elec_tanks = 0
36
+ num_hphw_tanks = 0
37
+ num_high_eff_gas_tanks = 0
38
+ num_high_eff_oil_tanks = 0
39
+
40
+ # HPHW heaters are stored outside of the plant loop
41
+ # Iterate through these first to determine if their are HPHW heaters
42
+ model.getWaterHeaterHeatPumps.each do |hphw|
43
+ if hphw.to_WaterHeaterHeatPump.is_initialized
44
+ hphw_tank_name = hphw.tank.name.get
45
+ hphw_tank_names << hphw_tank_name
46
+ end
47
+ end
48
+ # Iterate through the plant loops to get shw tank & pump data...
49
+ model.getPlantLoops.each do |plant_loop|
50
+ next unless plant_loop.name.get.to_s =~ /Main Service Water Loop/i
51
+ plant_loop.supplyComponents.each do |supply_comp|
52
+ if supply_comp.to_WaterHeaterMixed.is_initialized
53
+ tank = supply_comp.to_WaterHeaterMixed.get
54
+ tank_info = {}
55
+ plant_loop_info[:shwtanks] << tank_info
56
+ tank_info[:name] = tank.name.get
57
+ tank_info[:type] = "WaterHeater:Mixed"
58
+ tank_info[:heater_thermal_efficiency] = tank.heaterThermalEfficiency.get unless tank.heaterThermalEfficiency.empty?
59
+ tank_info[:heater_fuel_type] = tank.heaterFuelType
60
+ tank_info[:nominal_capacity] = tank.heaterMaximumCapacity.to_f / 1000 # kW
61
+ tank_info[:heater_volume_gal] = (OpenStudio.convert(tank.tankVolume.to_f, 'm^3', 'gal').get)
62
+ tank_info[:eff_mult] = 1.0
63
+ if tank.heaterFuelType =~ /Electric/i
64
+ # Check if the tank is associated with a HPHW heater
65
+ if hphw_tank_names.include?(tank.name.get)
66
+ tank_info[:heater_fuel_type] = 'HPHW_Heater'
67
+ tank_info[:tank_mult] = get_HVAC_multiplier(tank_info[:heater_fuel_type], tank_info[:nominal_capacity])
68
+ tank_info[:nominal_capacity] /= tank_info[:tank_mult]
69
+ tank_info[:heater_volume_gal] /= tank_info[:tank_mult]
70
+ num_hphw_tanks += tank_info[:tank_mult]
71
+ elsif !hphw_tank_names.include?(tank.name.get)
72
+ tank_info[:heater_fuel_type] = 'WaterElec'
73
+ tank_info[:tank_mult] = get_HVAC_multiplier(tank_info[:heater_fuel_type], tank_info[:nominal_capacity])
74
+ tank_info[:nominal_capacity] /= tank_info[:tank_mult]
75
+ tank_info[:heater_volume_gal] /= tank_info[:tank_mult]
76
+ num_elec_tanks += tank_info[:tank_mult]
77
+ end
78
+ elsif tank.heaterFuelType =~ /NaturalGas/i
79
+ tank_info[:heater_fuel_type] = 'WaterGas'
80
+ tank_info[:tank_mult] = get_HVAC_multiplier(tank_info[:heater_fuel_type], tank_info[:nominal_capacity])
81
+ tank_info[:nominal_capacity] /= tank_info[:tank_mult]
82
+ tank_info[:heater_volume_gal] /= tank_info[:tank_mult]
83
+ if tank_info[:heater_thermal_efficiency] >= 0.85
84
+ tank_info[:heater_fuel_type] = 'WaterGas_HE'
85
+ tank_info[:eff_mult] = 1.3
86
+ num_high_eff_gas_tanks += tank_info[:tank_mult]
87
+ else
88
+ num_reg_gas_tanks += tank_info[:tank_mult]
89
+ end
90
+ elsif tank.heaterFuelType =~ /Oil/i # Oil, FuelOil, FuelOil#2
91
+ tank_info[:heater_fuel_type] = 'WaterOil'
92
+ tank_info[:tank_mult] = get_HVAC_multiplier(tank_info[:heater_fuel_type], tank_info[:nominal_capacity])
93
+ tank_info[:nominal_capacity] /= tank_info[:tank_mult]
94
+ tank_info[:heater_volume_gal] /= tank_info[:tank_mult]
95
+ if tank_info[:heater_thermal_efficiency] >= 0.85
96
+ tank_info[:heater_fuel_type] = 'WaterOil_HE'
97
+ tank_info[:eff_mult] = 1.3
98
+ num_high_eff_oil_tanks += tank_info[:tank_mult]
99
+ else
100
+ num_reg_oil_tanks += tank_info[:tank_mult]
101
+ end
102
+ end
103
+ elsif supply_comp.to_PumpConstantSpeed.is_initialized
104
+ csPump = supply_comp.to_PumpConstantSpeed.get
105
+ csPump_info = {}
106
+ plant_loop_info[:shwpumps] << csPump_info
107
+ csPump_info[:name] = csPump.name.get
108
+ if csPump.isRatedPowerConsumptionAutosized.to_bool
109
+ csPumpSize = csPump.autosizedRatedPowerConsumption.to_f
110
+ else
111
+ csPumpSize = csPump.ratedPowerConsumption.to_f
112
+ end
113
+ csPump_info[:size] = csPumpSize.to_f # Watts
114
+ elsif supply_comp.to_PumpVariableSpeed.is_initialized
115
+ vsPump = supply_comp.to_PumpVariableSpeed.get
116
+ vsPump_info = {}
117
+ plant_loop_info[:shwpumps] << vsPump_info
118
+ vsPump_info[:name] = vsPump.name.get
119
+ if vsPump.isRatedPowerConsumptionAutosized.to_bool
120
+ vsPumpSize = vsPump.autosizedRatedPowerConsumption.to_f
121
+ else
122
+ vsPumpSize = vsPump.ratedPowerConsumption.to_f
123
+ end
124
+ vsPump_info[:size] = vsPumpSize.to_f # Watts
125
+ end
126
+ end
127
+ end
128
+
129
+ # Get costs associated with each shw tank
130
+ tankCost = 0.0 ; flueCost = 0.0 ; utilCost = 0.0 ; fuelFittingCost = 0.0; fuelLineCost = 0.0
131
+ multiplier = 1.0 ; primaryFuel = ''; primaryCap = 0
132
+
133
+ plant_loop_info[:shwtanks].each do |tank|
134
+ # Get primary/secondary/backup tank cost based on fuel type and capacity for each tank
135
+ #set to local variables.
136
+ primaryFuel = tank[:heater_fuel_type]
137
+ primaryCap = tank[:nominal_capacity].to_f
138
+ heaterVolGal = tank[:heater_volume_gal].to_f
139
+
140
+ #Get tank cost.
141
+ if primaryFuel.include?("WaterGas")
142
+ # For gas fired shw tanks we don't have to bother with volume. However, we have to accept a revised tank volume
143
+ # which is there for electric and oil tanks even though we won't use it.
144
+ shwTankCostInfo = getSHWTankCost(name: tank[:name], materialLookup: primaryFuel, materialSize: primaryCap, tankVol: nil)
145
+ tank[:nominal_cacacity] = shwTankCostInfo[:Cap_kW]
146
+ else
147
+ # If the SHW tank is electric or oil need to find the cost for one with a large enough capacity and volume. If
148
+ # no tanks have a large enough volume then get_SHWTankCost will find the tank with the largest volume and find
149
+ # how many tanks of that size are needed (multiplier). Below, if this multiplier is larger than one then
150
+ # the tank volume is adjusted to be the largest one that was found (by revVol) and the tank required capacity is
151
+ # reduced by dividing by the multiplier.
152
+ shwTankCostInfo = getSHWTankCost(name: tank[:name], materialLookup: primaryFuel, materialSize: primaryCap, tankVol: heaterVolGal)
153
+ tank[:heater_volume_gal] = shwTankCostInfo[:Vol_USGal]
154
+ tank[:nominal_capacity] = shwTankCostInfo[:Cap_kW]
155
+ if shwTankCostInfo[:multiplier] > 1.0
156
+ if primaryFuel.include?("WaterElec")
157
+ num_elec_tanks -= tank[:tank_mult]
158
+ tank[:tank_mult] *= shwTankCostInfo[:multiplier]
159
+ num_elec_tanks += tank[:tank_mult]
160
+ elsif primaryFuel.include?("HPHW_Heater")
161
+ num_hphw_tanks -= tank[:tank_mult]
162
+ tank[:tank_mult] *= shwTankCostInfo[:multiplier]
163
+ num_hphw_tanks += tank[:tank_mult]
164
+ else
165
+ if tank[:heater_thermal_efficiency] >= 0.85
166
+ num_high_eff_oil_tanks -= tank[:tank_mult]
167
+ tank[:tank_mult] *= shwTankCostInfo[:multiplier]
168
+ num_high_eff_oil_tanks += tank[:tank_mult]
169
+ else
170
+ num_reg_oil_tanks -= tank[:tank_mult]
171
+ tank[:tank_mult] *= shwTankCostInfo[:multiplier]
172
+ num_reg_oil_tanks += tank[:tank_mult]
173
+ end
174
+ end
175
+ end
176
+ end
177
+ matCost = shwTankCostInfo[:matCost]*tank[:tank_mult].to_f
178
+ labCost = shwTankCostInfo[:labCost]*tank[:tank_mult].to_f
179
+
180
+ thisTankCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
181
+ tankCost += thisTankCost
182
+
183
+ # Determine power venting costs for high efficiency tanks. Doing this here because tank multiplier and capacity
184
+ # may have changed.
185
+ if tank[:eff_mult] > 1.1
186
+ if shwTankCostInfo[:Cap_kW] < 200
187
+ # 1/8 hp power vent
188
+ materialHash = materials_hvac.find {|data|
189
+ data['Material'].to_s == 'Waterheater_power_vent' && data['Size'].to_s == '0.125'}
190
+ matCost, labCost = getCost('1/8 hp power vent', materialHash, multiplier)
191
+ flueCost += (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * tank[:tank_mult]
192
+ else
193
+ # 1/2 hp power vent
194
+ materialHash = materials_hvac.find {|data|
195
+ data['Material'].to_s == 'Waterheater_power_vent' && data['Size'].to_s == '0.5'}
196
+ matCost, labCost = getCost('1/2 hp power vent', materialHash, multiplier)
197
+ flueCost += (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0) * tank[:tank_mult]
198
+ end
199
+ end
200
+ end
201
+
202
+ numTanks = num_elec_tanks + num_hphw_tanks + num_reg_gas_tanks + num_high_eff_gas_tanks + num_reg_oil_tanks + num_high_eff_oil_tanks
203
+ numFuelTanks = num_reg_gas_tanks + num_high_eff_gas_tanks + num_reg_oil_tanks + num_high_eff_oil_tanks
204
+
205
+ if numTanks > 0
206
+ # Electric utility cost components (i.e., power lines).
207
+
208
+ # elec 600V #14 wire /100 ft (#848)
209
+ materialHash = get_cost_info(mat: 'Wiring', size: 14)
210
+ matCost, labCost = getCost('electrical wire - 600V #14', materialHash, multiplier)
211
+ elecWireCost = matCost * regional_material_elec / 100.0 + labCost * regional_installation_elec / 100.0
212
+
213
+ # 1 inch metal conduit (#851)
214
+ materialHash = get_cost_info(mat: 'Conduit', unit: 'L.F.')
215
+ matCost, labCost = getCost('1 inch metal conduit', materialHash, multiplier)
216
+ metalConduitCost = matCost * regional_material_elec / 100.0 + labCost * regional_installation_elec / 100.0
217
+
218
+ # Electric utility wire and conduit cost used by all tanks except HPHW
219
+ utilCost += (metalConduitCost * util_dist + elecWireCost * util_dist / 100) * (numTanks - num_hphw_tanks)
220
+
221
+ # Get costs condition on fuel types.
222
+ if numFuelTanks> 0
223
+ numRegFuelTanks = num_reg_gas_tanks + num_reg_oil_tanks
224
+ numHighEffFuelTanks = num_high_eff_gas_tanks + num_high_eff_oil_tanks
225
+
226
+ # Gas/Oil line piping cost per ft (#1)
227
+ materialHash = get_cost_info(mat: 'GasLine', unit: 'L.F.')
228
+ matCost, labCost = getCost('fuel line', materialHash, multiplier)
229
+ fuelLineCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
230
+
231
+ # Gas/Oil line fitting connection per tank (#2)
232
+ materialHash = get_cost_info(mat: 'GasLine', unit: 'each')
233
+ matCost, labCost = getCost('fuel line fitting connection', materialHash, multiplier)
234
+ fuelFittingCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
235
+
236
+ if numRegFuelTanks > 0
237
+ # Flue and utility component costs (for gas and oil tanks only)
238
+ # Calculate flue costs once for all tanks since flues combined by header when multiple tanks
239
+ # 6 inch diameter flue (#384)
240
+ materialHash = get_cost_info(mat: 'Venting', size: 6)
241
+ matCost, labCost = getCost('flue', materialHash, multiplier)
242
+ flueVentCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
243
+
244
+ #6 inch elbow fitting (#386)
245
+ materialHash = get_cost_info(mat: 'VentingElbow', size: 6)
246
+ matCost, labCost = getCost('flue elbow', materialHash, multiplier)
247
+ flueElbowCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
248
+
249
+ # 6 inch top (#392)
250
+ materialHash = get_cost_info(mat: 'VentingTop', size: 6)
251
+ matCost, labCost = getCost('flue top', materialHash, multiplier)
252
+ flueTopCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
253
+
254
+ # Adding one regular flue if any regular efficiency shw tanks are present
255
+ flueCost += flueVentCost * ht_roof + flueElbowCost + flueTopCost
256
+
257
+ # Header cost only non-zero if there is a secondary/backup gas/oil tank
258
+ if numRegFuelTanks > 1
259
+ # Check if need a flue header (i.e., there are both primary and secondary/backup tanks)
260
+ # 6 inch diameter header (#384)
261
+ materialHash = get_cost_info(mat: 'Venting', size: 6)
262
+ matCost, labCost = getCost('flue header', materialHash, multiplier)
263
+ headerVentCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
264
+
265
+ #6 inch elbow fitting for header (#386)
266
+ materialHash = get_cost_info(mat: 'VentingElbow', size: 6)
267
+ matCost, labCost = getCost('flue header elbow', materialHash, multiplier)
268
+ headerElbowCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
269
+
270
+ # Adding a regular tank header for every additional regular efficiency SHW tank present
271
+ # Assume a header length of 20 ft and an elbow fitting for each tank connected to the header
272
+ flueCost += (headerVentCost * 20 + headerElbowCost) * (numRegFuelTanks - 1)
273
+ end
274
+ end
275
+
276
+ # If high efficiency fuel fired shw tanks are present add flues (1 per tank)
277
+ if numHighEffFuelTanks > 0
278
+ #6 inch PVC pipe (#1327)
279
+ materialHash = get_cost_info(mat: 'Vent_pvc', size: 6)
280
+ matCost, labCost = getCost('flue', materialHash, multiplier)
281
+ pvcFluePipe = (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
282
+
283
+ #6 inch PVC Coupling (#1319)
284
+ materialHash = get_cost_info(mat: 'Vent_pvc_coupling', size: 6)
285
+ matCost, labCost = getCost('flue elbow', materialHash, multiplier)
286
+ pvcFlueCoupling = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
287
+
288
+ # 6 inch PVC elbow (#1329)
289
+ materialHash = get_cost_info(mat: 'Vent_pvc_elbow', size: 6)
290
+ matCost, labCost = getCost('flue top', materialHash, multiplier)
291
+ pvcFlueElbow = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
292
+
293
+ # Adding PVC flue costs for all high efficiency fuel fired SHW tanks
294
+ flueCost += (pvcFluePipe * 20.0 + pvcFlueCoupling + pvcFlueElbow) * numHighEffFuelTanks
295
+ end
296
+
297
+ # If natural gas tanks are present include fuel line and connectors
298
+ if (num_reg_gas_tanks + num_reg_gas_tanks) > 0
299
+ # Gas tanks require fuel line+valves+connectors
300
+ utilCost += (fuelLineCost * util_dist + fuelFittingCost) * (num_reg_gas_tanks + num_high_eff_gas_tanks)
301
+
302
+ elsif (num_reg_oil_tanks + num_high_eff_oil_tanks) > 0
303
+ # Oil tanks require fuel line+valves+connectors and electrical conduit
304
+
305
+ # Oil filtering system (#4)
306
+ materialHash = get_cost_info(mat: 'OilLine', unit: 'each')
307
+ matCost, labCost = getCost('Oil filtering system', materialHash, multiplier)
308
+ oilFilterCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
309
+
310
+ # 2000 USG above ground tank (#5)
311
+ materialHash = get_cost_info(mat: 'OilTanks', size: 2000)
312
+ matCost, labCost = getCost('Oil tank (2000 USG)', materialHash, multiplier)
313
+ oilTankCost = matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
314
+
315
+ utilCost += (fuelLineCost * util_dist + fuelFittingCost) * (num_reg_oil_tanks + num_high_eff_oil_tanks) + oilFilterCost + oilTankCost
316
+ end
317
+ end
318
+ end
319
+
320
+ # Tank pump costs
321
+ pumpCost = 0.0; pipingCost = 0.0; numPumps = 0; pumpName = ''; pumpSize = 0.0
322
+ plant_loop_info[:shwpumps].each do |pump|
323
+ numPumps += 1
324
+ # Cost variable and constant volume pumps the same (the difference is in extra cost for VFD controller)
325
+ pumpSize = pump[:size]; pumpName = pump[:name]
326
+ matCost, labCost = getHVACCost(pumpName, 'Pumps', pumpSize, false)
327
+ pumpCost += matCost * regional_material / 100.0 + labCost * regional_installation / 100.0
328
+ if pump[:name] =~ /variable/i
329
+ # Cost the VFD controller for the variable pump costed above
330
+ pumpSize = pump[:size]; pumpName = pump[:name]
331
+ matCost, labCost = getHVACCost(pumpName, 'VFD', pumpSize, false)
332
+ pumpCost += matCost * regional_material_elec / 100.0 + labCost * regional_installation_elec / 100.0
333
+ end
334
+ end
335
+ #if numTanks > 1 && numPumps < 2
336
+ # Add pump costing for the backup tank pump.
337
+ # 2024-04-25: No longer including redundant costs.
338
+ #pumpCost *= 2.0
339
+ #numPumps = 2 # reset the number of pumps for piping costs below
340
+ #end
341
+ # Double the pump costs to accomodate the costing of a backup pumps for each tank!
342
+ # 2024-04-25: No longer including redundant casts.
343
+ # pumpCost *= 2.0
344
+
345
+ # Tank water piping cost: Add piping elbows, valves and insulation from the tank(s)
346
+ # to the pumps(s) assuming a pipe diameter of 1” and a distance of 10 ft per pump
347
+ if numTanks > 0
348
+ # 1 inch Steel pipe
349
+ matCost, labCost = getHVACCost('1 inch steel pipe', 'SteelPipe', 1)
350
+ pipingCost += 10.0 * numPumps * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
351
+
352
+ # 1 inch Steel pipe insulation
353
+ matCost, labCost = getHVACCost('1 inch pipe insulation', 'PipeInsulation', 1)
354
+ pipingCost += 10.0 * numPumps * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
355
+
356
+ # 1 inch Steel pipe elbow
357
+ matCost, labCost = getHVACCost('1 inch steel pipe elbow', 'SteelPipeElbow', 1)
358
+ pipingCost += 2.0 * numPumps * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
359
+
360
+ # 1 inch gate valves
361
+ matCost, labCost = getHVACCost('1 inch gate valves', 'ValvesGate', 1)
362
+ pipingCost += 1.0 * numPumps * (matCost * regional_material / 100.0 + labCost * regional_installation / 100.0)
363
+ end
364
+
365
+ # 2023-04-25: Removing costing for redundant equipment and piping.
366
+ #if numTanks > 1
367
+ # Double pump piping cost to account for second tank
368
+ # pipingCost *= 2
369
+ #end
370
+
371
+ # ckirney, 2019-04-12: shw_distribution_costing mostly completed however priorities have changed for now so
372
+ # completion and testing will be delayed. Adding code to master for now but it will not be called until it is
373
+ # ready.
374
+ # distCost = shw_distribution_costing(model: model, prototype_creator: prototype_creator)
375
+
376
+ totalCost = tankCost + flueCost + utilCost + pumpCost + pipingCost
377
+
378
+ @costing_report['shw'] = {
379
+ 'shw_nom_flr2flr_hght_ft' => nominal_flr2flr_height.round(1),
380
+ 'shw_ht_roof' => ht_roof.round(1),
381
+ 'shw_longest_distance_to_ext_ft' => horizontal_dist.round(1),
382
+ 'shw_utility_distance_ft' => util_dist.round(1),
383
+ 'shw_tanks' => tankCost.round(2),
384
+ 'shw_num_of_modeled_tanks' => plant_loop_info[:shwtanks].size,
385
+ 'num_elec_tanks' => num_elec_tanks,
386
+ 'num_hphw_tanks' => num_hphw_tanks,
387
+ 'shw_num_reg_eff_gas_tanks' => num_reg_gas_tanks,
388
+ 'shw_num_high_eff_gas_tanks' => num_high_eff_gas_tanks,
389
+ 'shw_num_reg_eff_oil_tanks' => num_reg_oil_tanks,
390
+ 'shw_num_high_eff_oil_tanks' => num_high_eff_oil_tanks,
391
+ 'shw_num_of_costed_tanks' => numTanks,
392
+ 'shw_flues' => flueCost.round(2),
393
+ 'shw_utilties' => utilCost.round(2),
394
+ 'shw_pumps' => pumpCost.round(2),
395
+ 'shw_num_of_pumps' => plant_loop_info[:shwpumps].size,
396
+ 'shw_piping' => pipingCost.round(2),
397
+ 'shw_total' => totalCost.round(2)
398
+ }
399
+ puts "\nHVAC SHW costing data successfully generated. Total shw costs: $#{totalCost.round(2)}"
400
+
401
+ return totalCost
402
+ end
403
+
404
+ def shw_distribution_costing(model:, prototype_creator:)
405
+ total_shw_dist_cost = 0
406
+ roof_cent = prototype_creator.find_highest_roof_centre(model)
407
+ mech_room, cond_spaces = prototype_creator.find_mech_room(model)
408
+ min_space = get_lowest_space(spaces: cond_spaces)
409
+ mech_sizing_info = read_mech_sizing()
410
+ shw_sp_types = get_mech_table(mech_size_info: mech_sizing_info, table_name: 'shw_space_types')
411
+ excl_sp_types = get_mech_table(mech_size_info: mech_sizing_info, table_name: 'exclusive_shw_space_types')
412
+ shw_main_cost = cost_shw_main(mech_room: mech_room, roof_cent: roof_cent, min_space: min_space)
413
+ total_shw_dist_cost += shw_main_cost[:cost]
414
+ #determine if space is wet: prototype_creator.is_an_necb_wet_space?(space)
415
+ #Sort spaces by floor and conditioned spaces
416
+ space_mod = OpenstudioStandards::Space
417
+ model.getBuildingStorys.sort.each do |build_story|
418
+ public_wash = false
419
+ other_public_wash = false
420
+ build_story.spaces.sort.each do |space|
421
+ next unless (space_mod.space_heated?(space) || space_mod.space_cooled?(space)) && !space_mod.space_plenum?(space)
422
+ sp_type_name = space.spaceType.get.nameString
423
+ shw_neccesary = shw_sp_types.select {|table_sp_type|
424
+ !/#{table_sp_type.upcase}/.match(sp_type_name.upcase).nil?
425
+ }
426
+ if shw_neccesary.empty?
427
+ public_wash = true
428
+ else
429
+ shw_dist_cost = get_shw_dist_cost(space: space, roof_cent: roof_cent)
430
+ total_shw_dist_cost += shw_dist_cost[:cost]
431
+ public_shw = excl_sp_types.select {|ex_table_sp_type|
432
+ !/#{ex_table_sp_type.upcase}/.match(sp_type_name.upcase).nil?
433
+ }
434
+ other_public_wash = true
435
+ end
436
+ end
437
+ if public_wash == true && other_public_wash == false
438
+ #Cost two shw piping to two washrooms in the center of the story. Assume each has 20 feet of supply and return
439
+ #shw piping to the story center (10 feet supply, 10 feet return).
440
+ dist_ft = 40
441
+ shw_dist_search = []
442
+ shw_dist_search << {
443
+ mat: 'CopperPipe',
444
+ unit: 'L.F.',
445
+ size: 0.75,
446
+ mult: dist_ft
447
+ }
448
+ washroom_shw_cost = get_comp_cost(cost_info: shw_dist_search)
449
+ total_shw_dist_cost += washroom_shw_cost
450
+ end
451
+ end
452
+ return total_shw_dist_cost
453
+ end
454
+
455
+ def get_space_floor_centroid(space:)
456
+ # Determine the bottom surface of the space and calculate it's centroid.
457
+ # Get the coordinates of the origin for the space (the coordinates of points in the space are relative to this).
458
+ xOrigin = space.xOrigin
459
+ yOrigin = space.yOrigin
460
+ zOrigin = space.zOrigin
461
+ # Get the surfaces for the space.
462
+ space_surfaces = space.surfaces
463
+ # Find the floor (aka the surface with the lowest centroid).
464
+ min_surf = space_surfaces.min_by{|sp_surface| (sp_surface.centroid.z.to_f)}
465
+ # The following is added to determine the overall floor centroid because some spaces have floors composed of more than one surface.
466
+ floor_centroid = [0, 0, 0]
467
+ space_surfaces.each do |sp_surface|
468
+ if min_surf.centroid.z.to_f.round(8) == sp_surface.centroid.z.to_f.round(8)
469
+ floor_centroid[0] = floor_centroid[0] + sp_surface.centroid.x.to_f*sp_surface.grossArea.to_f
470
+ floor_centroid[1] = floor_centroid[1] + sp_surface.centroid.y.to_f*sp_surface.grossArea.to_f
471
+ floor_centroid[2] = floor_centroid[2] + sp_surface.grossArea
472
+ end
473
+ end
474
+
475
+ # Determine the floor centroid
476
+ floor_centroid[0] = floor_centroid[0]/floor_centroid[2]
477
+ floor_centroid[1] = floor_centroid[1]/floor_centroid[2]
478
+
479
+ return {
480
+ centroid: [floor_centroid[0] + xOrigin, floor_centroid[1] + yOrigin, min_surf.centroid.z.to_f + zOrigin],
481
+ floor_area_m2: floor_centroid[2]
482
+ }
483
+ end
484
+
485
+ def get_shw_dist_cost(space:, roof_cent:)
486
+ shw_dist_search = []
487
+ space_cent = get_space_floor_centroid(space: space)
488
+ dist_m = (roof_cent[:roof_centroid][0] - space_cent[:centroid][0]).abs + (roof_cent[:roof_centroid][1] - space_cent[:centroid][1]).abs
489
+ dist_ft = OpenStudio.convert(dist_m, 'm', 'ft').get
490
+ shw_dist_search << {
491
+ mat: 'CopperPipe',
492
+ unit: 'L.F.',
493
+ size: 0.75,
494
+ mult: dist_ft
495
+ }
496
+ total_comp_cost = get_comp_cost(cost_info: shw_dist_search)
497
+ return {
498
+ length_m: dist_m,
499
+ cost: total_comp_cost
500
+ }
501
+ end
502
+
503
+ def cost_shw_main(mech_room:, roof_cent:, min_space:)
504
+ shw_dist_search = []
505
+ building_height_m = (roof_cent[:roof_centroid][2] - min_space[:roof_cent][2]).abs
506
+ mech_to_cent_dist_m = (roof_cent[:roof_centroid][0] - mech_room['space_centroid'][0]).abs + (roof_cent[:roof_centroid][1] - mech_room['space_centroid'][1]).abs
507
+ #Twice the distance to account for supply and return shw piping.
508
+ total_dist_m = 2*(building_height_m + mech_to_cent_dist_m)
509
+ total_dist_ft = OpenStudio.convert(total_dist_m, 'm', 'ft').get
510
+ shw_dist_search << {
511
+ mat: 'CopperPipe',
512
+ unit: 'L.F.',
513
+ size: 0.75,
514
+ mult: total_dist_ft
515
+ }
516
+ total_comp_cost = get_comp_cost(cost_info: shw_dist_search)
517
+ return {
518
+ length_m: total_dist_m,
519
+ cost: total_comp_cost
520
+ }
521
+ end
522
+
523
+ # Getting cost for SHW Tanks. This method is different from the getHVACCost method used everywhere else in that it
524
+ # accepts a tank volume argument in addition to the tank capacity (materialSize in this case). This additional
525
+ # argument means that the method must search for a SHW tank heated with the right fuel that has a large enough
526
+ # capacity and volume.
527
+ #
528
+ # IMPORTANT: This method assumes that when SHW tanks are retrieved from the model their capacity is checked against
529
+ # the capacities of costed tanks. If the modeled capacity is too large then it is costed as though multiple smaller
530
+ # tanks are present. Thus, this method assumes that anything passed to it will be small enough to be costed. This is
531
+ # another difference from the getHVACCost method which includes a call to get_HVAC_multiplier that checks if a costed
532
+ # item is too big and should be replaced by several smaller items.
533
+ #
534
+ # Note that the multiplier is always set to 1.0 when claculating the cost. That is because the multiplier is applied
535
+ # to the cost in the main shw_costing method.
536
+ def getSHWTankCost(name:, materialLookup:, materialSize:, tankVol:)
537
+ multiplier = 1.0
538
+ materials_hvac = @costing_database['raw']['materials_hvac']
539
+ # Get costing spreadsheet data for gas and oil fired mixed shw tanks
540
+ if tankVol.nil?
541
+ # If no tank volume is provided then only look at capacity.
542
+ # Get all capacities hor that type of tank.
543
+ hvac_materials = materials_hvac.select {|data|
544
+ data['Material'].to_s == materialLookup.to_s && data['Size'].to_f >= materialSize.to_f
545
+ }
546
+ if hvac_materials.empty?
547
+ # If no tanks have a big enough capacity then something is amiss and return an error (this should never happen
548
+ # because tanks capacity should be checked before this method is called).
549
+ puts "HVAC material error! Could not find next largest size for #{name} in #{materials_hvac}"
550
+ raise
551
+ elsif hvac_materials.size == 1
552
+ # Only one tank has an appropriate capacity find it's cost and return it.
553
+ matCost, labCost = getCost(name, hvac_materials[0], 1.0)
554
+ ret_hash = {
555
+ matCost: matCost,
556
+ labCost: labCost,
557
+ multiplier: multiplier,
558
+ Vol_USGal: tankVol,
559
+ Cap_kW: hvac_materials[0]['Size'].to_f
560
+
561
+ }
562
+ return ret_hash
563
+ else
564
+ # More than one tank has a big enough capacity. Find the cost of the one with teh smallest capacity and return
565
+ # it.
566
+ hvac_material = hvac_materials.min_by {|data| data['Size'].to_f}
567
+ matCost, labCost = getCost(name, hvac_material, 1.0)
568
+ ret_hash = {
569
+ matCost: matCost,
570
+ labCost: labCost,
571
+ multiplier: multiplier,
572
+ Vol_USGal: tankVol,
573
+ Cap_kW: hvac_material['Size'].to_f
574
+ }
575
+ return ret_hash
576
+ end
577
+ else
578
+ # We need to find a tank with a big enough capacity and volume.
579
+ # First see if a unique tank with a large enough capacity and volume exists
580
+ hvac_materials = materials_hvac.select {|data|
581
+ data['Material'].to_s == materialLookup.to_s && data['Size'].to_f >= materialSize.to_f && data['Fuel'].to_f >= tankVol
582
+ }
583
+ if hvac_materials.empty?
584
+ # If none exists see if the tank volume is big enough. Note that tank capacity was checked earlier so capacity
585
+ # should not be an issue. However, volume was not checked. It is possible that tanks with a big enough
586
+ # capacity are in the costing database but not a big enough volume.
587
+ #
588
+ # Find the largest volume tank with a big enough capacity. Find out how many of those tanks are needed to
589
+ # satisfy the volume requirement.
590
+ multiplier, revVol = get_SHW_vol_multiplier(materialLookup: materialLookup, materialSize: materialSize, materialVol: tankVol)
591
+ materialSize /= multiplier
592
+ tankVol = revVol
593
+ # Try again to get tanks with a large enough size and capacity
594
+ hvac_materials = materials_hvac.select {|data|
595
+ data['Material'].to_s == materialLookup.to_s && data['Size'].to_f >= materialSize.to_f && data['Fuel'].to_f >= tankVol
596
+ }
597
+ # You may notice that there is no handling for cases where there is more than one tank with a large enough
598
+ # capacity and volume. There actually is, it is just a little further below.
599
+ if hvac_materials.empty?
600
+ puts "HVAC material error! Could not find a #{name} tank with a capacity >= #{materialSize} kW and a volume >= #{tankVol} US Gal in #{materials_hvac}"
601
+ elsif hvac_materials.size == 1
602
+ matCost, labCost = getCost(name, hvac_materials[0], 1.0)
603
+ ret_hash = {
604
+ matCost: matCost,
605
+ labCost: labCost,
606
+ multiplier: multiplier,
607
+ Vol_USGal: hvac_materials[0]['Fuel'].to_f,
608
+ Cap_kW: hvac_materials[0]['Size'].to_f
609
+ }
610
+ return ret_hash
611
+ end
612
+ elsif hvac_materials.size == 1
613
+ matCost, labCost = getCost(name, hvac_materials[0], 1.0)
614
+ ret_hash = {
615
+ matCost: matCost,
616
+ labCost: labCost,
617
+ multiplier: multiplier,
618
+ Vol_USGal: hvac_materials[0]['Fuel'].to_f,
619
+ Cap_kW: hvac_materials[0]['Size'].to_f
620
+ }
621
+ return ret_hash
622
+ end
623
+ # If mare than one tank has a lorge enough capacity and volume then find the one with the smallest volume.
624
+ hvac_materials_min_vol = hvac_materials.min_by {|data| data['Fuel'].to_f}
625
+ if hvac_materials_min_vol.nil?
626
+ # Well, something went horribly wrong. You should have gotten this far only if there were several tanks that
627
+ # had a large enough capacity and volume. Now we can't find the smallest one. Not sure what happened but
628
+ # whatever it was it is not good.
629
+ puts "HVAC material error! Could not find a #{name} tank with a capacity >= #{materialSize} kW and a volume >= #{materialVol} US Gal in costing database."
630
+ raise
631
+ else
632
+ # Find how many tanks have the lowest volume.
633
+ hvac_materials_vol = hvac_materials.select {|data| data['Fuel'].to_f == hvac_materials_min_vol['Fuel'].to_f}
634
+ if hvac_materials_vol.size == 1
635
+ hvac_material = hvac_materials_vol[0]
636
+ else
637
+ # If more than one tank as a small enough volume choose the one with the smallest capacity.
638
+ hvac_material = hvac_materials_vol.min_by {|data| data['Size'].to_f}
639
+ end
640
+ matCost, labCost = getCost(name, hvac_material, 1.0)
641
+ ret_hash = {
642
+ matCost: matCost,
643
+ labCost: labCost,
644
+ multiplier: multiplier,
645
+ Vol_USGal: hvac_material['Fuel'].to_f,
646
+ Cap_kW: hvac_material['Size'].to_f
647
+ }
648
+ return ret_hash
649
+ end
650
+ end
651
+ end
652
+
653
+ # This method is a copy of get_HVAC_multiplier but searches for volume in the 'Fuel' column of the materials_hvac
654
+ # sheet. The 'Fuel' column is where tank volume information is kept for electric and oil SHW tanks.
655
+ def get_SHW_vol_multiplier(materialLookup:, materialSize:, materialVol:)
656
+ multiplier = 1.0
657
+ materials_hvac = @costing_database['raw']['materials_hvac'].select {|data|
658
+ data['Material'].to_s.upcase == materialLookup.to_s.upcase && data['Size'].to_f >= materialSize
659
+ }
660
+ if materials_hvac.nil? || materials_hvac.empty?
661
+ puts("Error: no hvac information available for equipment #{materialLookup}!")
662
+ raise
663
+ end
664
+ materials_hvac.length == 1 ? max_size = materials_hvac[0] : max_size = materials_hvac.max_by {|d| d['Fuel'].to_f}
665
+ if max_size['Fuel'].to_f <= 0
666
+ puts("Error: #{materialLookup} has a volume of 0 or less. Please check that the correct costing_database.json file is being used or check the costing spreadsheet!")
667
+ raise
668
+ end
669
+ mult = materialVol.to_f / (max_size['Fuel'].to_f)
670
+
671
+ multiplier = (mult.to_i).to_f + 1.0 # Use next largest integer for multiplier
672
+ return multiplier.to_f, max_size['Fuel'].to_f
673
+ end
674
+
675
+ def get_cost_info(mat:, size: nil, unit: nil)
676
+ comp_info = nil
677
+ if unit.nil?
678
+ comp_info = @costing_database['raw']['materials_hvac'].select {|data|
679
+ data['Material'].to_s.upcase == mat.to_s.upcase and
680
+ data['Size'].to_f.round(2) == size.to_f.round(2)
681
+ }.first
682
+ elsif size.nil?
683
+ comp_info = @costing_database['raw']['materials_hvac'].select {|data|
684
+ data['Material'].to_s.upcase == mat.to_s.upcase and
685
+ data['unit'].to_s.upcase == unit.to_s.upcase
686
+ }.first
687
+ elsif size.nil? && unit.nil?
688
+ comp_info = @costing_database['raw']['materials_hvac'].select {|data|
689
+ data['Material'].to_s.upcase == mat.to_s.upcase
690
+ }.first
691
+ else
692
+ comp_info = @costing_database['raw']['materials_hvac'].select {|data|
693
+ data['Material'].to_s.upcase == mat.to_s.upcase and
694
+ data['Size'].to_f.round(2) == size.to_f.round(2) and
695
+ data['unit'].to_s.upcase == unit.to_s.upcase
696
+ }.first
697
+ end
698
+ if comp_info.nil?
699
+ puts("No data found for material: #{mat}, size: #{size}, with unit: #{unit}")
700
+ raise
701
+ end
702
+ return comp_info
703
+ end
704
+
705
+ end