openstudio-standards 0.8.5 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. checksums.yaml +4 -4
  2. data/data/standards/test_performance_expected_dd_results.csv +90 -90
  3. data/lib/openstudio-standards/btap/activity.rb +858 -61
  4. data/lib/openstudio-standards/btap/attributes.rb +297 -103
  5. data/lib/openstudio-standards/btap/bridging.rb +1335 -593
  6. data/lib/openstudio-standards/btap/btap.rb +60 -181
  7. data/lib/openstudio-standards/btap/carbon/btap_carbon.rb +199 -142
  8. data/lib/openstudio-standards/btap/common/constructions.json +1275 -0
  9. data/lib/openstudio-standards/btap/common/costs.csv +1969 -0
  10. data/lib/openstudio-standards/btap/common/costs_local_factors.csv +2315 -0
  11. data/lib/openstudio-standards/btap/common/materials_glazing.csv +61 -0
  12. data/lib/openstudio-standards/btap/{common_resources → common}/materials_hvac.csv +1685 -1685
  13. data/lib/openstudio-standards/btap/{common_resources → common}/materials_lighting.csv +266 -266
  14. data/lib/openstudio-standards/btap/common/materials_opaque.csv +214 -0
  15. data/lib/openstudio-standards/btap/common/necb_building_types.csv +35 -0
  16. data/lib/openstudio-standards/btap/common/necb_space_types.csv +110 -0
  17. data/lib/openstudio-standards/btap/common/thermal_bridging.csv +73 -0
  18. data/lib/openstudio-standards/btap/costing/btap_costing.rb +346 -429
  19. data/lib/openstudio-standards/btap/costing/database.rb +104 -0
  20. data/lib/openstudio-standards/btap/costing/daylighting_sensor_control_costing.rb +356 -344
  21. data/lib/openstudio-standards/btap/costing/dcv_costing.rb +282 -296
  22. data/lib/openstudio-standards/btap/costing/envelope_costing.rb +179 -192
  23. data/lib/openstudio-standards/btap/costing/heating_cooling_costing.rb +2477 -2316
  24. data/lib/openstudio-standards/btap/costing/led_lighting_costing.rb +127 -142
  25. data/lib/openstudio-standards/btap/costing/lighting_costing.rb +216 -198
  26. data/lib/openstudio-standards/btap/costing/nv_costing.rb +522 -523
  27. data/lib/openstudio-standards/btap/costing/pv_ground_costing.rb +649 -649
  28. data/lib/openstudio-standards/btap/costing/shw_costing.rb +626 -625
  29. data/lib/openstudio-standards/btap/costing/thermal_bridging_costing.rb +72 -0
  30. data/lib/openstudio-standards/btap/costing/ventilation_costing.rb +2363 -2361
  31. data/lib/openstudio-standards/btap/deprecated.rb +1033 -0
  32. data/lib/openstudio-standards/btap/envelope.rb +313 -572
  33. data/lib/openstudio-standards/btap/fileio.rb +152 -1070
  34. data/lib/openstudio-standards/btap/geometry.rb +486 -766
  35. data/lib/openstudio-standards/btap/linear_regression.rb +130 -0
  36. data/lib/openstudio-standards/btap/paths.rb +46 -0
  37. data/lib/openstudio-standards/btap/qaqc/analysis.rb +163 -0
  38. data/lib/openstudio-standards/btap/qaqc/data.rb +2293 -0
  39. data/lib/openstudio-standards/btap/qaqc/datapoint.rb +641 -0
  40. data/lib/openstudio-standards/btap/schedules.rb +11 -611
  41. data/lib/openstudio-standards/btap/structure.rb +574 -189
  42. data/lib/openstudio-standards/constructions/materials/information.rb +1 -1
  43. data/lib/openstudio-standards/create_typical/create_typical.rb +1 -1
  44. data/lib/openstudio-standards/exterior_lighting/create.rb +135 -85
  45. data/lib/openstudio-standards/exterior_lighting/data/typical_exterior_lighting.json +20 -0
  46. data/lib/openstudio-standards/exterior_lighting/information.rb +23 -2
  47. data/lib/openstudio-standards/geometry/create.rb +7 -3
  48. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.rb +3 -2
  49. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Model.rb +3 -2
  50. data/lib/openstudio-standards/prototypes/common/data/thermostat_schedule_lookup.json +84 -84
  51. data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +4 -4
  52. data/lib/openstudio-standards/refrigeration/create_walkin.rb +45 -5
  53. data/lib/openstudio-standards/refrigeration/data/refrigerated_walkins.csv +75 -75
  54. data/lib/openstudio-standards/schedules/parametric.rb +6 -0
  55. data/lib/openstudio-standards/service_water_heating/create_typical.rb +10 -3
  56. data/lib/openstudio-standards/service_water_heating/create_water_heating_loop.rb +30 -14
  57. data/lib/openstudio-standards/service_water_heating/information.rb +4 -1
  58. data/lib/openstudio-standards/sql_file/unmet_hours.rb +2 -1
  59. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXSingleSpeed.rb +3 -1
  60. data/lib/openstudio-standards/standards/Standards.CoilHeatingDXSingleSpeed.rb +3 -1
  61. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.boilers.json +12 -0
  62. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.construction_properties.json +18432 -3072
  63. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.boilers.json +26 -0
  64. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.construction_properties.json +8532 -1422
  65. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.boilers.json +26 -0
  66. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.construction_properties.json +8532 -1422
  67. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.boilers.json +16 -0
  68. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.computer_room_acs.json +30 -30
  69. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.construction_properties.json +7560 -1260
  70. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.PumpVariableSpeed.rb +1 -2
  71. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.boilers.json +16 -0
  72. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.construction_properties.json +7560 -1260
  73. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirLoopHVAC.rb +3 -3
  74. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.PumpVariableSpeed.rb +1 -2
  75. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.boilers.json +22 -0
  76. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.construction_properties.json +7236 -1206
  77. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.furnaces.json +223 -19
  78. data/lib/openstudio-standards/standards/ashrae_90_1/data/ashrae_90_1.schedules.json +37 -1
  79. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.boilers.json +3 -0
  80. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.construction_properties.json +6900 -1150
  81. data/lib/openstudio-standards/standards/cbes/data/cbes.schedules.json +1 -1
  82. data/lib/openstudio-standards/standards/deer/data/deer.schedules.json +1 -1
  83. data/lib/openstudio-standards/standards/necb/BTAP1980TO2010/data/surface_thermal_transmittance.json +76 -40
  84. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/surface_thermal_transmittance.json +76 -40
  85. data/lib/openstudio-standards/standards/necb/ECMS/hvac_systems.rb +6 -4
  86. data/lib/openstudio-standards/standards/necb/NECB2011/NECB2011_QAQC.json +2988 -0
  87. data/lib/openstudio-standards/standards/necb/NECB2011/autozone.rb +25 -3
  88. data/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +672 -534
  89. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/Hospital.osm +1791 -1792
  90. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPMultiTower.osm +7 -1
  91. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPTownHouse.osm +7 -1
  92. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LargeHotel.osm +5 -6
  93. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LargeOffice.osm +8 -9
  94. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernEducation.osm +10 -4
  95. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernHealthCare.osm +14 -15
  96. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/Outpatient.osm +7 -1
  97. data/lib/openstudio-standards/standards/necb/NECB2011/data/surface_thermal_transmittance.json +76 -40
  98. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +14 -6
  99. data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +542 -130
  100. data/lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb +30 -63
  101. data/lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb +7 -2
  102. data/lib/openstudio-standards/standards/necb/NECB2015/data/surface_thermal_transmittance.json +90 -0
  103. data/lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb +8 -6
  104. data/lib/openstudio-standards/standards/necb/NECB2017/data/surface_thermal_transmittance.json +76 -40
  105. data/lib/openstudio-standards/standards/necb/NECB2017/necb_2017.rb +2 -1
  106. data/lib/openstudio-standards/standards/necb/NECB2020/building_envelope.rb +0 -117
  107. data/lib/openstudio-standards/standards/necb/NECB2020/data/surface_thermal_transmittance.json +76 -45
  108. data/lib/openstudio-standards/standards/necb/NECB2020/necb_2020.rb +218 -0
  109. data/lib/openstudio-standards/standards/necb/common/construction_defaults.osm +12 -7
  110. data/lib/openstudio-standards/standards/standard.rb +1 -1
  111. data/lib/openstudio-standards/version.rb +1 -1
  112. data/lib/openstudio-standards.rb +9 -4
  113. metadata +59 -66
  114. data/data/inventory/thermal_bridging.csv +0 -90
  115. data/data/standards/OpenStudio_Standards-ashrae_90_1-ALL-comstock(space_types).xlsx +0 -0
  116. data/data/standards/openstudio_standards_duplicates_log.csv +0 -7962
  117. data/lib/openstudio-standards/btap/NECB_building_types.csv +0 -35
  118. data/lib/openstudio-standards/btap/NECB_space_types.csv +0 -109
  119. data/lib/openstudio-standards/btap/WeatherData1.json +0 -2110
  120. data/lib/openstudio-standards/btap/WeatherData1.xlsx +0 -0
  121. data/lib/openstudio-standards/btap/btap_result.rb +0 -1144
  122. data/lib/openstudio-standards/btap/btap_test_helper.rb +0 -86
  123. data/lib/openstudio-standards/btap/common_paths.rb +0 -81
  124. data/lib/openstudio-standards/btap/common_resources/ConstructionProperties.csv +0 -52
  125. data/lib/openstudio-standards/btap/common_resources/Constructions.csv +0 -37
  126. data/lib/openstudio-standards/btap/common_resources/construction_sets.csv +0 -1270
  127. data/lib/openstudio-standards/btap/common_resources/constructions_glazing.csv +0 -121
  128. data/lib/openstudio-standards/btap/common_resources/constructions_opaque.csv +0 -2256
  129. data/lib/openstudio-standards/btap/common_resources/costs.csv +0 -1897
  130. data/lib/openstudio-standards/btap/common_resources/costs_local_factors.csv +0 -2315
  131. data/lib/openstudio-standards/btap/common_resources/materials_glazing.csv +0 -35
  132. data/lib/openstudio-standards/btap/common_resources/materials_opaque.csv +0 -164
  133. data/lib/openstudio-standards/btap/costing/btap_measure_helper.rb +0 -359
  134. data/lib/openstudio-standards/btap/costing/btap_workflow.rb +0 -117
  135. data/lib/openstudio-standards/btap/costing/copy_test_results_files_to_expected_results.rb +0 -11
  136. data/lib/openstudio-standards/btap/costing/cost_building_from_file.rb +0 -136
  137. data/lib/openstudio-standards/btap/costing/costing_database_wrapper.rb +0 -173
  138. data/lib/openstudio-standards/btap/costing/dummy.epw +0 -8768
  139. data/lib/openstudio-standards/btap/costing/dummy.osm +0 -5320
  140. data/lib/openstudio-standards/btap/costing/neb_end_use_prices.csv +0 -42
  141. data/lib/openstudio-standards/btap/costing/necb_2011_spacetype_info.csv +0 -225
  142. data/lib/openstudio-standards/btap/costing/necb_reference_runs.csv +0 -28705
  143. data/lib/openstudio-standards/btap/costing/parallel_tests.rb +0 -92
  144. data/lib/openstudio-standards/btap/costing/test_list.txt +0 -15
  145. data/lib/openstudio-standards/btap/costing/test_run_all_test_locally.rb +0 -27
  146. data/lib/openstudio-standards/btap/costing/test_run_costing_tests.rb +0 -80
  147. data/lib/openstudio-standards/btap/necb_2011_spacetype_info.csv +0 -225
  148. data/lib/openstudio-standards/btap/utilities/Add_template_field_to_json.rb +0 -22
  149. data/lib/openstudio-standards/btap/utilities/convert_surfaces_to_adiabatic_necb_8426.rb +0 -204
  150. data/lib/openstudio-standards/btap/utilities/generate_space_types.rb +0 -8
  151. data/lib/openstudio-standards/btap/utilities/necb_to_epw_map.rb +0 -50
  152. data/lib/openstudio-standards/btap/utilities/os_sim_extract.rb +0 -48
  153. data/lib/openstudio-standards/btap/utilities/rename_surfaces.rb +0 -24
  154. data/lib/openstudio-standards/btap/utilities/round_surf_coords.rb +0 -32
  155. data/lib/openstudio-standards/btap/utilities/sched_create.rb +0 -145
  156. data/lib/openstudio-standards/btap/utilities/set_mult_to_adiabatic.rb +0 -53
  157. data/lib/openstudio-standards/btap/utilities/space_type.rb +0 -78
  158. data/lib/openstudio-standards/btap/utilities/weatherData1_xlsx_to_json.rb +0 -56
  159. data/lib/openstudio-standards/btap/vintagizer.rb +0 -161
  160. data/lib/openstudio-standards/btap/visualization.rb +0 -72
  161. data/lib/openstudio-standards/standards/necb/NECB2011/data/necb_2011_spacetype_info.csv +0 -225
  162. data/lib/openstudio-standards/standards/necb/common/btap_analysis.rb +0 -79
  163. data/lib/openstudio-standards/standards/necb/common/btap_data.rb +0 -2656
  164. data/lib/openstudio-standards/standards/necb/common/btap_datapoint.rb +0 -653
  165. data/lib/openstudio-standards/standards/necb/common/necb_reference_runs.csv +0 -28705
  166. data/lib/openstudio-standards/standards/necb/common/utility_pricing_2025-02-20.csv +0 -92
  167. /data/lib/openstudio-standards/btap/{common_resources → common}/carbon_frame.csv +0 -0
  168. /data/lib/openstudio-standards/btap/{common_resources → common}/carbon_glazing.csv +0 -0
  169. /data/lib/openstudio-standards/btap/{common_resources → common}/carbon_opaque.csv +0 -0
  170. /data/lib/openstudio-standards/btap/{common_resources → common}/hvac_vent_ahu.csv +0 -0
  171. /data/lib/openstudio-standards/btap/{common_resources → common}/lighting.csv +0 -0
  172. /data/lib/openstudio-standards/btap/{common_resources → common}/lighting_sets.csv +0 -0
  173. /data/lib/openstudio-standards/btap/{common_resources → common}/locations.csv +0 -0
  174. /data/lib/openstudio-standards/btap/{costing → common}/mech_sizing.json +0 -0
