lib/measures/tbd/resources/psi.rb in tbd-3.3.0 vs lib/measures/tbd/resources/psi.rb in tbd-3.4.0

- old
+ new

@@ -1,8 +1,8 @@ # MIT License # -# Copyright (c) 2020-2023 Denis Bourgeois & Dan Macumber +# Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell @@ -154,10 +154,11 @@ @set["poor (BETBG)"] = { rimjoist: 1.000000, # re: BETBG parapet: 0.800000, # re: BETBG roof: 0.800000, # same as parapet + ceiling: 0.000000, # e.g. suspended ceiling tiles fenestration: 0.500000, # re: BETBG door: 0.500000, # inferred, same as (vertical) fenestration skylight: 0.500000, # inferred, same as (vertical) fenestration spandrel: 0.155000, # Detail 5.4.4 corner: 0.850000, # re: BETBG @@ -174,10 +175,11 @@ @set["regular (BETBG)"] = { rimjoist: 0.500000, # re: BETBG parapet: 0.450000, # re: BETBG roof: 0.450000, # same as parapet + ceiling: 0.000000, # e.g. suspended ceiling tiles fenestration: 0.350000, # re: BETBG door: 0.350000, # inferred, same as (vertical) fenestration skylight: 0.350000, # inferred, same as (vertical) fenestration spandrel: 0.155000, # Detail 5.4.4 corner: 0.450000, # re: BETBG @@ -194,10 +196,11 @@ @set["efficient (BETBG)"] = { rimjoist: 0.200000, # re: BETBG parapet: 0.200000, # re: BETBG roof: 0.200000, # same as parapet + ceiling: 0.000000, # e.g. suspended ceiling tiles fenestration: 0.199999, # re: BETBG door: 0.199999, # inferred, same as (vertical) fenestration skylight: 0.199999, # inferred, same as (vertical) fenestration spandrel: 0.155000, # Detail 5.4.4 corner: 0.200000, # re: BETBG @@ -214,10 +217,11 @@ @set["spandrel (BETBG)"] = { rimjoist: 0.615000, # Detail 1.2.1 parapet: 1.000000, # Detail 1.3.2 roof: 1.000000, # same as parapet + ceiling: 0.000000, # e.g. suspended ceiling tiles fenestration: 0.000000, # inferred, generally part of clear-field RSi door: 0.000000, # inferred, generally part of clear-field RSi skylight: 0.350000, # same as "regular (BETBG)" spandrel: 0.155000, # Detail 5.4.4 corner: 0.425000, # Detail 1.4.1 @@ -234,10 +238,11 @@ @set["spandrel HP (BETBG)"] = { rimjoist: 0.170000, # Detail 1.2.7 parapet: 0.660000, # Detail 1.3.2 roof: 0.660000, # same as parapet + ceiling: 0.000000, # e.g. suspended ceiling tiles fenestration: 0.000000, # inferred, generally part of clear-field RSi door: 0.000000, # inferred, generally part of clear-field RSi skylight: 0.350000, # same as "regular (BETBG)" spandrel: 0.155000, # Detail 5.4.4 corner: 0.200000, # Detail 1.4.2 @@ -254,10 +259,11 @@ @set["code (Quebec)"] = { rimjoist: 0.300000, # re I1 parapet: 0.325000, # re I1 roof: 0.325000, # same as parapet + ceiling: 0.000000, # e.g. suspended ceiling tiles fenestration: 0.200000, # re I1 door: 0.200000, # re I1 skylight: 0.200000, # re I1 spandrel: 0.155000, # BETBG Detail 5.4.4 (same as uncompliant) corner: 0.300000, # inferred from description, not explicitely set @@ -274,10 +280,11 @@ @set["uncompliant (Quebec)"] = { rimjoist: 0.850000, # re I1 parapet: 0.800000, # re I1 roof: 0.800000, # same as parapet + ceiling: 0.000000, # e.g. suspended ceiling tiles fenestration: 0.500000, # re I1 door: 0.500000, # re I1 skylight: 0.500000, # re I1 spandrel: 0.155000, # BETBG Detail 5.4.4 (same as compliant) corner: 0.850000, # inferred from description, not explicitely set @@ -294,10 +301,11 @@ @set["90.1.22|steel.m|default"] = { rimjoist: 0.307000, # "intermediate floor to wall intersection" parapet: 0.260000, # "parapet" edge roof: 0.020000, # (non-parapet) "roof" edge + ceiling: 0.000000, # e.g. suspended ceiling tiles fenestration: 0.194000, # "wall to vertical fenestration intersection" door: 0.000000, # (unspecified, defaults to 0) skylight: 0.000000, # (unspecified, defaults to 0) spandrel: 0.000001, # (unspecified, defaults to 0) corner: 0.000002, # (unspecified, defaults to 0) @@ -314,10 +322,11 @@ @set["90.1.22|steel.m|unmitigated"] = { rimjoist: 0.842000, # "intermediate floor to wall intersection" parapet: 0.500000, # "parapet" edge roof: 0.650000, # (non-parapet) "roof" edge + ceiling: 0.000000, # e.g. suspended ceiling tiles fenestration: 0.505000, # "wall to vertical fenestration intersection" door: 0.000000, # (unspecified, defaults to 0) skylight: 0.000000, # (unspecified, defaults to 0) spandrel: 0.000001, # (unspecified, defaults to 0) corner: 0.000002, # (unspecified, defaults to 0) @@ -334,10 +343,11 @@ @set["90.1.22|mass.ex|default"] = { rimjoist: 0.205000, # "intermediate floor to wall intersection" parapet: 0.217000, # "parapet" edge roof: 0.150000, # (non-parapet) "roof" edge + ceiling: 0.000000, # e.g. suspended ceiling tiles fenestration: 0.226000, # "wall to vertical fenestration intersection" door: 0.000000, # (unspecified, defaults to 0) skylight: 0.000000, # (unspecified, defaults to 0) spandrel: 0.000001, # (unspecified, defaults to 0) corner: 0.000002, # (unspecified, defaults to 0) @@ -354,10 +364,11 @@ @set["90.1.22|mass.ex|unmitigated"] = { rimjoist: 0.824000, # "intermediate floor to wall intersection" parapet: 0.412000, # "parapet" edge roof: 0.750000, # (non-parapet) "roof" edge + ceiling: 0.000000, # e.g. suspended ceiling tiles fenestration: 0.325000, # "wall to vertical fenestration intersection" door: 0.000000, # (unspecified, defaults to 0) skylight: 0.000000, # (unspecified, defaults to 0) spandrel: 0.000001, # (unspecified, defaults to 0) corner: 0.000002, # (unspecified, defaults to 0) @@ -374,10 +385,11 @@ @set["90.1.22|mass.in|default"] = { rimjoist: 0.495000, # "intermediate floor to wall intersection" parapet: 0.393000, # "parapet" edge roof: 0.150000, # (non-parapet) "roof" edge + ceiling: 0.000000, # e.g. suspended ceiling tiles fenestration: 0.143000, # "wall to vertical fenestration intersection" door: 0.000000, # (unspecified, defaults to 0) skylight: 0.000000, # (unspecified, defaults to 0) spandrel: 0.000000, # (unspecified, defaults to 0) corner: 0.000001, # (unspecified, defaults to 0) @@ -394,10 +406,11 @@ @set["90.1.22|mass.in|unmitigated"] = { rimjoist: 0.824000, # "intermediate floor to wall intersection" parapet: 0.884000, # "parapet" edge roof: 0.750000, # (non-parapet) "roof" edge + ceiling: 0.000000, # e.g. suspended ceiling tiles fenestration: 0.543000, # "wall to vertical fenestration intersection" door: 0.000000, # (unspecified, defaults to 0) skylight: 0.000000, # (unspecified, defaults to 0) spandrel: 0.000000, # (unspecified, defaults to 0) corner: 0.000001, # (unspecified, defaults to 0) @@ -414,10 +427,11 @@ @set["90.1.22|wood.fr|default"] = { rimjoist: 0.084000, # "intermediate floor to wall intersection" parapet: 0.056000, # "parapet" edge roof: 0.020000, # (non-parapet) "roof" edge + ceiling: 0.000000, # e.g. suspended ceiling tiles fenestration: 0.171000, # "wall to vertical fenestration intersection" door: 0.000000, # (unspecified, defaults to 0) skylight: 0.000000, # (unspecified, defaults to 0) spandrel: 0.000000, # (unspecified, defaults to 0) corner: 0.000001, # (unspecified, defaults to 0) @@ -434,10 +448,11 @@ @set["90.1.22|wood.fr|unmitigated"] = { rimjoist: 0.582000, # "intermediate floor to wall intersection" parapet: 0.056000, # "parapet" edge roof: 0.150000, # (non-parapet) "roof" edge + ceiling: 0.000000, # e.g. suspended ceiling tiles fenestration: 0.260000, # "wall to vertical fenestration intersection" door: 0.000000, # (unspecified, defaults to 0) skylight: 0.000000, # (unspecified, defaults to 0) spandrel: 0.000000, # (unspecified, defaults to 0) corner: 0.000001, # (unspecified, defaults to 0) @@ -450,24 +465,26 @@ transition: 0.000000 # defaults to 0 }.freeze @set["(non thermal bridging)"] = { - rimjoist: 0.000000, # defaults to 0 - parapet: 0.000000, # defaults to 0 - roof: 0.000000, # defaults to 0 - fenestration: 0.000000, # defaults to 0 - door: 0.000000, # defaults to 0 - skylight: 0.000000, # defaults to 0 - spandrel: 0.000000, # defaults to 0 - corner: 0.000000, # defaults to 0 - balcony: 0.000000, # defaults to 0 - balconysill: 0.000000, # defaults to 0 - party: 0.000000, # defaults to 0 - grade: 0.000000, # defaults to 0 - joint: 0.000000, # defaults to 0 - transition: 0.000000 # defaults to 0 + rimjoist: 0.000000, # defaults to 0 + parapet: 0.000000, # defaults to 0 + roof: 0.000000, # defaults to 0 + ceiling: 0.000000, # defaults to 0 + fenestration: 0.000000, # defaults to 0 + door: 0.000000, # defaults to 0 + skylight: 0.000000, # defaults to 0 + spandrel: 0.000000, # defaults to 0 + corner: 0.000000, # defaults to 0 + balcony: 0.000000, # defaults to 0 + balconysill: 0.000000, # defaults to 0 + balconydoorsill: 0.000000, # defaults to 0 + party: 0.000000, # defaults to 0 + grade: 0.000000, # defaults to 0 + joint: 0.000000, # defaults to 0 + transition: 0.000000 # defaults to 0 }.freeze @set.keys.each { |k| self.gen(k) } end @@ -528,10 +545,13 @@ h[:partyconcave ] = @set[id].key?(:parapetconcave) h[:parapetconvex ] = @set[id].key?(:parapetconvex) h[:roof ] = @set[id].key?(:roof) h[:roofconcave ] = @set[id].key?(:roofconcave) h[:roofconvex ] = @set[id].key?(:roofconvex) + h[:ceiling ] = @set[id].key?(:ceiling) + h[:ceilingconcave ] = @set[id].key?(:ceilingconcave) + h[:ceilingconvex ] = @set[id].key?(:ceilingconvex) h[:grade ] = @set[id].key?(:grade) h[:gradeconcave ] = @set[id].key?(:gradeconcave) h[:gradeconvex ] = @set[id].key?(:gradeconvex) h[:balcony ] = @set[id].key?(:balcony) h[:balconyconcave ] = @set[id].key?(:balconyconcave) @@ -560,10 +580,11 @@ v[:skylightjamb ] = 0; v[:skylightjambconcave ] = 0; v[:skylightjambconvex ] = 0 v[:spandrel ] = 0; v[:spandrelconcave ] = 0; v[:spandrelconvex ] = 0 v[:corner ] = 0; v[:cornerconcave ] = 0; v[:cornerconvex ] = 0 v[:parapet ] = 0; v[:parapetconcave ] = 0; v[:parapetconvex ] = 0 v[:roof ] = 0; v[:roofconcave ] = 0; v[:roofconvex ] = 0 + v[:ceiling ] = 0; v[:ceilingconcave ] = 0; v[:ceilingconvex ] = 0 v[:party ] = 0; v[:partyconcave ] = 0; v[:partyconvex ] = 0 v[:grade ] = 0; v[:gradeconcave ] = 0; v[:gradeconvex ] = 0 v[:balcony ] = 0; v[:balconyconcave ] = 0; v[:balconyconvex ] = 0 v[:balconysill ] = 0; v[:balconysillconcave ] = 0; v[:balconysillconvex ] = 0 v[:balconydoorsill] = 0; v[:balconydoorsillconcave] = 0; v[:balconydoorsillconvex] = 0 @@ -695,10 +716,15 @@ v[:roof ] = @set[id][:roof ] if h[:roof ] v[:roofconcave ] = @set[id][:roof ] if h[:roof ] v[:roofconvex ] = @set[id][:roof ] if h[:roof ] v[:roofconcave ] = @set[id][:roofconcave ] if h[:roofconcave ] v[:roofconvex ] = @set[id][:roofconvex ] if h[:roofconvex ] + v[:ceiling ] = @set[id][:ceiling ] if h[:ceiling ] + v[:ceilingconcave ] = @set[id][:ceiling ] if h[:ceiling ] + v[:ceilingconvex ] = @set[id][:ceiling ] if h[:ceiling ] + v[:ceilingconcave ] = @set[id][:ceilingconcave ] if h[:ceilingconcave ] + v[:ceilingconvex ] = @set[id][:ceilingconvex ] if h[:ceilingconvex ] v[:party ] = @set[id][:party ] if h[:party ] v[:partyconcave ] = @set[id][:party ] if h[:party ] v[:partyconvex ] = @set[id][:party ] if h[:party ] v[:partyconcave ] = @set[id][:partyconcave ] if h[:partyconcave ] v[:partyconvex ] = @set[id][:partyconvex ] if h[:partyconvex ] @@ -762,10 +788,11 @@ max = [v[:parapetconcave], v[:parapetconvex]].max v[:parapet] = max unless @has[:parapet] max = [v[:roofconcave], v[:roofconvex]].max v[:roof] = max unless @has[:roof] + @val[id] = v true end @@ -781,10 +808,13 @@ # @option set [#to_f] :parapetconcave basilaire variant # @option set [#to_f] :parapetconvex typical # @option set [#to_f] :roof roof-to-wall intersection # @option set [#to_f] :roofconcave basilaire variant # @option set [#to_f] :roofconvex typical + # @option set [#to_f] :ceiling intermediate (uninsulated) ceiling perimeter + # @option set [#to_f] :ceilingconcave cantilever variant + # @option set [#to_f] :ceilingconvex colonnade variant # @option set [#to_f] :fenestration head/sill/jamb interface # @option set [#to_f] :head (fenestrated) header interface # @option set [#to_f] :headconcave (fenestrated) basilaire variant # @option set [#to_f] :headconvex (fenestrated) parapet variant # @option set [#to_f] :sill (fenestrated) threshold/sill interface @@ -868,10 +898,13 @@ s[:parapetconcave ] = set[:parapetconcave ] if set.key?(:parapetconcave) s[:parapetconvex ] = set[:parapetconvex ] if set.key?(:parapetconvex) s[:roof ] = set[:roof ] if set.key?(:roof) s[:roofconcave ] = set[:roofconcave ] if set.key?(:roofconcave) s[:roofconvex ] = set[:roofconvex ] if set.key?(:roofconvex) + s[:ceiling ] = set[:ceiling ] if set.key?(:ceiling) + s[:ceilingconcave ] = set[:ceilingconcave ] if set.key?(:ceilingconcave) + s[:ceilingconvex ] = set[:ceilingconvex ] if set.key?(:ceilingconvex) s[:fenestration ] = set[:fenestration ] if set.key?(:fenestration) s[:head ] = set[:head ] if set.key?(:head) s[:headconcave ] = set[:headconcave ] if set.key?(:headconcave) s[:headconvex ] = set[:headconvex ] if set.key?(:headconvex) s[:sill ] = set[:sill ] if set.key?(:sill) @@ -924,10 +957,11 @@ s[:joint ] = set[:joint ] if set.key?(:joint) s[:transition ] = set[:transition ] if set.key?(:transition) s[:joint ] = 0.000 unless set.key?(:joint) s[:transition ] = 0.000 unless set.key?(:transition) + s[:ceiling ] = 0.000 unless set.key?(:ceiling) @set[id] = s self.gen(id) true @@ -939,17 +973,17 @@ # Hash of PSI-factors (values) for any admissible PSI type (keys). # PSI-factors default to 0 W/K per linear meter if missing from set. # # @param id [#to_s] PSI set identifier # @example intermediate floor slab intersection - # shorthands("A901") + # shorthands("90.1.22|steel.m|default") # # @return [Hash] has: Hash (Bool), val: Hash (PSI factors) see logs if empty def shorthands(id = "") mth = "TBD::#{__callee__}" sh = { has: {}, val: {} } - id = trim(id) + id = trim(id) return mismatch("set ID", id, String, mth, ERR, a) if id.empty? return hashkey(id, @set , id, mth, ERR, sh) unless @set.key?(id) return hashkey(id, @has , id, mth, ERR, sh) unless @has.key?(id) return hashkey(id, @val , id, mth, ERR, sh) unless @val.key?(id) @@ -967,11 +1001,11 @@ # @return [Bool] whether provided PSI set is held in memory and is complete # @return [false] if invalid input (see logs) def complete?(id = "") mth = "TBD::#{__callee__}" a = false - id = trim(id) + id = trim(id) return mismatch("set ID", id, String, mth, ERR, a) if id.empty? return hashkey(id, @set , id, mth, ERR, a) unless @set.key?(id) return hashkey(id, @has , id, mth, ERR, a) unless @has.key?(id) return hashkey(id, @val , id, mth, ERR, a) unless @val.key?(id) @@ -1851,30 +1885,34 @@ next unless tbd[:surfaces].key?(id) next unless deratables.include?(id) # Evaluate current set content before processing a new linked surface. is = {} - is[:head ] = set.keys.to_s.include?("head") - is[:sill ] = set.keys.to_s.include?("sill") - is[:jamb ] = set.keys.to_s.include?("jamb") is[:doorhead ] = set.keys.to_s.include?("doorhead") is[:doorsill ] = set.keys.to_s.include?("doorsill") is[:doorjamb ] = set.keys.to_s.include?("doorjamb") is[:skylighthead ] = set.keys.to_s.include?("skylighthead") is[:skylightsill ] = set.keys.to_s.include?("skylightsill") is[:skylightjamb ] = set.keys.to_s.include?("skylightjamb") is[:spandrel ] = set.keys.to_s.include?("spandrel") is[:corner ] = set.keys.to_s.include?("corner") is[:parapet ] = set.keys.to_s.include?("parapet") is[:roof ] = set.keys.to_s.include?("roof") + is[:ceiling ] = set.keys.to_s.include?("ceiling") is[:party ] = set.keys.to_s.include?("party") is[:grade ] = set.keys.to_s.include?("grade") is[:balcony ] = set.keys.to_s.include?("balcony") is[:balconysill ] = set.keys.to_s.include?("balconysill") is[:balconydoorsill ] = set.keys.to_s.include?("balconydoorsill") is[:rimjoist ] = set.keys.to_s.include?("rimjoist") + if is.empty? + is[:head] = set.keys.to_s.include?("head") + is[:sill] = set.keys.to_s.include?("sill") + is[:jamb] = set.keys.to_s.include?("jamb") + end + # Label edge as ... # :head, :sill, :jamb (vertical fenestration) # :doorhead, :doorsill, :doorjamb (opaque door) # :skylighthead, :skylightsill, :skylightjamb (all other cases) # @@ -2090,10 +2128,45 @@ set[:cornerconcave] = shorts[:val][:cornerconcave] if concave set[:cornerconvex ] = shorts[:val][:cornerconvex ] if convex is[:corner ] = true end + # Label edge as :ceiling if linked to: + # +1 deratable surfaces + # 1x underatable CONDITIONED floor linked to an unoccupied space + # 1x adjacent CONDITIONED ceiling linked to an occupied space + edge[:surfaces].keys.each do |i| + break if is[:ceiling] + break unless deratables.size > 0 + break if floors.key?(id) + next if i == id + next unless floors.key?(i) + next if floors[i][:ground ] + next unless floors[i][:conditioned] + next if floors[i][:occupied ] + + ceiling = floors[i][:boundary] + next unless ceilings.key?(ceiling) + next unless ceilings[ceiling][:conditioned] + next unless ceilings[ceiling][:occupied ] + + other = deratables.first unless deratables.first == id + other = deratables.last unless deratables.last == id + other = id if deratables.size == 1 + + s1 = edge[:surfaces][id] + s2 = edge[:surfaces][other] + concave = concave?(s1, s2) + convex = convex?(s1, s2) + flat = !concave && !convex + + set[:ceiling ] = shorts[:val][:ceiling ] if flat + set[:ceilingconcave] = shorts[:val][:ceilingconcave] if concave + set[:ceilingconvex ] = shorts[:val][:ceilingconvex ] if convex + is[:ceiling ] = true + end + # Label edge as :parapet/:roof if linked to: # 1x deratable wall # 1x deratable ceiling edge[:surfaces].keys.each do |i| break if is[:parapet] @@ -2170,26 +2243,27 @@ set[:gradeconcave] = shorts[:val][:gradeconcave] if concave set[:gradeconvex ] = shorts[:val][:gradeconvex ] if convex is[:grade ] = true end - # Label edge as :rimjoist, :balcony, :balconysill or :balconydoorsill if linked to: + # Label edge as :rimjoist, :balcony, :balconysill or :balconydoorsill, + # if linked to: # 1x deratable surface # 1x CONDITIONED floor # 1x shade (optional) # 1x subsurface (optional) - # - # Despite referring to 'sill' or 'doorsill', a 'balconysill' or - # 'balconydoorsill' edge may instead link (rarer) cases of balcony and a - # fenestratio/door head. ASHRAE 90.1 2022 does not make the distinction - # between sill vs head when intermediatre floor, balcony and vertical - # fenestration meet. 'Sills' are simply the most common occurrence. balcony = false balconysill = false # vertical fenestration balconydoorsill = false # opaque door + # Despite referring to 'sill' or 'doorsill', a 'balconysill' or + # 'balconydoorsill' edge may instead link (rarer) cases of balcony and a + # fenestration/door head. ASHRAE 90.1 2022 does not make the distinction + # between sill vs head when intermediate floor, balcony and vertical + # fenestration meet. 'Sills' are simply the most common occurrence. edge[:surfaces].keys.each do |i| + break if is[:ceiling] break if balcony next if i == id balcony = shades.key?(i) end @@ -2257,18 +2331,20 @@ balconydoorsill = true end end edge[:surfaces].keys.each do |i| - break if is[:rimjoist ] || is[:balcony ] || - is[:balconysill] || is[:balconydoorsill] + break if is[:ceiling ] + break if is[:rimjoist ] + break if is[:balcony ] + break if is[:balconysill ] + break if is[:balconydoorsill] break unless deratables.size > 0 break if floors.key?(id) next if i == id next unless floors.key?(i) - next unless floors[i].key?(:conditioned) - next unless floors[i][:conditioned] next if floors[i][:ground ] + next unless floors[i][:conditioned] other = deratables.first unless deratables.first == id other = deratables.last unless deratables.last == id other = id if deratables.size == 1