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,5 +1,5 @@
1
1
  # **************************************************************************** /
2
- # * Copyright (c) 2008-2025, Natural Resources Canada
2
+ # * Copyright (c) 2008-2026, Natural Resources Canada
3
3
  # * All rights reserved.
4
4
  # *
5
5
  # * This library is free software; you can redistribute it and/or
@@ -18,6 +18,7 @@
18
18
  # **************************************************************************** /
19
19
 
20
20
  require 'tbd'
21
+ require 'json'
21
22
 
22
23
  module BTAP
23
24
  # ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- #
@@ -29,29 +30,19 @@ module BTAP
29
30
  # @author: Denis Bourgeois
30
31
 
31
32
  # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- #
32
- # BTAP/TBD data initially extracted from the BTAP costing spreadsheet:
33
+ # BTAP/TBD parameters are based on BTAP costing data:
34
+ # - paired low-performance (LP) vs high-performance (HP) wall variants
35
+ # - range of clear-field Uo factors for each variant
36
+ # - paired "bad" vs "good" PSI factor sets for each variant
33
37
  #
34
- # - range of clear-field Uo factors
35
- # - range of PSI factors (i.e. MAJOR thermal bridging), e.g. corners
36
- #
37
- # Ref: EVOKE BTAP costing spreadsheet modifications (2022), synced with:
38
+ # Ref: BTAP costing data modifications (2022), synced with:
38
39
  # - Building Envelope Thermal Bridging Guide (BETBG)
39
40
  # - ASHRAE RP-1365, ISO-12011, etc.
40
41
  #
41
- # This module has been subsequently adapted following the adoption of new
42
- # BTAP structure/envelope data model/classes.
42
+ # STRUCTURE parameters set paired constructions - see btap/structure.rb.
43
43
 
44
44
  # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- #
45
- # BTAP costing data (both original BTAP envelope entries and EVOKE add-ons)
46
- # hold sub-variants based on cladding/veneer, e.g.:
47
- #
48
- # - "BTAP-ExteriorWall-WoodFramed-5" ... brick veneer
49
- # - "BTAP-ExteriorWall-WoodFramed-1" ... wood siding
50
- #
51
- # Not all of these sub-variants are currently used within BTAP, e.g.
52
- # "BTAP-ExteriorWall-WoodFramed-1" is unused. BTAP/TBD data is limited
53
- # to the following wall assemblies (paired LP & HP variants), which
54
- # eventually should be located in a shared file (e.g. CSV, JSON).
45
+ # BTAP/TBD data groups above-grade wall assemblies as paired LP/HP variants.
55
46
  #
56
47
  # ----- Low Performance (LP) assemblies
57
48
  # ID : layers
@@ -70,7 +61,7 @@ module BTAP
70
61
  # MASS8 : precast | xps | wool | frame | gypsum ... switch from MASS4
71
62
  #
72
63
  # Paired LPs & HPs vall variants are critical for 'uprating' cases, e.g.
73
- # NECB2017/2020. See below, and end of this document for additional NOTES.
64
+ # NECB2017/2020/2025. See below, and additional Notes at the very end.
74
65
 
75
66
  MASS2 = "BTAP-ExteriorWall-Mass-2" # LP wall
76
67
  MASS2_BAD = "BTAP-ExteriorWall-Mass-2 bad" # LP "bad" PSI factors
@@ -100,114 +91,101 @@ module BTAP
100
91
  STEL2_BAD = "BTAP-ExteriorWall-SteelFramed-2 bad"
101
92
  STEL2_GOOD = "BTAP-ExteriorWall-SteelFramed-2 good"
102
93
 
103
- ROOF = "BTAP-ExteriorRoof-IEAD-4"
104
- FLOOR = "BTAP-ExteriorFloor-SteelFramed-1"
94
+ # Insulated roofs, exposed floors (and attic floors) are also up/de-rated.
95
+ ROOF = "BTAP-ExteriorRoof-IEAD-4" # no LP/HP variants
96
+ FLOOR = "BTAP-ExteriorFloor-SteelFramed-1" # no LP/HP variants
105
97
 
106
98
  # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- #
107
- # There are 2 distinct BTAP "building_envelope.rb" files to enrich with
108
- # TBD functionality (whether BTAP users choose to activate TBD or not):
99
+ # BTAP/TBD is de/activated by setting the NECB 'tbd_option' as follows:
100
+ # - "none" : TBD is deactivated, i.e. no uprating
101
+ # - "bad" or "good": TBD derates envelope
102
+ # - "uprate" : TBD uprates initial Uo ... prior to derating
109
103
  #
110
- # 1. BTAPPRE1980
111
- # - superclass for BTAP1980TO2010
112
- # 2. NECB2011
113
- # - superclass for NECB2015
114
- # - superclass for NECB2017 (inherits from NECB2015)
115
- # - superclass for NECB2020 (inherits from NECB2017)
116
- # - superclass for ECMS
104
+ # - (see necb_2011.rb/apply_thermal_bridging)
117
105
  #
118
- # In both files, a BTAP/TBD option switch allows BTAP users to activate
119
- # or deactivate TBD functionality :
120
- # - "none" : TBD is deactivated, i.e. no up/de-rating
121
- # - "bad" or "good": (BTAP-costed) PSI factor sets, i.e. derating only
122
- # - "uprate": iteratively determine initial Uo ... prior to derating
123
- #
124
- # For vintages < NECB2017, the default BTAP policy is to switch off TBD,
125
- # i.e. 'none' (see the NOTE on this topic at the end of this document). To
126
- # instead assess prescriptive Ut compliance for vintages NECB2017 and
127
- # NECB2020, the BTAP/TBD must be set to "uprate" so it can iteratively reset
128
- # combined Uo & PSI factors towards finding the least expensive, yet
129
- # compliant, combination. Why? Improved Uo assembly variants are necessarily
130
- # required, given:
106
+ # For NECB editions prior to NECB2017, the current recommended BTAP policy
107
+ # is to switch off TBD (i.e. 'none') - see Note on this topic at the end of
108
+ # this document. To instead assess prescriptive Ut compliance for NECB
109
+ # editions 2017 through 2025, BTAP/TBD must be set to "uprate" so it can
110
+ # iteratively reset combined Uo & PSI factors towards finding the most
111
+ # affordable (yet compliant) Ut combination. Why? Improved Uo assembly
112
+ # variants are necessarily required, given:
131
113
  #
132
114
  # Ut = Uo + ( ∑psi x L )/A + ( ∑khi x n )/A (ref: rd2.github.io/tbd)
133
115
  #
134
116
  # If one ignores linear ("( ∑psi x L )/A") and point ("( ∑khi x n )/A")
135
- # conductances, Ut simply equates to Uo. Yet for ANY added linear or
136
- # point conductance, Uo factors must necessarily be lower than required
137
- # NECB2017 or NECB2020 Ut factors. EVOKE's 2022 contribution extends
138
- # initial (pre-2022) BTAP wall assembly variants, offering much
139
- # lower Uo factors (in some cases slightly below 0.1 W/m2.K or ~R70).
140
- # These BTAP upgrades provide more options for attaining required Ut
141
- # factors. For some variants, this simply implies a thicker insulation
142
- # layer. For others, it involves more radical assembly changes, such
143
- # as switching over to the latest commercially-available HP
144
- # thermally-broken cladding clips. While some solutions are simple
145
- # (free) detailing changes, most improvements increase construction
146
- # costs. Despite adding new HP assemblies, it is unlikely that TBD
147
- # will find NECB2017 or NECB2020 compliant combinations (prescriptive
148
- # path) for EVERY OpenStudio model. Read here as to "why?":
117
+ # conductances, Ut simply equates to Uo. Yet for ANY added linear or point
118
+ # conductance, Uo factors must necessarily be lower than required NECB2017+
119
+ # Ut factor requirements. BTAP HP wall assembly variants indeed offer much
120
+ # lower Uo factors (than required Ut), down to 0.1 W/m2.K or ~R70. These
121
+ # BTAP upgrades certainly provide more options for attaining required Ut
122
+ # factors. In practice, this simply implies a thicker insulation layer. For
123
+ # others, it involves more radical assembly changes, such as switching over
124
+ # to the latest commercially-available HP thermally-broken cladding clips.
125
+ # While some solutions are simple (free) detailing changes, most
126
+ # improvements increase construction costs. Despite adding new BTAP HP
127
+ # assemblies, it is unlikely that TBD will find NECB2017+ compliant
128
+ # combinations (prescriptive path) for EVERY OpenStudio model. Why?
149
129
  #
150
- # github.com/rd2/tbd/blob/f34ec6a017fcc0f6022f2a46e056b46b9d036b3b/
151
- # spec/tbd_tests_spec.rb#L9219
130
+ # github.com/rd2/tbd/blob/dd6f12f8f2c24950485918c7eaca57d8f091a64d/spec/
131
+ # tbd_tests_spec.rb#L1674
152
132
  #
153
133
  # For these reasons, BTAP's use of TBD rests on an ITERATIVE uprating
154
- # solution for NECB2017 and NECB2020:
134
+ # solution for NECB2017+:
155
135
  #
156
136
  # 1. TBD attempts to achieve NECB-required area-weighted Ut factors
157
137
  # for above-grade walls (then for roofs and exposed floors),
158
138
  # starting with the least expensive combination:
159
- # - highest admissible Uo factors for the climate zone
160
- # - "bad" (LP) thermal bridging details
139
+ # - LP variant
140
+ # - "bad" thermal bridging details
161
141
  #
162
142
  # 2. If, for a given OpenStudio model, required area-weighted Ut
163
143
  # factors cannot be achieved, TBD then switches over to "good"
164
- # (HP) thermal bridging detailing for that same assembly, and
165
- # repeats the exercise.
144
+ # thermal bridging detailing for that same assembly, and repeats the
145
+ # exercise.
166
146
  #
167
- # 3. A subsequent failed attempt triggers a switch over to EVOKE's
168
- # HP (improved Uo) assemblies. For instance:
147
+ # 3. A subsequent failed attempt triggers a switch over to HP variants.
148
+ # For instance:
169
149
  # - "BTAP-ExteriorWall-WoodFramed-5" ... switches over to:
170
150
  # - "BTAP-ExteriorWall-WoodFramed-7"
171
151
  #
172
152
  # ... switching over to another assembly this way also means
173
- # reverting back to "bad" (LP) thermal bridging PSI factors.
153
+ # reverting back to "bad" thermal bridging PSI factors.
174
154
  #
175
- # 4. A final switch to "good" (HP) details is available (last resort).
155
+ # 4. A final switch to "good" details is triggered if needed.
176
156
  #
177
157
  # If NONE of the available combinations are sufficient:
178
- # - TBD red-flags a failed attempt at NECB2017 or NECB2020 compliance
179
- # - TBD keeps iteration #4 Uo + PSI combo, then derates before a
180
- # BTAP simulation run (giving some performance gap indication)
158
+ # - TBD red-flags a failed attempt at NECB2017+ compliance
159
+ # - TBD keeps iteration #4 (HP Uo + "good" PSI combo), then derates before
160
+ # a BTAP simulation run (giving some performance gap indication)
181
161
 
182
162
  # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- #
183
163
  # Notes:
184
164
  #
185
- # - Steel-framed assemblies: the selected HP variant has metal
186
- # cladding. The only LP steel-framed BTAP option is wood-clad -
187
- # something of an anomaly in commercial construction. By making the
188
- # switch earlier to metal cladding, everywhere in Canada except
189
- # (milder) SW BC and SW NS, it is hoped that a more consistent,
190
- # apples-to-apples comparison is ensured.
165
+ # - Steel-framed assemblies: the costed HP variant has metal cladding.
166
+ # The costed LP variant is wood-clad - something of an anomaly in
167
+ # commercial construction. By making the switch earlier to metal
168
+ # cladding, everywhere in Canada except (milder) SW BC and SW NS, it is
169
+ # hoped that a more consistent, apples-to-apples comparison is ensured.
191
170
  #
192
- # - ROOF and (exposed) floor surfaces refer to a single LP/HP selection
193
- # respectively. This is expected to change in the future @todo.
171
+ # - ROOF and FOOR surfaces refer to a single LP/HP selection respectively.
194
172
 
195
173
  # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- #
196
174
  # Preset BTAP/TBD wall assembly parameters.
197
175
  @@data = {}
198
176
 
199
177
  # Construction sub-variant identified strictly by Uo, e.g. 0.314 W/m2.K.
200
- @@data[MASS2] = {uos: [0.314, 0.278, 0.247, 0.210, 0.183]}
201
- @@data[MASSB] = {uos: [0.130, 0.100]}
178
+ @@data[MASS2] = {uos: [0.314, 0.278, 0.247, 0.210, 0.183]} # :lp
179
+ @@data[MASSB] = {uos: [0.130, 0.100]} # :hp
202
180
 
203
- @@data[MASS4] = {uos: [0.314, 0.278, 0.247, 0.210, 0.183]}
204
- @@data[MASS8] = {uos: [0.130, 0.100]}
181
+ @@data[MASS4] = {uos: [0.314, 0.278, 0.247, 0.210, 0.183]} # :lp
182
+ @@data[MASS8] = {uos: [0.130, 0.100]} # :hp
205
183
 
206
- @@data[WOOD5] = {uos: [0.314, 0.278, 0.247, 0.210, 0.183]}
207
- @@data[WOOD7] = {uos: [0.130]}
184
+ @@data[WOOD5] = {uos: [0.314, 0.278, 0.247, 0.210, 0.183]} # :lp
185
+ @@data[WOOD7] = {uos: [0.130]} # :hp
208
186
 
209
- @@data[STEL1] = {uos: [0.314, 0.278]}
210
- @@data[STEL2] = {uos: [0.247, 0.210, 0.183, 0.130, 0.100, 0.080]}
187
+ @@data[STEL1] = {uos: [0.314, 0.278]} # :lp
188
+ @@data[STEL2] = {uos: [0.247, 0.210, 0.183, 0.130, 0.100, 0.080]} # :hp
211
189
 