@@ -1,469 +1,386 @@
1
- require 'json'
2
- require_relative 'costing_database_wrapper'
3
-
4
- class SimpleLinearRegression
5
- #https://gist.github.com/rweald/3516193#file-full-slr-class-snippet-rb
6
- def initialize(xs, ys)
7
- @xs, @ys = xs, ys
8
- if @xs.length != @ys.length
9
- raise "Unbalanced data. xs need to be same length as ys"
10
- end
11
- end
12
-
13
- def y_intercept
14
- return mean(@ys) - (slope * mean(@xs))
15
- end
16
-
17
- def slope
18
- x_mean = mean(@xs)
19
- y_mean = mean(@ys)
1
+ module BTAP
2
+ class Costing
3
+
4
+ # Class to initialize BTAP Costing.
5
+ # @param costs_csv [String] Path to custom costing.
6
+ # @param factors_csv [String] Path to custom localization factors.
7
+ # @param attributes [BTAP::Atributes] Methods and helpers for model
8
+ # attributes, mostly envelopes and
9
+ # constructions.
10
+ def initialize(costs_csv: nil, factors_csv: nil, attributes:)
11
+ @costing_database = Database.instance
12
+ @attributes = attributes
13
+
14
+ # If the path for custom costing is defined, use custom costing.
15
+ if (not costs_csv.nil?) and File.exist?(costs_csv)
16
+ Paths.costs_path = costs_csv
17
+ end
20
18
 
