tbd 3.2.3 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
data/lib/tbd/ua.rb
CHANGED
@@ -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
|
|