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,284 @@
1
+ class String
2
+ def underscore
3
+ self.gsub(/::/, '/').
4
+ gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
5
+ gsub(/([a-z\d])([A-Z])/, '\1_\2').
6
+ tr("-", "_").
7
+ downcase
8
+ end
9
+ end
10
+
11
+ class BTAPCosting
12
+ def cost_audit_envelope(model, prototype_creator)
13
+ # These are the only envelope costing items we are considering for envelopes..
14
+ costed_surfaces = [
15
+ "ExteriorWall",
16
+ "ExteriorRoof",
17
+ "ExteriorFloor",
18
+ "ExteriorFixedWindow",
19
+ "ExteriorOperableWindow",
20
+ "ExteriorSkylight",
21
+ "ExteriorTubularDaylightDiffuser",
22
+ "ExteriorTubularDaylightDome",
23
+ "ExteriorDoor",
24
+ "ExteriorGlassDoor",
25
+ "ExteriorOverheadDoor",
26
+ "GroundContactWall",
27
+ "GroundContactRoof",
28
+ "GroundContactFloor"
29
+ ]
30
+ costed_surfaces.each do |surface_type|
31
+ @costing_report["envelope"]["#{surface_type.underscore}_cost"] = 0.00
32
+ @costing_report["envelope"]["#{surface_type.underscore}_area_m2"] = 0.0
33
+ @costing_report["envelope"]["#{surface_type.underscore}_cost_per_m2"] = 0.00
34
+ end
35
+
36
+ @costing_report["envelope"]["construction_costs"] = []
37
+
38
+ # Store number of stories. Required for envelope costing logic.
39
+ num_of_above_ground_stories = model.getBuilding.standardsNumberOfAboveGroundStories.to_i
40
+
41
+ template_type = prototype_creator.template
42
+
43
+ closest_loc = get_closest_cost_location(model.getWeatherFile.latitude, model.getWeatherFile.longitude)
44
+ generate_construction_cost_database_for_city(@costing_report["city"], @costing_report["province_state"])
45
+
46
+ totEnvCost = 0
47
+
48
+ # Iterate through the thermal zones.
49
+ model.getThermalZones.sort.each do |zone|
50
+ # Iterate through spaces.
51
+ zone.spaces.sort.each do |space|
52
+ # Get SpaceType defined for space.. if not defined it will skip the spacetype. May have to deal with Attic spaces.
53
+ if space.spaceType.empty? or space.spaceType.get.standardsSpaceType.empty? or space.spaceType.get.standardsBuildingType.empty?
54
+ raise ("standards Space type and building type is not defined for space:#{space.name.get}. Skipping this space for costing.")
55
+ end
56
+
57
+ # Get space type standard names.
58
+ space_type = space.spaceType.get.standardsSpaceType
59
+ building_type = space.spaceType.get.standardsBuildingType
60
+
61
+ # Get standard constructions based on collected information (spacetype, no of stories, etc..)
62
+ # This is a standard way to search a hash.
63
+ construction_set = @costing_database['raw']['construction_sets'].select { |data|
64
+ data['template'].to_s.gsub(/\s*/, '') == template_type and
65
+ data['building_type'].to_s.downcase == building_type.to_s.downcase and
66
+ data['space_type'].to_s.downcase == space_type.to_s.downcase and
67
+ data['min_stories'].to_i <= num_of_above_ground_stories and
68
+ data['max_stories'].to_i >= num_of_above_ground_stories
69
+ }.first
70
+
71
+
72
+ # Create Hash to store surfaces for this space by surface type
73
+ surfaces = {}
74
+ #Exterior
75
+ exterior_surfaces = BTAP::Geometry::Surfaces::filter_by_boundary_condition(space.surfaces, "Outdoors")
76
+ surfaces["ExteriorWall"] = BTAP::Geometry::Surfaces::filter_by_surface_types(exterior_surfaces, "Wall")
77
+ surfaces["ExteriorRoof"] = BTAP::Geometry::Surfaces::filter_by_surface_types(exterior_surfaces, "RoofCeiling")
78
+ surfaces["ExteriorFloor"] = BTAP::Geometry::Surfaces::filter_by_surface_types(exterior_surfaces, "Floor")
79
+ # Exterior Subsurface
80
+ exterior_subsurfaces = exterior_surfaces.flat_map(&:subSurfaces)
81
+ surfaces["ExteriorFixedWindow"] = BTAP::Geometry::Surfaces::filter_subsurfaces_by_types(exterior_subsurfaces, ["FixedWindow"])
82
+ surfaces["ExteriorOperableWindow"] = BTAP::Geometry::Surfaces::filter_subsurfaces_by_types(exterior_subsurfaces, ["OperableWindow"])
83
+ surfaces["ExteriorSkylight"] = BTAP::Geometry::Surfaces::filter_subsurfaces_by_types(exterior_subsurfaces, ["Skylight"])
84
+ surfaces["ExteriorTubularDaylightDiffuser"] = BTAP::Geometry::Surfaces::filter_subsurfaces_by_types(exterior_subsurfaces, ["TubularDaylightDiffuser"])
85
+ surfaces["ExteriorTubularDaylightDome"] = BTAP::Geometry::Surfaces::filter_subsurfaces_by_types(exterior_subsurfaces, ["TubularDaylightDome"])
86
+ surfaces["ExteriorDoor"] = BTAP::Geometry::Surfaces::filter_subsurfaces_by_types(exterior_subsurfaces, ["Door"])
87
+ surfaces["ExteriorGlassDoor"] = BTAP::Geometry::Surfaces::filter_subsurfaces_by_types(exterior_subsurfaces, ["GlassDoor"])
88
+ surfaces["ExteriorOverheadDoor"] = BTAP::Geometry::Surfaces::filter_subsurfaces_by_types(exterior_subsurfaces, ["OverheadDoor"])
89
+
90
+ # Ground Surfaces
91
+ ground_surfaces = BTAP::Geometry::Surfaces::filter_by_boundary_condition(space.surfaces, "Ground")
92
+ ground_surfaces += BTAP::Geometry::Surfaces::filter_by_boundary_condition(space.surfaces, "Foundation")
93
+ surfaces["GroundContactWall"] = BTAP::Geometry::Surfaces::filter_by_surface_types(ground_surfaces, "Wall")
94
+ surfaces["GroundContactRoof"] = BTAP::Geometry::Surfaces::filter_by_surface_types(ground_surfaces, "RoofCeiling")
95
+ surfaces["GroundContactFloor"] = BTAP::Geometry::Surfaces::filter_by_surface_types(ground_surfaces, "Floor")
96
+
97
+
98
+ # Iterate through
99
+ costed_surfaces.each do |surface_type|
100
+ # Get Costs for this construction type. This will get the cost for the particular construction type
101
+ # for all rsi levels for this location. This has been collected by the API costs data. Note that a space_type
102
+ # of "- undefined -" will create a nil construction_set!
103
+
104
+
105
+ if construction_set.nil?
106
+ cost_range_hash = {}
107
+ else
108
+ cost_range_hash = @costing_database['constructions_costs'].select { |construction|
109
+ construction['construction_type_name'] == construction_set[surface_type] &&
110
+ construction['province_state'] == @costing_report["province_state"] &&
111
+ construction['city'] == @costing_report["city"]
112
+ }
113
+ end
114
+
115
+ # We don't need all the information, just the rsi and cost. However, for windows rsi = 1/u_w_per_m2_k
116
+ surfaceIsGlazing = (surface_type == 'ExteriorFixedWindow' || surface_type == 'ExteriorOperableWindow' ||
117
+ surface_type == 'ExteriorSkylight' || surface_type == 'ExteriorTubularDaylightDiffuser' ||
118
+ surface_type == 'ExteriorTubularDaylightDome' || surface_type == 'ExteriorGlassDoor')
119
+ if surfaceIsGlazing
120
+ cost_range_array = cost_range_hash.map { |cost|
121
+ [
122
+ (1.0 / cost['u_w_per_m2_k'].to_f),
123
+ cost['total_cost_with_op']
124
+ ]
125
+ }
126
+ else
127
+ cost_range_array = cost_range_hash.map { |cost|
128
+ [
129
+ cost['rsi_k_m2_per_w'],
130
+ cost['total_cost_with_op']
131
+ ]
132
+ }
133
+ end
134
+ # Sorted based on rsi.
135
+ cost_range_array.sort! { |a, b| a[0] <=> b[0] }
136
+
137
+ # Iterate through actual surfaces in the model of surface_type.
138
+ numSurfType = 0
139
+ surfaces[surface_type].sort.each do |surface|
140
+ numSurfType = numSurfType + 1
141
+
142
+ # Get RSI of existing model surface (actually returns rsi for glazings too!).
143
+ # Make an array of constructions to use with surfaces_get_conductance method which replaces the get_rsi
144
+ # method
145
+ rsi = 1 / (OpenstudioStandards::Constructions.construction_get_conductance(OpenStudio::Model::getConstructionByName(surface.model, surface.construction.get.name.to_s).get))
146
+
147
+
148
+ #Check to see if it is in range
149
+
150
+
151
+ # Use the cost_range_array to interpolate the estimated cost for the given rsi.
152
+ # Note that window costs in the API data use U-value, which was converted to rsi for cost_range_array above
153
+ exterpolate_percentage_range = 30.0
154
+ cost = interpolate(x_y_array: cost_range_array, x2: rsi, exterpolate_percentage_range: exterpolate_percentage_range)
155
+
156
+
157
+ # If the cost is nil, that means the rsi is out of range. Flag in the report.
158
+ if cost.nil?
159
+ if !cost_range_array.empty?
160
+ notes = "Warning! RSI out of the range (#{'%.2f' % rsi}) or cost is 0!. Range for #{construction_set[surface_type]} is #{'%.2f' % cost_range_array.first[0]}-#{'%.2f' % cost_range_array.last[0]}."
161
+ cost = 0.0
162
+ else
163
+ notes = "No cost found for this! So Cost is set to 0.0!"
164
+ cost = 0.0
165
+ end
166
+ elsif cost.nan?
167
+ raise("the values for cost and conductance for #{construction_set[surface_type]} cannot be interpolated...cannot create an equation of a line from #{cost_range_array.sort.uniq}. Check construction database and either eliminate the errant row, or set the x value to an appropriate number. ")
168
+ else
169
+ #Tell user if we are extrapolating outside of library.
170
+ array = cost_range_array.sort { |a, b| a[0] <=> b[0] }
171
+ if rsi < (array.first[0].to_f) || rsi > (array.last[0].to_f)
172
+ notes = "RSI out of the range (#{'%.2f' % rsi}). Range for #{construction_set[surface_type]} is #{'%.2f' % cost_range_array.first[0]}-#{'%.2f' % cost_range_array.last[0]}.Using extrapolation up to +/-30% of library boundaries. "
173
+ else
174
+ notes = "OK"
175
+ end
176
+ end
177
+
178
+ # Calculate SHGC/film cost
179
+ film_cost = 0.0
180
+ if surfaceIsGlazing
181
+ #Get SHGC from surface.
182
+ shgc = OpenstudioStandards::Constructions.construction_get_solar_transmittance(surface.construction.get.to_Construction.get)
183
+ # Get the closest value in materials_glazing sheet of SolarFilms.
184
+ material_row = @costing_database["raw"]["materials_glazing"].select{ |row| row['material_type'] == 'Solarfilms' }.min_by {|row| (shgc.to_f - row['solar_heat_gain_coefficient'].to_f).abs}
185
+ standard_film_cost = getCost(material_row['description'], material_row, 1.0)
186
+ regional_factors = get_regional_cost_factors(@costing_report['province_state'], @costing_report['city'], material_row)
187
+ # mult regional cost and sum costs. Zip adds the arrays together, map multiplies each row and divides by 100.0 since the regional factor is in percents.
188
+ film_cost = standard_film_cost.zip(regional_factors).map{|cost,region_factor| cost * region_factor / 100.0}.inject(0, :+)
189
+ end
190
+
191
+
192
+ testSurfName = surface.name.to_s
193
+ testSpaceName = space.name.to_s
194
+ surfArea = surface.netArea * zone.multiplier
195
+ surfAreaft = (OpenStudio.convert(surfArea, "m^2", "ft^2").get).to_f
196
+ surfCost = (cost + film_cost) * surfAreaft
197
+ totEnvCost = totEnvCost + surfCost
198
+ name = ""
199
+
200
+ # Bin the costing by construction standard type and rsi
201
+ if construction_set.nil?
202
+ name = "undefined space type_#{(1.0 / rsi).round(3)}"
203
+ else
204
+ name = "#{construction_set[surface_type]}"
205
+ end
206
+ row = @costing_report["envelope"]["construction_costs"].detect { |row| (row['name'] == name) && (row['conductance'].round(3) == ((1.0 / rsi).round(3))) }
207
+ if row.nil?
208
+ @costing_report["envelope"]["construction_costs"] << {'name' => name, 'conductance' => ((1.0 / rsi).round(3)), 'area' => (surfArea.round(2)), 'cost' => (surfCost.round(2)), 'cost_per_area' => (surfCost / surfArea).round(2), 'note' => "Surf ##{numSurfType}: #{notes}"}
209
+ else
210
+ # Not using += for @costing_report additions so that output can be properly rounded
211
+ row['area'] = (row['area'] + surfArea).round(2)
212
+ row['cost'] = (row['cost'] + surfCost).round(2)
213
+ row['cost_per_area'] = ((row['cost'] / row['area']).to_f.round(2))
214
+ row['note'] += " / #{numSurfType}: #{notes}"
215
+ end
216
+ # Not using += for @costing_report additions so that output can be properly rounded
217
+ @costing_report["envelope"]["#{surface_type.underscore}_cost"] = (@costing_report["envelope"]["#{surface_type.underscore}_cost"] + surfCost).round(2)
218
+ @costing_report["envelope"]["#{surface_type.underscore}_area_m2"] = (@costing_report["envelope"]["#{surface_type.underscore}_area_m2"] + surfArea).round(2)
219
+ @costing_report["envelope"]["#{surface_type.underscore}_cost_per_m2"] = (@costing_report["envelope"]["#{surface_type.underscore}_cost"] / @costing_report["envelope"]["#{surface_type.underscore}_area_m2"]).round(2)
220
+ end # surfaces of surface type
221
+ end # surface_type
222
+ end # spaces
223
+ end # thermalzone
224
+
225
+ @costing_report["envelope"]['total_envelope_cost'] = totEnvCost.to_f.round(2)
226
+ puts "\nEnvelope costing data successfully generated. Total envelope cost is $#{totEnvCost.to_f.round(2)}"
227
+
228
+ return totEnvCost
229
+ end
230
+
231
+ def cost_construction(construction, location, type = 'opaque')
232
+
233
+ material_layers = "material_#{type}_id_layers"
234
+ material_id = "materials_#{type}_id"
235
+ materials_database = @costing_database["raw"]["materials_#{type}"]
236
+
237
+ total_with_op = 0.0
238
+ material_cost_pairs = []
239
+ construction[material_layers].split(',').reject { |c| c.empty? }.each do |material_index|
240
+ material = materials_database.find { |data| data[material_id].to_s == material_index.to_s }
241
+ if material.nil?
242
+ puts "material error..could not find material #{material_index} in #{materials_database}"
243
+ raise()
244
+ else
245
+ costing_data = @costing_database['costs'].detect { |data| data['id'].to_s.upcase == material['id'].to_s.upcase }
246
+ if costing_data.nil?
247
+ puts "This material id #{material['id']} was not found in the costing database. Skipping. This construction will be inaccurate. "
248
+ raise()
249
+ else
250
+ regional_material, regional_installation = get_regional_cost_factors(location['province_state'], location['city'], material)
251
+
252
+ # Get cost information from lookup.
253
+ # Note that "glazing" types don't have a 'quantity' hash entry!
254
+ # Don't need "and" below but using in-case this hash field is added in the future.
255
+ if type == 'glazing' and material['quantity'].to_f == 0.0
256
+ material['quantity'] = '1.0'
257
+ end
258
+ material_cost = costing_data['baseCosts']['materialOpCost'].to_f * material['material_mult'].to_f
259
+ labour_cost = costing_data['baseCosts']['laborOpCost'].to_f * material['labour_mult'].to_f
260
+ equipment_cost = costing_data['baseCosts']['equipmentOpCost'].to_f
261
+ layer_cost = (((material_cost * regional_material / 100.0) + (labour_cost * regional_installation / 100.0) + equipment_cost) * material['quantity'].to_f).round(2)
262
+ material_cost_pairs << {material_id.to_s => material_index,
263
+ 'cost' => layer_cost}
264
+ total_with_op += layer_cost
265
+ end
266
+ end
267
+ end
268
+ new_construction = {
269
+ 'province_state' => location['province_state'],
270
+ 'city' => location['city'],
271
+ "construction_type_name" => construction["construction_type_name"],
272
+ 'description' => construction["description"],
273
+ 'intended_surface_type' => construction["intended_surface_type"],
274
+ 'standards_construction_type' => construction["standards_construction_type"],
275
+ 'rsi_k_m2_per_w' => construction['rsi_k_m2_per_w'].to_f,
276
+ 'zone' => construction['climate_zone'],
277
+ 'fenestration_type' => construction['fenestration_type'],
278
+ 'u_w_per_m2_k' => construction['u_w_per_m2_k'],
279
+ 'materials' => material_cost_pairs,
280
+ 'total_cost_with_op' => total_with_op}
281
+
282
+ @costing_database['constructions_costs'] << new_construction
283
+ end
284
+ end