21
- numerator = (0...@xs.length).reduce(0) do |sum, i|
22
- sum + ((@xs[i] - x_mean) * (@ys[i] - y_mean))
19
+ # If the path for custom factors is defined, use custom factors.
20
+ if (not factors_csv.nil?) and File.exist?(factors_csv)
21
+ Paths.costs_local_factors_path = factors_csv
22
+ end
23
23
  end
24
24
 
25
- denominator = @xs.reduce(0) do |sum, x|
26
- sum + ((x - x_mean) ** 2)
25
+ def validate_database()
26
+ @costing_database.validate_ahu_items_and_quantities
27
27
  end
28
28
 
29
- return (numerator / denominator)
30
- end
31
-
32
- def mean(values)
33
- total = values.reduce(0) { |sum, x| x + sum }
34
- return Float(total) / Float(values.length)
35
- end
36
- end
37
-
38
-
39
- class BTAPCosting
40
- # May be initialized with custom databases:
41
- # costs_csv: Path to custom costing
42
- # factors_csv: Path to custom localization factors
43
- def initialize(costs_csv: nil, factors_csv: nil, attributes:)
44
- @cp = CommonPaths.instance
45
- @costing_database = CostingDatabase.instance
46
- @attributes = attributes
29
+ def cost_audit_all(model:,
30
+ prototype_creator:,
31
+ envelope_costing: true,
32
+ lighting_costing: true,
33
+ boilers_costing: true,
34
+ chillers_costing: true,
35
+ cooling_towers_costing: true,
36
+ shw_costing: true,
37
+ ventilation_costing: true,
38
+ zone_system_costing: true,
39
+ renewables_costing: true,
40
+ thermal_bridging_costing: @attributes.tbd_enabled,
41
+ template_type: nil)
42
+
43
+ @costing_report = {}
44
+
45
+ # Use the closest city.
46
+ closest_loc = get_closest_cost_location(model.getWeatherFile.latitude, model.getWeatherFile.longitude)
47
+ @costing_report['city'] = closest_loc['city']
48
+ @costing_report['province_state'] = closest_loc['province_state']
49
+
50
+ # Create an array to collect costed item information. First element is the
51
+ # costing location.
52
+ @cost_items = {
53
+ 'City' => closest_loc['city'],
54
+ 'Province' => closest_loc['province_state'],
55
+ 'Items' => []
56
+ }
57
+
58
+ # Create nested hashes for each category of costing.
59
+ @costing_report['envelope'] = {}
60
+ @costing_report['lighting'] = {}
61
+ @costing_report['lighting']['daylighting_sensor_control'] = []
62
+ @costing_report['lighting']['led_lighting'] = []
63
+ @costing_report['heating_and_cooling'] = {}
64
+ @costing_report['heating_and_cooling']['plant_equipment'] = []
65
+ @costing_report['heating_and_cooling']['zonal_systems'] = []
66
+ @costing_report['shw'] = {}
67
+ @costing_report['ventilation'] = {}
68
+ @costing_report['renewables'] = {}
69
+ @costing_report['renewables']['pv'] = []
70
+ @costing_report['totals'] = {}
71
+
72
+ # Check to see if standards building type and the number of stories has
73
+ # been defined. The former may be omitted in the future.
74
+ if model.getBuilding.standardsBuildingType.empty? or model.getBuilding.standardsNumberOfAboveGroundStories.empty?
75
+ raise("Building information is not complete, please ensure that the standardsBuildingType and standardsNumberOfAboveGroundStories are entered in the model. ")
76
+ end
47
77
 
48
- # If the path for custom costing is defined, use custom costing.
49
- if (not costs_csv.nil?) and File.exist?(costs_csv)
50
- @cp.costs_path = costs_csv
78
+ # Find the mechanical room.
79
+ mech_room, cond_spaces = prototype_creator.find_mech_room(model)
80
+
81
+ envCost = envelope_costing ? self.cost_audit_envelope(prototype_creator) : 0.0
82
+ thermalBridgingCost = thermal_bridging_costing ? self.cost_audit_thermal_bridging(prototype_creator) : 0.0
83
+ lgtCost = lighting_costing ? self.cost_audit_lighting(model, prototype_creator) : 0.0
84
+ boilerCost = boilers_costing ? self.boiler_costing(model, prototype_creator) : 0.0
85
+ chillerCost = chillers_costing ? self.chiller_costing(model, prototype_creator) : 0.0
86
+ coolingTowerCost = cooling_towers_costing ? self.coolingtower_costing(model, prototype_creator) : 0.0
87
+ shwCost = shw_costing ? self.shw_costing(model, prototype_creator) : 0.0
88
+ ventCost = ventilation_costing ? self.ventilation_costing(
89
+ model, prototype_creator,template_type, mech_room, cond_spaces) : 0.0
90
+ zonalSystemCost = zone_system_costing ? self.zonalsys_costing(
91
+ model, prototype_creator, mech_room, cond_spaces) : 0.0
92
+ pvGroundCost = renewables_costing ? self.cost_audit_pv_ground(model, prototype_creator) : 0.0
93
+
94
+ @costing_report["totals"] = {
95
+ 'envelope' => envCost.round(0),
96
+ 'thermal_bridging' => thermalBridgingCost.round(0),
97
+ 'lighting' => lgtCost.round(0),
98
+ 'heating_and_cooling' => (boilerCost + chillerCost + coolingTowerCost + zonalSystemCost).round(0),
99
+ 'shw' => shwCost.round(0),
100
+ 'ventilation' => ventCost.round(0),
101
+ 'renewables' => pvGroundCost.round(0),
102
+ 'grand_total' => (
103
+ envCost + thermalBridgingCost + lgtCost + boilerCost + chillerCost + coolingTowerCost + shwCost + ventCost +
104
+ zonalSystemCost + pvGroundCost).round(0)
105
+ }
106
+
107
+ return @costing_report, @cost_items
51
108
  end
52
109
 