212
190
  @@data[FLOOR] = {uos: [0.227, 0.183, 0.162, 0.142, 0.116, 0.101]}
213
191
  @@data[ROOF ] = {uos: [0.227, 0.193, 0.183, 0.162, 0.156, 0.142, 0.138, 0.121, 0.100]}
@@ -221,7 +199,7 @@ module BTAP
221
199
 
222
200
  # Thermal bridge types :balcony, :party and :joint are NOT expected to
223
201
  # be processed soon within BTAP. They are neither costed out, nor are carbon
224
- # intensities (kg CO2eq/linear meter) associated to them. At some point, it
202
+ # intensities (kgCO2-eq/linear meter) associated to them. At some point, it
225
203
  # may be wise to do so (notably cantilevered balconies in MURBs) - @todo.
226
204
  # Default, generic BETBG PSI factors are nonetheless provided here:
227
205
  # - for "bad" BTAP cases : generic BETBG set "bad"
@@ -405,26 +383,108 @@ module BTAP
405
383
  # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- #
406
384
 
407
385
  ##
408
- # Retrieves BTAP-costed assembly.
386
+ # Returns current BTAP list of admissible edges (i.e. linear thermal
387
+ # bridges), as Symbols. These are key for 'gen_tallies' & BTAP costing (TBD
388
+ # tracks a wider range of thermal bridge types than BTAP costing currently
389
+ # supports). This may be updated in the future.
409
390
  #
410
- # @param structure [BTAP::Structure] BTAP Structure object
411
- # @param stype [:walls, :floors or :roofs] surface type
412
- # @param perform [:hp or :lp] high- or low-performance variant
391
+ # @return [Array] admissible BTAP-costed edge types (Symbols)
392
+ def self.admissible_edges
393
+ [:rimjoist, :parapet, :fenestration, :corner, :grade]
394
+ end
395
+
396
+ ##
397
+ # Returns paired low-performance (LP) & high-performance (HP) BTAP-costed
398
+ # wall assembly identifiers, based on user-provided assembly ID. If valid,
399
+ # solution returns a Hash holding 2 keys (:lp and :hp).
413
400
  #
414
- # @return [String] BTAP assembly identifier for costing
415
- def costed_assembly(structure = nil, stype = :walls, perform = :lp)
416
- stype = :walls unless [:roofs, :floors].include?(stype)
417
- perform = :lp unless perform == :hp
418
- return STEL1 unless structure.is_a?(BTAP::Structure)
419
-
420
- # Select BTAP-costed assembly, matching:
421
- # - BTAP::Structure generated construction parameters
422
- # - requested high (HP) vs low-performance (LP) PSI-factor level
423
- #
424
- # Ideally, chosen PSI factor sets and matching OpenStudio constructions
425
- # shouldn't strictly be based on selected BTAP assemblies (e.g.
401
+ # @param id [String] a valid BTAP-costed wall assembly identifier
402
+ #
403
+ # @return [Hash] assembly :lp & :hp identifiers (empty if invalid input)
404
+ def assembly_id(id = "")
405
+ return {} unless @@data.key?(id)
406
+ pair = {}
407
+
408
+ if id == MASS2 || id == MASSB
409
+ pair[:lp] = MASS2
410
+ pair[:hp] = MASSB
411
+ elsif id == MASS4 || id == MASS8
412
+ pair[:lp] = MASS4
413
+ pair[:hp] = MASS8
414
+ elsif id == WOOD5 || id == WOOD7
415
+ pair[:lp] = WOOD5
416
+ pair[:hp] = WOOD7
417
+ elsif id == STEL1 || id == STEL2
418
+ pair[:lp] = STEL1
419
+ pair[:hp] = STEL2
420
+ end
421
+
422
+ pair
423
+ end
424
+
425
+ ##
426
+ # Returns an above-grade (opaque) BTAP-costed assembly identifier that
427
+ # matches selection criteria. Solution may return an assembly's low
428
+ # performance (:lp) or its paired high-performance (:hp) variant. Defaults
429
+ # to STEL2 (walls) if invalid input.
430
+ #
431
+ # @param [Hash] argh options
432
+ # @option argh [Symbol] :stype (:walls, :floors or :roofs)
433
+ # @option argh [Symbol] :perform (:lp or :hp) wall variant
434
+ # @option argh [Symbol] :framing (:steel, :wood, :cmu)
435
+ # @option argh [Symbol] :cladding (:heavy)
436
+ # @option argh [Symbol] :finish (:heavy)
437
+ # @option argh [String] :id a BTAP-costed assembly (often :lp)
438
+ # @option argh [Float] :uo desired U-factor
439
+ #
440
+ # @return [String] BTAP costed assembly identifier
441
+ def costed_assembly(argh = {})
442
+ return STEL2 unless argh.respond_to?(:keys)
443
+
444
+ # Key input.
445
+ stype = argh.key?(:stype) ? argh[:stype] : :walls
446
+ stype = :walls unless [:roofs, :floors].include?(stype)
447
+
448
+ # No BTAP variants yet for roofs or floors.
449
+ return ROOF if stype == :roofs
450
+ return FLOOR if stype == :floors
451
+
452
+ # Low-performance (:lp) or high-performance (:hp) variant?
453
+ perform = argh.key?(:perform) ? argh[:perform] : :lp
454
+ perform = :lp unless perform == :hp
455
+
456
+ # Prioritizing BTAP-costed wall assembly identifiers.
457
+ if argh.key?(:id)
458
+ pair = assembly_id(argh[:id])
459
+
460
+ unless pair.empty?
461
+ if argh.key?(:uo)
462
+ uo = costed_uo(pair[perform], argh[:uo])
463
+ perform = :hp if uo.nil?
464
+ end
465
+
466
+ return pair[perform]
467
+ end
468
+ end
469
+
470
+ # Relying instead on STRUCTURE-based parameters.
471
+ framing = argh.key?(:framing) ? argh[:framing ] : :steel
472
+ cladding = argh.key?(:cladding) ? argh[:cladding] : :light
473
+ finish = argh.key?(:finish) ? argh[:finish ] : :light
474
+
475
+ framing = :steel unless [:wood, :cmu].include?(framing)
476
+ cladding = :light unless cladding == :heavy
477
+ finish = :light unless finish == :heavy
478
+
479
+ # Select BTAP-costed wall assembly, matching:
480
+ # - BTAP::Structure generated construction parameters (e.g. framing)
481
+ # - requested high- (:hp) vs low-performance (:lp) variant
482
+ pair = {}
483
+
484
+ # Note: Chosen PSI factor sets and matching OpenStudio constructions
485
+ # shouldn't strictly be based on selected BTAP-costed assemblies (e.g.
426
486
  # wood-framed vs steel-framed, cladding choice), but also on selected
427
- # building 'structure', e.g.:
487
+ # building STRUCTURE, e.g.:
428
488
  #
429
489
  # - "wood-framed" MURB
430
490
  # - "steel post/beam" office building
@@ -432,43 +492,32 @@ module BTAP
432
492
  # - "metal(-building)" warehouse
433
493
  # - "mass-timber (CLT)" university pavilion
434
494
  #
435
- # Major thermal bridges often consist of anchors or supports that transmit
436
- # structural loads (and by the same token, 'heat') to a building's main
437
- # structure. Examples include balconies, parapets and shelf angles.
438
- # Highly conductive building structures (e.g. steel, aluminium) exacerbate
439
- # thermal bridging effects - so building structural selection matters.
495
+ # Linear thermal bridges often consist of structural elements that
496
+ # transmit envelope structural loads (and by the same token, 'heat') to a
497
+ # building's main structure. Examples include balconies, parapets and
498
+ # shelf angles. Highly conductive building structural materials (e.g.
499
+ # steel, concrete) exacerbate thermal bridging - building structural
500
+ # choices should matter.
440
501
  #
441
502
  # The BTAP::Structure module generates such attributes, yet BTAP's costed
442
503
  # thermal bridging database doesn't yet distinguish between building
443
504
  # structures - @todo. For the moment, BTAP PSI set selection is strictly
444
505
  # based on BTAP::Structure's :framing, :cladding and :finish attributes,
445
- # which must be set prior to initiating BTAP's TBD's thermal bridging
446
- # solution:
447
-
448
- # Light gauge steel framing by default. Override if wood, cmu or precast.
449
- case stype
450
- when :roofs then return ROOF
451
- when :floors then return FLOOR
506
+ # which are set prior to initiating BTAP's TBD solution.
507
+ case framing
508
+ when :wood then pair = assembly_id(WOOD5)
509
+ when :cmu then pair = assembly_id(MASS2)
452
510
  else
453
- case structure.framing
454
- when :wood
455
- c1 = WOOD5
456
- c2 = WOOD7
457
- when :cmu
458
- c1 = MASS2
459
- c2 = MASSB
460
- else
461
- if structure.cladding == :heavy && structure.finish == :heavy
462
- c1 = MASS4
463
- c2 = MASS8
464
- else
465
- c1 = STEL1
466
- c2 = STEL2
467
- end
468
- end
511
+ pair = assembly_id(STEL1)
512
+ pair = assembly_id(MASS4) if cladding == :heavy && finish == :heavy
513
+ end
514
+
515
+ if argh.key?(:uo)
516
+ uo = costed_uo(pair[perform], argh[:uo])
517
+ perform = :hp if uo.nil?
469
518
  end
470
519
 
471
- perform == :lp ? c1 : c2
520
+ pair[perform]
472
521
  end
473
522
 
474
523
  ##
@@ -564,229 +613,399 @@ module BTAP
564
613
  attr_reader :tally
565
614
 
566
615
  ##
567
- # Initializes OpenStudio model-specific BTAP/TBD data - uprates/derates.
616
+ # Initializes BTAP/TBD data, uprates/derates, generates output.
568
617
  #
569
618
  # @param model [OpenStudio::Model::Model] a model
570
619
  # @param [Hash] argh BTAP/TBD argument hash
571
- # @option argh [BTAP::Structure] structure a BTAP STRUCTURE object
572
- # @option argh [Hash] walls exterior wall parameters e.g. :uo, :ut
573
- # @option argh [Hash] floors exposed floor parameters e.g. :uo, :ut
574
- # @option argh [Hash] roofs exterior roof parameters e.g. :uo, :ut
575
- # @option argh [:good, :bad] quality derating option (if not uprating)
576
- # @option argh [Boolean] interpolate if TBD interpolates among Uo (uprate)
620
+ # @option argh [BTAP::Structure] :structure a BTAP STRUCTURE instance
621
+ # @option argh [Hash] :walls exterior wall parameters (:uo, :ut)
622
+ # @option argh [Hash] :floors exposed floor parameters (:uo, :ut)
623
+ # @option argh [Hash] :roofs exterior roof parameters (:uo, :ut)
624
+ # @option argh [:good, :bad] :quality of thermal bridging (derating)
625
+ # @option argh [Boolean] :interpolate between costed Uo factors (uprating)
577
626
  def initialize(model = nil, argh = {})
578
- btp = BTAP::Resources::Envelope::Constructions # alias
579
- mth = "BTAP::Bridging::#{__callee__}"
580
- @model = {}
581
- @tally = {}
627
+ mth = "BTAP::Bridging::#{__callee__}"
628
+
629
+ # Track warnings & errors.
582
630
  @feedback = {logs: []}
583
- lgs = @feedback[:logs]
631
+ lgs = @feedback[:logs]
632
+
633
+ # Store model & arguments.
634
+ @model = {osm: model, argh: argh}
584
635
 
585
- # Populate and validate BTAP/TBD & OpenStudio model parameters. This does
636
+ # Validate and populate BTAP/TBD & OpenStudio model parameters. This does
586
637
  # a safe TBD trial run, returning true if successful. If false, TBD leaves
587
638
  # the model unaltered. Check @feedback logs.
588
- return unless self.populate(model, argh)
639
+ return unless populate
589
640
 
590
- # Initialize loop controls and flags.
591
- initial = true
592
- complies = false
593
- comply = {} # specific to :walls, :floors & :roofs (if uprating)
594
- perform = :lp
595
- quality = argh[:quality] == :good ? :good : :bad
596
- combo = "#{perform.to_s}_#{quality.to_s}".to_sym # e.g. :lp_bad
597
- args = {} # TBD's own argument hash
598
-
599
- # Initialize surface types & TBD arguments for iterative uprating runs.
600
- @model[:stypes].each do |stypes|
601
- next unless argh[stypes].key?(:ut)
602
-
603
- stype = stypes.to_s.chop
604
- uprate = "uprate_#{stypes.to_s}".to_sym
605
- option = "#{stype}_option".to_sym
606
- ut = "#{stype}_ut".to_sym
607
-
608
- args[uprate] = true
609
- args[option] = "ALL #{stype} constructions"
610
- args[ut ] = argh[stypes][:ut]
641
+ # Tracking uprating success, separately for :walls, :roofs vs :floors.
642
+ comply = {}
611
643
 
612
- comply[stypes] = false
613
- end
644
+ # TRUE if TBD successfully uprates all surface types.
645
+ complies = false
614
646
 
615
- # Building-wide PSI set.
616
- @model[:constructions].values.each do |v|
617
- args[:io_path] = v[combo] if v.key?(combo)
618
- end
647
+ # Initialize TBD's own argument hash.
648
+ args = {option: "", io_path: inputs( @model[:quality] )}
619
649
 
620
- return false if args[:io_path].nil?
650
+ # Uprating? Possibly 2x trial runs to identify candidate constructions.
651
+ 2.times do |i|
652
+ break unless @model[:uprating]
653
+ break if complies
654
+ break if @model[:quality] == :good && i > 0
621
655
 
622
- args[:option ] = ""
656
+ # Reset overall PSI factor quality.
657
+ @model[:quality] = :good unless i.zero?
623
658
 
624
- loop do
625
- if initial
626
- initial = false
627
- else
628
- # Subsequent uprating runs. Upgrade technologies. Reset TBD args.
629
- if quality == :bad
630
- quality = :good
631
- combo = "#{perform.to_s}_#{quality.to_s}".to_sym
659
+ # Reset TBD arguments.
660
+ args = {option: "", io_path: inputs( @model[:quality] )}
632
661
 
633
- @model[:constructions].values.each do |v|
634
- args[:io_path] = v[combo] if v.key?(combo)
635
- end
636
- elsif perform == :lp
637
- # Switch 'perform' from :lp to :hp - reset quality to :bad.
638
- perform = :hp
639
- quality = :bad
640
- combo = "#{perform.to_s}_#{quality.to_s}".to_sym
641
-
642
- @model[:constructions].values.each do |v|
643
- args[:io_path] = v[combo] if v.key?(combo)
644
- end
645
- end
662
+ # Add surface type (uprating) inputs to TBD argument hash.
663
+ @model[:stypes].each do |stypes, u|
664
+ stype = stypes.to_s.chop # e.g. "wall" (from :walls)
665
+ uprate = "uprate_#{stypes.to_s}".to_sym # e.g. :uprate_walls
666
+ option = "#{stype}_option".to_sym # e.g. :wall_option
667
+ ut = "#{stype}_ut".to_sym # e.g. :wall_ut
646
668
 
647
- # Delete previously-generated TBD args Uo key/value pairs.
648
- @model[:stypes].each do |stypes|
649
- next unless comply.key?(stypes)
669
+ args[uprate] = true
670
+ args[option] = "ALL #{stype} constructions"
671
+ args[ut ] = u[:ut]
650
672
 
651
- uo = "#{stypes.to_s.chop}_uo".to_sym
652
- args.delete(uo) if args.key?(uo)
653
- end
673
+ comply[stypes] = false
654
674
  end
655
675
 
656
- # Run TBD on cloned OpenStudio model - compliant combo?
676
+ # Trial runs on cloned OpenStudio model.
657
677
  mdl = OpenStudio::Model::Model.new
658
678
  mdl.addObjects(model.toIdfFile.objects)
659
679
  TBD.clean!
680
+ TBD.process(mdl, args)
681
+
682
+ # If uprating is successful, TBD 'args' hash holds (new) uprated Uo
683
+ # keys/values for :walls, :floors and/or :roofs.
684
+ complies = true
685
+
686
+ # Even if all constructions were successfully uprated by TBD, BTAP
687
+ # may not hold costed assembly variants that match required targets.
688
+ # If TBD-uprated Uo factors are lower than any of these admissible
689
+ # BTAP Uo factors, the solution logs the failed attempt and defaults
690
+ # to the lowest Uo factors in the BTAP costing database.
691
+ @model[:stypes].keys.each do |stypes|
692
+ break unless complies
693
+
694
+ # Initialize.
695
+ comply[stypes] = true
660
696
 
661
- # fil = File.join("/Users/rd2/Desktop/test.osm")
662
- # mdl.save(fil, true)
697
+ stype_uo = "#{stypes.to_s.chop}_uo".to_sym
698
+ target = args.key?(stype_uo) ? args[stype_uo] : nil
699
+
700
+ unless target
701
+ comply[stypes] = false
702
+ complies = false
703
+ next
704
+ else
705
+ bOPTs = {} # building
706
+ bOPTs[:id ] = @model[:building][stypes][:id]
707
+ bOPTs[:stype] = stypes
708
+ bOPTs[:uo ] = target
709
+ bID = costed_assembly(bOPTs)
710
+
711
+ unless costed_uo(bID, target)
712
+ comply[stypes] = false
713
+ complies = false
714
+ next
715
+ end
716
+
717
+ unless @model[:attic][stypes].empty?
718
+ types = case stypes
719
+ when :roofs then :floors
720
+ when :floors then :roofs
721
+ else :walls
722
+ end
723
+
724
+ aOPTs = {} # attic(s)
725
+ aOPTs[:id ] = @model[:attic][types][:id]
726
+ aOPTs[:stype] = types
727
+ aOPTs[:uo ] = target
728
+ aID = costed_assembly(aOPTs)
729
+
730
+ unless costed_uo(aID, target)
731
+ comply[types] = false
732
+ complies = false
733
+ next
734
+ end
735
+ end
663
736
 
664
- res = TBD.process(mdl, args)
737
+ @model[:spaces].each do |id, sp|
738
+ break unless comply[stypes]
665
739
 
666
- # Halt all processes if fatal errors raised by TBD (e.g. badly formatted
667
- # TBD arguments, poorly-structured OpenStudio models).
668
- if TBD.fatal?
669
- TBD.logs.each { |lg| lgs << lg[:message] if lg[:level] == TBD::FTL }
670
- break
740
+ sOPTs = {} # customized spaces
741
+ sOPTs[:id ] = sp[stypes][:id]
742
+ sOPTs[:stype] = stypes
743
+ sOPTs[:uo ] = target
744
+ sID = costed_assembly(sOPTs)
745
+
746
+ unless costed_uo(sID, target)
747
+ comply[stypes] = false
748
+ complies = false
749
+ next
750
+ end
751
+ end
752
+ end
671
753
  end
672
754
 
673
- complies = true
674
- # Check if TBD-uprated Uo factors are valid: TBD args hash holds (new)
675
- # uprated Uo keys/values for :walls, :floors and/or :roofs if uprating
676
- # is successful. In most cases, uprating tends to fail for wall
677
- # constructions rather than roof or floor constructions, due to the
678
- # typically larger density of linear thermal bridging per surface area
679
- # Yet even if all constructions were successfully uprated by TBD, one
680
- # must then determine if BTAP holds admissible (i.e. costed) assembly
681
- # variants with corresponding Uo factors. If TBD-uprated Uo factors are
682
- # lower than any of these admissible BTAP Uo factors, then no
683
- # commercially available solution can been identified.
684
- @model[:stypes].each do |stypes|
685
- next unless comply.key?(stypes) # true only if uprating
686
-
687
- # TBD-estimated Uo target to meet NECB-required Ut - nil if invalid.
755
+ # Skip if first iteration fails.
756
+ next if i.zero? && complies == false
757
+
758
+ @model[:stypes].each do |stypes, u|
688
759
  stype_uo = "#{stypes.to_s.chop}_uo".to_sym
689
760
  target = args.key?(stype_uo) ? args[stype_uo] : nil
690
- assembly = self.costed_assembly(argh[:structure], stypes, perform)
691
761
 
692
- uo = target ? self.costed_uo(assembly, target) : nil
762
+ if target
763
+ u[:uo] = target
764
+
765
+ bOPTs = {} # building
766
+ bOPTs[:id ] = @model[:building][stypes][:id]
767
+ bOPTs[:stype] = stypes
768
+ bOPTs[:uo ] = target
769
+ bID = costed_assembly(bOPTs)
770
+
771
+ @model[:building][stypes][:id] = bID
772
+ bUo = costed_uo(bID, target)
773
+
774
+ if bUo
775
+ bUo = target if @model[:interpolate]
693
776
 
694
- if uo
695
- uo = target if argh[:interpolate]
696
- comply[stypes] = true
777
+ # Candidate construction successfully identified.
778
+ @model[:building][stypes][:compliant] = true
779
+ @model[:building][stypes][:uo ] = bUo
780
+ else
781
+ bUo = lowest_uo(bID)
782
+ comply[stypes] = false
783
+ complies = false
784
+
785
+ # Fallback.
786
+ @model[:building][stypes][:compliant] = false
787
+ @model[:building][stypes][:uo ] = bUo
788
+ end
789
+
790
+ unless @model[:attic][stypes].empty?
791
+ types = case stypes
792
+ when :roofs then :floors
793
+ when :floors then :roofs
794
+ else :walls
795
+ end
796
+
797
+ aOPTs = {} # attic
798
+ aOPTs[:id ] = @model[:attic][types][:id]
799
+ aOPTs[:stype] = types
800
+ aOPTs[:uo ] = target
801
+ aID = costed_assembly(aOPTs)
802
+
803
+ @model[:attic][types][:id] = aID
804
+ aUo = costed_uo(aID, target)
805
+
806
+ if aUo
807
+ aUo = target if @model[:interpolate]
808
+
809
+ # Candidate construction successfully identified.
810
+ @model[:attic][types][:compliant] = true
811
+ @model[:attic][types][:uo ] = aUo
812
+ else
813
+ aUo = lowest_uo(aID)
814
+ comply[types] = false
815
+ complies = false
816
+
817
+ # Fallback.
818
+ @model[:attic][types][:compliant] = false
819
+ @model[:attic][types][:uo ] = aUo
820
+ end
821
+ end
822
+
823
+ @model[:spaces].each do |id, sp|
824
+ sOPTs = {} # customized space(s)
825
+ sOPTs[:id ] = sp[stypes][:id]
826
+ sOPTs[:stype] = stypes
827
+ sOPTs[:uo ] = target
828
+ sID = costed_assembly(sOPTs)
829
+
830
+ sp[stypes][:id] = sID
831
+ sUo = costed_uo(sID, target)
832
+
833
+ if sUo
834
+ sUo = target if @model[:interpolate]
835
+
836
+ # Candidate construction successfully identified.
837
+ sp[stypes][:compliant] = true
838
+ sp[stypes][:uo ] = sUo
839
+ else
840
+ sUo = lowest_uo(sID)
841
+ comply[stypes] = false
842
+ complies = false
843
+
844
+ # Fallback.
845
+ sp[stypes][:compliant] = false
846
+ sp[stypes][:uo] = sUo
847
+ end
848
+ end
697
849
  else
698
- uo = self.lowest_uo(assembly)
699
850
  comply[stypes] = false
700
- end
851
+ complies = false
852
+
853
+ bID = @model[:building][stypes][:id]
854
+ bUo = lowest_uo(bID)
855
+
856
+ bOPTs = {}
857
+ bOPTs[:id ] = bID
858
+ bOPTs[:stype] = stypes
859
+ bOPTs[:uo ] = bUo
860
+ bID = costed_assembly(bOPTs)
861
+ bUo = costed_uo(bID, bUo)
862
+
863
+ @model[:building][stypes][:compliant] = false
864
+ @model[:building][stypes][:id ] = bID
865
+ @model[:building][stypes][:uo ] = bUo
866
+
867
+ u[:uo] = bUo
868
+
869
+ unless @model[:attic][stypes].empty?
870
+ types = case stypes
871
+ when :roofs then :floors
872
+ when :floors then :roofs
873
+ else :walls
874
+ end
875
+
876
+ aID = @model[:building][types][:id]
877
+ aUo = lowest_uo(aID)
878
+
879
+ aOPTs = {}
880
+ aOPTs[:id ] = aID
881
+ aOPTs[:stype] = types
882
+ aOPTs[:uo ] = aUo
883
+ aID = costed_assembly(aOPTs)
884
+ aUo = costed_uo(aID, aUo)
885
+
886
+ @model[:attic][types][:compliant] = false
887
+ @model[:attic][types][:id ] = aID
888
+ @model[:attic][types][:uo ] = aUo
889
+ end
701
890
 
702
- @model[:constructions].each do |lc, v|
703
- next unless v[:stypes] == stypes
891
+ @model[:spaces].each do |id, sp|
892
+ sID = sp[stypes][:id]
893
+ sUo = lowest_uo(sID)
704
894
 
705
- v[:uo] = uo
706
- v[:compliant] = comply[stypes]
707
- end
895
+ sOPTs = {}
896
+ sOPTs[:id ] = sID
897
+ sOPTs[:stype] = stypes
898
+ sOPTs[:uo ] = sUo
899
+ sID = costed_assembly(sOPTs)
900
+ sUo = costed_uo(sID, sUo)
708
901
 
709
- complies = false unless comply[stypes]
902
+ sp[stypes][:compliant] = false
903
+ sp[stypes][:id ] = sID
904
+ sp[stypes][:uo ] = sUo
905
+ end
906
+ end
710
907
  end
711
-
712
- # Exit if successful or if final BTAP uprating option.
713
- break if combo == :hp_good
714
- break if complies
715
908
  end
716
909
 
717
910
  # Post-loop steps (if uprating).
718
- @model[:stypes].each do |stypes|
911
+ @model[:stypes].each do |stypes, u|
719
912
  next unless comply.key?(stypes) # true only if uprating
720
913
 
721
- # Cancel uprating request before final derating.
722
- stype = stypes.to_s.chop
723
- uprate = "uprate_#{stypes.to_s}".to_sym
724
- option = "#{stype}_option".to_sym
725
- ut = "#{stype}_ut".to_sym
726
- args.delete(uprate)
727
- args.delete(option)
728
- args.delete(ut)
729
-
730
914
  # Reset uprated Uo factor for each 'deratable' construction.
731
- @model[:constructions].each do |lc, v|
732
- next unless v[:stypes] == stypes
915
+ bLC = @model[:building][stypes][:lc]
916
+ bFr = @model[:building][stypes][:film]
917
+ bIx = @model[:building][stypes][:indx]
918
+ bUo = @model[:building][stypes][:uo]
919
+ bRSI = TBD.resetUo(bLC, bFr, bIx, bUo)
920
+
921
+ # Re/set Uo as AdditionalProperty:
922
+ addprop(bLC, "btap_uo", bUo)
923
+
924
+ # Repeat for attics (if applicable).
925
+ unless @model[:attic][stypes].empty?
926
+ types = case stypes
927
+ when :roofs then :floors
928
+ when :floors then :roofs
929
+ else :walls
930
+ end
931
+
932
+ aLC = @model[:attic][types][:lc]
933
+ aFr = @model[:attic][types][:film]
934
+ aIx = @model[:attic][types][:indx]
935
+ aUo = @model[:attic][types][:uo]
936
+ aRSI = TBD.resetUo(aLC, aFr, aIx, aUo)
937
+
938
+ # Re/set Uo as AdditionalProperty:
939
+ addprop(aLC, "btap_uo", aUo)
940
+ end
733
941
 
