tbd 3.2.3 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/pull_request.yml +32 -0
- data/.yardopts +14 -0
- data/README.md +23 -25
- data/json/tbd_warehouse17.json +8 -0
- data/json/tbd_warehouse18.json +12 -0
- data/json/tbd_warehouse4.json +17 -0
- data/lib/measures/tbd/README.md +27 -11
- data/lib/measures/tbd/measure.rb +155 -72
- data/lib/measures/tbd/measure.xml +168 -66
- data/lib/measures/tbd/resources/geo.rb +435 -221
- data/lib/measures/tbd/resources/oslog.rb +213 -161
- data/lib/measures/tbd/resources/psi.rb +1849 -900
- data/lib/measures/tbd/resources/ua.rb +380 -309
- data/lib/measures/tbd/resources/utils.rb +2491 -764
- data/lib/measures/tbd/resources/version.rb +1 -1
- data/lib/measures/tbd/tests/tbd_tests.rb +1 -1
- data/lib/tbd/geo.rb +435 -221
- data/lib/tbd/psi.rb +1849 -900
- data/lib/tbd/ua.rb +380 -309
- data/lib/tbd/version.rb +1 -1
- data/lib/tbd.rb +14 -34
- data/tbd.gemspec +2 -2
- data/tbd.schema.json +189 -20
- data/v291_MacOS.md +2 -4
- metadata +10 -6
@@ -26,13 +26,13 @@ module TBD
|
|
26
26
|
#
|
27
27
|
# @param model [OpenStudio::Model::Model] a model
|
28
28
|
# @param lc [OpenStudio::Model::LayeredConstruction] a layered construction
|
29
|
-
# @param id [
|
30
|
-
# @param
|
31
|
-
# @param film [
|
32
|
-
# @param ut [
|
29
|
+
# @param id [#to_s] layered construction identifier
|
30
|
+
# @param hloss [Numeric] heat loss from major thermal bridging, in W/K
|
31
|
+
# @param film [Numeric] target surface film resistance, in m2•K/W
|
32
|
+
# @param ut [Numeric] target overall Ut for lc, in W/m2•K
|
33
33
|
#
|
34
|
-
# @return [Hash] uo: lc Uo [W/m2
|
35
|
-
# @return [Hash] uo:
|
34
|
+
# @return [Hash] uo: lc Uo [W/m2•K] to meet Ut, m: uprated lc layer
|
35
|
+
# @return [Hash] uo: (nil), m: (nil) if invalid input (see logs)
|
36
36
|
def uo(model = nil, lc = nil, id = "", hloss = 0.0, film = 0.0, ut = 0.0)
|
37
37
|
mth = "TBD::#{__callee__}"
|
38
38
|
res = { uo: nil, m: nil }
|
@@ -40,53 +40,51 @@ module TBD
|
|
40
40
|
cl2 = OpenStudio::Model::LayeredConstruction
|
41
41
|
cl3 = Numeric
|
42
42
|
cl4 = String
|
43
|
-
|
43
|
+
id = trim(id)
|
44
44
|
return mismatch("model", model, cl1, mth, DBG, res) unless model.is_a?(cl1)
|
45
|
-
return mismatch("id" , id, cl4, mth, DBG, res)
|
45
|
+
return mismatch("id" , id, cl4, mth, DBG, res) if id.empty?
|
46
46
|
return mismatch("lc" , lc, cl2, mth, DBG, res) unless lc.is_a?(cl2)
|
47
47
|
return mismatch("hloss", hloss, cl3, mth, DBG, res) unless hloss.is_a?(cl3)
|
48
48
|
return mismatch("film" , film, cl3, mth, DBG, res) unless film.is_a?(cl3)
|
49
49
|
return mismatch("Ut" , ut, cl3, mth, DBG, res) unless ut.is_a?(cl3)
|
50
50
|
|
51
|
-
loss = 0.0
|
51
|
+
loss = 0.0 # residual heatloss (not assigned) [W/K]
|
52
52
|
area = lc.getNetArea
|
53
53
|
lyr = insulatingLayer(lc)
|
54
54
|
lyr[:index] = nil unless lyr[:index].is_a?(Numeric)
|
55
55
|
lyr[:index] = nil unless lyr[:index] >= 0
|
56
56
|
lyr[:index] = nil unless lyr[:index] < lc.layers.size
|
57
|
-
|
58
|
-
return
|
59
|
-
return zero("
|
60
|
-
return zero("
|
61
|
-
return
|
62
|
-
return
|
63
|
-
return zero("'#{id}': net area (m2)", mth, ERR, res) unless area > TOL
|
57
|
+
return invalid("#{id} layer index", mth, 3, ERR, res) unless lyr[:index]
|
58
|
+
return zero("#{id}: heatloss" , mth, WRN, res) unless hloss > TOL
|
59
|
+
return zero("#{id}: films" , mth, WRN, res) unless film > TOL
|
60
|
+
return zero("#{id}: Ut" , mth, WRN, res) unless ut > TOL
|
61
|
+
return invalid("#{id}: Ut" , mth, 6, WRN, res) unless ut < 5.678
|
62
|
+
return zero("#{id}: net area (m2)", mth, ERR, res) unless area > TOL
|
64
63
|
|
65
64
|
# First, calculate initial layer RSi to initially meet Ut target.
|
66
|
-
rt = 1 / ut
|
67
|
-
ro = rsi(lc, film)
|
68
|
-
new_r = lyr[:r] + (rt - ro)
|
65
|
+
rt = 1 / ut # target construction Rt
|
66
|
+
ro = rsi(lc, film) # current construction Ro
|
67
|
+
new_r = lyr[:r] + (rt - ro) # new, un-derated layer RSi
|
69
68
|
new_u = 1 / new_r
|
70
69
|
|
71
70
|
# Then, uprate (if possible) to counter expected thermal bridging effects.
|
72
|
-
u_psi = hloss / area
|
73
|
-
new_u -= u_psi
|
74
|
-
new_r = 1 / new_u
|
75
|
-
|
76
|
-
return zero("'#{id}': new Rsi", mth, ERR, res) unless new_r > 0.001
|
71
|
+
u_psi = hloss / area # from psi+khi
|
72
|
+
new_u -= u_psi # uprated layer USi to counter psi+khi
|
73
|
+
new_r = 1 / new_u # uprated layer RSi to counter psi+khi
|
74
|
+
return zero("#{id}: new Rsi", mth, ERR, res) unless new_r > 0.001
|
77
75
|
|
78
76
|
if lyr[:type] == :massless
|
79
77
|
m = lc.getLayer(lyr[:index]).to_MasslessOpaqueMaterial
|
80
|
-
return invalid("
|
78
|
+
return invalid("#{id} massless layer?", mth, 0, DBG, res) if m.empty?
|
81
79
|
|
82
80
|
m = m.get.clone(model).to_MasslessOpaqueMaterial.get
|
83
81
|
m.setName("#{id} uprated")
|
84
|
-
new_r = 0.001
|
85
|
-
loss = (new_u - 1 / new_r) * area
|
82
|
+
new_r = 0.001 unless new_r > 0.001
|
83
|
+
loss = (new_u - 1 / new_r) * area unless new_r > 0.001
|
86
84
|
m.setThermalResistance(new_r)
|
87
85
|
else # type == :standard
|
88
86
|
m = lc.getLayer(lyr[:index]).to_StandardOpaqueMaterial
|
89
|
-
return invalid("
|
87
|
+
return invalid("#{id} standard layer?", mth, 0, DBG, res) if m.empty?
|
90
88
|
|
91
89
|
m = m.get.clone(model).to_StandardOpaqueMaterial.get
|
92
90
|
m.setName("#{id} uprated")
|
@@ -98,30 +96,31 @@ module TBD
|
|
98
96
|
unless d > 0.003
|
99
97
|
d = 0.003
|
100
98
|
k = d / new_r
|
101
|
-
k = 3.0
|
102
|
-
loss = (new_u - k / d) * area
|
99
|
+
k = 3.0 unless k < 3.0
|
100
|
+
loss = (new_u - k / d) * area unless k < 3.0
|
103
101
|
end
|
104
|
-
else
|
102
|
+
else # new_r < 0.001 m2•K/W
|
105
103
|
d = 0.001 * k
|
106
|
-
d = 0.003
|
107
|
-
k = d / 0.001
|
104
|
+
d = 0.003 unless d > 0.003
|
105
|
+
k = d / 0.001 unless d > 0.003
|
108
106
|
loss = (new_u - k / d) * area
|
109
107
|
end
|
110
108
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
109
|
+
if m.setThickness(d)
|
110
|
+
m.setThermalConductivity(k)
|
111
|
+
else
|
112
|
+
return invalid("Can't uprate #{id}: #{d} > 3m", mth, 0, ERR, res)
|
113
|
+
end
|
115
114
|
end
|
116
115
|
|
117
|
-
return invalid("", mth, 0, ERR, res) unless m
|
116
|
+
return invalid("Can't ID insulating layer", mth, 0, ERR, res) unless m
|
118
117
|
|
119
118
|
lc.setLayer(lyr[:index], m)
|
120
119
|
uo = 1 / rsi(lc, film)
|
121
120
|
|
122
121
|
if loss > TOL
|
123
122
|
h_loss = format "%.3f", loss
|
124
|
-
return invalid("Can't assign #{h_loss} W/K to
|
123
|
+
return invalid("Can't assign #{h_loss} W/K to #{id}", mth, 0, ERR, res)
|
125
124
|
end
|
126
125
|
|
127
126
|
res[:uo] = uo
|
@@ -131,60 +130,85 @@ module TBD
|
|
131
130
|
end
|
132
131
|
|
133
132
|
##
|
134
|
-
#
|
133
|
+
# Uprates insulation layer of construction, based on user-selected Ut (argh).
|
135
134
|
#
|
136
135
|
# @param model [OpenStudio::Model::Model] a model
|
137
|
-
# @param
|
138
|
-
# @
|
136
|
+
# @param [Hash] s preprocessed collection of TBD surfaces
|
137
|
+
# @option s [:wall, :ceiling, :floor] :type surface type
|
138
|
+
# @option s [Bool] :deratable whether surface can be thermally bridged
|
139
|
+
# @option s [OpenStudio::LayeredConstruction] :construction construction
|
140
|
+
# @option s [#to_i] :index deratable construction layer index
|
141
|
+
# @option s [:massless, :standard] :ltype indexed layer type
|
142
|
+
# @option s [#to_f] :filmRSI air film resistances (optional)
|
143
|
+
# @option s [#to_f] :r thermal resistance (RSI) of indexed layer
|
144
|
+
# @param [Hash] argh TBD arguments
|
145
|
+
# @option argh [Bool] :uprate_walls (false) whether to uprate walls
|
146
|
+
# @option argh [Bool] :uprate_roofs (false) whether to uprate roofs
|
147
|
+
# @option argh [Bool] :uprate_floors (false) whether to uprate floors
|
148
|
+
# @option argh [#to_f] :wall_ut (5.678) uprated wall Usi-factor target
|
149
|
+
# @option argh [#to_f] :roof_ut (5.678) uprated roof Usi-factor target
|
150
|
+
# @option argh [#to_f] :floor_ut (5.678) uprated floor Usi-factor target
|
151
|
+
# @option argh [#to_s] :wall_option ("") construction to uprate (or "all")
|
152
|
+
# @option argh [#to_s] :roof_option ("") construction to uprate (or "all")
|
153
|
+
# @option argh [#to_s] :floor_option ("") construction to uprate (or "all")
|
139
154
|
#
|
140
|
-
# @return [Bool]
|
155
|
+
# @return [Bool] whether successfully uprated
|
156
|
+
# @return [false] if invalid input (see logs)
|
141
157
|
def uprate(model = nil, s = {}, argh = {})
|
142
|
-
mth
|
143
|
-
cl1
|
144
|
-
cl2
|
145
|
-
cl3
|
146
|
-
|
147
|
-
|
158
|
+
mth = "TBD::#{__callee__}"
|
159
|
+
cl1 = OpenStudio::Model::Model
|
160
|
+
cl2 = Hash
|
161
|
+
cl3 = OpenStudio::Model::LayeredConstruction
|
162
|
+
tout = []
|
163
|
+
tout << "all wall constructions"
|
164
|
+
tout << "all roof constructions"
|
165
|
+
tout << "all floor constructions"
|
166
|
+
a = false
|
167
|
+
groups = { wall: {}, roof: {}, floor: {} }
|
148
168
|
return mismatch("model" , model, cl1, mth, DBG, a) unless model.is_a?(cl1)
|
149
169
|
return mismatch("surfaces", s, cl2, mth, DBG, a) unless s.is_a?(cl2)
|
150
170
|
return mismatch("argh" , model, cl1, mth, DBG, a) unless argh.is_a?(cl2)
|
151
171
|
|
152
|
-
argh[:uprate_walls ] = false unless argh.key?(:uprate_walls
|
153
|
-
argh[:uprate_roofs ] = false unless argh.key?(:uprate_roofs
|
172
|
+
argh[:uprate_walls ] = false unless argh.key?(:uprate_walls)
|
173
|
+
argh[:uprate_roofs ] = false unless argh.key?(:uprate_roofs)
|
154
174
|
argh[:uprate_floors] = false unless argh.key?(:uprate_floors)
|
155
|
-
argh[:wall_ut ] = 5.678 unless argh.key?(:wall_ut
|
156
|
-
argh[:roof_ut ] = 5.678 unless argh.key?(:roof_ut
|
157
|
-
argh[:floor_ut ] = 5.678 unless argh.key?(:floor_ut
|
158
|
-
argh[:wall_option ] = "" unless argh.key?(:wall_option
|
159
|
-
argh[:roof_option ] = "" unless argh.key?(:roof_option
|
160
|
-
argh[:floor_option ] = "" unless argh.key?(:floor_option
|
161
|
-
|
162
|
-
|
175
|
+
argh[:wall_ut ] = 5.678 unless argh.key?(:wall_ut)
|
176
|
+
argh[:roof_ut ] = 5.678 unless argh.key?(:roof_ut)
|
177
|
+
argh[:floor_ut ] = 5.678 unless argh.key?(:floor_ut)
|
178
|
+
argh[:wall_option ] = "" unless argh.key?(:wall_option)
|
179
|
+
argh[:roof_option ] = "" unless argh.key?(:roof_option)
|
180
|
+
argh[:floor_option ] = "" unless argh.key?(:floor_option)
|
181
|
+
|
182
|
+
argh[:wall_option ] = trim(argh[:wall_option ])
|
183
|
+
argh[:roof_option ] = trim(argh[:roof_option ])
|
184
|
+
argh[:floor_option ] = trim(argh[:floor_option])
|
185
|
+
|
163
186
|
groups[:wall ][:up] = argh[:uprate_walls ]
|
164
187
|
groups[:roof ][:up] = argh[:uprate_roofs ]
|
165
188
|
groups[:floor][:up] = argh[:uprate_floors]
|
166
189
|
groups[:wall ][:ut] = argh[:wall_ut ]
|
167
190
|
groups[:roof ][:ut] = argh[:roof_ut ]
|
168
191
|
groups[:floor][:ut] = argh[:floor_ut ]
|
169
|
-
|
170
|
-
groups[:
|
171
|
-
groups[:
|
192
|
+
|
193
|
+
groups[:wall ][:op] = trim(argh[:wall_option ])
|
194
|
+
groups[:roof ][:op] = trim(argh[:roof_option ])
|
195
|
+
groups[:floor][:op] = trim(argh[:floor_option ])
|
172
196
|
|
173
197
|
groups.each do |type, g|
|
174
198
|
next unless g[:up]
|
175
199
|
next unless g[:ut].is_a?(Numeric)
|
176
200
|
next unless g[:ut] < 5.678
|
201
|
+
next if g[:ut] < 0
|
177
202
|
|
178
203
|
typ = type
|
179
|
-
typ = :ceiling if typ == :roof
|
204
|
+
typ = :ceiling if typ == :roof
|
180
205
|
coll = {}
|
181
206
|
area = 0
|
182
207
|
film = 100000000000000
|
183
208
|
lc = nil
|
184
209
|
id = ""
|
185
|
-
|
186
|
-
|
187
|
-
g[:op].downcase == "all floor constructions"
|
210
|
+
op = g[:op].downcase
|
211
|
+
all = tout.include?(op)
|
188
212
|
|
189
213
|
if g[:op].empty?
|
190
214
|
log(ERR, "Construction (#{type}) to uprate? (#{mth})")
|
@@ -217,8 +241,8 @@ module TBD
|
|
217
241
|
id = i
|
218
242
|
end
|
219
243
|
|
220
|
-
coll[i]
|
221
|
-
coll[i][:s][nom] = { a: surface[:net] }
|
244
|
+
coll[i] = { area: aire, lc: c, s: {} } unless coll.key?(i)
|
245
|
+
coll[i][:s][nom] = { a: surface[:net] } unless coll[i][:s].key?(nom)
|
222
246
|
end
|
223
247
|
else
|
224
248
|
id = g[:op]
|
@@ -267,8 +291,8 @@ module TBD
|
|
267
291
|
lyr[:index] = nil unless lyr[:index] >= 0
|
268
292
|
lyr[:index] = nil unless lyr[:index] < lc.layers.size
|
269
293
|
|
270
|
-
log(ERR, "Insulation index for '#{id}'? (#{mth})")
|
271
|
-
next
|
294
|
+
log(ERR, "Insulation index for '#{id}'? (#{mth})") unless lyr[:index]
|
295
|
+
next unless lyr[:index]
|
272
296
|
|
273
297
|
# Ensure lc is exclusively linked to deratable surfaces of right type.
|
274
298
|
# If not, assign new lc clone to non-targeted surfaces.
|
@@ -287,7 +311,7 @@ module TBD
|
|
287
311
|
|
288
312
|
unless ok
|
289
313
|
log(WRN, "Cloning '#{nom}' construction - not '#{id}' (#{mth})")
|
290
|
-
sss
|
314
|
+
sss = model.getSurfaceByName(nom)
|
291
315
|
next if sss.empty?
|
292
316
|
|
293
317
|
sss = sss.get
|
@@ -299,18 +323,18 @@ module TBD
|
|
299
323
|
end
|
300
324
|
end
|
301
325
|
|
302
|
-
hloss
|
326
|
+
hloss = 0 # sum of applicable psi+khi-related losses [W/K]
|
303
327
|
|
304
|
-
# Tally applicable psi
|
328
|
+
# Tally applicable psi+khi losses. Possible construction reassignment.
|
305
329
|
coll.each do |i, col|
|
306
330
|
col[:s].keys.each do |nom|
|
307
331
|
next unless s.key?(nom)
|
308
332
|
next unless s[nom].key?(:construction)
|
309
|
-
next unless s[nom].key?(:index
|
310
|
-
next unless s[nom].key?(:ltype
|
311
|
-
next unless s[nom].key?(:r
|
333
|
+
next unless s[nom].key?(:index)
|
334
|
+
next unless s[nom].key?(:ltype)
|
335
|
+
next unless s[nom].key?(:r)
|
312
336
|
|
313
|
-
# Tally applicable psi
|
337
|
+
# Tally applicable psi+khi.
|
314
338
|
hloss += s[nom][:heatloss ] if s[nom].key?(:heatloss)
|
315
339
|
next if s[nom][:construction] == lc
|
316
340
|
|
@@ -321,7 +345,7 @@ module TBD
|
|
321
345
|
sss = sss.get
|
322
346
|
|
323
347
|
if sss.isConstructionDefaulted
|
324
|
-
set = defaultConstructionSet(
|
348
|
+
set = defaultConstructionSet(sss) # building? story?
|
325
349
|
constructions = set.defaultExteriorSurfaceConstructions
|
326
350
|
|
327
351
|
unless constructions.empty?
|
@@ -334,10 +358,10 @@ module TBD
|
|
334
358
|
sss.setConstruction(lc)
|
335
359
|
end
|
336
360
|
|
337
|
-
s[nom][:construction] = lc
|
361
|
+
s[nom][:construction] = lc # reset TBD attributes
|
338
362
|
s[nom][:index ] = lyr[:index]
|
339
363
|
s[nom][:ltype ] = lyr[:type ]
|
340
|
-
s[nom][:r ] = lyr[:r ]
|
364
|
+
s[nom][:r ] = lyr[:r ] # temporary
|
341
365
|
end
|
342
366
|
end
|
343
367
|
|
@@ -351,14 +375,19 @@ module TBD
|
|
351
375
|
end
|
352
376
|
|
353
377
|
coll.delete_if { |i, _| i != id }
|
354
|
-
log(DBG, "Collection == 1? for '#{id}' (#{mth})") unless coll.size == 1
|
355
|
-
next unless coll.size == 1
|
356
378
|
|
357
|
-
|
358
|
-
|
379
|
+
unless coll.size == 1
|
380
|
+
log(DBG, "Collection == 1? for '#{id}' (#{mth})")
|
381
|
+
next
|
382
|
+
end
|
383
|
+
|
384
|
+
coll[id][:area] = lc.getNetArea
|
359
385
|
res = uo(model, lc, id, hloss, film, g[:ut])
|
360
|
-
|
361
|
-
|
386
|
+
|
387
|
+
unless res[:uo] && res[:m]
|
388
|
+
log(ERR, "Unable to uprate '#{id}' (#{mth})")
|
389
|
+
next
|
390
|
+
end
|
362
391
|
|
363
392
|
lyr = insulatingLayer(lc)
|
364
393
|
|
@@ -371,7 +400,7 @@ module TBD
|
|
371
400
|
next unless s[nom][:index] == lyr[:index]
|
372
401
|
next unless s[nom][:ltype] == lyr[:type ]
|
373
402
|
|
374
|
-
s[nom][:r] = lyr[:r]
|
403
|
+
s[nom][:r] = lyr[:r] # uprated insulating RSi factor, before derating
|
375
404
|
end
|
376
405
|
|
377
406
|
argh[:wall_uo ] = res[:uo] if typ == :wall
|
@@ -387,40 +416,49 @@ module TBD
|
|
387
416
|
end
|
388
417
|
|
389
418
|
##
|
390
|
-
#
|
419
|
+
# Sets reference values for points, edges & surfaces (& subsurfaces) to
|
391
420
|
# compute Quebec energy code (Section 3.3) UA' comparison (2021).
|
392
421
|
#
|
393
|
-
# @param
|
422
|
+
# @param [Hash] s TBD surfaces (keys: Openstudio surface names)
|
423
|
+
# @option s [Bool] :deratable whether surface is deratable, s[][:deratable]
|
424
|
+
# @option s [:wall, :ceiling, :floor] :type TBD surface type
|
425
|
+
# @option s [#to_f] :heating applicable heating setpoint temperature in °C
|
426
|
+
# @option s [#to_f] :cooling applicable cooling setpoint temperature in °C
|
427
|
+
# @option s [Hash] :windows TBD surface-specific windows e.g. s[][:windows]
|
428
|
+
# @option s [Hash] :doors TBD surface-specific doors
|
429
|
+
# @option s [Hash] :skylights TBD surface-specific skylights
|
430
|
+
# @option s [Hash] :pts point thermal bridges, e.g. s[][:pts] see KHI class
|
431
|
+
# @option s [Hash] :edges TBD edges (keys: Topolys edge identifiers)
|
394
432
|
# @param sets [TBD::PSI] a TBD model's PSI sets
|
395
|
-
# @param spts [Bool]
|
433
|
+
# @param spts [Bool] whether OpenStudio model holds heating/cooling setpoints
|
396
434
|
#
|
397
|
-
# @return [Bool]
|
435
|
+
# @return [Bool] whether successful in generating UA' reference values
|
436
|
+
# @return [false] if invalid inputs (see logs)
|
398
437
|
def qc33(s = {}, sets = nil, spts = true)
|
399
438
|
mth = "TBD::#{__callee__}"
|
400
439
|
cl1 = Hash
|
401
440
|
cl2 = TBD::PSI
|
402
|
-
|
403
|
-
return mismatch("
|
404
|
-
return mismatch("sets", sets, cl1, mth, DBG, false) unless sets.is_a?(cl2)
|
441
|
+
return mismatch("surfaces", s, cl1, mth, DBG, false) unless s.is_a?(cl1)
|
442
|
+
return mismatch("sets", sets, cl1, mth, DBG, false) unless sets.is_a?(cl2)
|
405
443
|
|
406
444
|
shorts = sets.shorthands("code (Quebec)")
|
407
|
-
empty
|
408
|
-
log(DBG, "Missing QC PSI set for 3.3 UA' tradeoff (#{mth})")
|
409
|
-
return false
|
445
|
+
empty = shorts[:has].empty? || shorts[:val].empty?
|
446
|
+
log(DBG, "Missing QC PSI set for 3.3 UA' tradeoff (#{mth})") if empty
|
447
|
+
return false if empty
|
410
448
|
|
411
|
-
ok =
|
412
|
-
log(DBG, "
|
413
|
-
return false
|
449
|
+
ok = [true, false].include?(spts)
|
450
|
+
log(DBG, "setpoints must be true or false for 3.3 UA' tradeoff") unless ok
|
451
|
+
return false unless ok
|
414
452
|
|
415
453
|
s.each do |id, surface|
|
416
454
|
next unless surface.key?(:deratable)
|
417
455
|
next unless surface[:deratable]
|
418
456
|
next unless surface.key?(:type)
|
419
457
|
|
420
|
-
heating = -50
|
421
|
-
cooling = 50
|
422
|
-
heating = 21
|
423
|
-
cooling = 24
|
458
|
+
heating = -50 if spts
|
459
|
+
cooling = 50 if spts
|
460
|
+
heating = 21 unless spts
|
461
|
+
cooling = 24 unless spts
|
424
462
|
heating = surface[:heating] if surface.key?(:heating)
|
425
463
|
cooling = surface[:cooling] if surface.key?(:cooling)
|
426
464
|
|
@@ -429,18 +467,21 @@ module TBD
|
|
429
467
|
ref = 1 / 3.60 if surface[:type] == :wall
|
430
468
|
|
431
469
|
# Adjust for lower heating setpoint (assumes -25°C design conditions).
|
432
|
-
ref *= 43 / (heating + 25)
|
433
|
-
|
470
|
+
ref *= 43 / (heating + 25) if heating < 18 && cooling > 40
|
471
|
+
|
472
|
+
surface[:ref] = ref
|
434
473
|
|
435
|
-
if surface.key?(:skylights)
|
474
|
+
if surface.key?(:skylights) # loop through subsurfaces
|
436
475
|
ref = 2.85
|
437
|
-
ref *= 43 / (heating + 25)
|
476
|
+
ref *= 43 / (heating + 25) if heating < 18 && cooling > 40
|
477
|
+
|
438
478
|
surface[:skylights].values.map { |skylight| skylight[:ref] = ref }
|
439
479
|
end
|
440
480
|
|
441
481
|
if surface.key?(:windows)
|
442
482
|
ref = 2.0
|
443
|
-
ref *= 43 / (heating + 25)
|
483
|
+
ref *= 43 / (heating + 25) if heating < 18 && cooling > 40
|
484
|
+
|
444
485
|
surface[:windows].values.map { |window| window[:ref] = ref }
|
445
486
|
end
|
446
487
|
|
@@ -448,21 +489,22 @@ module TBD
|
|
448
489
|
surface[:doors].each do |i, door|
|
449
490
|
ref = 0.9
|
450
491
|
ref = 2.0 if door.key?(:glazed) && door[:glazed]
|
451
|
-
ref *= 43 / (heating + 25)
|
492
|
+
ref *= 43 / (heating + 25) if heating < 18 && cooling > 40
|
452
493
|
door[:ref] = ref
|
453
494
|
end
|
454
495
|
end
|
455
496
|
|
456
497
|
# Loop through point thermal bridges.
|
457
|
-
surface[:pts].map { |i, pt| pt[:ref] = 0.5 }
|
498
|
+
surface[:pts].map { |i, pt| pt[:ref] = 0.5 } if surface.key?(:pts)
|
458
499
|
|
459
500
|
# Loop through linear thermal bridges.
|
460
501
|
if surface.key?(:edges)
|
461
502
|
surface[:edges].values.each do |edge|
|
462
503
|
next unless edge.key?(:type)
|
463
504
|
next unless edge.key?(:ratio)
|
505
|
+
|
464
506
|
safe = sets.safe("code (Quebec)", edge[:type])
|
465
|
-
edge[:ref] = shorts[:val][safe] * edge[:ratio]
|
507
|
+
edge[:ref] = shorts[:val][safe] * edge[:ratio] if safe
|
466
508
|
end
|
467
509
|
end
|
468
510
|
end
|
@@ -471,52 +513,67 @@ module TBD
|
|
471
513
|
end
|
472
514
|
|
473
515
|
##
|
474
|
-
#
|
516
|
+
# Generates multilingual UA' summary.
|
475
517
|
#
|
476
518
|
# @param date [Time] Time stamp
|
477
|
-
# @param
|
519
|
+
# @param [Hash] argh TBD arguments
|
520
|
+
# @option argh [#to_s] :seed OpenStudio file, e.g. "school23.osm"
|
521
|
+
# @option argh [#to_s] :ua_ref reference ruleset e.g. "code (Quebec)"
|
522
|
+
# @option argh [Hash] :surfaces set of TBD surfaces (see )
|
523
|
+
# @option argh [#to_s] :version OpenStudio SDK, e.g. "3.6.1"
|
524
|
+
# @option argh [Hash] :io TBD input/output variables (see TBD JSON schema)
|
478
525
|
#
|
479
|
-
# @return [Hash]
|
526
|
+
# @return [Hash] binned values for UA' (see logs if empty)
|
480
527
|
def ua_summary(date = Time.now, argh = {})
|
481
528
|
mth = "TBD::#{__callee__}"
|
529
|
+
cl1 = Time
|
530
|
+
cl2 = String
|
531
|
+
cl3 = Hash
|
532
|
+
ua = {}
|
533
|
+
return mismatch("date", date, cl1, mth, DBG, ua) unless date.is_a?(cl1)
|
534
|
+
return mismatch("argh", argh, cl3, mth, DBG, ua) unless argh.is_a?(cl3)
|
535
|
+
|
536
|
+
argh[:seed ] = "" unless argh.key?(:seed)
|
537
|
+
argh[:ua_ref ] = "" unless argh.key?(:ua_ref)
|
538
|
+
argh[:surfaces] = nil unless argh.key?(:surfaces)
|
539
|
+
argh[:version ] = "" unless argh.key?(:version)
|
540
|
+
argh[:io ] = {} unless argh.key?(:io)
|
541
|
+
|
542
|
+
file = argh[:seed ]
|
543
|
+
ref = argh[:ua_ref ]
|
544
|
+
s = argh[:surfaces]
|
545
|
+
v = argh[:version ]
|
546
|
+
io = argh[:io ]
|
547
|
+
return mismatch( "seed", file, cl2, mth, DBG, ua) unless file.is_a?(cl2)
|
548
|
+
return mismatch( "UA' ref", ref, cl2, mth, DBG, ua) unless ref.is_a?(cl2)
|
549
|
+
return mismatch( "version", v, cl2, mth, DBG, ua) unless v.is_a?(cl2)
|
550
|
+
return mismatch("surfaces", s, cl3, mth, DBG, ua) unless s.is_a?(cl3)
|
551
|
+
return mismatch( "io", io, cl3, mth, DBG, ua) unless io.is_a?(cl3)
|
552
|
+
return empty( "surfaces", mth, WRN, ua) if s.empty?
|
482
553
|
|
483
|
-
ua = {}
|
484
|
-
argh = {} unless argh.is_a?(Hash )
|
485
|
-
argh[:seed ] = "" unless argh.key?(:seed )
|
486
|
-
argh[:ua_ref ] = "" unless argh.key?(:ua_ref )
|
487
|
-
argh[:surfaces ] = nil unless argh.key?(:surfaces )
|
488
|
-
argh[:version ] = "" unless argh.key?(:version )
|
489
|
-
argh[:io ] = {} unless argh.key?(:io )
|
490
554
|
argh[:io][:description] = "" unless argh[:io].key?(:description)
|
491
|
-
|
492
|
-
descr = argh[:io][:description]
|
493
|
-
file = argh[:seed ]
|
494
|
-
version = argh[:version ]
|
495
|
-
s = argh[:surfaces ]
|
496
|
-
|
497
|
-
return mismatch("TBD surfaces", s, Hash, mth, DBG, ua) unless s.is_a?(Hash)
|
498
|
-
return empty("TBD Surfaces", mth, WRN, ua) if s.empty?
|
555
|
+
descr = argh[:io][:description]
|
499
556
|
|
500
557
|
ua[:descr ] = ""
|
501
558
|
ua[:file ] = ""
|
502
559
|
ua[:version] = ""
|
503
560
|
ua[:model ] = "∑U•A + ∑PSI•L + ∑KHI•n"
|
504
561
|
ua[:date ] = date
|
505
|
-
ua[:descr ] = descr
|
506
|
-
ua[:file ] = file
|
507
|
-
ua[:version] =
|
562
|
+
ua[:descr ] = descr unless descr.nil? || descr.empty?
|
563
|
+
ua[:file ] = file unless file.nil? || file.empty?
|
564
|
+
ua[:version] = v unless v.nil? || v.empty?
|
508
565
|
|
509
566
|
[:en, :fr].each { |lang| ua[lang] = {} }
|
510
567
|
|
511
|
-
ua[:en][:notes] = "Automated assessment from the OpenStudio Measure, "
|
512
|
-
"Thermal Bridging and Derating (TBD). Open source and MIT-licensed, "
|
513
|
-
"TBD is provided as is (without warranty). Procedures are documented "
|
568
|
+
ua[:en][:notes] = "Automated assessment from the OpenStudio Measure, "\
|
569
|
+
"Thermal Bridging and Derating (TBD). Open source and MIT-licensed, "\
|
570
|
+
"TBD is provided as is (without warranty). Procedures are documented "\
|
514
571
|
"in the source code: https://github.com/rd2/tbd. "
|
515
572
|
|
516
|
-
ua[:fr][:notes] = "Analyse automatisée à partir de la measure
|
517
|
-
"'Thermal Bridging and Derating' (ou TBD). Distribuée
|
518
|
-
"(licence MIT), TBD est offerte telle quelle (sans
|
519
|
-
"L'approche est documentée au sein du code source : "
|
573
|
+
ua[:fr][:notes] = "Analyse automatisée à partir de la measure "\
|
574
|
+
"OpenStudio, 'Thermal Bridging and Derating' (ou TBD). Distribuée "\
|
575
|
+
"librement (licence MIT), TBD est offerte telle quelle (sans "\
|
576
|
+
"garantie). L'approche est documentée au sein du code source : "\
|
520
577
|
"https://github.com/rd2/tbd."
|
521
578
|
|
522
579
|
walls = { net: 0, gross: 0, subs: 0 }
|
@@ -527,17 +584,17 @@ module TBD
|
|
527
584
|
val = {}
|
528
585
|
psi = PSI.new
|
529
586
|
|
530
|
-
unless
|
531
|
-
shorts = psi.shorthands(
|
587
|
+
unless ref.empty?
|
588
|
+
shorts = psi.shorthands(ref)
|
532
589
|
empty = shorts[:has].empty? && shorts[:val].empty?
|
533
|
-
has = shorts[:has]
|
534
|
-
val = shorts[:val]
|
535
|
-
log(ERR, "Invalid UA' reference set (#{mth})")
|
590
|
+
has = shorts[:has] unless empty
|
591
|
+
val = shorts[:val] unless empty
|
592
|
+
log(ERR, "Invalid UA' reference set (#{mth})") if empty
|
536
593
|
|
537
594
|
unless empty
|
538
|
-
ua[:model] += " : Design vs '#{
|
595
|
+
ua[:model] += " : Design vs '#{ref}'"
|
539
596
|
|
540
|
-
case
|
597
|
+
case ref
|
541
598
|
when "code (Quebec)"
|
542
599
|
ua[:en][:objective] = "COMPLIANCE ASSESSMENT"
|
543
600
|
ua[:en][:details ] = []
|
@@ -546,9 +603,9 @@ module TBD
|
|
546
603
|
ua[:en][:details ] << "Division B, Section 3.3"
|
547
604
|
ua[:en][:details ] << "Building Envelope Trade-off Path"
|
548
605
|
|
549
|
-
ua[:en][:notes] << " Calculations comply with Section 3.3 "
|
550
|
-
"requirements. Results are based on user input not subject to "
|
551
|
-
"prior validation (see DESCRIPTION), and as such the assessment "
|
606
|
+
ua[:en][:notes] << " Calculations comply with Section 3.3 "\
|
607
|
+
"requirements. Results are based on user input not subject to "\
|
608
|
+
"prior validation (see DESCRIPTION), and as such the assessment "\
|
552
609
|
"shall not be considered as a certification of compliance."
|
553
610
|
|
554
611
|
ua[:fr][:objective] = "ANALYSE DE CONFORMITÉ"
|
@@ -558,10 +615,10 @@ module TBD
|
|
558
615
|
ua[:fr][:details ] << "Division B, Section 3.3"
|
559
616
|
ua[:fr][:details ] << "Méthode des solutions de remplacement"
|
560
617
|
|
561
|
-
ua[:fr][:notes] << " Les calculs sont conformes aux dispositions
|
562
|
-
"la Section 3.3. Les résultats sont tributaires d'intrants "
|
563
|
-
"fournis par l'utilisateur, sans validation préalable (voir "
|
564
|
-
"DESCRIPTION). Ce document ne peut constituer une attestation de "
|
618
|
+
ua[:fr][:notes] << " Les calculs sont conformes aux dispositions "\
|
619
|
+
"de la Section 3.3. Les résultats sont tributaires d'intrants "\
|
620
|
+
"fournis par l'utilisateur, sans validation préalable (voir "\
|
621
|
+
"DESCRIPTION). Ce document ne peut constituer une attestation de "\
|
565
622
|
"conformité."
|
566
623
|
else
|
567
624
|
ua[:en][:objective] = "UA'"
|
@@ -582,26 +639,28 @@ module TBD
|
|
582
639
|
blc = { walls: 0, roofs: 0, floors: 0, doors: 0,
|
583
640
|
windows: 0, skylights: 0, rimjoists: 0, parapets: 0,
|
584
641
|
trim: 0, corners: 0, balconies: 0, grade: 0,
|
585
|
-
other: 0 } #
|
642
|
+
other: 0 } # party edges, expansion joints, spandrel edges, etc.
|
586
643
|
|
587
644
|
b1 = {}
|
588
645
|
b2 = {}
|
589
|
-
b1[:pro] = blc
|
590
|
-
b1[:ref] = blc.clone
|
591
|
-
b2[:pro] = blc.clone
|
592
|
-
b2[:ref] = blc.clone
|
646
|
+
b1[:pro] = blc # proposed design
|
647
|
+
b1[:ref] = blc.clone # reference
|
648
|
+
b2[:pro] = blc.clone # proposed design
|
649
|
+
b2[:ref] = blc.clone # reference
|
593
650
|
|
594
651
|
# Loop through surfaces, subsurfaces and edges and populate bloc1 & bloc2.
|
595
|
-
|
652
|
+
s.each do |id, surface|
|
596
653
|
next unless surface.key?(:deratable)
|
597
654
|
next unless surface[:deratable]
|
598
655
|
next unless surface.key?(:type)
|
656
|
+
|
599
657
|
type = surface[:type]
|
600
|
-
next unless
|
658
|
+
next unless [:wall, :ceiling, :floor].include?(type)
|
601
659
|
next unless surface.key?(:net)
|
602
660
|
next unless surface[:net] > TOL
|
603
661
|
next unless surface.key?(:u)
|
604
662
|
next unless surface[:u] > TOL
|
663
|
+
|
605
664
|
heating = 21.0
|
606
665
|
heating = surface[:heating] if surface.key?(:heating)
|
607
666
|
bloc = b1
|
@@ -611,18 +670,18 @@ module TBD
|
|
611
670
|
if type == :wall
|
612
671
|
areas[:walls][:net ] += surface[:net]
|
613
672
|
bloc[:pro][:walls ] += surface[:net] * surface[:u ]
|
614
|
-
bloc[:ref][:walls ] += surface[:net] * surface[:ref]
|
615
|
-
bloc[:ref][:walls ] += surface[:net] * surface[:u ]
|
673
|
+
bloc[:ref][:walls ] += surface[:net] * surface[:ref] if reference
|
674
|
+
bloc[:ref][:walls ] += surface[:net] * surface[:u ] unless reference
|
616
675
|
elsif type == :ceiling
|
617
676
|
areas[:roofs][:net ] += surface[:net]
|
618
677
|
bloc[:pro][:roofs ] += surface[:net] * surface[:u ]
|
619
|
-
bloc[:ref][:roofs ] += surface[:net] * surface[:ref]
|
620
|
-
bloc[:ref][:roofs ] += surface[:net] * surface[:u ]
|
678
|
+
bloc[:ref][:roofs ] += surface[:net] * surface[:ref] if reference
|
679
|
+
bloc[:ref][:roofs ] += surface[:net] * surface[:u ] unless reference
|
621
680
|
else
|
622
681
|
areas[:floors][:net] += surface[:net]
|
623
682
|
bloc[:pro][:floors ] += surface[:net] * surface[:u ]
|
624
|
-
bloc[:ref][:floors ] += surface[:net] * surface[:ref]
|
625
|
-
bloc[:ref][:floors ] += surface[:net] * surface[:u ]
|
683
|
+
bloc[:ref][:floors ] += surface[:net] * surface[:ref] if reference
|
684
|
+
bloc[:ref][:floors ] += surface[:net] * surface[:u ] unless reference
|
626
685
|
end
|
627
686
|
|
628
687
|
[:doors, :windows, :skylights].each do |subs|
|
@@ -635,13 +694,13 @@ module TBD
|
|
635
694
|
next unless sub[:u ] > TOL
|
636
695
|
|
637
696
|
gross = sub[:gross]
|
638
|
-
gross *= sub[:mult ]
|
639
|
-
areas[:walls ][:subs] += gross
|
640
|
-
areas[:roofs ][:subs] += gross
|
641
|
-
areas[:floors][:subs] += gross
|
697
|
+
gross *= sub[:mult ] if sub.key?(:mult)
|
698
|
+
areas[:walls ][:subs] += gross if type == :wall
|
699
|
+
areas[:roofs ][:subs] += gross if type == :ceiling
|
700
|
+
areas[:floors][:subs] += gross if type == :floor
|
642
701
|
bloc[:pro ][subs ] += gross * sub[:u ]
|
643
|
-
bloc[:ref ][subs ] += gross * sub[:ref]
|
644
|
-
bloc[:ref ][subs ] += gross * sub[:u ]
|
702
|
+
bloc[:ref ][subs ] += gross * sub[:ref] if sub.key?(:ref)
|
703
|
+
bloc[:ref ][subs ] += gross * sub[:u ] unless sub.key?(:ref)
|
645
704
|
end
|
646
705
|
end
|
647
706
|
|
@@ -653,52 +712,67 @@ module TBD
|
|
653
712
|
next unless edge.key?(:psi)
|
654
713
|
|
655
714
|
loss = edge[:length] * edge[:psi]
|
656
|
-
type = edge[:type].to_s
|
715
|
+
type = edge[:type].to_s.downcase
|
657
716
|
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
when /fenestration/i
|
717
|
+
if edge[:type].to_s.downcase.include?("balcony")
|
718
|
+
bloc[:pro][:balconies] += loss
|
719
|
+
elsif edge[:type].to_s.downcase.include?("door")
|
720
|
+
bloc[:pro][:trim ] += loss
|
721
|
+
elsif edge[:type].to_s.downcase.include?("skylight")
|
664
722
|
bloc[:pro][:trim ] += loss
|
665
|
-
|
723
|
+
elsif edge[:type].to_s.downcase.include?("fenestration")
|
666
724
|
bloc[:pro][:trim ] += loss
|
667
|
-
|
725
|
+
elsif edge[:type].to_s.downcase.include?("head")
|
668
726
|
bloc[:pro][:trim ] += loss
|
669
|
-
|
727
|
+
elsif edge[:type].to_s.downcase.include?("sill")
|
670
728
|
bloc[:pro][:trim ] += loss
|
671
|
-
|
729
|
+
elsif edge[:type].to_s.downcase.include?("jamb")
|
730
|
+
bloc[:pro][:trim ] += loss
|
731
|
+
elsif edge[:type].to_s.downcase.include?("rimjoist")
|
732
|
+
bloc[:pro][:rimjoists] += loss
|
733
|
+
elsif edge[:type].to_s.downcase.include?("parapet")
|
734
|
+
bloc[:pro][:parapets ] += loss
|
735
|
+
elsif edge[:type].to_s.downcase.include?("roof")
|
736
|
+
bloc[:pro][:parapets ] += loss
|
737
|
+
elsif edge[:type].to_s.downcase.include?("corner")
|
672
738
|
bloc[:pro][:corners ] += loss
|
673
|
-
|
739
|
+
elsif edge[:type].to_s.downcase.include?("grade")
|
674
740
|
bloc[:pro][:grade ] += loss
|
675
741
|
else
|
676
742
|
bloc[:pro][:other ] += loss
|
677
743
|
end
|
678
744
|
|
679
745
|
next if val.empty?
|
680
|
-
next if
|
681
|
-
|
746
|
+
next if ref.empty?
|
747
|
+
|
748
|
+
safer = psi.safe(ref, edge[:type])
|
682
749
|
ok = edge.key?(:ref)
|
683
|
-
loss = edge[:length] * edge[:ref]
|
684
|
-
loss = edge[:length] * val[safer] * edge[:ratio]
|
750
|
+
loss = edge[:length] * edge[:ref] if ok
|
751
|
+
loss = edge[:length] * val[safer] * edge[:ratio] unless ok
|
685
752
|
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
when /parapet/i
|
690
|
-
bloc[:ref][:parapets ] += loss
|
691
|
-
when /fenestration/i
|
753
|
+
if edge[:type].to_s.downcase.include?("balcony")
|
754
|
+
bloc[:ref][:balconies] += loss
|
755
|
+
elsif edge[:type].to_s.downcase.include?("door")
|
692
756
|
bloc[:ref][:trim ] += loss
|
693
|
-
|
757
|
+
elsif edge[:type].to_s.downcase.include?("skylight")
|
694
758
|
bloc[:ref][:trim ] += loss
|
695
|
-
|
759
|
+
elsif edge[:type].to_s.downcase.include?("fenestration")
|
696
760
|
bloc[:ref][:trim ] += loss
|
697
|
-
|
761
|
+
elsif edge[:type].to_s.downcase.include?("head")
|
698
762
|
bloc[:ref][:trim ] += loss
|
699
|
-
|
763
|
+
elsif edge[:type].to_s.downcase.include?("sill")
|
764
|
+
bloc[:ref][:trim ] += loss
|
765
|
+
elsif edge[:type].to_s.downcase.include?("jamb")
|
766
|
+
bloc[:ref][:trim ] += loss
|
767
|
+
elsif edge[:type].to_s.downcase.include?("rimjoist")
|
768
|
+
bloc[:ref][:rimjoists] += loss
|
769
|
+
elsif edge[:type].to_s.downcase.include?("parapet")
|
770
|
+
bloc[:ref][:parapets ] += loss
|
771
|
+
elsif edge[:type].to_s.downcase.include?("roof")
|
772
|
+
bloc[:ref][:parapets ] += loss
|
773
|
+
elsif edge[:type].to_s.downcase.include?("corner")
|
700
774
|
bloc[:ref][:corners ] += loss
|
701
|
-
|
775
|
+
elsif edge[:type].to_s.downcase.include?("grade")
|
702
776
|
bloc[:ref][:grade ] += loss
|
703
777
|
else
|
704
778
|
bloc[:ref][:other ] += loss
|
@@ -710,8 +784,10 @@ module TBD
|
|
710
784
|
surface[:pts].values.each do |pts|
|
711
785
|
next unless pts.key?(:val)
|
712
786
|
next unless pts.key?(:n)
|
787
|
+
|
713
788
|
bloc[:pro][:other] += pts[:val] * pts[:n]
|
714
789
|
next unless pts.key?(:ref)
|
790
|
+
|
715
791
|
bloc[:ref][:other] += pts[:ref] * pts[:n]
|
716
792
|
end
|
717
793
|
end
|
@@ -735,97 +811,69 @@ module TBD
|
|
735
811
|
ua[lang][b] = {}
|
736
812
|
|
737
813
|
if b == :b1
|
738
|
-
ua[:en][b][:summary] = "heated : #{str}"
|
739
|
-
ua[:fr][b][:summary] = "chauffé : #{str}"
|
814
|
+
ua[:en][b][:summary] = "heated : #{str}" if lang == :en
|
815
|
+
ua[:fr][b][:summary] = "chauffé : #{str}" if lang == :fr
|
740
816
|
else
|
741
|
-
ua[:en][b][:summary] = "semi-heated : #{str}"
|
742
|
-
ua[:fr][b][:summary] = "semi-chauffé : #{str}"
|
817
|
+
ua[:en][b][:summary] = "semi-heated : #{str}" if lang == :en
|
818
|
+
ua[:fr][b][:summary] = "semi-chauffé : #{str}" if lang == :fr
|
743
819
|
end
|
744
820
|
|
745
|
-
# ** https://bugs.ruby-lang.org/issues/13761 (Ruby > 2.2.5)
|
746
|
-
# str += format(" +%.1f%", ratio) if ratio && pro_sum > ref_sum ... now:
|
747
|
-
# str += format(" +%.1f%%", ratio) if ratio && pro_sum > ref_sum
|
748
|
-
|
749
821
|
bloc[:pro].each do |k, v|
|
750
822
|
rf = bloc[:ref][k]
|
751
823
|
next if v < TOL && rf < TOL
|
752
824
|
ratio = nil
|
753
|
-
ratio = (100.0 * (v - rf) / rf).abs
|
825
|
+
ratio = (100.0 * (v - rf) / rf).abs if rf > TOL
|
754
826
|
str = format("%.1f W/K (vs %.1f W/K)", v, rf)
|
755
|
-
str += format(" +%.1f%%", ratio)
|
756
|
-
str += format(" -%.1f%%", ratio)
|
827
|
+
str += format(" +%.1f%%", ratio) if ratio && v > rf
|
828
|
+
str += format(" -%.1f%%", ratio) if ratio && v < rf
|
757
829
|
|
758
830
|
case k
|
759
831
|
when :walls
|
760
|
-
ua[:en][b][k] = "walls : #{str}"
|
761
|
-
ua[:fr][b][k] = "murs : #{str}"
|
832
|
+
ua[:en][b][k] = "walls : #{str}" if lang == :en
|
833
|
+
ua[:fr][b][k] = "murs : #{str}" if lang == :fr
|
762
834
|
when :roofs
|
763
|
-
ua[:en][b][k] = "roofs : #{str}"
|
764
|
-
ua[:fr][b][k] = "toits : #{str}"
|
835
|
+
ua[:en][b][k] = "roofs : #{str}" if lang == :en
|
836
|
+
ua[:fr][b][k] = "toits : #{str}" if lang == :fr
|
765
837
|
when :floors
|
766
|
-
ua[:en][b][k] = "floors : #{str}"
|
767
|
-
ua[:fr][b][k] = "planchers : #{str}"
|
838
|
+
ua[:en][b][k] = "floors : #{str}" if lang == :en
|
839
|
+
ua[:fr][b][k] = "planchers : #{str}" if lang == :fr
|
768
840
|
when :doors
|
769
|
-
ua[:en][b][k] = "doors : #{str}"
|
770
|
-
ua[:fr][b][k] = "portes : #{str}"
|
841
|
+
ua[:en][b][k] = "doors : #{str}" if lang == :en
|
842
|
+
ua[:fr][b][k] = "portes : #{str}" if lang == :fr
|
771
843
|
when :windows
|
772
|
-
ua[:en][b][k] = "windows : #{str}"
|
773
|
-
ua[:fr][b][k] = "fenêtres : #{str}"
|
844
|
+
ua[:en][b][k] = "windows : #{str}" if lang == :en
|
845
|
+
ua[:fr][b][k] = "fenêtres : #{str}" if lang == :fr
|
774
846
|
when :skylights
|
775
|
-
ua[:en][b][k] = "skylights : #{str}"
|
776
|
-
ua[:fr][b][k] = "lanterneaux : #{str}"
|
847
|
+
ua[:en][b][k] = "skylights : #{str}" if lang == :en
|
848
|
+
ua[:fr][b][k] = "lanterneaux : #{str}" if lang == :fr
|
777
849
|
when :rimjoists
|
778
|
-
ua[:en][b][k] = "rimjoists : #{str}"
|
779
|
-
ua[:fr][b][k] = "rives : #{str}"
|
850
|
+
ua[:en][b][k] = "rimjoists : #{str}" if lang == :en
|
851
|
+
ua[:fr][b][k] = "rives : #{str}" if lang == :fr
|
780
852
|
when :parapets
|
781
|
-
ua[:en][b][k] = "parapets : #{str}"
|
782
|
-
ua[:fr][b][k] = "parapets : #{str}"
|
853
|
+
ua[:en][b][k] = "parapets : #{str}" if lang == :en
|
854
|
+
ua[:fr][b][k] = "parapets : #{str}" if lang == :fr
|
783
855
|
when :trim
|
784
|
-
ua[:en][b][k] = "trim : #{str}"
|
785
|
-
ua[:fr][b][k] = "chassis : #{str}"
|
856
|
+
ua[:en][b][k] = "trim : #{str}" if lang == :en
|
857
|
+
ua[:fr][b][k] = "chassis : #{str}" if lang == :fr
|
786
858
|
when :corners
|
787
|
-
ua[:en][b][k] = "corners : #{str}"
|
788
|
-
ua[:fr][b][k] = "coins : #{str}"
|
859
|
+
ua[:en][b][k] = "corners : #{str}" if lang == :en
|
860
|
+
ua[:fr][b][k] = "coins : #{str}" if lang == :fr
|
789
861
|
when :balconies
|
790
|
-
ua[:en][b][k] = "balconies : #{str}"
|
791
|
-
ua[:fr][b][k] = "balcons : #{str}"
|
862
|
+
ua[:en][b][k] = "balconies : #{str}" if lang == :en
|
863
|
+
ua[:fr][b][k] = "balcons : #{str}" if lang == :fr
|
792
864
|
when :grade
|
793
|
-
ua[:en][b][k] = "grade : #{str}"
|
794
|
-
ua[:fr][b][k] = "tracé : #{str}"
|
865
|
+
ua[:en][b][k] = "grade : #{str}" if lang == :en
|
866
|
+
ua[:fr][b][k] = "tracé : #{str}" if lang == :fr
|
795
867
|
else
|
796
|
-
ua[:en][b][k] = "other : #{str}"
|
797
|
-
ua[:fr][b][k] = "autres : #{str}"
|
868
|
+
ua[:en][b][k] = "other : #{str}" if lang == :en
|
869
|
+
ua[:fr][b][k] = "autres : #{str}" if lang == :fr
|
798
870
|
end
|
799
871
|
end
|
800
872
|
|
801
|
-
# Deterministic sorting
|
873
|
+
# Deterministic sorting.
|
802
874
|
ua[lang][b][:summary] = ua[lang][b].delete(:summary)
|
803
|
-
|
804
|
-
ua[lang][b][
|
805
|
-
ok = ua[lang][b].key?(:roofs)
|
806
|
-
ua[lang][b][:roofs] = ua[lang][b].delete(:roofs) if ok
|
807
|
-
ok = ua[lang][b].key?(:floors)
|
808
|
-
ua[lang][b][:floors] = ua[lang][b].delete(:floors) if ok
|
809
|
-
ok = ua[lang][b].key?(:doors)
|
810
|
-
ua[lang][b][:doors] = ua[lang][b].delete(:doors) if ok
|
811
|
-
ok = ua[lang][b].key?(:windows)
|
812
|
-
ua[lang][b][:windows] = ua[lang][b].delete(:windows) if ok
|
813
|
-
ok = ua[lang][b].key?(:skylights)
|
814
|
-
ua[lang][b][:skylights] = ua[lang][b].delete(:skylights) if ok
|
815
|
-
ok = ua[lang][b].key?(:rimjoists)
|
816
|
-
ua[lang][b][:rimjoists] = ua[lang][b].delete(:rimjoists) if ok
|
817
|
-
ok = ua[lang][b].key?(:parapets)
|
818
|
-
ua[lang][b][:parapets] = ua[lang][b].delete(:parapets) if ok
|
819
|
-
ok = ua[lang][b].key?(:trim)
|
820
|
-
ua[lang][b][:trim] = ua[lang][b].delete(:trim) if ok
|
821
|
-
ok = ua[lang][b].key?(:corners)
|
822
|
-
ua[lang][b][:corners] = ua[lang][b].delete(:corners) if ok
|
823
|
-
ok = ua[lang][b].key?(:balconies)
|
824
|
-
ua[lang][b][:balconies] = ua[lang][b].delete(:balconies) if ok
|
825
|
-
ok = ua[lang][b].key?(:grade)
|
826
|
-
ua[lang][b][:grade] = ua[lang][b].delete(:grade) if ok
|
827
|
-
ok = ua[lang][b].key?(:other)
|
828
|
-
ua[lang][b][:other] = ua[lang][b].delete(:other) if ok
|
875
|
+
|
876
|
+
ua[lang][b].keys.each { |k| ua[lang][b][k] = ua[lang][b].delete(k) }
|
829
877
|
end
|
830
878
|
end
|
831
879
|
end
|
@@ -834,62 +882,85 @@ module TBD
|
|
834
882
|
areas[:walls ][:gross] = areas[:walls ][:net] + areas[:walls ][:subs]
|
835
883
|
areas[:roofs ][:gross] = areas[:roofs ][:net] + areas[:roofs ][:subs]
|
836
884
|
areas[:floors][:gross] = areas[:floors][:net] + areas[:floors][:subs]
|
885
|
+
|
837
886
|
ua[:en][:areas] = {}
|
838
887
|
ua[:fr][:areas] = {}
|
839
888
|
|
840
889
|
str = format("walls : %.1f m2 (net)", areas[:walls][:net])
|
841
890
|
str += format(", %.1f m2 (gross)", areas[:walls][:gross])
|
842
|
-
ua[:en][:areas][:walls]
|
891
|
+
ua[:en][:areas][:walls] = str unless areas[:walls ][:gross] < TOL
|
892
|
+
|
843
893
|
str = format("roofs : %.1f m2 (net)", areas[:roofs][:net])
|
844
894
|
str += format(", %.1f m2 (gross)", areas[:roofs][:gross])
|
845
|
-
ua[:en][:areas][:roofs]
|
895
|
+
ua[:en][:areas][:roofs] = str unless areas[:roofs ][:gross] < TOL
|
896
|
+
|
846
897
|
str = format("floors : %.1f m2 (net)", areas[:floors][:net])
|
847
898
|
str += format(", %.1f m2 (gross)", areas[:floors][:gross])
|
848
|
-
ua[:en][:areas][:floors] = str
|
899
|
+
ua[:en][:areas][:floors] = str unless areas[:floors][:gross] < TOL
|
849
900
|
|
850
901
|
str = format("murs : %.1f m2 (net)", areas[:walls][:net])
|
851
902
|
str += format(", %.1f m2 (brut)", areas[:walls][:gross])
|
852
|
-
ua[:fr][:areas][:walls]
|
903
|
+
ua[:fr][:areas][:walls] = str unless areas[:walls ][:gross] < TOL
|
904
|
+
|
853
905
|
str = format("toits : %.1f m2 (net)", areas[:roofs][:net])
|
854
906
|
str += format(", %.1f m2 (brut)", areas[:roofs][:gross])
|
855
|
-
ua[:fr][:areas][:roofs]
|
907
|
+
ua[:fr][:areas][:roofs] = str unless areas[:roofs ][:gross] < TOL
|
908
|
+
|
856
909
|
str = format("planchers : %.1f m2 (net)", areas[:floors][:net])
|
857
910
|
str += format(", %.1f m2 (brut)", areas[:floors][:gross])
|
858
|
-
ua[:fr][:areas][:floors] = str
|
911
|
+
ua[:fr][:areas][:floors] = str unless areas[:floors][:gross] < TOL
|
859
912
|
|
860
913
|
ua
|
861
914
|
end
|
862
915
|
|
863
916
|
##
|
864
|
-
#
|
917
|
+
# Generates MD-formatted, UA' summary file.
|
865
918
|
#
|
866
|
-
# @param
|
867
|
-
#
|
919
|
+
# @param [#key?] ua preprocessed collection of UA-related strings
|
920
|
+
# option ua [#to_s] :objective ua[lang][:objective] = "COMPLIANCE [...]"
|
921
|
+
# option ua [#&] :details ua[lang][:details] = "QC Energy Code [...]"
|
922
|
+
# option ua [#to_s] :model "∑U•A + ∑PSI•L + ∑KHI•n [...]"
|
923
|
+
# option ua [#key?] :b1 TB block of CONDITIONED spaces, ua[lang][:b1]
|
924
|
+
# option ua [#key?] :b2 TB block of SEMIHEATED spaces, ua[lang][:b2]
|
925
|
+
# option ua [#to_s] :descr user-provided project/summary description
|
926
|
+
# option ua [#to_s] :file OpenStudio file, e.g. "school23.osm"
|
927
|
+
# option ua [#to_s] :version OpenStudio SDK, e.g. "3.6.1"
|
928
|
+
# option ua [Time] :date time signature
|
929
|
+
# option ua [#to_s] :notes advisory info, ua[lang][:notes]
|
930
|
+
# option ua [#key?] :areas binned areas (String), ua[lang][:areas][:walls]
|
931
|
+
# @param lang [#to_sym] selected language, :en or :fr
|
868
932
|
#
|
869
|
-
# @return [Array] MD-formatted strings (
|
933
|
+
# @return [Array<String>] MD-formatted strings (see logs if empty)
|
870
934
|
def ua_md(ua = {}, lang = :en)
|
871
935
|
mth = "TBD::#{__callee__}"
|
872
936
|
report = []
|
937
|
+
ck1 = ua.respond_to?(:key?)
|
938
|
+
ck2 = lang.respond_to?(:to_sym)
|
939
|
+
return mismatch( "ua", ua, Hash, mth, DBG, report) unless ck1
|
940
|
+
return mismatch("lang", lang, Symbol, mth, DBG, report) unless ck2
|
873
941
|
|
874
|
-
|
875
|
-
return
|
876
|
-
return
|
942
|
+
lang = lang.to_sym
|
943
|
+
return hashkey("language", ua, lang, mth, DBG, report) unless ua.key?(lang)
|
944
|
+
return empty("ua" , mth, DBG, report) if ua.empty?
|
877
945
|
|
878
|
-
if ua[lang].key?(:objective)
|
879
|
-
report << "# #{ua[lang][:objective]} "
|
946
|
+
if ua[lang].key?(:objective) && ua[lang][:objective].respond_to?(:to_s)
|
947
|
+
report << "# #{ua[lang][:objective].to_s} "
|
880
948
|
report << " "
|
881
949
|
end
|
882
950
|
|
883
|
-
if ua[lang].key?(:details)
|
884
|
-
ua[lang][:details].each
|
951
|
+
if ua[lang].key?(:details) && ua[lang][:details].respond_to?(:&)
|
952
|
+
ua[lang][:details].each do |d|
|
953
|
+
report << "#{d.to_s} " if d.respond_to?(:to_s)
|
954
|
+
end
|
955
|
+
|
885
956
|
report << " "
|
886
957
|
end
|
887
958
|
|
888
|
-
if ua.key?(:model)
|
889
|
-
report << "##### SUMMARY "
|
890
|
-
report << "##### SOMMAIRE "
|
959
|
+
if ua.key?(:model) && ua[:model].respond_to?(:to_s)
|
960
|
+
report << "##### SUMMARY " if lang == :en
|
961
|
+
report << "##### SOMMAIRE " if lang == :fr
|
891
962
|
report << " "
|
892
|
-
report << "#{ua[:model]} "
|
963
|
+
report << "#{ua[:model].to_s} "
|
893
964
|
report << " "
|
894
965
|
end
|
895
966
|
|
@@ -898,10 +969,10 @@ module TBD
|
|
898
969
|
report << "* #{ua[lang][:b1][:summary]}"
|
899
970
|
|
900
971
|
ua[lang][:b1].each do |k, v|
|
901
|
-
next
|
902
|
-
report << " * #{v}"
|
903
|
-
report << " * #{v} "
|
904
|
-
report << " "
|
972
|
+
next if k == :summary
|
973
|
+
report << " * #{v}" unless k == last
|
974
|
+
report << " * #{v} " if k == last
|
975
|
+
report << " " if k == last
|
905
976
|
end
|
906
977
|
report << " "
|
907
978
|
end
|
@@ -911,10 +982,10 @@ module TBD
|
|
911
982
|
report << "* #{ua[lang][:b2][:summary]}"
|
912
983
|
|
913
984
|
ua[lang][:b2].each do |k, v|
|
914
|
-
next
|
915
|
-
report << " * #{v}"
|
916
|
-
report << " * #{v} "
|
917
|
-
report << " "
|
985
|
+
next if k == :summary
|
986
|
+
report << " * #{v}" unless k == last
|
987
|
+
report << " * #{v} " if k == last
|
988
|
+
report << " " if k == last
|
918
989
|
end
|
919
990
|
report << " "
|
920
991
|
end
|
@@ -922,36 +993,36 @@ module TBD
|
|
922
993
|
if ua.key?(:date)
|
923
994
|
report << "##### DESCRIPTION "
|
924
995
|
report << " "
|
925
|
-
report << "* project : #{ua[:descr]}"
|
926
|
-
report << "* projet : #{ua[:descr]}"
|
996
|
+
report << "* project : #{ua[:descr]}" if ua.key?(:descr) && lang == :en
|
997
|
+
report << "* projet : #{ua[:descr]}" if ua.key?(:descr) && lang == :fr
|
927
998
|
model = ""
|
928
|
-
model = "* model : #{ua[:file]}"
|
929
|
-
model = "* modèle : #{ua[:file]}"
|
930
|
-
model += " (v#{ua[:version]})"
|
931
|
-
report << model
|
932
|
-
report << "* TBD : v3.
|
999
|
+
model = "* model : #{ua[:file]}" if ua.key?(:file) && lang == :en
|
1000
|
+
model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
|
1001
|
+
model += " (v#{ua[:version]})" if ua.key?(:version)
|
1002
|
+
report << model unless model.empty?
|
1003
|
+
report << "* TBD : v3.3.0"
|
933
1004
|
report << "* date : #{ua[:date]}"
|
934
1005
|
|
935
1006
|
if lang == :en
|
936
|
-
report << "* status : #{msg(status)}"
|
937
|
-
report << "* status : success !"
|
1007
|
+
report << "* status : #{msg(status)}" unless status.zero?
|
1008
|
+
report << "* status : success !" if status.zero?
|
938
1009
|
elsif lang == :fr
|
939
|
-
report << "* statut : #{msg(status)}"
|
940
|
-
report << "* statut : succès !"
|
1010
|
+
report << "* statut : #{msg(status)}" unless status.zero?
|
1011
|
+
report << "* statut : succès !" if status.zero?
|
941
1012
|
end
|
942
1013
|
report << " "
|
943
1014
|
end
|
944
1015
|
|
945
1016
|
if ua[lang].key?(:areas)
|
946
|
-
report << "##### AREAS "
|
947
|
-
report << "##### AIRES "
|
1017
|
+
report << "##### AREAS " if lang == :en
|
1018
|
+
report << "##### AIRES " if lang == :fr
|
948
1019
|
report << " "
|
949
1020
|
ok = ua[lang][:areas].key?(:walls)
|
950
|
-
report << "* #{ua[lang][:areas][:walls]}"
|
1021
|
+
report << "* #{ua[lang][:areas][:walls]}" if ok
|
951
1022
|
ok = ua[lang][:areas].key?(:roofs)
|
952
|
-
report << "* #{ua[lang][:areas][:roofs]}"
|
1023
|
+
report << "* #{ua[lang][:areas][:roofs]}" if ok
|
953
1024
|
ok = ua[lang][:areas].key?(:floors)
|
954
|
-
report << "* #{ua[lang][:areas][:floors]}"
|
1025
|
+
report << "* #{ua[lang][:areas][:floors]}" if ok
|
955
1026
|
report << " "
|
956
1027
|
end
|
957
1028
|
|