53
- # If the path for custom factors is defined, use custom factors.
54
- if (not factors_csv.nil?) and File.exist?(factors_csv)
55
- @cp.costs_local_factors_path = factors_csv
110
+ def get_regional_cost_factors(province_state, city, material)
111
+ @costing_database['localization_factors'].select { |code|
112
+ code['province_state'] == province_state && code['city'] == city }.each do |code|
113
+ prefix_id = material['id'][0..1]
114
+ prefix_stored = code['code_prefix']
115
+ if prefix_id == prefix_stored
116
+ return code['material'], code['installation'], code['total']
117
+ end
118
+ end
119
+ error = [material, "Could not find regional adjustment factor for material used in #{city}, #{province_state}."]
120
+ @costing_database['db_errors'] << error unless @costing_database['db_errors'].include?(error)
121
+ return 100.0, 100.0, 100.0
56
122
  end
57
- end
58
123
 
59
- def validate_database()
60
- @costing_database.validate_database
61
- end
124
+ # Enter in [latitude, longitude] for each loc and this method will return the distance.
125
+ def distance(loc1, loc2)
126
+ rad_per_deg = Math::PI / 180 # PI / 180
127
+ rkm = 6371 # Earth radius in kilometers
128
+ rm = rkm * 1000 # Radius in meters
62
129
 
63
- def generate_construction_cost_database_for_all_cities()
64
- result = Array.new
65
- @costing_database['raw']['locations'].each do |location|
66
- province_state = location["province_state"]
67
- city = location['city']
68
- result.concat(generate_construction_cost_database_for_city(city, province_state))
69
- end
70
- return result
71
- end
130
+ dlat_rad = (loc2[0] - loc1[0]) * rad_per_deg # Delta, converted to rad
131
+ dlon_rad = (loc2[1] - loc1[1]) * rad_per_deg
72
132
 
73
- def generate_construction_cost_database_for_city(city, province_state)
74
- @costing_database['constructions_costs'] = Array.new
75
- puts "Costing for: #{province_state},#{city}"
76
- @costing_database["raw"]['constructions_opaque'].each do |construction|
77
- cost_construction(construction, {"province_state" => province_state, "city" => city}, 'opaque')
78
- end
79
- @costing_database["raw"]['constructions_glazing'].each do |construction|
80
- cost_construction(construction, {"province_state" => province_state, "city" => city}, 'glazing')
81
- end
82
- puts "#{@costing_database['constructions_costs'].size} Costed Constructions for #{province_state},#{city}."
83
- return @costing_database['constructions_costs']
84
- end
133
+ lat1_rad, lon1_rad = loc1.map { |i| i * rad_per_deg }
134
+ lat2_rad, lon2_rad = loc2.map { |i| i * rad_per_deg }
85
135
 
86
-
87
- def cost_audit_all(model:,
88
- prototype_creator:,
89
- envelope_costing: true,
90
- lighting_costing: true,
91
- boilers_costing: true,
92
- chillers_costing: true,
93
- cooling_towers_costing: true,
94
- shw_costing: true,
95
- ventilation_costing: true,
96
- zone_system_costing: true,
97
- renewables_costing: true,
98
- template_type: nil
99
- )
100
- # Create a Hash to collect costing data.
101
- @costing_report = {}
102
-
103
- #Use closest city.
104
- closest_loc = get_closest_cost_location(model.getWeatherFile.latitude, model.getWeatherFile.longitude)
105
- @costing_report['city'] = closest_loc['city']
106
- @costing_report['province_state'] = closest_loc['province_state']
107
-
108
- # Create array to collect costed item information. First element is the costing location.
109
- @cost_items = {
110
- 'City' => closest_loc['city'],
111
- 'Province' => closest_loc['province_state'],
112
- 'Items' => []
113
- }
114
-
115
- # Create a Hash in the hash for categories of costing.
116
- @costing_report['envelope'] = {}
117
- @costing_report['lighting'] = {}
118
- @costing_report['lighting']['daylighting_sensor_control'] = []
119
- @costing_report['lighting']['led_lighting'] = []
120
- @costing_report['heating_and_cooling'] = {}
121
- @costing_report['heating_and_cooling']['plant_equipment'] = []
122
- @costing_report['heating_and_cooling']['zonal_systems'] = []
123
- @costing_report['shw'] = {}
124
- @costing_report['ventilation'] = {}
125
- @costing_report['renewables'] = {}
126
- @costing_report['renewables']['pv'] = []
127
- @costing_report['totals'] = {}
128
-
129
- # Check to see if standards building type and the number of stories has been defined. The former may be omitted in the future.
130
- if model.getBuilding.standardsBuildingType.empty? or model.getBuilding.standardsNumberOfAboveGroundStories.empty?
131
- raise("Building information is not complete, please ensure that the standardsBuildingType and standardsNumberOfAboveGroundStories are entered in the model. ")
136
+ a = Math.sin(dlat_rad / 2) ** 2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin(dlon_rad / 2) ** 2
137
+ c = 2 * Math::atan2(Math::sqrt(a), Math::sqrt(1 - a))
138
+ rm * c # Delta in meters
132
139
  end
133
140
 
