From 8e4317cdcea406f495fa25dcff9ef426efc579e8 Mon Sep 17 00:00:00 2001 From: rd2 Date: Mon, 22 Apr 2024 10:32:33 -0400 Subject: [PATCH 01/24] Adds (then zeroes) door PSI-factors for NECBs --- lib/openstudio-standards/btap/bridging.rb | 754 +++++----------------- 1 file changed, 176 insertions(+), 578 deletions(-) diff --git a/lib/openstudio-standards/btap/bridging.rb b/lib/openstudio-standards/btap/bridging.rb index cfa027820c..f01cd904f2 100644 --- a/lib/openstudio-standards/btap/bridging.rb +++ b/lib/openstudio-standards/btap/bridging.rb @@ -33,10 +33,9 @@ module BridgingData # # - range of clear-field Uo factors # - 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 +52,9 @@ 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) @@ -152,9 +154,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 +206,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 +227,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 @@ -249,80 +251,72 @@ module BridgingData # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- # # A construction sub-variant is identified strictly by its Uo factor: # - # 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] + # e.g. "314" describes a Uo factor of 0.314 W/m2.K. + @@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 @@ -330,6 +324,8 @@ module BridgingData # linkage is now extended to OpenStudio models (not just costing), # 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.: @@ -429,677 +425,282 @@ 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 +779,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 +857,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 +928,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 +945,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 @@ -1682,8 +1281,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 +1369,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 +1430,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,9 +1559,8 @@ 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) @@ -2097,7 +1695,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 +1704,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 +1713,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. From 02075b2e88f7c12146af7fb1ae3d0aa1b8b86c8a Mon Sep 17 00:00:00 2001 From: rd2 Date: Thu, 25 Apr 2024 08:17:49 -0400 Subject: [PATCH 02/24] Initiates new BTAP Structure --- lib/openstudio-standards/btap/structure.rb | 370 +++++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 lib/openstudio-standards/btap/structure.rb diff --git a/lib/openstudio-standards/btap/structure.rb b/lib/openstudio-standards/btap/structure.rb new file mode 100644 index 0000000000..d67a7388ee --- /dev/null +++ b/lib/openstudio-standards/btap/structure.rb @@ -0,0 +1,370 @@ +# **************************************************************************** / +# * Copyright (c) 2008-2024, Natural Resources Canada +# * All rights reserved. +# * +# * This library is free software; you can redistribute it and/or +# * modify it under the terms of the GNU Lesser General Public +# * License as published by the Free Software Foundation; either +# * version 2.1 of the License, or (at your option) any later version. +# * +# * This library is distributed in the hope that it will be useful, +# * but WITHOUT ANY WARRANTY; without even the implied warranty of +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# * Lesser General Public License for more details. +# * +# * You should have received a copy of the GNU Lesser General Public +# * License along with this library; if not, write to the Free Software +# * Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# **************************************************************************** / + +# Note: The following is fairly self-contained, requiring only a few OSut +# functions. OpenStudio-Standards has TBD as a dependency, which itself extends +# OSut. It's odd to pull OSut via TBD - likely a temporary solution (@todo). +require "tbd" + +module BTAP + # Building STRUCTURE parameters, ultimately driving BTAP definitions of e.g.: + # - internal mass + # - envelope CLADDING/FRAMING/FINISH selection + # - related thermal bridging calculations (and uprated insulation levels) + # - costing + # - embodied carbon tallies + # + # As detailed a bit further on, this determination is either via user input: + # - e.g. "clt" (mass timber) post/beam STRUCTURE, for a school. + # + # Or auto-assigned based on the prevalence of model space type assignments: + # - e.g. 75% of spaces are commercial in nature, therefore the building + # STRUCTURE defaults to "steel" post/beam + # + # The overarching idea is that (in most cases) OpenStudio surface construction + # & material choices (in addition to internal mass definitions), mostly stem + # from underlying structural design choices (which aren't natively defined in + # OpenStudio). Structural choices have more to do with fire safety, budget, + # practicality (low-rise vs high-rise), local workforce, durability, etc. + # Ensuring consistency between building STRUCTURE, envelope selection, + # internal mass definitions, etc. is key in harmonizing predicted energy use, + # peak demand assessments, GHG emissions, vs thermal resilience and embodied + # energy/GHG tallies. + # + # Although "wood" framed walls constitute the load-bearing components of a + # "wood" framed building STRUCTURE (ex. low-rise housing), they can equally be + # found as non-load-bearing components in a "clt" post-beam STRUCTURE. Light + # gauge "steel" framed walls are much more common in a non-residential + # STRUCTURE (e.g. "steel" post/frame, "concrete" post & beam, and even "clt"), + # though rarely found in low-rise housing. Although one may observe some real + # -world mixing of STRUCTURE vs FRAMING in a building, it remains largely + # deterministic: designers select constructions (FRAMING, insulation) while + # taking building classification and STRUCTURE selection into consideration + # - the inverse is rarely true. + # + # STRUCTURE description + # __________ ___________________________________________________________ + # "steel" steel, post/frame (default) + # "metal" pre-fab panelized steel STRUCTURE (**, ++), typically 1 story + # "concrete" reinforced concrete, post/beam/slab + # "cmu" load-bearing concrete masonry unit walls, typically 1-story + # "wood" conventional load-bearing wood-framed and/or -engineered + # "clt" pre-fab, post/beam mass/cross-laminated/timber (**) + # + # NOTES: + # + # ** Neither "metal" nor "clt" options can be considered as fully + # supported by BTAP, e.g.: + # - no range of admissible envelope Uo factors + # - no associated PSI-factors (thermal bridging) + # - no costing data + # - no embodied energy/carbon data + # They are nonetheless (minimally) maintained here as an "aide-mémoire" + # for future BTAP upgrades - @todo. + # + # ++ ASHRAE 90.1 2022 definitions of: + # + # "METAL BUILDING": a complete integrated set of mutually dependent + # components and assemblies that form a building, which consists of a + # steel-framed superSTRUCTURE and metal skin. + # + # "METAL BUILDING ROOF": a roof that: + # a. is constructed with a metal, structural, weathering surface; + # b. has no ventilated cavity; and + # c. has the insulation entirely below deck (i.e., does not include + # composite concrete and metal deck construction nor a roof + # FRAMING system that is separated from the superSTRUCTURE by a + # wood substrate) and whose STRUCTURE consists of one or more of the + # following configurations: + # 1. Metal roofing in direct contact with the steel FRAMING members + # 2. Metal roofing separated from steel FRAMING by insulation + # 3. Insulated metal roofing panels installed per (a) or (b) + # + # "METAL BUILDING WALL": a wall whose STRUCTURE consists of metal + # spanning members supported by steel structural members (i.e. does not + # include spandrel glass or metal panels in curtain wall systems). + # + # Note that there's a (growing?) need to contrast "metal" buildings against + # the default "steel" post/beam option. Like a "wood" framed STRUCTURE or a + # load-bearing "cmu" wall, a "metal" building's perimeter STRUCTURE and + # envelope are indistinguishable, i.e. no mixing/matching between STRUCTURE + # and envelope. + # + # There are of course several other (smaller scale) structural options, + # often load-bearing envelopes like adobe/hemp/straw bale construction. Most + # would agree that these are fairly rare occurrences - rare enough to avoid + # shortlisting them for commercial building stock assessments. One could + # state the same when it comes to the current (marginal) use of "clt". Yet as + # the latter is rapidly becoming a robust low-carbon alternative to "steel" + # and "concrete" options, its inclusion is justified. Additional options may + # nonetheless be added in the future. + + module StructureData + # Each STRUCTURE inherits a default FRAMING option. Together with the + # STRUCTURE selection, FRAMING determines inter alia: + # - above-grade floor assemblies + # - insulated roof assemblies + # - cantilevered balconies + # - interzone walls + @@structure = {} + @@structure[:steel ] = {framing: :steel} + @@structure[:metal ] = {framing: :metal} + @@structure[:concrete] = {framing: :steel} + @@structure[:cmu ] = {framing: :cmu } + @@structure[:wood ] = {framing: :wood } + @@structure[:clt ] = {framing: :wood } + + # An example. STRUCTURE == "wood" + default FRAMING == "wood", e.g. housing: + # - typical engineered wood joists + FINISH + # - similar engineered wood rafters + FINISH (if flat or cathedral roof) + # - anchored engineered wood joist balconies + # - standard 2"x4" wood-framed interzone walls + # + # FRAMING may also determine above-grade exterior wall composition (e.g. + # wool-insulated wood-framed exterior walls, if FRAMING == "wood"). This + # may instead be determined by CLADDING selection in several cases. + # + # Exterior CLADDING and interior FINISH options are both limited to 4 + # generic labels. Defaults for all STRUCTUREs are "light", for both CLADDING + # (e.g. metal siding on vented hat-channels) and FINISH (e.g. painted + # drywall). Brick veneer is an example of "medium" CLADDING, while a 4" + # precast concrete panel is considered "heavy" CLADDING. A "medium" FINISH + # is akin to a 4" precast panel concrete, while a "heavy" FINISH is a + # heftier 8" of (poured) reinforced concrete. Option "none" for CLADDING is + # rare, even in pre-code buildings. An example would be a load-bearing, + # "cmu" wall with 2 coats of paint in a semi-heated industrial facility. The + # "none" FINSIH option is slightly more common, e.g. exposed ceilings, bare + # "clt" walls, and again bare "cmu" walls (or with 2 coats of paint). + @@cladding = [:none, :light, :medium, :heavy] + @@finish = [:none, :light, :medium, :heavy] + + # An above-grade building STRUCTURE may be optionally auto-assigned based on + # the prevalence of space type selections in the model (see CATEGORY below). + # Note that all below-grade STRUCTUREs remain "concrete", e.g.: + # - basement slabs and slabs-on-grade + # - load-bearing basement walls + # - basement columns, shear walls, etc. (internal mass) + # + # Users can optionally assign STRUCTURE, FRAMING, CLADDING & FINISH options + # along OpenStudio's building-to-space hierarchy, e.g.: + # + # Example A: Composite STRUCTURE: + # - "concrete" post/beam STRUCTURE for first 4 building stories + # - s"teel" post/frame STRUCTURE for building stories > 4 + # + # Example B: School gym: + # - "cmu" gymnasium walls in an otherwise "steel" post/frame school + # + # An invalid user-selected STRUCTURE is however caught/logged/corrected: + # - no other STRUCTURE above "steel", "metal", "cmu" or "wood" STRUCTUREs + # - a "wood" STRUCTURE may rest upon a "clt" STRUCTURE + # - any other STRUCTURE may rest upon a "concrete" STRUCTURE + # + # With the exception of "metal" buildings, users may optionally interchange + # some paired STRUCTURE vs FRAMING options (among available :frames above). + # Unusual in some cases, yet not completely unheard of. + @@structure[:steel ][:frames] = [:wood ] + @@structure[:metal ][:frames] = [ ] + @@structure[:concrete][:frames] = [:wood, :cmu ] + @@structure[:cmu ][:frames] = [:wood, :steel] + @@structure[:wood ][:frames] = [:steel ] + @@structure[:clt ][:frames] = [:clt, :steel ] + + + # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- # + # STRUCTURE options hold "co2" carbon intensities (in CO2-e kg/m2), which + # are placeholders for now (to be replaced at some point by 3rd-party + # estimates). They're meant to specifically track the carbon footprint of + # the STRUCTURE (not the envelope, nor interior partitions, nor integrated + # furniture). These estimates should include the embodied carbon of + # above-grade, structural floors per se, in addition to the embodied carbon + # of structural elements that can't (or are unlikely to) be represented in + # an OpenStudio model, e.g.: + # - columns + # - bracing + # - stairwells and elevator shafts + @@structure[:steel ][:co2] = 200 + @@structure[:metal ][:co2] = 200 + @@structure[:concrete][:co2] = 200 + @@structure[:cmu ][:co2] = 200 + @@structure[:wood ][:co2] = 200 + @@structure[:clt ][:co2] = 200 + + # Once refined, these CO2-e kg/m2 estimates are expected to differ + # considerably between STRUCTURE options, and possibly affected by regional + # considerations (i.e. national vs local estimates), number of building + # stories, structural requirements, etc. - to be set parametrically. + + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # To simplify data management, building TYPES (e.g. those listed in Table + # A-8.4.3.2.(2)-A of the NECB 2020) are proposed to fall into more general + # building CATEGORIES: + # + # CATEGORY examples + # _______________ _______________________________________________________ + # "dwelling" MURB, long-term stay, hotel, dormitory + # "detention" penitentiary + # "institutional" office, museum, town hall, education, convention centre + # "commercial" dining, retail, gym, dealership, theatre, transportation + # "industrial" automotive, manufacturing, fire station, storage + # "athletics" ice arena, indoor soccer, indoor pool, gymnastics + # + # Each CATEGORY holds "small"-scale and "large"-scale STRUCTURE options by + # defaults, depending on the characteristics of the building. + @@category = {} + @@category[:dwelling ] = {small: :wood , large: :concrete} + @@category[:detention ] = {small: :concrete, large: :concrete} + @@category[:institutional] = {small: :steel , large: :concrete} + @@category[:commercial ] = {small: :steel , large: :steel} + @@category[:industrial ] = {small: :concrete, large: :metal} + @@category[:athletics ] = {small: :metal , large: :steel} + + # What constitutes small- vs large-scale varies between CATEGORY, depending + # either on the maximum number of stories or the total building height. + # Categories "detention" and "commercial" do not vary. + @@category[:dwelling ][:stories] = 5 + @@category[:institutional][:stories] = 3 + @@category[:industrial ][:height ] = 4 + @@category[:athletics ][:height ] = 10 + + # For instance, (by default) a multi-unit residential buildings (MURB) would + # have a typical "wood" framed, load-bearing envelope/STRUCTURE up to (and + # including) 5 stories above-grade. This default STRUCTURE assignment + # switches to reinforced "concrete" post + flat slab beyond 5 stories. + # Building CATEGORIES that hold neither :stories nor :height key:value pairs + # simply retain the same STRUCTURE option by default (e.g. "detention", + # "commercial"), regardless of scale. + # + # This default STRUCTURE assignment (per building CATEGORY) does not imply + # one cannot investigate the pros/cons of CLT construction in MURBs, offices + # or athletic facilities. It rather reflects the current state of affairs + # (i.e. 'dominant' STRUCTURE option per building CATEGORY in large parts of + # the US and Canada). Users have the option of overriding these. + # ... more to come (@todo). + + + ## + # Returns building structure data. + # + # @return [Hash] building structure data + def structure + @@structure + end + + ## + # Returns exterior cladding options. + # + # @return [Array] exterior cladding options + def cladding + @@cladding + end + + ## + # Returns interior finish options. + # + # @return [Array] interior finish options + def finish + @@finish + end + + ## + # Returns building category data. + # + # @return [Hash] building category data + def category + @@category + end + + def self.extended(base) + base.send(:include, self) + end + end + + class BTAP::Structure + extend StructureData + + TOL = TBD::TOL + TOL2 = TBD::TOL2 + DBG = TBD::DBG + INF = TBD::INF + WRN = TBD::WRN + ERR = TBD::ERR + FTL = TBD::FTL + + # @return [String] dominant building type CATEGORY (e.g. :institutional) + attr_reader :category + + # @return [String] dominant building STRUCTURE selection (e.g. :steel) + attr_reader :structure + + # @return [Float] calculated embodied carbon of STRUCTURE (CO2-e kg/m2) + attr_reader :co2 + + # @return [Hash] logged messages + attr_reader :feedback + + + ## + # Initialize BTAP STRUCTURE parameters. + # + # @param model [OpenStudio::Model::Model] a model + # @param argh [Hash] BTAP STRUCTURE argument hash + def initialize(model = nil, argh = {}) + @category = :institutional + @co2 = 0 + @feedback = {logs: []} + lgs = @feedback[:logs] + + # @todo + end + + ## + # Returns embodied carbon, strictly related to building STRUCTURE. + # + # @param model [OpenStudio::Model::Model] a model + # + # @return [Float] STRUCTURE related embodied carbon (CO2-e kg/m2) + def tallyCO2(model) + mth = "BTAP::Structure::#{__callee__}" + cl = OpenStudio::Model::Model + lgs = @feedback[:logs] + + unless model.is_a?(cl) + lgs << "Invalid OpenStudio model (#{mth})" + return 0 + end + + unless argh.is_a?(Hash) + lgs << "Invalid argument Hash (#{mth})" + return 0 + end + + if argh.empty? + lgs << "Empty argument hash (#{mth})" + return 0 + end + + # - tally above-grade vs below-grade floor areas + # - apply associated CO2-e kg/m2 + # - return + # @todo + end + end +end From 1246b76fe6e92e53fb5bc3af5497d75888ed7d34 Mon Sep 17 00:00:00 2001 From: rd2 Date: Thu, 25 Apr 2024 09:23:02 -0400 Subject: [PATCH 03/24] Updates copyright year and TODOs --- lib/openstudio-standards/btap/bridging.rb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/openstudio-standards/btap/bridging.rb b/lib/openstudio-standards/btap/bridging.rb index f01cd904f2..33ebaeb904 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-2024, Natural Resources Canada # * All rights reserved. # * # * This library is free software; you can redistribute it and/or @@ -206,7 +206,7 @@ module BridgingData # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- # # There are 3x exceptions to the aforementioned iterative solution, - # hopefully to correct (TODO): + # hopefully to correct (@todo): # # - Steel-framed construction: the selected HP variant has metal # cladding. The only LP steel-framed BTAP option is wood-clad - @@ -227,7 +227,7 @@ module BridgingData # respectively. This is expected to change in the future ... # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- # - # Preset BTAP/TBD wall construction parameters (to be revised ... TODO). + # 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 @@ -324,7 +324,7 @@ module BridgingData # linkage is now extended to OpenStudio models (not just costing), # given the construction-specific nature of MAJOR thermal bridging. # - # NOTE: Expect radical changes to the NECB building/space type model (TODO). + # 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 @@ -700,7 +700,7 @@ module BridgingData # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- # ## - # Retrieve TBD building/space type keyword (to revise TODO). + # 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 @@ -779,7 +779,7 @@ def spacetype(sptype = "", stories = 999) end ## - # Retrieve building/space type-specific assembly/construction (to revise TODO). + # Retrieve building/space type-specific assembly/construction (to revise @todo). # # @param sptype [Symbol] BTAP/TBD spacetype # @param stypes [Symbol] :walls, :floors or :roofs @@ -928,7 +928,7 @@ 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 (TODO). In the meantime, and in an effort to harmonize TBD + # 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 @@ -945,7 +945,7 @@ def initialize(model = nil, argh = {}) initial = true complies = false comply = {} # specific to :walls, :floors & :roofs - perform = :lp # Low-performance wall constructions (revise, TODO ...) + 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 @@ -1695,7 +1695,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 (TODO). 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). # @@ -1704,7 +1704,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. TODO. +# insulation for "good" (HP) details. See final TBD tally. @todo. # # NOTE: Overview of current BTAP building/space type construction link, e.g.: # @@ -1713,4 +1713,4 @@ def get_material_quantities() # # ... yet all (public) washrooms, corridors, stairwells, etc. are # steel-framed (regardless of building type). Overview of possible fixes. -# TODO. +# @todo. From 706fbfa8ddde3be3729ccad274f6739f0ca4ed00 Mon Sep 17 00:00:00 2001 From: rd2 Date: Wed, 1 May 2024 08:43:28 -0400 Subject: [PATCH 04/24] Adds draft ACTIVITY class/tests --- .../btap/NECB_building_types.csv | 36 +++ .../btap/NECB_space_types.csv | 120 ++++++++++ lib/openstudio-standards/btap/activity.rb | 222 ++++++++++++++++++ lib/openstudio-standards/btap/btap.rb | 3 +- lib/openstudio-standards/btap/structure.rb | 78 +++--- .../standards/necb/NECB2011/necb_2011.rb | 53 +++-- .../unit_tests/tests/test_necb_activities.rb | 99 ++++++++ 7 files changed, 556 insertions(+), 55 deletions(-) create mode 100644 lib/openstudio-standards/btap/NECB_building_types.csv create mode 100644 lib/openstudio-standards/btap/NECB_space_types.csv create mode 100644 lib/openstudio-standards/btap/activity.rb create mode 100644 test/necb/unit_tests/tests/test_necb_activities.rb diff --git a/lib/openstudio-standards/btap/NECB_building_types.csv b/lib/openstudio-standards/btap/NECB_building_types.csv new file mode 100644 index 0000000000..90495fa531 --- /dev/null +++ b/lib/openstudio-standards/btap/NECB_building_types.csv @@ -0,0 +1,36 @@ +Activity,Category,NECB 2011 Building Type,NECB 2015 Building Type,NECB 2017 Building Type,NECB 2020 Building Type +arena,recreation,Sports arena,Sports arena,Sports arena,Sports arena +gym,recreation,Gymnasium,Gymnasium,Gymnasium,Gymnasium +exercise,commerce,Exercise centre,Exercise centre,Exercise centre,Exercise centre +office,commerce,Office,Office,Office,Office +retail,commerce,Retail,Retail area,Retail area,Retail area +leisure,commerce,Dining - bar/lounge/leisure,Dining - bar lounge/leisure,Dining - bar lounge/leisure,Dining - bar lounge/leisure +fastfood,commerce,Dining - cafeteria/fastfood,Dining - cafeteria/fastfood,Dining - cafeteria/fastfood,Dining - cafeteria/fastfood +restaurant,commerce,Dining - family restaurant,Dining - family restaurant,Dining - family restaurant,Dining - family restaurant +hotel,lodging,Hotel,Hotel and other lodging,Hotel and other lodging,Hotel and other lodging +motel,lodging,Motel,,, +dorm,housing,Dormitory,Dormitory,Dormitory,Dormitory +residential,housing,Multi-unit residential,Multi-unit residential building,Multi-unit residential building,Multi-unit residential building +care,housing,,Long-term care - dwelling units,Long-term care - dwelling units,Long-term care - dwelling units +clinic,public,Health clinic,Health clinic,Health clinic,Health clinic +hospital,public,Hospital,Hospital,Hospital,Hospital +religious,public,Religious,Religious building,Religious building,Religious building +terminal,public,Terminal (transportation),Terminal (transportation facility),Terminal (transportation facility),Terminal (transportation facility) +theatre,public,Performing arts theatre,Performing arts theatre,Performing arts theatre,Performing arts theatre +cineplex,public,Cineplex (motion picture),Cineplex (motion picture),Cineplex (motion picture),Cineplex (motion picture) +convention,public,Convention centre,Convention centre,Convention centre,Convention centre +museum,public,Museum,Museum,Museum,Museum +library,public,Library,Library,Library,Library +school,public,School/university,School/university,School/university,School/university +postal,public,Postal building,Postal building,Postal building,Postal building +townhall,public,Townhall,Townhall,Townhall,Townhall +court,public,Courthouse,Courthouse,Courthouse,Courthouse +precinct,public,Precinct (police station),Precinct (police station),Precinct (police station),Precinct (police station) +penitentiary,robust,Penitentiary,Penitentiary,Penitentiary,Penitentiary +parking,robust,Parking garage,,, +storage,industry,,Storage garage,Storage garage,Storage garage +warehouse,industry,Warehouse,Warehouse,Warehouse,Warehouse +workshop,industry,Workshop,Workshop,Workshop,Workshop +manufacturing,industry,Manufacturing facility,Manufacturing facility,Manufacturing facility,Manufacturing facility +automotive,industry,Automotive facility,Automotive facility,Automotive facility,Automotive facility +firehouse,industry,Firehouse (fire station),Firehouse (fire station),Firehouse (fire station),Firehouse (fire station) \ No newline at end of file diff --git a/lib/openstudio-standards/btap/NECB_space_types.csv b/lib/openstudio-standards/btap/NECB_space_types.csv new file mode 100644 index 0000000000..b15bff5289 --- /dev/null +++ b/lib/openstudio-standards/btap/NECB_space_types.csv @@ -0,0 +1,120 @@ +Activity,NECB 2011 Space Type,NECB 2015 Space Type,NECB 2017 Space Type,NECB 2020 Space Type +undefined::common,undefined,undefined,undefined,undefined +washroom::rp28,,Washroom - space designed to ANSI/IES RP28 (used primarily by residents),Washroom - space designed to ANSI/IES RP28 (used primarily by residents),Washroom - space designed to ANSI/IES RP28 (used primarily by residents) +washroom::common,Washroom,Washroom - other,Washroom - other,Washroom - other +atrium::common,Atrium,Atrium,Atrium,Atrium +concourse::retail,Retail - mall concourse,Retail facility mall concourse,Retail facility mall concourse,Retail facility mall concourse +concourse::hospital,Hospital concourse (>= 2.4m),,, +concourse::terminal,Terminal (transportation) - concourse,"Terminal (tansportation facility, airport) - concourse","Terminal (tansportation facility, airport) - concourse","Terminal (tansportation facility, airport) - concourse" +concourse::manufacturing,Manufactoring - concourse (>= 2.4m),,, +concourse::common,Concourse (>= 2.4m wide),,, +corridor::rp28,,Corridor/Transition area - space designed to ANSI/IES RP28 (and used primarily by residents),Corridor/Transition area - space designed to ANSI/IES RP28 (and used primarily by residents),Corridor/Transition area - space designed to ANSI/IES RP28 (and used primarily by residents) +corridor::hospital,Hospital corridor (< 2.4m),Corridor/Transition area - hospital,Corridor/Transition area - hospital,Corridor/Transition area - hospital +corridor::manufacturing,Manufacturing - corridor < 2.4m,Corridor/Transition area - manufacturing facility,Corridor/Transition area - manufacturing facility,Corridor/Transition area - manufacturing facility +corridor::common,Corridor < 2.4m wide,Corridor/Transition area other,Corridor/Transition area other,Corridor/Transition area other +stairway::common,Stairway,Stairway/Stairwell,Stairway/Stairwell,Stairway/Stairwell +elevator::common,Foyer - elevator,Foyer - elevator,Foyer - elevator,Foyer - elevator +lobby::rp28,,Lobby - space designed to ANSI/IES RP28 (used primarily by residents),Lobby - space designed to ANSI/IES RP28 (used primarily by residents),Lobby - space designed to ANSI/IES RP28 (used primarily by residents) +lobby::hotel,Hotel/Motel - lobby,Lobby - hotel,Lobby - hotel,Lobby - hotel +lobby::cineplex,Lobby - cineplex (motion picture),Lobby - cineplex (motion picture),Lobby - cineplex (motion picture),Lobby - cineplex (motion picture) +lobby::theatre,Lobby - performance arts theatre,Lobby - performing arts theatre,Lobby - performing arts theatre,Lobby - performing arts theatre +lobby::common,Lobby - other,Lobby - other,Lobby - other,Lobby - other +restoration::museum,Museum - restoration,Museum restoration room,Museum restoration room,Museum restoration room +exhibit::museum,Museum - exhibit,Museum - exhibit,Museum - exhibit,Museum - exhibit +exhibit::convention,Convention centre - exhibit,Convention centre exhibit space,Convention centre exhibit space,Convention centre exhibit space +baggage::terminal,Terminal (transportation) - baggage,Terminal (transportation) baggage/carousel,Terminal (transportation) baggage/carousel,Terminal (transportation) baggage/carousel +counter::terminal,Terminal (transportation) - counter,Terminal (transportation) - ticket counter,Terminal (transportation) - ticket counter,Terminal (transportation) - ticket counter +seating::terminal,Terminal (transportation) seating ,,, +seating::common,,Seating area general,Seating area general,Seating area general +auditorium::common,Audience - auditorium,Audience (permanent) - auditorium,Audience (permanent) - auditorium,Audience (permanent) - auditorium +refectory::religious,Religious - fellowship/refectory,Religious building fellowship/refectory,Religious building fellowship/refectory,Religious building fellowship/refectory +pulpit::religious,Religious - pulpit/choir,Religious building worship/pulpit/choir area,Religious building worship/pulpit/choir area,Religious building worship/pulpit/choir area +chapel::rp28,,Space designed to ANSI/IES RP28 chapel (used primarily by residents),Space designed to ANSI/IES RP28 chapel (used primarily by residents),Space designed to ANSI/IES RP28 chapel (used primarily by residents) +audience::religious,Religious - audience,Audience (permanent) - religious building,Audience (permanent) - religious building,Audience (permanent) - religious building +audience::penitentiary,Penitentiary - audience,Audience (permament) - penitentiary,Audience (permament) - penitentiary,Audience (permament) - penitentiary +audience::cineplex,Audience - cineplex (motion picture),Audience (permament) - cineplex (motion picture),Audience (permament) - cineplex (motion picture),Audience (permament) - cineplex (motion picture) +audience::theatre,Audience - performance arts theatre,Audience (permament) - performing arts theatre,Audience (permament) - performing arts theatre,Audience (permament) - performing arts theatre +audience::convention,Convention centre - audience,Audience (permament) - convention centre,Audience (permament) - convention centre,Audience (permament) - convention centre +audience::gym,Gym - audience,Audience (permament) - gymnasium,Audience (permament) - gymnasium,Audience (permament) - gymnasium +audience::arena,Sports arena - audience,Audience (permament) - sports arena,Audience (permament) - sports arena,Audience (permament) - sports arena +audience::common,,Audience (permament) - other,Audience (permament) - other,Audience (permament) - other +c1::arena,Sports arena - court c1,Sports arena playing area c1 facility,Sports arena playing area c1 facility,Sports arena playing area c1 facility +c2::arena,Sports arena - court c2,Sports arena playing area c2 facility,Sports arena playing area c2 facility,Sports arena playing area c2 facility +c3::arena,Sports arena - court c3,Sports arena playing area c3 facility,Sports arena playing area c3 facility,Sports arena playing area c3 facility +c4::arena,Sports arena - court c4,Sports arena playing area c4 facility,Sports arena playing area c4 facility,Sports arena playing area c4 facility +octogon::arena,"Sports arena - ring, octogon",,, +exercise::gym,"Gym - exercise, fitness",Gymnasium/Fitness centre exercise area,Gymnasium/Fitness centre exercise area,Gymnasium/Fitness centre exercise area +court::gym,Gym - court (play area),Gymnasium/Fitness centre court (playing area),Gymnasium/Fitness centre court (playing area),Gymnasium/Fitness centre court (playing area) +locker::common,Locker room,Locker room,Locker room,Locker room +dressing::theatre,Dressing/fitting - performance arts theatre,Dressing/Fitting room - performing arts theatre,Dressing/Fitting room - performing arts theatre,Dressing/Fitting room - performing arts theatre +dressing::retail,Retail - dressing/fitting,Retail facility dressing/fitting room,Retail facility dressing/fitting room,Retail facility dressing/fitting room +sales::retail,Retail - sales,,, +sales::common,Sales area,Sales area,Sales area,Sales area +dining::rp28,,Dining area - space designed to ANSI/IES RP28 (used primarily by residents),Dining area - space designed to ANSI/IES RP28 (used primarily by residents),Dining area - space designed to ANSI/IES RP28 (used primarily by residents) +dining::penitentiary,Penitentiary - dining,Dining area - penitentiary,Dining area - penitentiary,Dining area - penitentiary +dining::leisure,Dining - bar/leisure,Dining area - bar//leisure,Dining area - bar//leisure,Dining area - bar//leisure +dining::fastfood,,Dining area - cafeteria/fastfood dining,Dining area - cafeteria/fastfood dining,Dining area - cafeteria/fastfood dining +dining::restaurant,Dining - family restaurant,Dining area - family restaurant,Dining area - family restaurant,Dining area - family restaurant +dining::motel,Motel/Hway lodging - dining,,, +dining::hotel,Hotel - dining,,, +dining::common,Dining - other,Dining area - other,Dining area - other,Dining area - other +cuisine::common,Cuisine (preparation area),Cuisine (preparation area),Cuisine (preparation area),Cuisine (preparation area) +rooms::motel,"Motel, Hway lodging - rooms",,, +rooms::hotel,Hotel - rooms,,, +rooms::common,,Guest rooms,Guest rooms,Guest rooms +quarters::dorm,Dormitory - living quarters,Dormitory living quarters,Dormitory living quarters,Dormitory living quarters +quarters::firehouse,Firehouse (fire station) - quarters,Firehouse (fire station) sleeping quarters,Firehouse (fire station) sleeping quarters,Firehouse (fire station) sleeping quarters +units::residential,Residential dwelling unit(s),Residential dwelling units general,Residential dwelling units general,Residential dwelling units general +units::care,,Dwelling units long term care,Dwelling units long term care,Dwelling units long term care +recreation::rp28,,Space designed to ANSI/IES RP28 recreation room (used primarily by residents),Space designed to ANSI/IES RP28 recreation room (used primarily by residents),Space designed to ANSI/IES RP28 recreation room (used primarily by residents) +meeting::common,Conference/meeting/multi-purpose,Conference/Meeting/Multi-purpose room,Conference/Meeting/Multi-purpose room,Conference/Meeting/Multi-purpose room +bureau::common,Bureau (enclosed office),"Bureau (enclosed office, <= 25 m2)","Bureau (office enclosed, <= 25 m2)","Bureau (office enclosed, <= 25 m2)" +workspace::common,,"Workspace (enclosed office, > 25 m2)","Workspace (enclosed office, > 25 m2)","Workspace (enclosed office, > 25 m2)" +openplan::common,Openplan office,Openplan office,Openplan office,Openplan office +bank::common,Bank - banking and offices,Banking activity area and offices,Banking activity area and offices,Banking activity area and offices +copiers::common,,Copiers/Print room,Copiers/Print room,Copiers/Print room +computer::common,,Computer room,Computer room,Computer room +server::common,,Server room,Server room,Server room +sorting::postal,Postal building - sorting,Postal building - sorting,Postal building - sorting,Postal building - sorting +chambers::court,Courthouse - chambers,,, +tribunal::court,Courthouse - tribunal (court room),Tribunal (court room),Tribunal (court room),Tribunal (court room) +cell::court,Courthouse - cell,,, +cell::common,,Confinement cell,Confinement cell,Confinement cell +classroom::penitentiary,Penitentiary - classroom,Classroom/Lecture/room - Penitentary,Classroom/Lecture/Training - Penitentary,Classroom/Lecture/Training - Penitentary +classroom::common,Classroom/lecture/training,Classroom/Lecture/Training - other,Classroom/Lecture/Training - other, +reading::library,Library - reading,Library reading area,Library reading area,Library reading area +stackes::library,Library - stacks,Library stacks,Library stacks,Library stacks +researchlab::common,ResearchLab,,, +teachinglab::common,TeachingLab (class),TeachingLab (class),TeachingLab (class),TeachingLab (class) +lab::common,,Laboratory - other,Laboratory - other,Laboratory - other +storage::common,,Storage (> 100 m2),Storage (> 100 m2), +storeroom::common,Storeroom,"Storeroom (<= 5 m2, <= 100 m2)","Storeroom (<= 5 m2, <= 100 m2)",Storeroom (<= 5 m2) +supplies::common,,Supplies (< 5 m2),Supplies (< 5 m2),Supplies (< 5 m2) +supplies::hospital,Hospital - medical supplies,Hospital - medical supplies,Hospital - medical supplies,Hospital - medical supplies +exam::hospital,Hospital - exam,Hospital - exam/treatment,Hospital - exam/treatment,Hospital - exam/treatment +nursery::hospital,Hospital - nursery,Hospital - nursery,Hospital - nursery,Hospital - nursery +nurses::hospital,Hospital - nurses station,Hospital - nurses station,Hospital - nurses station,Hospital - nurses station +operating::hospital,Hospital - operating room,Hospital - operating room,Hospital - operating room,Hospital - operating room +patient::hospital,Hospital - patient room,Hospital - patient room,Hospital - patient room,Hospital - patient room +therapy::hospital,Hospital - physical therapy,Hospital - physical therapy,Hospital - physical therapy,Hospital - physical therapy +imaging::hospital,Hospital - radiology/imaging,Hospital - radiology/imaging,Hospital - radiology/imaging,Hospital - radiology/imaging +recovery::hospital,Hospital - recovery,Hospital - recovery,Hospital - recovery,Hospital - recovery +laundry::hospital,Hospital - laundry/washing,,, +laundry::common,,Laundry/Washing area,Laundry/Washing area,Laundry/Washing area +lounge::hospital,Hospital - lounge,Lounge/Break room - hospital,Lounge/Break room - hospital,Lounge/Break room - hospital +lounge::common,Lounge/Break room - other,Lounge/Break room - other,Lounge/Break room - other,Lounge/Break room - other +pharmacy::hospital,Hospital - pharmacy,,, +pharmacy::common,,Pharmacy area,Pharmacy area,Pharmacy area +emergency::hospital,Hospital - emergency,,, +emergency::common,,Emergency vehicle garage,Emergency vehicle garage,Emergency vehicle garage +engineroom::firehouse,Firehouse (fire station) - engineroom,,, +garage::parking,Parking garage space,Parking garage interior,Parking garage interior,Parking garage interior +repair::automotive,Automotive - repair,Vehicle repair/maintenance area,Vehicle repair/maintenance area,Vehicle repair/maintenance area +dock::common,,Loading dock interior,Loading dock interior,Loading dock interior +bulky::warehouse,Warehouse - medium/bulky,Warehouse - medium to bulky palletized items,Warehouse - medium to bulky palletized items,Warehouse - medium to bulky palletized items +finer::warehouse,Warehouse - finer,Warehouse - small (finer) hand-carried items,Warehouse - small (finer) hand-carried items,Warehouse - small (finer) hand-carried items +workshop::common,Workshop space,Workshop,Workshop,Workshop +bay::manufacturing,Manufacturing - low bay (H < 7.5m),Manufacturing facility low bay area (H < 7.5m),Manufacturing facility low bay area (H < 7.5m),Manufacturing facility low bay area (H < 7.5m) +detailed::manufacturing,Manufactoring - detailed,Manufacturing facility detailed manufacturing area,Manufacturing facility detailed manufacturing area,Manufacturing facility detailed manufacturing area +equipment::manufacturing,Manufactoring - equipment,Manufacturing facility equipment room,Manufacturing facility equipment room,Manufacturing facility equipment room +mechanical::common,Electrical/Mechanical,Electrical/Mechanical room,Electrical/Mechanical room,Electrical/Mechanical room \ No newline at end of file diff --git a/lib/openstudio-standards/btap/activity.rb b/lib/openstudio-standards/btap/activity.rb new file mode 100644 index 0000000000..7aeee21782 --- /dev/null +++ b/lib/openstudio-standards/btap/activity.rb @@ -0,0 +1,222 @@ +# **************************************************************************** / +# * Copyright (c) 2008-2024, Natural Resources Canada +# * All rights reserved. +# * +# * This library is free software; you can redistribute it and/or +# * modify it under the terms of the GNU Lesser General Public +# * License as published by the Free Software Foundation; either +# * version 2.1 of the License, or (at your option) any later version. +# * +# * This library is distributed in the hope that it will be useful, +# * but WITHOUT ANY WARRANTY; without even the implied warranty of +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# * Lesser General Public License for more details. +# * +# * You should have received a copy of the GNU Lesser General Public +# * License along with this library; if not, write to the Free Software +# * Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# **************************************************************************** / + +require "csv" + +module BTAP + module ActivityData + @@data = {bldg: {}, space: {}} + + @@data[:templates] = ["NECB2011", "NECB2015", "NECB2017", "NECB2020"] + + # Hard setting path for both files (temporary @todo) + @@data[:bldg ][:file ] = File.join(__dir__, "NECB_building_types.csv") + @@data[:space][:file ] = File.join(__dir__, "NECB_space_types.csv") + @@data[:bldg ][:table ] = nil + @@data[:space][:table ] = nil + @@data[:bldg ][:activity ] = {} + @@data[:space][:activity ] = {} + @@data[:bldg ][:activities] = [] + @@data[:bldg ][:categories] = [] + + # Parse building data on file. + if File.exists?(@@data[:bldg][:file]) + table = CSV.open(@@data[:bldg][:file], headers: true).read + + # 35 unique entries, 6 columns per row: + # column 0: BTAP building ACTIVITY e.g. "restaurant" + # column 1: BTAP building CATEGORY e.g. "commerce" + # column 2: NECB 2011 building type e.g. "Dining - family restaurant" + # column 3: NECB 2015 building type + # column 4: NECB 2017 building type + # column 5: NECB 2020 building type + table.each do |row| + key = row[0] + + @@data[:bldg][:activity][key] = {} + @@data[:bldg][:activity][key][:category ] = row[1] + @@data[:bldg][:activity][key]["NECB2011"] = row[2] + @@data[:bldg][:activity][key]["NECB2015"] = row[3] + @@data[:bldg][:activity][key]["NECB2017"] = row[4] + @@data[:bldg][:activity][key]["NECB2020"] = row[5] + end + + # Keep CSV table. Isolate admissible building activities & categories. + @@data[:bldg][:table ] = table + @@data[:bldg][:activities] = table.by_col[0].uniq + @@data[:bldg][:categories] = table.by_col[1].uniq + # @@data[:bldg][:activities] = table.by_col[0].uniq.map!(&:to_sym) + # @@data[:bldg][:categories] = table.by_col[1].uniq.map!(&:to_sym) + + # Add "common" & "rp28" as building activities. + # @@data[:bldg][:activities] << :common + # @@data[:bldg][:activities] << :rp28 + @@data[:bldg][:activities] << "common" + @@data[:bldg][:activities] << "rp28" + @@data[:bldg][:activities].freeze + @@data[:bldg][:categories].freeze + else + # raise? + end + + # Parse space data on file. + if File.exists?(@@data[:space][:file]) + table = CSV.open(@@data[:space][:file], headers: true).read + + # 119 unique rows, 5 columns per row: + # column 0: BTAP space ACTIVITY e.g. "exhibit::convention" + # column 1: NECB 2011 space type e.g. "Convention centre - exhibit" + # column 2: NECB 2015 space type e.g. "Convention centre exhibit space" + # column 3: NECB 2017 space type + # column 4: NECB 2020 space type + table.each do |row| + key = row[0] + str = key.split("::") + cat = str[0] + act = str[1] + + @@data[:space][:activity][key] = {} + @@data[:space][:activity][key][:act ] = act + @@data[:space][:activity][key][:cat ] = cat + @@data[:space][:activity][key]["NECB2011"] = row[1] + @@data[:space][:activity][key]["NECB2015"] = row[2] + @@data[:space][:activity][key]["NECB2017"] = row[3] + @@data[:space][:activity][key]["NECB2020"] = row[4] + end + + @@data[:space][:table] = table + else + # raise? + end + + ## + # Returns BTAP Activity data. + # + # @return [Hash] BTAP Activity data + def data + @@data + end + + def self.extended(base) + base.send(:include, self) + end + end + + class BTAP::Activity + extend ActivityData + + # @return [Integer] NECB template (e.g. "NECB2011") + attr_reader :template + + # @return [String] assigned building ACTIVITY (e.g. "warehouse") + attr_reader :activity + + # @return [String] building type CATEGORY (e.g. "industry") + attr_reader :category + + # @return [String] associated BTAP/NECB building type (e.g. "Warehouse") + attr_reader :stdtype + + # @return [Hash] logged messages + attr_reader :feedback + + + ## + # Initialize BTAP Activity parameters. + # + # @param model [OpenStudio::Model::Model] a model + # @param template [String] an NECB template + def initialize(model = nil, template = "NECB2011") + @template = template.respond_to?(:to_s) ? template.to_s.upcase : "" + @activity = "" + @category = "" + @type = "" + @feedback = {logs: []} + + lgs = @feedback[:logs] + mth = "BTAP::Activity::#{__callee__}" + + unless model.is_a?(OpenStudio::Model::Model) + lgs << "Invalid or empty OpenStudio model (#{mth})" + return + end + + if @template.empty? + lgs << "Invalid NECB template: #{template.class} (#{mth})" + return + else + unless data[:templates].include?(@template) + lgs << "#{@template}? Unknown NECB template (#{mth})" + return + end + end + + # Both module CSV files, in addition to a valid OSM file holding either: + # - a valid NECB building TYPE string, or + # - a combination of valid NECB space TYPE strings + # + # ... allow BTAP to set a single building CATEGORY per model, from which + # other key BTAP attributes are set, e.g. structure, envelope. BTAP users + # may hard-set an OSM's building TYPE, or let the solution auto-assign an + # NECB building TYPE, ACTIVITY and CATEGORY, based on the prevalence of + # NECB space types in a model. + bldg = model.getBuilding + type = bldg.standardsBuildingType + + unless type.empty? + type = type.get.downcase + + # Matching building ACTIVITY? + data[:bldg][:activity].each do |key, value| + break unless activity.empty? + next unless type.include?(key) + + @activity = key + @category = value[:category] + @stdtype = value[template] + end + end + + # @todo: Pursue if @activity empty (e.g. loop through std space types). + + # @todo: Many NECB space types are listed as "common". Examples include + # spaces that are educational in nature (e.g. "clssroom", + # "teachinglabs", "auditorium"), typical office spaces (e.g. + # "openplan", "office", "meeting"). This prevents easily auto- + # assigning an overall building type/activity, based on the + # prevalence of space types in a model (e.g. "school"). A fallback + # solution is needed when the predominant space type ends up + # "common" or "rp28" for a given model, such as looking up the 2nd + # (or 3rd) most predominant space type, e.g. "classroom" or + # "office". In such cases, the easiest remains simply assigning an + # NECB building type in the model. + end + end + + # ---- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---- # + # Temporary testing. + # activity = BTAP::Activity.new + # puts activity.data[:space][:table].headers + # puts activity.data[:bldg][:activity].size + # puts activity.data[:bldg][:activities].size # 37 = 35 + "common" + "rp28" + + # activity.data[:space][:activity].values.each_with_index do |v, i| + # raise "BLDG?" unless activity.data[:bldg][:activities].include?(v[:act]) + # end +end diff --git a/lib/openstudio-standards/btap/btap.rb b/lib/openstudio-standards/btap/btap.rb index 47c3fc4d0b..686acb2186 100644 --- a/lib/openstudio-standards/btap/btap.rb +++ b/lib/openstudio-standards/btap/btap.rb @@ -23,6 +23,8 @@ require 'find' require 'date' require_relative 'fileio' +require_relative 'activity' +require_relative 'structure' require_relative 'geometry' require_relative 'envelope' require_relative 'bridging' @@ -359,4 +361,3 @@ def self.get_time_from_string(timestring) end end #module BTAP - diff --git a/lib/openstudio-standards/btap/structure.rb b/lib/openstudio-standards/btap/structure.rb index d67a7388ee..b756314595 100644 --- a/lib/openstudio-standards/btap/structure.rb +++ b/lib/openstudio-standards/btap/structure.rb @@ -21,6 +21,7 @@ # functions. OpenStudio-Standards has TBD as a dependency, which itself extends # OSut. It's odd to pull OSut via TBD - likely a temporary solution (@todo). require "tbd" +require_relative "activity" module BTAP # Building STRUCTURE parameters, ultimately driving BTAP definitions of e.g.: @@ -102,9 +103,8 @@ module BTAP # # Note that there's a (growing?) need to contrast "metal" buildings against # the default "steel" post/beam option. Like a "wood" framed STRUCTURE or a - # load-bearing "cmu" wall, a "metal" building's perimeter STRUCTURE and - # envelope are indistinguishable, i.e. no mixing/matching between STRUCTURE - # and envelope. + # load-bearing "cmu" wall, a "metal" building's envelope structure and skin + # are indistinguishable, i.e. no mixing/matching of STRUCTURE vs envelope. # # There are of course several other (smaller scale) structural options, # often load-bearing envelopes like adobe/hemp/straw bale construction. Most @@ -215,49 +215,51 @@ module StructureData # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # To simplify data management, building TYPES (e.g. those listed in Table # A-8.4.3.2.(2)-A of the NECB 2020) are proposed to fall into more general - # building CATEGORIES: + # building CATEGORIES (see activity.rb): # - # CATEGORY examples - # _______________ _______________________________________________________ - # "dwelling" MURB, long-term stay, hotel, dormitory - # "detention" penitentiary - # "institutional" office, museum, town hall, education, convention centre - # "commercial" dining, retail, gym, dealership, theatre, transportation - # "industrial" automotive, manufacturing, fire station, storage - # "athletics" ice arena, indoor soccer, indoor pool, gymnastics + # CATEGORY examples + # _____________ __________________________________________________________ + # "housing" MURB, long-term stay, dormitory + # "lodging" hotel, motel, highway lodging + # "public" museum, hospital, school, theatre, terminal + # "commerce" office, dining, retail, fitness, dealership, theatre + # "industry" automotive, manufacturing, workshop, storage + # "recreation" gymnastics, ice arena, indoor soccer/pool + # "robust" penitentiary, parking garage (i.e. heavyduty, resistant) # # Each CATEGORY holds "small"-scale and "large"-scale STRUCTURE options by # defaults, depending on the characteristics of the building. - @@category = {} - @@category[:dwelling ] = {small: :wood , large: :concrete} - @@category[:detention ] = {small: :concrete, large: :concrete} - @@category[:institutional] = {small: :steel , large: :concrete} - @@category[:commercial ] = {small: :steel , large: :steel} - @@category[:industrial ] = {small: :concrete, large: :metal} - @@category[:athletics ] = {small: :metal , large: :steel} + @@category = {} + @@category[:housing ] = {small: :wood , large: :concrete} + @@category[:lodging ] = {small: :wood , large: :concrete} + @@category[:robust ] = {small: :concrete, large: :concrete} + @@category[:public ] = {small: :steel , large: :concrete} + @@category[:commerce ] = {small: :steel , large: :steel} + @@category[:industry ] = {small: :cmu , large: :metal} + @@category[:recreation] = {small: :metal , large: :steel} # What constitutes small- vs large-scale varies between CATEGORY, depending - # either on the maximum number of stories or the total building height. - # Categories "detention" and "commercial" do not vary. - @@category[:dwelling ][:stories] = 5 - @@category[:institutional][:stories] = 3 - @@category[:industrial ][:height ] = 4 - @@category[:athletics ][:height ] = 10 - - # For instance, (by default) a multi-unit residential buildings (MURB) would - # have a typical "wood" framed, load-bearing envelope/STRUCTURE up to (and - # including) 5 stories above-grade. This default STRUCTURE assignment + # either on the maximum number of stories, or the max floor-to-roof height + # of the tallest first story space. + @@category[:housing ][:stories] = 4 + @@category[:lodging ][:stories] = 2 + @@category[:public ][:stories] = 2 + @@category[:industry ][:height ] = 4 + @@category[:recreation][:height ] = 10 + + # For instance, a multi-unit residential buildings (MURB) would have a + # typical "wood" framed, load-bearing envelope/STRUCTURE up to (and + # including) 4 stories above-grade. This default STRUCTURE assignment # switches to reinforced "concrete" post + flat slab beyond 5 stories. # Building CATEGORIES that hold neither :stories nor :height key:value pairs - # simply retain the same STRUCTURE option by default (e.g. "detention", - # "commercial"), regardless of scale. + # simply retain the same STRUCTURE option by default, regardless of scale + # (e.g. "robust", "commerce"). # - # This default STRUCTURE assignment (per building CATEGORY) does not imply - # one cannot investigate the pros/cons of CLT construction in MURBs, offices - # or athletic facilities. It rather reflects the current state of affairs - # (i.e. 'dominant' STRUCTURE option per building CATEGORY in large parts of - # the US and Canada). Users have the option of overriding these. - # ... more to come (@todo). + # Default STRUCTURE assignment per building CATEGORY does not preclude the + # investigation of e.g. "clt" construction in MURBs, offices or sporting + # facilities. It simply sets a reasonable reference set of constructions for + # in large parts of the US and Canada. Users have the option of overriding + # default assignments (@todo). ## @@ -327,7 +329,7 @@ class BTAP::Structure # @param model [OpenStudio::Model::Model] a model # @param argh [Hash] BTAP STRUCTURE argument hash def initialize(model = nil, argh = {}) - @category = :institutional + @category = :public @co2 = 0 @feedback = {logs: []} lgs = @feedback[:logs] diff --git a/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb b/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb index 05c1116eca..1aaec1d04f 100644 --- a/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +++ b/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb @@ -6,13 +6,15 @@ class NECB2011 < Standard @template = new.class.name register_standard(@template) - attr_reader :tbd - attr_reader :template + attr_reader :tbd + attr_reader :activity + attr_reader :structure + attr_reader :template attr_accessor :standards_data attr_accessor :space_type_map attr_accessor :space_multiplier_map attr_accessor :fuel_type_set - + # This is a helper method to convert arguments that may support 'NECB_Default, and nils to convert to float' def convert_arg_to_f(variable:, default:) return variable if variable.kind_of?(Numeric) @@ -137,11 +139,14 @@ def initialize @standards_data = load_standards_database_new corrupt_standards_database @tbd = nil + @activity = nil + @structure = nil # puts "loaded these tables..." # puts @standards_data.keys.size # raise("tables not all loaded in parent #{}") if @standards_data.keys.size < 24 end + # @todo Would need revisiting. def get_all_spacetype_names return @standards_data['space_types'].map { |space_types| [space_types['building_type'], space_types['space_type']] } end @@ -401,6 +406,7 @@ def model_apply_standard(model:, space.autocalculateVolume end + assign_building_activity(model: model) apply_weather_data(model: model, epw_file: epw_file, custom_weather_folder: custom_weather_folder) apply_loads(model: model, lights_type: lights_type, @@ -776,10 +782,10 @@ def apply_kiva_foundation(model) kiva_settings = model.getFoundationKivaSettings if !model.getFoundationKivas.empty? end - # check if two surfaces are in contact. For every two consecutive vertices on surface 1, - # loop through two consecutive vertices of surface two. Then check whether the vertices - # of surfaces 2 are on the same line as the vertices from surface 1. If the two vectors - # defined by the two vertices on surface 1 and those on surface 2 overlap, then the two + # check if two surfaces are in contact. For every two consecutive vertices on surface 1, + # loop through two consecutive vertices of surface two. Then check whether the vertices + # of surfaces 2 are on the same line as the vertices from surface 1. If the two vectors + # defined by the two vertices on surface 1 and those on surface 2 overlap, then the two # surfaces are in contact. If a side from surface 2 is in contact with a side from surface 1, # the length of the side from surface 2 is limited to the length of the side from surface 1. # created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca) @@ -819,9 +825,9 @@ def surfaces_are_in_contact?(surf1,surf2) return surfaces_in_contact end - # Loop through the layers of the construction of the surface and replace any massless material with - # a standard one. The material used instead is from the EnergyPlus dataset file 'ASHRAE_2005_HOF_Materials.idf' - # with the name: 'Insulation: Expanded polystyrene - extruded (smooth skin surface) (HCFC-142b exp.)'. + # Loop through the layers of the construction of the surface and replace any massless material with + # a standard one. The material used instead is from the EnergyPlus dataset file 'ASHRAE_2005_HOF_Materials.idf' + # with the name: 'Insulation: Expanded polystyrene - extruded (smooth skin surface) (HCFC-142b exp.)'. # The thickness of the new material is based on the thermal resistance of the massless material it replaces. # created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca) def replace_massless_material_with_std_material(model,surf) @@ -860,8 +866,8 @@ def replace_massless_material_with_std_material(model,surf) end - # Find the exposed perimeter of a floor surface. For each side of the floor loop through - # the walls and find the walls that share sides with the floor. Then sum the lengths of + # Find the exposed perimeter of a floor surface. For each side of the floor loop through + # the walls and find the walls that share sides with the floor. Then sum the lengths of # the sides of the walls that come in contact with sides of the floor. # created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca) def get_surface_exp_per(floor,walls) @@ -896,7 +902,7 @@ def get_surface_exp_per(floor,walls) vert3 = vert4 end end - # increment the exposed perimeter of the floor. Limit the length of the walls in contact with the + # increment the exposed perimeter of the floor. Limit the length of the walls in contact with the # side of the floor to the length of the side of the floor. floor_exp_per += [walls_exp_per,side_length].min vert1 = vert2 @@ -905,7 +911,7 @@ def get_surface_exp_per(floor,walls) return floor_exp_per end - # check that three vertices are on the same line. Also check that the vectors + # check that three vertices are on the same line. Also check that the vectors # from vert1 and vert2 and from vert1 and vert3 are in the same direction. # created by: Kamel Haddad (kamel.haddad@nrcan-rncan.gc.ca) def three_vertices_same_line_and_dir?(vert1,vert2,vert3) @@ -936,7 +942,7 @@ def three_vertices_same_line_and_dir?(vert1,vert2,vert3) return same_line_same_dir 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 @@ -952,10 +958,22 @@ def apply_fdwr_srr_daylighting(model:, fdwr_set: -1.0, srr_set: -1.0, necb_hdd: 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) + + # Denis: Needs revisiting @todo apply_standard_skylight_to_roof_ratio(model: model, srr_set: srr_set) # model_add_daylighting_controls(model) # to be removed after refactor. end + ## + # Assigns BTAP building ACTIVITY (based on NECB 2011 building types). + # + # @param model [OpenStudio::Model::Model] a model + # + # @return [Symbol] BTAP building ACTIVITY (:office if failed, see logs) + def assign_building_activity(model: nil) + @activity = BTAP::Activity.new(model, 2011) + end + ## # Optionally uprates, then derates, envelope surfaces due to MAJOR thermal # bridges (e.g. roof parapets, corners, fenestration perimeters). See @@ -1133,6 +1151,7 @@ def determine_spacetype_vintage(model) end # This method will validate that the space types in the model are indeed the correct NECB spacetypes names. + # Denis: Needs revisiting @todo def validate_and_upate_space_types(model) space_type_vintage = determine_spacetype_vintage(model) if space_type_vintage.nil? @@ -1239,7 +1258,8 @@ def space_apply_infiltration_rate(space) return true end - # @return [Boolean] returns true if successful, false if not + # @return [Boolean] returns true if successful, false if not. + # Denis: Needs revisiting @todo def set_occ_sensor_spacetypes(model, space_type_map) building_type = 'Space Function' space_type_map.each do |space_type_name, space_names| @@ -1845,6 +1865,7 @@ def model_enable_demand_controlled_ventilation(model, dcv_type = 'No_DCV') end end + # Denis: Needs revisiting @todo def set_lighting_per_area_led_lighting(space_type:, definition:, lighting_per_area_led_lighting:, lights_scale:) # puts "#{space_type.name.to_s} - 'space_height' - #{space_height.to_s}" occ_sens_lpd_frac = 1.0 diff --git a/test/necb/unit_tests/tests/test_necb_activities.rb b/test/necb/unit_tests/tests/test_necb_activities.rb new file mode 100644 index 0000000000..697e3bfb7c --- /dev/null +++ b/test/necb/unit_tests/tests/test_necb_activities.rb @@ -0,0 +1,99 @@ +require_relative '../../../helpers/minitest_helper' +require_relative '../../../helpers/create_doe_prototype_helper' +require 'json' + + +# Checks if BTAP::Activity instances are correctly deployed within BTAP. +class NECB_Activity_Tests < Minitest::Test + def test_necb_activities() + + # File paths. + @output_folder = File.join(__dir__, 'output/test_necb_activities') + @expected_results_file = File.join(__dir__, '../expected_results/necb_activities_expected_results.json') + @test_results_file = File.join(__dir__, '../expected_results/necb_activities_test_results.json') + @sizing_run_dir = File.join(@output_folder, 'sizing_folder') + @test_results_array = [] # test results storage array + + # Intial test condition. + @test_passed = true + + # Range of test options. + @templates = [ + 'NECB2011', + # 'NECB2015', + # 'NECB2017' + ] + + @epws = {} + @epws['Calgary'] = 'CAN_AB_Calgary.Intl.AP.718770_CWEC2020.epw' + + @buildings = [ + # 'FullServiceRestaurant', + # 'HighriseApartment', + # 'Hospital', + # 'LargeHotel', + # 'LargeOffice', + # 'MediumOffice', + # 'MidriseApartment', + # 'Outpatient', + # 'PrimarySchool', + # 'QuickServiceRestaurant', + # 'RetailStandalone', + # 'SecondarySchool', + # 'SmallHotel', + 'Warehouse' + ] + + @fuels = ['Electricity'] + + fdback = [] + fdback << "" + fdback << "BTAP::Activity Unit Tests" + fdback << "~~~~ ~~~~ ~~~~ ~~~~" + + @templates.sort.each do |template | + @epws.sort.each do |site, epw| + @buildings.sort.each do |building | + @fuels.sort.each do |fuel | + argh = {} + cas = "CASE #{building} | #{site} (#{template})" + fdback << "" + fdback << cas + + argh[:template ] = template + argh[:epw_file ] = epw + argh[:building_type ] = building + argh[:primary_heating_fuel] = fuel + argh[:sizing_run_dir ] = @sizing_run_dir + + st = Standard.build(template) + model = st.model_create_prototype_model(argh) + fdback << st.activity.template + + if st.activity.activity.empty? + fdback << "Empty BTAP::Activity 'activity' (TODO)" + else + fdback << st.activity.activity + fdback << st.activity.category + fdback << st.activity.stdtype + end + + st.activity.feedback[:logs].each do |log| + end + + # @todo: More testing ... + end # @fuels.sort.each do |fuel | + end # @buildings.sort.each do |building | + end # @epws.sort.each do |site, epw| + end # @templates.sort.each do |template | + + # 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 From 63cba0aa5c59d3737171a8325cc616c081276443 Mon Sep 17 00:00:00 2001 From: rd2 Date: Mon, 25 Nov 2024 07:16:44 -0500 Subject: [PATCH 05/24] Fixes 'steel' typo --- lib/openstudio-standards/btap/structure.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/openstudio-standards/btap/structure.rb b/lib/openstudio-standards/btap/structure.rb index b756314595..2181e77196 100644 --- a/lib/openstudio-standards/btap/structure.rb +++ b/lib/openstudio-standards/btap/structure.rb @@ -166,7 +166,7 @@ module StructureData # # Example A: Composite STRUCTURE: # - "concrete" post/beam STRUCTURE for first 4 building stories - # - s"teel" post/frame STRUCTURE for building stories > 4 + # - "steel" post/frame STRUCTURE for building stories > 4 # # Example B: School gym: # - "cmu" gymnasium walls in an otherwise "steel" post/frame school From 2a4ed9b52a07b6dc70e776b130ba14a482d46f0b Mon Sep 17 00:00:00 2001 From: rd2 Date: Mon, 25 Nov 2024 11:55:39 -0500 Subject: [PATCH 06/24] Initializes nrcan_446 branch: skylights & wells --- .../necb/BTAPPRE1980/btap_pre1980.rb | 23 +++++++++-------- .../necb/BTAPPRE1980/building_envelope.rb | 20 ++++++++------- .../necb/NECB2011/building_envelope.rb | 18 +++++++------ .../standards/necb/NECB2011/necb_2011.rb | 25 ++++++++++--------- 4 files changed, 47 insertions(+), 39 deletions(-) diff --git a/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb b/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb index 976853c958..52b094af8d 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, :osut is set to 'false'. If :osut is set to 'true', 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, osut: false) 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, osut: osut) # 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..6c51f4db3e 100644 --- a/lib/openstudio-standards/standards/necb/BTAPPRE1980/building_envelope.rb +++ b/lib/openstudio-standards/standards/necb/BTAPPRE1980/building_envelope.rb @@ -25,19 +25,21 @@ 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, osut: false) # 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, :osut is set to 'false'. If :osut is set to 'true', SRR is + # instead met using OSut's addSkylights (:srr_set numeric values may apply). @todo 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 diff --git a/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb b/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb index 11d6982f11..a7c6dda571 100644 --- a/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +++ b/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb @@ -161,19 +161,21 @@ 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, osut: false) # 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, :osut is set to 'false'. If :osut is set to 'true', 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 # Get the maximum NECB srr diff --git a/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb b/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb index 935bb0f120..3a6dc264d1 100644 --- a/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +++ b/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb @@ -968,21 +968,24 @@ 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, :osut is set to 'false'. If :osut is set to 'true', 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, osut: false) 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, osut: osut) # model_add_daylighting_controls(model) # to be removed after refactor. end @@ -2495,5 +2498,3 @@ def set_boiler_cap_ratios(boiler_cap_ratio:, boiler_fuel:) return boiler_cap_ratios end end - - From 9dd78b68aa74bd2e469c33b3a0de9affd0658f80 Mon Sep 17 00:00:00 2001 From: rd2 Date: Fri, 6 Dec 2024 14:38:24 -0500 Subject: [PATCH 07/24] Comments out (broken) 'setConstruction' call --- lib/openstudio-standards/btap/bridging.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/openstudio-standards/btap/bridging.rb b/lib/openstudio-standards/btap/bridging.rb index 42b6ca28f0..a0b1603ba9 100644 --- a/lib/openstudio-standards/btap/bridging.rb +++ b/lib/openstudio-standards/btap/bridging.rb @@ -1502,7 +1502,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]) } + # construction[:surfaces].values.each { |surface| surface.setConstruction(construction[:uo]) } end end @@ -2115,4 +2115,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 +# TO-DO. From 4fad85dbfdcd3efbca574e47026a867f33160b83 Mon Sep 17 00:00:00 2001 From: rd2 Date: Mon, 9 Dec 2024 11:28:07 -0500 Subject: [PATCH 08/24] Initial implementation + unit test --- Gemfile.lock.3.7.0 | 10 +- Gemfile.lock.3.8.0 | 10 +- Gemfile.lock.3.9.0 | 10 +- .../necb/NECB2011/building_envelope.rb | 152 +++++++++++----- .../standards/necb/NECB2011/necb_2011.rb | 62 +++++-- openstudio-standards.gemspec | 2 +- .../unit_tests/tests/test_necb_skylights.rb | 162 ++++++++++++++++++ utilities/btap_cli/tests/run_options.yml | 1 + .../btap_cli/tests/run_options_local_osm.yml | 3 +- 9 files changed, 339 insertions(+), 73 deletions(-) create mode 100644 test/necb/unit_tests/tests/test_necb_skylights.rb diff --git a/Gemfile.lock.3.7.0 b/Gemfile.lock.3.7.0 index af9a79d350..1d43ec8628 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.3.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/standards/necb/NECB2011/building_envelope.rb b/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb index a7c6dda571..4a39c4cbdb 100644 --- a/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +++ b/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb @@ -160,8 +160,7 @@ 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, osut: false) + 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). @@ -174,14 +173,16 @@ def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0, osut: false) # <-3.1: Remove all skylights # > 1: Do nothing # - # By default, :osut is set to 'false'. If :osut is set to 'true', SRR is + # 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 @@ -199,6 +200,7 @@ def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0, osut: false) 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 @@ -726,44 +728,116 @@ 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) + # 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 + 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 + + 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 / gra0) * 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}) + + 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 3a6dc264d1..c6e2918c8f 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, @@ -977,29 +1016,18 @@ def three_vertices_same_line_and_dir?(vert1,vert2,vert3) # <-3.1: Remove all the windows/skylights # > 1: Do nothing # - # By default, :osut is set to 'false'. If :osut is set to 'true', 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, osut: false) + # 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, osut: osut) + 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 diff --git a/openstudio-standards.gemspec b/openstudio-standards.gemspec index dcf7e88528..6842e40cc3 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_runtime_dependency 'tbd' 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_skylights.rb b/test/necb/unit_tests/tests/test_necb_skylights.rb new file mode 100644 index 0000000000..9d092edd6b --- /dev/null +++ b/test/necb/unit_tests/tests/test_necb_skylights.rb @@ -0,0 +1,162 @@ +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' + ] + + # 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 = model.getSubSurfaces.sum(&:grossArea) + 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. + ratio = gra0.round > graX.round ? skm2 / graX : skm2 / gra0 + 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 +] From 64536f3304a524e7ad9e58df14a96d1d0a25fda1 Mon Sep 17 00:00:00 2001 From: rd2 Date: Tue, 10 Dec 2024 05:03:35 -0500 Subject: [PATCH 09/24] BTAP pre-1980 tweaks + gem spec tweaks --- Gemfile.lock.3.7.0 | 2 +- .../standards/necb/BTAPPRE1980/btap_pre1980.rb | 8 ++++---- .../standards/necb/BTAPPRE1980/building_envelope.rb | 9 ++++----- openstudio-standards.gemspec | 2 +- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Gemfile.lock.3.7.0 b/Gemfile.lock.3.7.0 index 1d43ec8628..259e56fb02 100644 --- a/Gemfile.lock.3.7.0 +++ b/Gemfile.lock.3.7.0 @@ -139,7 +139,7 @@ GEM stringio (3.1.2) tbd (3.4.4) json-schema (~> 4) - osut (0.3.0) + osut (0.6.0) topolys (0.6.2) thor (1.3.2) tilt (2.4.0) diff --git a/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb b/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb index 52b094af8d..aaf9f48a48 100644 --- a/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb +++ b/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb @@ -54,15 +54,15 @@ def load_standards_database_new # <-3.1: Remove all the windows/skylights # > 1: Do nothing # - # By default, :osut is set to 'false'. If :osut is set to 'true', 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, osut: false) + # 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, osut: osut) + 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 6c51f4db3e..03a4e83d13 100644 --- a/lib/openstudio-standards/standards/necb/BTAPPRE1980/building_envelope.rb +++ b/lib/openstudio-standards/standards/necb/BTAPPRE1980/building_envelope.rb @@ -24,8 +24,7 @@ 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, osut: false) + 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). @@ -38,10 +37,10 @@ def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0, osut: false) # <-3.1: Remove all skylights # > 1: Do nothing # - # By default, :osut is set to 'false'. If :osut is set to 'true', SRR is - # instead met using OSut's addSkylights (:srr_set numeric values may apply). @todo + # 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/openstudio-standards.gemspec b/openstudio-standards.gemspec index 6842e40cc3..759f5c5ec4 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' + spec.add_runtime_dependency 'tbd', '~> 3.4.4' spec.add_development_dependency 'aws-sdk-s3' spec.add_development_dependency 'git-revision' spec.add_development_dependency 'bundler-audit' From 32cb6f100bae0f5e30647c1670108c3fa7999a51 Mon Sep 17 00:00:00 2001 From: rd2 Date: Wed, 11 Dec 2024 10:18:52 -0500 Subject: [PATCH 10/24] Fixes skylight gross area tally --- openstudio-standards.gemspec | 2 +- .../unit_tests/tests/test_necb_skylights.rb | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/openstudio-standards.gemspec b/openstudio-standards.gemspec index 759f5c5ec4..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.4.4' + 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_skylights.rb b/test/necb/unit_tests/tests/test_necb_skylights.rb index 9d092edd6b..70d3ad28ee 100644 --- a/test/necb/unit_tests/tests/test_necb_skylights.rb +++ b/test/necb/unit_tests/tests/test_necb_skylights.rb @@ -33,13 +33,17 @@ def test_necb_skylights() # to toplight occupied spaces below. @buildings = [ 'FullServiceRestaurant', - # 'LargeOffice', - # 'MediumOffice', + 'LargeOffice', + 'MediumOffice', # 'NorthernEducation', - # 'QuickServiceRestaurant', + 'QuickServiceRestaurant', # 'SmallOffice' ] + # NOTE: Skipping NorthernEducation for now: + # - Standards.Model.rb:5432: warning: instance variable @space_type_map not initialized + # - model works in isolation (e.g. SDK 3.6.1, 3.7.0, 3.8.0) + # 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 @@ -56,6 +60,7 @@ def test_necb_skylights() @buildings.sort.each do |building| @templates.sort.each do |template| cas = "CASE #{option} | #{building} (#{template})" + puts; puts; puts cas; puts "--- --- --- --- --- -- ---" srr = case template when 'NECB2020' then 0.02 when 'NECB2017' then 0.02 @@ -94,7 +99,14 @@ def test_necb_skylights() assert(st.osut[:logs].is_a?(Array), err_msg) # Tally skylight areas. Compare with GRAs. - skm2 = model.getSubSurfaces.sum(&:grossArea) + 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) @@ -129,6 +141,9 @@ def test_necb_skylights() # SRR% of 4.5%. The effective 'ratio' would vary based on geometry, # e.g. larger building footprint, wider overhangs. ratio = gra0.round > graX.round ? skm2 / graX : skm2 / gra0 + + puts; puts "#{gra0.round} vs #{graX.round} #{skm2.round}"; puts + assert(ratio.round(2) == srr, "BTAP/OSut: Incorrect SRR (#{cas})?") # Higher level feedback. From cfc5d7114f9ed85d1c053a2619781fb10a2279ad Mon Sep 17 00:00:00 2001 From: rd2 Date: Wed, 11 Dec 2024 14:49:02 -0500 Subject: [PATCH 11/24] Fixes SmallOffice roof overhang area bug --- .../standards/necb/NECB2011/building_envelope.rb | 4 +++- .../standards/necb/NECB2011/necb_2011.rb | 2 +- test/necb/unit_tests/tests/test_necb_skylights.rb | 9 +++------ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb b/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb index 4a39c4cbdb..e1bfa30a2d 100644 --- a/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +++ b/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb @@ -776,6 +776,7 @@ def apply_max_srr_nrcan(model:, srr_lim:, srr_opt: '') 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") @@ -810,7 +811,7 @@ def apply_max_srr_nrcan(model:, srr_lim:, srr_opt: '') # 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 / gra0) * graX + target = (srr_lim * graX / gra0) * gra0 # Filtering out tiny roof surfaces, twisty corridors, etc. types = types.reject { |tp| tp.nameString.downcase.include?("undefined") } @@ -827,6 +828,7 @@ def apply_max_srr_nrcan(model:, srr_lim:, srr_opt: '') 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) diff --git a/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb b/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb index c6e2918c8f..cdb7f21566 100644 --- a/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +++ b/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb @@ -301,7 +301,7 @@ def model_create_prototype_model(template:, space.additionalProperties.setFeature(tag, "nonresconditioned") else if space.nameString.downcase.include?("attic") - space.additionalProperties.setFeature(tag, "unconditioned") + space.additionalProperties.setFeature(tag, "unconditioned") else # treat all other cases as indirectly-conditioned e.g. plenums space.additionalProperties.setFeature(tag, "nonresconditioned") end diff --git a/test/necb/unit_tests/tests/test_necb_skylights.rb b/test/necb/unit_tests/tests/test_necb_skylights.rb index 70d3ad28ee..7277ff1973 100644 --- a/test/necb/unit_tests/tests/test_necb_skylights.rb +++ b/test/necb/unit_tests/tests/test_necb_skylights.rb @@ -37,12 +37,12 @@ def test_necb_skylights() 'MediumOffice', # 'NorthernEducation', 'QuickServiceRestaurant', - # 'SmallOffice' + 'SmallOffice' ] # NOTE: Skipping NorthernEducation for now: - # - Standards.Model.rb:5432: warning: instance variable @space_type_map not initialized - # - model works in isolation (e.g. SDK 3.6.1, 3.7.0, 3.8.0) + # 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 @@ -60,7 +60,6 @@ def test_necb_skylights() @buildings.sort.each do |building| @templates.sort.each do |template| cas = "CASE #{option} | #{building} (#{template})" - puts; puts; puts cas; puts "--- --- --- --- --- -- ---" srr = case template when 'NECB2020' then 0.02 when 'NECB2017' then 0.02 @@ -142,8 +141,6 @@ def test_necb_skylights() # e.g. larger building footprint, wider overhangs. ratio = gra0.round > graX.round ? skm2 / graX : skm2 / gra0 - puts; puts "#{gra0.round} vs #{graX.round} #{skm2.round}"; puts - assert(ratio.round(2) == srr, "BTAP/OSut: Incorrect SRR (#{cas})?") # Higher level feedback. From 4f72316af32f97f1f54f218bd43cad4e6dccabf5 Mon Sep 17 00:00:00 2001 From: brgix Date: Tue, 14 Jan 2025 08:09:31 -0500 Subject: [PATCH 12/24] Reduces gra0 vs graX equations --- .../standards/necb/NECB2011/building_envelope.rb | 2 +- test/necb/unit_tests/tests/test_necb_skylights.rb | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb b/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb index e1bfa30a2d..3e226557c7 100644 --- a/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +++ b/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb @@ -811,7 +811,7 @@ def apply_max_srr_nrcan(model:, srr_lim:, srr_opt: '') # 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 / gra0) * gra0 + target = srr_lim * graX # Filtering out tiny roof surfaces, twisty corridors, etc. types = types.reject { |tp| tp.nameString.downcase.include?("undefined") } diff --git a/test/necb/unit_tests/tests/test_necb_skylights.rb b/test/necb/unit_tests/tests/test_necb_skylights.rb index 7277ff1973..a6453718e6 100644 --- a/test/necb/unit_tests/tests/test_necb_skylights.rb +++ b/test/necb/unit_tests/tests/test_necb_skylights.rb @@ -139,8 +139,15 @@ def test_necb_skylights() # 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. - ratio = gra0.round > graX.round ? skm2 / graX : skm2 / gra0 + 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. From 68e3f30de03441ff318def2efe824bdc79fe8055 Mon Sep 17 00:00:00 2001 From: brgix Date: Wed, 15 Jan 2025 16:09:52 -0500 Subject: [PATCH 13/24] Reintroduce uprated Uo assignment --- lib/openstudio-standards/btap/bridging.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/openstudio-standards/btap/bridging.rb b/lib/openstudio-standards/btap/bridging.rb index a0b1603ba9..04e4958f64 100644 --- a/lib/openstudio-standards/btap/bridging.rb +++ b/lib/openstudio-standards/btap/bridging.rb @@ -1502,7 +1502,8 @@ 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]) + # construction[:surfaces].values.each { |surface| surface.setConstruction(construction[:uo]) } end end From 6691d2ceaa1214442f90a99ee9fde1aa60c99294 Mon Sep 17 00:00:00 2001 From: brgix Date: Thu, 16 Jan 2025 14:47:58 -0500 Subject: [PATCH 14/24] Cleanup + full unit TBD tests --- lib/openstudio-standards/btap/bridging.rb | 792 +++++--------------- test/necb/unit_tests/tests/test_NECB_TBD.rb | 30 +- 2 files changed, 210 insertions(+), 612 deletions(-) diff --git a/lib/openstudio-standards/btap/bridging.rb b/lib/openstudio-standards/btap/bridging.rb index 04e4958f64..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,8 +1106,7 @@ def initialize(model = nil, argh = {}) next unless construction[:stypes ] == stypes next if construction[:surfaces].empty? - BTAP::Geometry::Surfaces.set_surfaces_construction_conductance(construction[:surfaces].values, construction[:uo]) - # construction[:surfaces].values.each { |surface| surface.setConstruction(construction[:uo]) } + BTAP::Geometry::Surfaces.set_surfaces_construction_conductance(construction[:surfaces].values, construction[:uo]) end end @@ -1683,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] = {} @@ -1771,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 @@ -1832,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| @@ -1961,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 @@ -1978,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" @@ -1997,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 # ----- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----- # @@ -2098,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). # @@ -2107,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.: # @@ -2116,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. +# @todo. 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 From a01b87fe4ba7769fd1aaa266a93b2edaa8895a6a Mon Sep 17 00:00:00 2001 From: brgix Date: Mon, 27 Jan 2025 09:48:52 -0500 Subject: [PATCH 15/24] Harmonizes necb_2011.rb (vs nrcan_446) --- .../standards/necb/NECB2011/necb_2011.rb | 237 +++++++++++++----- 1 file changed, 176 insertions(+), 61 deletions(-) diff --git a/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb b/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb index 1aaec1d04f..d26baeec1f 100644 --- a/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +++ b/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb @@ -6,10 +6,11 @@ class NECB2011 < Standard @template = new.class.name register_standard(@template) - attr_reader :tbd - attr_reader :activity - attr_reader :structure - attr_reader :template + attr_reader :tbd + attr_reader :osut + attr_reader :activity + attr_reader :structure + attr_reader :template attr_accessor :standards_data attr_accessor :space_type_map attr_accessor :space_multiplier_map @@ -18,7 +19,7 @@ class NECB2011 < Standard # This is a helper method to convert arguments that may support 'NECB_Default, and nils to convert to float' def convert_arg_to_f(variable:, default:) return variable if variable.kind_of?(Numeric) - return default if variable.nil? || (variable == 'NECB_Default') + return default if variable.nil? || (variable.to_s == 'NECB_Default') return unless variable.kind_of?(String) variable = variable.strip @@ -28,13 +29,25 @@ def convert_arg_to_f(variable:, default:) # This method converts arguments to bool. Anything other than a bool false or string 'false' is converted # to a bool true. Bool false and case insesitive string false are turned into bool false. def convert_arg_to_bool(variable:, default:) - return true if variable.nil? + return default if variable.nil? if variable.is_a? String - return true if variable.to_s.downcase == 'necb_default' + return default if variable.to_s.downcase == 'necb_default' return false if variable.to_s.downcase == 'false' + return true if variable.to_s.downcase == 'true' end return false if variable == false - return true + return variable + end + + # This method checks if a variable is a string. If it is anything but a string it returns the default. If it is a + # string set to "NECB_Default" it return the default. Otherwise it returns the strirng set to it. + def convert_arg_to_string(variable:, default:) + return default if variable.nil? + if variable.is_a? String + return default if variable.to_s.downcase == 'necb_default' + return variable + end + return default end def get_standards_table(table_name:) @@ -141,12 +154,13 @@ def initialize @tbd = nil @activity = nil @structure = 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 end - # @todo Would need revisiting. def get_all_spacetype_names return @standards_data['space_types'].map { |space_types| [space_types['building_type'], space_types['space_type']] } end @@ -172,25 +186,28 @@ def get_necb_hdd18(model:, necb_hdd: true) max_distance_tolerance = 500000 min_distance = 100000000000000.0 necb_closest = nil - epw = BTAP::Environment::WeatherFile.new(model.weatherFile.get.path.get) + weather_file_path = model.weatherFile.get.path.get.to_s + epw_file = model.weatherFile.get.file.get + stat_file_path = weather_file_path.gsub('.epw', '.stat') + stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path) # If necb_hdd is false use the information in the .stat file associated with the.epw file. unless necb_hdd - return epw.hdd18.to_f + return stat_file.hdd18 end # this extracts the table from the json database. necb_2015_table_c1 = @standards_data['tables']['necb_2015_table_c1']['table'] necb_2015_table_c1.each do |necb| next if necb['lat_long'].nil? # Need this until Tyson cleans up table. - dist = distance([epw.latitude.to_f, epw.longitude.to_f], necb['lat_long']) + dist = distance([epw_file.latitude, epw_file.longitude], necb['lat_long']) if min_distance > dist min_distance = dist necb_closest = necb end end - if ((min_distance / 1000.0) > max_distance_tolerance) && !epw.hdd18.nil? + if ((min_distance / 1000.0) > max_distance_tolerance) && !stat_file.hdd18.nil? puts "Could not find close NECB HDD from Table C1 < #{max_distance_tolerance}km. Closest city is #{min_distance / 1000.0}km away. Using epw hdd18 instead." - return epw.hdd18.to_f + return stat_file.hdd18 else dist_clause = "%.2f % #{(min_distance / 1000.0)}" puts "INFO:NECB HDD18 of #{necb_closest['degree_days_below_18_c'].to_f} at nearest city #{necb_closest['city']},#{necb_closest['province']}, at a distance of " + dist_clause + 'km from epw location. Ref: nbc_2015_table_c1' @@ -198,7 +215,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:, @@ -206,6 +223,7 @@ def model_create_prototype_model(template:, debug: false, sizing_run_dir: Dir.pwd, primary_heating_fuel: 'Electricity', + swh_fuel: nil, dcv_type: 'NECB_Default', lights_type: 'NECB_Default', lights_scale: 1.0, @@ -234,6 +252,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, @@ -258,8 +277,43 @@ def model_create_prototype_model(template:, baseline_system_zones_map_option: nil, tbd_option: nil, tbd_interpolate: false, - necb_hdd: true) + 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 + # - "resconditioned" + # + # Sticking to "nonresconditioned" - NECBs do not distinguish between "res" + # vs "non-res" (for e.g. envelope), as opposed to ASHRAE 90.1. + # + # The solution could be further refined in future BTAP versions by 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, @@ -267,6 +321,7 @@ def model_create_prototype_model(template:, custom_weather_folder: custom_weather_folder, sizing_run_dir: sizing_run_dir, primary_heating_fuel: primary_heating_fuel, + swh_fuel: swh_fuel, dcv_type: dcv_type, # Four options: (1) 'NECB_Default', (2) 'No_DCV', (3) 'Occupancy_based_DCV' , (4) 'CO2_based_DCV' lights_type: lights_type, # Two options: (1) 'NECB_Default', (2) 'LED' lights_scale: lights_scale, @@ -295,6 +350,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 @@ -317,9 +373,10 @@ def model_create_prototype_model(template:, output_meters: output_meters, airloop_economizer_type: airloop_economizer_type, # (1) 'NECB_Default'/nil/' (2) 'DifferentialEnthalpy' (3) 'DifferentialTemperature' baseline_system_zones_map_option: baseline_system_zones_map_option, # Three options: (1) 'NECB_Default'/'none'/nil (i.e. 'one_sys_per_bldg'), (2) 'one_sys_per_dwelling_unit', (3) 'one_sys_per_bldg' - necb_hdd: necb_hdd + necb_hdd: necb_hdd, + boiler_fuel: boiler_fuel, + boiler_cap_ratio: boiler_cap_ratio ) - end def load_building_type_from_library(building_type:) @@ -342,7 +399,8 @@ def model_apply_standard(model:, sizing_run_dir: Dir.pwd, necb_reference_hp: false, necb_reference_hp_supp_fuel: 'DefaultFuel', - primary_heating_fuel: 'DefaultFuel', + primary_heating_fuel: 'Electricity', + swh_fuel: nil, dcv_type: 'NECB_Default', lights_type: 'NECB_Default', lights_scale: 'NECB_Default', @@ -370,6 +428,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, @@ -393,13 +452,26 @@ def model_apply_standard(model:, output_meters: nil, airloop_economizer_type: nil, baseline_system_zones_map_option: nil, - necb_hdd: true) + necb_hdd: true, + boiler_fuel: nil, + boiler_cap_ratio: nil) + + apply_weather_data(model: model, epw_file: epw_file, custom_weather_folder: custom_weather_folder) + primary_heating_fuel = validate_primary_heating_fuel(primary_heating_fuel: primary_heating_fuel, model: model) self.fuel_type_set = SystemFuels.new() self.fuel_type_set.set_defaults(standards_data: @standards_data, primary_heating_fuel: primary_heating_fuel) 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) + swh_fuel = convert_arg_to_string(variable: swh_fuel, default: nil) + + boiler_cap_ratios = set_boiler_cap_ratios(boiler_cap_ratio: boiler_cap_ratio, boiler_fuel: boiler_fuel) unless boiler_cap_ratio.nil? && boiler_fuel.nil? + self.fuel_type_set.set_boiler_fuel(standards_data: @standards_data, boiler_fuel: boiler_fuel, boiler_cap_ratios: boiler_cap_ratios) unless boiler_fuel.nil? + self.fuel_type_set.set_swh_fuel(swh_fuel: swh_fuel) unless swh_fuel.nil? || swh_fuel.to_s.downcase == 'defaultfuel' # Ensure the volume calculation in all spaces is done automatically model.getSpaces.sort.each do |space| @@ -407,7 +479,6 @@ def model_apply_standard(model:, end assign_building_activity(model: model) - apply_weather_data(model: model, epw_file: epw_file, custom_weather_folder: custom_weather_folder) apply_loads(model: model, lights_type: lights_type, lights_scale: lights_scale, @@ -434,6 +505,7 @@ def model_apply_standard(model:, apply_fdwr_srr_daylighting(model: model, fdwr_set: fdwr_set, srr_set: srr_set, + srr_opt: srr_opt, necb_hdd: necb_hdd) apply_thermal_bridging(model: model, tbd_option: tbd_option, @@ -551,7 +623,6 @@ def apply_systems_and_efficiencies(model:, # Create Default Systems. apply_systems(model: model, - primary_heating_fuel: primary_heating_fuel, sizing_run_dir: sizing_run_dir, shw_scale: shw_scale, baseline_system_zones_map_option: baseline_system_zones_map_option) @@ -560,7 +631,6 @@ def apply_systems_and_efficiencies(model:, ecm.apply_system_ecm(model: model, ecm_system_name: ecm_system_name, template_standard: self, - primary_heating_fuel: self.fuel_type_set.ecm_fueltype, ecm_system_zones_map_option: ecm_system_zones_map_option) # -------- Performace, Efficiencies, Controls and Sensors ------------ @@ -672,13 +742,13 @@ def apply_weather_data(model:, epw_file:, custom_weather_folder: nil) # If btap_batch didn't transfer the weather file, download it. get_weather_file_from_repo(epw_file: epw_file) unless weather_transfer end - climate_zone = 'NECB HDD Method' + # Fix EMS references. Temporary workaround for OS issue #2598 model_temp_fix_ems_references(model) model.getThermostatSetpointDualSetpoints(&:remove) model.getYearDescription.setDayofWeekforStartDay('Sunday') - model_add_design_days_and_weather_file(model, climate_zone, epw_file) # Standards - model_add_ground_temperatures(model, nil, climate_zone) + weather_file_path = OpenstudioStandards::Weather.get_standards_weather_file_path(epw_file) + OpenstudioStandards::Weather.model_set_building_location(model, weather_file_path: weather_file_path) end def apply_envelope(model:, @@ -944,23 +1014,24 @@ 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) - - # Denis: Needs revisiting @todo - 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 @@ -974,17 +1045,6 @@ def assign_building_activity(model: nil) @activity = BTAP::Activity.new(model, 2011) 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 @@ -1033,7 +1093,7 @@ def apply_thermal_bridging(model: nil, argh[:roofs ][:ut] = roof_U elsif tbd_option == 'good' argh[:quality] = :good - end # default == :bad + end # default == :bad @tbd = BTAP::Bridging.new(model, argh) @@ -1151,7 +1211,6 @@ def determine_spacetype_vintage(model) end # This method will validate that the space types in the model are indeed the correct NECB spacetypes names. - # Denis: Needs revisiting @todo def validate_and_upate_space_types(model) space_type_vintage = determine_spacetype_vintage(model) if space_type_vintage.nil? @@ -1184,11 +1243,6 @@ def validate_and_upate_space_types(model) end end - # Determine whether or not water fixtures are attached to spaces - def model_attach_water_fixtures_to_spaces?(model) - return true - end - # Set the infiltration rate for this space to include # the impact of air leakage requirements in the standard. # @@ -1203,7 +1257,7 @@ def space_apply_infiltration_rate(space) # Remove infiltration rates set at the space object. space.spaceInfiltrationDesignFlowRates.each(&:remove) - exterior_wall_and_roof_and_subsurface_area = space_exterior_wall_and_roof_and_subsurface_area(space) # To do + exterior_wall_and_roof_and_subsurface_area = OpenstudioStandards::Geometry.space_get_exterior_wall_and_subsurface_and_roof_area(space) # To do # Don't create an object if there is no exterior wall area if exterior_wall_and_roof_and_subsurface_area <= 0.0 OpenStudio.logFree(OpenStudio::Info, 'openstudio.Standards.Model', "For #{template}, no exterior wall area was found, no infiltration will be added.") @@ -1258,8 +1312,7 @@ def space_apply_infiltration_rate(space) return true end - # @return [Boolean] returns true if successful, false if not. - # Denis: Needs revisiting @todo + # @return [Boolean] returns true if successful, false if not def set_occ_sensor_spacetypes(model, space_type_map) building_type = 'Space Function' space_type_map.each do |space_type_name, space_names| @@ -1865,7 +1918,6 @@ def model_enable_demand_controlled_ventilation(model, dcv_type = 'No_DCV') end end - # Denis: Needs revisiting @todo def set_lighting_per_area_led_lighting(space_type:, definition:, lighting_per_area_led_lighting:, lights_scale:) # puts "#{space_type.name.to_s} - 'space_height' - #{space_height.to_s}" occ_sens_lpd_frac = 1.0 @@ -2410,7 +2462,7 @@ def extract_weather_data(zipped_file:, weather_dir:) end end if future_file - # Rename the non-ASHRAE.ddy as '_non_ASHRAE.ddy' and save the '_ASHRAE.ddy' as the regular '.ddy' file. This is + # Rename the non ASHRAE.ddy as '_non_ASHRAE.ddy' and save the '_ASHRAE.ddy' as the regular '.ddy' file. This is # because the ASHRAE .ddy file includes sizing information not included in the regular .ddy file for future # weather data files. Unfortunately, openstudio-standards just looks for the regular .ddy file for sizing # information which is why the switch is done. @@ -2427,4 +2479,67 @@ def extract_weather_data(zipped_file:, weather_dir:) return true end + # This method is defined and used by the vintage classes to address he issue with the heat pump fuel types. This + # method does nothing when creating NECB reference buildings. + def validate_primary_heating_fuel(primary_heating_fuel:, model:) + if primary_heating_fuel.to_s.downcase == 'defaultfuel' || primary_heating_fuel.to_s.downcase == 'necb_default' + epw = OpenStudio::EpwFile.new(model.weatherFile.get.path.get) + default_fuel = @standards_data['regional_fuel_use'].detect { |fuel_sources| fuel_sources['state_province_regions'].include?(epw.stateProvinceRegion) }['fueltype_set'] + if default_fuel.nil? + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.swh', "Could not find a default fuel for #{epw.stateProvinceRegion}. Using Electricity as the fuel type instead.") + return 'Electricity' + end + return default_fuel + end + return primary_heating_fuel + end + + # This method expects a string with the following pattern: number-number + # The first number is the percent of the total capacity that the primary boiler's capacity will be set to. + # The second number is the percent of the total capacity that the secondary boiler's capacity will be set to. + # If no boiler_cap_ratio is provided the the primary boiler will have its capacity set to 75% of the total and the + # secondary boiler will have its capacity set to 25% of the total. If a boiler_cap_ratio is set to '0_0' then the + # NECB default capacities are assigned. + def set_boiler_cap_ratios(boiler_cap_ratio:, boiler_fuel:) + # Rules if boiler_fuel is defined + unless boiler_fuel.nil? + # Set the NECB default boiler capacities if the boiler_cap_ratio is set to '0-0' + if boiler_cap_ratio == '0-0' + boiler_cap_ratios = { + primary_ratio: nil, + secondary_ratio: nil + } + return boiler_cap_ratios + elsif !boiler_fuel.to_s.downcase.include?('backup') && boiler_cap_ratio.nil? + # Set the NECB default boiler capacities if the boiler_cap_ratio in not defined and the boiler fuel type is set + # and the primary and secondary fuel types are the same. + boiler_cap_ratios = { + primary_ratio: nil, + secondary_ratio: nil + } + return boiler_cap_ratios + end + end + # Assuming the above rules do not apply, set the default boiler capacity ratio to 75% for the primary boiler and 25% + # for the secondary boiler + if boiler_cap_ratio.nil? + boiler_cap_ratios = { + primary_ratio: 0.75, + secondary_ratio: 0.25 + } + return boiler_cap_ratios + end + # If you defined the boiler capacity ratios set them accordingly. + # Split the capacity ratio using the '-' symbol + string_ratios = boiler_cap_ratio.to_s.split('-') + # Turn the percentages into fractions + primary_ratio = string_ratios[0].to_f/100.0 + secondary_ratio = string_ratios[1].to_f/100.0 + # Set the hash containg the ratios and return + boiler_cap_ratios = { + primary_ratio: primary_ratio, + secondary_ratio: secondary_ratio + } + return boiler_cap_ratios + end end From 524d039df0a846aa481cc57221d99e99f714629b Mon Sep 17 00:00:00 2001 From: brgix Date: Tue, 28 Jan 2025 08:22:02 -0500 Subject: [PATCH 16/24] First consolidation attempt --- .../btap/NECB_building_types.csv | 36 + .../btap/NECB_space_types.csv | 120 +++ lib/openstudio-standards/btap/activity.rb | 222 +++++ lib/openstudio-standards/btap/bridging.rb | 791 +++++------------- lib/openstudio-standards/btap/btap.rb | 3 +- lib/openstudio-standards/btap/structure.rb | 372 ++++++++ .../necb/BTAPPRE1980/btap_pre1980.rb | 23 +- .../necb/BTAPPRE1980/building_envelope.rb | 23 +- .../necb/NECB2011/building_envelope.rb | 164 +++- .../standards/necb/NECB2011/necb_2011.rb | 88 +- openstudio-standards.gemspec | 2 +- .../unit_tests/tests/test_necb_activities.rb | 99 +++ .../unit_tests/tests/test_necb_skylights.rb | 181 ++++ utilities/btap_cli/tests/run_options.yml | 1 + .../btap_cli/tests/run_options_local_osm.yml | 3 +- 15 files changed, 1443 insertions(+), 685 deletions(-) create mode 100644 lib/openstudio-standards/btap/NECB_building_types.csv create mode 100644 lib/openstudio-standards/btap/NECB_space_types.csv create mode 100644 lib/openstudio-standards/btap/activity.rb create mode 100644 lib/openstudio-standards/btap/structure.rb create mode 100644 test/necb/unit_tests/tests/test_necb_activities.rb create mode 100644 test/necb/unit_tests/tests/test_necb_skylights.rb diff --git a/lib/openstudio-standards/btap/NECB_building_types.csv b/lib/openstudio-standards/btap/NECB_building_types.csv new file mode 100644 index 0000000000..90495fa531 --- /dev/null +++ b/lib/openstudio-standards/btap/NECB_building_types.csv @@ -0,0 +1,36 @@ +Activity,Category,NECB 2011 Building Type,NECB 2015 Building Type,NECB 2017 Building Type,NECB 2020 Building Type +arena,recreation,Sports arena,Sports arena,Sports arena,Sports arena +gym,recreation,Gymnasium,Gymnasium,Gymnasium,Gymnasium +exercise,commerce,Exercise centre,Exercise centre,Exercise centre,Exercise centre +office,commerce,Office,Office,Office,Office +retail,commerce,Retail,Retail area,Retail area,Retail area +leisure,commerce,Dining - bar/lounge/leisure,Dining - bar lounge/leisure,Dining - bar lounge/leisure,Dining - bar lounge/leisure +fastfood,commerce,Dining - cafeteria/fastfood,Dining - cafeteria/fastfood,Dining - cafeteria/fastfood,Dining - cafeteria/fastfood +restaurant,commerce,Dining - family restaurant,Dining - family restaurant,Dining - family restaurant,Dining - family restaurant +hotel,lodging,Hotel,Hotel and other lodging,Hotel and other lodging,Hotel and other lodging +motel,lodging,Motel,,, +dorm,housing,Dormitory,Dormitory,Dormitory,Dormitory +residential,housing,Multi-unit residential,Multi-unit residential building,Multi-unit residential building,Multi-unit residential building +care,housing,,Long-term care - dwelling units,Long-term care - dwelling units,Long-term care - dwelling units +clinic,public,Health clinic,Health clinic,Health clinic,Health clinic +hospital,public,Hospital,Hospital,Hospital,Hospital +religious,public,Religious,Religious building,Religious building,Religious building +terminal,public,Terminal (transportation),Terminal (transportation facility),Terminal (transportation facility),Terminal (transportation facility) +theatre,public,Performing arts theatre,Performing arts theatre,Performing arts theatre,Performing arts theatre +cineplex,public,Cineplex (motion picture),Cineplex (motion picture),Cineplex (motion picture),Cineplex (motion picture) +convention,public,Convention centre,Convention centre,Convention centre,Convention centre +museum,public,Museum,Museum,Museum,Museum +library,public,Library,Library,Library,Library +school,public,School/university,School/university,School/university,School/university +postal,public,Postal building,Postal building,Postal building,Postal building +townhall,public,Townhall,Townhall,Townhall,Townhall +court,public,Courthouse,Courthouse,Courthouse,Courthouse +precinct,public,Precinct (police station),Precinct (police station),Precinct (police station),Precinct (police station) +penitentiary,robust,Penitentiary,Penitentiary,Penitentiary,Penitentiary +parking,robust,Parking garage,,, +storage,industry,,Storage garage,Storage garage,Storage garage +warehouse,industry,Warehouse,Warehouse,Warehouse,Warehouse +workshop,industry,Workshop,Workshop,Workshop,Workshop +manufacturing,industry,Manufacturing facility,Manufacturing facility,Manufacturing facility,Manufacturing facility +automotive,industry,Automotive facility,Automotive facility,Automotive facility,Automotive facility +firehouse,industry,Firehouse (fire station),Firehouse (fire station),Firehouse (fire station),Firehouse (fire station) \ No newline at end of file diff --git a/lib/openstudio-standards/btap/NECB_space_types.csv b/lib/openstudio-standards/btap/NECB_space_types.csv new file mode 100644 index 0000000000..b15bff5289 --- /dev/null +++ b/lib/openstudio-standards/btap/NECB_space_types.csv @@ -0,0 +1,120 @@ +Activity,NECB 2011 Space Type,NECB 2015 Space Type,NECB 2017 Space Type,NECB 2020 Space Type +undefined::common,undefined,undefined,undefined,undefined +washroom::rp28,,Washroom - space designed to ANSI/IES RP28 (used primarily by residents),Washroom - space designed to ANSI/IES RP28 (used primarily by residents),Washroom - space designed to ANSI/IES RP28 (used primarily by residents) +washroom::common,Washroom,Washroom - other,Washroom - other,Washroom - other +atrium::common,Atrium,Atrium,Atrium,Atrium +concourse::retail,Retail - mall concourse,Retail facility mall concourse,Retail facility mall concourse,Retail facility mall concourse +concourse::hospital,Hospital concourse (>= 2.4m),,, +concourse::terminal,Terminal (transportation) - concourse,"Terminal (tansportation facility, airport) - concourse","Terminal (tansportation facility, airport) - concourse","Terminal (tansportation facility, airport) - concourse" +concourse::manufacturing,Manufactoring - concourse (>= 2.4m),,, +concourse::common,Concourse (>= 2.4m wide),,, +corridor::rp28,,Corridor/Transition area - space designed to ANSI/IES RP28 (and used primarily by residents),Corridor/Transition area - space designed to ANSI/IES RP28 (and used primarily by residents),Corridor/Transition area - space designed to ANSI/IES RP28 (and used primarily by residents) +corridor::hospital,Hospital corridor (< 2.4m),Corridor/Transition area - hospital,Corridor/Transition area - hospital,Corridor/Transition area - hospital +corridor::manufacturing,Manufacturing - corridor < 2.4m,Corridor/Transition area - manufacturing facility,Corridor/Transition area - manufacturing facility,Corridor/Transition area - manufacturing facility +corridor::common,Corridor < 2.4m wide,Corridor/Transition area other,Corridor/Transition area other,Corridor/Transition area other +stairway::common,Stairway,Stairway/Stairwell,Stairway/Stairwell,Stairway/Stairwell +elevator::common,Foyer - elevator,Foyer - elevator,Foyer - elevator,Foyer - elevator +lobby::rp28,,Lobby - space designed to ANSI/IES RP28 (used primarily by residents),Lobby - space designed to ANSI/IES RP28 (used primarily by residents),Lobby - space designed to ANSI/IES RP28 (used primarily by residents) +lobby::hotel,Hotel/Motel - lobby,Lobby - hotel,Lobby - hotel,Lobby - hotel +lobby::cineplex,Lobby - cineplex (motion picture),Lobby - cineplex (motion picture),Lobby - cineplex (motion picture),Lobby - cineplex (motion picture) +lobby::theatre,Lobby - performance arts theatre,Lobby - performing arts theatre,Lobby - performing arts theatre,Lobby - performing arts theatre +lobby::common,Lobby - other,Lobby - other,Lobby - other,Lobby - other +restoration::museum,Museum - restoration,Museum restoration room,Museum restoration room,Museum restoration room +exhibit::museum,Museum - exhibit,Museum - exhibit,Museum - exhibit,Museum - exhibit +exhibit::convention,Convention centre - exhibit,Convention centre exhibit space,Convention centre exhibit space,Convention centre exhibit space +baggage::terminal,Terminal (transportation) - baggage,Terminal (transportation) baggage/carousel,Terminal (transportation) baggage/carousel,Terminal (transportation) baggage/carousel +counter::terminal,Terminal (transportation) - counter,Terminal (transportation) - ticket counter,Terminal (transportation) - ticket counter,Terminal (transportation) - ticket counter +seating::terminal,Terminal (transportation) seating ,,, +seating::common,,Seating area general,Seating area general,Seating area general +auditorium::common,Audience - auditorium,Audience (permanent) - auditorium,Audience (permanent) - auditorium,Audience (permanent) - auditorium +refectory::religious,Religious - fellowship/refectory,Religious building fellowship/refectory,Religious building fellowship/refectory,Religious building fellowship/refectory +pulpit::religious,Religious - pulpit/choir,Religious building worship/pulpit/choir area,Religious building worship/pulpit/choir area,Religious building worship/pulpit/choir area +chapel::rp28,,Space designed to ANSI/IES RP28 chapel (used primarily by residents),Space designed to ANSI/IES RP28 chapel (used primarily by residents),Space designed to ANSI/IES RP28 chapel (used primarily by residents) +audience::religious,Religious - audience,Audience (permanent) - religious building,Audience (permanent) - religious building,Audience (permanent) - religious building +audience::penitentiary,Penitentiary - audience,Audience (permament) - penitentiary,Audience (permament) - penitentiary,Audience (permament) - penitentiary +audience::cineplex,Audience - cineplex (motion picture),Audience (permament) - cineplex (motion picture),Audience (permament) - cineplex (motion picture),Audience (permament) - cineplex (motion picture) +audience::theatre,Audience - performance arts theatre,Audience (permament) - performing arts theatre,Audience (permament) - performing arts theatre,Audience (permament) - performing arts theatre +audience::convention,Convention centre - audience,Audience (permament) - convention centre,Audience (permament) - convention centre,Audience (permament) - convention centre +audience::gym,Gym - audience,Audience (permament) - gymnasium,Audience (permament) - gymnasium,Audience (permament) - gymnasium +audience::arena,Sports arena - audience,Audience (permament) - sports arena,Audience (permament) - sports arena,Audience (permament) - sports arena +audience::common,,Audience (permament) - other,Audience (permament) - other,Audience (permament) - other +c1::arena,Sports arena - court c1,Sports arena playing area c1 facility,Sports arena playing area c1 facility,Sports arena playing area c1 facility +c2::arena,Sports arena - court c2,Sports arena playing area c2 facility,Sports arena playing area c2 facility,Sports arena playing area c2 facility +c3::arena,Sports arena - court c3,Sports arena playing area c3 facility,Sports arena playing area c3 facility,Sports arena playing area c3 facility +c4::arena,Sports arena - court c4,Sports arena playing area c4 facility,Sports arena playing area c4 facility,Sports arena playing area c4 facility +octogon::arena,"Sports arena - ring, octogon",,, +exercise::gym,"Gym - exercise, fitness",Gymnasium/Fitness centre exercise area,Gymnasium/Fitness centre exercise area,Gymnasium/Fitness centre exercise area +court::gym,Gym - court (play area),Gymnasium/Fitness centre court (playing area),Gymnasium/Fitness centre court (playing area),Gymnasium/Fitness centre court (playing area) +locker::common,Locker room,Locker room,Locker room,Locker room +dressing::theatre,Dressing/fitting - performance arts theatre,Dressing/Fitting room - performing arts theatre,Dressing/Fitting room - performing arts theatre,Dressing/Fitting room - performing arts theatre +dressing::retail,Retail - dressing/fitting,Retail facility dressing/fitting room,Retail facility dressing/fitting room,Retail facility dressing/fitting room +sales::retail,Retail - sales,,, +sales::common,Sales area,Sales area,Sales area,Sales area +dining::rp28,,Dining area - space designed to ANSI/IES RP28 (used primarily by residents),Dining area - space designed to ANSI/IES RP28 (used primarily by residents),Dining area - space designed to ANSI/IES RP28 (used primarily by residents) +dining::penitentiary,Penitentiary - dining,Dining area - penitentiary,Dining area - penitentiary,Dining area - penitentiary +dining::leisure,Dining - bar/leisure,Dining area - bar//leisure,Dining area - bar//leisure,Dining area - bar//leisure +dining::fastfood,,Dining area - cafeteria/fastfood dining,Dining area - cafeteria/fastfood dining,Dining area - cafeteria/fastfood dining +dining::restaurant,Dining - family restaurant,Dining area - family restaurant,Dining area - family restaurant,Dining area - family restaurant +dining::motel,Motel/Hway lodging - dining,,, +dining::hotel,Hotel - dining,,, +dining::common,Dining - other,Dining area - other,Dining area - other,Dining area - other +cuisine::common,Cuisine (preparation area),Cuisine (preparation area),Cuisine (preparation area),Cuisine (preparation area) +rooms::motel,"Motel, Hway lodging - rooms",,, +rooms::hotel,Hotel - rooms,,, +rooms::common,,Guest rooms,Guest rooms,Guest rooms +quarters::dorm,Dormitory - living quarters,Dormitory living quarters,Dormitory living quarters,Dormitory living quarters +quarters::firehouse,Firehouse (fire station) - quarters,Firehouse (fire station) sleeping quarters,Firehouse (fire station) sleeping quarters,Firehouse (fire station) sleeping quarters +units::residential,Residential dwelling unit(s),Residential dwelling units general,Residential dwelling units general,Residential dwelling units general +units::care,,Dwelling units long term care,Dwelling units long term care,Dwelling units long term care +recreation::rp28,,Space designed to ANSI/IES RP28 recreation room (used primarily by residents),Space designed to ANSI/IES RP28 recreation room (used primarily by residents),Space designed to ANSI/IES RP28 recreation room (used primarily by residents) +meeting::common,Conference/meeting/multi-purpose,Conference/Meeting/Multi-purpose room,Conference/Meeting/Multi-purpose room,Conference/Meeting/Multi-purpose room +bureau::common,Bureau (enclosed office),"Bureau (enclosed office, <= 25 m2)","Bureau (office enclosed, <= 25 m2)","Bureau (office enclosed, <= 25 m2)" +workspace::common,,"Workspace (enclosed office, > 25 m2)","Workspace (enclosed office, > 25 m2)","Workspace (enclosed office, > 25 m2)" +openplan::common,Openplan office,Openplan office,Openplan office,Openplan office +bank::common,Bank - banking and offices,Banking activity area and offices,Banking activity area and offices,Banking activity area and offices +copiers::common,,Copiers/Print room,Copiers/Print room,Copiers/Print room +computer::common,,Computer room,Computer room,Computer room +server::common,,Server room,Server room,Server room +sorting::postal,Postal building - sorting,Postal building - sorting,Postal building - sorting,Postal building - sorting +chambers::court,Courthouse - chambers,,, +tribunal::court,Courthouse - tribunal (court room),Tribunal (court room),Tribunal (court room),Tribunal (court room) +cell::court,Courthouse - cell,,, +cell::common,,Confinement cell,Confinement cell,Confinement cell +classroom::penitentiary,Penitentiary - classroom,Classroom/Lecture/room - Penitentary,Classroom/Lecture/Training - Penitentary,Classroom/Lecture/Training - Penitentary +classroom::common,Classroom/lecture/training,Classroom/Lecture/Training - other,Classroom/Lecture/Training - other, +reading::library,Library - reading,Library reading area,Library reading area,Library reading area +stackes::library,Library - stacks,Library stacks,Library stacks,Library stacks +researchlab::common,ResearchLab,,, +teachinglab::common,TeachingLab (class),TeachingLab (class),TeachingLab (class),TeachingLab (class) +lab::common,,Laboratory - other,Laboratory - other,Laboratory - other +storage::common,,Storage (> 100 m2),Storage (> 100 m2), +storeroom::common,Storeroom,"Storeroom (<= 5 m2, <= 100 m2)","Storeroom (<= 5 m2, <= 100 m2)",Storeroom (<= 5 m2) +supplies::common,,Supplies (< 5 m2),Supplies (< 5 m2),Supplies (< 5 m2) +supplies::hospital,Hospital - medical supplies,Hospital - medical supplies,Hospital - medical supplies,Hospital - medical supplies +exam::hospital,Hospital - exam,Hospital - exam/treatment,Hospital - exam/treatment,Hospital - exam/treatment +nursery::hospital,Hospital - nursery,Hospital - nursery,Hospital - nursery,Hospital - nursery +nurses::hospital,Hospital - nurses station,Hospital - nurses station,Hospital - nurses station,Hospital - nurses station +operating::hospital,Hospital - operating room,Hospital - operating room,Hospital - operating room,Hospital - operating room +patient::hospital,Hospital - patient room,Hospital - patient room,Hospital - patient room,Hospital - patient room +therapy::hospital,Hospital - physical therapy,Hospital - physical therapy,Hospital - physical therapy,Hospital - physical therapy +imaging::hospital,Hospital - radiology/imaging,Hospital - radiology/imaging,Hospital - radiology/imaging,Hospital - radiology/imaging +recovery::hospital,Hospital - recovery,Hospital - recovery,Hospital - recovery,Hospital - recovery +laundry::hospital,Hospital - laundry/washing,,, +laundry::common,,Laundry/Washing area,Laundry/Washing area,Laundry/Washing area +lounge::hospital,Hospital - lounge,Lounge/Break room - hospital,Lounge/Break room - hospital,Lounge/Break room - hospital +lounge::common,Lounge/Break room - other,Lounge/Break room - other,Lounge/Break room - other,Lounge/Break room - other +pharmacy::hospital,Hospital - pharmacy,,, +pharmacy::common,,Pharmacy area,Pharmacy area,Pharmacy area +emergency::hospital,Hospital - emergency,,, +emergency::common,,Emergency vehicle garage,Emergency vehicle garage,Emergency vehicle garage +engineroom::firehouse,Firehouse (fire station) - engineroom,,, +garage::parking,Parking garage space,Parking garage interior,Parking garage interior,Parking garage interior +repair::automotive,Automotive - repair,Vehicle repair/maintenance area,Vehicle repair/maintenance area,Vehicle repair/maintenance area +dock::common,,Loading dock interior,Loading dock interior,Loading dock interior +bulky::warehouse,Warehouse - medium/bulky,Warehouse - medium to bulky palletized items,Warehouse - medium to bulky palletized items,Warehouse - medium to bulky palletized items +finer::warehouse,Warehouse - finer,Warehouse - small (finer) hand-carried items,Warehouse - small (finer) hand-carried items,Warehouse - small (finer) hand-carried items +workshop::common,Workshop space,Workshop,Workshop,Workshop +bay::manufacturing,Manufacturing - low bay (H < 7.5m),Manufacturing facility low bay area (H < 7.5m),Manufacturing facility low bay area (H < 7.5m),Manufacturing facility low bay area (H < 7.5m) +detailed::manufacturing,Manufactoring - detailed,Manufacturing facility detailed manufacturing area,Manufacturing facility detailed manufacturing area,Manufacturing facility detailed manufacturing area +equipment::manufacturing,Manufactoring - equipment,Manufacturing facility equipment room,Manufacturing facility equipment room,Manufacturing facility equipment room +mechanical::common,Electrical/Mechanical,Electrical/Mechanical room,Electrical/Mechanical room,Electrical/Mechanical room \ No newline at end of file diff --git a/lib/openstudio-standards/btap/activity.rb b/lib/openstudio-standards/btap/activity.rb new file mode 100644 index 0000000000..6b6a7380ec --- /dev/null +++ b/lib/openstudio-standards/btap/activity.rb @@ -0,0 +1,222 @@ +# **************************************************************************** / +# * Copyright (c) 2008-2025, Natural Resources Canada +# * All rights reserved. +# * +# * This library is free software; you can redistribute it and/or +# * modify it under the terms of the GNU Lesser General Public +# * License as published by the Free Software Foundation; either +# * version 2.1 of the License, or (at your option) any later version. +# * +# * This library is distributed in the hope that it will be useful, +# * but WITHOUT ANY WARRANTY; without even the implied warranty of +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# * Lesser General Public License for more details. +# * +# * You should have received a copy of the GNU Lesser General Public +# * License along with this library; if not, write to the Free Software +# * Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# **************************************************************************** / + +require "csv" + +module BTAP + module ActivityData + @@data = {bldg: {}, space: {}} + + @@data[:templates] = ["NECB2011", "NECB2015", "NECB2017", "NECB2020"] + + # Hard setting path for both files (temporary @todo) + @@data[:bldg ][:file ] = File.join(__dir__, "NECB_building_types.csv") + @@data[:space][:file ] = File.join(__dir__, "NECB_space_types.csv") + @@data[:bldg ][:table ] = nil + @@data[:space][:table ] = nil + @@data[:bldg ][:activity ] = {} + @@data[:space][:activity ] = {} + @@data[:bldg ][:activities] = [] + @@data[:bldg ][:categories] = [] + + # Parse building data on file. + if File.exists?(@@data[:bldg][:file]) + table = CSV.open(@@data[:bldg][:file], headers: true).read + + # 35 unique entries, 6 columns per row: + # column 0: BTAP building ACTIVITY e.g. "restaurant" + # column 1: BTAP building CATEGORY e.g. "commerce" + # column 2: NECB 2011 building type e.g. "Dining - family restaurant" + # column 3: NECB 2015 building type + # column 4: NECB 2017 building type + # column 5: NECB 2020 building type + table.each do |row| + key = row[0] + + @@data[:bldg][:activity][key] = {} + @@data[:bldg][:activity][key][:category ] = row[1] + @@data[:bldg][:activity][key]["NECB2011"] = row[2] + @@data[:bldg][:activity][key]["NECB2015"] = row[3] + @@data[:bldg][:activity][key]["NECB2017"] = row[4] + @@data[:bldg][:activity][key]["NECB2020"] = row[5] + end + + # Keep CSV table. Isolate admissible building activities & categories. + @@data[:bldg][:table ] = table + @@data[:bldg][:activities] = table.by_col[0].uniq + @@data[:bldg][:categories] = table.by_col[1].uniq + # @@data[:bldg][:activities] = table.by_col[0].uniq.map!(&:to_sym) + # @@data[:bldg][:categories] = table.by_col[1].uniq.map!(&:to_sym) + + # Add "common" & "rp28" as building activities. + # @@data[:bldg][:activities] << :common + # @@data[:bldg][:activities] << :rp28 + @@data[:bldg][:activities] << "common" + @@data[:bldg][:activities] << "rp28" + @@data[:bldg][:activities].freeze + @@data[:bldg][:categories].freeze + else + # raise? + end + + # Parse space data on file. + if File.exists?(@@data[:space][:file]) + table = CSV.open(@@data[:space][:file], headers: true).read + + # 119 unique rows, 5 columns per row: + # column 0: BTAP space ACTIVITY e.g. "exhibit::convention" + # column 1: NECB 2011 space type e.g. "Convention centre - exhibit" + # column 2: NECB 2015 space type e.g. "Convention centre exhibit space" + # column 3: NECB 2017 space type + # column 4: NECB 2020 space type + table.each do |row| + key = row[0] + str = key.split("::") + cat = str[0] + act = str[1] + + @@data[:space][:activity][key] = {} + @@data[:space][:activity][key][:act ] = act + @@data[:space][:activity][key][:cat ] = cat + @@data[:space][:activity][key]["NECB2011"] = row[1] + @@data[:space][:activity][key]["NECB2015"] = row[2] + @@data[:space][:activity][key]["NECB2017"] = row[3] + @@data[:space][:activity][key]["NECB2020"] = row[4] + end + + @@data[:space][:table] = table + else + # raise? + end + + ## + # Returns BTAP Activity data. + # + # @return [Hash] BTAP Activity data + def data + @@data + end + + def self.extended(base) + base.send(:include, self) + end + end + + class BTAP::Activity + extend ActivityData + + # @return [Integer] NECB template (e.g. "NECB2011") + attr_reader :template + + # @return [String] assigned building ACTIVITY (e.g. "warehouse") + attr_reader :activity + + # @return [String] building type CATEGORY (e.g. "industry") + attr_reader :category + + # @return [String] associated BTAP/NECB building type (e.g. "Warehouse") + attr_reader :stdtype + + # @return [Hash] logged messages + attr_reader :feedback + + + ## + # Initialize BTAP Activity parameters. + # + # @param model [OpenStudio::Model::Model] a model + # @param template [String] an NECB template + def initialize(model = nil, template = "NECB2011") + @template = template.respond_to?(:to_s) ? template.to_s.upcase : "" + @activity = "" + @category = "" + @type = "" + @feedback = {logs: []} + + lgs = @feedback[:logs] + mth = "BTAP::Activity::#{__callee__}" + + unless model.is_a?(OpenStudio::Model::Model) + lgs << "Invalid or empty OpenStudio model (#{mth})" + return + end + + if @template.empty? + lgs << "Invalid NECB template: #{template.class} (#{mth})" + return + else + unless data[:templates].include?(@template) + lgs << "#{@template}? Unknown NECB template (#{mth})" + return + end + end + + # Both module CSV files, in addition to a valid OSM file holding either: + # - a valid NECB building TYPE string, or + # - a combination of valid NECB space TYPE strings + # + # ... allow BTAP to set a single building CATEGORY per model, from which + # other key BTAP attributes are set, e.g. structure, envelope. BTAP users + # may hard-set an OSM's building TYPE, or let the solution auto-assign an + # NECB building TYPE, ACTIVITY and CATEGORY, based on the prevalence of + # NECB space types in a model. + bldg = model.getBuilding + type = bldg.standardsBuildingType + + unless type.empty? + type = type.get.downcase + + # Matching building ACTIVITY? + data[:bldg][:activity].each do |key, value| + break unless activity.empty? + next unless type.include?(key) + + @activity = key + @category = value[:category] + @stdtype = value[template] + end + end + + # @todo: Pursue if @activity empty (e.g. loop through std space types). + + # @todo: Many NECB space types are listed as "common". Examples include + # spaces that are educational in nature (e.g. "clssroom", + # "teachinglabs", "auditorium"), typical office spaces (e.g. + # "openplan", "office", "meeting"). This prevents easily auto- + # assigning an overall building type/activity, based on the + # prevalence of space types in a model (e.g. "school"). A fallback + # solution is needed when the predominant space type ends up + # "common" or "rp28" for a given model, such as looking up the 2nd + # (or 3rd) most predominant space type, e.g. "classroom" or + # "office". In such cases, the easiest remains simply assigning an + # NECB building type in the model. + end + end + + # ---- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---- # + # Temporary testing. + # activity = BTAP::Activity.new + # puts activity.data[:space][:table].headers + # puts activity.data[:bldg][:activity].size + # puts activity.data[:bldg][:activities].size # 37 = 35 + "common" + "rp28" + + # activity.data[:space][:activity].values.each_with_index do |v, i| + # raise "BLDG?" unless activity.data[:bldg][:activities].include?(v[:act]) + # end +end 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/btap/btap.rb b/lib/openstudio-standards/btap/btap.rb index 7bf428167f..58de4ec809 100644 --- a/lib/openstudio-standards/btap/btap.rb +++ b/lib/openstudio-standards/btap/btap.rb @@ -23,6 +23,8 @@ require 'find' require 'date' require_relative 'fileio' +require_relative 'activity' +require_relative 'structure' require_relative 'geometry' require_relative 'envelope' require_relative 'bridging' @@ -341,4 +343,3 @@ def self.get_time_from_string(timestring) end end #module BTAP - diff --git a/lib/openstudio-standards/btap/structure.rb b/lib/openstudio-standards/btap/structure.rb new file mode 100644 index 0000000000..49c9a95cd4 --- /dev/null +++ b/lib/openstudio-standards/btap/structure.rb @@ -0,0 +1,372 @@ +# **************************************************************************** / +# * Copyright (c) 2008-2025, Natural Resources Canada +# * All rights reserved. +# * +# * This library is free software; you can redistribute it and/or +# * modify it under the terms of the GNU Lesser General Public +# * License as published by the Free Software Foundation; either +# * version 2.1 of the License, or (at your option) any later version. +# * +# * This library is distributed in the hope that it will be useful, +# * but WITHOUT ANY WARRANTY; without even the implied warranty of +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# * Lesser General Public License for more details. +# * +# * You should have received a copy of the GNU Lesser General Public +# * License along with this library; if not, write to the Free Software +# * Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# **************************************************************************** / + +# Note: The following is fairly self-contained, requiring only a few OSut +# functions. OpenStudio-Standards has TBD as a dependency, which itself extends +# OSut. It's odd to pull OSut via TBD - likely a temporary solution (@todo). +require "tbd" +require_relative "activity" + +module BTAP + # Building STRUCTURE parameters, ultimately driving BTAP definitions of e.g.: + # - internal mass + # - envelope CLADDING/FRAMING/FINISH selection + # - related thermal bridging calculations (and uprated insulation levels) + # - costing + # - embodied carbon tallies + # + # As detailed a bit further on, this determination is either via user input: + # - e.g. "clt" (mass timber) post/beam STRUCTURE, for a school. + # + # Or auto-assigned based on the prevalence of model space type assignments: + # - e.g. 75% of spaces are commercial in nature, therefore the building + # STRUCTURE defaults to "steel" post/beam + # + # The overarching idea is that (in most cases) OpenStudio surface construction + # & material choices (in addition to internal mass definitions), mostly stem + # from underlying structural design choices (which aren't natively defined in + # OpenStudio). Structural choices have more to do with fire safety, budget, + # practicality (low-rise vs high-rise), local workforce, durability, etc. + # Ensuring consistency between building STRUCTURE, envelope selection, + # internal mass definitions, etc. is key in harmonizing predicted energy use, + # peak demand assessments, GHG emissions, vs thermal resilience and embodied + # energy/GHG tallies. + # + # Although "wood" framed walls constitute the load-bearing components of a + # "wood" framed building STRUCTURE (ex. low-rise housing), they can equally be + # found as non-load-bearing components in a "clt" post-beam STRUCTURE. Light + # gauge "steel" framed walls are much more common in a non-residential + # STRUCTURE (e.g. "steel" post/frame, "concrete" post & beam, and even "clt"), + # though rarely found in low-rise housing. Although one may observe some real + # -world mixing of STRUCTURE vs FRAMING in a building, it remains largely + # deterministic: designers select constructions (FRAMING, insulation) while + # taking building classification and STRUCTURE selection into consideration + # - the inverse is rarely true. + # + # STRUCTURE description + # __________ ___________________________________________________________ + # "steel" steel, post/frame (default) + # "metal" pre-fab panelized steel STRUCTURE (**, ++), typically 1 story + # "concrete" reinforced concrete, post/beam/slab + # "cmu" load-bearing concrete masonry unit walls, typically 1-story + # "wood" conventional load-bearing wood-framed and/or -engineered + # "clt" pre-fab, post/beam mass/cross-laminated/timber (**) + # + # NOTES: + # + # ** Neither "metal" nor "clt" options can be considered as fully + # supported by BTAP, e.g.: + # - no range of admissible envelope Uo factors + # - no associated PSI-factors (thermal bridging) + # - no costing data + # - no embodied energy/carbon data + # They are nonetheless (minimally) maintained here as an "aide-mémoire" + # for future BTAP upgrades - @todo. + # + # ++ ASHRAE 90.1 2022 definitions of: + # + # "METAL BUILDING": a complete integrated set of mutually dependent + # components and assemblies that form a building, which consists of a + # steel-framed superSTRUCTURE and metal skin. + # + # "METAL BUILDING ROOF": a roof that: + # a. is constructed with a metal, structural, weathering surface; + # b. has no ventilated cavity; and + # c. has the insulation entirely below deck (i.e., does not include + # composite concrete and metal deck construction nor a roof + # FRAMING system that is separated from the superSTRUCTURE by a + # wood substrate) and whose STRUCTURE consists of one or more of the + # following configurations: + # 1. Metal roofing in direct contact with the steel FRAMING members + # 2. Metal roofing separated from steel FRAMING by insulation + # 3. Insulated metal roofing panels installed per (a) or (b) + # + # "METAL BUILDING WALL": a wall whose STRUCTURE consists of metal + # spanning members supported by steel structural members (i.e. does not + # include spandrel glass or metal panels in curtain wall systems). + # + # Note that there's a (growing?) need to contrast "metal" buildings against + # the default "steel" post/beam option. Like a "wood" framed STRUCTURE or a + # load-bearing "cmu" wall, a "metal" building's envelope structure and skin + # are indistinguishable, i.e. no mixing/matching of STRUCTURE vs envelope. + # + # There are of course several other (smaller scale) structural options, + # often load-bearing envelopes like adobe/hemp/straw bale construction. Most + # would agree that these are fairly rare occurrences - rare enough to avoid + # shortlisting them for commercial building stock assessments. One could + # state the same when it comes to the current (marginal) use of "clt". Yet as + # the latter is rapidly becoming a robust low-carbon alternative to "steel" + # and "concrete" options, its inclusion is justified. Additional options may + # nonetheless be added in the future. + + module StructureData + # Each STRUCTURE inherits a default FRAMING option. Together with the + # STRUCTURE selection, FRAMING determines inter alia: + # - above-grade floor assemblies + # - insulated roof assemblies + # - cantilevered balconies + # - interzone walls + @@structure = {} + @@structure[:steel ] = {framing: :steel} + @@structure[:metal ] = {framing: :metal} + @@structure[:concrete] = {framing: :steel} + @@structure[:cmu ] = {framing: :cmu } + @@structure[:wood ] = {framing: :wood } + @@structure[:clt ] = {framing: :wood } + + # An example. STRUCTURE == "wood" + default FRAMING == "wood", e.g. housing: + # - typical engineered wood joists + FINISH + # - similar engineered wood rafters + FINISH (if flat or cathedral roof) + # - anchored engineered wood joist balconies + # - standard 2"x4" wood-framed interzone walls + # + # FRAMING may also determine above-grade exterior wall composition (e.g. + # wool-insulated wood-framed exterior walls, if FRAMING == "wood"). This + # may instead be determined by CLADDING selection in several cases. + # + # Exterior CLADDING and interior FINISH options are both limited to 4 + # generic labels. Defaults for all STRUCTUREs are "light", for both CLADDING + # (e.g. metal siding on vented hat-channels) and FINISH (e.g. painted + # drywall). Brick veneer is an example of "medium" CLADDING, while a 4" + # precast concrete panel is considered "heavy" CLADDING. A "medium" FINISH + # is akin to a 4" precast panel concrete, while a "heavy" FINISH is a + # heftier 8" of (poured) reinforced concrete. Option "none" for CLADDING is + # rare, even in pre-code buildings. An example would be a load-bearing, + # "cmu" wall with 2 coats of paint in a semi-heated industrial facility. The + # "none" FINSIH option is slightly more common, e.g. exposed ceilings, bare + # "clt" walls, and again bare "cmu" walls (or with 2 coats of paint). + @@cladding = [:none, :light, :medium, :heavy] + @@finish = [:none, :light, :medium, :heavy] + + # An above-grade building STRUCTURE may be optionally auto-assigned based on + # the prevalence of space type selections in the model (see CATEGORY below). + # Note that all below-grade STRUCTUREs remain "concrete", e.g.: + # - basement slabs and slabs-on-grade + # - load-bearing basement walls + # - basement columns, shear walls, etc. (internal mass) + # + # Users can optionally assign STRUCTURE, FRAMING, CLADDING & FINISH options + # along OpenStudio's building-to-space hierarchy, e.g.: + # + # Example A: Composite STRUCTURE: + # - "concrete" post/beam STRUCTURE for first 4 building stories + # - "steel" post/frame STRUCTURE for building stories > 4 + # + # Example B: School gym: + # - "cmu" gymnasium walls in an otherwise "steel" post/frame school + # + # An invalid user-selected STRUCTURE is however caught/logged/corrected: + # - no other STRUCTURE above "steel", "metal", "cmu" or "wood" STRUCTUREs + # - a "wood" STRUCTURE may rest upon a "clt" STRUCTURE + # - any other STRUCTURE may rest upon a "concrete" STRUCTURE + # + # With the exception of "metal" buildings, users may optionally interchange + # some paired STRUCTURE vs FRAMING options (among available :frames above). + # Unusual in some cases, yet not completely unheard of. + @@structure[:steel ][:frames] = [:wood ] + @@structure[:metal ][:frames] = [ ] + @@structure[:concrete][:frames] = [:wood, :cmu ] + @@structure[:cmu ][:frames] = [:wood, :steel] + @@structure[:wood ][:frames] = [:steel ] + @@structure[:clt ][:frames] = [:clt, :steel ] + + + # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- # + # STRUCTURE options hold "co2" carbon intensities (in CO2-e kg/m2), which + # are placeholders for now (to be replaced at some point by 3rd-party + # estimates). They're meant to specifically track the carbon footprint of + # the STRUCTURE (not the envelope, nor interior partitions, nor integrated + # furniture). These estimates should include the embodied carbon of + # above-grade, structural floors per se, in addition to the embodied carbon + # of structural elements that can't (or are unlikely to) be represented in + # an OpenStudio model, e.g.: + # - columns + # - bracing + # - stairwells and elevator shafts + @@structure[:steel ][:co2] = 200 + @@structure[:metal ][:co2] = 200 + @@structure[:concrete][:co2] = 200 + @@structure[:cmu ][:co2] = 200 + @@structure[:wood ][:co2] = 200 + @@structure[:clt ][:co2] = 200 + + # Once refined, these CO2-e kg/m2 estimates are expected to differ + # considerably between STRUCTURE options, and possibly affected by regional + # considerations (i.e. national vs local estimates), number of building + # stories, structural requirements, etc. - to be set parametrically. + + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # To simplify data management, building TYPES (e.g. those listed in Table + # A-8.4.3.2.(2)-A of the NECB 2020) are proposed to fall into more general + # building CATEGORIES (see activity.rb): + # + # CATEGORY examples + # _____________ __________________________________________________________ + # "housing" MURB, long-term stay, dormitory + # "lodging" hotel, motel, highway lodging + # "public" museum, hospital, school, theatre, terminal + # "commerce" office, dining, retail, fitness, dealership, theatre + # "industry" automotive, manufacturing, workshop, storage + # "recreation" gymnastics, ice arena, indoor soccer/pool + # "robust" penitentiary, parking garage (i.e. heavyduty, resistant) + # + # Each CATEGORY holds "small"-scale and "large"-scale STRUCTURE options by + # defaults, depending on the characteristics of the building. + @@category = {} + @@category[:housing ] = {small: :wood , large: :concrete} + @@category[:lodging ] = {small: :wood , large: :concrete} + @@category[:robust ] = {small: :concrete, large: :concrete} + @@category[:public ] = {small: :steel , large: :concrete} + @@category[:commerce ] = {small: :steel , large: :steel} + @@category[:industry ] = {small: :cmu , large: :metal} + @@category[:recreation] = {small: :metal , large: :steel} + + # What constitutes small- vs large-scale varies between CATEGORY, depending + # either on the maximum number of stories, or the max floor-to-roof height + # of the tallest first story space. + @@category[:housing ][:stories] = 4 + @@category[:lodging ][:stories] = 2 + @@category[:public ][:stories] = 2 + @@category[:industry ][:height ] = 4 + @@category[:recreation][:height ] = 10 + + # For instance, a multi-unit residential buildings (MURB) would have a + # typical "wood" framed, load-bearing envelope/STRUCTURE up to (and + # including) 4 stories above-grade. This default STRUCTURE assignment + # switches to reinforced "concrete" post + flat slab beyond 5 stories. + # Building CATEGORIES that hold neither :stories nor :height key:value pairs + # simply retain the same STRUCTURE option by default, regardless of scale + # (e.g. "robust", "commerce"). + # + # Default STRUCTURE assignment per building CATEGORY does not preclude the + # investigation of e.g. "clt" construction in MURBs, offices or sporting + # facilities. It simply sets a reasonable reference set of constructions for + # in large parts of the US and Canada. Users have the option of overriding + # default assignments (@todo). + + + ## + # Returns building structure data. + # + # @return [Hash] building structure data + def structure + @@structure + end + + ## + # Returns exterior cladding options. + # + # @return [Array] exterior cladding options + def cladding + @@cladding + end + + ## + # Returns interior finish options. + # + # @return [Array] interior finish options + def finish + @@finish + end + + ## + # Returns building category data. + # + # @return [Hash] building category data + def category + @@category + end + + def self.extended(base) + base.send(:include, self) + end + end + + class BTAP::Structure + extend StructureData + + TOL = TBD::TOL + TOL2 = TBD::TOL2 + DBG = TBD::DBG + INF = TBD::INF + WRN = TBD::WRN + ERR = TBD::ERR + FTL = TBD::FTL + + # @return [String] dominant building type CATEGORY (e.g. :institutional) + attr_reader :category + + # @return [String] dominant building STRUCTURE selection (e.g. :steel) + attr_reader :structure + + # @return [Float] calculated embodied carbon of STRUCTURE (CO2-e kg/m2) + attr_reader :co2 + + # @return [Hash] logged messages + attr_reader :feedback + + + ## + # Initialize BTAP STRUCTURE parameters. + # + # @param model [OpenStudio::Model::Model] a model + # @param argh [Hash] BTAP STRUCTURE argument hash + def initialize(model = nil, argh = {}) + @category = :public + @co2 = 0 + @feedback = {logs: []} + lgs = @feedback[:logs] + + # @todo + end + + ## + # Returns embodied carbon, strictly related to building STRUCTURE. + # + # @param model [OpenStudio::Model::Model] a model + # + # @return [Float] STRUCTURE related embodied carbon (CO2-e kg/m2) + def tallyCO2(model) + mth = "BTAP::Structure::#{__callee__}" + cl = OpenStudio::Model::Model + lgs = @feedback[:logs] + + unless model.is_a?(cl) + lgs << "Invalid OpenStudio model (#{mth})" + return 0 + end + + unless argh.is_a?(Hash) + lgs << "Invalid argument Hash (#{mth})" + return 0 + end + + if argh.empty? + lgs << "Empty argument hash (#{mth})" + return 0 + end + + # - tally above-grade vs below-grade floor areas + # - apply associated CO2-e kg/m2 + # - return + # @todo + end + end +end 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..41f053005f 100644 --- a/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +++ b/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb @@ -161,25 +161,27 @@ 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.to_f <= 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 @@ -724,44 +726,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 - # 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) + 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 + + 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..d26baeec1f 100644 --- a/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +++ b/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb @@ -7,6 +7,9 @@ class NECB2011 < Standard @template = new.class.name register_standard(@template) attr_reader :tbd + attr_reader :osut + attr_reader :activity + attr_reader :structure attr_reader :template attr_accessor :standards_data attr_accessor :space_type_map @@ -149,6 +152,10 @@ def initialize @standards_data = load_standards_database_new corrupt_standards_database @tbd = nil + @activity = nil + @structure = 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 +215,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 +252,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 +280,40 @@ 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 + # - "resconditioned" + # + # Sticking to "nonresconditioned" - NECBs do not distinguish between "res" + # vs "non-res" (for e.g. envelope), as opposed to ASHRAE 90.1. + # + # The solution could be further refined in future BTAP versions by 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 +350,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 +428,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 +463,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) @@ -434,6 +478,7 @@ def model_apply_standard(model:, space.autocalculateVolume end + assign_building_activity(model: model) apply_loads(model: model, lights_type: lights_type, lights_scale: lights_scale, @@ -460,6 +505,7 @@ def model_apply_standard(model:, apply_fdwr_srr_daylighting(model: model, fdwr_set: fdwr_set, srr_set: srr_set, + srr_opt: srr_opt, necb_hdd: necb_hdd) apply_thermal_bridging(model: model, tbd_option: tbd_option, @@ -968,34 +1014,36 @@ 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. + # Assigns BTAP building ACTIVITY (based on NECB 2011 building types). # - # @param model [OpenStudio::Model::Model] an OpenStudio model - # @param tbd_option [String] BTAP/TBD option + # @param model [OpenStudio::Model::Model] a model # - # @return [Boolean] true if successful, e.g. no errors, compliant if uprated + # @return [Symbol] BTAP building ACTIVITY (:office if failed, see logs) + def assign_building_activity(model: nil) + @activity = BTAP::Activity.new(model, 2011) + end ## # (Optionally) uprates, then derates, envelope surface constructions due to @@ -1045,7 +1093,7 @@ def apply_thermal_bridging(model: nil, argh[:roofs ][:ut] = roof_U elsif tbd_option == 'good' argh[:quality] = :good - end # default == :bad + end # default == :bad @tbd = BTAP::Bridging.new(model, argh) @@ -2495,5 +2543,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_activities.rb b/test/necb/unit_tests/tests/test_necb_activities.rb new file mode 100644 index 0000000000..4ab1c49a17 --- /dev/null +++ b/test/necb/unit_tests/tests/test_necb_activities.rb @@ -0,0 +1,99 @@ +require_relative '../../../helpers/minitest_helper' +require_relative '../../../helpers/create_doe_prototype_helper' +require 'json' + + +# Checks if BTAP::Activity instances are correctly deployed within BTAP. +class NECB_Activity_Tests < Minitest::Test + def test_necb_activities() + + # File paths. + @output_folder = File.join(__dir__, 'output/test_necb_activities') + @expected_results_file = File.join(__dir__, '../expected_results/necb_activities_expected_results.json') + @test_results_file = File.join(__dir__, '../expected_results/necb_activities_test_results.json') + @sizing_run_dir = File.join(@output_folder, 'sizing_folder') + @test_results_array = [] # test results storage array + + # Intial test condition. + @test_passed = true + + # Range of test options. + @templates = [ + 'NECB2011', + # 'NECB2015', + # 'NECB2017' + ] + + @epws = {} + @epws['Calgary'] = 'CAN_AB_Calgary.Intl.AP.718770_CWEC2020.epw' + + @buildings = [ + # 'FullServiceRestaurant', + # 'HighriseApartment', + # 'Hospital', + # 'LargeHotel', + # 'LargeOffice', + # 'MediumOffice', + # 'MidriseApartment', + # 'Outpatient', + # 'PrimarySchool', + # 'QuickServiceRestaurant', + # 'RetailStandalone', + # 'SecondarySchool', + # 'SmallHotel', + 'Warehouse' + ] + + @fuels = ['Electricity'] + + fdback = [] + fdback << "" + fdback << "BTAP::Activity Unit Tests" + fdback << "~~~~ ~~~~ ~~~~ ~~~~" + + @templates.sort.each do |template | + @epws.sort.each do |site, epw| + @buildings.sort.each do |building | + @fuels.sort.each do |fuel | + argh = {} + cas = "CASE #{building} | #{site} (#{template})" + fdback << "" + fdback << cas + + argh[:template ] = template + argh[:epw_file ] = epw + argh[:building_type ] = building + argh[:primary_heating_fuel] = fuel + argh[:sizing_run_dir ] = @sizing_run_dir + + st = Standard.build(template) + model = st.model_create_prototype_model(argh) + fdback << st.activity.template + + if st.activity.activity.empty? + fdback << "Empty BTAP::Activity 'activity' (TODO)" + else + fdback << st.activity.activity + fdback << st.activity.category + fdback << st.activity.stdtype + end + + st.activity.feedback[:logs].each do |log| + end + + # @todo: More testing ... + end # @fuels.sort.each do |fuel | + end # @buildings.sort.each do |building | + end # @epws.sort.each do |site, epw| + end # @templates.sort.each do |template | + + # 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/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..e24cbe51d4 100644 --- a/utilities/btap_cli/tests/run_options.yml +++ b/utilities/btap_cli/tests/run_options.yml @@ -55,6 +55,7 @@ :shw_eff: NECB_Default :skylight_cond: NECB_Default :skylight_solar_trans: NECB_Default +:srr_opt: '' :srr_set: NECB_Default :swh_fuel: NECB_Default :template: NECB2017 diff --git a/utilities/btap_cli/tests/run_options_local_osm.yml b/utilities/btap_cli/tests/run_options_local_osm.yml index 9fed56fc55..2fe72a2c49 100644 --- a/utilities/btap_cli/tests/run_options_local_osm.yml +++ b/utilities/btap_cli/tests/run_options_local_osm.yml @@ -54,6 +54,7 @@ :shw_eff: NECB_Default :skylight_cond: NECB_Default :skylight_solar_trans: NECB_Default +:srr_opt: '' :srr_set: NECB_Default :template: NECB2017 :npv_start_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 +] From 9ba851c0674181bacb282784516d77e79391ef9e Mon Sep 17 00:00:00 2001 From: brgix Date: Tue, 28 Jan 2025 08:32:44 -0500 Subject: [PATCH 17/24] Updates copyright year --- lib/openstudio-standards/btap/activity.rb | 2 +- lib/openstudio-standards/btap/structure.rb | 2 +- test/necb/unit_tests/tests/test_necb_activities.rb | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/openstudio-standards/btap/activity.rb b/lib/openstudio-standards/btap/activity.rb index 7aeee21782..6b6a7380ec 100644 --- a/lib/openstudio-standards/btap/activity.rb +++ b/lib/openstudio-standards/btap/activity.rb @@ -1,5 +1,5 @@ # **************************************************************************** / -# * Copyright (c) 2008-2024, Natural Resources Canada +# * Copyright (c) 2008-2025, Natural Resources Canada # * All rights reserved. # * # * This library is free software; you can redistribute it and/or diff --git a/lib/openstudio-standards/btap/structure.rb b/lib/openstudio-standards/btap/structure.rb index 2181e77196..49c9a95cd4 100644 --- a/lib/openstudio-standards/btap/structure.rb +++ b/lib/openstudio-standards/btap/structure.rb @@ -1,5 +1,5 @@ # **************************************************************************** / -# * Copyright (c) 2008-2024, Natural Resources Canada +# * Copyright (c) 2008-2025, Natural Resources Canada # * All rights reserved. # * # * This library is free software; you can redistribute it and/or diff --git a/test/necb/unit_tests/tests/test_necb_activities.rb b/test/necb/unit_tests/tests/test_necb_activities.rb index 697e3bfb7c..4ab1c49a17 100644 --- a/test/necb/unit_tests/tests/test_necb_activities.rb +++ b/test/necb/unit_tests/tests/test_necb_activities.rb @@ -82,10 +82,10 @@ def test_necb_activities() end # @todo: More testing ... - end # @fuels.sort.each do |fuel | - end # @buildings.sort.each do |building | - end # @epws.sort.each do |site, epw| - end # @templates.sort.each do |template | + end # @fuels.sort.each do |fuel | + end # @buildings.sort.each do |building | + end # @epws.sort.each do |site, epw| + end # @templates.sort.each do |template | # Temporary. fdback.each { |msg| puts msg } From b816e2529fa14f09dab0252328ee01ec42492428 Mon Sep 17 00:00:00 2001 From: brgix Date: Tue, 28 Jan 2025 08:46:08 -0500 Subject: [PATCH 18/24] Harmonizes with nrcan_450 --- .../necb/NECB2011/building_envelope.rb | 2 +- .../standards/necb/NECB2011/necb_2011.rb | 26 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb b/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb index 3e226557c7..3ac4b4d847 100644 --- a/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +++ b/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb @@ -176,7 +176,7 @@ def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0, srr_opt: '') # 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, srr_opt: srr_opt) 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.to_f <= 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, srr_opt: srr_opt) if srr_set.to_f >= -1.1 && srr_set.to_f <= -0.9 diff --git a/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb b/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb index cdb7f21566..b858d6354c 100644 --- a/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +++ b/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb @@ -8,6 +8,8 @@ class NECB2011 < Standard register_standard(@template) attr_reader :tbd attr_reader :osut + attr_reader :activity + attr_reader :structure attr_reader :template attr_accessor :standards_data attr_accessor :space_type_map @@ -150,6 +152,8 @@ def initialize @standards_data = load_standards_database_new corrupt_standards_database @tbd = nil + @activity = nil + @structure = nil @osut = {gra0: 0, graX: 0, status: 0, logs: []} # puts "loaded these tables..." @@ -286,10 +290,12 @@ def model_create_prototype_model(template:, # # As per ASHRE 90.1, OpenStudio-Standards distinguishes between: # - "nonresconditioned" vs - # - "nonresconditioned" + # - "resconditioned" # - # Sticking to "nonresconditioned" - NECBs don't care. This could be further - # refined in future BTAP versions though, e.g.: + # Sticking to "nonresconditioned" - NECBs do not distinguish between "res" + # vs "non-res" (for e.g. envelope), as opposed to ASHRAE 90.1. + # + # The solution could be further refined in future BTAP versions by e.g.: # - relying on user-defined thermostats # - expanded to cover semi-heated and refrigerated spaces tag = "space_conditioning_category" @@ -498,8 +504,8 @@ def model_apply_standard(model:, apply_fdwr_srr_daylighting(model: model, fdwr_set: fdwr_set, srr_set: srr_set, - necb_hdd: necb_hdd, - srr_opt: srr_opt) + srr_opt: srr_opt, + necb_hdd: necb_hdd) apply_thermal_bridging(model: model, tbd_option: tbd_option, tbd_interpolate: tbd_interpolate, @@ -1028,6 +1034,16 @@ def apply_fdwr_srr_daylighting(model:, fdwr_set: -1.0, srr_set: -1.0, necb_hdd: # model_add_daylighting_controls(model) # to be removed after refactor. end + ## + # Assigns BTAP building ACTIVITY (based on NECB 2011 building types). + # + # @param model [OpenStudio::Model::Model] a model + # + # @return [Symbol] BTAP building ACTIVITY (:office if failed, see logs) + def assign_building_activity(model: nil) + @activity = BTAP::Activity.new(model, 2011) + end + ## # (Optionally) uprates, then derates, envelope surface constructions due to # MAJOR thermal bridges (e.g. roof parapets, corners, fenestration From 563a9d4fc1ea7a66aa87cb63811189bee5da7321 Mon Sep 17 00:00:00 2001 From: brgix Date: Tue, 28 Jan 2025 08:50:22 -0500 Subject: [PATCH 19/24] Manual tweaks (minor) --- .../standards/necb/NECB2011/building_envelope.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb b/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb index 41f053005f..be1d747dcc 100644 --- a/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +++ b/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb @@ -160,7 +160,6 @@ 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, 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 @@ -178,8 +177,10 @@ def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0, srr_opt: '') # 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, srr_opt: srr_opt) if srr_set.to_f >= 0.0 && srr_set.to_f <= 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, 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, 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 @@ -837,7 +838,7 @@ def apply_max_srr_nrcan(model:, srr_lim:, srr_opt: '') return false end end - + return true end end From fbb46e91a9cbaa26a47177bf869799265a2aa139 Mon Sep 17 00:00:00 2001 From: brgix Date: Tue, 28 Jan 2025 08:58:16 -0500 Subject: [PATCH 20/24] Minor edits towards merging --- lib/openstudio-standards/btap/bridging.rb | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/openstudio-standards/btap/bridging.rb b/lib/openstudio-standards/btap/bridging.rb index 33ebaeb904..a9edc94816 100644 --- a/lib/openstudio-standards/btap/bridging.rb +++ b/lib/openstudio-standards/btap/bridging.rb @@ -1,5 +1,5 @@ # **************************************************************************** / -# * Copyright (c) 2008-2024, Natural Resources Canada +# * Copyright (c) 2008-2025, Natural Resources Canada # * All rights reserved. # * # * This library is free software; you can redistribute it and/or @@ -55,6 +55,7 @@ module BridgingData # NOTE: This will soon be revised, largely inferred from building structure # selection. # + # # ---- (Basic) Low Performance (LP) assemblies # # ID : (layers) @@ -227,7 +228,7 @@ module BridgingData # respectively. This is expected to change in the future ... # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- # - # Preset BTAP/TBD wall construction parameters (to be revised ... @todo). + # 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 @@ -252,6 +253,12 @@ module BridgingData # A construction sub-variant is identified strictly by its Uo factor: # # e.g. "314" describes a Uo factor of 0.314 W/m2.K. + # + # 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"] = [] @@ -434,6 +441,7 @@ module BridgingData # 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 } @@ -945,7 +953,7 @@ def initialize(model = nil, argh = {}) initial = true complies = false comply = {} # specific to :walls, :floors & :roofs - perform = :lp # Low-performance wall constructions (revise, @todo ...) + 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 @@ -1704,7 +1712,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. @todo. +# insulation for "good" (HP) details. See final TBD tally, @todo. # # NOTE: Overview of current BTAP building/space type construction link, e.g.: # From b68ed01626ae58731e7bf15ebca2d03ff3e53c21 Mon Sep 17 00:00:00 2001 From: brgix Date: Tue, 28 Jan 2025 08:59:48 -0500 Subject: [PATCH 21/24] Minor edits towards merging - redux --- lib/openstudio-standards/btap/bridging.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/openstudio-standards/btap/bridging.rb b/lib/openstudio-standards/btap/bridging.rb index a9edc94816..f1b92475f4 100644 --- a/lib/openstudio-standards/btap/bridging.rb +++ b/lib/openstudio-standards/btap/bridging.rb @@ -228,7 +228,7 @@ module BridgingData # respectively. This is expected to change in the future ... # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- # - # Preset BTAP/TBD wall construction parameters (to be revised @todo). + # 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 @@ -252,7 +252,7 @@ module BridgingData # --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --- # # A construction sub-variant is identified strictly by its Uo factor: # - # e.g. "314" describes a Uo factor of 0.314 W/m2.K. + # e.g. "314" describes a Uo factor of 0.314 W/m2.K # # Uo sub-variants point to empty arrays. These arrays initially held layer # identifiers (integers), for BTAP costing only. These arrays may be From 6cb71506ec55ef20e3be9c50944a3e1c208e1a67 Mon Sep 17 00:00:00 2001 From: brgix Date: Thu, 30 Jan 2025 15:56:13 -0500 Subject: [PATCH 22/24] Samples TBD unit tests --- test/necb/unit_tests/tests/test_NECB_TBD.rb | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/test/necb/unit_tests/tests/test_NECB_TBD.rb b/test/necb/unit_tests/tests/test_NECB_TBD.rb index 6f400a13c8..c89ae1fbb2 100644 --- a/test/necb/unit_tests/tests/test_NECB_TBD.rb +++ b/test/necb/unit_tests/tests/test_NECB_TBD.rb @@ -20,7 +20,7 @@ def test_necb_tbd() #Range of test options. @templates = [ 'NECB2011', - 'NECB2015', + # 'NECB2015', 'NECB2017' ] @@ -28,18 +28,18 @@ def test_necb_tbd() @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' ] From 92e13ef147c7ab3513f8b1a06d12f95c226745ac Mon Sep 17 00:00:00 2001 From: brgix Date: Fri, 31 Jan 2025 07:00:17 -0500 Subject: [PATCH 23/24] Trims down TBD testing --- test/necb/unit_tests/tests/test_NECB_TBD.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/necb/unit_tests/tests/test_NECB_TBD.rb b/test/necb/unit_tests/tests/test_NECB_TBD.rb index c89ae1fbb2..40b428a2ca 100644 --- a/test/necb/unit_tests/tests/test_NECB_TBD.rb +++ b/test/necb/unit_tests/tests/test_NECB_TBD.rb @@ -55,7 +55,7 @@ def test_necb_tbd() # Otherwise, :bad vs :good PSI factor sets refer to costed BTAP details. @options = ['none', 'bad', - 'good', + # 'good', 'uprate'] # BTAP holds discrete performance levels for each e.g. wall construction: From 724aaa693dde8691663ce2915d681aaed8e1b2bc Mon Sep 17 00:00:00 2001 From: brgix Date: Fri, 31 Jan 2025 07:05:57 -0500 Subject: [PATCH 24/24] Further trims down TBD testing --- test/necb/unit_tests/tests/test_NECB_TBD.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/necb/unit_tests/tests/test_NECB_TBD.rb b/test/necb/unit_tests/tests/test_NECB_TBD.rb index c89ae1fbb2..40b428a2ca 100644 --- a/test/necb/unit_tests/tests/test_NECB_TBD.rb +++ b/test/necb/unit_tests/tests/test_NECB_TBD.rb @@ -55,7 +55,7 @@ def test_necb_tbd() # Otherwise, :bad vs :good PSI factor sets refer to costed BTAP details. @options = ['none', 'bad', - 'good', + # 'good', 'uprate'] # BTAP holds discrete performance levels for each e.g. wall construction: