Class: BTAP::Activity

Inherits:
Object
  • Object
show all
Extended by:
ActivityData
Defined in:
lib/openstudio-standards/btap/activity.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ActivityData

data, extended

Constructor Details

#initialize(model = nil) ⇒ Activity

Initialize BTAP Activity parameters.

Parameters:



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/openstudio-standards/btap/activity.rb', line 218

def initialize(model = nil)
  mth         = "BTAP::Activity::#{__callee__}"
  @feedback   = {logs: []}
  lgs         = @feedback[:logs]
  @activity   = ""
  @activities = {}
  @category   = ""

  unless model.is_a?(OpenStudio::Model::Model)
    lgs << "Invalid or empty OpenStudio model (#{mth})"
    return
  end

  # Tag spaces as un/conditioned with "space_conditioning_category". For

  # now, this is simply determined based on whether spaces are:

  #   - part of the total floor area (i.e. occupied)

  #   - have "attic" included in their identifiers (i.e. unconditioned)

  #

  # As per ASHRE 90.1, OpenStudio-Standards distinguishes between:

  #   - "nonresconditioned" vs

  #   - "resconditioned"

  #

  # Sticking to "nonresconditioned" - NECBs do not distinguish between "res"

  # vs "non-res" (for e.g. envelope), as opposed to ASHRAE 90.1.

  #

  # The solution could be further refined in future BTAP versions by e.g.:

  #   - relying on user-defined thermostats

  #   - expanded to cover semi-heated and refrigerated spaces

  tag = "space_conditioning_category"

  model.getSpaces.each do |space|
    next unless space.additionalProperties.getFeatureAsString(tag).empty?

    if space.partofTotalFloorArea
      space.additionalProperties.setFeature(tag, "nonresconditioned")
    else
      if space.nameString.downcase.include?("attic")
        space.additionalProperties.setFeature(tag, "unconditioned")
      else # treat all other cases as indirectly-conditioned e.g. plenums

        space.additionalProperties.setFeature(tag, "nonresconditioned")
      end
    end
  end

  # Determine activities of occupied spaces in the model, then building.

  @activities = self.getSpaceActivities(model)
  @activity   = self.getBuildingActivity(model)
  @liveload   = data[:bldg][:activity][@activity][:liveload]

  # Assign building category.

  unless @activity.empty?
    @category = data[:bldg][:activity][@activity][:category]
  end

  true
end

Instance Attribute Details

#activitiesHash (readonly)

Returns collection of space ACTIVITIES (e.g. “bulk::warehouse”).

Returns:

  • (Hash)

    collection of space ACTIVITIES (e.g. “bulk::warehouse”)



202
203
204
# File 'lib/openstudio-standards/btap/activity.rb', line 202

def activities
  @activities
end

#activityString (readonly)

Returns assigned or inferred building ACTIVITY (e.g. “warehouse”).

Returns:

  • (String)

    assigned or inferred building ACTIVITY (e.g. “warehouse”)



199
200
201
# File 'lib/openstudio-standards/btap/activity.rb', line 199

def activity
  @activity
end

#categoryString (readonly)

Returns building type CATEGORY (e.g. “industry”).

Returns:

  • (String)

    building type CATEGORY (e.g. “industry”)



205
206
207
# File 'lib/openstudio-standards/btap/activity.rb', line 205

def category
  @category
end

#feedbackHash (readonly)

Returns logged messages.

Returns:

  • (Hash)

    logged messages



211
212
213
# File 'lib/openstudio-standards/btap/activity.rb', line 211

def feedback
  @feedback
end

#liveloadFloat (readonly)

Returns expected non-occupant liveload (e.g. 90 kg/m2).

Returns:

  • (Float)

    expected non-occupant liveload (e.g. 90 kg/m2)



208
209
210
# File 'lib/openstudio-standards/btap/activity.rb', line 208

def liveload
  @liveload
end

Instance Method Details

#getBuildingActivity(model = nil) ⇒ String

Determines general building activity, either set by user or inferred.

Parameters:

  • model (defaults to: nil)

    OpenStudio::Model::Model] a model

Returns:

  • (String)

    keyword describing a model’s general activity



360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# File 'lib/openstudio-standards/btap/activity.rb', line 360