134
- # Find the mechanical room
135
- mech_room, cond_spaces = prototype_creator.find_mech_room(model)
136
-
137
- envCost = envelope_costing ? self.cost_audit_envelope(model, prototype_creator) : 0.0
138
- lgtCost = lighting_costing ? self.cost_audit_lighting(model, prototype_creator) : 0.0
139
- boilerCost = boilers_costing ? self.boiler_costing(model, prototype_creator) : 0.0
140
- chillerCost = chillers_costing ? self.chiller_costing(model, prototype_creator) : 0.0
141
- coolingTowerCost = cooling_towers_costing ? self.coolingtower_costing(model, prototype_creator) : 0.0
142
- shwCost = shw_costing ? self.shw_costing(model, prototype_creator) : 0.0
143
- ventCost = ventilation_costing ? self.ventilation_costing(model, prototype_creator,template_type, mech_room, cond_spaces) : 0.0
144
- zonalSystemCost = zone_system_costing ? self.zonalsys_costing(model, prototype_creator, mech_room, cond_spaces) : 0.0
145
- pvGroundCost = renewables_costing ? self.cost_audit_pv_ground(model, prototype_creator) : 0.0
146
- thermalBridgingCost = 0.0
147
-
148
- @costing_report["totals"] = {
149
- 'envelope' => envCost.round(0),
150
- 'thermal_bridging' => thermalBridgingCost.round(0),
151
- 'lighting' => lgtCost.round(0),
152
- 'heating_and_cooling' => (boilerCost + chillerCost + coolingTowerCost + zonalSystemCost).round(0),
153
- 'shw' => shwCost.round(0),
154
- 'ventilation' => ventCost.round(0),
155
- 'renewables' => pvGroundCost.round(0),
156
- 'grand_total' => (envCost + thermalBridgingCost + lgtCost + boilerCost + chillerCost + coolingTowerCost +
157
- shwCost + ventCost + zonalSystemCost + pvGroundCost).round(0)
158
- }
159
-
160
- return @costing_report, @cost_items
161
- end
162
-
163
- def get_regional_cost_factors(provinceState, city, material)
164
- @costing_database['localization_factors'].select { |code|
165
- code['province_state'] == provinceState && code['city'] == city }.each do |code|
166
- prefix_id = material['id'][0..1]
167
- prefix_stored = code['code_prefix']
168
- if prefix_id == prefix_stored
169
- return code['material'], code['installation'], code['total']
170
- end
171
-
172
- end
173
- error = [material, "Could not find regional adjustment factor for material used in #{city}, #{provinceState}."]
174
- @costing_database['db_errors'] << error unless @costing_database['db_errors'].include?(error)
175
- return 100.0, 100.0, 100.0
176
- end
177
-
178
- # Interpolate array of hashes that contain 2 values (key=rsi, data=cost)
179
- def interpolate(x_y_array:, x2:, exterpolate_percentage_range: 30.0)
180
- ratio_range = exterpolate_percentage_range / 100.0
181
- array = x_y_array.uniq.sort { |a, b| a[0] <=> b[0] }
182
- #if there is only one...return what you got.
183
- if array.size == 1
184
- return array.first[1].to_f
185
- end
186
- # Check if value x2 is within range of array for interpolation
187
- # Extrapolate when x2 is out-of-range by +/- 10% of end values.
188
- if array.empty? || x2 < ((1.0 - ratio_range) * array.first[0].to_f) || x2 > ((1.0 + ratio_range) * array.last[0].to_f)
189
- return nil
190
- elsif x2 < array.first[0].to_f
191
- # Extrapolate down using first and second cost value to this out-of-range input
192
- x_array = [array[0][0].to_f, array[1][0].to_f]
193
- y_array = [array[0][1].to_f, array[1][1].to_f]
194
- linear_model = SimpleLinearRegression.new(x_array, y_array)
195
- y2 = linear_model.y_intercept + linear_model.slope * x2
196
- return y2
197
- elsif x2 > array.last[0].to_f
198
- # Extrapolate up using second to last and last cost value to this out-of-range input
199
- x_array = [array[-2][0].to_f, array[-1][0].to_f]
200
- y_array = [array[-2][1].to_f, array[-1][1].to_f]
201
- linear_model = SimpleLinearRegression.new(x_array, y_array)
202
- y2 = linear_model.y_intercept + linear_model.slope * x2
203
- return y2
204
- else
205
- array.each_index do |counter|
206
-
207
- # skip last value.
208
- next if array[counter] == array.last
209
-
210
- x0 = array[counter][0]
211
- y0 = array[counter][1]
212
- x1 = array[counter + 1][0]
213
- y1 = array[counter + 1][1]
214
-
215
- # skip to next if x2 is not between x0 and x1
216
- next if x2 < x0 || x2 > x1
217
-
218
- # Do interpolation
219
- y2 = y0 # just in-case x0, x1 and x2 are identical!
220
- if (x1 - x0) > 0.0
221
- y2 = y0.to_f + ((y1 - y0).to_f * (x2 - x0).to_f / (x1 - x0).to_f)
141
+ def get_closest_cost_location(lat, long)
142
+ dist = 1000000000000000000000.0
143
+ closest_loc = nil
144
+ # province_state city latitude longitude source
145
+ @costing_database['raw']['locations'].each do |location|
146
+ if distance([lat, long], [location['latitude'].to_f, location['longitude'].to_f]) < dist
147
+ closest_loc = location
148
+ dist = distance([lat, long], [location['latitude'].to_f, location['longitude'].to_f])
222
149
  end
223
- return y2
224
150
  end
151
+ return closest_loc
225
152
  end
226
- end
227
153
 
228
- # Enter in [latitude, longitude] for each loc and this method will return the distance.
229
- def distance(loc1, loc2)
230
- rad_per_deg = Math::PI / 180 # PI / 180
231
- rkm = 6371 # Earth radius in kilometers
232
- rm = rkm * 1000 # Radius in meters
154
+ def read_mech_sizing()
155
+ return JSON.parse(File.read(Paths::MECH_SIZING_PATH))
156
+ end
233
157
 
234
- dlat_rad = (loc2[0] - loc1[0]) * rad_per_deg # Delta, converted to rad
235
- dlon_rad = (loc2[1] - loc1[1]) * rad_per_deg
158
+ # This adds costed items to the array of cousted items which end up in
159
+ # btap_items.json. Note that the array this method uses is created in the
160
+ # cost_audit_all method. The array is created with an initial element that
161
+ # contains the city and province whose localiazation factors are used for
162
+ # costing.
163
+ # @param id [String] The costing database id for the item being costed.
164
+ # @param quantity [Float] The total amount of the item being costed in
165
+ # whatever units the item is costed in. This should include all multiplier
166
+ # used to determine this cost (e.g. such as thermal_zone multipliers).
167
+ # As an example, if 32 ft. of wire were required for a piece of equipment
168
+ # used in a thermal zone with a multiplier of 10, the quantity would be
169
+ # 3.2 (32 ft. * 10 / 100 since wire is costed per 100 ft.).
170
+ #
171
+ # @param material_mult [Float] The multiplier used to estimate the cost
172
+ # of an item from the base cost. For example, high efficiency SHW tanks
173
+ # are estimated to cost 30% higher than regular SHW tanks so the cost of
174
+ # these tanks are calculated by the cost * 1.3. Thus, the material_mult
175
+ # for high efficiency tanks would be 1.3. This is defauted to 1.0 if it is
176
+ # not provided.
177
+ #
178
+ # @param labour_mult [Float] Similar to material_mult only applied to labour
179
+ # costs. The labour_mult can be different than the material_mult. This is
180
+ # defaulted to 1.0 if it is not provided. equipment_mult: (float) Similar
181
+ # to material_mult and labour_mult only for equipment. This will always be
182
+ # 1.0 until equipment costs are supported.
183
+ #
184
+ # @param tags [Array[String]] This is an array which links the costed
185
+ # item to a component of the model that is being costed. For example,
186
+ # a material_id related to a boiler pump might have tags like ["boiler",
187
+ # "pump"].
188
+ def add_costed_item(material_id:, quantity:, material_mult: 1.0, labour_mult: 1.0, equip_mult: 1.0, tags: [])
189
+
190
+ # Do some error handling for the tags argument.
191
+ tags_out = [tags] if tags.kind_of?(String)
192
+ tags_out = tags if tags.kind_of?(Array)
193
+
194
+ # Validate the type of the arguments.
195
+ if (tags_out.kind_of?(Array) == false)
196
+ raise("The tags for the item #{material_id} were not properly defined. Please search for where the item is being added to the @cost_items hash via the add_costed_item method and correct the entry.")
197
+ end
236
198
 
237
- lat1_rad, lon1_rad = loc1.map { |i| i * rad_per_deg }
238
- lat2_rad, lon2_rad = loc2.map { |i| i * rad_per_deg }
199
+ if (material_id.kind_of?(String) == false)
200
+ raise("The material_id for the item #{material_id} is not a string. Please search for where the item is being added to the @cost_items hash via the add_costed_item method and correct the entry.")
201
+ end
239
202
 
240
- a = Math.sin(dlat_rad / 2) ** 2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin(dlon_rad / 2) ** 2
241
- c = 2 * Math::atan2(Math::sqrt(a), Math::sqrt(1 - a))
242
- rm * c # Delta in meters
243
- end
203
+ if (quantity.kind_of?(Float) == false)
204
+ raise("The quantity for the item #{material_id} is not a float. Please search for where the item is being added to the @cost_items hash via the add_costed_item method and correct the entry.")
205
+ end
244
206
 
245
- def get_closest_cost_location(lat, long)
246
- dist = 1000000000000000000000.0
247
- closest_loc = nil
248
- # province_state city latitude longitude source
249
- @costing_database['raw']['locations'].each do |location|
250
- if distance([lat, long], [location['latitude'].to_f, location['longitude'].to_f]) < dist
251
- closest_loc = location
252
- dist = distance([lat, long], [location['latitude'].to_f, location['longitude'].to_f])
207
+ if (material_mult.kind_of?(Float) == false)
208
+ raise("The material_mult for the item #{material_id} is not a float. Please search for where the item is being added to the @cost_items hash via the add_costed_item method and correct the entry.")
253
209
  end
