diff --git a/Gemfile.lock.3.7.0 b/Gemfile.lock.3.7.0 index af9a79d350..259e56fb02 100644 --- a/Gemfile.lock.3.7.0 +++ b/Gemfile.lock.3.7.0 @@ -2,7 +2,7 @@ PATH remote: . specs: openstudio-standards (0.6.3) - tbd (~> 3) + tbd (3.4.4) GEM remote: https://rubygems.org/ @@ -73,7 +73,7 @@ GEM openstudio-api-stubs (0.1.0) oslg (0.3.0) osut (0.6.0) - oslg (>= 0.3.0) + oslg (0.3.0) parallel (1.26.3) parallel_tests (3.7.3) parallel @@ -137,10 +137,10 @@ GEM tilt (~> 2.0) yard (~> 0.9, >= 0.9.24) stringio (3.1.2) - tbd (3.4.3) + tbd (3.4.4) json-schema (~> 4) - osut (~> 0) - topolys (~> 0) + osut (0.6.0) + topolys (0.6.2) thor (1.3.2) tilt (2.4.0) topolys (0.6.2) diff --git a/Gemfile.lock.3.8.0 b/Gemfile.lock.3.8.0 index dc3776d303..6efe5caf4b 100644 --- a/Gemfile.lock.3.8.0 +++ b/Gemfile.lock.3.8.0 @@ -2,7 +2,7 @@ PATH remote: . specs: openstudio-standards (0.6.3) - tbd (~> 3) + tbd (3.4.4) GEM remote: https://rubygems.org/ @@ -71,7 +71,7 @@ GEM openstudio-api-stubs (0.1.0) oslg (0.3.0) osut (0.6.0) - oslg (>= 0.3.0) + oslg (0.3.0) parallel (1.26.3) parallel_tests (3.7.3) parallel @@ -135,10 +135,10 @@ GEM tilt (~> 2.0) yard (~> 0.9, >= 0.9.24) stringio (3.1.2) - tbd (3.4.3) + tbd (3.4.4) json-schema (~> 4) - osut (~> 0) - topolys (~> 0) + osut (0.6.0) + topolys (0.6.2) thor (1.3.2) tilt (2.4.0) topolys (0.6.2) diff --git a/Gemfile.lock.3.9.0 b/Gemfile.lock.3.9.0 index 90188f7b86..929f60a2e2 100644 --- a/Gemfile.lock.3.9.0 +++ b/Gemfile.lock.3.9.0 @@ -2,7 +2,7 @@ PATH remote: . specs: openstudio-standards (0.6.3) - tbd (~> 3) + tbd (3.4.4) GEM remote: http://rubygems.org/ @@ -71,7 +71,7 @@ GEM openstudio-api-stubs (0.1.0) oslg (0.3.0) osut (0.6.0) - oslg (>= 0.3.0) + oslg (0.3.0) parallel (1.26.3) parallel_tests (3.7.3) parallel @@ -135,10 +135,10 @@ GEM tilt (~> 2.0) yard (~> 0.9, >= 0.9.24) stringio (3.1.2) - tbd (3.4.3) + tbd (3.4.4) json-schema (~> 4) - osut (~> 0) - topolys (~> 0) + osut (0.6.0) + topolys (0.6.2) thor (1.3.2) tilt (2.4.0) topolys (0.6.2) diff --git a/lib/openstudio-standards/btap/bridging.rb b/lib/openstudio-standards/btap/bridging.rb index 42b6ca28f0..41116e6d50 100644 --- a/lib/openstudio-standards/btap/bridging.rb +++ b/lib/openstudio-standards/btap/bridging.rb @@ -1,5 +1,5 @@ # **************************************************************************** / -# * Copyright (c) 2008-2023, Natural Resources Canada +# * Copyright (c) 2008-2025, Natural Resources Canada # * All rights reserved. # * # * This library is free software; you can redistribute it and/or @@ -35,8 +35,8 @@ module BridgingData # - range of PSI factors (i.e. MAJOR thermal bridging), e.g. corners # - costing parameters # - # NOTE: This module is to be replaced with roo-based spreadsheet parsing, - # generating a BTAP costing JSON file. TO DO. + # NOTE: This module is to be adapted once new BTAP structure/envelope data + # model/classes are in place, including file formats (e.g. CSV, JSON). # # Ref: EVOKE BTAP costing spreadsheet modifications (2022), synced with: # - Building Envelope Thermal Bridging Guide (BETBG) @@ -53,6 +53,10 @@ module BridgingData # "BTAP-ExteriorWall-WoodFramed-1" is unused. BTAP/TBD data is limited # to the following wall constructions (paired LP & HP variants). # + # NOTE: This will soon be revised, largely inferred from building structure + # selection. + # + # # ---- (Basic) Low Performance (LP) assemblies # # ID : (layers) @@ -127,7 +131,7 @@ module BridgingData UMAX = 5.678 # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- # - # There are 3 distinct BTAP "building_envelope" classes to enrich with + # There are 2 distinct BTAP "building_envelope.rb" files to enrich with # TBD functionality (whether BTAP users choose to activate TBD or not): # # 1. BTAPPRE1980 @@ -135,10 +139,10 @@ module BridgingData # 2. NECB2011 # - superclass for NECB2015 # - superclass for NECB2017 (inherits from NECB2015) + # - superclass for NECB2020 (inherits from NECB2017) # - superclass for ECMS - # 3. NECB2020 # - # In all 3 classes, a BTAP/TBD option switch allows BTAP users to activate + # In both files, a BTAP/TBD option switch allows BTAP users to activate # or deactivate TBD functionality : # - "none" : TBD is deactivated, i.e. no up/de-rating # - "bad" or "good": (BTAP-costed) PSI factor sets, i.e. derating only @@ -152,9 +156,9 @@ module BridgingData # compliant, combination. Why? Improved Uo construction variants are # necessarily required, given: # - # Ut = Uo + ( ∑psi L )/A + ( ∑khi n )/A (ref: rd2.github.io/tbd) + # Ut = Uo + ( ∑psi x L )/A + ( ∑khi x n )/A (ref: rd2.github.io/tbd) # - # If one ignores linear ("( ∑psi L )/A") and point ("( ∑khi n )/A") + # If one ignores linear ("( ∑psi x L )/A") and point ("( ∑khi x n )/A") # conductances, Ut simply equates to Uo. Yet for ANY added linear or # point conductance, Uo factors must necessarily be lower than required # NECB2017 or NECB2020 Ut factors. EVOKE's 2022 contribution extends @@ -204,7 +208,7 @@ module BridgingData # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- # # There are 3x exceptions to the aforementioned iterative solution, - # hopefully to correct (TO-DO): + # hopefully to correct (@todo): # # - Steel-framed construction: the selected HP variant has metal # cladding. The only LP steel-framed BTAP option is wood-clad - @@ -225,7 +229,7 @@ module BridgingData # respectively. This is expected to change in the future ... # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- # - # Preset BTAP/TBD wall construction parameters. + # Preset BTAP/TBD wall construction parameters (to be revised, @todo). # :sptypes : BTAP/TBD Hash of linked NECB SpaceTypes (symbols) # :uos : BTAP/TBD Hash of associated of Uo sub-variants # :lp or :hp : low- or high-performance attribute @@ -251,85 +255,84 @@ module BridgingData # # e.g. "314" describes a Uo factor of 0.314 W/m2.K # - # Listed items for each sub-variant are layer identifiers (for BTAP - # costing only). For the moment, they are listed integers (but should - # be expanded - e.g. as Hash keys - to hold additional costing metadata, - # e.g. $/m2). This should be (soon) removed from BTAP/TBD data. - # - # NOTE: Missing gypsum finish for WOOD7 Uo 0.130? - - @@data[MASS2][:uos]["314"] = [ 24, 25, 26, 27, 28,134, 20, 21,139,141 ] - @@data[MASS2][:uos]["278"] = [ 24, 25, 26, 27, 28, 42, 20, 21,139,141 ] - @@data[MASS2][:uos]["247"] = [ 24, 25, 26, 27, 28, 58, 20, 21,139,141 ] - @@data[MASS2][:uos]["210"] = [ 24, 25, 26, 27, 28, 55, 20, 21,139,141 ] - @@data[MASS2][:uos]["183"] = [ 24, 25, 26, 27, 28, 68, 20, 21,139,141 ] - @@data[MASSB][:uos]["130"] = [ 1, 11, 24,160,164,179,141 ] - @@data[MASSB][:uos]["100"] = [ 1, 11, 24,160,165,179,141 ] - - @@data[MASS4][:uos]["314"] = [ 1, 11, 43, 6, 92, 41 ] - @@data[MASS4][:uos]["278"] = [ 1, 11, 69, 6, 41,150 ] - @@data[MASS4][:uos]["247"] = [ 1, 11, 43, 6, 58, 41 ] - @@data[MASS4][:uos]["210"] = [ 1, 11, 43, 6,134, 41 ] - @@data[MASS4][:uos]["183"] = [ 1, 11, 49, 80, 41 ] - @@data[MASS8][:uos]["130"] = [ 1, 11,168,195 ] - @@data[MASS8][:uos]["100"] = [ 1, 11,168,195 ] - - @@data[MASS6][:uos]["314"] = [ 24, 25, 26, 27, 28,134, 20, 21,139,141 ] - @@data[MASS6][:uos]["278"] = [ 24, 25, 26, 27, 28, 42, 20, 21,139,141 ] - @@data[MASS6][:uos]["247"] = [ 24, 25, 26, 27, 28, 58, 20, 21,139,141 ] - @@data[MASSC][:uos]["210"] = [ 1, 11,160, 24, 25, 26, 27, 28,172,181,162,196,180,141 ] - @@data[MASSC][:uos]["183"] = [ 1, 11,160, 24, 25, 26, 27, 28,172,182,163,196,180,141 ] - @@data[MASSC][:uos]["130"] = [ 1, 11,160, 24, 25, 26, 27, 28,172,185,165,196,180,141 ] - @@data[MASSC][:uos]["100"] = [ 1, 11,160, 24, 25, 26, 27, 28,172,186,163,165,196,180,141] - @@data[MASSC][:uos]["080"] = [ 1, 11,160, 24, 25, 26, 27, 28,172,188,165,165,196,180,141] - - @@data[MTAL1][:uos]["314"] = [ 1, 11, 43, 6, 56,150, 48 ] - @@data[MTAL1][:uos]["278"] = [ 1, 11, 43, 6, 48, 55 ] - @@data[MTAL1][:uos]["247"] = [ 1, 11, 43, 56, 6, 48, 59 ] - @@data[MTAL1][:uos]["210"] = [ 1, 11, 43, 63, 6, 48, 59 ] - @@data[MTAL1][:uos]["183"] = [ 1, 11, 43, 58, 6, 48, 59 ] - @@data[MTALD][:uos]["130"] = [ 11,160,204,203,205,204,174,173,180, 1 ] - @@data[MTALD][:uos]["100"] = [ 11,160,204,203,205,204,174,174,180, 1 ] - - @@data[WOOD5][:uos]["314"] = [138, 3, 43, 5, 6,153, 20, 21,139,141, 1] - @@data[WOOD5][:uos]["278"] = [138, 3, 53, 56, 5, 6, 20, 21,139,141, 1] - @@data[WOOD5][:uos]["247"] = [138, 3, 4, 5, 56, 6, 20, 21,139,141, 1] - @@data[WOOD5][:uos]["210"] = [138, 3, 53, 5, 56, 6, 20, 21,139,141, 1] - @@data[WOOD5][:uos]["183"] = [138, 3, 53, 5, 67, 6, 20, 21,139,141, 1] - @@data[WOOD7][:uos]["130"] = [138,160, 56,163,197, 20, 21,139,141, 1 ] # < added '1' for gypsum finish - - @@data[STEL1][:uos]["314"] = [ 11, 3, 43,153, 6, 7,141, 9, 10, 1 ] - @@data[STEL1][:uos]["278"] = [ 11, 3, 53, 5, 56, 6, 7,141, 9, 10, 1 ] - @@data[STEL2][:uos]["247"] = [ 11, 3, 53, 5, 63, 6, 7,141, 9, 10, 1 ] - @@data[STEL2][:uos]["210"] = [ 11, 3, 53, 5, 67, 6, 7,141, 9, 10, 1 ] - @@data[STEL2][:uos]["183"] = [ 11, 3, 53, 5, 56, 67, 6, 7,141, 9, 10, 1] - @@data[STEL2][:uos]["130"] = [ 11, 3, 43,171,172,164,163,186,196,180,141, 1] - @@data[STEL2][:uos]["100"] = [ 11, 3, 43,171,172,165,163,187,197,180,141, 1] - @@data[STEL2][:uos]["080"] = [ 11, 3, 43,171,172,165,165,188,197,180,141, 1] - - @@data[FLOOR][:uos]["227"] = [117,145,118, 3, 99, 6,119 ] - @@data[FLOOR][:uos]["183"] = [117,145,118, 3, 99, 56, 6,119] - @@data[FLOOR][:uos]["162"] = [117,145,118, 3, 99, 67, 6,119] - @@data[FLOOR][:uos]["142"] = [117,145,118, 3, 68, 56, 6,119] - @@data[FLOOR][:uos]["116"] = [117,145,118, 3,157, 6,157, 6] - @@data[FLOOR][:uos]["101"] = [117,145,118, 3,157,158, 6,119] - - @@data[ROOFS][:uos]["227"] = [ 94, 97, 71, 92, 93] - @@data[ROOFS][:uos]["193"] = [ 94, 97, 80, 80, 93] - @@data[ROOFS][:uos]["183"] = [ 94, 97,134,134, 93] - @@data[ROOFS][:uos]["162"] = [ 94, 97,102,153, 93] - @@data[ROOFS][:uos]["156"] = [ 94, 97,134, 91, 93] - @@data[ROOFS][:uos]["142"] = [ 94, 97,106, 93 ] - @@data[ROOFS][:uos]["138"] = [ 94, 97,106, 93 ] # same as :142 ? - @@data[ROOFS][:uos]["121"] = [ 94, 97,106,150, 93] - @@data[ROOFS][:uos]["100"] = [ 94, 97,106,106, 93] + # Uo sub-variants point to empty arrays. These arrays initially held layer + # identifiers (integers), for BTAP costing only. These arrays may be + # reactivated at some point e.g. as Hash entries as additional metadata, + # e.g. $/m2, kg CO2/m2. Although potentially useful, such metadata should + # ideally be held elsewhere within BTAP. Maintaining empty arrays for now. + @@data[MASS2][:uos]["314"] = [] + @@data[MASS2][:uos]["278"] = [] + @@data[MASS2][:uos]["247"] = [] + @@data[MASS2][:uos]["210"] = [] + @@data[MASS2][:uos]["183"] = [] + @@data[MASSB][:uos]["130"] = [] + @@data[MASSB][:uos]["100"] = [] + + @@data[MASS4][:uos]["314"] = [] + @@data[MASS4][:uos]["278"] = [] + @@data[MASS4][:uos]["247"] = [] + @@data[MASS4][:uos]["210"] = [] + @@data[MASS4][:uos]["183"] = [] + @@data[MASS8][:uos]["130"] = [] + @@data[MASS8][:uos]["100"] = [] + + @@data[MASS6][:uos]["314"] = [] + @@data[MASS6][:uos]["278"] = [] + @@data[MASS6][:uos]["247"] = [] + @@data[MASSC][:uos]["210"] = [] + @@data[MASSC][:uos]["183"] = [] + @@data[MASSC][:uos]["130"] = [] + @@data[MASSC][:uos]["100"] = [] + @@data[MASSC][:uos]["080"] = [] + + @@data[MTAL1][:uos]["314"] = [] + @@data[MTAL1][:uos]["278"] = [] + @@data[MTAL1][:uos]["247"] = [] + @@data[MTAL1][:uos]["210"] = [] + @@data[MTAL1][:uos]["183"] = [] + @@data[MTALD][:uos]["130"] = [] + @@data[MTALD][:uos]["100"] = [] + + @@data[WOOD5][:uos]["314"] = [] + @@data[WOOD5][:uos]["278"] = [] + @@data[WOOD5][:uos]["247"] = [] + @@data[WOOD5][:uos]["210"] = [] + @@data[WOOD5][:uos]["183"] = [] + @@data[WOOD7][:uos]["130"] = [] + + @@data[STEL1][:uos]["314"] = [] + @@data[STEL1][:uos]["278"] = [] + @@data[STEL2][:uos]["247"] = [] + @@data[STEL2][:uos]["210"] = [] + @@data[STEL2][:uos]["183"] = [] + @@data[STEL2][:uos]["130"] = [] + @@data[STEL2][:uos]["100"] = [] + @@data[STEL2][:uos]["080"] = [] + + @@data[FLOOR][:uos]["227"] = [] + @@data[FLOOR][:uos]["183"] = [] + @@data[FLOOR][:uos]["162"] = [] + @@data[FLOOR][:uos]["142"] = [] + @@data[FLOOR][:uos]["116"] = [] + @@data[FLOOR][:uos]["101"] = [] + + @@data[ROOFS][:uos]["227"] = [] + @@data[ROOFS][:uos]["193"] = [] + @@data[ROOFS][:uos]["183"] = [] + @@data[ROOFS][:uos]["162"] = [] + @@data[ROOFS][:uos]["156"] = [] + @@data[ROOFS][:uos]["142"] = [] + @@data[ROOFS][:uos]["138"] = [] + @@data[ROOFS][:uos]["121"] = [] + @@data[ROOFS][:uos]["100"] = [] # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- # - # In BTAP costing, each NECB building/space type is linked to a default - # construction set, which holds one of the preceding wall options. This - # linkage is now extended to OpenStudio models (not just costing), + # In BTAP, each NECB building/space type is linked to a default construction + # set, which holds one of the preceding wall options. This is key here, # given the construction-specific nature of MAJOR thermal bridging. # + # NOTE: Expect radical changes to the NECB building/space type model (@todo). + # # Each of these wall options holds NECB building (or space) type keywords # (see below). The default (fall back) keyword is :office. String pattern # recognition, e.g.: @@ -341,9 +344,6 @@ module BridgingData # factor selection is based strictly on selected wall construction, i.e. # regardless of selected roof, fenestration, etc. The linkage remains valid # for both building and space types (regardless of NECB vintage). - # - # The implementation is likely to be revised in the future, yet would - # remain conceptually similar. # "BTAP-ExteriorWall-Mass-2" & "BTAP-ExteriorWall-Mass-2b" @@data[MASS2][:sptypes][:exercise ] = {} @@ -429,677 +429,283 @@ module BridgingData end # Thermal bridge types :balcony, :party and :joint are NOT expected to - # be processed within BTAP. They are not costed out either. At some - # point, it may become wise to do so (notably for cantilevered balconies - # in MURBs). Default, generic BETBG PSI factors are nonetheless provided - # here (just in case): + # be processed soon within BTAP. They are not costed out either, nor are + # carbon intensities associated to them. At some point, it may be wise to do + # so (notably for cantilevered balconies in MURBs). Default, generic BETBG + # PSI factors are nonetheless provided here (just in case): # # - for the "bad" BTAP cases, retained values are those of the # generic "bad" BETBG set # - while "good" BTAP values are those of the generic BETBG # "efficient" set + @@data[MASS2][ :bad][:id ] = MASS2_BAD @@data[MASS2][ :bad][:rimjoist ] = { psi: 0.470 } @@data[MASS2][ :bad][:parapet ] = { psi: 0.500 } - @@data[MASS2][ :bad][:head ] = { psi: 0.350 } - @@data[MASS2][ :bad][:jamb ] = { psi: 0.350 } - @@data[MASS2][ :bad][:sill ] = { psi: 0.350 } + @@data[MASS2][ :bad][:fenestration] = { psi: 0.350 } + @@data[MASS2][ :bad][:door ] = { psi: 0.000 } @@data[MASS2][ :bad][:corner ] = { psi: 0.150 } @@data[MASS2][ :bad][:balcony ] = { psi: 1.000 } @@data[MASS2][ :bad][:party ] = { psi: 0.850 } @@data[MASS2][ :bad][:grade ] = { psi: 0.520 } @@data[MASS2][ :bad][:joint ] = { psi: 0.300 } - @@data[MASS2][ :bad][:transition ] = { psi: 0.000 } + @@data[MASS2][:good][:id ] = MASS2_GOOD @@data[MASS2][:good][:rimjoist ] = { psi: 0.100 } @@data[MASS2][:good][:parapet ] = { psi: 0.230 } - @@data[MASS2][:good][:head ] = { psi: 0.078 } - @@data[MASS2][:good][:jamb ] = { psi: 0.078 } - @@data[MASS2][:good][:sill ] = { psi: 0.078 } + @@data[MASS2][:good][:fenestration] = { psi: 0.078 } + @@data[MASS2][:good][:door ] = { psi: 0.000 } @@data[MASS2][:good][:corner ] = { psi: 0.090 } @@data[MASS2][:good][:balcony ] = { psi: 0.200 } @@data[MASS2][:good][:party ] = { psi: 0.200 } @@data[MASS2][:good][:grade ] = { psi: 0.090 } @@data[MASS2][:good][:joint ] = { psi: 0.100 } - @@data[MASS2][:good][:transition ] = { psi: 0.000 } + @@data[MASSB][ :bad][:id ] = MASSB_BAD @@data[MASSB][ :bad][:rimjoist ] = { psi: 0.470 } @@data[MASSB][ :bad][:parapet ] = { psi: 0.500 } - @@data[MASSB][ :bad][:head ] = { psi: 0.350 } - @@data[MASSB][ :bad][:jamb ] = { psi: 0.350 } - @@data[MASSB][ :bad][:sill ] = { psi: 0.350 } + @@data[MASSB][ :bad][:fenestration] = { psi: 0.350 } + @@data[MASSB][ :bad][:door ] = { psi: 0.000 } @@data[MASSB][ :bad][:corner ] = { psi: 0.150 } @@data[MASSB][ :bad][:balcony ] = { psi: 1.000 } @@data[MASSB][ :bad][:party ] = { psi: 0.850 } @@data[MASSB][ :bad][:grade ] = { psi: 0.520 } @@data[MASSB][ :bad][:joint ] = { psi: 0.300 } - @@data[MASSB][ :bad][:transition ] = { psi: 0.000 } + @@data[MASSB][:good][:id ] = MASSB_GOOD @@data[MASSB][:good][:rimjoist ] = { psi: 0.100 } @@data[MASSB][:good][:parapet ] = { psi: 0.230 } - @@data[MASSB][:good][:head ] = { psi: 0.078 } - @@data[MASSB][:good][:jamb ] = { psi: 0.078 } - @@data[MASSB][:good][:sill ] = { psi: 0.078 } + @@data[MASSB][:good][:fenestration] = { psi: 0.078 } + @@data[MASSB][:good][:door ] = { psi: 0.000 } @@data[MASSB][:good][:corner ] = { psi: 0.090 } @@data[MASSB][:good][:balcony ] = { psi: 0.200 } @@data[MASSB][:good][:party ] = { psi: 0.200 } @@data[MASSB][:good][:grade ] = { psi: 0.090 } @@data[MASSB][:good][:joint ] = { psi: 0.100 } - @@data[MASSB][:good][:transition ] = { psi: 0.000 } + @@data[MASS4][ :bad][:id ] = MASS4_BAD @@data[MASS4][ :bad][:rimjoist ] = { psi: 0.200 } @@data[MASS4][ :bad][:parapet ] = { psi: 0.650 } - @@data[MASS4][ :bad][:head ] = { psi: 0.078 } - @@data[MASS4][ :bad][:jamb ] = { psi: 0.078 } - @@data[MASS4][ :bad][:sill ] = { psi: 0.078 } + @@data[MASS4][ :bad][:fenestration] = { psi: 0.078 } + @@data[MASS4][ :bad][:door ] = { psi: 0.000 } @@data[MASS4][ :bad][:corner ] = { psi: 0.370 } @@data[MASS4][ :bad][:balcony ] = { psi: 1.000 } @@data[MASS4][ :bad][:party ] = { psi: 0.850 } @@data[MASS4][ :bad][:grade ] = { psi: 0.800 } @@data[MASS4][ :bad][:joint ] = { psi: 0.300 } - @@data[MASS4][ :bad][:transition ] = { psi: 0.000 } + @@data[MASS4][:good][:id ] = MASS4_GOOD @@data[MASS4][:good][:rimjoist ] = { psi: 0.020 } @@data[MASS4][:good][:parapet ] = { psi: 0.240 } - @@data[MASS4][:good][:head ] = { psi: 0.078 } - @@data[MASS4][:good][:jamb ] = { psi: 0.078 } - @@data[MASS4][:good][:sill ] = { psi: 0.078 } + @@data[MASS4][:good][:fenestration] = { psi: 0.078 } + @@data[MASS4][:good][:door ] = { psi: 0.000 } @@data[MASS4][:good][:corner ] = { psi: 0.160 } @@data[MASS4][:good][:balcony ] = { psi: 0.200 } @@data[MASS4][:good][:party ] = { psi: 0.200 } @@data[MASS4][:good][:grade ] = { psi: 0.320 } @@data[MASS4][:good][:joint ] = { psi: 0.100 } - @@data[MASS4][:good][:transition ] = { psi: 0.000 } + @@data[MASS8][ :bad][:id ] = MASS8_BAD @@data[MASS8][ :bad][:rimjoist ] = { psi: 0.200 } @@data[MASS8][ :bad][:parapet ] = { psi: 0.650 } - @@data[MASS8][ :bad][:head ] = { psi: 0.078 } - @@data[MASS8][ :bad][:jamb ] = { psi: 0.078 } - @@data[MASS8][ :bad][:sill ] = { psi: 0.078 } + @@data[MASS8][ :bad][:fenestration] = { psi: 0.078 } + @@data[MASS8][ :bad][:door ] = { psi: 0.000 } @@data[MASS8][ :bad][:corner ] = { psi: 0.370 } @@data[MASS8][ :bad][:balcony ] = { psi: 1.000 } @@data[MASS8][ :bad][:party ] = { psi: 0.850 } @@data[MASS8][ :bad][:grade ] = { psi: 0.800 } @@data[MASS8][ :bad][:joint ] = { psi: 0.300 } - @@data[MASS8][ :bad][:transition ] = { psi: 0.000 } + @@data[MASS8][:good][:id ] = MASS8_GOOD @@data[MASS8][:good][:rimjoist ] = { psi: 0.020 } @@data[MASS8][:good][:parapet ] = { psi: 0.240 } - @@data[MASS8][:good][:head ] = { psi: 0.078 } - @@data[MASS8][:good][:jamb ] = { psi: 0.078 } - @@data[MASS8][:good][:sill ] = { psi: 0.078 } + @@data[MASS8][:good][:fenestration] = { psi: 0.078 } + @@data[MASS8][:good][:door ] = { psi: 0.000 } @@data[MASS8][:good][:corner ] = { psi: 0.160 } @@data[MASS8][:good][:balcony ] = { psi: 0.200 } @@data[MASS8][:good][:party ] = { psi: 0.200 } @@data[MASS8][:good][:grade ] = { psi: 0.320 } @@data[MASS8][:good][:joint ] = { psi: 0.100 } - @@data[MASS8][:good][:transition ] = { psi: 0.000 } + @@data[MASS6][ :bad][:id ] = MASS6_BAD @@data[MASS6][ :bad][:rimjoist ] = { psi: 0.470 } @@data[MASS6][ :bad][:parapet ] = { psi: 0.500 } - @@data[MASS6][ :bad][:head ] = { psi: 0.350 } - @@data[MASS6][ :bad][:jamb ] = { psi: 0.350 } - @@data[MASS6][ :bad][:sill ] = { psi: 0.350 } + @@data[MASS6][ :bad][:fenestration] = { psi: 0.350 } + @@data[MASS6][ :bad][:door ] = { psi: 0.000 } @@data[MASS6][ :bad][:corner ] = { psi: 0.150 } @@data[MASS6][ :bad][:balcony ] = { psi: 1.000 } @@data[MASS6][ :bad][:party ] = { psi: 0.850 } @@data[MASS6][ :bad][:grade ] = { psi: 0.520 } @@data[MASS6][ :bad][:joint ] = { psi: 0.300 } - @@data[MASS6][ :bad][:transition ] = { psi: 0.000 } + @@data[MASS6][:good][:id ] = MASS6_GOOD @@data[MASS6][:good][:rimjoist ] = { psi: 0.100 } @@data[MASS6][:good][:parapet ] = { psi: 0.230 } - @@data[MASS6][:good][:head ] = { psi: 0.078 } - @@data[MASS6][:good][:jamb ] = { psi: 0.078 } - @@data[MASS6][:good][:sill ] = { psi: 0.078 } + @@data[MASS6][:good][:fenestration] = { psi: 0.078 } + @@data[MASS6][:good][:door ] = { psi: 0.000 } @@data[MASS6][:good][:corner ] = { psi: 0.090 } @@data[MASS6][:good][:balcony ] = { psi: 0.200 } @@data[MASS6][:good][:party ] = { psi: 0.200 } @@data[MASS6][:good][:grade ] = { psi: 0.090 } @@data[MASS6][:good][:joint ] = { psi: 0.100 } - @@data[MASS6][:good][:transition ] = { psi: 0.000 } + @@data[MASSC][ :bad][:id ] = MASSC_BAD @@data[MASSC][ :bad][:rimjoist ] = { psi: 0.170 } @@data[MASSC][ :bad][:parapet ] = { psi: 0.500 } - @@data[MASSC][ :bad][:head ] = { psi: 0.350 } - @@data[MASSC][ :bad][:jamb ] = { psi: 0.350 } - @@data[MASSC][ :bad][:sill ] = { psi: 0.350 } + @@data[MASSC][ :bad][:fenestration] = { psi: 0.350 } + @@data[MASSC][ :bad][:door ] = { psi: 0.000 } @@data[MASSC][ :bad][:corner ] = { psi: 0.150 } @@data[MASSC][ :bad][:balcony ] = { psi: 1.000 } @@data[MASSC][ :bad][:party ] = { psi: 0.850 } @@data[MASSC][ :bad][:grade ] = { psi: 0.720 } @@data[MASSC][ :bad][:joint ] = { psi: 0.300 } - @@data[MASSC][ :bad][:transition ] = { psi: 0.000 } + @@data[MASSC][:good][:id ] = MASSC_GOOD @@data[MASSC][:good][:rimjoist ] = { psi: 0.017 } @@data[MASSC][:good][:parapet ] = { psi: 0.230 } - @@data[MASSC][:good][:head ] = { psi: 0.078 } - @@data[MASSC][:good][:jamb ] = { psi: 0.078 } - @@data[MASSC][:good][:sill ] = { psi: 0.078 } + @@data[MASSC][:good][:fenestration] = { psi: 0.078 } + @@data[MASSC][:good][:door ] = { psi: 0.000 } @@data[MASSC][:good][:corner ] = { psi: 0.090 } @@data[MASSC][:good][:balcony ] = { psi: 0.200 } @@data[MASSC][:good][:party ] = { psi: 0.200 } @@data[MASSC][:good][:grade ] = { psi: 0.470 } @@data[MASSC][:good][:joint ] = { psi: 0.100 } - @@data[MASSC][:good][:transition ] = { psi: 0.000 } + @@data[MTAL1][ :bad][:id ] = MTAL1_BAD @@data[MTAL1][ :bad][:rimjoist ] = { psi: 0.320 } @@data[MTAL1][ :bad][:parapet ] = { psi: 0.420 } - @@data[MTAL1][ :bad][:head ] = { psi: 0.520 } - @@data[MTAL1][ :bad][:jamb ] = { psi: 0.520 } - @@data[MTAL1][ :bad][:sill ] = { psi: 0.520 } + @@data[MTAL1][ :bad][:fenestration] = { psi: 0.520 } + @@data[MTAL1][ :bad][:door ] = { psi: 0.000 } @@data[MTAL1][ :bad][:corner ] = { psi: 0.150 } @@data[MTAL1][ :bad][:balcony ] = { psi: 1.000 } @@data[MTAL1][ :bad][:party ] = { psi: 0.850 } @@data[MTAL1][ :bad][:grade ] = { psi: 0.700 } @@data[MTAL1][ :bad][:joint ] = { psi: 0.300 } - @@data[MTAL1][ :bad][:transition ] = { psi: 0.000 } + @@data[MTAL1][:good][:id ] = MTAL1_GOOD @@data[MTAL1][:good][:rimjoist ] = { psi: 0.030 } @@data[MTAL1][:good][:parapet ] = { psi: 0.350 } - @@data[MTAL1][:good][:head ] = { psi: 0.078 } - @@data[MTAL1][:good][:jamb ] = { psi: 0.078 } - @@data[MTAL1][:good][:sill ] = { psi: 0.078 } + @@data[MTAL1][:good][:fenestration] = { psi: 0.078 } + @@data[MTAL1][:good][:door ] = { psi: 0.000 } @@data[MTAL1][:good][:corner ] = { psi: 0.070 } @@data[MTAL1][:good][:balcony ] = { psi: 0.200 } @@data[MTAL1][:good][:party ] = { psi: 0.200 } @@data[MTAL1][:good][:grade ] = { psi: 0.500 } @@data[MTAL1][:good][:joint ] = { psi: 0.100 } - @@data[MTAL1][:good][:transition ] = { psi: 0.000 } + @@data[MTALD][ :bad][:id ] = MTALD_BAD @@data[MTALD][ :bad][:rimjoist ] = { psi: 0.320 } @@data[MTALD][ :bad][:parapet ] = { psi: 0.420 } - @@data[MTALD][ :bad][:head ] = { psi: 0.520 } - @@data[MTALD][ :bad][:jamb ] = { psi: 0.520 } - @@data[MTALD][ :bad][:sill ] = { psi: 0.520 } + @@data[MTALD][ :bad][:fenestration] = { psi: 0.520 } + @@data[MTALD][ :bad][:door ] = { psi: 0.000 } @@data[MTALD][ :bad][:corner ] = { psi: 0.150 } @@data[MTALD][ :bad][:balcony ] = { psi: 1.000 } @@data[MTALD][ :bad][:party ] = { psi: 0.850 } @@data[MTALD][ :bad][:grade ] = { psi: 0.700 } @@data[MTALD][ :bad][:joint ] = { psi: 0.300 } - @@data[MTALD][ :bad][:transition ] = { psi: 0.000 } + @@data[MTALD][:good][:id ] = MTALD_GOOD @@data[MTALD][:good][:rimjoist ] = { psi: 0.030 } @@data[MTALD][:good][:parapet ] = { psi: 0.350 } - @@data[MTALD][:good][:head ] = { psi: 0.078 } - @@data[MTALD][:good][:jamb ] = { psi: 0.078 } - @@data[MTALD][:good][:sill ] = { psi: 0.078 } + @@data[MTALD][:good][:fenestration] = { psi: 0.078 } + @@data[MTALD][:good][:door ] = { psi: 0.000 } @@data[MTALD][:good][:corner ] = { psi: 0.070 } @@data[MTALD][:good][:balcony ] = { psi: 0.200 } @@data[MTALD][:good][:party ] = { psi: 0.200 } @@data[MTALD][:good][:grade ] = { psi: 0.500 } @@data[MTALD][:good][:joint ] = { psi: 0.100 } - @@data[MTALD][:good][:transition ] = { psi: 0.000 } + @@data[WOOD5][ :bad][:id ] = WOOD5_BAD @@data[WOOD5][ :bad][:rimjoist ] = { psi: 0.050 } @@data[WOOD5][ :bad][:parapet ] = { psi: 0.050 } - @@data[WOOD5][ :bad][:head ] = { psi: 0.270 } - @@data[WOOD5][ :bad][:jamb ] = { psi: 0.270 } - @@data[WOOD5][ :bad][:sill ] = { psi: 0.270 } + @@data[WOOD5][ :bad][:fenestration] = { psi: 0.270 } + @@data[WOOD5][ :bad][:door ] = { psi: 0.000 } @@data[WOOD5][ :bad][:corner ] = { psi: 0.040 } @@data[WOOD5][ :bad][:balcony ] = { psi: 1.000 } @@data[WOOD5][ :bad][:party ] = { psi: 0.850 } @@data[WOOD5][ :bad][:grade ] = { psi: 0.550 } @@data[WOOD5][ :bad][:joint ] = { psi: 0.300 } - @@data[WOOD5][ :bad][:transition ] = { psi: 0.000 } + @@data[WOOD5][:good][:id ] = WOOD5_GOOD @@data[WOOD5][:good][:rimjoist ] = { psi: 0.030 } @@data[WOOD5][:good][:parapet ] = { psi: 0.050 } - @@data[WOOD5][:good][:head ] = { psi: 0.078 } - @@data[WOOD5][:good][:jamb ] = { psi: 0.078 } - @@data[WOOD5][:good][:sill ] = { psi: 0.078 } + @@data[WOOD5][:good][:fenestration] = { psi: 0.078 } + @@data[WOOD5][:good][:door ] = { psi: 0.000 } @@data[WOOD5][:good][:corner ] = { psi: 0.040 } @@data[WOOD5][:good][:balcony ] = { psi: 0.200 } @@data[WOOD5][:good][:party ] = { psi: 0.200 } @@data[WOOD5][:good][:grade ] = { psi: 0.090 } @@data[WOOD5][:good][:joint ] = { psi: 0.100 } - @@data[WOOD5][:good][:transition ] = { psi: 0.000 } + @@data[WOOD7][ :bad][:id ] = WOOD7_BAD @@data[WOOD7][ :bad][:rimjoist ] = { psi: 0.050 } @@data[WOOD7][ :bad][:parapet ] = { psi: 0.050 } - @@data[WOOD7][ :bad][:head ] = { psi: 0.270 } - @@data[WOOD7][ :bad][:jamb ] = { psi: 0.270 } - @@data[WOOD7][ :bad][:sill ] = { psi: 0.270 } + @@data[WOOD7][ :bad][:fenestration] = { psi: 0.270 } + @@data[WOOD7][ :bad][:door ] = { psi: 0.000 } @@data[WOOD7][ :bad][:corner ] = { psi: 0.040 } @@data[WOOD7][ :bad][:balcony ] = { psi: 1.000 } @@data[WOOD7][ :bad][:party ] = { psi: 0.850 } @@data[WOOD7][ :bad][:grade ] = { psi: 0.550 } @@data[WOOD7][ :bad][:joint ] = { psi: 0.300 } - @@data[WOOD7][ :bad][:transition ] = { psi: 0.000 } + @@data[WOOD7][:good][:id ] = WOOD7_GOOD @@data[WOOD7][:good][:rimjoist ] = { psi: 0.030 } @@data[WOOD7][:good][:parapet ] = { psi: 0.050 } - @@data[WOOD7][:good][:head ] = { psi: 0.078 } - @@data[WOOD7][:good][:jamb ] = { psi: 0.078 } - @@data[WOOD7][:good][:sill ] = { psi: 0.078 } + @@data[WOOD7][:good][:fenestration] = { psi: 0.078 } + @@data[WOOD7][:good][:door ] = { psi: 0.000 } @@data[WOOD7][:good][:corner ] = { psi: 0.040 } @@data[WOOD7][:good][:balcony ] = { psi: 0.200 } @@data[WOOD7][:good][:party ] = { psi: 0.200 } @@data[WOOD7][:good][:grade ] = { psi: 0.090 } @@data[WOOD7][:good][:joint ] = { psi: 0.100 } - @@data[WOOD7][:good][:transition ] = { psi: 0.000 } + @@data[STEL1][ :bad][:id ] = STEL1_BAD @@data[STEL1][ :bad][:rimjoist ] = { psi: 0.280 } @@data[STEL1][ :bad][:parapet ] = { psi: 0.650 } - @@data[STEL1][ :bad][:head ] = { psi: 0.270 } - @@data[STEL1][ :bad][:jamb ] = { psi: 0.270 } - @@data[STEL1][ :bad][:sill ] = { psi: 0.270 } + @@data[STEL1][ :bad][:fenestration] = { psi: 0.270 } + @@data[STEL1][ :bad][:door ] = { psi: 0.000 } @@data[STEL1][ :bad][:corner ] = { psi: 0.150 } @@data[STEL1][ :bad][:balcony ] = { psi: 1.000 } @@data[STEL1][ :bad][:party ] = { psi: 0.850 } @@data[STEL1][ :bad][:grade ] = { psi: 0.720 } @@data[STEL1][ :bad][:joint ] = { psi: 0.300 } - @@data[STEL1][ :bad][:transition ] = { psi: 0.000 } + @@data[STEL1][:good][:id ] = STEL1_GOOD @@data[STEL1][:good][:rimjoist ] = { psi: 0.090 } @@data[STEL1][:good][:parapet ] = { psi: 0.350 } - @@data[STEL1][:good][:head ] = { psi: 0.078 } - @@data[STEL1][:good][:jamb ] = { psi: 0.078 } - @@data[STEL1][:good][:sill ] = { psi: 0.078 } + @@data[STEL1][:good][:fenestration] = { psi: 0.078 } + @@data[STEL1][:good][:door ] = { psi: 0.000 } @@data[STEL1][:good][:corner ] = { psi: 0.090 } @@data[STEL1][:good][:balcony ] = { psi: 0.200 } @@data[STEL1][:good][:party ] = { psi: 0.200 } @@data[STEL1][:good][:grade ] = { psi: 0.470 } @@data[STEL1][:good][:joint ] = { psi: 0.100 } - @@data[STEL1][:good][:transition ] = { psi: 0.000 } + @@data[STEL2][ :bad][:id ] = STEL2_BAD @@data[STEL2][ :bad][:rimjoist ] = { psi: 0.280 } @@data[STEL2][ :bad][:parapet ] = { psi: 0.650 } - @@data[STEL2][ :bad][:head ] = { psi: 0.270 } - @@data[STEL2][ :bad][:jamb ] = { psi: 0.270 } - @@data[STEL2][ :bad][:sill ] = { psi: 0.270 } + @@data[STEL2][ :bad][:fenestration] = { psi: 0.270 } + @@data[STEL2][ :bad][:door ] = { psi: 0.000 } @@data[STEL2][ :bad][:corner ] = { psi: 0.150 } @@data[STEL2][ :bad][:balcony ] = { psi: 1.000 } @@data[STEL2][ :bad][:party ] = { psi: 0.850 } @@data[STEL2][ :bad][:grade ] = { psi: 0.720 } @@data[STEL2][ :bad][:joint ] = { psi: 0.300 } - @@data[STEL2][ :bad][:transition ] = { psi: 0.000 } + @@data[STEL2][:good][:id ] = STEL2_GOOD @@data[STEL2][:good][:rimjoist ] = { psi: 0.090 } @@data[STEL2][:good][:parapet ] = { psi: 0.100 } - @@data[STEL2][:good][:head ] = { psi: 0.078 } - @@data[STEL2][:good][:jamb ] = { psi: 0.078 } - @@data[STEL2][:good][:sill ] = { psi: 0.078 } + @@data[STEL2][:good][:fenestration] = { psi: 0.078 } + @@data[STEL2][:good][:door ] = { psi: 0.000 } @@data[STEL2][:good][:corner ] = { psi: 0.090 } @@data[STEL2][:good][:balcony ] = { psi: 0.200 } @@data[STEL2][:good][:party ] = { psi: 0.200 } @@data[STEL2][:good][:grade ] = { psi: 0.470 } @@data[STEL2][:good][:joint ] = { psi: 0.100 } - @@data[STEL2][:good][:transition ] = { psi: 0.000 } - - # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- # - # Extend for BTAP costing. - @@data.values.each do |construction| - construction[:good].values.each { |bridge| bridge[:mat] = {} } - construction[ :bad].values.each { |bridge| bridge[:mat] = {} } - end - - # BTAP costed "materials" (Hash keywords in double quotations) for MAJOR - # thermal bridges. Corresponding Hash values are multipliers. - # - # NOTE: "0" as a NIL placeholder (no cost associated to thermal bridge). - @@data[MASS2][ :bad][:id ] = MASS2_BAD - @@data[MASS2][ :bad][:rimjoist ][:mat][ "21"] = 1.000 - @@data[MASS2][ :bad][:rimjoist ][:mat]["172"] = 0.250 - @@data[MASS2][ :bad][:parapet ][:mat][ "0"] = 1.000 - @@data[MASS2][ :bad][:head ][:mat]["139"] = 0.750 - @@data[MASS2][ :bad][:jamb ][:mat]["139"] = 0.750 - @@data[MASS2][ :bad][:sill ][:mat]["139"] = 0.750 - @@data[MASS2][ :bad][:corner ][:mat][ "0"] = 1.000 - @@data[MASS2][ :bad][:balcony ][:mat][ ""] = 1.000 - @@data[MASS2][ :bad][:party ][:mat][ ""] = 1.000 - @@data[MASS2][ :bad][:grade ][:mat][ "21"] = 1.000 - @@data[MASS2][ :bad][:grade ][:mat]["139"] = 0.500 - @@data[MASS2][ :bad][:joint ][:mat][ ""] = 1.000 - @@data[MASS2][ :bad][:transition ][:mat][ ""] = 1.000 - - @@data[MASS2][:good][:id ] = MASS2_GOOD - @@data[MASS2][:good][:rimjoist ][:mat]["189"] = 1.000 - @@data[MASS2][:good][:rimjoist ][:mat]["172"] = 0.500 - @@data[MASS2][:good][:parapet ][:mat][ "57"] = 3.300 - @@data[MASS2][:good][:parapet ][:mat]["139"] = 1.000 - @@data[MASS2][:good][:head ][:mat]["139"] = 0.500 - @@data[MASS2][:good][:jamb ][:mat]["139"] = 0.500 - @@data[MASS2][:good][:sill ][:mat]["139"] = 0.500 - @@data[MASS2][:good][:corner ][:mat][ "0"] = 1.000 - @@data[MASS2][:good][:balcony ][:mat][ ""] = 1.000 - @@data[MASS2][:good][:party ][:mat][ ""] = 1.000 - @@data[MASS2][:good][:grade ][:mat]["189"] = 1.000 - @@data[MASS2][:good][:grade ][:mat]["139"] = 0.500 - @@data[MASS2][:good][:grade ][:mat]["192"] = 0.500 - @@data[MASS2][:good][:joint ][:mat][ ""] = 1.000 - @@data[MASS2][:good][:transition ][:mat][ ""] = 1.000 - - @@data[MASSB][ :bad][:id ] = MASSB_BAD - @@data[MASSB][ :bad][:rimjoist ][:mat][ "21"] = 1.000 - @@data[MASSB][ :bad][:rimjoist ][:mat]["172"] = 0.250 - @@data[MASSB][ :bad][:parapet ][:mat][ "0"] = 1.000 - @@data[MASSB][ :bad][:head ][:mat]["139"] = 0.750 - @@data[MASSB][ :bad][:jamb ][:mat]["139"] = 0.750 - @@data[MASSB][ :bad][:sill ][:mat]["139"] = 0.750 - @@data[MASSB][ :bad][:corner ][:mat][ "0"] = 1.000 - @@data[MASSB][ :bad][:balcony ][:mat][ ""] = 1.000 - @@data[MASSB][ :bad][:party ][:mat][ ""] = 1.000 - @@data[MASSB][ :bad][:grade ][:mat][ "21"] = 1.000 - @@data[MASSB][ :bad][:grade ][:mat]["139"] = 0.500 - @@data[MASSB][ :bad][:joint ][:mat][ ""] = 1.000 - @@data[MASSB][ :bad][:transition ][:mat][ ""] = 1.000 - - @@data[MASSB][:good][:id ] = MASSB_GOOD - @@data[MASSB][:good][:rimjoist ][:mat]["189"] = 1.000 - @@data[MASSB][:good][:rimjoist ][:mat]["172"] = 0.500 - @@data[MASSB][:good][:parapet ][:mat][ "57"] = 3.300 - @@data[MASSB][:good][:parapet ][:mat]["139"] = 1.000 - @@data[MASSB][:good][:head ][:mat]["139"] = 0.500 - @@data[MASSB][:good][:jamb ][:mat]["139"] = 0.500 - @@data[MASSB][:good][:sill ][:mat]["139"] = 0.500 - @@data[MASSB][:good][:corner ][:mat][ "0"] = 1.000 - @@data[MASSB][:good][:balcony ][:mat][ ""] = 1.000 - @@data[MASSB][:good][:party ][:mat][ ""] = 1.000 - @@data[MASSB][:good][:grade ][:mat]["189"] = 1.000 - @@data[MASSB][:good][:grade ][:mat]["139"] = 0.500 - @@data[MASSB][:good][:grade ][:mat]["192"] = 0.500 - @@data[MASSB][:good][:joint ][:mat][ ""] = 1.000 - @@data[MASSB][:good][:transition ][:mat][ ""] = 1.000 - - @@data[MASS4][ :bad][:id ] = MASS4_BAD - @@data[MASS4][ :bad][:rimjoist ][:mat][ "0"] = 1.000 - @@data[MASS4][ :bad][:parapet ][:mat][ "0"] = 1.000 - @@data[MASS4][ :bad][:head ][:mat]["139"] = 0.250 - @@data[MASS4][ :bad][:jamb ][:mat]["139"] = 0.250 - @@data[MASS4][ :bad][:sill ][:mat]["139"] = 0.250 - @@data[MASS4][ :bad][:corner ][:mat]["141"] = 1.000 - @@data[MASS4][ :bad][:balcony ][:mat][ ""] = 1.000 - @@data[MASS4][ :bad][:party ][:mat][ ""] = 1.000 - @@data[MASS4][ :bad][:grade ][:mat]["139"] = 0.500 - @@data[MASS4][ :bad][:joint ][:mat][ ""] = 1.000 - @@data[MASS4][ :bad][:transition ][:mat][ ""] = 1.000 - - @@data[MASS4][:good][:id ] = MASS4_GOOD - @@data[MASS4][:good][:rimjoist ][:mat][ "0"] = 1.000 - @@data[MASS4][:good][:parapet ][:mat][ "57"] = 3.300 - @@data[MASS4][:good][:parapet ][:mat]["139"] = 1.000 - @@data[MASS4][:good][:head ][:mat]["139"] = 0.250 - @@data[MASS4][:good][:head ][:mat]["150"] = 0.083 - @@data[MASS4][:good][:jamb ][:mat]["139"] = 0.250 - @@data[MASS4][:good][:jamb ][:mat]["150"] = 0.083 - @@data[MASS4][:good][:sill ][:mat]["139"] = 0.250 - @@data[MASS4][:good][:sill ][:mat]["150"] = 0.083 - @@data[MASS4][:good][:corner ][:mat]["141"] = 1.250 - @@data[MASS4][:good][:balcony ][:mat][ "0"] = 1.000 - @@data[MASS4][:good][:party ][:mat][ ""] = 1.000 - @@data[MASS4][:good][:grade ][:mat]["192"] = 0.500 - @@data[MASS4][:good][:grade ][:mat]["139"] = 0.500 - @@data[MASS4][:good][:joint ][:mat][ ""] = 1.000 - @@data[MASS4][:good][:transition ][:mat][ ""] = 1.000 - - @@data[MASS8][ :bad][:id ] = MASS8_BAD - @@data[MASS8][ :bad][:rimjoist ][:mat][ "0"] = 1.000 - @@data[MASS8][ :bad][:parapet ][:mat][ "0"] = 1.000 - @@data[MASS8][ :bad][:head ][:mat]["139"] = 0.250 - @@data[MASS8][ :bad][:jamb ][:mat]["139"] = 0.250 - @@data[MASS8][ :bad][:sill ][:mat]["139"] = 0.250 - @@data[MASS8][ :bad][:corner ][:mat]["141"] = 1.000 - @@data[MASS8][ :bad][:balcony ][:mat][ ""] = 1.000 - @@data[MASS8][ :bad][:party ][:mat][ ""] = 1.000 - @@data[MASS8][ :bad][:grade ][:mat]["139"] = 0.500 - @@data[MASS8][ :bad][:joint ][:mat][ ""] = 1.000 - @@data[MASS8][ :bad][:transition ][:mat][ ""] = 1.000 - - @@data[MASS8][:good][:id ] = MASS8_GOOD - @@data[MASS8][:good][:rimjoist ][:mat][ "0"] = 1.000 - @@data[MASS8][:good][:parapet ][:mat][ "57"] = 3.300 - @@data[MASS8][:good][:parapet ][:mat]["139"] = 1.000 - @@data[MASS8][:good][:head ][:mat]["139"] = 0.250 - @@data[MASS8][:good][:head ][:mat]["150"] = 0.083 - @@data[MASS8][:good][:jamb ][:mat]["139"] = 0.250 - @@data[MASS8][:good][:jamb ][:mat]["150"] = 0.083 - @@data[MASS8][:good][:sill ][:mat]["139"] = 0.250 - @@data[MASS8][:good][:sill ][:mat]["150"] = 0.083 - @@data[MASS8][:good][:corner ][:mat]["141"] = 1.250 - @@data[MASS8][:good][:balcony ][:mat][ "0"] = 1.000 - @@data[MASS8][:good][:party ][:mat][ ""] = 1.000 - @@data[MASS8][:good][:grade ][:mat]["192"] = 0.500 - @@data[MASS8][:good][:grade ][:mat]["139"] = 0.500 - @@data[MASS8][:good][:joint ][:mat][ ""] = 1.000 - @@data[MASS8][:good][:transition ][:mat][ ""] = 1.000 - - @@data[MASS6][ :bad][:id ] = MASS6_BAD - @@data[MASS6][ :bad][:rimjoist ][:mat][ "21"] = 1.000 - @@data[MASS6][ :bad][:rimjoist ][:mat]["172"] = 0.250 - @@data[MASS6][ :bad][:parapet ][:mat][ "0"] = 1.000 - @@data[MASS6][ :bad][:head ][:mat]["139"] = 0.750 - @@data[MASS6][ :bad][:jamb ][:mat]["139"] = 0.750 - @@data[MASS6][ :bad][:sill ][:mat]["139"] = 0.750 - @@data[MASS6][ :bad][:corner ][:mat][ "0"] = 1.000 - @@data[MASS6][ :bad][:balcony ][:mat][ ""] = 1.000 - @@data[MASS6][ :bad][:party ][:mat][ ""] = 1.000 - @@data[MASS6][ :bad][:grade ][:mat][ "21"] = 1.000 - @@data[MASS6][ :bad][:grade ][:mat]["139"] = 0.500 - @@data[MASS6][ :bad][:joint ][:mat][ ""] = 1.000 - @@data[MASS6][ :bad][:transition ][:mat][ ""] = 1.000 - - @@data[MASS6][:good][:id ] = MASS6_GOOD - @@data[MASS6][:good][:rimjoist ][:mat]["189"] = 1.000 - @@data[MASS6][:good][:rimjoist ][:mat]["172"] = 0.500 - @@data[MASS6][:good][:parapet ][:mat][ "57"] = 3.300 - @@data[MASS6][:good][:parapet ][:mat]["139"] = 1.000 - @@data[MASS6][:good][:head ][:mat]["139"] = 0.500 - @@data[MASS6][:good][:jamb ][:mat]["139"] = 0.500 - @@data[MASS6][:good][:sill ][:mat]["139"] = 0.500 - @@data[MASS6][:good][:corner ][:mat][ "0"] = 1.000 - @@data[MASS6][:good][:balcony ][:mat][ ""] = 1.000 - @@data[MASS6][:good][:party ][:mat][ ""] = 1.000 - @@data[MASS6][:good][:grade ][:mat]["189"] = 1.000 - @@data[MASS6][:good][:grade ][:mat]["139"] = 0.500 - @@data[MASS6][:good][:grade ][:mat]["192"] = 0.500 - @@data[MASS6][:good][:joint ][:mat][ ""] = 1.000 - @@data[MASS6][:good][:transition ][:mat][ ""] = 1.000 - - @@data[MASSC][ :bad][:id ] = MASSC_BAD - @@data[MASSC][ :bad][:rimjoist ][:mat]["139"] = 10.000 - @@data[MASSC][ :bad][:parapet ][:mat][ "0"] = 1.000 - @@data[MASSC][ :bad][:head ][:mat]["139"] = 0.750 - @@data[MASSC][ :bad][:jamb ][:mat]["139"] = 0.750 - @@data[MASSC][ :bad][:sill ][:mat]["139"] = 0.750 - @@data[MASSC][ :bad][:corner ][:mat][ "0"] = 1.000 - @@data[MASSC][ :bad][:balcony ][:mat][ ""] = 1.000 - @@data[MASSC][ :bad][:party ][:mat][ ""] = 1.000 - @@data[MASSC][ :bad][:grade ][:mat]["139"] = 0.000 - @@data[MASSC][ :bad][:joint ][:mat][ ""] = 1.000 - @@data[MASSC][ :bad][:transition ][:mat][ ""] = 1.000 - - @@data[MASSC][:good][:id ] = MASSC_GOOD - @@data[MASSC][:good][:rimjoist ][:mat]["172"] = 0.500 - @@data[MASSC][:good][:parapet ][:mat][ "57"] = 3.300 - @@data[MASSC][:good][:parapet ][:mat]["139"] = 1.000 - @@data[MASSC][:good][:head ][:mat]["139"] = 0.500 - @@data[MASSC][:good][:jamb ][:mat]["139"] = 0.500 - @@data[MASSC][:good][:sill ][:mat]["139"] = 0.500 - @@data[MASSC][:good][:corner ][:mat][ "0"] = 1.000 - @@data[MASSC][:good][:balcony ][:mat][ ""] = 1.000 - @@data[MASSC][:good][:party ][:mat][ ""] = 1.000 - @@data[MASSC][:good][:grade ][:mat]["192"] = 1.000 - @@data[MASSC][:good][:grade ][:mat]["139"] = 1.000 - @@data[MASSC][:good][:joint ][:mat][ ""] = 1.000 - @@data[MASSC][:good][:transition ][:mat][ ""] = 1.000 - - @@data[MTAL1][ :bad][:id ] = MTAL1_BAD - @@data[MTAL1][ :bad][:rimjoist ][:mat][ "0"] = 1.000 - @@data[MTAL1][ :bad][:parapet ][:mat][ "0"] = 1.000 - @@data[MTAL1][ :bad][:head ][:mat]["139"] = 1.000 - @@data[MTAL1][ :bad][:jamb ][:mat]["139"] = 1.000 - @@data[MTAL1][ :bad][:sill ][:mat]["139"] = 1.000 - @@data[MTAL1][ :bad][:corner ][:mat]["191"] = 1.000 - @@data[MTAL1][ :bad][:balcony ][:mat][ ""] = 1.000 - @@data[MTAL1][ :bad][:party ][:mat][ ""] = 1.000 - @@data[MTAL1][ :bad][:grade ][:mat]["139"] = 0.500 - @@data[MTAL1][ :bad][:joint ][:mat][ ""] = 1.000 - @@data[MTAL1][ :bad][:transition ][:mat][ ""] = 1.000 - - @@data[MTAL1][:good][:id ] = MTAL1_GOOD - @@data[MTAL1][:good][:rimjoist ][:mat][ "0"] = 1.000 - @@data[MTAL1][:good][:parapet ][:mat][ "57"] = 3.300 - @@data[MTAL1][:good][:parapet ][:mat]["139"] = 1.000 - @@data[MTAL1][:good][:head ][:mat]["139"] = 0.500 - @@data[MTAL1][:good][:jamb ][:mat]["139"] = 0.500 - @@data[MTAL1][:good][:sill ][:mat]["139"] = 0.500 - @@data[MTAL1][:good][:corner ][:mat]["191"] = 1.000 - @@data[MTAL1][:good][:balcony ][:mat][ ""] = 1.000 - @@data[MTAL1][:good][:party ][:mat][ ""] = 1.000 - @@data[MTAL1][:good][:grade ][:mat]["192"] = 0.500 - @@data[MTAL1][:good][:grade ][:mat]["139"] = 0.500 - @@data[MTAL1][:good][:joint ][:mat][ ""] = 1.000 - @@data[MTAL1][:good][:transition ][:mat][ ""] = 1.000 - - @@data[MTALD][ :bad][:id ] = MTALD_BAD - @@data[MTALD][ :bad][:rimjoist ][:mat][ "0"] = 1.000 - @@data[MTALD][ :bad][:parapet ][:mat][ "0"] = 1.000 - @@data[MTALD][ :bad][:head ][:mat]["139"] = 1.000 - @@data[MTALD][ :bad][:jamb ][:mat]["139"] = 1.000 - @@data[MTALD][ :bad][:sill ][:mat]["139"] = 1.000 - @@data[MTALD][ :bad][:corner ][:mat]["191"] = 1.000 - @@data[MTALD][ :bad][:balcony ][:mat][ ""] = 1.000 - @@data[MTALD][ :bad][:party ][:mat][ ""] = 1.000 - @@data[MTALD][ :bad][:grade ][:mat]["139"] = 0.500 - @@data[MTALD][ :bad][:joint ][:mat][ ""] = 1.000 - @@data[MTALD][ :bad][:transition ][:mat][ ""] = 1.000 - - @@data[MTALD][:good][:id ] = MTALD_GOOD - @@data[MTALD][:good][:rimjoist ][:mat][ "0"] = 1.000 - @@data[MTALD][:good][:parapet ][:mat][ "57"] = 3.300 - @@data[MTALD][:good][:parapet ][:mat]["139"] = 1.000 - @@data[MTALD][:good][:head ][:mat]["139"] = 0.500 - @@data[MTALD][:good][:jamb ][:mat]["139"] = 0.500 - @@data[MTALD][:good][:sill ][:mat]["139"] = 0.500 - @@data[MTALD][:good][:corner ][:mat]["191"] = 1.000 - @@data[MTALD][:good][:balcony ][:mat][ ""] = 1.000 - @@data[MTALD][:good][:party ][:mat][ ""] = 1.000 - @@data[MTALD][:good][:grade ][:mat]["192"] = 0.500 - @@data[MTALD][:good][:grade ][:mat]["139"] = 0.500 - @@data[MTALD][:good][:joint ][:mat][ ""] = 1.000 - @@data[MTALD][:good][:transition ][:mat][ ""] = 1.000 - - @@data[WOOD5][ :bad][:id ] = WOOD5_BAD - @@data[WOOD5][ :bad][:rimjoist ][:mat][ "21"] = 1.000 - @@data[WOOD5][ :bad][:rimjoist ][:mat]["172"] = 0.250 - @@data[WOOD5][ :bad][:parapet ][:mat][ "0"] = 1.000 - @@data[WOOD5][ :bad][:head ][:mat][ "0"] = 1.000 - @@data[WOOD5][ :bad][:jamb ][:mat][ "0"] = 1.000 - @@data[WOOD5][ :bad][:sill ][:mat][ "0"] = 1.000 - @@data[WOOD5][ :bad][:corner ][:mat][ "0"] = 1.000 - @@data[WOOD5][ :bad][:balcony ][:mat][ ""] = 1.000 - @@data[WOOD5][ :bad][:party ][:mat][ ""] = 1.000 - @@data[WOOD5][ :bad][:grade ][:mat][ "21"] = 1.000 - @@data[WOOD5][ :bad][:grade ][:mat]["139"] = 0.500 - @@data[WOOD5][ :bad][:joint ][:mat][ ""] = 1.000 - @@data[WOOD5][ :bad][:transition ][:mat][ ""] = 1.000 - - @@data[WOOD5][:good][:id ] = WOOD5_GOOD - @@data[WOOD5][:good][:rimjoist ][:mat]["189"] = 1.000 - @@data[WOOD5][:good][:rimjoist ][:mat]["172"] = 0.500 - @@data[WOOD5][:good][:parapet ][:mat]["190"] = 0.500 - @@data[WOOD5][:good][:head ][:mat][ "0"] = 1.000 - @@data[WOOD5][:good][:jamb ][:mat][ "0"] = 1.000 - @@data[WOOD5][:good][:sill ][:mat][ "0"] = 1.000 - @@data[WOOD5][:good][:corner ][:mat][ "0"] = 1.000 - @@data[WOOD5][:good][:balcony ][:mat][ ""] = 1.000 - @@data[WOOD5][:good][:party ][:mat][ ""] = 1.000 - @@data[WOOD5][:good][:grade ][:mat]["189"] = 1.000 - @@data[WOOD5][:good][:grade ][:mat]["139"] = 0.500 - @@data[WOOD5][:good][:grade ][:mat]["192"] = 0.500 - @@data[WOOD5][:good][:joint ][:mat][ ""] = 1.000 - @@data[WOOD5][:good][:transition ][:mat][ ""] = 1.000 - - @@data[WOOD7][ :bad][:id ] = WOOD7_BAD - @@data[WOOD7][ :bad][:rimjoist ][:mat][ "21"] = 1.000 - @@data[WOOD7][ :bad][:rimjoist ][:mat]["172"] = 0.250 - @@data[WOOD7][ :bad][:parapet ][:mat][ "0"] = 1.000 - @@data[WOOD7][ :bad][:head ][:mat][ "0"] = 1.000 - @@data[WOOD7][ :bad][:jamb ][:mat][ "0"] = 1.000 - @@data[WOOD7][ :bad][:sill ][:mat][ "0"] = 1.000 - @@data[WOOD7][ :bad][:corner ][:mat][ "0"] = 1.000 - @@data[WOOD7][ :bad][:balcony ][:mat][ ""] = 1.000 - @@data[WOOD7][ :bad][:party ][:mat][ ""] = 1.000 - @@data[WOOD7][ :bad][:grade ][:mat][ "21"] = 1.000 - @@data[WOOD7][ :bad][:grade ][:mat]["139"] = 0.500 - @@data[WOOD7][ :bad][:joint ][:mat][ ""] = 1.000 - @@data[WOOD7][ :bad][:transition ][:mat][ ""] = 1.000 - - @@data[WOOD7][:good][:id ] = WOOD7_GOOD - @@data[WOOD7][:good][:rimjoist ][:mat]["189"] = 1.000 - @@data[WOOD7][:good][:rimjoist ][:mat]["172"] = 0.500 - @@data[WOOD7][:good][:parapet ][:mat]["190"] = 0.500 - @@data[WOOD7][:good][:head ][:mat][ "0"] = 1.000 - @@data[WOOD7][:good][:jamb ][:mat][ "0"] = 1.000 - @@data[WOOD7][:good][:sill ][:mat][ "0"] = 1.000 - @@data[WOOD7][:good][:corner ][:mat][ "0"] = 1.000 - @@data[WOOD7][:good][:balcony ][:mat][ ""] = 1.000 - @@data[WOOD7][:good][:party ][:mat][ ""] = 1.000 - @@data[WOOD7][:good][:grade ][:mat]["189"] = 1.000 - @@data[WOOD7][:good][:grade ][:mat]["139"] = 0.500 - @@data[WOOD7][:good][:grade ][:mat]["192"] = 0.500 - @@data[WOOD7][:good][:joint ][:mat][ ""] = 1.000 - @@data[WOOD7][:good][:transition ][:mat][ ""] = 1.000 - - @@data[STEL1][ :bad][:id ] = STEL1_BAD - @@data[STEL1][ :bad][:rimjoist ][:mat]["139"] = 10.000 - @@data[STEL1][ :bad][:parapet ][:mat][ "0"] = 1.000 - @@data[STEL1][ :bad][:head ][:mat]["139"] = 0.750 - @@data[STEL1][ :bad][:jamb ][:mat]["139"] = 0.750 - @@data[STEL1][ :bad][:sill ][:mat]["139"] = 0.750 - @@data[STEL1][ :bad][:corner ][:mat][ "0"] = 1.000 - @@data[STEL1][ :bad][:balcony ][:mat][ ""] = 1.000 - @@data[STEL1][ :bad][:party ][:mat][ ""] = 1.000 - @@data[STEL1][ :bad][:grade ][:mat]["139"] = 1.000 - @@data[STEL1][ :bad][:joint ][:mat][ ""] = 1.000 - @@data[STEL1][ :bad][:transition ][:mat][ ""] = 1.000 - - @@data[STEL1][:good][:id ] = STEL1_GOOD - @@data[STEL1][:good][:rimjoist ][:mat]["172"] = 0.500 - @@data[STEL1][:good][:parapet ][:mat][ "57"] = 3.300 - @@data[STEL1][:good][:parapet ][:mat]["139"] = 1.000 - @@data[STEL1][:good][:head ][:mat]["139"] = 0.500 - @@data[STEL1][:good][:jamb ][:mat]["139"] = 0.500 - @@data[STEL1][:good][:sill ][:mat]["139"] = 0.500 - @@data[STEL1][:good][:corner ][:mat][ "0"] = 1.000 - @@data[STEL1][:good][:balcony ][:mat][ ""] = 1.000 - @@data[STEL1][:good][:party ][:mat][ ""] = 1.000 - @@data[STEL1][:good][:grade ][:mat]["192"] = 1.000 - @@data[STEL1][:good][:grade ][:mat]["139"] = 1.000 - @@data[STEL1][:good][:joint ][:mat][ ""] = 1.000 - @@data[STEL1][:good][:transition ][:mat][ ""] = 1.000 - - @@data[STEL2][ :bad][:id ] = STEL2_BAD - @@data[STEL2][ :bad][:rimjoist ][:mat]["139"] = 10.000 - @@data[STEL2][ :bad][:parapet ][:mat][ "0"] = 1.000 - @@data[STEL2][ :bad][:head ][:mat]["139"] = 0.750 - @@data[STEL2][ :bad][:jamb ][:mat]["139"] = 0.750 - @@data[STEL2][ :bad][:sill ][:mat]["139"] = 0.750 - @@data[STEL2][ :bad][:corner ][:mat][ "0"] = 1.000 - @@data[STEL2][ :bad][:balcony ][:mat][ ""] = 1.000 - @@data[STEL2][ :bad][:party ][:mat][ ""] = 1.000 - @@data[STEL2][ :bad][:grade ][:mat]["139"] = 1.000 - @@data[STEL2][ :bad][:joint ][:mat][ ""] = 1.000 - @@data[STEL2][ :bad][:transition ][:mat][ ""] = 1.000 - - @@data[STEL2][:good][:id ] = STEL2_GOOD - @@data[STEL2][:good][:rimjoist ][:mat]["172"] = 0.500 - @@data[STEL2][:good][:parapet ][:mat]["206"] = 1.000 - @@data[STEL2][:good][:head ][:mat]["139"] = 0.500 - @@data[STEL2][:good][:jamb ][:mat]["139"] = 0.500 - @@data[STEL2][:good][:sill ][:mat]["139"] = 0.500 - @@data[STEL2][:good][:corner ][:mat][ "0"] = 1.000 - @@data[STEL2][:good][:balcony ][:mat][ ""] = 1.000 - @@data[STEL2][:good][:party ][:mat][ ""] = 1.000 - @@data[STEL2][:good][:grade ][:mat]["192"] = 1.000 - @@data[STEL2][:good][:grade ][:mat]["139"] = 1.000 - @@data[STEL2][:good][:joint ][:mat][ ""] = 1.000 - @@data[STEL2][:good][:transition ][:mat][ ""] = 1.000 # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- # ## - # Retrieve TBD building/space type keyword. + # Retrieve TBD building/space type keyword (to revise @todo). # # @param spacetype [String] NECB (or other) building/space type # @param stories [Integer] number of building stories @@ -1178,7 +784,7 @@ def spacetype(sptype = "", stories = 999) end ## - # Retrieve building/space type-specific assembly/construction. + # Retrieve building/space type-specific assembly/construction (to revise @todo). # # @param sptype [Symbol] BTAP/TBD spacetype # @param stypes [Symbol] :walls, :floors or :roofs @@ -1256,18 +862,16 @@ def set(assembly = STEL2, quality = :good) chx = @@data[assembly][:good ] unless @@data[assembly].key?(quality) end - psi[:id ] = chx[:id ] - psi[:rimjoist ] = chx[:rimjoist ][:psi] - psi[:parapet ] = chx[:parapet ][:psi] - psi[:head ] = chx[:head ][:psi] - psi[:jamb ] = chx[:jamb ][:psi] - psi[:sill ] = chx[:sill ][:psi] - psi[:corner ] = chx[:corner ][:psi] - psi[:balcony ] = chx[:balcony ][:psi] - psi[:party ] = chx[:party ][:psi] - psi[:grade ] = chx[:grade ][:psi] - psi[:joint ] = chx[:joint ][:psi] - psi[:transition] = chx[:transition][:psi] + psi[:id ] = chx[:id ] + psi[:rimjoist ] = chx[:rimjoist ][:psi] + psi[:parapet ] = chx[:parapet ][:psi] + psi[:fenestration] = chx[:fenestration][:psi] + psi[:door ] = chx[:door ][:psi] + psi[:corner ] = chx[:corner ][:psi] + psi[:balcony ] = chx[:balcony ][:psi] + psi[:party ] = chx[:party ][:psi] + psi[:grade ] = chx[:grade ][:psi] + psi[:joint ] = chx[:joint ][:psi] psi end @@ -1329,8 +933,8 @@ def initialize(model = nil, argh = {}) # based on OpenStudio-Standards), and so TBD ends up tagging such spaces # as unconditioned. Consequently, TBD attempts to up/de-rate attic floors # - not sloped roof surfaces. The upstream BTAP solution will undoubtedly - # need revision. In the meantime, and in an effort to harmonize TBD with - # BTAP's current approach, an OpenStudio model may be temporarily + # need revision (@todo). In the meantime, and in an effort to harmonize TBD + # with BTAP's current approach, an OpenStudio model may be temporarily # modified prior to TBD processes, ensuring that each attic space is # temporarily mistaken as a conditioned plenum. The return variable of the # following method is a Hash holding temporarily-modified spaces, @@ -1346,7 +950,7 @@ def initialize(model = nil, argh = {}) initial = true complies = false comply = {} # specific to :walls, :floors & :roofs - perform = :lp # Low-performance wall constructions (revise, TO-DO ...) + perform = :lp # Low-performance wall constructions (revise, @todo) quality = :bad # default PSI factors - BTAP users can reset to :good quality = :good if argh.key?(:quality) && argh[:quality] == :good combo = "#{perform.to_s}_#{quality.to_s}".to_sym # e.g. :lp_bad @@ -1502,7 +1106,7 @@ def initialize(model = nil, argh = {}) next unless construction[:stypes ] == stypes next if construction[:surfaces].empty? - construction[:surfaces].values.each { |surface| surface.setConstruction(construction[:uo]) } + BTAP::Geometry::Surfaces.set_surfaces_construction_conductance(construction[:surfaces].values, construction[:uo]) end end @@ -1682,8 +1286,8 @@ def populate(model = nil, argh = {}) stories = model.getBuildingStorys.size unless stories.is_a?(Integer) @model[:stories] = stories - @model[:stories] = 1 if stories < 1 - @model[:stories] = 999 if stories > 999 + @model[:stories] = 1 if stories < 1 + @model[:stories] = 999 if stories > 999 @model[:spaces ] = {} @model[:sptypes] = {} @@ -1770,7 +1374,7 @@ def populate(model = nil, argh = {}) return false unless ok argh[stypes][:uo] = uo - next unless argh[stypes].key?(:ut) + next unless argh[stypes].key?(:ut) argh[stypes][:ut] = uo end @@ -1831,7 +1435,7 @@ def inputs(perform = :hp, quality = :good) schema = "https://github.com/rd2/tbd/blob/master/tbd.schema.json" input[:schema ] = schema - input[:description] = "TBD input for BTAP" # append run # ? + input[:description] = "TBD input for BTAP" # append run # ? input[:psis ] = psis.values @model[:sptypes].values.each do |sptype| @@ -1960,14 +1564,12 @@ def gen_feedback def get_material_quantities() material_quantities = {} csv = CSV.read("#{File.dirname(__FILE__)}/../../../data/inventory/thermal_bridging.csv", headers: true) - tally_edges = @tally[:edges].transform_keys(&:to_s) + tally_edges = @tally[:edges].transform_keys(&:to_s) - #tally_edges = JSON.parse('{"edges":{"jamb":{"BTAP-ExteriorWall-SteelFramed-1 good":13.708557548340757},"sill":{"BTAP-ExteriorWall-SteelFramed-1 good":90.13000000000001},"head":{"BTAP-ExteriorWall-SteelFramed-1 good":90.13000000000001},"gradeconvex":{"BTAP-ExteriorWall-SteelFramed-1 good":90.4348},"parapetconvex":{"BTAP-ExteriorWall-SteelFramed-1 good":45.2174},"parapet":{"BTAP-ExteriorWall-SteelFramed-1 good":45.2174},"transition":{"BTAP-ExteriorWall-SteelFramed-1 good":71.16038874419307},"cornerconvex":{"BTAP-ExteriorWall-SteelFramed-1 good":12.1952}}}')['edges'] tally_edges.each do |edge_type_full, value| edge_type = edge_type_full.delete_suffix('convex') - if ['head', 'jamb', 'sill'].include?(edge_type) - edge_type = 'fenestration' - end + edge_type = 'fenestration' if ['head', 'jamb', 'sill'].include?(edge_type) + value.each do |wall_ref_and_quality, quantity| /(.*)\s(.*)/ =~ wall_ref_and_quality wall_reference = $1 @@ -1977,14 +1579,13 @@ def get_material_quantities() wall_reference = 'BTAP-ExteriorWall-SteelFramed-2' end - if edge_type == 'transition' - next - end + next if edge_type == 'transition' result = csv.find { |row| row['edge_type'] == edge_type && row['quality'] == quality && row['wall_reference'] == wall_reference } + if result.nil? puts ("#{edge_type}-#{wall_reference}-#{quality}") puts "not found in tb database" @@ -1996,23 +1597,21 @@ def get_material_quantities() id_layers_quantity_multipliers = result['id_layers_quantity_multipliers'].split(",") material_opaque_id_layers.zip(id_layers_quantity_multipliers).each do |id, scale| - if material_quantities[id].nil? then material_quantities[id] = 0.0 end + material_quantities[id] = 0.0 if material_quantities[id].nil? material_quantities[id] = material_quantities[id] + scale.to_f * quantity.to_f end end end + material_opaque_id_quantities = [] + material_quantities.each do |id,quantity| material_opaque_id_quantities << { 'materials_opaque_id' => id, 'quantity' => quantity, 'domain'=> 'thermal_bridging' } end return material_opaque_id_quantities end - - end - - end # ----- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----- # @@ -2097,7 +1696,7 @@ def get_material_quantities() # default fenestration layout. As a result, BTAP/TBD presumes continuous # shelf angles, offset by the height difference between slab edge and # window head. Loose lintels are however included in the clear field -# costing ($/m2), yet should be limited to doors (TO-DO). A more flexible, +# costing ($/m2), yet should be limited to doors (@todo). A more flexible, # general solution would be required for 3rd-party OpenStudio models # (without strip windows as a basic fenestration layout). # @@ -2106,7 +1705,7 @@ def get_material_quantities() # BTAP costing requires extending the areas (m2) of OpenStudio wall # surfaces (along parapet edges) by 3'-6" (1.1 m) x parapet lengths, to # account for the extra cost of completely wrapping the parapet in -# insulation for "good" (HP) details. See final TBD tally. TO-DO. +# insulation for "good" (HP) details. See final TBD tally, @todo. # # NOTE: Overview of current BTAP building/space type construction link, e.g.: # @@ -2115,4 +1714,4 @@ def get_material_quantities() # # ... yet all (public) washrooms, corridors, stairwells, etc. are # steel-framed (regardless of building type). Overview of possible fixes. -# TO-DO. \ No newline at end of file +# @todo. diff --git a/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb b/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb index 976853c958..aaf9f48a48 100644 --- a/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb +++ b/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb @@ -45,21 +45,24 @@ def load_standards_database_new end # Thermal zones need to be set to determine conditioned spaces when applying fdwr and srr limits. - # # fdwr_set/srr_set settings: - # # 0-1: Remove all windows/skylights and add windows/skylights to match this fdwr/srr - # # -1: Remove all windows/skylights and add windows/skylights to match max fdwr/srr from NECB - # # -2: Do not apply any fdwr/srr changes, leave windows/skylights alone (also works for fdwr/srr > 1) - # # -3: Use old method which reduces existing window/skylight size (if necessary) to meet maximum NECB fdwr/srr - # # limit - # # <-3.1: Remove all the windows/skylights - # # > 1: Do nothing - def apply_fdwr_srr_daylighting(model:, fdwr_set: -2.0, srr_set: -2.0, necb_hdd: true) + # + # fdwr_set/srr_set settings: + # 0-1: Remove all windows/skylights and add windows/skylights to match this fdwr/srr + # -1: Remove all windows/skylights and add windows/skylights to match max fdwr/srr from NECB + # -2: Do not apply any fdwr/srr changes, leave windows/skylights alone (also works for fdwr/srr > 1) + # -3: Use old method which reduces existing window/skylight size (if necessary) to meet maximum NECB fdwr/srr limit + # <-3.1: Remove all the windows/skylights + # > 1: Do nothing + # + # By default, :srr_opt is an empty string (" "). If set to "osut", SRR is + # instead met using OSut's 'addSkylights' (:srr_set numeric values may apply). + def apply_fdwr_srr_daylighting(model:, fdwr_set: -2.0, srr_set: -2.0, necb_hdd: true, srr_opt: '') fdwr_set = -2.0 if (fdwr_set == 'NECB_default') || fdwr_set.nil? || (fdwr_set.to_f.round(0) == -1.0) srr_set = -2.0 if (srr_set == 'NECB_default') || srr_set.nil? || (srr_set.to_f.round(0) == -1.0) fdwr_set = fdwr_set.to_f srr_set = srr_set.to_f apply_standard_window_to_wall_ratio(model: model, fdwr_set: fdwr_set, necb_hdd: true) - apply_standard_skylight_to_roof_ratio(model: model, srr_set: srr_set) + apply_standard_skylight_to_roof_ratio(model: model, srr_set: srr_set, srr_opt: srr_opt) # model_add_daylighting_controls(model) # to be removed after refactor. end diff --git a/lib/openstudio-standards/standards/necb/BTAPPRE1980/building_envelope.rb b/lib/openstudio-standards/standards/necb/BTAPPRE1980/building_envelope.rb index 49fd67da50..03a4e83d13 100644 --- a/lib/openstudio-standards/standards/necb/BTAPPRE1980/building_envelope.rb +++ b/lib/openstudio-standards/standards/necb/BTAPPRE1980/building_envelope.rb @@ -24,22 +24,23 @@ def apply_standard_window_to_wall_ratio(model:, fdwr_set: -1.0, necb_hdd: true) # Reduces the SRR to the values specified by the PRM. SRR reduction # will be done by shrinking vertices toward the centroid. - # - def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0) + def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0, srr_opt: '') # If srr_set is between 1.0 and 1.2 set it to the maximum allowed by the NECB. If srr_set is between 0.0 and 1.0 # apply whatever was passed. If srr_set >= 1.2 then set the existing srr of the building to be the necb maximum # only if the the srr exceeds this maximum (otherwise leave it to be whatever was modeled). - + # # srr_set settings: - # 0-1: Remove all skylights and add skylights to match this srr - # -1: Remove all skylights and add skylights to match max srr from NECB - # -2: Do not apply any srr changes, leave skylights alone (also works for srr > 1) - # -3: Use old method which reduces existing skylight size (if necessary) to meet maximum NECB skylight limit - # <-3.1: Remove all the skylights - # > 1: Do nothing - + # 0-1: Remove all skylights and add skylights to match this srr + # -1: Remove all skylights and add skylights to match max srr from NECB + # -2: Do not apply any srr changes, leave skylights alone (also works for srr > 1) + # -3: Use old method which reduces existing skylight size (if necessary) to meet maximum NECB skylight limit + # <-3.1: Remove all skylights + # > 1: Do nothing + # + # By default, :srr_opt is an empty string (" "). If set to "osut", SRR is + # instead met using OSut's addSkylights (:srr_set numeric values may apply). return if srr_set.to_f > 1.0 - return apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f) if srr_set.to_f >= 0.0 && srr_set <= 1.0 + return apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f, srr_opt: srr_opt) if srr_set.to_f >= 0.0 && srr_set <= 1.0 # No skylights set for BTAPPRE1980 buildings. return if srr_set.to_f >= -1.1 && srr_set <= -0.9 diff --git a/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb b/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb index 11d6982f11..3e226557c7 100644 --- a/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +++ b/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb @@ -160,26 +160,29 @@ def apply_limit_fdwr(model:, fdwr_lim:) # Reduces the SRR to the values specified by the PRM. SRR reduction # will be done by shrinking vertices toward the centroid. - # - def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0) + def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0, srr_opt: '') # If srr_set is between 1.0 and 1.2 set it to the maximum allowed by the NECB. If srr_set is between 0.0 and 1.0 # apply whatever was passed. If srr_set >= 1.2 then set the existing srr of the building to be the necb maximum # only if the the srr exceeds this maximum (otherwise leave it to be whatever was modeled). - + # # srr_set settings: - # 0-1: Remove all skylights and add skylights to match this srr - # -1: Remove all skylights and add skylights to match max srr from NECB - # -2: Do not apply any srr changes, leave skylights alone (also works for srr > 1) - # -3: Use old method which reduces existing skylight size (if necessary) to meet maximum NECB skylight limit + # 0-1: Remove all skylights and add skylights to match this srr + # -1: Remove all skylights and add skylights to match max srr from NECB + # -2: Do not apply any srr changes, leave skylights alone (also works for srr > 1) + # -3: Use old method which reduces existing skylight size (if necessary) to meet maximum NECB skylight limit # <-3.1: Remove all skylights - # > 1: Do nothing - + # > 1: Do nothing + # + # By default, :srr_opt is an empty string (" "). If set to "osut", SRR is + # instead met using OSut's addSkylights (:srr_set numeric values may apply). return if srr_set.to_f > 1.0 - return apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f) if srr_set.to_f >= 0.0 && srr_set.to_f <= 1.0 + return apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f, srr_opt: srr_opt) if srr_set.to_f >= 0.0 && srr_set <= 1.0 + # Get the maximum NECB srr - return apply_max_srr_nrcan(model: model, srr_lim: get_standards_constant('skylight_to_roof_ratio_max_value').to_f) if srr_set.to_f >= -1.1 && srr_set.to_f <= -0.9 + return apply_max_srr_nrcan(model: model, srr_lim: get_standards_constant('skylight_to_roof_ratio_max_value').to_f, srr_opt: srr_opt) if srr_set.to_f >= -1.1 && srr_set.to_f <= -0.9 + return if srr_set.to_f >= -2.1 && srr_set.to_f <= -1.9 - return apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f) if srr_set.to_f < -3.1 + return apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f, srr_opt: srr_opt) if srr_set.to_f < -3.1 return unless srr_set.to_f >= -3.1 && srr_set.to_f <= -2.9 # SRR limit @@ -197,6 +200,7 @@ def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0) sh_sky_m2 = 0 total_roof_m2 = 0.001 total_subsurface_m2 = 0 + model.getSpaces.sort.each do |space| # Loop through all surfaces in this space wall_area_m2 = 0 @@ -724,44 +728,118 @@ def apply_max_fdwr_nrcan(model:, fdwr_lim:) return true end - # This method is similar to the 'apply_max_fdwr' method above but applies the maximum skylight to roof area ratio to a - # building as per NECB 2011 8.4.4.3 and 3.2.1.4 (or equivalent in other versions of the NECB). It first checks for all - # exterior roofs adjacent to conditioned spaces. It distinguishes between plenums and other conditioned spaces. It - # uses only the non-plenum roof area to calculate the maximum skylight area to be applied to the building. - def apply_max_srr_nrcan(model:, srr_lim:) - # First determine which roof surfaces are adjacent to heated spaces (both plenum and non-plenum). - exp_surf_info = find_exposed_conditioned_roof_surfaces(model) - # If the non-plenum roof area is very small raise a warning. It may be perfectly fine but it is probably a good - # idea to warn the user. - if exp_surf_info['exp_nonplenum_roof_area_m2'] < 0.1 - OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'This building has no exposed ceilings adjacent to spaces that are not attics or plenums. No skylights will be added.') - return false - end + # This method is similar to the 'apply_max_fdwr' method above, but applies a maximum skylight-to-roof-area-ratio (SRR) + # to a building model, as per NECB 2011 8.4.4.3 and 3.2.1.4 (or equivalent in other NECB vintages). There are 2 options: + # + # OPTION A: Default, initial BTAP solution. It first checks for all exterior roofs adjacent to conditioned spaces. + # It distinguishes between plenums and other conditioned spaces. It uses only the non-plenum roof area to + # calculate the maximum skylight area to be applied to the building. + # + # OPTION B: Selected if srr_opt == 'osut'. OSut's 'addSkylights' attempts to meet the requested SRR% target, even if + # occupied spaces to toplight are under unoccupied plenums or attics - skylight wells are added if needed. + # With attics, skylight well walls are considered part of the 'building envelope' (and therefore insulated + # like exterior walls). The method returns a building 'gross roof area' (see attr_reader :osut), which + # excludes the area of attic roof overhangs. + def apply_max_srr_nrcan(model:, srr_lim:, srr_opt: '') + construct_set = model.getBuilding.defaultConstructionSet.get + skylight_construct_set = construct_set.defaultExteriorSubSurfaceConstructions.get.skylightConstruction.get - # If the SRR is greater than one something is seriously wrong so raise an error. If it is less than 0.001 assume - # all the skylights should go. - if srr_lim > 1 - OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'This building requires a larger skylight area than there is roof area.') - return false - elsif srr_lim < 0.001 - exp_surf_info['exp_nonplenum_roofs'].sort.each do |exp_surf| - exp_surf.subSurfaces.sort.each(&:remove) + unless srr_opt.to_s.downcase == 'osut' # OPTION A + # First determine which roof surfaces are adjacent to heated spaces (both plenum and non-plenum). + exp_surf_info = find_exposed_conditioned_roof_surfaces(model) + # If the non-plenum roof area is very small raise a warning. It may be perfectly fine but it is probably a good + # idea to warn the user. + if exp_surf_info['exp_nonplenum_roof_area_m2'] < 0.1 + OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'This building has no exposed ceilings adjacent to spaces that are not attics or plenums. No skylights will be added.') + return false end - return true - end - construct_set = model.getBuilding.defaultConstructionSet.get - skylight_construct_set = construct_set.defaultExteriorSubSurfaceConstructions.get.skylightConstruction.get + # If the SRR is greater than one something is seriously wrong so raise an error. If it is less than 0.001 assume + # all the skylights should go. + if srr_lim > 1 + OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'This building requires a larger skylight area than there is roof area.') + return false + elsif srr_lim < 0.001 + exp_surf_info['exp_nonplenum_roofs'].sort.each do |exp_surf| + exp_surf.subSurfaces.sort.each(&:remove) + end + return true + end + + # Go through all of exposed roofs adjacent to heated, non-plenum spaces, remove any existing subsurfaces, and add + # a skylight in the centroid of the surface, with the same shape of the surface, only scaled to be the area + # determined by the SRR. The name of the skylight will be the surface name with the subsurface type attached + # ('skylight' in this case). Note that this method will only work if the surface does not fold into itself (like an + # L or a V). + exp_surf_info['exp_nonplenum_roofs'].sort.each do |roof| + # sub_surface_create_centered_subsurface_from_scaled_surface(roof, srr_lim, model) + sub_surface_create_scaled_subsurfaces_from_surface(surface: roof, area_fraction: srr_lim, construction: skylight_construct_set) + end + else # OPTION B + TBD.clean! + spaces = model.getSpaces + types = model.getSpaceTypes + roofs = TBD.facets(spaces, "Outdoors", "RoofCeiling") + + # A model's 'nominal' gross roof area (gra0) may be greater than its + # 'effective' gross roof area (graX). For instance, the "SmallOffice" + # prototype model has (unconditioned) attic roof overhangs that end up + # tallied as a gross roof area in OpenStudio and EnergyPlus. See: + # + # github.com/rd2/osut/blob/117c7dceb59fd8aab771da8ba672c14c97d23bd0 + # /lib/osut/utils.rb#L6268 + # + gra0 = roofs.sum(&:grossArea) # nominal gross roof area + graX = TBD.grossRoofArea(spaces) # effective gross roof area + + unless gra0.round > 0 + msg = 'Invalid nominal gross roof area. No skylights will be added.' + OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', msg) + return false + end + + unless graX.round > 0 + msg = 'Invalid effective gross roof area. No skylights will be added.' + OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', msg) + return false + end - # Go through all of exposed roofs adjacent to heated, non-plenum spaces, remove any existing subsurfaces, and add - # a skylight in the centroid of the surface, with the same shape of the surface, only scaled to be the area - # determined by the SRR. The name of the skylight will be the surface name with the subsurface type attached - # ('skylight' in this case). Note that this method will only work if the surface does not fold into itself (like an - # L or a V). - exp_surf_info['exp_nonplenum_roofs'].sort.each do |roof| - # sub_surface_create_centered_subsurface_from_scaled_surface(roof, srr_lim, model) - sub_surface_create_scaled_subsurfaces_from_surface(surface: roof, area_fraction: srr_lim, construction: skylight_construct_set) + self.osut[:gra0] = gra0 + self.osut[:graX] = graX + + # Relying on the total area of attic roof surfaces (for SRR%) exagerrates + # the requested skylight area, often by 10% to 15%. This makes it unfair + # for NECBs, and more challenging when dealing with skylight wells. This + # issue only applies with attics - not plenums. Trim down SRR if required. + target = srr_lim * graX + + # Filtering out tiny roof surfaces, twisty corridors, etc. + types = types.reject { |tp| tp.nameString.downcase.include?("undefined") } + types = types.reject { |tp| tp.nameString.downcase.include?("mech" ) } + types = types.reject { |tp| tp.nameString.downcase.include?("elec" ) } + types = types.reject { |tp| tp.nameString.downcase.include?("toilet" ) } + types = types.reject { |tp| tp.nameString.downcase.include?("locker" ) } + types = types.reject { |tp| tp.nameString.downcase.include?("shower" ) } + types = types.reject { |tp| tp.nameString.downcase.include?("washroom" ) } + types = types.reject { |tp| tp.nameString.downcase.include?("corr" ) } + types = types.reject { |tp| tp.nameString.downcase.include?("stair" ) } + + spaces = spaces.reject { |sp| sp.spaceType.empty? } + spaces = spaces.select { |sp| types.include?(sp.spaceType.get) } + + TBD.addSkyLights(spaces, {area: target}) + self.osut[:logs] = TBD.logs + + skys = TBD.facets(model.getSpaces, "Outdoors", "Skylight") + skm2 = skys.sum(&:grossArea) + + unless skm2.round == target.round + msg = "Skylights m2: failed to meet #{target.round} (vs #{skm2.round})" + OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', msg) + return false + end end + return true end end diff --git a/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb b/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb index 935bb0f120..cdb7f21566 100644 --- a/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +++ b/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb @@ -7,6 +7,7 @@ class NECB2011 < Standard @template = new.class.name register_standard(@template) attr_reader :tbd + attr_reader :osut attr_reader :template attr_accessor :standards_data attr_accessor :space_type_map @@ -149,6 +150,8 @@ def initialize @standards_data = load_standards_database_new corrupt_standards_database @tbd = nil + @osut = {gra0: 0, graX: 0, status: 0, logs: []} + # puts "loaded these tables..." # puts @standards_data.keys.size # raise("tables not all loaded in parent #{}") if @standards_data.keys.size < 24 @@ -208,7 +211,7 @@ def get_necb_hdd18(model:, necb_hdd: true) end end - # This method is a wrapper to create the 16 archetypes easily. # 55 args + # This method is a wrapper to create the 16 archetypes easily. def model_create_prototype_model(template:, building_type:, epw_file:, @@ -245,6 +248,7 @@ def model_create_prototype_model(template:, rotation_degrees: nil, fdwr_set: -1.0, srr_set: -1.0, + srr_opt: '', nv_type: nil, nv_opening_fraction: nil, nv_temp_out_min: nil, @@ -272,7 +276,38 @@ def model_create_prototype_model(template:, necb_hdd: true, boiler_fuel: nil, boiler_cap_ratio: nil) + model = load_building_type_from_library(building_type: building_type) + + # Tag spaces as un/conditioned with "space_conditioning_category". For now, + # this is simply determined based on whether spaces are: + # - part of the total floor area (i.e. occupied) + # - have "attic" included in their identifiers (i.e. unconditioned) + # + # As per ASHRE 90.1, OpenStudio-Standards distinguishes between: + # - "nonresconditioned" vs + # - "nonresconditioned" + # + # Sticking to "nonresconditioned" - NECBs don't care. This could be further + # refined in future BTAP versions though, e.g.: + # - relying on user-defined thermostats + # - expanded to cover semi-heated and refrigerated spaces + tag = "space_conditioning_category" + + model.getSpaces.each do |space| + next unless space.additionalProperties.getFeatureAsString(tag).empty? + + if space.partofTotalFloorArea + space.additionalProperties.setFeature(tag, "nonresconditioned") + else + if space.nameString.downcase.include?("attic") + space.additionalProperties.setFeature(tag, "unconditioned") + else # treat all other cases as indirectly-conditioned e.g. plenums + space.additionalProperties.setFeature(tag, "nonresconditioned") + end + end + end + return model_apply_standard(model: model, tbd_option: tbd_option, tbd_interpolate: tbd_interpolate, @@ -309,6 +344,7 @@ def model_create_prototype_model(template:, rotation_degrees: rotation_degrees, fdwr_set: fdwr_set, srr_set: srr_set, + srr_opt: srr_opt, nv_type: nv_type, # Two options: (1) nil/none/false/'NECB_Default', (2) 'add_nv' nv_opening_fraction: nv_opening_fraction, # options: (1) nil/none/false (2) 'NECB_Default' (i.e. 0.1), (3) opening fraction of windows, which can be a float number between 0.0 and 1.0 nv_temp_out_min: nv_temp_out_min, # options: (1) nil/none/false(2) 'NECB_Default' (i.e. 13.0 based on inputs from Michel Tardif re a real school in QC), (3) minimum outdoor air temperature (in Celsius) below which natural ventilation is shut down @@ -386,6 +422,7 @@ def model_apply_standard(model:, skylight_solar_trans: nil, fdwr_set: nil, srr_set: nil, + srr_opt: '', rotation_degrees: nil, scale_x: nil, scale_y: nil, @@ -420,6 +457,7 @@ def model_apply_standard(model:, clean_and_scale_model(model: model, rotation_degrees: rotation_degrees, scale_x: scale_x, scale_y: scale_y, scale_z: scale_z) fdwr_set = convert_arg_to_f(variable: fdwr_set, default: -1) srr_set = convert_arg_to_f(variable: srr_set, default: -1) + srr_opt = convert_arg_to_string(variable: srr_opt, default: '') necb_hdd = convert_arg_to_bool(variable: necb_hdd, default: true) boiler_fuel = convert_arg_to_string(variable: boiler_fuel, default: nil) boiler_cap_ratio = convert_arg_to_string(variable: boiler_cap_ratio, default: nil) @@ -460,7 +498,8 @@ def model_apply_standard(model:, apply_fdwr_srr_daylighting(model: model, fdwr_set: fdwr_set, srr_set: srr_set, - necb_hdd: necb_hdd) + necb_hdd: necb_hdd, + srr_opt: srr_opt) apply_thermal_bridging(model: model, tbd_option: tbd_option, tbd_interpolate: tbd_interpolate, @@ -968,35 +1007,27 @@ def three_vertices_same_line_and_dir?(vert1,vert2,vert3) end # Thermal zones need to be set to determine conditioned spaces when applying fdwr and srr limits. - # # fdwr_set/srr_set settings: - # # 0-1: Remove all windows/skylights and add windows/skylights to match this fdwr/srr - # # -1: Remove all windows/skylights and add windows/skylights to match max fdwr/srr from NECB - # # -2: Do not apply any fdwr/srr changes, leave windows/skylights alone (also works for fdwr/srr > 1) - # # -3: Use old method which reduces existing window/skylight size (if necessary) to meet maximum NECB fdwr/srr - # # limit - # # <-3.1: Remove all the windows/skylights - # # > 1: Do nothing - def apply_fdwr_srr_daylighting(model:, fdwr_set: -1.0, srr_set: -1.0, necb_hdd: true) + # + # fdwr_set/srr_set settings: + # 0-1: Remove all windows/skylights and add windows/skylights to match this fdwr/srr + # -1: Remove all windows/skylights and add windows/skylights to match max fdwr/srr from NECB + # -2: Do not apply any fdwr/srr changes, leave windows/skylights alone (also works for fdwr/srr > 1) + # -3: Use old method which reduces existing window/skylight size (if necessary) to meet maximum NECB fdwr/srr limit + # <-3.1: Remove all the windows/skylights + # > 1: Do nothing + # + # By default, :srr_opt is an empty string (" "). If set to "osut", SRR is + # instead met using OSut's 'addSkylights' (:srr_set numeric values may apply). + def apply_fdwr_srr_daylighting(model:, fdwr_set: -1.0, srr_set: -1.0, necb_hdd: true, srr_opt: '') fdwr_set = -1.0 if (fdwr_set == 'NECB_default') || fdwr_set.nil? srr_set = -1.0 if (srr_set == 'NECB_default') || srr_set.nil? fdwr_set = fdwr_set.to_f srr_set = srr_set.to_f apply_standard_window_to_wall_ratio(model: model, fdwr_set: fdwr_set, necb_hdd: necb_hdd) - apply_standard_skylight_to_roof_ratio(model: model, srr_set: srr_set) + apply_standard_skylight_to_roof_ratio(model: model, srr_set: srr_set, srr_opt: srr_opt) # model_add_daylighting_controls(model) # to be removed after refactor. end - ## - # Optionally uprates, then derates, envelope surfaces due to MAJOR thermal - # bridges (e.g. roof parapets, corners, fenestration perimeters). See - # lib/openstudio-standards/btap/bridging.rb, which relies on the Thermal - # Bridging & Derating (TBD) gem. - # - # @param model [OpenStudio::Model::Model] an OpenStudio model - # @param tbd_option [String] BTAP/TBD option - # - # @return [Boolean] true if successful, e.g. no errors, compliant if uprated - ## # (Optionally) uprates, then derates, envelope surface constructions due to # MAJOR thermal bridges (e.g. roof parapets, corners, fenestration @@ -2495,5 +2526,3 @@ def set_boiler_cap_ratios(boiler_cap_ratio:, boiler_fuel:) return boiler_cap_ratios end end - - diff --git a/openstudio-standards.gemspec b/openstudio-standards.gemspec index dcf7e88528..93335582bd 100644 --- a/openstudio-standards.gemspec +++ b/openstudio-standards.gemspec @@ -51,7 +51,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rubyXL', '~> 3.4' spec.add_development_dependency 'simplecov', '0.22.0' spec.add_development_dependency 'yard', '~> 0.9' - spec.add_runtime_dependency 'tbd', '~> 3' + spec.add_development_dependency 'tbd', '~> 3.4.4' spec.add_development_dependency 'aws-sdk-s3' spec.add_development_dependency 'git-revision' spec.add_development_dependency 'bundler-audit' diff --git a/test/necb/unit_tests/tests/test_NECB_TBD.rb b/test/necb/unit_tests/tests/test_NECB_TBD.rb index 0315ca4d7e..6f400a13c8 100644 --- a/test/necb/unit_tests/tests/test_NECB_TBD.rb +++ b/test/necb/unit_tests/tests/test_NECB_TBD.rb @@ -20,26 +20,26 @@ def test_necb_tbd() #Range of test options. @templates = [ 'NECB2011', - # 'NECB2015', - # 'NECB2017' + 'NECB2015', + 'NECB2017' ] @epws = ['CAN_AB_Calgary.Intl.AP.718770_CWEC2020.epw'] @buildings = [ 'FullServiceRestaurant', - # 'HighriseApartment', - # 'Hospital', - # 'LargeHotel', - # 'LargeOffice', - # 'MediumOffice', - # 'MidriseApartment', - # 'Outpatient', - # 'PrimarySchool', - # 'QuickServiceRestaurant', - # 'RetailStandalone', - # 'SecondarySchool', - # 'SmallHotel', + 'HighriseApartment', + 'Hospital', + 'LargeHotel', + 'LargeOffice', + 'MediumOffice', + 'MidriseApartment', + 'Outpatient', + 'PrimarySchool', + 'QuickServiceRestaurant', + 'RetailStandalone', + 'SecondarySchool', + 'SmallHotel', 'Warehouse' ] @@ -196,4 +196,4 @@ def test_necb_tbd() # end end -end \ No newline at end of file +end diff --git a/test/necb/unit_tests/tests/test_necb_skylights.rb b/test/necb/unit_tests/tests/test_necb_skylights.rb new file mode 100644 index 0000000000..a6453718e6 --- /dev/null +++ b/test/necb/unit_tests/tests/test_necb_skylights.rb @@ -0,0 +1,181 @@ +require_relative '../../../helpers/minitest_helper' +require_relative '../../../helpers/create_doe_prototype_helper' +require 'json' + +# This checks that skylight wells are correctly autogenerated in BTAP. +class NECB_Skylights_Tests < Minitest::Test + def test_necb_skylights() + outd = 'output/test_necb_skylights' + eres = '../expected_results/necb_skylights_expected_results.json' + tres = '../expected_results/necb_skylights_test_results.json' + sizd = 'sizing_folder' + + # File/folder paths. + @output_folder = File.join(__dir__, outd) + @expected_results_file = File.join(__dir__, eres) + @test_results_file = File.join(__dir__, tres) + @sizing_run_dir = File.join(@output_folder, sizd) + @test_results_array = [] + + # Intial test condition. + @test_passed = true + + @epws = ['CAN_AB_Calgary.Intl.AP.718770_CWEC2020.epw'] + + # Skylight wells are autogenerated by OSut's 'addSkylights'. OSut is a TBD + # extended dependency, called in BTAP as: "TBD.addSkylights()". This is + # enabled in BTAP by passing the optional :srr_opt = 'osut', which is by + # default an empty string. This leaves the door open for future options. + @options = ['osut'] + + # Tested models are limited to NECB2011 Prototypes that hold unoccupied + # spaces below roofs (e.g. attics or plenums). Both require skylight wells + # to toplight occupied spaces below. + @buildings = [ + 'FullServiceRestaurant', + 'LargeOffice', + 'MediumOffice', + # 'NorthernEducation', + 'QuickServiceRestaurant', + 'SmallOffice' + ] + + # NOTE: Skipping NorthernEducation for now: + # Minitest::UnexpectedError: RuntimeError: validation of model failed. + # ... /openstudio-standards/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb:714:in `apply_loads' + + # Range of test options. NECB2011 for now. Skipping later NECBs - they're + # systematically easier to deploy, given their lower reference building SRR + # targets (e.g. 2% vs 5%). BTAPPRE1980 cases are likely worth testing at + # some point, given their unique decision matrix. + @templates = ['NECB2011'] + + fdback = [] + fdback << "" + fdback << "BTAP/Skylight Unit Tests" + fdback << "~~~~ ~~~~ ~~~~ ~~~~ ~~~~" + + @epws.sort.each do |epw | + @options.sort.each do |option | + @buildings.sort.each do |building| + @templates.sort.each do |template| + cas = "CASE #{option} | #{building} (#{template})" + srr = case template + when 'NECB2020' then 0.02 + when 'NECB2017' then 0.02 + else 0.05 # e.g. NECB2011, NECB2015 + end + + st = Standard.build(template) + model = st.model_create_prototype_model(template:template, + epw_file: epw, + building_type: building, + srr_opt: option, + sizing_run_dir: @sizing_run_dir) + + # OSut addSkylight-specific info/warning/error feedback. + err_msg = "BTAP/OSut: OSut Hash (#{cas})?" + assert(st.osut.is_a?(Hash), err_msg) + err_msg = "BTAP/OSut: Missing nominal gross roof area (#{cas})?" + assert(st.osut.key?(:gra0), err_msg) + err_msg = "BTAP/OSut: Missing effective gross roof area (#{cas})?" + assert(st.osut.key?(:graX), err_msg) + err_msg = "BTAP/OSut: Nominal gross roof area (#{cas})?" + assert(st.osut[:gra0].respond_to?(:to_f), err_msg) + err_msg = "BTAP/OSut: Effective gross roof area (#{cas})?" + assert(st.osut[:graX].respond_to?(:to_f), err_msg) + err_msg = "BTAP/OSut: Negative nomianl gross roof area (#{cas})?" + assert(st.osut[:gra0] > 0, err_msg) + err_msg = "BTAP/OSut: Negative effective gross roof area (#{cas})?" + assert(st.osut[:graX] > 0, err_msg) + err_msg = "BTAP/OSut: Missing log status (#{cas})?" + assert(st.osut.key?(:status), err_msg) + err_msg = "BTAP/OSut: Log status (#{cas})?" + assert(st.osut[:status].respond_to?(:to_i), err_msg) + err_msg = "BTAP/OSut: Missing OSut logs (#{cas})?" + assert(st.osut.key?(:logs), err_msg) + err_msg = "BTAP/OSut: OSut logs (#{cas})?" + assert(st.osut[:logs].is_a?(Array), err_msg) + + # Tally skylight areas. Compare with GRAs. + skm2 = 0 + + model.getSubSurfaces.each do |sub| + next unless sub.subSurfaceType.downcase == "skylight" + + skm2 += sub.grossArea + end + + assert(skm2 > 0, "BTAP/OSut: Negative skylight area (#{cas})?") + gra0 = st.osut[:gra0] # gross roof area (GRA) in m2, as per SDK + graX = st.osut[:graX] # GRA minus overhang areas (see SmallOffice) + + # The "SmallOffice" has an unconditioned (unoccupied) attic with + # roof overhangs. The overhanged sections of attic roof surfaces + # should be excluded from the calculated gross roof area, as per + # ASHRAE 90.1 definitions (NECB definitions are more vague). See: + # + # github.com/rd2/osut/blob/ + # 117c7dceb59fd8aab771da8ba672c14c97d23bd0/ + # lib/osut/utils.rb#L6304 + # + # Relying on the total area of attic roof surfaces (for SRR%) + # exagerrates required skylight area targets, often by 10% to 15%. + # Such targets are harder to meet when dealing with skylight wells. + # This also leads to unfair assessments of NECB rulesets. This + # issue only applies for attics - not plenums. + # + # Since neither OpenStudio nor OpenStudio-Standards consider this + # as an issue (i.e. ASHRAE 90.1 doesn't require NECB fixed SRRs), + # BTAP would be perpetually 'swimming against the tide' if forcing + # the use of revised GRA calculations. An easier alternative is + # to lower proportionately the requested SRR%, while logging an + # informative warning to the user, e.g.: + # + # nominal NECB2011 SRR% = 5.0% + # ratio graX / gra0 = 90.0% + # effective NECB2011 SRR% = 4.5% + # + # EnergyPlus, OpenStudio & OpenStudio-Standards would report here a + # SRR% of 4.5%. The effective 'ratio' would vary based on geometry, + # e.g. larger building footprint, wider overhangs. + if building == 'SmallOffice' + err_msg = "BTAP/OSut: GRA0 <= GRAX (#{cas})?" + assert(gra0.round > graX.round, err_msg) + else + err_msg = "BTAP/OSut: GRA0 != GRAX (#{cas})?" + assert(gra0.round == graX.round, err_msg) + end + + ratio = skm2 / graX + assert(ratio.round(2) == srr, "BTAP/OSut: Incorrect SRR (#{cas})?") + + # Higher level feedback. + fdback << "" + fdback << cas + status = st.osut[:status] + + st.osut[:logs].each do |log| + assert(log.is_a?(Hash), "BTAP/OSut: log (#{cas})?") + assert(log.key?(:level), "BTAP/OSut: log level (#{cas})?") + assert(log.key?(:message), "BTAP/OSut: log message (#{cas})?") + next if log[:level] < 1 # 'INFO' + next unless log.include?("(OSut::addSkylights)") + + fdback << log[:message] + end + end # |template| + end # |building| + end # |option | + end # |epw | + + # Temporary. + fdback.each { |msg| puts msg } + + # Save test results to file. + File.open(@test_results_file, 'w') do |f| + f.write(JSON.pretty_generate(@test_results_array)) + end + end + +end diff --git a/utilities/btap_cli/tests/run_options.yml b/utilities/btap_cli/tests/run_options.yml index d110b1f1a7..42d49d76e4 100644 --- a/utilities/btap_cli/tests/run_options.yml +++ b/utilities/btap_cli/tests/run_options.yml @@ -56,6 +56,7 @@ :skylight_cond: NECB_Default :skylight_solar_trans: NECB_Default :srr_set: NECB_Default +:srr_opt: '' :swh_fuel: NECB_Default :template: NECB2017 :npv_start_year: NECB_Default diff --git a/utilities/btap_cli/tests/run_options_local_osm.yml b/utilities/btap_cli/tests/run_options_local_osm.yml index 9fed56fc55..77d1746411 100644 --- a/utilities/btap_cli/tests/run_options_local_osm.yml +++ b/utilities/btap_cli/tests/run_options_local_osm.yml @@ -55,6 +55,7 @@ :skylight_cond: NECB_Default :skylight_solar_trans: NECB_Default :srr_set: NECB_Default +:srr_opt: '' :template: NECB2017 :npv_start_year: NECB_Default :npv_end_year: NECB_Default @@ -69,4 +70,4 @@ { name: 'DISTRICTHEATING:FACILITY', frequency: 'hourly'}, { name: 'DISTRICTCOOLING:FACILITY', frequency: 'hourly'}, { name: 'FuelOil#2:Facility', frequency: 'hourly'} -] \ No newline at end of file +]