734
- v[:r] = TBD.resetUo(lc, v[:filmRSI], v[:index], v[:uo])
942
+ # Repeat for customized spaces (if applicable).
943
+ @model[:spaces].values.each do |sp|
944
+ sLC = sp[stypes][:lc]
945
+ sFr = sp[stypes][:film]
946
+ sIx = sp[stypes][:indx]
947
+ sUo = sp[stypes][:uo]
948
+ sRSI = TBD.resetUo(sLC, sFr, sIx, sUo)
949
+
950
+ # Re/set Uo as AdditionalProperty:
951
+ addprop(sLC, "btap_uo", sUo)
735
952
  end
736
953
  end
737
954
 
738
- @model[:comply ] = comply
739
- @model[:complies] = complies
740
- @model[:perform ] = perform
741
- @model[:quality ] = quality
742
- @model[:combo ] = combo
955
+ complies = true unless @model[:uprating]
956
+ @model[:comply ] = comply # true/false, specific to surface type
957
+ @model[:complies] = complies # true/false for entire model
958
+
959
+ # Log NECB Part 3 prescriptive compliance as AdditionalProperty.
960
+ if @model[:uprating]
961
+ model.getBuilding.additionalProperties.setFeature("tbd_compliance", complies)
962
+ end
743
963
 
744
964
  # Run "process" TBD one last time, on "model" (not cloned "mdl").
965
+ args = {option: "", io_path: inputs( @model[:quality] )}
745
966
  TBD.clean!
746
- res = TBD.process(model, args)
967
+ TBD.process(model, args)
747
968
 
748
- @model[:io ] = res[:io ] # TBD outputs (i.e. "tbd.out.json")
749
- @model[:surfaces] = res[:surfaces] # TBD derated surface data
750
- @model[:argh ] = argh # method argument hash
751
- @model[:args ] = args # last TBD inputs (i.e. "tbd.json")
969
+ @model[:args ] = args # last TBD inputs (i.e. "tbd.json")
970
+ @model[:io ] = args[:io]
971
+ @model[:surfaces] = args[:surfaces]
752
972
 
753
- self.gen_tallies # tallies for BTAP costing
754
- self.gen_feedback # log success messages for BTAP
973
+ gen_tallies # tallies for BTAP costing
974
+ gen_feedback # log success messages for BTAP
755
975
  end
756
976
 
757
977
  ##
758
- # Populates and validates BTAP/TBD & OpenStudio model parameters for thermal
759
- # bridging. This also does a safe TBD trial run, returning true if
760
- # successful. Check @feedback logs if false.
761
- #
762
- # @param model [OpenStudio::Model::Model] a model
763
- # @param [Hash] argh BTAP/TBD argument hash
764
- # @option argh [BTAP::Structure] structure a BTAP STRUCTURE object
765
- # @option argh [Hash] walls exterior wall parameters e.g. :uo, :ut
766
- # @option argh [Hash] floors exposed floor parameters e.g. :uo, :ut
767
- # @option argh [Hash] roofs exterior roof parameters e.g. :uo, :ut
768
- # @option argh [Symbol] quality derating option (if not uprating)
769
- # @option argh [Boolean] interpolate if TBD should pick between Uo values
978
+ # Minimally validates an OpenStudio model, as well as BTAP/TBD parameters,
979
+ # towards thermal bridging calculations. This also does a safe TBD trial
980
+ # run, returning true if successful. Check @feedback logs if false.
981
+ # AdditionalProperties may be re/set in the OpenStudio model.
770
982
  #
771
983
  # @return [Boolean] true if valid (check @feedback logs if false)
772
- def populate(model = nil, argh = {})
773
- mth = "BTAP::Bridging::#{__callee__}"
774
- args = { option: "(non thermal bridging)" } # for initial TBD dry run
775
- lgs = @feedback[:logs]
776
- cl = OpenStudio::Model::LayeredConstruction
984
+ def populate()
985
+ mth = "BTAP::Bridging::#{__callee__}"
986
+ lgs = @feedback[:logs]
987
+ cl = OpenStudio::Model::LayeredConstruction
988
+
989
+ model = @model[:osm]
990
+ argh = @model[:argh]
777
991
 
992
+ # Valid model?
778
993
  unless model.is_a?(OpenStudio::Model::Model)
779
994
  lgs << "Invalid OpenStudio model to de/up-rate (#{mth})"
780
995
  return false
781
996
  end
782
997
 
998
+ # Valid argument hash?
783
999
  unless argh.is_a?(Hash)
784
1000
  lgs << "Invalid BTAP/TBD argument Hash (#{mth})"
785
1001
  return false
786
1002
  end
787
1003
 
1004
+ # Valid BTAP::Structure instance?
788
1005
  if argh.key?(:structure)
789
- unless argh[:structure].is_a?(BTAP::Structure)
1006
+ stc = argh[:structure]
1007
+
1008
+ unless stc.is_a?(BTAP::Structure)
790
1009
  lgs << "Invalid BTAP::Structure (#{mth})"
791
1010
  return false
792
1011
  end
@@ -795,15 +1014,10 @@ module BTAP
795
1014
  return false
796
1015
  end
797
1016
 
798
- # Building-wide envelope (e.g. assemblies, U-factors, PSI-factors) options
799
- # depend on building structure-dependent features such as framing,
800
- # cladding, etc. This will need to adapt to upcoming story/space
801
- # construction/PSI customization - @todo.
802
- strc = argh[:structure]
803
-
804
- argh[:interpolate] = false unless argh.key?(:interpolate)
805
- argh[:interpolate] = false unless [true, false].include?(argh[:interpolate])
1017
+ # Uprating? Initialize.
1018
+ @model[:uprating] = false
806
1019
 
1020
+ # All 3 surface types initiated?
807
1021
  [:walls, :floors, :roofs].each do |stypes|
808
1022
  unless argh.key?(stypes)
809
1023
  lgs << "Missing BTAP/TBD #{stypes} (#{mth})"
@@ -824,6 +1038,8 @@ module BTAP
824
1038
 
825
1039
  next unless argh[stypes].key?(:ut)
826
1040
 
1041
+ # Uprating!
1042
+ @model[:uprating] = true if stypes == :walls
827
1043
  ut = argh[stypes][:ut]
828
1044
 
829
1045
  unless ut.is_a?(Numeric) && ut.between?(TBD::UMIN, TBD::UMAX)
@@ -832,230 +1048,802 @@ module BTAP
832
1048
  end
833
1049
  end
834
1050
 