254
- end
255
- return closest_loc
256
- end
257
210
 
258
- # This will expand the two letter province abbreviation to a full uppercase province name
259
- def expandProvAbbrev(abbrev)
260
-
261
- # Note that the proper abbreviation for Quebec is QC not PQ. However, we've used PQ in openstudio-standards!
262
- Hash provAbbrev = {"AB" => "ALBERTA",
263
- "BC" => "BRITISH COLUMBIA",
264
- "MB" => "MANITOBA",
265
- "NB" => "NEW BRUNSWICK",
266
- "NL" => "NEWFOUNDLAND AND LABRADOR",
267
- "NT" => "NORTHWEST TERRITORIES",
268
- "NS" => "NOVA SCOTIA",
269
- "NU" => "NUNAVUT",
270
- "ON" => "ONTARIO",
271
- "PE" => "PRINCE EDWARD ISLAND",
272
- "PQ" => "QUEBEC",
273
- "SK" => "SASKATCHEWAN",
274
- "YT" => "YUKON"
275
- }
276
- return provAbbrev[abbrev]
277
- end
211
+ if (labour_mult.kind_of?(Float) == false)
212
+ raise("The labour_mult for the item #{material_id} is not a float. Please search for where the item is being added to the @cost_items hash via the add_costed_item method and correct the entry.")
213
+ end
278
214
 
279
- def read_mech_sizing()
280
- file = File.read(@cp.mech_sizing_data_file)
281
- return JSON.parse(file)
282
- end
215
+ if (equip_mult.kind_of?(Float) == false)
216
+ raise("The equip_mult for the item #{material_id} is not a float. Please search for where the item is being added to the @cost_items hash via the add_costed_item method and correct the entry.")
217
+ end
283
218
 
284
- # This adds costed items to the array of cousted items which end up in btap_itmes.json. Note that the array this
285
- # method uses is created in the cost_audit_all method. The array is created with an initial element that contains the
286
- # city and province whose localiazation factors are used for costing.
287
- # The inputs are:
288
- # id: (string) The costing database id for the item being costed.
289
- # quantity: (float) The total amount of the item being costed in whatever units the item is costed in. This should
290
- # include all multiplier used to determine this cost (e.g. such as thermal_zone multipliers). As
291
- # an example, if 32 ft. of wire were required for a piece of equipment used in a thermal zone with
292
- # a multiplier of 10, the quantity would be 3.2 (32 ft. * 10 / 100 since wire is costed per
293
- # 100 ft.).
294
- # material_mult: (float) The multiplier used to estimate the cost of an item from the base cost. For example, high
295
- # efficiency SHW tanks are estimated to cost 30% higher than regular SHW tanks so the cost of these
296
- # tanks are calculated by the cost * 1.3. Thus, the material_mult for high efficiency tanks
297
- # would be 1.3. This is defauted to 1.0 if it is not provided.
298
- # labour_mult: (float) Similar to material_mult only applied to labour costs. The labour_mult can be different than
299
- # the material_mult. This is defaulted to 1.0 if it is not provided.
300
- # equipment_mult: (float) Similar to material_mult and labour_mult only for equipment. This will always be 1.0 until
301
- # equipment costs are supported.
302
- # tags: (array of strings) This is an array which links the costed item to a component of the model that is being
303
- # costed. For example, a material_id related to a boiler pump might have tags like ["boiler", "pump"].
304
- #
305
- def add_costed_item(material_id:, quantity:, material_mult: 1.0, labour_mult: 1.0, equip_mult: 1.0, tags: [])
306
- # Do some error handling for the tags argument
307
- tags_out = [tags] if tags.kind_of?(String)
308
- tags_out = tags if tags.kind_of?(Array)
309
-
310
- # Validate the type of the arguments.
311
- if (tags_out.kind_of?(Array) == false)
312
- raise("The tags for the item #{material_id} were not properly defined. Please search for where the item is being added to the @cost_items hash via the add_costed_item method and correct the entry.")
219
+ # Add the costed item to the output output hash.
220
+ @cost_items['Items'] << {
221
+ 'id' => material_id,
222
+ 'quantity' => quantity,
223
+ 'material_mult' => material_mult,
224
+ 'labour_mult' => labour_mult,
225
+ 'equipment_mult' => equip_mult,
226
+ 'tags' => tags_out
227
+ }
313
228
  end
314
229
 
315
- if (material_id.kind_of?(String) == false)
316
- raise("The material_id for the item #{material_id} is not a string. Please search for where the item is being added to the @cost_items hash via the add_costed_item method and correct the entry.")
317
- end
230
+ # This method takes the list of costed items in the building generated with
231
+ # the help of the above add_costed_item method and finds the costs for the
232
+ # list of items.
233
+ # @param btap_items [Array[Hash]] This array contains all the items that
234
+ # must be costed. The first element of the array is:
235
+ # {
236
+ # City: [String] City used for cost lacalization factor
237
+ # Province: [String] Province used for cost localization factor
238
+ # }
239
+ # The remaining arrays look like:
240
+ # {
241
+ # id: [String] ID of the coested item in question.
242
+ # quantity: [Float] Amount of costed item (should include all
243
+ # multipliers except localization factors, material_mult,
244
+ # labour_mult, equipment_mult).
245
+ #
246
+ # material_mult: [Float] Material multiplier from cost spreadsheet used
247
+ # mainly for higher performance equipment (for example, regular and
248
+ # high performance boilers share the same id but high performance
249
+ # boilers have a material_mult of around 1.3-that is they are
250
+ # estimated to be 1.3 times as expensive as regular boilers).
251
+ #
252
+ # labour_mult: [Float] Same idea as material_mult only for labour
253
+ # (often this will be 1.0 even if material_mult is something else).
254
+ #
255
+ # equipment_mult: [Float] Same idea as labour_mult only for equipment.
256
+ # It will always be 1.0 until equipment costs are implemented in
257
+ # costing.
258
+ #
259
+ # tags: [Array[String]] An array of strings used to define what
260
+ # part of the building is being costed (e.g. an component for
261
+ # a ccashp might have these tags: "Ventilation", "CCASHP",
262
+ # "ccashp_condensor")
263
+ # }
264
+ #
265
+ # @param custom_costing [Array[Hash]] A custom costing database if you do
266
+ # not want to use the default one. This must have the same format as that
267
+ # found by @costing_database['costs']
268
+ #
269
+ # @param custCity [String] A custom cost localization city if you do not
270
+ # want to use the one in the first item in the btap_itmes hash.
271
+ #
272
+ # @param custProvince [String] A custom cost localization province if you do
273
+ # not want to use the one in the first item in the btap_items hash.
274
+ #
275
+ # @return [Hash] A hash containing these summary costs:
276
+ # {
277
+ # envelope: [Float] Building envelope costs (to 2 decimal places).
278
+ # lighting: [Float] Ligting costs (to 2 decimal places).
279
+ # heating_and_cooling: [Float] Heating and cooling costs (not related to
280
+ # ventilation) (to 2 decimal places).
281
+ #
282
+ # shw: [Float] Service hot water costs (to 2 decimal places).
283
+ # ventilation: [Float] Ventilation (including ventilation air heating
284
+ # and cooling) costs (to 2 decimal places).
285
+ #
286
+ # grand_total: [Float] Total costs (to 2 decimal places).
287
+ # }
288
+ def cost_list_items(btap_items:, custom_costing: nil, custCity: nil, custProvince: nil)
289
+
290
+ # Check if costing is for a custom city and province. If not use the city
291
+ # and province found in the first entry of the array of costed items.
292
+ if custCity.nil? || custProvince.nil?
293
+ costCity = btap_items['City'].to_s
294
+ costProvince = btap_items['Province'].to_s
295
+ else
296
+ costCity = custCity
297
+ costProvince = custProvince
298
+ end
318
299
 