def getBuildingActivity(model = nil)
  mth = "BTAP::Activity::#{__callee__}"
  cl  = OpenStudio::Model::Model
  return mismatch("model", model, cl, mth, DBG, "") unless model.is_a?(cl)

  # OPTION A: Extract building activity from user-set 'additionalProperty'.

  tag      = "btap_building_activity"
  bldg     = model.getBuilding
  activity = bldg.additionalProperties.getFeatureAsString(tag)

  if activity.empty?
    activity = ""
  else
    activity = activity.get.downcase
    return activity if data[:bldg][:activities].include?(activity)
  end

  # OPTION B: Extract building activity from user-set 'building type'.

  bldgtype = model.getBuilding.standardsBuildingType

  unless bldgtype.empty?
    bldgtype   = bldgtype.get.downcase
    candidates = []
    fallbacks  = []

    # Fetch matching BTAP data, if keywords included.

    data[:bldg][:activity].each do |k, v|
      v[:includes].each do |kword|
        candidates << k if bldgtype.include?(kword)
      end
    end

    # Keep track of fallbacks, if applicable.

    candidates.each do |candidate|
      fallback = data[:bldg][:activity][candidate][:fallback]
      fallbacks << fallback unless fallback.empty?
    end

    # Reject if matching excluded keywords.

    data[:bldg][:activity].each do |k, v|
      v[:excludes].each do |kword|
        candidates.delete(k) if bldgtype.include?(kword)
      end
    end

    # Fallbacks?

    if candidates.empty?
      fallbacks.each do |fallback|
        return fallback if data[:bldg][:activity].key?(fallback)
      end
    else
      return candidates.first
    end
  end

  # OPTION C: Infer building activity from distribution of spacetypes.

  bldgtypes = {}

  @activities.values.each do |v|
    next unless v.key?(:m2)
    next unless v.key?(:bldgtype)

    bldgtypes[v[:bldgtype]]  = 0 unless bldgtypes.include?(v[:bldgtype])
    bldgtypes[v[:bldgtype]] += v[:m2]
  end

  activity = bldgtypes.sort.reverse.to_h.keys.first unless bldgtypes.empty?
  # Many NECB space types are listed as "common". Examples include spaces

  # that are educational in nature (e.g. "classroom", "teachinglabs") and

  # typical office spaces (e.g. "openplan", "office"). This is odd, as NECBs

  # list "school/university" and "office" as admissible building types.

  # Inferring an overall building type/activity (e.g. "school"), based on

  # the prevalence of space types (e.g. "classrooms") in a model, becomes

  # unnecessarily challenging. A fallback solution is needed when

  # predominant space types end up as "common" for a given model.

  #

  # One odd exception is 'audience' seating for an "auditorium", which is

  # found in all NECB editions. All listed 'audience' seating space types

  # are linked to a listed building type, e.g.:

  #   - "religious building"

  #   - "sports arena"

  #   - "motion picture theatre"

  #

  # ... except for "auditorium". No NECB edition holds an "auditorium"

  # building type entry. For the moment, "auditorium" will be associated

  # with the ubiquitous high-school or college auditorium.

  if activity == "common"
    activities = {}

    @activities.values.each do |v|
      next unless v.key?(:m2)
      next unless v.key?(:activity)

      activities[v[:activity]]  = 0 unless activities.key?(v[:activity])
      activities[v[:activity]] += v[:m2]
    end

    activity = case activities.sort.reverse.to_h.keys.first
               when "audience"    then "school"
               when "sales"       then "retail"
               when "dining"      then "restaurant"
               when "cuisine"     then "restaurant"
               when "rooms"       then "hotel"
               when "recreation"  then "exercise"
               when "cell"        then "penitentiary"
               when "classroom"   then "school"
               when "teachinglab" then "school"
               when "storage"     then "warehouse"
               when "laundry"     then "retail"
               when "lounge"      then "leisure"
               when "pharmacy"    then "retail"
               else                    "office"
               end
  end

  activity = "office" if activity.empty?

  activity
end

#getSpaceActivities(model = nil) ⇒ Hash

Gather activities of occupied spaces in a model.

Parameters:

Returns:

  • (Hash)

    a collection of space activities (see logs if empty)



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/openstudio-standards/btap/activity.rb', line 281

def getSpaceActivities(model = nil)
  mth = "BTAP::Activity::#{__callee__}"
  cl  = OpenStudio::Model::Model
  return mismatch("model", model, cl, mth, DBG, {}) unless model.is_a?(cl)

  activities = {}

  model.getSpaces.each do |space|
    next unless space.partofTotalFloorArea

    # Defaulted values (if missing or invalid entries).

    spacetype  = nil
    standards  = ""
    activity   = ""
    bldgtype   = ""
    fallbacks  = []
    candidates = []

    # Recover user-set space types?

    unless space.spaceType.empty?
      spacetype = space.spaceType.get
      stdstype  = spacetype.standardsSpaceType
      standards = stdstype.get.downcase unless stdstype.empty?
    end

    # Fetch matching BTAP data, if keywords included.

    data[:space][:activity].each do |k, v|
      v[:includes].each do |kword|
        candidates << k if standards.include?(kword)
      end
    end

    # Keep track of fallbacks, if applicable.

    candidates.each do |candidate|
      fallback = data[:space][:activity][candidate][:fallback]
      fallbacks << fallback unless fallback.empty?
    end

    # Reject if matching excluded keywords.

    data[:space][:activity].each do |k, v|
      v[:excludes].each do |kword|
        candidates.delete(k) if standards.include?(kword)
      end
    end

    # Fallbacks?

    if candidates.empty?
      candidate = ""

      fallbacks.each do |fallback|
        break unless candidate.empty?

        candidate = fallback if data[:space][:activity].key?(fallback)
      end

      candidate = data[:space][:activity].keys.first if candidate.empty?
    else
      candidate = candidates.first
    end

    entry             = {}
    entry[:m2       ] = space.floorArea
    entry[:spacetype] = spacetype
    entry[:standards] = standards
    entry[:activity ] = data[:space][:activity][candidate][:activity]
    entry[:bldgtype ] = data[:space][:activity][candidate][:bldgtype]

    activities[space] = entry
  end

  activities
end