835
- # Run TBD on a cloned OpenStudio model (dry run).
1051
+ # When uprating: interpolating between discrete Uo factors?
1052
+ @model[:interpolate] = argh.key?(:interpolate) ? argh[:interpolate] : true
1053
+ @model[:interpolate] = false unless @model[:interpolate] == true
1054
+
1055
+ # Selected PSI factor quality (:good or :bad):
1056
+ # - required when strictly derating
1057
+ # - iteratively reset when uprating
1058
+ @model[:quality] = argh.key?(:quality) ? argh[:quality] : :bad
1059
+ @model[:quality] = :bad unless @model[:quality] == :good
1060
+
1061
+ # NECB 2011-2015 Uo targets.
1062
+ wUo = argh[:walls ][:uo]
1063
+ rUo = argh[:roofs ][:uo]
1064
+ fUo = argh[:floors][:uo]
1065
+
1066
+ # NECB 2017-2025 Ut targets - Uo is reset when uprating.
1067
+ wUt = argh[:walls ].key?(:ut) ? argh[:walls ][:ut] : wUo
1068
+ rUt = argh[:roofs ].key?(:ut) ? argh[:roofs ][:ut] : rUo
1069
+ fUt = argh[:floors].key?(:ut) ? argh[:floors][:ut] : fUo
1070
+
1071
+ # Track building, attic(s), customized spaces & actual surface types.
1072
+ @model[:building] = {walls: {}, roofs: {}, floors: {}}
1073
+ @model[:attic ] = {walls: {}, roofs: {}, floors: {}}
1074
+ @model[:spaces ] = {}
1075
+ @model[:stypes ] = {}
1076
+
1077
+ # Track constructions. REMOVE @todo.
1078
+ @model[:constructions] = {}
1079
+
1080
+ # Track 'building' default construction set.
1081
+ bldg = model.getBuilding
1082
+ bset = bldg.defaultConstructionSet
1083
+
1084
+ if bset.empty?
1085
+ lgs << "No BUILDING default construction set (#{mth})"
1086
+ return false
1087
+ end
1088
+
1089
+ bset = bset.get
1090
+ extC = bset.defaultExteriorSurfaceConstructions
1091
+
1092
+ if extC.empty?
1093
+ lgs << "No BUILDING default exterior constructions (#{mth})"
1094
+ return false
1095
+ end
1096
+
1097
+ wallC = extC.get.wallConstruction
1098
+ roofC = extC.get.roofCeilingConstruction
1099
+ floorC = extC.get.floorConstruction
1100
+
1101
+ if wallC.empty? || roofC.empty? || floorC.empty?
1102
+ lgs << "Missing BUILDING default exterior constructions (#{mth})"
1103
+ return false
1104
+ end
1105
+
1106
+ wallC = wallC.get.to_LayeredConstruction
1107
+ roofC = roofC.get.to_LayeredConstruction
1108
+ floorC = floorC.get.to_LayeredConstruction
1109
+
1110
+ if wallC.empty? || roofC.empty? || floorC.empty?
1111
+ lgs << "BUILDING default exterior layered constructions (#{mth})"
1112
+ return false
1113
+ end
1114
+
1115
+ wallC = wallC.get
1116
+ roofC = roofC.get
1117
+ floorC = floorC.get
1118
+ walliL = TBD.insulatingLayer(wallC)
1119
+ roofiL = TBD.insulatingLayer(roofC)
1120
+ flooriL = TBD.insulatingLayer(floorC)
1121
+
1122
+ if walliL[:index].nil? || roofiL[:index].nil? || flooriL.nil?
1123
+ lgs << "Missing BUILDING insulating layer(s) (#{mth})"
1124
+ return false
1125
+ end
1126
+
1127
+ # Previously-assigned BTAP-costed surface air film resistances?
1128
+ wFR = prop(wallC, "btap_film", Float)
1129
+ rFR = prop(roofC, "btap_film", Float)
1130
+ fFR = prop(floorC, "btap_film", Float)
1131
+ wFR = 0 unless wFR
1132
+ rFR = 0 unless rFR
1133
+ fFR = 0 unless fFR
1134
+
1135
+ # Previously-assigned construction net area?
1136
+ wM2 = prop(wallC, "btap_area", Float)
1137
+ rM2 = prop(roofC, "btap_area", Float)
1138
+ fM2 = prop(floorC, "btap_area", Float)
1139
+
1140
+ wM2 = wallC.getNetArea unless wM2
1141
+ rM2 = roofC.getNetArea unless rM2
1142
+ fM2 = floorC.getNetArea unless fM2
1143
+
1144
+ # Previously-assigned BTAP-costed construction IDs?
1145
+ wID = prop(wallC, "btap_id", String)
1146
+ rID = prop(roofC, "btap_id", String)
1147
+ fID = prop(floorC, "btap_id", String)
1148
+ wID = "" unless wID
1149
+ rID = "" unless rID
1150
+ fID = "" unless fID
1151
+ wID = "" unless data.include?(wID)
1152
+ rID = "" unless data.include?(rID)
1153
+ fID = "" unless data.include?(fID)
1154
+
1155
+ # Fallback: BTAP-costed wall construction from STRUCTURE parameters.
1156
+ if wID.empty?
1157
+ opts = {}
1158
+ opts[:framing ] = stc.framing
1159
+ opts[:cladding] = stc.cladding
1160
+ opts[:finish ] = stc.finish
1161
+ opts[:perform ] = :lp
1162
+ opts[:stype ] = :walls
1163
+ opts[:uo ] = wUo
1164
+ wID = BTAP::Bridging.costed_assembly(argh)
1165
+ end
1166
+
1167
+ # Fallback: BTAP-costed roof construction from STRUCTURE parameters.
1168
+ if rID.empty?
1169
+ opts = {}
1170
+ opts[:framing ] = stc.framing
1171
+ opts[:cladding] = stc.cladding
1172
+ opts[:finish ] = stc.finish
1173
+ opts[:perform ] = :lp
1174
+ opts[:stype ] = :roofs
1175
+ opts[:uo ] = rUo
1176
+ rID = BTAP::Bridging.costed_assembly(argh)
1177
+ end
1178
+
1179
+ # Fallback: BTAP-costed floor construction from STRUCTURE parameters.
1180
+ if fID.empty?
1181
+ opts = {}
1182
+ opts[:framing ] = stc.framing
1183
+ opts[:cladding] = stc.cladding
1184
+ opts[:finish ] = stc.finish
1185
+ opts[:perform ] = :lp
1186
+ opts[:stype ] = :floors
1187
+ opts[:uo ] = fUo
1188
+ fID = BTAP::Bridging.costed_assembly(argh)
1189
+ end
1190
+
1191
+ addprop(bldg, "btap_ut", wUt)
1192
+ addprop(bldg, "btap_uo", wUo)
1193
+ addprop(bldg, "btap_id", wID)
1194
+ addprop(bldg, "btap_area", wM2)
1195
+ addprop(bldg, "btap_film", wFR)
1196
+ addprop(wallC, "btap_ut", wUt)
1197
+ addprop(wallC, "btap_uo", wUo)
1198
+ addprop(wallC, "btap_id", wID)
1199
+ addprop(wallC, "btap_area", wM2)
1200
+ addprop(wallC, "btap_film", wFR)
1201
+ addprop(roofC, "btap_ut", rUt)
1202
+ addprop(roofC, "btap_uo", rUo)
1203
+ addprop(roofC, "btap_id", rID)
1204
+ addprop(roofC, "btap_area", rM2)
1205
+ addprop(roofC, "btap_film", rFR)
1206
+ addprop(floorC, "btap_ut", fUt)
1207
+ addprop(floorC, "btap_uo", fUo)
1208
+ addprop(floorC, "btap_id", fID)
1209
+ addprop(floorC, "btap_area", fM2)
1210
+ addprop(floorC, "btap_film", fFR)
1211
+
1212
+ # Building shortcuts.
1213
+ @model[:building][:walls ][:lc ] = wallC
1214
+ @model[:building][:roofs ][:lc ] = roofC
1215
+ @model[:building][:floors][:lc ] = floorC
1216
+ @model[:building][:walls ][:indx] = walliL[:index]
1217
+ @model[:building][:roofs ][:indx] = roofiL[:index]
1218
+ @model[:building][:floors][:indx] = flooriL[:index]
1219
+ @model[:building][:walls ][:area] = wM2
1220
+ @model[:building][:roofs ][:area] = rM2
1221
+ @model[:building][:floors][:area] = fM2
1222
+ @model[:building][:walls ][:id ] = wID
1223
+ @model[:building][:roofs ][:id ] = rID
1224
+ @model[:building][:floors][:id ] = fID
1225
+ @model[:building][:walls ][:film] = wFR
1226
+ @model[:building][:roofs ][:film] = rFR
1227
+ @model[:building][:floors][:film] = fFR
1228
+ @model[:building][:walls ][:ut ] = wUt
1229
+ @model[:building][:roofs ][:ut ] = rUt
1230
+ @model[:building][:floors][:ut ] = fUt
1231
+ @model[:building][:walls ][:uo ] = wUo
1232
+ @model[:building][:roofs ][:uo ] = rUo
1233
+ @model[:building][:floors][:uo ] = fUo
1234
+
1235
+ # Repeat for unoccupied, unconditioned spaces (e.g. crawlspaces, attics).
1236
+ attics = []
1237
+
1238
+ model.getSpaces.each do |space|
1239
+ next if space.partofTotalFloorArea
1240
+ next unless TBD.unconditioned?(space)
1241
+
1242
+ attics << space
1243
+ end
1244
+
1245
+ # All attic/crawlspace spaces reference the same default construction set.
1246
+ unless attics.empty?
1247
+ attic = attics.first
1248
+ aset = attic.defaultConstructionSet
1249
+
1250
+ unless aset.empty?
1251
+ aset = aset.get
1252
+
1253
+ if aset == bset
1254
+ lgs << "BUILDING/ATTIC shared default construction set (#{mth})"
1255
+ return false
1256
+ end
1257
+
1258
+ # Attic/crawlspace interzone constructions are insulated.
1259
+ intC = aset.defaultInteriorSurfaceConstructions
1260
+
1261
+ if intC.empty?
1262
+ lgs << "No ATTIC default interior constructions (#{mth})"
1263
+ return false
1264
+ end
1265
+
1266
+ wallC = intC.get.wallConstruction
1267
+ roofC = intC.get.roofCeilingConstruction
1268
+ floorC = intC.get.floorConstruction
1269
+
1270
+ if wallC.empty? || roofC.empty? || floorC.empty?
1271
+ lgs << "Missing ATTIC default exterior constructions (#{mth})"
1272
+ return false
1273
+ end
1274
+
1275
+ wallC = wallC.get.to_LayeredConstruction
1276
+ roofC = roofC.get.to_LayeredConstruction
1277
+ floorC = floorC.get.to_LayeredConstruction
1278
+
1279
+ if wallC.empty? || roofC.empty? || floorC.empty?
1280
+ lgs << "ATTIC default interior layered constructions (#{mth})"
1281
+ return false
1282
+ end
1283
+
1284
+ wallC = wallC.get
1285
+ roofC = roofC.get
1286
+ floorC = floorC.get
1287
+ walliL = TBD.insulatingLayer(wallC)
1288
+ roofiL = TBD.insulatingLayer(roofC)
1289
+ flooriL = TBD.insulatingLayer(floorC)
1290
+
1291
+ if walliL[:index].nil? || roofiL[:index].nil? || flooriL.nil?
1292
+ lgs << "Missing ATTIC insulating layer(s) (#{mth})"
1293
+ return false
1294
+ end
1295
+
1296
+ # Previously-assigned BTAP-costed surface air film resistances?
1297
+ wFR = prop(wallC, "btap_film", Float)
1298
+ rFR = prop(roofC, "btap_film", Float)
1299
+ fFR = prop(floorC, "btap_film", Float)
1300
+ wFR = 0 unless wFR
1301
+ rFR = 0 unless rFR
1302
+ fFR = 0 unless fFR
1303
+
1304
+ # Previously-assigned construction net area?
1305
+ wM2 = prop(wallC, "btap_area", Float)
1306
+ rM2 = prop(roofC, "btap_area", Float)
1307
+ fM2 = prop(floorC, "btap_area", Float)
1308
+ wM2 = wallC.getNetArea unless wM2
1309
+ rM2 = roofC.getNetArea unless rM2
1310
+ fM2 = floorC.getNetArea unless fM2
1311
+
1312
+ # Previously-assigned BTAP-costed wall construction ID?
1313
+ wID = prop(wallC, "btap_id", String)
1314
+ rID = prop(roofC, "btap_id", String)
1315
+ fID = prop(floorC, "btap_id", String)
1316
+ wID = "" unless wID
1317
+ rID = "" unless rID
1318
+ fID = "" unless fID
1319
+ wID = "" unless data.include?(wID)
1320
+ rID = "" unless data.include?(rID)
1321
+ fID = "" unless data.include?(fID)
1322
+
1323
+ # Fallback: BTAP-costed wall construction from STRUCTURE parameters.
1324
+ if wID.empty?
1325
+ opts = {}
1326
+ opts[:framing ] = stc.framing
1327
+ opts[:cladding] = stc.cladding
1328
+ opts[:finish ] = stc.finish
1329
+ opts[:perform ] = :lp
1330
+ opts[:stype ] = :walls
1331
+ opts[:uo ] = wUo
1332
+ wID = BTAP::Bridging.costed_assembly(argh)
1333
+ end
1334
+
1335
+ # Fallback: BTAP-costed roof construction from STRUCTURE parameters.
1336
+ if rID.empty?
1337
+ opts = {}
1338
+ opts[:framing ] = stc.framing
1339
+ opts[:cladding] = stc.cladding
1340
+ opts[:finish ] = stc.finish
1341
+ opts[:perform ] = :lp
1342
+ opts[:stype ] = :floors # not roofs:
1343
+ opts[:uo ] = fUo # not rUo
1344
+ rID = BTAP::Bridging.costed_assembly(argh)
1345
+ end
1346
+
1347
+ # Fallback: BTAP-costed floor construction from STRUCTURE parameters.
1348
+ if fID.empty?
1349
+ opts = {}
1350
+ opts[:framing ] = stc.framing
1351
+ opts[:cladding] = stc.cladding
1352
+ opts[:finish ] = stc.finish
1353
+ opts[:perform ] = :lp
1354
+ opts[:stype ] = :roofs # not floors
1355
+ opts[:uo ] = rUo # not fUo
1356
+ fID = BTAP::Bridging.costed_assembly(argh)
1357
+ end
1358
+
1359
+ # Re/set AdditionalProperties of ATTIC interior constructions.
1360
+ addprop(wallC, "btap_ut", wUt)
1361
+ addprop(wallC, "btap_uo", wUo)
1362
+ addprop(wallC, "btap_id", wID)
1363
+ addprop(wallC, "btap_area", wM2)
1364
+ addprop(wallC, "btap_film", wFR)
1365
+ addprop(roofC, "btap_ut", fUt) # not roof Ut
1366
+ addprop(roofC, "btap_uo", fUo) # not roof Uo
1367
+ addprop(roofC, "btap_id", rID)
1368
+ addprop(roofC, "btap_area", rM2)
1369
+ addprop(roofC, "btap_film", rFR)
1370
+ addprop(floorC, "btap_ut", rUt) # not floor Ut
1371
+ addprop(floorC, "btap_uo", rUo) # not floor Uo
1372
+ addprop(floorC, "btap_id", fID)
1373
+ addprop(floorC, "btap_area", fM2)
1374
+ addprop(floorC, "btap_film", fFR)
1375
+
1376
+ # Add interior wall parameters to attic spaces.
1377
+ attics.each do |att|
1378
+ addprop(att, "btap_ut", wUt)
1379
+ addprop(att, "btap_uo", wUo)
1380
+ addprop(att, "btap_id", wID)
1381
+ addprop(att, "btap_area", wM2)
1382
+ addprop(att, "btap_film", wFR)
1383
+ end
1384
+
1385
+ # Shortcuts.
1386
+ @model[:attic][:walls ][:lc ] = wallC
1387
+ @model[:attic][:roofs ][:lc ] = roofC
1388
+ @model[:attic][:floors][:lc ] = floorC
1389
+ @model[:attic][:walls ][:indx] = walliL[:index]
1390
+ @model[:attic][:roofs ][:indx] = roofiL[:index]
1391
+ @model[:attic][:floors][:indx] = flooriL[:index]
1392
+ @model[:attic][:walls ][:area] = wM2
1393
+ @model[:attic][:roofs ][:area] = rM2
1394
+ @model[:attic][:floors][:area] = fM2
1395
+ @model[:attic][:walls ][:id ] = wID
1396
+ @model[:attic][:roofs ][:id ] = rID
1397
+ @model[:attic][:floors][:id ] = fID
1398
+ @model[:attic][:walls ][:film] = wFR
1399
+ @model[:attic][:roofs ][:film] = rFR
1400
+ @model[:attic][:floors][:film] = fFR
1401
+ @model[:attic][:walls ][:ut ] = wUt
1402
+ @model[:attic][:roofs ][:ut ] = fUt
1403
+ @model[:attic][:floors][:ut ] = rUt
1404
+ @model[:attic][:walls ][:uo ] = wUo
1405
+ @model[:attic][:roofs ][:uo ] = fUo
1406
+ @model[:attic][:floors][:uo ] = rUo
1407
+ end
1408
+ end
1409
+
1410
+ # Repeat for customized spaces.
1411
+ stc.spaces.each do |id, sp|
1412
+ space = model.getSpaceByName(id)
1413
+ next if space.empty?
1414
+
1415
+ space = space.get
1416
+ sset = space.defaultConstructionSet
1417
+ next if sset.empty?
1418
+
1419
+ sset = sset.get
1420
+ next if sset == bset
1421
+
1422
+ extC = sset.defaultExteriorSurfaceConstructions
1423
+ next if extC.empty?
1424
+
1425
+ wallC = extC.get.wallConstruction
1426
+ roofC = extC.get.roofCeilingConstruction
1427
+ floorC = extC.get.floorConstruction
1428
+ next if wallC.empty?
1429
+ next if roofC.empty?
1430
+ next if floorC.empty?
1431
+
1432
+ wallC = wallC.get.to_LayeredConstruction
1433
+ roofC = roofC.get.to_LayeredConstruction
1434
+ floorC = floorC.get.to_LayeredConstruction
1435
+ next if wallC.empty?
1436
+ next if roofC.empty?
1437
+ next if floorC.empty?
1438
+
1439
+ wallC = wallC.get
1440
+ roofC = roofC.get
1441
+ floorC = floorC.get
1442
+ walliL = TBD.insulatingLayer(wallC)
1443
+ roofiL = TBD.insulatingLayer(roofC)
1444
+ flooriL = TBD.insulatingLayer(floorC)
1445
+ next if walliL[:index].nil?
1446
+ next if roofiL[:index].nil?
1447
+ next if flooriL[:index].nil?
1448
+
1449
+ # Previously-assigned BTAP-costed surface air film resistances?
1450
+ wFR = prop(wallC, "btap_film", Float)
1451
+ rFR = prop(roofC, "btap_film", Float)
1452
+ fFR = prop(floorC, "btap_film", Float)
1453
+ wFR = 0 unless wFR
1454
+ rFR = 0 unless rFR
1455
+ fFR = 0 unless fFR
1456
+
1457
+ # Previously-assigned construction net area?
1458
+ wM2 = prop(wallC, "btap_area", Float)
1459
+ rM2 = prop(roofC, "btap_area", Float)
1460
+ fM2 = prop(floorC, "btap_area", Float)
1461
+ wM2 = wallC.getNetArea unless wM2
1462
+ rM2 = roofC.getNetArea unless rM2
1463
+ fM2 = floorC.getNetArea unless fM2
1464
+
1465
+ # Previously-assigned BTAP-costed construction IDs?
1466
+ wID = prop(wallC, "btap_id", String)
1467
+ rID = prop(roofC, "btap_id", String)
1468
+ fID = prop(floorC, "btap_id", String)
1469
+ wID = "" unless wID
1470
+ rID = "" unless rID
1471
+ fID = "" unless fID
1472
+ wID = "" unless data.include?(wID)
1473
+ rID = "" unless data.include?(rID)
1474
+ fID = "" unless data.include?(fID)
1475
+
1476
+ # Fallback: BTAP-costed wall construction from STRUCTURE parameters.
1477
+ if wID.empty?
1478
+ opts = {}
1479
+ opts[:framing ] = sp[:framing]
1480
+ opts[:cladding] = sp[:cladding]
1481
+ opts[:finish ] = sp[:finish]
1482
+ opts[:perform ] = :lp
1483
+ opts[:stype ] = :walls
1484
+ opts[:uo ] = wUo
1485
+ wID = BTAP::Bridging.costed_assembly(argh)
1486
+ end
1487
+
1488
+ # Fallback: BTAP-costed roof construction from STRUCTURE parameters.
1489
+ if rID.empty?
1490
+ opts = {}
1491
+ opts[:framing ] = sp[:framing]
1492
+ opts[:cladding] = sp[:cladding]
1493
+ opts[:finish ] = sp[:finish]
1494
+ opts[:perform ] = :lp
1495
+ opts[:stype ] = :roofs
1496
+ opts[:uo ] = rUo
1497
+ rID = BTAP::Bridging.costed_assembly(argh)
1498
+ end
1499
+
1500
+ # Fallback: BTAP-costed floor construction from STRUCTURE parameters.
1501
+ if fID.empty?
1502
+ opts = {}
1503
+ opts[:framing ] = sp[:framing]
1504
+ opts[:cladding] = sp[:cladding]
1505
+ opts[:finish ] = sp[:finish]
1506
+ opts[:perform ] = :lp
1507
+ opts[:stype ] = :floors
1508
+ opts[:uo ] = fUo
1509
+ fID = BTAP::Bridging.costed_assembly(argh)
1510
+ end
1511
+
1512
+ # Re/set AdditionalProperties to space exterior constructions.
1513
+ addprop(space, "btap_ut", wUt)
1514
+ addprop(space, "btap_uo", wUo)
1515
+ addprop(space, "btap_id", wID)
1516
+ addprop(space, "btap_area", wM2)
1517
+ addprop(space, "btap_film", wFR)
1518
+ addprop(wallC, "btap_ut", wUt)
1519
+ addprop(wallC, "btap_uo", wUo)
1520
+ addprop(wallC, "btap_id", wID)
1521
+ addprop(wallC, "btap_area", wM2)
1522
+ addprop(wallC, "btap_film", wFR)
1523
+ addprop(roofC, "btap_ut", rUt)
1524
+ addprop(roofC, "btap_uo", rUo)
1525
+ addprop(roofC, "btap_id", rID)
1526
+ addprop(roofC, "btap_area", rM2)
1527
+ addprop(roofC, "btap_film", rFR)
1528
+ addprop(floorC, "btap_ut", fUt)
1529
+ addprop(floorC, "btap_uo", fUo)
1530
+ addprop(floorC, "btap_id", fID)
1531
+ addprop(floorC, "btap_area", fM2)
1532
+ addprop(floorC, "btap_film", fFR)
1533
+
1534
+ @model[:spaces][id] = {walls: {}, roofs: {}, floors: {}}
1535
+
1536
+ # Custom space shortcuts.
1537
+ @model[:spaces][id][:walls ][:lc ] = wallC
1538
+ @model[:spaces][id][:roofs ][:lc ] = roofC
1539
+ @model[:spaces][id][:floors][:lc ] = floorC
1540
+ @model[:spaces][id][:walls ][:indx] = walliL[:index]
1541
+ @model[:spaces][id][:roofs ][:indx] = roofiL[:index]
1542
+ @model[:spaces][id][:floors][:indx] = flooriL[:index]
1543
+ @model[:spaces][id][:walls ][:area] = wM2
1544
+ @model[:spaces][id][:roofs ][:area] = rM2
1545
+ @model[:spaces][id][:floors][:area] = fM2
1546
+ @model[:spaces][id][:walls ][:id ] = wID
1547
+ @model[:spaces][id][:roofs ][:id ] = rID
1548
+ @model[:spaces][id][:floors][:id ] = fID
1549
+ @model[:spaces][id][:walls ][:film] = wFR
1550
+ @model[:spaces][id][:roofs ][:film] = rFR
1551
+ @model[:spaces][id][:floors][:film] = fFR
1552
+ @model[:spaces][id][:walls ][:ut ] = wUt
1553
+ @model[:spaces][id][:roofs ][:ut ] = rUt
1554
+ @model[:spaces][id][:floors][:ut ] = fUt
1555
+ @model[:spaces][id][:walls ][:uo ] = wUo
1556
+ @model[:spaces][id][:roofs ][:uo ] = rUo
1557
+ @model[:spaces][id][:floors][:uo ] = fUo
1558
+ end
1559
+
1560
+ # Dry TBD run on a clone of the OpenStudio model.
836
1561
  mdl = OpenStudio::Model::Model.new