319
- if (quantity.kind_of?(Float) == false)
320
- raise("The quantity for the item #{material_id} is not a float. Please search for where the item is being added to the @cost_items hash via the add_costed_item method and correct the entry.")
321
- end
300
+ totCost = 0.0
301
+ envCost = 0.0
302
+ lightCost = 0.0
303
+ heatCoolCost = 0.0
304
+ shwCost = 0.0
305
+ ventCost = 0.0
306
+ renewCost = 0.0
322
307
 
323
- if (material_mult.kind_of?(Float) == false)
324
- raise("The material_mult for the item #{material_id} is not a float. Please search for where the item is being added to the @cost_items hash via the add_costed_item method and correct the entry.")
325
- end
308
+ custom_costing.nil? ? costingDB = @costing_database['costs'] : costingDB = custom_costing
326
309
 
327
- if (labour_mult.kind_of?(Float) == false)
328
- raise("The labour_mult for the item #{material_id} is not a float. Please search for where the item is being added to the @cost_items hash via the add_costed_item method and correct the entry.")
329
- end
310
+ btap_items['Items'].each do |costing_item|
330
311
 
331
- if (equip_mult.kind_of?(Float) == false)
332
- raise("The equip_mult for the item #{material_id} is not a float. Please search for where the item is being added to the @cost_items hash via the add_costed_item method and correct the entry.")
333
- end
312
+ # Look for the costing information for the piece of equipment in the costing database.
313
+ costing_data = costingDB.detect {|data| data['id'].to_s.upcase == costing_item['id'].to_s.upcase}
334
314
 
335
- # Add the costed item to the output output hash.
336
- @cost_items['Items'] << {
337
- 'id' => material_id,
338
- 'quantity' => quantity,
339
- 'material_mult' => material_mult,
340
- 'labour_mult' => labour_mult,
341
- 'equipment_mult' => equip_mult,
342
- 'tags' => tags_out
343
- }
344
- end
315
+ # If no costing information is found then return an error.
316
+ if costing_data.nil?
317
+ raise "Error: no costing information available for material id #{costing_item['id']}!"
318
+ elsif costing_data['baseCosts']['materialOpCost'].nil? && costing_data['baseCosts']['laborOpCost'].nil?
345
319
 
346
- # This method takes the list of costed items in the building generated with the help of the above add_costed_item
347
- # method and finds the costs for the list of items. It takes in:
348
- # btap_items: (array of hashes) This array contains all the items that must be costed. The first element of the
349
- # array is:
350
- # {
351
- # City: (string) City used for cost lacalization factor
352
- # Province: (string) Province used for cost localization factor
353
- # }
354
- # The remaining arrays look like:
355
- # {
356
- # id: (string) ID of the coested item in question.
357
- # quantity: (float) Amount of costed item (should include all multipliers except localization factors,
358
- # material_mult, labour_mult, equipment_mult).
359
- # material_mult: (float) Material multiplier from cost spreadsheet used mainly for higher performance equipment
360
- # (for example, regular and high performance boilers share the same id but high performance
361
- # boilers have a material_mult of around 1.3-that is they are estimated to be 1.3 times as
362
- # expensive as regular boilers).
363
- # labour_mult: (float) Same idea as material_mult only for labour (often this will be 1.0 even if material_mult
364
- # is something else).
365
- # equipment_mult: (float) Same idea as labour_mult only for equipment. It will always be 1.0 until equipment
366
- # costs are implemented in costing.
367
- # tags: (array of strings) An array of strings used to define what part of the building is being costed (e.g.
368
- # an component for a ccashp might have these tags: "Ventilation", "CCASHP", "ccashp_condensor")
369
- # }
370
- # custom_costing: (array of hashes) A custom costing database if you do not want to use the default one. This must
371
- # have the same format as that found by @costing_database['costs']
372
- # custCity: (string) A custom cost localization city if you do not want to use the one in the first item in the
373
- # btap_itmes hash.
374
- # custProvince: (string) A custom cost localization province if you do not want to use the one in the first item in
375
- # the btap_items hash.
376
- #
377
- # The output of the method is a hash containing these summary costs:
378
- # costRetHash = {
379
- # envelope: (float) Building envelope costs (to 2 decimal places).
380
- # lighting: (float) Ligting costs (to 2 decimal places).
381
- # heating_and_cooling: (float) Heating and cooling costs (not related to ventilation) (to 2 decimal places).
382
- # shw: (float) Service hot water costs (to 2 decimal places).
383
- # ventilation: (float) Ventilation (including ventilation air heating and cooling) costs (to 2 decimal places).
384
- # grand_total: (float) Total costs (to 2 decimal places).
385
- # }
386
- #
387
- def cost_list_items(btap_items:, custom_costing: nil, custCity: nil, custProvince: nil)
388
- # Check if costing is for a custom city and province. If not use the city and province found in the first entry
389
- # of the array of costed items.
390
- if custCity.nil? || custProvince.nil?
391
- costCity = btap_items['City'].to_s
392
- costProvince = btap_items['Province'].to_s
393
- else
394
- costCity = custCity
395
- costProvince = custProvince
396
- end
320
+ # This is a stub for some work that needs to be done to account for
321
+ # equipment costing. For now this is zeroed out. A similar test is
322
+ # done on reading the data from the database and collected in the
323
+ # error file when the costing database is generated.
324
+ raise "Error: costing information for material id #{costing_item['id']} is nil. Please check costing data."
325
+ end
397
326
 
