lib/measures/tbd/resources/ua.rb in tbd-3.2.3 vs lib/measures/tbd/resources/ua.rb in tbd-3.3.0
- old
+ new
@@ -24,71 +24,69 @@
##
# Calculates construction Uo (including surface film resistances) to meet Ut.
#
# @param model [OpenStudio::Model::Model] a model
# @param lc [OpenStudio::Model::LayeredConstruction] a layered construction
- # @param id [String] layered construction identifier
- # @param heatloss [Double] heat loss from major thermal bridging [W/K]
- # @param film [Double] target surface film resistance [m2.K/W]
- # @param ut [Double] target overall Ut for lc [W/m2.K]
+ # @param id [#to_s] layered construction identifier
+ # @param hloss [Numeric] heat loss from major thermal bridging, in W/K
+ # @param film [Numeric] target surface film resistance, in m2•K/W
+ # @param ut [Numeric] target overall Ut for lc, in W/m2•K
#
- # @return [Hash] uo: lc Uo [W/m2.K] to meet Ut, m: uprated lc layer
- # @return [Hash] uo: NilClass, m: NilClass (if invalid input)
+ # @return [Hash] uo: lc Uo [W/m2•K] to meet Ut, m: uprated lc layer
+ # @return [Hash] uo: (nil), m: (nil) if invalid input (see logs)
def uo(model = nil, lc = nil, id = "", hloss = 0.0, film = 0.0, ut = 0.0)
mth = "TBD::#{__callee__}"
res = { uo: nil, m: nil }
cl1 = OpenStudio::Model::Model
cl2 = OpenStudio::Model::LayeredConstruction
cl3 = Numeric
cl4 = String
-
+ id = trim(id)
return mismatch("model", model, cl1, mth, DBG, res) unless model.is_a?(cl1)
- return mismatch("id" , id, cl4, mth, DBG, res) unless id.is_a?(cl4)
+ return mismatch("id" , id, cl4, mth, DBG, res) if id.empty?
return mismatch("lc" , lc, cl2, mth, DBG, res) unless lc.is_a?(cl2)
return mismatch("hloss", hloss, cl3, mth, DBG, res) unless hloss.is_a?(cl3)
return mismatch("film" , film, cl3, mth, DBG, res) unless film.is_a?(cl3)
return mismatch("Ut" , ut, cl3, mth, DBG, res) unless ut.is_a?(cl3)
- loss = 0.0 # residual heatloss (not assigned) [W/K]
+ loss = 0.0 # residual heatloss (not assigned) [W/K]
area = lc.getNetArea
lyr = insulatingLayer(lc)
lyr[:index] = nil unless lyr[:index].is_a?(Numeric)
lyr[:index] = nil unless lyr[:index] >= 0
lyr[:index] = nil unless lyr[:index] < lc.layers.size
+ return invalid("#{id} layer index", mth, 3, ERR, res) unless lyr[:index]
+ return zero("#{id}: heatloss" , mth, WRN, res) unless hloss > TOL
+ return zero("#{id}: films" , mth, WRN, res) unless film > TOL
+ return zero("#{id}: Ut" , mth, WRN, res) unless ut > TOL
+ return invalid("#{id}: Ut" , mth, 6, WRN, res) unless ut < 5.678
+ return zero("#{id}: net area (m2)", mth, ERR, res) unless area > TOL
- return invalid("'#{id}' layer index", mth, 0, ERR, res) unless lyr[:index]
- return zero("'#{id}': heatloss" , mth, WRN, res) unless hloss > TOL
- return zero("'#{id}': films" , mth, WRN, res) unless film > TOL
- return zero("'#{id}': Ut" , mth, WRN, res) unless ut > TOL
- return invalid("'#{id}': Ut" , mth, 0, WRN, res) unless ut < 5.678
- return zero("'#{id}': net area (m2)", mth, ERR, res) unless area > TOL
-
# First, calculate initial layer RSi to initially meet Ut target.
- rt = 1 / ut # target construction Rt
- ro = rsi(lc, film) # current construction Ro
- new_r = lyr[:r] + (rt - ro) # new, un-derated layer RSi
+ rt = 1 / ut # target construction Rt
+ ro = rsi(lc, film) # current construction Ro
+ new_r = lyr[:r] + (rt - ro) # new, un-derated layer RSi
new_u = 1 / new_r
# Then, uprate (if possible) to counter expected thermal bridging effects.
- u_psi = hloss / area # from psi & khi
- new_u -= u_psi # uprated layer USi to counter psi & khi
- new_r = 1 / new_u # uprated layer RSi to counter psi & khi
+ u_psi = hloss / area # from psi+khi
+ new_u -= u_psi # uprated layer USi to counter psi+khi
+ new_r = 1 / new_u # uprated layer RSi to counter psi+khi
+ return zero("#{id}: new Rsi", mth, ERR, res) unless new_r > 0.001
- return zero("'#{id}': new Rsi", mth, ERR, res) unless new_r > 0.001
-
if lyr[:type] == :massless
m = lc.getLayer(lyr[:index]).to_MasslessOpaqueMaterial
- return invalid("'#{id}' massless layer?", mth, 0, DBG, res) if m.empty?
+ return invalid("#{id} massless layer?", mth, 0, DBG, res) if m.empty?
m = m.get.clone(model).to_MasslessOpaqueMaterial.get
m.setName("#{id} uprated")
- new_r = 0.001 unless new_r > 0.001
- loss = (new_u - 1 / new_r) * area unless new_r > 0.001
+ new_r = 0.001 unless new_r > 0.001
+ loss = (new_u - 1 / new_r) * area unless new_r > 0.001
m.setThermalResistance(new_r)
else # type == :standard
m = lc.getLayer(lyr[:index]).to_StandardOpaqueMaterial
- return invalid("'#{id}' standard layer?", mth, 0, DBG, res) if m.empty?
+ return invalid("#{id} standard layer?", mth, 0, DBG, res) if m.empty?
m = m.get.clone(model).to_StandardOpaqueMaterial.get
m.setName("#{id} uprated")
k = m.thermalConductivity
@@ -96,97 +94,123 @@
d = new_r * k
unless d > 0.003
d = 0.003
k = d / new_r
- k = 3.0 unless k < 3.0
- loss = (new_u - k / d) * area unless k < 3.0
+ k = 3.0 unless k < 3.0
+ loss = (new_u - k / d) * area unless k < 3.0
end
- else # new_r < 0.001 m2.K/W
+ else # new_r < 0.001 m2•K/W
d = 0.001 * k
- d = 0.003 unless d > 0.003
- k = d / 0.001 unless d > 0.003
+ d = 0.003 unless d > 0.003
+ k = d / 0.001 unless d > 0.003
loss = (new_u - k / d) * area
end
- ok = m.setThickness(d)
- return invalid("Can't uprate '#{id}': > 3m", mth, 0, ERR, res) unless ok
-
- m.setThermalConductivity(k) if ok
+ if m.setThickness(d)
+ m.setThermalConductivity(k)
+ else
+ return invalid("Can't uprate #{id}: #{d} > 3m", mth, 0, ERR, res)
+ end
end
- return invalid("", mth, 0, ERR, res) unless m
+ return invalid("Can't ID insulating layer", mth, 0, ERR, res) unless m
lc.setLayer(lyr[:index], m)
uo = 1 / rsi(lc, film)
if loss > TOL
h_loss = format "%.3f", loss
- return invalid("Can't assign #{h_loss} W/K to '#{id}'", mth, 0, ERR, res)
+ return invalid("Can't assign #{h_loss} W/K to #{id}", mth, 0, ERR, res)
end
res[:uo] = uo
res[:m ] = m
res
end
##
- # Uprate insulation layer of construction, based on user-selected Ut (argh).
+ # Uprates insulation layer of construction, based on user-selected Ut (argh).
#
# @param model [OpenStudio::Model::Model] a model
- # @param s [Hash] preprocessed collection of TBD surfaces
- # @param argh [Hash] TBD arguments
+ # @param [Hash] s preprocessed collection of TBD surfaces
+ # @option s [:wall, :ceiling, :floor] :type surface type
+ # @option s [Bool] :deratable whether surface can be thermally bridged
+ # @option s [OpenStudio::LayeredConstruction] :construction construction
+ # @option s [#to_i] :index deratable construction layer index
+ # @option s [:massless, :standard] :ltype indexed layer type
+ # @option s [#to_f] :filmRSI air film resistances (optional)
+ # @option s [#to_f] :r thermal resistance (RSI) of indexed layer
+ # @param [Hash] argh TBD arguments
+ # @option argh [Bool] :uprate_walls (false) whether to uprate walls
+ # @option argh [Bool] :uprate_roofs (false) whether to uprate roofs
+ # @option argh [Bool] :uprate_floors (false) whether to uprate floors
+ # @option argh [#to_f] :wall_ut (5.678) uprated wall Usi-factor target
+ # @option argh [#to_f] :roof_ut (5.678) uprated roof Usi-factor target
+ # @option argh [#to_f] :floor_ut (5.678) uprated floor Usi-factor target
+ # @option argh [#to_s] :wall_option ("") construction to uprate (or "all")
+ # @option argh [#to_s] :roof_option ("") construction to uprate (or "all")
+ # @option argh [#to_s] :floor_option ("") construction to uprate (or "all")
#
- # @return [Bool] true if successfully uprated
+ # @return [Bool] whether successfully uprated
+ # @return [false] if invalid input (see logs)
def uprate(model = nil, s = {}, argh = {})
- mth = "TBD::#{__callee__}"
- cl1 = OpenStudio::Model::Model
- cl2 = Hash
- cl3 = OpenStudio::Model::LayeredConstruction
- a = false
-
+ mth = "TBD::#{__callee__}"
+ cl1 = OpenStudio::Model::Model
+ cl2 = Hash
+ cl3 = OpenStudio::Model::LayeredConstruction
+ tout = []
+ tout << "all wall constructions"
+ tout << "all roof constructions"
+ tout << "all floor constructions"
+ a = false
+ groups = { wall: {}, roof: {}, floor: {} }
return mismatch("model" , model, cl1, mth, DBG, a) unless model.is_a?(cl1)
return mismatch("surfaces", s, cl2, mth, DBG, a) unless s.is_a?(cl2)
return mismatch("argh" , model, cl1, mth, DBG, a) unless argh.is_a?(cl2)
- argh[:uprate_walls ] = false unless argh.key?(:uprate_walls )
- argh[:uprate_roofs ] = false unless argh.key?(:uprate_roofs )
+ argh[:uprate_walls ] = false unless argh.key?(:uprate_walls)
+ argh[:uprate_roofs ] = false unless argh.key?(:uprate_roofs)
argh[:uprate_floors] = false unless argh.key?(:uprate_floors)
- argh[:wall_ut ] = 5.678 unless argh.key?(:wall_ut )
- argh[:roof_ut ] = 5.678 unless argh.key?(:roof_ut )
- argh[:floor_ut ] = 5.678 unless argh.key?(:floor_ut )
- argh[:wall_option ] = "" unless argh.key?(:wall_option )
- argh[:roof_option ] = "" unless argh.key?(:roof_option )
- argh[:floor_option ] = "" unless argh.key?(:floor_option )
+ argh[:wall_ut ] = 5.678 unless argh.key?(:wall_ut)
+ argh[:roof_ut ] = 5.678 unless argh.key?(:roof_ut)
+ argh[:floor_ut ] = 5.678 unless argh.key?(:floor_ut)
+ argh[:wall_option ] = "" unless argh.key?(:wall_option)
+ argh[:roof_option ] = "" unless argh.key?(:roof_option)
+ argh[:floor_option ] = "" unless argh.key?(:floor_option)
- groups = { wall: {}, roof: {}, floor: {} }
+ argh[:wall_option ] = trim(argh[:wall_option ])
+ argh[:roof_option ] = trim(argh[:roof_option ])
+ argh[:floor_option ] = trim(argh[:floor_option])
+
groups[:wall ][:up] = argh[:uprate_walls ]
groups[:roof ][:up] = argh[:uprate_roofs ]
groups[:floor][:up] = argh[:uprate_floors]
groups[:wall ][:ut] = argh[:wall_ut ]
groups[:roof ][:ut] = argh[:roof_ut ]
groups[:floor][:ut] = argh[:floor_ut ]
- groups[:wall ][:op] = argh[:wall_option ]
- groups[:roof ][:op] = argh[:roof_option ]
- groups[:floor][:op] = argh[:floor_option ]
+ groups[:wall ][:op] = trim(argh[:wall_option ])
+ groups[:roof ][:op] = trim(argh[:roof_option ])
+ groups[:floor][:op] = trim(argh[:floor_option ])
+
groups.each do |type, g|
next unless g[:up]
next unless g[:ut].is_a?(Numeric)
next unless g[:ut] < 5.678
+ next if g[:ut] < 0
typ = type
- typ = :ceiling if typ == :roof # fix in future revision. TO-DO.
+ typ = :ceiling if typ == :roof
coll = {}
area = 0
film = 100000000000000
lc = nil
id = ""
- all = g[:op].downcase == "all wall constructions" ||
- g[:op].downcase == "all roof constructions" ||
- g[:op].downcase == "all floor constructions"
+ op = g[:op].downcase
+ all = tout.include?(op)
if g[:op].empty?
log(ERR, "Construction (#{type}) to uprate? (#{mth})")
elsif all
s.each do |nom, surface|
@@ -215,12 +239,12 @@
lc = c
area = aire
id = i
end
- coll[i] = { area: aire, lc: c, s: {} } unless coll.key?(i)
- coll[i][:s][nom] = { a: surface[:net] } unless coll[i][:s].key?(nom)
+ coll[i] = { area: aire, lc: c, s: {} } unless coll.key?(i)
+ coll[i][:s][nom] = { a: surface[:net] } unless coll[i][:s].key?(nom)
end
else
id = g[:op]
lc = model.getConstructionByName(id)
log(ERR, "Construction '#{id}'? (#{mth})") if lc.empty?
@@ -265,12 +289,12 @@
lyr = insulatingLayer(lc)
lyr[:index] = nil unless lyr[:index].is_a?(Numeric)
lyr[:index] = nil unless lyr[:index] >= 0
lyr[:index] = nil unless lyr[:index] < lc.layers.size
- log(ERR, "Insulation index for '#{id}'? (#{mth})") unless lyr[:index]
- next unless lyr[:index]
+ log(ERR, "Insulation index for '#{id}'? (#{mth})") unless lyr[:index]
+ next unless lyr[:index]
# Ensure lc is exclusively linked to deratable surfaces of right type.
# If not, assign new lc clone to non-targeted surfaces.
s.each do |nom, surface|
next unless surface.key?(:type )
@@ -285,11 +309,11 @@
ok = false unless coll.key?(id)
ok = false unless coll[id][:s].key?(nom)
unless ok
log(WRN, "Cloning '#{nom}' construction - not '#{id}' (#{mth})")
- sss = model.getSurfaceByName(nom)
+ sss = model.getSurfaceByName(nom)
next if sss.empty?
sss = sss.get
cloned = lc.clone(model).to_LayeredConstruction.get
cloned.setName("#{nom} - cloned")
@@ -297,33 +321,33 @@
surface[:construction] = cloned
coll[id][:s].delete(nom)
end
end
- hloss = 0 # sum of applicable psi & khi effects [W/K]
+ hloss = 0 # sum of applicable psi+khi-related losses [W/K]
- # Tally applicable psi + khi losses. Possible construction reassignment.
+ # Tally applicable psi+khi losses. Possible construction reassignment.
coll.each do |i, col|
col[:s].keys.each do |nom|
next unless s.key?(nom)
next unless s[nom].key?(:construction)
- next unless s[nom].key?(:index )
- next unless s[nom].key?(:ltype )
- next unless s[nom].key?(:r )
+ next unless s[nom].key?(:index)
+ next unless s[nom].key?(:ltype)
+ next unless s[nom].key?(:r)
- # Tally applicable psi + khi.
+ # Tally applicable psi+khi.
hloss += s[nom][:heatloss ] if s[nom].key?(:heatloss)
next if s[nom][:construction] == lc
# Reassign construction unless referencing lc.
sss = model.getSurfaceByName(nom)
next if sss.empty?
sss = sss.get
if sss.isConstructionDefaulted
- set = defaultConstructionSet(model, sss) # building? story?
+ set = defaultConstructionSet(sss) # building? story?
constructions = set.defaultExteriorSurfaceConstructions
unless constructions.empty?
constructions = constructions.get
constructions.setWallConstruction(lc) if typ == :wall
@@ -332,14 +356,14 @@
end
else
sss.setConstruction(lc)
end
- s[nom][:construction] = lc # reset TBD attributes
+ s[nom][:construction] = lc # reset TBD attributes
s[nom][:index ] = lyr[:index]
s[nom][:ltype ] = lyr[:type ]
- s[nom][:r ] = lyr[:r ] # temporary
+ s[nom][:r ] = lyr[:r ] # temporary
end
end
# Merge to ensure a single entry for coll Hash.
coll.each do |i, col|
@@ -349,19 +373,24 @@
coll[id][:s][nom] = sss unless coll[id][:s].key?(nom)
end
end
coll.delete_if { |i, _| i != id }
- log(DBG, "Collection == 1? for '#{id}' (#{mth})") unless coll.size == 1
- next unless coll.size == 1
- area = lc.getNetArea
- coll[id][:area] = area
+ unless coll.size == 1
+ log(DBG, "Collection == 1? for '#{id}' (#{mth})")
+ next
+ end
+
+ coll[id][:area] = lc.getNetArea
res = uo(model, lc, id, hloss, film, g[:ut])
- log(ERR, "Unable to uprate '#{id}' (#{mth})") unless res[:uo] && res[:m]
- next unless res[:uo] && res[:m]
+ unless res[:uo] && res[:m]
+ log(ERR, "Unable to uprate '#{id}' (#{mth})")
+ next
+ end
+
lyr = insulatingLayer(lc)
# Loop through coll :s, and reset :r - likely modified by uo().
coll.values.first[:s].keys.each do |nom|
next unless s.key?(nom)
@@ -369,11 +398,11 @@
next unless s[nom].key?(:ltype)
next unless s[nom].key?(:r )
next unless s[nom][:index] == lyr[:index]
next unless s[nom][:ltype] == lyr[:type ]
- s[nom][:r] = lyr[:r] # uprated insulating RSi factor, before derating
+ s[nom][:r] = lyr[:r] # uprated insulating RSi factor, before derating
end
argh[:wall_uo ] = res[:uo] if typ == :wall
argh[:roof_uo ] = res[:uo] if typ == :ceiling
argh[:floor_uo] = res[:uo] if typ == :floor
@@ -385,185 +414,213 @@
true
end
##
- # Set reference values for points, edges & surfaces (& subsurfaces) to
+ # Sets reference values for points, edges & surfaces (& subsurfaces) to
# compute Quebec energy code (Section 3.3) UA' comparison (2021).
#
- # @param s [Hash] preprocessed collection of TBD surfaces
+ # @param [Hash] s TBD surfaces (keys: Openstudio surface names)
+ # @option s [Bool] :deratable whether surface is deratable, s[][:deratable]
+ # @option s [:wall, :ceiling, :floor] :type TBD surface type
+ # @option s [#to_f] :heating applicable heating setpoint temperature in °C
+ # @option s [#to_f] :cooling applicable cooling setpoint temperature in °C
+ # @option s [Hash] :windows TBD surface-specific windows e.g. s[][:windows]
+ # @option s [Hash] :doors TBD surface-specific doors
+ # @option s [Hash] :skylights TBD surface-specific skylights
+ # @option s [Hash] :pts point thermal bridges, e.g. s[][:pts] see KHI class
+ # @option s [Hash] :edges TBD edges (keys: Topolys edge identifiers)
# @param sets [TBD::PSI] a TBD model's PSI sets
- # @param spts [Bool] true if OpenStudio model has valid setpoints
+ # @param spts [Bool] whether OpenStudio model holds heating/cooling setpoints
#
- # @return [Bool] true if successful in generating UA' reference values
+ # @return [Bool] whether successful in generating UA' reference values
+ # @return [false] if invalid inputs (see logs)
def qc33(s = {}, sets = nil, spts = true)
mth = "TBD::#{__callee__}"
cl1 = Hash
cl2 = TBD::PSI
+ return mismatch("surfaces", s, cl1, mth, DBG, false) unless s.is_a?(cl1)
+ return mismatch("sets", sets, cl1, mth, DBG, false) unless sets.is_a?(cl2)
- return mismatch("surfaces", s, cl1, mth, DBG, false) unless s.is_a?(cl1)
- return mismatch("sets", sets, cl1, mth, DBG, false) unless sets.is_a?(cl2)
-
shorts = sets.shorthands("code (Quebec)")
- empty = shorts[:has].empty? || shorts[:val].empty?
- log(DBG, "Missing QC PSI set for 3.3 UA' tradeoff (#{mth})") if empty
- return false if empty
+ empty = shorts[:has].empty? || shorts[:val].empty?
+ log(DBG, "Missing QC PSI set for 3.3 UA' tradeoff (#{mth})") if empty
+ return false if empty
- ok = spts == true || spts == false
- log(DBG, "'setpoints' must be true/false for 3.3 UA' tradeoff") unless ok
- return false unless ok
+ ok = [true, false].include?(spts)
+ log(DBG, "setpoints must be true or false for 3.3 UA' tradeoff") unless ok
+ return false unless ok
s.each do |id, surface|
next unless surface.key?(:deratable)
next unless surface[:deratable]
next unless surface.key?(:type)
- heating = -50 if spts
- cooling = 50 if spts
- heating = 21 unless spts
- cooling = 24 unless spts
+ heating = -50 if spts
+ cooling = 50 if spts
+ heating = 21 unless spts
+ cooling = 24 unless spts
heating = surface[:heating] if surface.key?(:heating)
cooling = surface[:cooling] if surface.key?(:cooling)
# Start with surface U-factors.
ref = 1 / 5.46
ref = 1 / 3.60 if surface[:type] == :wall
# Adjust for lower heating setpoint (assumes -25°C design conditions).
- ref *= 43 / (heating + 25) if heating < 18 && cooling > 40
- surface[:ref] = ref # ... and store
+ ref *= 43 / (heating + 25) if heating < 18 && cooling > 40
- if surface.key?(:skylights) # loop through subsurfaces
+ surface[:ref] = ref
+
+ if surface.key?(:skylights) # loop through subsurfaces
ref = 2.85
- ref *= 43 / (heating + 25) if heating < 18 && cooling > 40
+ ref *= 43 / (heating + 25) if heating < 18 && cooling > 40
+
surface[:skylights].values.map { |skylight| skylight[:ref] = ref }
end
if surface.key?(:windows)
ref = 2.0
- ref *= 43 / (heating + 25) if heating < 18 && cooling > 40
+ ref *= 43 / (heating + 25) if heating < 18 && cooling > 40
+
surface[:windows].values.map { |window| window[:ref] = ref }
end
if surface.key?(:doors)
surface[:doors].each do |i, door|
ref = 0.9
ref = 2.0 if door.key?(:glazed) && door[:glazed]
- ref *= 43 / (heating + 25) if heating < 18 && cooling > 40
+ ref *= 43 / (heating + 25) if heating < 18 && cooling > 40
door[:ref] = ref
end
end
# Loop through point thermal bridges.
- surface[:pts].map { |i, pt| pt[:ref] = 0.5 } if surface.key?(:pts)
+ surface[:pts].map { |i, pt| pt[:ref] = 0.5 } if surface.key?(:pts)
# Loop through linear thermal bridges.
if surface.key?(:edges)
surface[:edges].values.each do |edge|
next unless edge.key?(:type)
next unless edge.key?(:ratio)
+
safe = sets.safe("code (Quebec)", edge[:type])
- edge[:ref] = shorts[:val][safe] * edge[:ratio] if safe
+ edge[:ref] = shorts[:val][safe] * edge[:ratio] if safe
end
end
end
true
end
##
- # Generate UA' summary.
+ # Generates multilingual UA' summary.
#
# @param date [Time] Time stamp
- # @param argh [Hash] TBD arguments
+ # @param [Hash] argh TBD arguments
+ # @option argh [#to_s] :seed OpenStudio file, e.g. "school23.osm"
+ # @option argh [#to_s] :ua_ref reference ruleset e.g. "code (Quebec)"
+ # @option argh [Hash] :surfaces set of TBD surfaces (see )
+ # @option argh [#to_s] :version OpenStudio SDK, e.g. "3.6.1"
+ # @option argh [Hash] :io TBD input/output variables (see TBD JSON schema)
#
- # @return [Hash] multilingual binned values for UA' summary
+ # @return [Hash] binned values for UA' (see logs if empty)
def ua_summary(date = Time.now, argh = {})
mth = "TBD::#{__callee__}"
+ cl1 = Time
+ cl2 = String
+ cl3 = Hash
+ ua = {}
+ return mismatch("date", date, cl1, mth, DBG, ua) unless date.is_a?(cl1)
+ return mismatch("argh", argh, cl3, mth, DBG, ua) unless argh.is_a?(cl3)
- ua = {}
- argh = {} unless argh.is_a?(Hash )
- argh[:seed ] = "" unless argh.key?(:seed )
- argh[:ua_ref ] = "" unless argh.key?(:ua_ref )
- argh[:surfaces ] = nil unless argh.key?(:surfaces )
- argh[:version ] = "" unless argh.key?(:version )
- argh[:io ] = {} unless argh.key?(:io )
- argh[:io][:description] = "" unless argh[:io].key?(:description)
+ argh[:seed ] = "" unless argh.key?(:seed)
+ argh[:ua_ref ] = "" unless argh.key?(:ua_ref)
+ argh[:surfaces] = nil unless argh.key?(:surfaces)
+ argh[:version ] = "" unless argh.key?(:version)
+ argh[:io ] = {} unless argh.key?(:io)
- descr = argh[:io][:description]
- file = argh[:seed ]
- version = argh[:version ]
- s = argh[:surfaces ]
+ file = argh[:seed ]
+ ref = argh[:ua_ref ]
+ s = argh[:surfaces]
+ v = argh[:version ]
+ io = argh[:io ]
+ return mismatch( "seed", file, cl2, mth, DBG, ua) unless file.is_a?(cl2)
+ return mismatch( "UA' ref", ref, cl2, mth, DBG, ua) unless ref.is_a?(cl2)
+ return mismatch( "version", v, cl2, mth, DBG, ua) unless v.is_a?(cl2)
+ return mismatch("surfaces", s, cl3, mth, DBG, ua) unless s.is_a?(cl3)
+ return mismatch( "io", io, cl3, mth, DBG, ua) unless io.is_a?(cl3)
+ return empty( "surfaces", mth, WRN, ua) if s.empty?
- return mismatch("TBD surfaces", s, Hash, mth, DBG, ua) unless s.is_a?(Hash)
- return empty("TBD Surfaces", mth, WRN, ua) if s.empty?
+ argh[:io][:description] = "" unless argh[:io].key?(:description)
+ descr = argh[:io][:description]
ua[:descr ] = ""
ua[:file ] = ""
ua[:version] = ""
ua[:model ] = "∑U•A + ∑PSI•L + ∑KHI•n"
ua[:date ] = date
- ua[:descr ] = descr unless descr.nil? || descr.empty?
- ua[:file ] = file unless file.nil? || file.empty?
- ua[:version] = version unless version.nil? || version.empty?
+ ua[:descr ] = descr unless descr.nil? || descr.empty?
+ ua[:file ] = file unless file.nil? || file.empty?
+ ua[:version] = v unless v.nil? || v.empty?
[:en, :fr].each { |lang| ua[lang] = {} }
- ua[:en][:notes] = "Automated assessment from the OpenStudio Measure, " \
- "Thermal Bridging and Derating (TBD). Open source and MIT-licensed, " \
- "TBD is provided as is (without warranty). Procedures are documented " \
+ ua[:en][:notes] = "Automated assessment from the OpenStudio Measure, "\
+ "Thermal Bridging and Derating (TBD). Open source and MIT-licensed, "\
+ "TBD is provided as is (without warranty). Procedures are documented "\
"in the source code: https://github.com/rd2/tbd. "
- ua[:fr][:notes] = "Analyse automatisée à partir de la measure OpenStudio, "\
- "'Thermal Bridging and Derating' (ou TBD). Distribuée librement " \
- "(licence MIT), TBD est offerte telle quelle (sans garantie). " \
- "L'approche est documentée au sein du code source : " \
+ ua[:fr][:notes] = "Analyse automatisée à partir de la measure "\
+ "OpenStudio, 'Thermal Bridging and Derating' (ou TBD). Distribuée "\
+ "librement (licence MIT), TBD est offerte telle quelle (sans "\
+ "garantie). L'approche est documentée au sein du code source : "\
"https://github.com/rd2/tbd."
walls = { net: 0, gross: 0, subs: 0 }
roofs = { net: 0, gross: 0, subs: 0 }
floors = { net: 0, gross: 0, subs: 0 }
areas = { walls: walls, roofs: roofs, floors: floors }
has = {}
val = {}
psi = PSI.new
- unless argh[:ua_ref].empty?
- shorts = psi.shorthands(argh[:ua_ref])
+ unless ref.empty?
+ shorts = psi.shorthands(ref)
empty = shorts[:has].empty? && shorts[:val].empty?
- has = shorts[:has] unless empty
- val = shorts[:val] unless empty
- log(ERR, "Invalid UA' reference set (#{mth})") if empty
+ has = shorts[:has] unless empty
+ val = shorts[:val] unless empty
+ log(ERR, "Invalid UA' reference set (#{mth})") if empty
unless empty
- ua[:model] += " : Design vs '#{argh[:ua_ref]}'"
+ ua[:model] += " : Design vs '#{ref}'"
- case argh[:ua_ref]
+ case ref
when "code (Quebec)"
ua[:en][:objective] = "COMPLIANCE ASSESSMENT"
ua[:en][:details ] = []
ua[:en][:details ] << "Quebec Construction Code, Chapter I.1"
ua[:en][:details ] << "NECB 2015, modified version (2020)"
ua[:en][:details ] << "Division B, Section 3.3"
ua[:en][:details ] << "Building Envelope Trade-off Path"
- ua[:en][:notes] << " Calculations comply with Section 3.3 " \
- "requirements. Results are based on user input not subject to " \
- "prior validation (see DESCRIPTION), and as such the assessment " \
+ ua[:en][:notes] << " Calculations comply with Section 3.3 "\
+ "requirements. Results are based on user input not subject to "\
+ "prior validation (see DESCRIPTION), and as such the assessment "\
"shall not be considered as a certification of compliance."
ua[:fr][:objective] = "ANALYSE DE CONFORMITÉ"
ua[:fr][:details ] = []
ua[:fr][:details ] << "Code de construction du Québec, Chapitre I.1"
ua[:fr][:details ] << "CNÉB 2015, version modifiée (2020)"
ua[:fr][:details ] << "Division B, Section 3.3"
ua[:fr][:details ] << "Méthode des solutions de remplacement"
- ua[:fr][:notes] << " Les calculs sont conformes aux dispositions de "\
- "la Section 3.3. Les résultats sont tributaires d'intrants " \
- "fournis par l'utilisateur, sans validation préalable (voir " \
- "DESCRIPTION). Ce document ne peut constituer une attestation de " \
+ ua[:fr][:notes] << " Les calculs sont conformes aux dispositions "\
+ "de la Section 3.3. Les résultats sont tributaires d'intrants "\
+ "fournis par l'utilisateur, sans validation préalable (voir "\
+ "DESCRIPTION). Ce document ne peut constituer une attestation de "\
"conformité."
else
ua[:en][:objective] = "UA'"
ua[:fr][:objective] = "UA'"
end
@@ -580,51 +637,53 @@
#
# Each block's UA' = ∑ U•area + ∑ PSI•length + ∑ KHI•count
blc = { walls: 0, roofs: 0, floors: 0, doors: 0,
windows: 0, skylights: 0, rimjoists: 0, parapets: 0,
trim: 0, corners: 0, balconies: 0, grade: 0,
- other: 0 } # includes party wall edges, expansion joints, etc.
+ other: 0 } # party edges, expansion joints, spandrel edges, etc.
b1 = {}
b2 = {}
- b1[:pro] = blc # proposed design
- b1[:ref] = blc.clone # reference
- b2[:pro] = blc.clone # proposed design
- b2[:ref] = blc.clone # reference
+ b1[:pro] = blc # proposed design
+ b1[:ref] = blc.clone # reference
+ b2[:pro] = blc.clone # proposed design
+ b2[:ref] = blc.clone # reference
# Loop through surfaces, subsurfaces and edges and populate bloc1 & bloc2.
- argh[:surfaces].each do |id, surface|
+ s.each do |id, surface|
next unless surface.key?(:deratable)
next unless surface[:deratable]
next unless surface.key?(:type)
+
type = surface[:type]
- next unless type == :wall || type == :ceiling || type == :floor
+ next unless [:wall, :ceiling, :floor].include?(type)
next unless surface.key?(:net)
next unless surface[:net] > TOL
next unless surface.key?(:u)
next unless surface[:u] > TOL
+
heating = 21.0
heating = surface[:heating] if surface.key?(:heating)
bloc = b1
bloc = b2 if heating < 18
reference = surface.key?(:ref)
if type == :wall
areas[:walls][:net ] += surface[:net]
bloc[:pro][:walls ] += surface[:net] * surface[:u ]
- bloc[:ref][:walls ] += surface[:net] * surface[:ref] if reference
- bloc[:ref][:walls ] += surface[:net] * surface[:u ] unless reference
+ bloc[:ref][:walls ] += surface[:net] * surface[:ref] if reference
+ bloc[:ref][:walls ] += surface[:net] * surface[:u ] unless reference
elsif type == :ceiling
areas[:roofs][:net ] += surface[:net]
bloc[:pro][:roofs ] += surface[:net] * surface[:u ]
- bloc[:ref][:roofs ] += surface[:net] * surface[:ref] if reference
- bloc[:ref][:roofs ] += surface[:net] * surface[:u ] unless reference
+ bloc[:ref][:roofs ] += surface[:net] * surface[:ref] if reference
+ bloc[:ref][:roofs ] += surface[:net] * surface[:u ] unless reference
else
areas[:floors][:net] += surface[:net]
bloc[:pro][:floors ] += surface[:net] * surface[:u ]
- bloc[:ref][:floors ] += surface[:net] * surface[:ref] if reference
- bloc[:ref][:floors ] += surface[:net] * surface[:u ] unless reference
+ bloc[:ref][:floors ] += surface[:net] * surface[:ref] if reference
+ bloc[:ref][:floors ] += surface[:net] * surface[:u ] unless reference
end
[:doors, :windows, :skylights].each do |subs|
next unless surface.key?(subs)
@@ -633,17 +692,17 @@
next unless sub.key?(:u )
next unless sub[:gross] > TOL
next unless sub[:u ] > TOL
gross = sub[:gross]
- gross *= sub[:mult ] if sub.key?(:mult)
- areas[:walls ][:subs] += gross if type == :wall
- areas[:roofs ][:subs] += gross if type == :ceiling
- areas[:floors][:subs] += gross if type == :floor
+ gross *= sub[:mult ] if sub.key?(:mult)
+ areas[:walls ][:subs] += gross if type == :wall
+ areas[:roofs ][:subs] += gross if type == :ceiling
+ areas[:floors][:subs] += gross if type == :floor
bloc[:pro ][subs ] += gross * sub[:u ]
- bloc[:ref ][subs ] += gross * sub[:ref] if sub.key?(:ref)
- bloc[:ref ][subs ] += gross * sub[:u ] unless sub.key?(:ref)
+ bloc[:ref ][subs ] += gross * sub[:ref] if sub.key?(:ref)
+ bloc[:ref ][subs ] += gross * sub[:u ] unless sub.key?(:ref)
end
end
if surface.key?(:edges)
surface[:edges].values.each do |edge|
@@ -651,56 +710,71 @@
next unless edge.key?(:length)
next unless edge[:length] > TOL
next unless edge.key?(:psi)
loss = edge[:length] * edge[:psi]
- type = edge[:type].to_s
+ type = edge[:type].to_s.downcase
- case type
- when /rimjoist/i
- bloc[:pro][:rimjoists] += loss
- when /parapet/i
- bloc[:pro][:parapets ] += loss
- when /fenestration/i
+ if edge[:type].to_s.downcase.include?("balcony")
+ bloc[:pro][:balconies] += loss
+ elsif edge[:type].to_s.downcase.include?("door")
bloc[:pro][:trim ] += loss
- when /head/i
+ elsif edge[:type].to_s.downcase.include?("skylight")
bloc[:pro][:trim ] += loss
- when /sill/i
+ elsif edge[:type].to_s.downcase.include?("fenestration")
bloc[:pro][:trim ] += loss
- when /jamb/i
+ elsif edge[:type].to_s.downcase.include?("head")
bloc[:pro][:trim ] += loss
- when /corner/i
+ elsif edge[:type].to_s.downcase.include?("sill")
+ bloc[:pro][:trim ] += loss
+ elsif edge[:type].to_s.downcase.include?("jamb")
+ bloc[:pro][:trim ] += loss
+ elsif edge[:type].to_s.downcase.include?("rimjoist")
+ bloc[:pro][:rimjoists] += loss
+ elsif edge[:type].to_s.downcase.include?("parapet")
+ bloc[:pro][:parapets ] += loss
+ elsif edge[:type].to_s.downcase.include?("roof")
+ bloc[:pro][:parapets ] += loss
+ elsif edge[:type].to_s.downcase.include?("corner")
bloc[:pro][:corners ] += loss
- when /grade/i
+ elsif edge[:type].to_s.downcase.include?("grade")
bloc[:pro][:grade ] += loss
else
bloc[:pro][:other ] += loss
end
next if val.empty?
- next if argh[:ua_ref].empty?
- safer = psi.safe(argh[:ua_ref], edge[:type])
+ next if ref.empty?
+
+ safer = psi.safe(ref, edge[:type])
ok = edge.key?(:ref)
- loss = edge[:length] * edge[:ref] if ok
- loss = edge[:length] * val[safer] * edge[:ratio] unless ok
+ loss = edge[:length] * edge[:ref] if ok
+ loss = edge[:length] * val[safer] * edge[:ratio] unless ok
- case safer.to_s
- when /rimjoist/i
- bloc[:ref][:rimjoists] += loss
- when /parapet/i
- bloc[:ref][:parapets ] += loss
- when /fenestration/i
+ if edge[:type].to_s.downcase.include?("balcony")
+ bloc[:ref][:balconies] += loss
+ elsif edge[:type].to_s.downcase.include?("door")
bloc[:ref][:trim ] += loss
- when /head/i
+ elsif edge[:type].to_s.downcase.include?("skylight")
bloc[:ref][:trim ] += loss
- when /sill/i
+ elsif edge[:type].to_s.downcase.include?("fenestration")
bloc[:ref][:trim ] += loss
- when /jamb/i
+ elsif edge[:type].to_s.downcase.include?("head")
bloc[:ref][:trim ] += loss
- when /corner/i
+ elsif edge[:type].to_s.downcase.include?("sill")
+ bloc[:ref][:trim ] += loss
+ elsif edge[:type].to_s.downcase.include?("jamb")
+ bloc[:ref][:trim ] += loss
+ elsif edge[:type].to_s.downcase.include?("rimjoist")
+ bloc[:ref][:rimjoists] += loss
+ elsif edge[:type].to_s.downcase.include?("parapet")
+ bloc[:ref][:parapets ] += loss
+ elsif edge[:type].to_s.downcase.include?("roof")
+ bloc[:ref][:parapets ] += loss
+ elsif edge[:type].to_s.downcase.include?("corner")
bloc[:ref][:corners ] += loss
- when /grade/i
+ elsif edge[:type].to_s.downcase.include?("grade")
bloc[:ref][:grade ] += loss
else
bloc[:ref][:other ] += loss
end
end
@@ -708,12 +782,14 @@
if surface.key?(:pts)
surface[:pts].values.each do |pts|
next unless pts.key?(:val)
next unless pts.key?(:n)
+
bloc[:pro][:other] += pts[:val] * pts[:n]
next unless pts.key?(:ref)
+
bloc[:ref][:other] += pts[:ref] * pts[:n]
end
end
end
@@ -733,226 +809,221 @@
str += format(" +%.1f%%", ratio) if ratio && pro_sum > ref_sum # **
str += format(" -%.1f%%", ratio) if ratio && pro_sum < ref_sum
ua[lang][b] = {}
if b == :b1
- ua[:en][b][:summary] = "heated : #{str}" if lang == :en
- ua[:fr][b][:summary] = "chauffé : #{str}" if lang == :fr
+ ua[:en][b][:summary] = "heated : #{str}" if lang == :en
+ ua[:fr][b][:summary] = "chauffé : #{str}" if lang == :fr
else
- ua[:en][b][:summary] = "semi-heated : #{str}" if lang == :en
- ua[:fr][b][:summary] = "semi-chauffé : #{str}" if lang == :fr
+ ua[:en][b][:summary] = "semi-heated : #{str}" if lang == :en
+ ua[:fr][b][:summary] = "semi-chauffé : #{str}" if lang == :fr
end
- # ** https://bugs.ruby-lang.org/issues/13761 (Ruby > 2.2.5)
- # str += format(" +%.1f%", ratio) if ratio && pro_sum > ref_sum ... now:
- # str += format(" +%.1f%%", ratio) if ratio && pro_sum > ref_sum
-
bloc[:pro].each do |k, v|
rf = bloc[:ref][k]
next if v < TOL && rf < TOL
ratio = nil
- ratio = (100.0 * (v - rf) / rf).abs if rf > TOL
+ ratio = (100.0 * (v - rf) / rf).abs if rf > TOL
str = format("%.1f W/K (vs %.1f W/K)", v, rf)
- str += format(" +%.1f%%", ratio) if ratio && v > rf
- str += format(" -%.1f%%", ratio) if ratio && v < rf
+ str += format(" +%.1f%%", ratio) if ratio && v > rf
+ str += format(" -%.1f%%", ratio) if ratio && v < rf
case k
when :walls
- ua[:en][b][k] = "walls : #{str}" if lang == :en
- ua[:fr][b][k] = "murs : #{str}" if lang == :fr
+ ua[:en][b][k] = "walls : #{str}" if lang == :en
+ ua[:fr][b][k] = "murs : #{str}" if lang == :fr
when :roofs
- ua[:en][b][k] = "roofs : #{str}" if lang == :en
- ua[:fr][b][k] = "toits : #{str}" if lang == :fr
+ ua[:en][b][k] = "roofs : #{str}" if lang == :en
+ ua[:fr][b][k] = "toits : #{str}" if lang == :fr
when :floors
- ua[:en][b][k] = "floors : #{str}" if lang == :en
- ua[:fr][b][k] = "planchers : #{str}" if lang == :fr
+ ua[:en][b][k] = "floors : #{str}" if lang == :en
+ ua[:fr][b][k] = "planchers : #{str}" if lang == :fr
when :doors
- ua[:en][b][k] = "doors : #{str}" if lang == :en
- ua[:fr][b][k] = "portes : #{str}" if lang == :fr
+ ua[:en][b][k] = "doors : #{str}" if lang == :en
+ ua[:fr][b][k] = "portes : #{str}" if lang == :fr
when :windows
- ua[:en][b][k] = "windows : #{str}" if lang == :en
- ua[:fr][b][k] = "fenêtres : #{str}" if lang == :fr
+ ua[:en][b][k] = "windows : #{str}" if lang == :en
+ ua[:fr][b][k] = "fenêtres : #{str}" if lang == :fr
when :skylights
- ua[:en][b][k] = "skylights : #{str}" if lang == :en
- ua[:fr][b][k] = "lanterneaux : #{str}" if lang == :fr
+ ua[:en][b][k] = "skylights : #{str}" if lang == :en
+ ua[:fr][b][k] = "lanterneaux : #{str}" if lang == :fr
when :rimjoists
- ua[:en][b][k] = "rimjoists : #{str}" if lang == :en
- ua[:fr][b][k] = "rives : #{str}" if lang == :fr
+ ua[:en][b][k] = "rimjoists : #{str}" if lang == :en
+ ua[:fr][b][k] = "rives : #{str}" if lang == :fr
when :parapets
- ua[:en][b][k] = "parapets : #{str}" if lang == :en
- ua[:fr][b][k] = "parapets : #{str}" if lang == :fr
+ ua[:en][b][k] = "parapets : #{str}" if lang == :en
+ ua[:fr][b][k] = "parapets : #{str}" if lang == :fr
when :trim
- ua[:en][b][k] = "trim : #{str}" if lang == :en
- ua[:fr][b][k] = "chassis : #{str}" if lang == :fr
+ ua[:en][b][k] = "trim : #{str}" if lang == :en
+ ua[:fr][b][k] = "chassis : #{str}" if lang == :fr
when :corners
- ua[:en][b][k] = "corners : #{str}" if lang == :en
- ua[:fr][b][k] = "coins : #{str}" if lang == :fr
+ ua[:en][b][k] = "corners : #{str}" if lang == :en
+ ua[:fr][b][k] = "coins : #{str}" if lang == :fr
when :balconies
- ua[:en][b][k] = "balconies : #{str}" if lang == :en
- ua[:fr][b][k] = "balcons : #{str}" if lang == :fr
+ ua[:en][b][k] = "balconies : #{str}" if lang == :en
+ ua[:fr][b][k] = "balcons : #{str}" if lang == :fr
when :grade
- ua[:en][b][k] = "grade : #{str}" if lang == :en
- ua[:fr][b][k] = "tracé : #{str}" if lang == :fr
+ ua[:en][b][k] = "grade : #{str}" if lang == :en
+ ua[:fr][b][k] = "tracé : #{str}" if lang == :fr
else
- ua[:en][b][k] = "other : #{str}" if lang == :en
- ua[:fr][b][k] = "autres : #{str}" if lang == :fr
+ ua[:en][b][k] = "other : #{str}" if lang == :en
+ ua[:fr][b][k] = "autres : #{str}" if lang == :fr
end
end
- # Deterministic sorting
+ # Deterministic sorting.
ua[lang][b][:summary] = ua[lang][b].delete(:summary)
- ok = ua[lang][b].key?(:walls)
- ua[lang][b][:walls] = ua[lang][b].delete(:walls) if ok
- ok = ua[lang][b].key?(:roofs)
- ua[lang][b][:roofs] = ua[lang][b].delete(:roofs) if ok
- ok = ua[lang][b].key?(:floors)
- ua[lang][b][:floors] = ua[lang][b].delete(:floors) if ok
- ok = ua[lang][b].key?(:doors)
- ua[lang][b][:doors] = ua[lang][b].delete(:doors) if ok
- ok = ua[lang][b].key?(:windows)
- ua[lang][b][:windows] = ua[lang][b].delete(:windows) if ok
- ok = ua[lang][b].key?(:skylights)
- ua[lang][b][:skylights] = ua[lang][b].delete(:skylights) if ok
- ok = ua[lang][b].key?(:rimjoists)
- ua[lang][b][:rimjoists] = ua[lang][b].delete(:rimjoists) if ok
- ok = ua[lang][b].key?(:parapets)
- ua[lang][b][:parapets] = ua[lang][b].delete(:parapets) if ok
- ok = ua[lang][b].key?(:trim)
- ua[lang][b][:trim] = ua[lang][b].delete(:trim) if ok
- ok = ua[lang][b].key?(:corners)
- ua[lang][b][:corners] = ua[lang][b].delete(:corners) if ok
- ok = ua[lang][b].key?(:balconies)
- ua[lang][b][:balconies] = ua[lang][b].delete(:balconies) if ok
- ok = ua[lang][b].key?(:grade)
- ua[lang][b][:grade] = ua[lang][b].delete(:grade) if ok
- ok = ua[lang][b].key?(:other)
- ua[lang][b][:other] = ua[lang][b].delete(:other) if ok
+
+ ua[lang][b].keys.each { |k| ua[lang][b][k] = ua[lang][b].delete(k) }
end
end
end
# Areas (m2).
areas[:walls ][:gross] = areas[:walls ][:net] + areas[:walls ][:subs]
areas[:roofs ][:gross] = areas[:roofs ][:net] + areas[:roofs ][:subs]
areas[:floors][:gross] = areas[:floors][:net] + areas[:floors][:subs]
+
ua[:en][:areas] = {}
ua[:fr][:areas] = {}
str = format("walls : %.1f m2 (net)", areas[:walls][:net])
str += format(", %.1f m2 (gross)", areas[:walls][:gross])
- ua[:en][:areas][:walls] = str unless areas[:walls][:gross] < TOL
+ ua[:en][:areas][:walls] = str unless areas[:walls ][:gross] < TOL
+
str = format("roofs : %.1f m2 (net)", areas[:roofs][:net])
str += format(", %.1f m2 (gross)", areas[:roofs][:gross])
- ua[:en][:areas][:roofs] = str unless areas[:roofs][:gross] < TOL
+ ua[:en][:areas][:roofs] = str unless areas[:roofs ][:gross] < TOL
+
str = format("floors : %.1f m2 (net)", areas[:floors][:net])
str += format(", %.1f m2 (gross)", areas[:floors][:gross])
- ua[:en][:areas][:floors] = str unless areas[:floors][:gross] < TOL
+ ua[:en][:areas][:floors] = str unless areas[:floors][:gross] < TOL
str = format("murs : %.1f m2 (net)", areas[:walls][:net])
str += format(", %.1f m2 (brut)", areas[:walls][:gross])
- ua[:fr][:areas][:walls] = str unless areas[:walls][:gross] < TOL
+ ua[:fr][:areas][:walls] = str unless areas[:walls ][:gross] < TOL
+
str = format("toits : %.1f m2 (net)", areas[:roofs][:net])
str += format(", %.1f m2 (brut)", areas[:roofs][:gross])
- ua[:fr][:areas][:roofs] = str unless areas[:roofs][:gross] < TOL
+ ua[:fr][:areas][:roofs] = str unless areas[:roofs ][:gross] < TOL
+
str = format("planchers : %.1f m2 (net)", areas[:floors][:net])
str += format(", %.1f m2 (brut)", areas[:floors][:gross])
- ua[:fr][:areas][:floors] = str unless areas[:floors][:gross] < TOL
+ ua[:fr][:areas][:floors] = str unless areas[:floors][:gross] < TOL
ua
end
##
- # Generate MD-formatted file.
+ # Generates MD-formatted, UA' summary file.
#
- # @param ua [Hash] preprocessed collection of UA-related strings
- # @param lang [String] preferred language ("en" vs "fr")
+ # @param [#key?] ua preprocessed collection of UA-related strings
+ # option ua [#to_s] :objective ua[lang][:objective] = "COMPLIANCE [...]"
+ # option ua [#&] :details ua[lang][:details] = "QC Energy Code [...]"
+ # option ua [#to_s] :model "∑U•A + ∑PSI•L + ∑KHI•n [...]"
+ # option ua [#key?] :b1 TB block of CONDITIONED spaces, ua[lang][:b1]
+ # option ua [#key?] :b2 TB block of SEMIHEATED spaces, ua[lang][:b2]
+ # option ua [#to_s] :descr user-provided project/summary description
+ # option ua [#to_s] :file OpenStudio file, e.g. "school23.osm"
+ # option ua [#to_s] :version OpenStudio SDK, e.g. "3.6.1"
+ # option ua [Time] :date time signature
+ # option ua [#to_s] :notes advisory info, ua[lang][:notes]
+ # option ua [#key?] :areas binned areas (String), ua[lang][:areas][:walls]
+ # @param lang [#to_sym] selected language, :en or :fr
#
- # @return [Array] MD-formatted strings (empty if invalid inputs)
+ # @return [Array<String>] MD-formatted strings (see logs if empty)
def ua_md(ua = {}, lang = :en)
mth = "TBD::#{__callee__}"
report = []
+ ck1 = ua.respond_to?(:key?)
+ ck2 = lang.respond_to?(:to_sym)
+ return mismatch( "ua", ua, Hash, mth, DBG, report) unless ck1
+ return mismatch("lang", lang, Symbol, mth, DBG, report) unless ck2
- return mismatch("ua", ua, Hash, mth, DBG, report) unless ua.is_a?(Hash)
- return empty("ua", mth, DBG, report) if ua.empty?
- return hashkey("", ua, lang, mth, DBG, report) unless ua.key?(lang)
+ lang = lang.to_sym
+ return hashkey("language", ua, lang, mth, DBG, report) unless ua.key?(lang)
+ return empty("ua" , mth, DBG, report) if ua.empty?
- if ua[lang].key?(:objective)
- report << "# #{ua[lang][:objective]} "
+ if ua[lang].key?(:objective) && ua[lang][:objective].respond_to?(:to_s)
+ report << "# #{ua[lang][:objective].to_s} "
report << " "
end
- if ua[lang].key?(:details)
- ua[lang][:details].each { |d| report << "#{d} " }
+ if ua[lang].key?(:details) && ua[lang][:details].respond_to?(:&)
+ ua[lang][:details].each do |d|
+ report << "#{d.to_s} " if d.respond_to?(:to_s)
+ end
+
report << " "
end
- if ua.key?(:model)
- report << "##### SUMMARY " if lang == :en
- report << "##### SOMMAIRE " if lang == :fr
+ if ua.key?(:model) && ua[:model].respond_to?(:to_s)
+ report << "##### SUMMARY " if lang == :en
+ report << "##### SOMMAIRE " if lang == :fr
report << " "
- report << "#{ua[:model]} "
+ report << "#{ua[:model].to_s} "
report << " "
end
if ua[lang].key?(:b1) && ua[lang][:b1].key?(:summary)
last = ua[lang][:b1].keys.to_a.last
report << "* #{ua[lang][:b1][:summary]}"
ua[lang][:b1].each do |k, v|
- next if k == :summary
- report << " * #{v}" unless k == last
- report << " * #{v} " if k == last
- report << " " if k == last
+ next if k == :summary
+ report << " * #{v}" unless k == last
+ report << " * #{v} " if k == last
+ report << " " if k == last
end
report << " "
end
if ua[lang].key?(:b2) && ua[lang][:b2].key?(:summary)
last = ua[lang][:b2].keys.to_a.last
report << "* #{ua[lang][:b2][:summary]}"
ua[lang][:b2].each do |k, v|
- next if k == :summary
- report << " * #{v}" unless k == last
- report << " * #{v} " if k == last
- report << " " if k == last
+ next if k == :summary
+ report << " * #{v}" unless k == last
+ report << " * #{v} " if k == last
+ report << " " if k == last
end
report << " "
end
if ua.key?(:date)
report << "##### DESCRIPTION "
report << " "
- report << "* project : #{ua[:descr]}" if ua.key?(:descr) && lang == :en
- report << "* projet : #{ua[:descr]}" if ua.key?(:descr) && lang == :fr
+ report << "* project : #{ua[:descr]}" if ua.key?(:descr) && lang == :en
+ report << "* projet : #{ua[:descr]}" if ua.key?(:descr) && lang == :fr
model = ""
- model = "* model : #{ua[:file]}" if ua.key?(:file) && lang == :en
- model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
- model += " (v#{ua[:version]})" if ua.key?(:version)
- report << model unless model.empty?
- report << "* TBD : v3.2.3"
+ model = "* model : #{ua[:file]}" if ua.key?(:file) && lang == :en
+ model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
+ model += " (v#{ua[:version]})" if ua.key?(:version)
+ report << model unless model.empty?
+ report << "* TBD : v3.3.0"
report << "* date : #{ua[:date]}"
if lang == :en
- report << "* status : #{msg(status)}" unless status.zero?
- report << "* status : success !" if status.zero?
+ report << "* status : #{msg(status)}" unless status.zero?
+ report << "* status : success !" if status.zero?
elsif lang == :fr
- report << "* statut : #{msg(status)}" unless status.zero?
- report << "* statut : succès !" if status.zero?
+ report << "* statut : #{msg(status)}" unless status.zero?
+ report << "* statut : succès !" if status.zero?
end
report << " "
end
if ua[lang].key?(:areas)
- report << "##### AREAS " if lang == :en
- report << "##### AIRES " if lang == :fr
+ report << "##### AREAS " if lang == :en
+ report << "##### AIRES " if lang == :fr
report << " "
ok = ua[lang][:areas].key?(:walls)
- report << "* #{ua[lang][:areas][:walls]}" if ok
+ report << "* #{ua[lang][:areas][:walls]}" if ok
ok = ua[lang][:areas].key?(:roofs)
- report << "* #{ua[lang][:areas][:roofs]}" if ok
+ report << "* #{ua[lang][:areas][:roofs]}" if ok
ok = ua[lang][:areas].key?(:floors)
- report << "* #{ua[lang][:areas][:floors]}" if ok
+ report << "* #{ua[lang][:areas][:floors]}" if ok
report << " "
end
if ua[lang].key?(:notes)
report << "##### NOTES "