837
1562
  mdl.addObjects(model.toIdfFile.objects)
1563
+
1564
+ args = {option: "", io_path: inputs( @model[:quality] )}
838
1565
  TBD.clean!
839
- res = TBD.process(mdl, args)
1566
+ TBD.process(mdl, args)
840
1567
 
841
- # TBD validation of OpenStudio model.
842
1568
  if TBD.fatal? || TBD.error?
843
1569
  lgs << "TBD-identified FATAL error(s):" if TBD.fatal?
844
1570
  lgs << "TBD-identified non-FATAL error(s):" if TBD.error?
845
-
846
1571
  TBD.logs.each { |log| lgs << log[:message] }
847
1572
  return false if TBD.fatal?
848
1573
  end
849
1574
 
850
- # Fetch number of stories in OpenStudio model.
851
- stories = model.getBuilding.standardsNumberOfAboveGroundStories
852
- stories = stories.get unless stories.empty?
853
- stories = model.getBuildingStorys.size unless stories.is_a?(Integer)
854
-
855
- # Story/space construction/PSI customization is yet to be implemented.
856
- # Keeping placeholders for now - @todo.
857
- @model[:stories] = stories.clamp(1, 999)
858
- @model[:spaces ] = {}
859
-
860
- # Initialize deratable opaque, layered constructions & surface types.
861
- @model[:constructions] = {}
862
- @model[:stypes ] = []
863
-
864
- if res[:surfaces].nil?
1575
+ if args[:surfaces].nil? || args[:surfaces].empty?
865
1576
  lgs << "No deratable surfaces in model (#{mth})"
866
1577
  return false
867
1578
  end
868
1579
 
869
- # TBD surface objects hold certain attributes (keys) to signal if they're
870
- # deratable. Concentrating only on those. Relying on reported strings
871
- # (e.g. surface identifier) or integers (e.g. a layer :index) seems fine.
872
- # Yet referecing TBD-cloned OpenStudio objects (e.g. key :construction)
873
- # is a no-no (e.g. seg faults).
874
- res[:surfaces].each do |id, surface|
1580
+ if args[:io][:edges].empty?
1581
+ lgs << "No deratable linear thermal bridge edges in model (#{mth})"
1582
+ return false
1583
+ end
1584
+
1585
+ # Process surfaces deemed 'deratable' by TBD.
1586
+ args[:surfaces].each do |identifier, surface|
875
1587
  next unless surface.key?(:type) # :wall, :ceiling or :floor
876
- next unless surface.key?(:filmRSI) # sum of air film resistances
1588
+ next unless surface.key?(:net) # surface net area
877
1589
  next unless surface.key?(:index) # deratable layer index
878
1590
  next unless surface.key?(:r) # deratable layer RSi
879
1591
  next unless surface.key?(:deratable) # true or false
880
-
881
1592
  next unless surface[:deratable]
882
- next unless surface[:index ]
1593
+ next unless surface[:index]
883
1594
 
884
1595
  stypes = case surface[:type]
885
1596
  when :wall then :walls
886
1597
  when :floor then :floors
887
1598
  when :ceiling then :roofs
888
- else ""
1599
+ else next
889
1600
  end
890
1601
 
891
- next if stypes.empty?
1602
+ # Track deratable surface types, e.g. any exposed :floors?
1603
+ unless @model[:stypes].include?(stypes)
1604
+ @model[:stypes][stypes] = {uo: argh[stypes][:uo]}
892
1605
 
893
- # Track surface type.
894
- @model[:stypes] << stypes unless @model[:stypes].include?(stypes)
895
-
896
- # Track TBD-targeted constructions for uprating/derating.
897
- srf = model.getSurfaceByName(id)
898
-
899
- if srf.empty?
900
- lgs << "Mismatched surface: #{id} (#{mth})?"
901
- return false
1606
+ if argh[stypes].key?(:ut)
1607
+ @model[:stypes][stypes][:ut] = argh[stypes][:ut]
1608
+ else
1609
+ @model[:stypes][stypes][:ut] = argh[stypes][:uo]
1610
+ end
902
1611
  end
1612
+ end
903
1613
 
904
- srf = srf.get
905
- space = srf.space
1614
+ true
1615
+ end
906
1616
 
907
- if space.empty?
908
- lgs << "Missing space: #{id} (#{mth})?"
909
- return false
910
- end
1617
+ ##
1618
+ # Assigns a BTAP attribute (as AdditionalProperty) to an OpenStudio Model
1619
+ # object, e.g. a building, one or more spaces, layered constructions.
1620
+ #
1621
+ # @param obj [-] one or more OpenStudio::Model objects
1622
+ # @param tag [#to_sym] attribute identifier
1623
+ # @param prp [#to_sym or Numeric] attribute (or property)
1624
+ #
1625
+ # @return [Boolean] true if successful
1626
+ def addprop(obj = nil, tag = "", prp = "")
1627
+ return false unless tag.respond_to?(:to_sym)
911
1628
 
912
- space = space.get
913
- lc = srf.construction
1629
+ # 1x OpenStudio::Model object, or multiple similar objects?
1630
+ obj = obj.respond_to?(:to_a) ? obj.to_a : [obj]
1631
+ return false unless obj.first.respond_to?(:model)
914
1632
 
915
- if lc.empty?
916
- lgs << "Mismatched construction: #{id} (#{mth})?"
917
- return false
918
- end
1633
+ cls = obj.first.class
1634
+ tag = tag.to_s
1635
+ return false if tag.empty?
919
1636
 
920
- lc = lc.get.to_LayeredConstruction
1637
+ obj.each { |ob| return false unless ob.is_a?(cls) }
921
1638
 
922
- if lc.empty?
923
- lgs << "Mismatched layered construction: #{id} (#{mth})?"
924
- return false
925
- end
1639
+ if prp.respond_to?(:to_sym)
1640
+ prp = prp.to_s
1641
+ return false if prp.empty?
1642
+ else
1643
+ return false unless prp.is_a?(Numeric)
1644
+ end
926
1645
 
927
- lc = lc.get
928
-
929
- unless @model[:constructions].key?(lc)
930
- @model[:constructions][lc] = {}
931
- @model[:constructions][lc][:index ] = surface[:index] # material
932
- @model[:constructions][lc][:r ] = surface[:r] # material
933
- @model[:constructions][lc][:filmRSI ] = surface[:filmRSI] # assembly
934
- @model[:constructions][lc][:uo ] = nil # assembly
935
- @model[:constructions][lc][:compliant] = nil # assembly
936
- @model[:constructions][lc][:stypes ] = []
937
- @model[:constructions][lc][:surfaces ] = []
938
- @model[:constructions][lc][:spaces ] = []
939
-
940
- # Generate TBD input hashes for both :good & :bad PSI factor sets.
941
- # This depends solely on assigned wall constructions (e.g. steel- vs
942
- # wood-framed) - not roof or floor constructions. Until space- and
943
- # storey-specific structure/construction customization is enabled in
944
- # BTAP, this is set for the entire building. In other words - for now
945
- # - there should be a single assigned layered construction for all
946
- # walls in a BTAP-altered OpenStudio model.
947
- if stypes == :walls
948
- @model[:constructions][lc][:lp_bad ] = self.inputs(strc, :lp, :bad )
949
- @model[:constructions][lc][:lp_good] = self.inputs(strc, :lp, :good)
950
- @model[:constructions][lc][:hp_bad ] = self.inputs(strc, :hp, :bad )
951
- @model[:constructions][lc][:hp_good] = self.inputs(strc, :hp, :good)
952
- end
953
- end
1646
+ obj.each { |ob| ob.additionalProperties.setFeature(tag, prp) }
954
1647
 
955
- # Select lowest applicable air film resistances (given surface slope).
956
- film = [@model[:constructions][lc][:filmRSI], surface[:filmRSI]].min
1648
+ true
1649
+ end
957
1650
 
958
- @model[:constructions][lc][:filmRSI ] = film
959
- @model[:constructions][lc][:stypes ] << stypes # 1x
960
- @model[:constructions][lc][:surfaces] << id # many
961
- @model[:constructions][lc][:spaces ] << space.nameString # less
1651
+ ##
1652
+ # Fetches a BTAP attribute (as AdditionalProperty), from an OpenStudio Model
1653
+ # object, e.g. a building, a space, a layered construction.
1654
+ #
1655
+ # @param obj [#model] an OpenStudio::Model object
1656
+ # @param tag [#to_sym] attribute tag
1657
+ # @param typ [-] attribute type, e.g. String, Symbol, Integer
1658
+ #
1659
+ # @return [String or Float] attribute value (nil if invalid input)
1660
+ def prop(obj = nil, tag = "", typ = nil)
1661
+ return nil unless obj.respond_to?(:model)
1662
+ return nil unless tag.respond_to?(:to_sym)
1663
+
1664
+ tag = tag.to_s
1665
+ return nil if tag.empty?
1666
+
1667
+ if typ == Integer
1668
+ prp = obj.additionalProperties.getFeatureAsInteger(tag)
1669
+ elsif typ <= Numeric
1670
+ prp = obj.additionalProperties.getFeatureAsDouble(tag)
1671
+ else
1672
+ prp = obj.additionalProperties.getFeatureAsString(tag)
962
1673
  end
963
1674
 
964
- # Loop through all tracked deratable constructions. Ensure a single
965
- # surface type per construction. Ensure at least one wall construction.
966
- @model[:constructions].values.each { |v| v[:stypes].uniq! }
967
- nb = 0
1675
+ prp.empty? ? nil : prp.get
1676
+ end
968
1677
 
969
- @model[:constructions].each do |lc, v|
970
- if v[:stypes].size != 1
971
- lgs << "Multiple surface types per construction (#{mth})?"
972
- return false
973
- else
974
- v[:stypes] = v[:stypes].first
1678
+ ##
1679
+ # Generates TBD input hash. Relies on OpenStudio model, BTAP::Structure
1680
+ # attributes and additional parameters set at initialization.
1681
+ #
1682
+ # @param quality [:good, :bad] thermal bridging PSI set quality
1683
+ #
1684
+ # @return [Hash] TBD inputs (empty if invalid input)
1685
+ def inputs(quality = :good)
1686
+ argh = @model[:argh]
1687
+ mdl = @model[:osm]
975
1688
 
976
- # Assign construction for each deratable surface.
977
- v[:surfaces].each do |id|
978
- surface = model.getSurfaceByName(id)
979
- next if surface.empty?
1689
+ # PSI set quality?
1690
+ quality = :good unless quality == :bad
980
1691
 
981
- surface.get.setConstruction(lc)
982
- end
983
- end
1692
+ # Initialize TBD input hash.
1693
+ input = {}
1694
+ psis = {} # construction-specific PSI sets
1695
+ spaces = {} # custom space-specific PSI references
984
1696
 
985
- nb += 1 if v[:stypes] == :walls
986
- end
1697
+ # A single, default PSI-factor set for the building.
1698
+ bldg = @model[:building]
1699
+ bID = bldg[:walls][:id]
1700
+ bPSI = set(bID, quality)
1701
+ psis[ bPSI[:id] ] = bPSI
987
1702
 
988
- if nb < 1
989
- lgs << "No deratable walls (#{mth})?"
990
- return false
991
- end
1703
+ # Repeat for customized spaces, e.g.
1704
+ # - "cmu" gymnasium walls in an otherwise "steel" post/frame school
1705
+ @model[:spaces].each do |id, sp|
1706
+ cID = sp[:walls ][:id]
1707
+ next if cID == bID
992
1708
 
993
- @model[:osm] = model
1709
+ # Fetch BTAP PSI factor set identifier, e.g. STEL1_GOOD
1710
+ cPSI = set(cID, quality)
1711
+ next if psis.key?( cPSI[:id] )
994
1712
 
995
- true
996
- end
1713
+ # Append customized PSI-factor set.
1714
+ psis[ cPSI[:id] ] = cPSI
997
1715
 
998
- ##
999
- # Generate TBD input hash.
1000
- #
1001
- # @param structure [BTAP::Structure] a BTAP STRUCTURE object
1002
- # @param perform [Symbol] :lp or :hp wall variant
1003
- # @param quality [Symbol] :bad or :good PSI-factor
1004
- #
1005
- # @return [Hash] TBD inputs
1006
- def inputs(structure = nil, perform = :hp, quality = :good)
1007
- input = {}
1008
- psis = {} # construction-specific PSI sets
1009
- perform = :hp unless [:lp, :hp].include?(perform)
1010
- quality = :good unless [:bad, :good].include?(quality)
1011
-
1012
- # A single PSI set for the entire building, based strictly on exterior
1013
- # wall selection. Adapt once BTAP::Structure supports STRUCTURE
1014
- # assignements per OpenStudio's building-to-space hierarchy, e.g. "cmu"
1015
- # gymnasium walls in an otherwise "steel"post/frame school. @todo
1016
- assembly = self.costed_assembly(structure, :walls, perform)
1017
- building_psi = self.set(assembly, quality)
1018
-
1019
- psis[ building_psi[:id] ] = building_psi
1716
+ # Add reference to customized space PSI-factor set.
1717
+ spaces[id] = { id: id, psi: cPSI[:id] }
1718
+ end
1020
1719
 
1021
1720
  # TBD JSON schema added as a reminder. No schema validation in BTAP.
1022
1721
  schema = "https://github.com/rd2/tbd/blob/master/tbd.schema.json"
1023
1722
 
1024
1723
  input[:schema ] = schema
1025
- input[:description] = "TBD input for BTAP" # append run # ?
1026
- input[:psis ] = psis.values # maybe more than 1 in future
1027
- input[:building ] = { psi: building_psi[:id] }
1724
+ input[:description] = "TBD input for BTAP"
1725
+ input[:psis ] = psis.values
1726
+ input[:spaces ] = spaces.values unless spaces.empty?
1727
+ input[:building ] = { psi: bPSI[:id] }
1028
1728
 
1029
1729
  input
1030
1730
  end
1031
1731
 
1032
1732
  ##
1033
- # Generate BTAP/TBD tallies
1733
+ # Generate BTAP/TBD tallies, namely for QAQC, costing & embodied carbon.
1034
1734
  #
1035
1735
  # @return [Boolean] true if BTAP/TBD tally is successful
1036
1736
  def gen_tallies
1037
- edges = {}
1038
1737
  return false unless @model.key?(:io)
1039
- return false unless @model.key?(:constructions)
1040
- return false unless @model[:io].key?(:edges)
1738
+ return false unless @model[:io][:edges]
1739
+
1740
+ # Initialize.
1741
+ @tally = {edges: {}}
1742
+ model = @model[:osm]
1743
+ bldg = @model[:osm].getBuilding
1744
+
1745
+ # TBD/BTAP track a wider range of thermal bridge types than BTAP costing
1746
+ # currently supports. Current BTAP-costed, linear thermal bridge types:
1747
+ @model[:bridges] = BTAP::BridgingData.admissible_edges
1041
1748
 
1042
1749
  @model[:io][:edges].each do |e|
1043
- # Content of TBD-generated 'edges' (hashes):
1044
- # psi: BTAP PSI set ID, e.g. "BTAP-ExteriorWall-Mass-6 good"
1045
- # type: thermal bridge type, e.g. :corner
1046
- # length: (in m)
1047
- # surfaces: linked OpenStudio surface IDs
1048
- edges[e[:type]] = {} unless edges.key?(e[:type])
1049
- edges[e[:type]][e[:psi]] = 0 unless edges[e[:type]].key?(e[:psi])
1050
- edges[e[:type]][e[:psi]] += e[:length]
1750
+ pID = e[:psi]
1751
+ next if e[:length] < 0.025
1752
+
1753
+ # Prune away concave/convex suffixes.
1754
+ type = e[:type].to_s.gsub(/concave|convex/, "")
1755
+
1756
+ # Recast linear bridge types: match ~limited costed options.
1757
+ type = case type
1758
+ when /balcony/ then :rimjoist
1759
+ when /roof/ then :parapet
1760
+ when /sky/ then :fenestration
1761
+ when /sill/ then :fenestration
1762
+ when /jamb/ then :fenestration
1763
+ when /head/ then :fenestration
1764
+ when /door/ then ""
1765
+ when /party/ then ""
1766
+ when /joint/ then ""
1767
+ else type.to_sym
1768
+ end
1769
+
1770
+ next unless @model[:bridges].include?(type)
1771
+
1772
+ # Split PSI identifier: construction 'ID' + PSI 'quality'.
1773
+ cID, quality = pID.split(" ")
1774
+
1775
+ # Valid?
1776
+ quality = quality.to_sym
1777
+ next unless data.key?(cID)
1778
+ next unless data[cID].key?(quality)
1779
+ next unless data[cID][quality].key?(type)
1780
+ next unless data[cID][quality][:id] == pID
1781
+
1782
+ # Determine multiplier(s).
1783
+ mSb = 1
1784
+ mSp = 1
1785
+
1786
+ # SubSurface multiplier.
1787
+ e[:surfaces].each do |srf|
1788
+ if type == :fenestration
1789
+ unless model.getSubSurfaceByName(srf).empty?
1790
+ mlt = model.getSubSurfaceByName(srf).get.multiplier
1791
+ mSb = mlt if mlt > mSb
1792
+ end
1793
+ end
1794
+
1795
+ next if model.getSurfaceByName(srf).empty?
1796
+ next if model.getSurfaceByName(srf).get.space.empty?
1797
+
1798
+ # Space multiplier.
1799
+ mlt = model.getSurfaceByName(srf).get.space.get.multiplier
1800
+ mSp = mlt if mlt > mSp
1801
+ end
1802
+
1803
+ @tally[:edges][type] = {} unless @tally[:edges].key?(type)
1804
+ @tally[:edges][type][pID] = 0 unless @tally[:edges][type].key?(pID)
1805
+ @tally[:edges][type][pID] += e[:length] * mSb * mSp
1051
1806
  end
1052
1807
 
1053
- return false if edges.empty?
1808
+ addprop(bldg, "psi_quality", @model[:quality])
1054
1809
 
1055
- @tally[:edges] = edges
1056
- @tally[:constructions] = @model[:constructions]
1810
+ # Add (to a model's building) PSI tallies as AdditionalProperties.
1811
+ @tally[:edges].each do |type, edge|
1812
+ edge.each.with_index(1) do |(pID, m), i|
1813
+ next if m < 0.025
1057
1814
 
1058
- true
1815
+ tag = "PSI" + type.to_s + i.to_s
1816
+ val = "#{pID} #{m.round(2)}"
1817
+ addprop(bldg, tag, val)
1818
+ end
1819
+ end
1820
+
1821
+ # Update other related parameters (as building AdditionalProperties).
1822
+ addprop(bldg, "btap_uo", @model[:building][:walls][:uo])
1823
+ addprop(bldg, "btap_id", @model[:building][:walls][:id])
1824
+
1825
+ # Repeat for attic(s) - if applicable.
1826
+ unless @model[:attic][:walls].empty?
1827
+ model.getSpaces.each do |space|
1828
+ next if space.partofTotalFloorArea
1829
+ next unless TBD.unconditioned?(space)
1830
+
1831
+ addprop(space, "btap_uo", @model[:attic][:walls][:uo])
1832
+ addprop(space, "btap_id", @model[:attic][:walls][:id])
1833
+ end
1834
+ end
1835
+
1836
+ # Repeat for customized spaces.
1837
+ @model[:spaces].each do |id, sp|
1838
+ space = model.getSpaceByName(id)
1839
+ next if space.empty?
1840
+
1841
+ space = space.get
1842
+ addprop(space, "btap_uo", sp[:walls][:uo])
1843
+ addprop(space, "btap_id", sp[:walls][:id])
1844
+ end
1845
+
1846
+ @tally[:edges].empty? ? false : true
1059
1847
  end
1060
1848
 
1061
1849
  ##
@@ -1066,151 +1854,117 @@ module BTAP
1066
1854
  lgs = @feedback[:logs]
1067
1855
  return false unless @model.key?(:complies) # all model constructions
1068
1856
  return false unless @model.key?(:comply) # surface type specific ...
1069
- return false unless @model.key?(:argh) # BTAP/TBD inputs + ouputs
1070
1857
  return false unless @model.key?(:stypes) # :walls, :roofs, :floors
1071
1858
 
1072
- argh = @model[:argh]
1073
-
1074
1859
  # Uprating. Report first on surface types (compliant or not).
1075
- @model[:stypes].each do |stypes|
1860
+ @model[:stypes].each do |stypes, u|
1861
+ next unless @model[:uprating]
1076
1862
  next unless @model[:comply].key?(stypes)
1077
1863
 
1078
- ut = format("%.3f", argh[stypes][:ut])
1079
1864
  lg = @model[:comply][stypes] ? "Compliant " : "Non-compliant "
1080
- lg += "#{stypes}: Ut #{ut} W/m2.K"
1865
+ lg += stypes.to_s
1866
+ lgs << lg
1867
+
1868
+ bUt = format("%.3f", @model[:building][stypes][:ut])
1869
+ bUo = format("%.3f", @model[:building][stypes][:uo])
1870
+ bCP = @model[:building][stypes][:compliant]
1871
+ bID = @model[:building][stypes][:id]
1872
+ lg = " - "
1873
+ lg += bCP ? " Compliant " : " Non-compliant "
1874
+ lg += "BUILDING: Ut #{bUt} W/m2.K "
1875
+ lg += "(Uo #{bUo} W/m2.K, #{bID})"
1876
+ lg += " - #{@model[:quality]}" if stypes == :walls
1081
1877
  lgs << lg
1082
1878
 
1083
- # Report then on required Uo factor per construction (compliant or not).
1084
- @model[:constructions].each do |lc, v|
1085
- next unless v.key?(:stypes)
1086
- next unless v.key?(:uo)
1087
- next unless v.key?(:compliant)
1088
- next unless v.key?(:surfaces)
1089
- next unless v[:stypes ] == stypes
1090
- next if v[:surfaces].empty?
1091
-
1092
- uo = format("%.3f", v[:uo])
1093
- lg = v[:compliant] ? " Compliant " : " Non-compliant "
1094
- lg += "#{lc.nameString} Uo #{uo} (W/K.m2)"
1879
+ unless @model[:attic][stypes].empty?
1880
+ types = case stypes
1881
+ when :roofs then :floors
1882
+ when :floors then :roofs
1883
+ else :walls
1884
+ end
1885
+
1886
+ aUt = format("%.3f", @model[:attic][types][:ut])
1887
+ aUo = format("%.3f", @model[:attic][types][:uo])
1888
+ aCP = @model[:attic][types][:compliant]
1889
+ aID = @model[:attic][types][:id]
1890
+ lg = " - "
1891
+ lg += aCP ? " Compliant " : " Non-compliant "
1892
+ lg += "ATTIC: Ut #{aUt} W/m2.K "
1893
+ lg += "(Uo #{aUo} W/m2.K, #{aID})"
1894
+ lg += " - #{@model[:quality]}" if stypes == :walls
1095
1895
  lgs << lg
1096
1896
  end
1097
- end
1098
1897
 