398
- # Initialize cost counters
399
- totCost = 0.0
400
- envCost = 0.0
401
- lightCost = 0.0
402
- heatCoolCost = 0.0
403
- shwCost = 0.0
404
- ventCost = 0.0
405
- renewCost = 0.0
406
-
407
- custom_costing.nil? ? costingDB = @costing_database['costs'] : costingDB = custom_costing
408
-
409
- btap_items['Items'].each do |costing_item|
410
- # Look for the costing information for the piece of equipment in the costing database.
411
- costing_data = costingDB.detect {|data| data['id'].to_s.upcase == costing_item['id'].to_s.upcase}
412
- # If no costing information is found then return an error.
413
- if costing_data.nil?
414
- raise "Error: no costing information available for material id #{costing_item['id']}!"
415
- elsif costing_data['baseCosts']['materialOpCost'].nil? && costing_data['baseCosts']['laborOpCost'].nil?
416
- #This is a stub for some work that needs to be done to account for equipment costing. For now this is zeroed out.
417
- # A similar test is done on reading the data from the database and collected in the error file when the
418
- # costing database is generated.
419
- raise "Error: costing information for material id #{costing_item['id']} is nil. Please check costing data."
420
- end
421
- costing_data['baseCosts']['equipmentOpCost'].nil? ? equip_base_cost = 0.0 : equip_base_cost = costing_data['baseCosts']['equipmentOpCost'].to_f
422
- costing_data['baseCosts']['materialOpCost'].nil? ? mat_base_cost = 0.0 : mat_base_cost = costing_data['baseCosts']['materialOpCost'].to_f
423
- costing_data['baseCosts']['laborOpCost'].nil? ? lab_base_cost = 0.0 : lab_base_cost = costing_data['baseCosts']['laborOpCost'].to_f
424
-
425
- # The costs from the costing database are US national average costs (for placeholder costs) or whatever is in the
426
- # 'province_state' and 'city' fields (for custom costs). These costs need to be adjusted to reflect the costs
427
- # expected in the location of interest. The 'get_regional_cost_factors' method finds the appropriate cost
428
- # adjustment factors.
429
-
430
- mat_mult, inst_mult, eq_mult = get_regional_cost_factors(costProvince, costCity, costing_item)
431
- if mat_mult.nil? || inst_mult.nil?
432
- raise("Error: no localization information available for material id #{costing_item['material_id']}!")
327
+ costing_data['baseCosts']['equipmentOpCost'].nil? ? equip_base_cost = 0.0 : equip_base_cost =
328
+ costing_data['baseCosts']['equipmentOpCost'].to_f
329
+ costing_data['baseCosts']['materialOpCost'].nil? ? mat_base_cost = 0.0 : mat_base_cost =
330
+ costing_data['baseCosts']['materialOpCost'].to_f
331
+ costing_data['baseCosts']['laborOpCost'].nil? ? lab_base_cost = 0.0 : lab_base_cost =
332
+ costing_data['baseCosts']['laborOpCost'].to_f
333
+
334
+ # The costs from the costing database are US national average costs
335
+ # (for placeholder costs) or whatever is in the 'province_state' and
336
+ # 'city' fields (for custom costs). These costs need to be adjusted
337
+ # to reflect the costs expected in the location of interest. The
338
+ # 'get_regional_cost_factors' method finds the appropriate cost
339
+ # adjustment factors.
340
+ mat_mult, inst_mult, eq_mult = get_regional_cost_factors(costProvince, costCity, costing_item)
341
+
342
+ if mat_mult.nil? || inst_mult.nil?
343
+ raise("Error: no localization information available for material id #{costing_item['material_id']}!")
344
+ end
345
+
346
+ # Get any associated material or labour multiplier for the equipment
347
+ # present in the 'materials_hvac' sheet in the costing spreadsheet.
348
+ costing_item['material_mult'].to_f == 0 ? mat_quant = 1.0 : mat_quant = costing_item['material_mult'].to_f
349
+ costing_item['labour_mult'].to_f == 0 ? lab_quant = 1.0 : lab_quant = costing_item['labour_mult'].to_f
350
+ costing_item['equipment_mult'].to_f == 0 || costing_item['equipment_mult'].nil? ? eq_quant = 1.0 :
351
+ eq_quant = costing_item['equipment_mult'].to_f
352
+
353
+ # Calculate the adjusted material and labour costs.
354
+ mat_cost = mat_base_cost * (mat_mult / 100.0) * mat_quant
355
+ lab_cost = lab_base_cost * (inst_mult / 100.0) * lab_quant
356
+ eq_cost = equip_base_cost * (eq_mult / 100.0) * eq_quant
357
+
358
+ # Calculate the total item cost.
359
+ item_cost = (mat_cost + lab_cost + eq_cost)*(costing_item["quantity"].to_f)
360
+
361
+ # Add cost to sub-type counters.
362
+ envCost += item_cost unless (costing_item['tags'].select { |data| data.to_s.upcase == "ENVELOPE" }).empty?
363
+ lightCost += item_cost unless (costing_item['tags'].select { |data| data.to_s.upcase == "LIGHTING" }).empty?
364
+ heatCoolCost += item_cost unless (costing_item['tags'].select { |data|
365
+ data.to_s.upcase == "HEATING_COOLING" }).empty?
366
+ shwCost += item_cost unless (costing_item['tags'].select { |data| data.to_s.upcase == "SHW" }).empty?
367
+ ventCost += item_cost unless (costing_item['tags'].select { |data|
368
+ data.to_s.upcase == "VENTILATION" }).empty?
369
+ renewCost += item_cost unless (costing_item['tags'].select { |data| data.to_s.upcase == "RENEWABLES" }).empty?
370
+ totCost += item_cost
433
371
  end
434
- # Get any associated material or labour multiplier for the equipment present in the 'materials_hvac' sheet in the
435
- # costing spreadsheet.
436
- costing_item['material_mult'].to_f == 0 ? mat_quant = 1.0 : mat_quant = costing_item['material_mult'].to_f
437
- costing_item['labour_mult'].to_f == 0 ? lab_quant = 1.0 : lab_quant = costing_item['labour_mult'].to_f
438
- costing_item['equipment_mult'].to_f == 0 || costing_item['equipment_mult'].nil? ? eq_quant = 1.0 : eq_quant = costing_item['equipment_mult'].to_f
439
- # Calculate the adjusted material and labour costs.
440
- mat_cost = mat_base_cost*(mat_mult/100.0)*mat_quant
441
- lab_cost = lab_base_cost*(inst_mult/100.0)*lab_quant
442
- eq_cost = equip_base_cost*(eq_mult/100.0)*eq_quant
443
- # Calculate the total item cost.
444
- item_cost = (mat_cost + lab_cost + eq_cost)*(costing_item["quantity"].to_f)
445
-
446
- # Add cost to sub-type counters
447
- envCost += item_cost unless (costing_item['tags'].select{|data| data.to_s.upcase == "ENVELOPE"}).empty?
448
- lightCost += item_cost unless (costing_item['tags'].select{|data| data.to_s.upcase == "LIGHTING"}).empty?
449
- heatCoolCost += item_cost unless (costing_item['tags'].select{|data| data.to_s.upcase == "HEATING_COOLING"}).empty?
450
- shwCost += item_cost unless (costing_item['tags'].select{|data| data.to_s.upcase == "SHW"}).empty?
451
- ventCost += item_cost unless (costing_item['tags'].select{|data| data.to_s.upcase == "VENTILATION"}).empty?
452
- renewCost += item_cost unless (costing_item['tags'].select{|data| data.to_s.upcase == "RENEWABLES"}).empty?
453
- totCost += item_cost
454
- end
455
372
 
456
- # Create and return hash containing costing results
457
- costRetHash = {
458
- 'envelope' => envCost.round(2),
459
- 'lighting' => lightCost.round(2),
460
- 'heating_and_cooling' => heatCoolCost.round(2),
461
- 'shw' => shwCost.round(2),
462
- 'ventilation' => ventCost.round(2),
463
- 'renewables' => renewCost.round(2),
464
- 'grand_total' => totCost.round(2)
465
- }
466
- return costRetHash
373
+ # Create and return hash containing costing results.
374
+ costRetHash = {
375
+ 'envelope' => envCost.round(2),
376
+ 'lighting' => lightCost.round(2),
377
+ 'heating_and_cooling' => heatCoolCost.round(2),
378
+ 'shw' => shwCost.round(2),
379
+ 'ventilation' => ventCost.round(2),
380
+ 'renewables' => renewCost.round(2),
381
+ 'grand_total' => totCost.round(2)
382
+ }
383
+ return costRetHash
384
+ end
467
385
  end
468
-
469
386
  end