1099
- # Summary of TBD-derated constructions.
1100
- @model[:osm].getSurfaces.each do |s|
1101
- next if s.construction.empty?
1102
- next if s.construction.get.to_LayeredConstruction.empty?
1103
-
1104
- lc = s.construction.get.to_LayeredConstruction.get
1105
- id = lc.nameString
1106
- next unless id.include?(" c tbd")
1898
+ @model[:spaces].each do |id, sp|
1899
+ sUt = format("%.3f", sp[stypes][:ut])
1900
+ sUo = format("%.3f", sp[stypes][:uo])
1901
+ sCP = sp[stypes][:compliant]
1902
+ sID = sp[stypes][:id]
1903
+ sQY =
1904
+ lg = " - "
1905
+ lg += sCP ? " Compliant " : " Non-compliant "
1906
+ lg += "SPACE #{id}: Ut #{sUt} W/m2.K "
1907
+ lg += "(Uo #{sUo} W/m2.K, #{sID})"
1908
+ lg += " - #{@model[:quality]}" if stypes == :walls
1909
+ lgs << lg
1910
+ end
1911
+ end
1107
1912
 
1108
- rsi = TBD.rsi(lc, s.filmResistance)
1109
- usi = format("%.3f", 1/rsi)
1110
- rsi = format("%.1f", rsi)
1111
- area = format("%.1f", lc.getNetArea) + " m2"
1913
+ # Report on edges (as linear thermal bridges).
1914
+ lgs << "Edges (i.e. linear thermal bridges):"
1112
1915
 
1113
- lgs << "~ '#{id}' derated Rsi: #{rsi} [Usi #{usi} x #{area}]"
1114
- end
1916
+ @tally[:edges].each do |type, edge|
1917
+ edge.each.with_index(1) do |(pID, m), i|
1918
+ next if m < 0.025
1115
1919
 
1116
- # Log PSI factor tallies (per thermal bridge type).
1117
- if @tally.key?(:edges)
1118
- @tally[:edges].each do |type, e|
1119
- next if type == :transition
1920
+ lgs << " #{type}: ".rjust(15) + pID.ljust(37) + " #{m.round(1)} m".rjust(9)
1120
1921
 
1121
- lgs << "# '#{type}' (#{e.size}x):"
1922
+ # LargeOffice (without factoring space/subsurface multipliers):
1923
+ # fenestration: BTAP-ExteriorWall-SteelFramed-2 good 1802.1 m
1924
+ # grade: BTAP-ExteriorWall-SteelFramed-2 good 243.7 m
1925
+ # rimjoist: BTAP-ExteriorWall-SteelFramed-2 good 487.4 m
1926
+ # parapet: BTAP-ExteriorWall-SteelFramed-2 good 243.7 m
1927
+ # corner: BTAP-ExteriorWall-SteelFramed-2 good 47.6 m
1122
1928
 
1123
- e.each do |psi, length|
1124
- l = format("%.2f", length)
1125
- lgs << "... PSI set '#{psi}' : #{l} m"
1126
- end
1929
+ # LargeOffice (with factoring space/subsurface multipliers):
1930
+ # fenestration: BTAP-ExteriorWall-SteelFramed-2 good 6280.0 m
1931
+ # grade: BTAP-ExteriorWall-SteelFramed-2 good 243.7 m
1932
+ # rimjoist: BTAP-ExteriorWall-SteelFramed-2 good 2680.6 m
1933
+ # parapet: BTAP-ExteriorWall-SteelFramed-2 good 243.7 m
1934
+ # corner: BTAP-ExteriorWall-SteelFramed-2 good 190.2 m
1127
1935
  end
1128
1936
  end
1129
1937
 
1130
1938
  true
1131
1939
  end
1132
-
1133
- # def get_material_quantities()
1134
- # material_quantities = {}
1135
- # csv = CSV.read("#{File.dirname(__FILE__)}/../../../data/inventory/thermal_bridging.csv", headers: true)
1136
- # tally_edges = @tally[:edges].transform_keys(&:to_s)
1137
- #
1138
- # tally_edges.each do |edge_type_full, value|
1139
- # edge_type = edge_type_full.delete_suffix('convex')
1140
- # edge_type = 'fenestration' if ['head', 'jamb', 'sill'].include?(edge_type)
1141
- #
1142
- # value.each do |wall_ref_and_quality, quantity|
1143
- # /(.*)\s(.*)/ =~ wall_ref_and_quality
1144
- # wall_reference = $1
1145
- # quality = $2
1146
- #
1147
- # if wall_reference =='BTAP-ExteriorWall-SteelFramed-1'
1148
- # wall_reference = 'BTAP-ExteriorWall-SteelFramed-2'
1149
- # end
1150
- #
1151
- # next if edge_type == 'transition'
1152
- #
1153
- # result = csv.find { |row| row['edge_type'] == edge_type &&
1154
- # row['quality'] == quality &&
1155
- # row['wall_reference'] == wall_reference
1156
- # }
1157
- #
1158
- # if result.nil?
1159
- # puts ("#{edge_type}-#{wall_reference}-#{quality}")
1160
- # puts "not found in tb database"
1161
- # next
1162
- # end
1163
- #
1164
- # # Split
1165
- # material_opaque_id_layers = result['material_opaque_id_layers'].split(",")
1166
- # id_layers_quantity_multipliers = result['id_layers_quantity_multipliers'].split(",")
1167
- #
1168
- # material_opaque_id_layers.zip(id_layers_quantity_multipliers).each do |id, scale|
1169
- # material_quantities[id] = 0.0 if material_quantities[id].nil?
1170
- # material_quantities[id] = material_quantities[id] + scale.to_f * quantity.to_f
1171
- # end
1172
- # end
1173
- # end
1174
- #
1175
- # material_opaque_id_quantities = []
1176
- #
1177
- # material_quantities.each do |id,quantity|
1178
- # material_opaque_id_quantities << { 'materials_opaque_id' => id, 'quantity' => quantity, 'domain'=> 'thermal_bridging' }
1179
- # end
1180
- #
1181
- # return material_opaque_id_quantities
1182
- # end
1183
1940
  end
1184
1941
  end
1185
1942
 
1186
1943
  # ----- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----- #
1187
- # NOTE: BTAP supports Uo variants for each of the aforementioned wall
1944
+ # Note: BTAP supports Uo variants for each of the aforementioned wall
1188
1945
  # constructions, e.g. meeting NECB2011 and NECB2015 prescriptive "Uo"
1189
1946
  # requirements for each NECB climate zone. By definition, these Uo
1190
- # variants ignore the effects of MAJOR thermal bridging, such as
1947
+ # variants ignore the effects of MAJOR linear thermal bridging, such as
1191
1948
  # intermediate slab edges. This does not imply that NECB2011 and NECB2015
1192
- # do not hold prescriptive requirements for MAJOR thermal bridging. There
1193
- # are indeed a handful of general, qualitative requirements (those of the
1194
- # MNECB1997) that would make NECB2011- and NECB2015-compliant buildings
1195
- # slightly better than BTAPPRE1980 "bottom-of-the-barrel" construction,
1196
- # but likely not any better than circa 1990s "run-of-the-mill" commercial
1197
- # construction. Currently, BTAP does not assess the impact of MAJOR
1198
- # thermal bridging for vintages < NECB2017. But ideally it SHOULD, if the
1199
- # goal remains a fair assessment of the (relative) contribution of more
1200
- # recent NECB requirements (e.g. 2020).
1201
-
1202
- # NOTE: The BTAP costing spreadsheet holds entries for curtain wall (CW)
1203
- # spandrel inserts above/below fenestration for certain spacetypes, see:
1204
- #
1205
- # test/necb/unit_tests/resources/btap_spandrels.png
1206
- #
1207
- # This is yet to be implemented. This note is an "aide-mémoire" for future
1949
+ # do not hold prescriptive requirements for MAJOR linear thermal bridging.
1950
+ # There are indeed a handful of general, descriptive requirements (those
1951
+ # of the MNECB1997) that would make NECB2011- and NECB2015-compliant
1952
+ # buildings slightly better than BTAPPRE1980 bottom-of-the-barrel
1953
+ # construction, but likely not any better than circa 1990s run-of-the-mill
1954
+ # commercial construction. Currently, BTAP does not assess the impact of
1955
+ # MAJOR linear thermal bridging for vintages < NECB2017. But ideally it
1956
+ # SHOULD, if the goal remains a fair assessment of the (relative)
1957
+ # contribution of more recent NECB requirements (e.g. 2025).
1958
+
1959
+ # Note: The original BTAP costing database holds entries for curtain wall (CW)
1960
+ # spandrel inserts above/below fenestration for certain spacetypes. This
1961
+ # is yet to be implemented. This note is an "aide-mémoire" for future
1208
1962
  # consideration. The BETBG does not hold any CW glazed spandrels achieving
1209
- # U factors ANYWHERE near NECB requirements, regardless of NECB vintage or
1210
- # NECB climate zone. Same for the Guide to Low Thermal Energy Demand for
1211
- # Large Buildings. The original intention was to rely on BTAP variants
1212
- # "Metal-2" and "Metal-3" as HP CW spandrels ACTUALLY achieving NECB
1213
- # prescriptive targets, which could only be possible in practice at
1963
+ # U-factors ANYWHERE near NECB requirements, regardless of NECB vintage or
1964
+ # NECB climate zone. Same for the 'Guide to Low Thermal Energy Demand for
1965
+ # Large Buildings'. It seems the original intent was to rely on BTAP
1966
+ # variants "Metal-2" and "Metal-3" as HP CW spandrels ACTUALLY achieving
1967
+ # NECB prescriptive targets, which could only be possible in practice at
1214
1968
  # great cost and effort (e.g. a 2nd insulated wall behind the spandrel).
1215
1969
  #
1216
1970
  # If TBD's uprating calculations (e.g. NECB 2017) were in theory no longer
@@ -1219,39 +1973,34 @@ end
1219
1973
  # to be altered. Otherwise, adaptations would be required. PSI factors are
1220
1974
  # noticeably different for spandrels (obviously no lintels/shelf-angles).
1221
1975
  # More importantly, the default assumption with CW technology is that
1222
- # there wouldn't be any additional linear conductances to consider along
1223
- # vision vs spandrel sections (as perimeter heat loss would already have
1224
- # been considered as per NFRC or CSA rating methodologies). On the other
1225
- # hand, vision jambs along non-CW assemblies (i.e. original BTAP
1226
- # intention) most certainly constitute (new) MAJOR thermal bridges to
1976
+ # there wouldn't be any linear thermal bridges along shared vision vs
1977
+ # spandrel edges (as perimeter heat loss would already have been taken
1978
+ # into as consideration, as per NFRC or CSA rating methodologies). On the
1979
+ # other hand, CW vision jambs along non-CW assemblies (i.e. original BTAP
1980
+ # goal) most certainly constitute (new) MAJOR linear thermal bridges to
1227
1981
  # consider (just as with shared edges between spandrels and other wall
1228
1982
  # assemblies). Again, none of these features are currently implemented
1229
1983
  # within BTAP. Recommended (future) solution, if desired:
1230
1984
  #
1231
1985
  # - Automated OSM façade-splitting feature
1232
1986
  # - insert spandrels above/below windows
1233
- # > ~200 lines of Ruby code
1234
1987
  # - simple cases only e.g., vertical, no overlaps, h > 200mm
1235
- # > split above/below plenum walls as well
1236
- # > potentially another 200 lines to catch invalid input
1988
+ # - split above/below plenum walls as well
1237
1989
  #
1238
1990
  # - Further develop PSI sets to cover CWs (see below)
1239
1991
  # - e.g. PSI factors for CW vision "jamb" transitions
1240
1992
  # - e.g. PSI factors for CW spandrel "jamb" transitions
1241
1993
  #
1242
1994
  # These added features would simplify the process tremendously. Yet
1243
- # without admissible CW spandrel U factors down to 0.130 or 0.100 W/m2.K,
1244
- # TBD's uprating features would necessarily push OTHER wall constructions
1245
- # to compensate - noticeably for climate zone 7 (or colder). This would
1246
- # make it MUCH MORE difficult to identify NECB2017 or NECB2020 compliant
1247
- # combinations of Uo+PSI factors if ever HP CW spandrels were integrated
1248
- # within BTAP.
1249
-
1250
- # NOTE: Some of the aforementioned constructions have exterior brick veneer.
1995
+ # without admissible CW spandrel U-factors down to 0.130 or 0.100 W/m2.K,
1996
+ # this would make it pretty much impossible to identify NECB2017 through
1997
+ # NECB2025 compliant combinations of Uo+PSI factors.
1998
+
1999
+ # Note: Some of the aforementioned constructions have exterior brick veneer.
1251
2000
  # For 2-story OpenStudio models with punch windows (i.e. not strip
1252
2001
  # windows), one would NOT expect a continuous steel shelf angle along the
1253
- # intermediate floor slab edge (typically a MAJOR thermal bridge). One
1254
- # would instead expect loose lintels above punch windows, just as with
2002
+ # intermediate floor slab edge (typically a MAJOR linear thermal bridge).
2003
+ # One would instead expect loose lintels above punch windows, just as with
1255
2004
  # doors. Loose lintels usually compound heat loss along window head edges,
1256
2005
  # but are currently considered as factored in the retained PSI factors for
1257
2006
  # window and door head details (a postulate that likely needs revision).
@@ -1268,10 +2017,3 @@ end
1268
2017
  # costing ($/m2), yet should be limited to doors (@todo). A more flexible,
1269
2018
  # general solution would be required for 3rd-party OpenStudio models
1270
2019
  # (without strip windows as a basic fenestration layout).
1271
- #
1272
- # NOTE: BTAP costing: In addition to the listed items for parapets as MAJOR
1273
- # thermal bridges (eventually generating an overall $ per linear meter),
1274
- # BTAP costing requires extending the areas (m2) of OpenStudio wall
1275
- # surfaces (along parapet edges) by 3'-6" (1.1 m) x parapet lengths, to
1276
- # account for the extra cost of completely wrapping the parapet in
1277
- # insulation for "good" (HP) details. See final TBD tally - @todo.