openstudio-standards 0.8.5 → 0.8.7

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.
Files changed (174) hide show
  1. checksums.yaml +4 -4
  2. data/data/standards/test_performance_expected_dd_results.csv +90 -90
  3. data/lib/openstudio-standards/btap/activity.rb +858 -61
  4. data/lib/openstudio-standards/btap/attributes.rb +297 -103
  5. data/lib/openstudio-standards/btap/bridging.rb +1335 -593
  6. data/lib/openstudio-standards/btap/btap.rb +60 -181
  7. data/lib/openstudio-standards/btap/carbon/btap_carbon.rb +199 -142
  8. data/lib/openstudio-standards/btap/common/constructions.json +1275 -0
  9. data/lib/openstudio-standards/btap/common/costs.csv +1969 -0
  10. data/lib/openstudio-standards/btap/common/costs_local_factors.csv +2315 -0
  11. data/lib/openstudio-standards/btap/common/materials_glazing.csv +61 -0
  12. data/lib/openstudio-standards/btap/{common_resources → common}/materials_hvac.csv +1685 -1685
  13. data/lib/openstudio-standards/btap/{common_resources → common}/materials_lighting.csv +266 -266
  14. data/lib/openstudio-standards/btap/common/materials_opaque.csv +214 -0
  15. data/lib/openstudio-standards/btap/common/necb_building_types.csv +35 -0
  16. data/lib/openstudio-standards/btap/common/necb_space_types.csv +110 -0
  17. data/lib/openstudio-standards/btap/common/thermal_bridging.csv +73 -0
  18. data/lib/openstudio-standards/btap/costing/btap_costing.rb +346 -429
  19. data/lib/openstudio-standards/btap/costing/database.rb +104 -0
  20. data/lib/openstudio-standards/btap/costing/daylighting_sensor_control_costing.rb +356 -344
  21. data/lib/openstudio-standards/btap/costing/dcv_costing.rb +282 -296
  22. data/lib/openstudio-standards/btap/costing/envelope_costing.rb +179 -192
  23. data/lib/openstudio-standards/btap/costing/heating_cooling_costing.rb +2477 -2316
  24. data/lib/openstudio-standards/btap/costing/led_lighting_costing.rb +127 -142
  25. data/lib/openstudio-standards/btap/costing/lighting_costing.rb +216 -198
  26. data/lib/openstudio-standards/btap/costing/nv_costing.rb +522 -523
  27. data/lib/openstudio-standards/btap/costing/pv_ground_costing.rb +649 -649
  28. data/lib/openstudio-standards/btap/costing/shw_costing.rb +626 -625
  29. data/lib/openstudio-standards/btap/costing/thermal_bridging_costing.rb +72 -0
  30. data/lib/openstudio-standards/btap/costing/ventilation_costing.rb +2363 -2361
  31. data/lib/openstudio-standards/btap/deprecated.rb +1033 -0
  32. data/lib/openstudio-standards/btap/envelope.rb +313 -572
  33. data/lib/openstudio-standards/btap/fileio.rb +152 -1070
  34. data/lib/openstudio-standards/btap/geometry.rb +486 -766
  35. data/lib/openstudio-standards/btap/linear_regression.rb +130 -0
  36. data/lib/openstudio-standards/btap/paths.rb +46 -0
  37. data/lib/openstudio-standards/btap/qaqc/analysis.rb +163 -0
  38. data/lib/openstudio-standards/btap/qaqc/data.rb +2293 -0
  39. data/lib/openstudio-standards/btap/qaqc/datapoint.rb +641 -0
  40. data/lib/openstudio-standards/btap/schedules.rb +11 -611
  41. data/lib/openstudio-standards/btap/structure.rb +574 -189
  42. data/lib/openstudio-standards/constructions/materials/information.rb +1 -1
  43. data/lib/openstudio-standards/create_typical/create_typical.rb +1 -1
  44. data/lib/openstudio-standards/exterior_lighting/create.rb +135 -85
  45. data/lib/openstudio-standards/exterior_lighting/data/typical_exterior_lighting.json +20 -0
  46. data/lib/openstudio-standards/exterior_lighting/information.rb +23 -2
  47. data/lib/openstudio-standards/geometry/create.rb +7 -3
  48. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.rb +3 -2
  49. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Model.rb +3 -2
  50. data/lib/openstudio-standards/prototypes/common/data/thermostat_schedule_lookup.json +84 -84
  51. data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +4 -4
  52. data/lib/openstudio-standards/refrigeration/create_walkin.rb +45 -5
  53. data/lib/openstudio-standards/refrigeration/data/refrigerated_walkins.csv +75 -75
  54. data/lib/openstudio-standards/schedules/parametric.rb +6 -0
  55. data/lib/openstudio-standards/service_water_heating/create_typical.rb +10 -3
  56. data/lib/openstudio-standards/service_water_heating/create_water_heating_loop.rb +30 -14
  57. data/lib/openstudio-standards/service_water_heating/information.rb +4 -1
  58. data/lib/openstudio-standards/sql_file/unmet_hours.rb +2 -1
  59. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXSingleSpeed.rb +3 -1
  60. data/lib/openstudio-standards/standards/Standards.CoilHeatingDXSingleSpeed.rb +3 -1
  61. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.boilers.json +12 -0
  62. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.construction_properties.json +18432 -3072
  63. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.boilers.json +26 -0
  64. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.construction_properties.json +8532 -1422
  65. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.boilers.json +26 -0
  66. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.construction_properties.json +8532 -1422
  67. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.boilers.json +16 -0
  68. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.computer_room_acs.json +30 -30
  69. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.construction_properties.json +7560 -1260
  70. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.PumpVariableSpeed.rb +1 -2
  71. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.boilers.json +16 -0
  72. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.construction_properties.json +7560 -1260
  73. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirLoopHVAC.rb +3 -3
  74. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.PumpVariableSpeed.rb +1 -2
  75. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.boilers.json +22 -0
  76. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.construction_properties.json +7236 -1206
  77. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.furnaces.json +223 -19
  78. data/lib/openstudio-standards/standards/ashrae_90_1/data/ashrae_90_1.schedules.json +37 -1
  79. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.boilers.json +3 -0
  80. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.construction_properties.json +6900 -1150
  81. data/lib/openstudio-standards/standards/cbes/data/cbes.schedules.json +1 -1
  82. data/lib/openstudio-standards/standards/deer/data/deer.schedules.json +1 -1
  83. data/lib/openstudio-standards/standards/necb/BTAP1980TO2010/data/surface_thermal_transmittance.json +76 -40
  84. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/surface_thermal_transmittance.json +76 -40
  85. data/lib/openstudio-standards/standards/necb/ECMS/hvac_systems.rb +6 -4
  86. data/lib/openstudio-standards/standards/necb/NECB2011/NECB2011_QAQC.json +2988 -0
  87. data/lib/openstudio-standards/standards/necb/NECB2011/autozone.rb +25 -3
  88. data/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +672 -534
  89. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/Hospital.osm +1791 -1792
  90. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPMultiTower.osm +7 -1
  91. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPTownHouse.osm +7 -1
  92. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LargeHotel.osm +5 -6
  93. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LargeOffice.osm +8 -9
  94. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernEducation.osm +10 -4
  95. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernHealthCare.osm +14 -15
  96. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/Outpatient.osm +7 -1
  97. data/lib/openstudio-standards/standards/necb/NECB2011/data/surface_thermal_transmittance.json +76 -40
  98. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +14 -6
  99. data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +542 -130
  100. data/lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb +30 -63
  101. data/lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb +7 -2
  102. data/lib/openstudio-standards/standards/necb/NECB2015/data/surface_thermal_transmittance.json +90 -0
  103. data/lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb +8 -6
  104. data/lib/openstudio-standards/standards/necb/NECB2017/data/surface_thermal_transmittance.json +76 -40
  105. data/lib/openstudio-standards/standards/necb/NECB2017/necb_2017.rb +2 -1
  106. data/lib/openstudio-standards/standards/necb/NECB2020/building_envelope.rb +0 -117
  107. data/lib/openstudio-standards/standards/necb/NECB2020/data/surface_thermal_transmittance.json +76 -45
  108. data/lib/openstudio-standards/standards/necb/NECB2020/necb_2020.rb +218 -0
  109. data/lib/openstudio-standards/standards/necb/common/construction_defaults.osm +12 -7
  110. data/lib/openstudio-standards/standards/standard.rb +1 -1
  111. data/lib/openstudio-standards/version.rb +1 -1
  112. data/lib/openstudio-standards.rb +9 -4
  113. metadata +59 -66
  114. data/data/inventory/thermal_bridging.csv +0 -90
  115. data/data/standards/OpenStudio_Standards-ashrae_90_1-ALL-comstock(space_types).xlsx +0 -0
  116. data/data/standards/openstudio_standards_duplicates_log.csv +0 -7962
  117. data/lib/openstudio-standards/btap/NECB_building_types.csv +0 -35
  118. data/lib/openstudio-standards/btap/NECB_space_types.csv +0 -109
  119. data/lib/openstudio-standards/btap/WeatherData1.json +0 -2110
  120. data/lib/openstudio-standards/btap/WeatherData1.xlsx +0 -0
  121. data/lib/openstudio-standards/btap/btap_result.rb +0 -1144
  122. data/lib/openstudio-standards/btap/btap_test_helper.rb +0 -86
  123. data/lib/openstudio-standards/btap/common_paths.rb +0 -81
  124. data/lib/openstudio-standards/btap/common_resources/ConstructionProperties.csv +0 -52
  125. data/lib/openstudio-standards/btap/common_resources/Constructions.csv +0 -37
  126. data/lib/openstudio-standards/btap/common_resources/construction_sets.csv +0 -1270
  127. data/lib/openstudio-standards/btap/common_resources/constructions_glazing.csv +0 -121
  128. data/lib/openstudio-standards/btap/common_resources/constructions_opaque.csv +0 -2256
  129. data/lib/openstudio-standards/btap/common_resources/costs.csv +0 -1897
  130. data/lib/openstudio-standards/btap/common_resources/costs_local_factors.csv +0 -2315
  131. data/lib/openstudio-standards/btap/common_resources/materials_glazing.csv +0 -35
  132. data/lib/openstudio-standards/btap/common_resources/materials_opaque.csv +0 -164
  133. data/lib/openstudio-standards/btap/costing/btap_measure_helper.rb +0 -359
  134. data/lib/openstudio-standards/btap/costing/btap_workflow.rb +0 -117
  135. data/lib/openstudio-standards/btap/costing/copy_test_results_files_to_expected_results.rb +0 -11
  136. data/lib/openstudio-standards/btap/costing/cost_building_from_file.rb +0 -136
  137. data/lib/openstudio-standards/btap/costing/costing_database_wrapper.rb +0 -173
  138. data/lib/openstudio-standards/btap/costing/dummy.epw +0 -8768
  139. data/lib/openstudio-standards/btap/costing/dummy.osm +0 -5320
  140. data/lib/openstudio-standards/btap/costing/neb_end_use_prices.csv +0 -42
  141. data/lib/openstudio-standards/btap/costing/necb_2011_spacetype_info.csv +0 -225
  142. data/lib/openstudio-standards/btap/costing/necb_reference_runs.csv +0 -28705
  143. data/lib/openstudio-standards/btap/costing/parallel_tests.rb +0 -92
  144. data/lib/openstudio-standards/btap/costing/test_list.txt +0 -15
  145. data/lib/openstudio-standards/btap/costing/test_run_all_test_locally.rb +0 -27
  146. data/lib/openstudio-standards/btap/costing/test_run_costing_tests.rb +0 -80
  147. data/lib/openstudio-standards/btap/necb_2011_spacetype_info.csv +0 -225
  148. data/lib/openstudio-standards/btap/utilities/Add_template_field_to_json.rb +0 -22
  149. data/lib/openstudio-standards/btap/utilities/convert_surfaces_to_adiabatic_necb_8426.rb +0 -204
  150. data/lib/openstudio-standards/btap/utilities/generate_space_types.rb +0 -8
  151. data/lib/openstudio-standards/btap/utilities/necb_to_epw_map.rb +0 -50
  152. data/lib/openstudio-standards/btap/utilities/os_sim_extract.rb +0 -48
  153. data/lib/openstudio-standards/btap/utilities/rename_surfaces.rb +0 -24
  154. data/lib/openstudio-standards/btap/utilities/round_surf_coords.rb +0 -32
  155. data/lib/openstudio-standards/btap/utilities/sched_create.rb +0 -145
  156. data/lib/openstudio-standards/btap/utilities/set_mult_to_adiabatic.rb +0 -53
  157. data/lib/openstudio-standards/btap/utilities/space_type.rb +0 -78
  158. data/lib/openstudio-standards/btap/utilities/weatherData1_xlsx_to_json.rb +0 -56
  159. data/lib/openstudio-standards/btap/vintagizer.rb +0 -161
  160. data/lib/openstudio-standards/btap/visualization.rb +0 -72
  161. data/lib/openstudio-standards/standards/necb/NECB2011/data/necb_2011_spacetype_info.csv +0 -225
  162. data/lib/openstudio-standards/standards/necb/common/btap_analysis.rb +0 -79
  163. data/lib/openstudio-standards/standards/necb/common/btap_data.rb +0 -2656
  164. data/lib/openstudio-standards/standards/necb/common/btap_datapoint.rb +0 -653
  165. data/lib/openstudio-standards/standards/necb/common/necb_reference_runs.csv +0 -28705
  166. data/lib/openstudio-standards/standards/necb/common/utility_pricing_2025-02-20.csv +0 -92
  167. /data/lib/openstudio-standards/btap/{common_resources → common}/carbon_frame.csv +0 -0
  168. /data/lib/openstudio-standards/btap/{common_resources → common}/carbon_glazing.csv +0 -0
  169. /data/lib/openstudio-standards/btap/{common_resources → common}/carbon_opaque.csv +0 -0
  170. /data/lib/openstudio-standards/btap/{common_resources → common}/hvac_vent_ahu.csv +0 -0
  171. /data/lib/openstudio-standards/btap/{common_resources → common}/lighting.csv +0 -0
  172. /data/lib/openstudio-standards/btap/{common_resources → common}/lighting_sets.csv +0 -0
  173. /data/lib/openstudio-standards/btap/{common_resources → common}/locations.csv +0 -0
  174. /data/lib/openstudio-standards/btap/{costing → common}/mech_sizing.json +0 -0
@@ -1,5 +1,5 @@
1
1
  # **************************************************************************** /
2
- # * Copyright (c) 2008-2025, Natural Resources Canada
2
+ # * Copyright (c) 2008-2026, Natural Resources Canada
3
3
  # * All rights reserved.
4
4
  # *
5
5
  # * This library is free software; you can redistribute it and/or
@@ -17,8 +17,6 @@
17
17
  # * Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
  # **************************************************************************** /
19
19
 
20
- require "csv"
21
-
22
20
  module BTAP
23
21
  module ActivityData
24
22
  ##
@@ -36,8 +34,8 @@ module BTAP
36
34
  # the repair or servicing of such vehicles.
37
35
  #
38
36
  # This mismatch, and other related issues of a similar nature, make it
39
- # challenging to cross-compare NECB editions, for instance. The 'exact' NECB
40
- # labels shouldn't matter - they almost always reference the same
37
+ # challenging to cross-compare NECB editions for instance. The 'exact' NECB
38
+ # spacetype strings shouldn't matter - they almost always reference the same
41
39
  # building 'activity' (e.g. a facility where vehicles are parked/stored).
42
40
  # BTAP should instead rely on abstract 'activity' designations, e.g.
43
41
  # 'parking'. This requires module/class methods to extract specific keywords
@@ -48,13 +46,13 @@ module BTAP
48
46
  # For instance, multi-unit residential buildings (MURBs), university/school
49
47
  # dormitories and long-term care facilities are all grouped under "housing",
50
48
  # which in turn sets building-wide 'structural' options, e.g. wood-framed
51
- # (small-scale) vs reinforced concrete flat slab & post-beam (mid- & large-
52
- # scale) "housing". See lib/openstudio-standards/btap/structure.rb.
53
- @@data = {bldg: {}, space: {}}
49
+ # (small-scale "housing") vs reinforced concrete flat slab & post-beam (mid-
50
+ # & large-scale "housing"). See lib/openstudio-standards/btap/structure.rb.
51
+ @@data = {bldg: {}, space: {}, ancillaries: [], auxiliaries: []}
54
52
 
55
53
  # Hard setting path for both files (temporary @todo).
56
- @@data[:bldg ][:file ] = File.join(__dir__, "NECB_building_types.csv")
57
- @@data[:space][:file ] = File.join(__dir__, "NECB_space_types.csv")
54
+ @@data[:bldg ][:file ] = Paths::NECB_BUILDING_TYPES_PATH
55
+ @@data[:space][:file ] = Paths::NECB_SPACE_TYPES_PATH
58
56
  @@data[:bldg ][:table ] = nil
59
57
  @@data[:space][:table ] = nil
60
58
  @@data[:bldg ][:activity ] = {}
@@ -62,27 +60,84 @@ module BTAP
62
60
  @@data[:bldg ][:activities] = []
63
61
  @@data[:bldg ][:categories] = []
64
62
 
63
+ # Key notes, common to NECB editions.
64
+ #
65
+ # Note to NECB Table 8.4.4.7.-A: (2) Small individual spaces in the
66
+ # proposed building that are located among larger spaces of another space
67
+ # type shall be considered ANCILLARY to that larger space: for example, a
68
+ # conference room serving office spaces would be grouped with the office
69
+ # spaces as one space type.
70
+ #
71
+ # A-8.4.3.2.(1): Tables [...] contain default values of operating
72
+ # schedules of building parameters for simulation purposes. These
73
+ # schedules MAY be used with Table A-8.4.3.2.(2)-B IF more accurate
74
+ # information IS NOT AVAILABLE.
75
+ #
76
+ # A-8.4.3.2.(2): Tables [...] contain representative internal and service
77
+ # water heating loads, operating schedules, and illuminance levels to be
78
+ # used as modeling GUIDANCE when actual values are not known.
79
+ #
80
+ # Note to Table A-8.4.3.3.(2)B. (1): An asterisk (*) in this column
81
+ # indicates that there is no recommended default schedule for the space
82
+ # type listed. In general, such space types will be simulated using a
83
+ # schedule that is similar to the ADJACENT spaces served: e.g. a corridor
84
+ # space serving an adjacent office space will be simulated using a
85
+ # schedule that is similar to that of the office space.
86
+ #
87
+ # This last note refers to the following NECB 'ancillary' spacetypes:
88
+ #
89
+ # 2011 2015 2017 2020
90
+ # @@data[:ancillaries] << "atrium::common" # C * * *
91
+ # @@data[:ancillaries] << "audience::common" # * * *
92
+ # @@data[:ancillaries] << "computer::common" # * * *
93
+ # @@data[:ancillaries] << "corridor::common" # * * * *
94
+ # @@data[:ancillaries] << "corridor::hospital" # * * * *
95
+ # @@data[:ancillaries] << "corridor::manufacturing" # * * * *
96
+ # @@data[:ancillaries] << "mechanical::common" # * * * *
97
+ # @@data[:ancillaries] << "locker::common" # * * * *
98
+ # @@data[:ancillaries] << "seating::common" # * * *
99
+ # @@data[:ancillaries] << "stairway::common" # * * * *
100
+ # @@data[:ancillaries] << "storage::common" # * * * *
101
+ # @@data[:ancillaries] << "washroom::common" # * * * *
102
+
103
+ # Note that NECB2011 recommends schedule set "C" for atria, while all other
104
+ # NECB editions point to Note (1) of Table A-8.4.3.3.(2)B (on schedule set
105
+ # inheritance). To facilitate cross-comparisons between NECB editions, the
106
+ # NECB 2011 atrium schedule set will harmonize with other NECB editions.
107
+ #
108
+ # With respect to other NECB editions, NECB2011 also has a few missing
109
+ # entries: There is neither GENERAL 'seating' (e.g. a waiting area) nor
110
+ # GENERAL 'audience seating' types: 'seating' is either building-SPECIFIC
111
+ # (e.g. transportation facility) or SPECIFIC 'audience seating' (e.g.
112
+ # theatre, convention centre). And there is no entry for computer or server
113
+ # rooms. Within the scope of BTAP::Activity, missing space types inherit
114
+ # fallbacks (e.g. audience seating, office).
115
+
65
116
  # Parse building type data on file.
66
117
  if File.exist?(@@data[:bldg][:file])
67
118
  table = CSV.open(@@data[:bldg][:file], headers: true).read
68
119
 
69
- # 35 unique entries (rows), 6 columns per row, e.g.:
70
- # COL1 COL2 COL3 COL4 COL5 COL6
71
- # ______ ____ ________ _____ ________________________________ ___________
72
- # "care, 20, housing, care, health/clinic/multi/residential, residential"
120
+ # 35 unique entries (rows), 10 columns per row, e.g.:
121
+ # C01 C02 C03 C04 C05 C06 C07 C08 C09 C10
122
+ # ____ ___ ____ ____ ___ ___ ________ _____ _______________ ____________
123
+ # care, 25, 1.5, 500, j, 13, housing, care, health/clinic/, residential
73
124
  #
74
- # COL1: BTAP building ACTIVITY e.g. "care"
75
- # COL2: non-occupant liveload e.g. 20 kg/m2, ~1/12 of NBC min liveload
76
- # COL3: BTAP building CATEGORY e.g. "housing"
77
- # COL4: selected sub-string(s) e.g. "care", as in "Long-term care"
78
- # COL5: rejected sub-string(s) e.g. "health", "multi", "residential"
79
- # COL6: fallback (if missing) e.g. "residential"
125
+ # C01: BTAP building ACTIVITY e.g. "care"
126
+ # C02: occupant density e.g. 25.0 m2/occupant
127
+ # C03: peak equipment load e.g. 1.5 W/m2
128
+ # C04: peak SWH load e.g. 500.0 W/occupant
129
+ # C05: schedule set e.g. "j"
130
+ # C06: non-occupant liveload e.g. 13 kg/m2, ~1/12 of NBC min liveload
131
+ # C07: BTAP building CATEGORY e.g. "housing"
132
+ # C08: selected sub-string(s) e.g. "care", as in "Long-term care"
133
+ # C09: rejected sub-string(s) e.g. "health", "multi", "residential"
134
+ # C10: fallback (if missing) e.g. "residential"
80
135
  #
81
136
  # Contrary to the aforementioned 'parking' case (where fortunately there
82
137
  # is an obvious one-to-one match between "Parking garage" (NECB2011) and
83
138
  # "Storage garage" (NECB2020)), there is no direct match here for a
84
139
  # long-term care facility when using the NECB2011. In this case, the
85
- # fallback 'activity' is 'residential' (COL6). So in any cross-comparison
140
+ # fallback 'activity' is 'residential' (COL10). So in any cross-comparison
86
141
  # of long-term care facilities between NECB editions, the NECB2011 variant
87
142
  # would be akin to a MURB.
88
143
  #
@@ -99,14 +154,18 @@ module BTAP
99
154
  # COL4 substrings, then rejecting COL5 substrings, there should be a
100
155
  # single selected row. See NECB unit test test_necb_activities.rb.
101
156
  table.each do |row|
102
- key = row[0]
157
+ key = row[0].to_s
103
158
 
104
159
  @@data[:bldg][:activity][key] = {}
105
- @@data[:bldg][:activity][key][:liveload] = row[1].to_f
106
- @@data[:bldg][:activity][key][:category] = row[2].to_s
107
- @@data[:bldg][:activity][key][:includes] = row[3].to_s.split("/")
108
- @@data[:bldg][:activity][key][:excludes] = row[4].to_s.split("/")
109
- @@data[:bldg][:activity][key][:fallback] = row[5].to_s
160
+ @@data[:bldg][:activity][key][:density ] = row[1].to_f
161
+ @@data[:bldg][:activity][key][:eqpload ] = row[2].to_f
162
+ @@data[:bldg][:activity][key][:swhload ] = row[3].to_f
163
+ @@data[:bldg][:activity][key][:schedule] = row[4].to_s
164
+ @@data[:bldg][:activity][key][:liveload] = row[5].to_f
165
+ @@data[:bldg][:activity][key][:category] = row[6].to_s
166
+ @@data[:bldg][:activity][key][:includes] = row[7].to_s.split("/")
167
+ @@data[:bldg][:activity][key][:excludes] = row[8].to_s.split("/")
168
+ @@data[:bldg][:activity][key][:fallback] = row[9].to_s
110
169
  end
111
170
 
112
171
  # Keep CSV table. Ensure building activities & categories uniqueness. Add
@@ -125,16 +184,19 @@ module BTAP
125
184
  if File.exist?(@@data[:space][:file])
126
185
  table = CSV.open(@@data[:space][:file], headers: true).read
127
186
 
128
- # 108 unique rows, 4 columns per row, e.g.:
129
- # COL1 COL2 COL3 COL4
130
- # _____________________ ________ ____________ ___________________
131
- # "units::care, unit, residential, units::residential"
132
- # "exhibit::convention, exhibit, museum, "
187
+ # 108 unique rows, 8 columns per row, e.g.:
188
+ # C01 C02 C03 C04 C05 C06 C07 C08
189
+ # ___________ ___ ____ ____ ____ ____ ____________ ___________________
190
+ # units::care, 25, 2.5, 500, j, unit, residential, units::residential
133
191
  #
134
- # COL1: BTAP space ACTIVITY e.g. "units::care"
135
- # COL2: selected sub-string(s) e.g. "unit"
136
- # COL3: rejected sub-string e.g. "residential"
137
- # COL4: fallback (if missing) e.g. "units::residential"
192
+ # C01: BTAP space ACTIVITY e.g. "units::care"
193
+ # C02: occupant density e.g. 25.0 m2/occupant
194
+ # C03: peak equipment load e.g. 2.5 W/m2
195
+ # C04: peak SWH load e.g. 500.0 W/occupant
196
+ # C05: schedule set e.g. "j"
197
+ # C06: selected sub-string(s) e.g. "unit"
198
+ # C07: rejected sub-string(s) e.g. "residential"
199
+ # C08: fallback (if missing) e.g. "units::residential"
138
200
  #
139
201
  # First, BTAP space 'activity' entries are namespaced, e.g.:
140
202
  # - "units": 1-word descriptor on the nature of the space 'activity'
@@ -160,18 +222,29 @@ module BTAP
160
222
  # share many features (e.g. sleeping, showers), yet each remains specific
161
223
  # to an NECB space type entry (as required).
162
224
  table.each do |row|
163
- key = row[0]
225
+ key = row[0].to_s
164
226
  str = key.split("::")
165
227
 
166
228
  activity = str[0].to_s
167
229
  bldgtype = str[1].to_s
230
+ schedule = row[4].to_s
168
231
 
169
232
  @@data[:space][:activity][key] = {}
170
233
  @@data[:space][:activity][key][:activity] = activity
171
234
  @@data[:space][:activity][key][:bldgtype] = bldgtype
172
- @@data[:space][:activity][key][:includes] = row[1].to_s.split("/")
173
- @@data[:space][:activity][key][:excludes] = row[2].to_s.split("/")
174
- @@data[:space][:activity][key][:fallback] = row[3].to_s
235
+ @@data[:space][:activity][key][:density ] = row[1].to_f
236
+ @@data[:space][:activity][key][:eqpload ] = row[2].to_f
237
+ @@data[:space][:activity][key][:swhload ] = row[3].to_f
238
+ @@data[:space][:activity][key][:schedule] = schedule
239
+ @@data[:space][:activity][key][:includes] = row[5].to_s.split("/")
240
+ @@data[:space][:activity][key][:excludes] = row[6].to_s.split("/")
241
+ @@data[:space][:activity][key][:fallback] = row[7].to_s
242
+
243
+ if schedule == "*"
244
+ @@data[:ancillaries] << key
245
+ elsif schedule.include?("*")
246
+ @@data[:auxiliaries] << key
247
+ end
175
248
  end
176
249
 
177
250
  @@data[:space][:table] = table
@@ -179,6 +252,154 @@ module BTAP
179
252
  # raise?
180
253
  end
181
254
 
255
+ ##
256
+ # Validates whether an activity is 'ancillary' to other(s). See relevant
257
+ # comments above, and note to Table A-8.4.3.3.(2)B. (1).
258
+ #
259
+ # @param activity [:to_sym] a BTAP::Activity keyword, e.g. "locker::common"
260
+ #
261
+ # @return [Boolean] whether activity is ancillary.
262
+ def ancillary?(activity = "")
263
+ return false unless activity.respond_to?(:to_sym)
264
+
265
+ activity = activity.to_s.strip.downcase
266
+ return true if @@data[:ancillaries].include?(activity)
267
+
268
+ false
269
+ end
270
+
271
+ ##
272
+ # Validates whether an activity is 'auxiliary' to the building activity.
273
+ # 'Auxiliary' spaces differ from NECB 'ancillary' spaces. If requested by
274
+ # the user (in the .osm file), the former may adopt the dominant building
275
+ # activity schedule set, instead of the NECB suggested one. Examples include
276
+ # a school's gymnasium, cafeteria or auditorium.
277
+ #
278
+ # @param activity [:to_sym] a BTAP::Activity keyword, e.g. "court::gym"
279
+ #
280
+ # @return [Boolean] whether activity is auxiliary.
281
+ def auxiliary?(activity = "")
282
+ return false unless activity.respond_to?(:to_sym)
283
+
284
+ activity = activity.to_s.strip.downcase
285
+ return true if @@data[:auxiliaries].include?(activity)
286
+
287
+ false
288
+ end
289
+
290
+ ##
291
+ # Validates whether an activity is considered 'wet', e.g. locker room.
292
+ #
293
+ # @param activity [:to_sym] a BTAP::Activity keyword, e.g. "locker::common"
294
+ #
295
+ # @return [Boolean] whether activity is 'wet'.
296
+ def wet?(activity = "")
297
+ return false unless activity.respond_to?(:to_sym)
298
+
299
+ activity = activity.to_s.strip.downcase
300
+ return true if activity.include?("locker")
301
+ return true if activity.include?("washroom")
302
+
303
+ false
304
+ end
305
+
306
+ ##
307
+ # Validates whether a space is targeted by ANSI/IES Recommended Practice RP28
308
+ # 2007 - see store.accuristech.com/standards/ies-rp-28-07?product_id=1555565.
309
+ #
310
+ # @param space [OpenStudio::Model::Space] an OpenStudio space
311
+ #
312
+ # @return [Boolean] true if RP28'ed
313
+ def rp28?(space = nil)
314
+ return false unless space.is_a?(OpenStudio::Model::Space)
315
+
316
+ # First check AdditionalProperty.
317
+ tag = "rp28"
318
+ rp28 = space.additionalProperties.getFeatureAsBoolean(tag)
319
+ return rp28.get unless rp28.empty?
320
+
321
+ # Check spacetype strings.
322
+ variants = ["rp28", "rp-28", "rp_28", "rp 28"]
323
+
324
+ unless space.spaceType.empty?
325
+ type = space.spaceType.get
326
+ tID = type.nameString.downcase
327
+
328
+ if variants.any? { |variant| tID.include?(variant) }
329
+ return space.additionalProperties.setFeature(tag, true)
330
+ end
331
+
332
+ unless type.standardsSpaceType.empty?
333
+ stype = type.standardsSpaceType.get.downcase
334
+
335
+ if variants.any? { |variant| stype.include?(variant) }
336
+ return space.additionalProperties.setFeature(tag, true)
337
+ end
338
+ end
339
+ end
340
+
341
+ # Check space identifier.
342
+ sID = space.nameString.downcase
343
+
344
+ if variants.any? { |variant| sID.include?(variant) }
345
+ return space.additionalProperties.setFeature(tag, true)
346
+ end
347
+
348
+ space.additionalProperties.setFeature(tag, false)
349
+
350
+ false
351
+ end
352
+
353
+ ##
354
+ # Validates whether a space is targeted for occupancy-sensing lighting
355
+ # control, per NECB2011.
356
+ #
357
+ # @param space [OpenStudio::Model::Space] an OpenStudio space
358
+ #
359
+ # @return [Boolean] true if occsensing
360
+ def occsensing_deprecated?(space = nil)
361
+ return false unless space.is_a?(OpenStudio::Model::Space)
362
+
363
+ # First check AdditionalProperty.
364
+ tag = "occsensing"
365
+ osns = space.additionalProperties.getFeatureAsBoolean(tag)
366
+ return osns.get unless osns.empty?
367
+
368
+ # Check spacetype strings.
369
+ variants = ["occ sens", "occ-sens", "occsens"]
370
+
371
+ unless space.spaceType.empty?
372
+ type = space.spaceType.get
373
+ tID = type.nameString.downcase
374
+
375
+ if variants.any? { |variant| tID.include?(variant) }
376
+ space.additionalProperties.setFeature(tag, true)
377
+ return true
378
+ end
379
+
380
+ unless type.standardsSpaceType.empty?
381
+ stype = type.standardsSpaceType.get.downcase
382
+
383
+ if variants.any? { |variant| stype.include?(variant) }
384
+ space.additionalProperties.setFeature(tag, true)
385
+ return true
386
+ end
387
+ end
388
+ end
389
+
390
+ # Check space identifier.
391
+ sID = space.nameString.downcase
392
+
393
+ if variants.any? { |variant| sID.include?(variant) }
394
+ space.additionalProperties.setFeature(tag, true)
395
+ return true
396
+ end
397
+
398
+ space.additionalProperties.setFeature(tag, false)
399
+
400
+ false
401
+ end
402
+
182
403
  ##
183
404
  # Returns BTAP Activity data.
184
405
  #
@@ -195,16 +416,16 @@ module BTAP
195
416
  class BTAP::Activity
196
417
  extend ActivityData
197
418
 
198
- # @return [String] assigned or inferred building ACTIVITY (e.g. "warehouse")
419
+ # @return [String] assigned or inferred building ACTIVITY, e.g. "warehouse"
199
420
  attr_reader :activity
200
421
 
201
- # @return [Hash] collection of space ACTIVITIES (e.g. "bulk::warehouse")
422
+ # @return [Hash] collection of space ACTIVITIES, e.g. "bulk::warehouse"
202
423
  attr_reader :activities
203
424
 
204
- # @return [String] building type CATEGORY (e.g. "industry")
425
+ # @return [String] building type CATEGORY, e.g. "industry"
205
426
  attr_reader :category
206
427
 
207
- # @return [Float] expected non-occupant liveload (e.g. 90 kg/m2)
428
+ # @return [Float] expected non-occupant liveload, e.g. 90 kg/m2
208
429
  attr_reader :liveload
209
430
 
210
431
  # @return [Hash] logged messages
@@ -215,7 +436,8 @@ module BTAP
215
436
  # Initialize BTAP Activity parameters.
216
437
  #
217
438
  # @param model [OpenStudio::Model::Model] a model
218
- def initialize(model = nil)
439
+ # @param option ["", "structure"] construction option
440
+ def initialize(model = nil, option = "")
219
441
  mth = "BTAP::Activity::#{__callee__}"
220
442
  @feedback = {logs: []}
221
443
  lgs = @feedback[:logs]
@@ -228,6 +450,8 @@ module BTAP
228
450
  return
229
451
  end
230
452
 
453
+ option = "" unless option == "structure"
454
+
231
455
  # Tag spaces as un/conditioned with "space_conditioning_category". For
232
456
  # now, this is simply determined based on whether spaces are:
233
457
  # - part of the total floor area (i.e. occupied)
@@ -252,7 +476,11 @@ module BTAP
252
476
  space.additionalProperties.setFeature(tag, "nonresconditioned")
253
477
  else
254
478
  if space.nameString.downcase.include?("attic")
255
- space.additionalProperties.setFeature(tag, "unconditioned")
479
+ if option.empty?
480
+ space.additionalProperties.setFeature(tag, "nonresconditioned")
481
+ else
482
+ space.additionalProperties.setFeature(tag, "unconditioned")
483
+ end
256
484
  else # treat all other cases as indirectly-conditioned e.g. plenums
257
485
  space.additionalProperties.setFeature(tag, "nonresconditioned")
258
486
  end
@@ -260,8 +488,8 @@ module BTAP
260
488
  end
261
489
 
262
490
  # Determine activities of occupied spaces in the model, then building.
263
- @activities = self.getSpaceActivities(model)
264
- @activity = self.getBuildingActivity(model)
491
+ @activities = self.spaceActivities(model)
492
+ @activity = self.buildingActivity(model)
265
493
  @liveload = data[:bldg][:activity][@activity][:liveload]
266
494
 
267
495
  # Assign building category.
@@ -269,6 +497,12 @@ module BTAP
269
497
  @category = data[:bldg][:activity][@activity][:category]
270
498
  end
271
499
 
500
+ # Assign schedules to auxiliary spaces - if present.
501
+ self.assignAuxiliarySchedules(self.buildingActivity?(model))
502
+
503
+ # Assign schedules to ancillary spaces - if present.
504
+ self.assignAncillarySchedules
505
+
272
506
  true
273
507
  end
274
508
 
@@ -278,10 +512,14 @@ module BTAP
278
512
  # @param model [OpenStudio::Model::Model] a model
279
513
  #
280
514
  # @return [Hash] a collection of space activities (see logs if empty)
281
- def getSpaceActivities(model = nil)
515
+ def spaceActivities(model = nil)
516
+ lgs = @feedback[:logs]
282
517
  mth = "BTAP::Activity::#{__callee__}"
283
- cl = OpenStudio::Model::Model
284
- return mismatch("model", model, cl, mth, DBG, {}) unless model.is_a?(cl)
518
+
519
+ unless model.is_a?(OpenStudio::Model::Model)
520
+ lgs << "Invalid or empty OpenStudio model (#{mth})"
521
+ return {}
522
+ end
285
523
 
286
524
  activities = {}
287
525
 
@@ -316,7 +554,7 @@ module BTAP
316
554
  fallbacks << fallback unless fallback.empty?
317
555
  end
318
556
 
319
- # Reject if matching excluded keywords.
557
+ # Reject if matching any excluded keyword.
320
558
  data[:space][:activity].each do |k, v|
321
559
  v[:excludes].each do |kword|
322
560
  candidates.delete(k) if standards.include?(kword)
@@ -339,11 +577,16 @@ module BTAP
339
577
  end
340
578
 
341
579
  entry = {}
342
- entry[:m2 ] = space.floorArea
580
+ entry[:m2 ] = space.floorArea * space.multiplier
343
581
  entry[:spacetype] = spacetype
344
582
  entry[:standards] = standards
583
+ entry[:keyword ] = candidate
345
584
  entry[:activity ] = data[:space][:activity][candidate][:activity]
346
585
  entry[:bldgtype ] = data[:space][:activity][candidate][:bldgtype]
586
+ entry[:density ] = data[:space][:activity][candidate][:density]
587
+ entry[:eqpload ] = data[:space][:activity][candidate][:eqpload]
588
+ entry[:swhload ] = data[:space][:activity][candidate][:swhload]
589
+ entry[:schedule ] = data[:space][:activity][candidate][:schedule]
347
590
 
348
591
  activities[space] = entry
349
592
  end
@@ -356,11 +599,15 @@ module BTAP
356
599
  #
357
600
  # @param model OpenStudio::Model::Model] a model
358
601
  #
359
- # @return [String] keyword describing a model's general activity
360
- def getBuildingActivity(model = nil)
602
+ # @return [String] a model's general activity (see logs if empty)
603
+ def buildingActivity(model = nil)
604
+ lgs = @feedback[:logs]
361
605
  mth = "BTAP::Activity::#{__callee__}"
362
- cl = OpenStudio::Model::Model
363
- return mismatch("model", model, cl, mth, DBG, "") unless model.is_a?(cl)
606
+
607
+ unless model.is_a?(OpenStudio::Model::Model)
608
+ lgs << "Invalid or empty OpenStudio model (#{mth})"
609
+ return "office"
610
+ end
364
611
 
365
612
  # OPTION A: Extract building activity from user-set 'additionalProperty'.
366
613
  tag = "btap_building_activity"
@@ -441,8 +688,7 @@ module BTAP
441
688
  # - "motion picture theatre"
442
689
  #
443
690
  # ... except for "auditorium". No NECB edition holds an "auditorium"
444
- # building type entry. For the moment, "auditorium" will be associated
445
- # with the ubiquitous high-school or college auditorium.
691
+ # building type entry. For the moment, "auditorium" == "theatre".
446
692
  if activity == "common"
447
693
  activities = {}
448
694
 
@@ -455,7 +701,7 @@ module BTAP
455
701
  end
456
702
 
457
703
  activity = case activities.sort.reverse.to_h.keys.first
458
- when "audience" then "school"
704
+ when "audience" then "theatre"
459
705
  when "sales" then "retail"
460
706
  when "dining" then "restaurant"
461
707
  when "cuisine" then "restaurant"
@@ -472,9 +718,560 @@ module BTAP
472
718
  end
473
719
  end
474
720
 
475
- activity = "office" if activity.empty?
721
+ # Log warning.
722
+ if activity.empty?
723
+ lgs << "Assigning building activity 'office' (#{mth})"
724
+ activity = "office"
725
+ end
726
+
727
+ activity = "hospital" if activity == "health"
476
728
 
477
729
  activity
478
730
  end
731
+
732
+ ##
733
+ # Validates whether user has explicitly set a valid building activity
734
+ # AdditionalProperty.
735
+ #
736
+ # @param model [OpenStudio::Model::Model] a model
737
+ #
738
+ # @return [Boolean] true if a valid building activity is set by user.
739
+ def buildingActivity?(model = nil)
740
+ lgs = @feedback[:logs]
741
+ mth = "BTAP::Activity::#{__callee__}"
742
+ cl = OpenStudio::Model::Model
743
+
744
+ unless model.is_a?(OpenStudio::Model::Model)
745
+ lgs << "Invalid or empty OpenStudio model (#{mth})"
746
+ return false
747
+ end
748
+
749
+ tag = "btap_building_activity"
750
+ bldg = model.getBuilding
751
+ activity = bldg.additionalProperties.getFeatureAsString(tag)
752
+ return false if activity.empty?
753
+
754
+ activity = activity.get.downcase
755
+ return false unless data[:bldg][:activities].include?(activity)
756
+
757
+ true
758
+ end
759
+
760
+ ##
761
+ # Collects list of neighbouring (side-by-side), non-ancillary spaces.
762
+ # Here, non-ancillary spaces include ancillary spaces that have valid
763
+ # assigned schedule sets.
764
+ #
765
+ # @param space [OpenStudio::Model::Space] a space
766
+ #
767
+ # @return [Array] neighbouring (non-ancillary) spaces
768
+ def neighbours(space = nil)
769
+ lgs = @feedback[:logs]
770
+ mth = "BTAP::Activity::#{__callee__}"
771
+ cl = OpenStudio::Model::Space
772
+
773
+ unless space.is_a?(OpenStudio::Model::Space)
774
+ lgs << "Invalid or empty OpenStudio space (#{mth})"
775
+ return {}
776
+ end
777
+
778
+ nghbours = []
779
+
780
+ space.surfaces.each do |s|
781
+ next unless s.surfaceType.downcase == "wall"
782
+
783
+ voisin = s.adjacentSurface
784
+ next if voisin.empty?
785
+
786
+ espace = voisin.get.space
787
+ next if espace.empty?
788
+
789
+ espace = espace.get
790
+ next if espace == space
791
+ next if nghbours.include?(espace)
792
+ next unless @activities.key?(espace)
793
+ next unless @activities[espace][:schedule] != "*"
794
+
795
+ nghbours << espace
796
+ end
797
+
798
+ nghbours
799
+ end
800
+
801
+ ##
802
+ # Sets schedules to auxiliary spaces.
803
+ #
804
+ # @param uset [Boolean] whether user has explicitly set a building activity.
805
+ #
806
+ # @return [Boolean] true if successful
807
+ def assignAuxiliarySchedules(uset = false)
808
+ lgs = @feedback[:logs]
809
+ mth = "BTAP::Activity::#{__callee__}"
810
+ bkup = "a"
811
+ uset = false unless [true, false].include?(uset)
812
+
813
+ if @@data[:bldg][:activity].key?(@activity)
814
+ bkup = @@data[:bldg][:activity][@activity][:schedule]
815
+ end
816
+
817
+ # Loop through auxiliary spaces (largest to smallest in floor area).
818
+ @activities.sort_by { |space, v| v[:m2] }.reverse.each do |space, v|
819
+ id = space.nameString
820
+ sch = self.schedule(space)
821
+ next unless self.auxiliary?(v[:keyword])
822
+
823
+ v[:schedule] = uset ? bkup : sch.delete("*")
824
+ end
825
+
826
+ true
827
+ end
828
+
829
+ ##
830
+ # Sets schedules to ancillary spaces.
831
+ #
832
+ # @return [Boolean] true if successful
833
+ def assignAncillarySchedules
834
+ lgs = @feedback[:logs]
835
+ mth = "BTAP::Activity::#{__callee__}"
836
+ bkup = "a"
837
+
838
+ if @@data[:bldg][:activity].key?(@activity)
839
+ bkup = @@data[:bldg][:activity][@activity][:schedule]
840
+ end
841
+
842
+ # Loop through ancillary spaces (largest to smallest in floor area).
843
+ @activities.sort_by { |space, v| v[:m2] }.reverse.each do |space, v|
844
+ id = space.nameString
845
+ next unless self.ancillary?(v[:keyword])
846
+
847
+ # Retrieve each space's non-ancillary neighbours.
848
+ schedules = {}
849
+ schedule = bkup
850
+
851
+ self.neighbours(space).each do |nghbour|
852
+ next unless @activities.key?(nghbour)
853
+
854
+ sched = @activities[nghbour][:schedule]
855
+ m2 = @activities[nghbour][:m2]
856
+
857
+ schedules[sched] = 0 unless schedules.key?(sched)
858
+ schedules[sched] += m2
859
+ end
860
+
861
+ if schedules.empty?
862
+ # Assign common building schedule if no non-ancillary adjacent spaces.
863
+ lgs << "Assigning BUILDING schedule #{bkup} for #{id} (#{mth})"
864
+ v[:schedule] = bkup
865
+ else
866
+ v[:schedule] = schedules.sort_by { |sched, m2| m2 }.reverse.first.first
867
+ end
868
+
869
+ # Forcing schedule set 'J' for ancillary spaces in:
870
+ # - short-term accomodation (e.g. hotels, firestation quarters)
871
+ # - housing (MURBs, dormitories)
872
+ v[:schedule] = "j" if ["f", "g"].include?(v[:schedule])
873
+ end
874
+
875
+ true
876
+ end
877
+
878
+ ##
879
+ # Returns a space's BTAP::Activity keyword, e.g. "corridor::hospital".
880
+ #
881
+ # @param space [OpenStudio::Model::Space] a space
882
+ #
883
+ # @return [String] a space's BTAP::Activity keyword - check logs if empty
884
+ def keyword(space = nil)
885
+ lgs = @feedback[:logs]
886
+ mth = "BTAP::Activity::#{__callee__}"
887
+ cl = OpenStudio::Model::Space
888
+
889
+ unless space.is_a?(OpenStudio::Model::Space)
890
+ lgs << "Invalid or empty OpenStudio space (#{mth})"
891
+ return ""
892
+ end
893
+
894
+ unless @activities.key?(space)
895
+ lgs << "Unlisted space #{space.nameString} (#{mth})"
896
+ return ""
897
+ end
898
+
899
+ @activities[space][:keyword]
900
+ end
901
+
902
+ ##
903
+ # Returns a space's activity, e.g. "corridor" in "corridor::hospital".
904
+ #
905
+ # @param space [OpenStudio::Model::Space] a space
906
+ #
907
+ # @return [String] a space's activity - check logs if empty
908
+ def act(space = nil)
909
+ lgs = @feedback[:logs]
910
+ mth = "BTAP::Activity::#{__callee__}"
911
+ cl = OpenStudio::Model::Space
912
+
913
+ unless space.is_a?(OpenStudio::Model::Space)
914
+ lgs << "Invalid or empty OpenStudio space (#{mth})"
915
+ return ""
916
+ end
917
+
918
+ unless @activities.key?(space)
919
+ lgs << "Unlisted space #{space.nameString} (#{mth})"
920
+ return ""
921
+ end
922
+
923
+ @activities[space][:activity]
924
+ end
925
+
926
+ ##
927
+ # Returns a space's building type, e.g. "hospital" in "corridor::hospital".
928
+ #
929
+ # @param space [OpenStudio::Model::Space] a space
930
+ #
931
+ # @return [String] a space's building type - check logs if empty
932
+ def bldg(space = nil)
933
+ lgs = @feedback[:logs]
934
+ mth = "BTAP::Activity::#{__callee__}"
935
+ cl = OpenStudio::Model::Space
936
+
937
+ unless space.is_a?(OpenStudio::Model::Space)
938
+ lgs << "Invalid or empty OpenStudio space (#{mth})"
939
+ return ""
940
+ end
941
+
942
+ unless @activities.key?(space)
943
+ lgs << "Unlisted space #{space.nameString} (#{mth})"
944
+ return ""
945
+ end
946
+
947
+ @activities[space][:bldgtype]
948
+ end
949
+
950
+ ##
951
+ # Returns a space's floor area (m2), factoring a space's multiplier.
952
+ #
953
+ # @param space [OpenStudio::Model::Space] a space
954
+ #
955
+ # @return [Float] space's floor area - check logs if 0 m2
956
+ def m2(space = nil)
957
+ lgs = @feedback[:logs]
958
+ mth = "BTAP::Activity::#{__callee__}"
959
+ cl = OpenStudio::Model::Space
960
+
961
+ unless space.is_a?(OpenStudio::Model::Space)
962
+ lgs << "Invalid or empty OpenStudio space (#{mth})"
963
+ return 0.0
964
+ end
965
+
966
+ unless @activities.key?(space)
967
+ lgs << "Unlisted space #{space.nameString} (#{mth})"
968
+ return 0.0
969
+ end
970
+
971
+ @activities[space][:m2]
972
+ end
973
+
974
+ ##
975
+ # Returns a space's occupant density (m2/occupant).
976
+ #
977
+ # @param space [OpenStudio::Model::Space] a space
978
+ #
979
+ # @return [Float] space's occupant density - check logs if 0 m2/occupant
980
+ def density(space = nil)
981
+ lgs = @feedback[:logs]
982
+ mth = "BTAP::Activity::#{__callee__}"
983
+ cl = OpenStudio::Model::Space
984
+
985
+ unless space.is_a?(OpenStudio::Model::Space)
986
+ lgs << "Invalid or empty OpenStudio space (#{mth})"
987
+ return 0.0
988
+ end
989
+
990
+ unless @activities.key?(space)
991
+ lgs << "Unlisted space #{space.nameString} (#{mth})"
992
+ return 0.0
993
+ end
994
+
995
+ @activities[space][:density]
996
+ end
997
+
998
+ ##
999
+ # Returns a space's peak receptacle load (W/m2).
1000
+ #
1001
+ # @param space [OpenStudio::Model::Space] a space
1002
+ #
1003
+ # @return [Float] space's peak equipment load - check logs if 0 W/m2
1004
+ def eqpWm2(space = nil)
1005
+ lgs = @feedback[:logs]
1006
+ mth = "BTAP::Activity::#{__callee__}"
1007
+ cl = OpenStudio::Model::Space
1008
+
1009
+ unless space.is_a?(OpenStudio::Model::Space)
1010
+ lgs << "Invalid or empty OpenStudio space (#{mth})"
1011
+ return 0.0
1012
+ end
1013
+
1014
+ unless @activities.key?(space)
1015
+ lgs << "Unlisted space #{space.nameString} (#{mth})"
1016
+ return 0.0
1017
+ end
1018
+
1019
+ @activities[space][:eqpload]
1020
+ end
1021
+
1022
+ ##
1023
+ # Returns a space's peak service water heating load (W/m2).
1024
+ #
1025
+ # @param space [OpenStudio::Model::Space] a space
1026
+ #
1027
+ # @return [Float] space's peak SWH load - check logs if 0 W/m2
1028
+ def swhWm2(space = nil)
1029
+ lgs = @feedback[:logs]
1030
+ mth = "BTAP::Activity::#{__callee__}"
1031
+ cl = OpenStudio::Model::Space
1032
+
1033
+ unless space.is_a?(OpenStudio::Model::Space)
1034
+ lgs << "Invalid or empty OpenStudio space (#{mth})"
1035
+ return 0.0
1036
+ end
1037
+
1038
+ unless @activities.key?(space)
1039
+ lgs << "Unlisted space #{space.nameString} (#{mth})"
1040
+ return 0.0
1041
+ end
1042
+
1043
+ @activities[space][:swhload]
1044
+ end
1045
+
1046
+ ##
1047
+ # Returns a space's (NECB-related) schedule set character, e.g. "a".
1048
+ #
1049
+ # @param space [OpenStudio::Model::Space] a space
1050
+ #
1051
+ # @return [String] schedule set. Check logs if empty string.
1052
+ def schedule(space = nil)
1053
+ lgs = @feedback[:logs]
1054
+ mth = "BTAP::Activity::#{__callee__}"
1055
+ cl = OpenStudio::Model::Space
1056
+
1057
+ unless space.is_a?(OpenStudio::Model::Space)
1058
+ lgs << "Invalid or empty OpenStudio space (#{mth})"
1059
+ return ""
1060
+ end
1061
+
1062
+ unless @activities.key?(space)
1063
+ lgs << "Unlisted space #{space.nameString} (#{mth})"
1064
+ return ""
1065
+ end
1066
+
1067
+ @activities[space][:schedule]
1068
+ end
1069
+
1070
+ ##
1071
+ # Validates whether a space is targeted for occupancy-sensing lighting
1072
+ # control, per NECB2011.
1073
+ #
1074
+ # @param space [OpenStudio::Model::Space] an OpenStudio space
1075
+ #
1076
+ # @return [Boolean] true if occsensing
1077
+ def occsensing?(space = nil)
1078
+ lgs = @feedback[:logs]
1079
+ mth = "BTAP::Activity::#{__callee__}"
1080
+ cl = OpenStudio::Model::Space
1081
+
1082
+ unless space.is_a?(OpenStudio::Model::Space)
1083
+ lgs << "Invalid or empty OpenStudio space (#{mth})"
1084
+ return false
1085
+ end
1086
+
1087
+ unless @activities.key?(space)
1088
+ lgs << "Unlisted space #{space.nameString} (#{mth})"
1089
+ return false
1090
+ end
1091
+
1092
+ activity = @activities[space][:keyword]
1093
+ area = space.floorArea
1094
+ return true if activity == "classroom::common"
1095
+ return true if activity == "classroom::penitentiary"
1096
+ return true if activity == "meeting::common"
1097
+ return true if activity == "lounge::common"
1098
+ return true if activity == "lounge::health"
1099
+ return true if activity == "storage::common" && area.round < 100
1100
+ return true if activity == "supplies::health" && area.round < 100
1101
+ return true if activity == "copiers::common"
1102
+ return true if activity == "office::common" && area.round < 25
1103
+ return true if activity == "washroom::common"
1104
+ return true if activity == "dressing::theatre"
1105
+ return true if activity == "dressing::retail"
1106
+ return true if activity == "locker::common"
1107
+
1108
+ false
1109
+ end
1110
+
1111
+ ##
1112
+ # Returns a space's "factor for occupancy control" of lighting (Focc), per
1113
+ # NECB 4.3.2.10.1 (2015-2025).
1114
+ #
1115
+ # @param space [OpenStudio::Model::Space] a space
1116
+ #
1117
+ # @return [Float] space's Focc of lighting - 1.0 if N/A.
1118
+ def fOCC(space = nil)
1119
+ lgs = @feedback[:logs]
1120
+ mth = "BTAP::Activity::#{__callee__}"
1121
+ cl = OpenStudio::Model::Space
1122
+
1123
+ unless space.is_a?(OpenStudio::Model::Space)
1124
+ lgs << "Invalid or empty OpenStudio space (#{mth})"
1125
+ return 1.0
1126
+ end
1127
+
1128
+ unless @activities.key?(space)
1129
+ lgs << "Unlisted space #{space.nameString} (#{mth})"
1130
+ return 1.0
1131
+ end
1132
+
1133
+ activity = @activities[space][:keyword]
1134
+
1135
+ # Focc = 1 - Ca x Cocc, where:
1136
+ # Ca = factor to account for the relative absence of occupants
1137
+ # Cocc = factor to account for the occupancy-sensing mechanism
1138
+ cA = case activity
1139
+ when "audience::convention" then 0.2
1140
+ when "audience::religious" then 0.3
1141
+ when "classroom::common" then 0.5
1142
+ when "classroom::penitentiary" then 0.5
1143
+ when "computer::common" then 0.7
1144
+ when "server::common" then 0.7
1145
+ when "meeting::common" then 0.5
1146
+ when "copiers::common" then 0.2
1147
+ when "tribunal::court" then 0.2
1148
+ when "dressing::theatre" then 0.4
1149
+ when "mechanical::common" then 0.9
1150
+ when "emergency::common" then 0.5
1151
+ when "teachinglab::common" then 0.4
1152
+ when "locker::common" then 0.5
1153
+ when "openplan::common" then 0.2
1154
+ when "office::common" then 0.3
1155
+ when "garage::parking" then 0.4
1156
+ when "storage::common" then 0.6
1157
+ when "washroom::common" then 0.5
1158
+ when "supplies::health" then 0.5
1159
+ when "exam::health" then 0.3
1160
+ when "operating::health" then 0.1
1161
+ when "patient::health" then 0.1
1162
+ when "therapy::health" then 0.2
1163
+ when "equipment::manufacturing" then 0.2
1164
+ when "restoration::museum" then 0.3
1165
+ when "exhibit::museum" then 0.2
1166
+ when "refectory::religious" then 0.3
1167
+ when "pulpit::religious" then 0.1
1168
+ when "dressing::retail" then 0.4
1169
+ when "chapel::religious" then 0.5
1170
+ when "recreation::common" then 0.2
1171
+ when "bulk::warehouse" then 0.5
1172
+ when "fine::warehouse" then 0.5
1173
+ else 0.0
1174
+ end
1175
+
1176
+ return 1.0 if cA.round(1) == 0.0
1177
+
1178
+ # NECB 2025 Table 4.3.2.10.-B : Cocc
1179
+ # _____________________________________________________________ ____
1180
+ # automatic FULL OFF (FULL ON) 0.67
1181
+ # automatic FULL OFF (manual ON or automatic PARTIAL ON) 0.75
1182
+ # automatic PARTIAL OFF (manual ON) 0.34
1183
+ # manual (ON/OFF or bi-level) – enclosed office less than 25 m2 0.30
1184
+ # manual – all other spaces 0.10
1185
+ # none 0.00
1186
+ #
1187
+ # The conditional application of lighting controls is often subject to
1188
+ # professional judgment (as described in the NECB). Automating their
1189
+ # assignment (e.g. Cocc within BTAP) becomes particularly challenging.
1190
+ #
1191
+ # The following are tentative postulates governing the automated
1192
+ # application of Cocc - needs a deeper dive, @todo:
1193
+ #
1194
+ # - almost all spaces require manual ON
1195
+ # - almost all spaces require automated or scheduled OFF (only)
1196
+ # - so by default, spaces with Ca > 0 (see above): Cocc of 0.75
1197
+ #
1198
+ # - spaces with automated PARTIAL OFF control have a Cocc of 0.34
1199
+ #
1200
+ # - all other (unlisted) spaces: Cocc of 0.10.
1201
+ cOCC = case activity
1202
+ when "audience::convention" then 0.75
1203
+ when "audience::religious" then 0.75
1204
+ when "classroom::common" then 0.75
1205
+ when "classroom::penitentiary" then 0.75
1206
+ when "computer::common" then 0.75
1207
+ when "server::common" then 0.75
1208
+ when "meeting::common" then 0.75
1209
+ when "copiers::common" then 0.75
1210
+ when "tribunal::court" then 0.75
1211
+ when "dressing::theatre" then 0.75
1212
+ when "mechanical::common" then 0.75
1213
+ when "emergency::common" then 0.75
1214
+ when "teachinglab::common" then 0.34 #
1215
+ when "locker::common" then 0.75
1216
+ when "openplan::common" then 0.75
1217
+ when "office::common" then 0.75
1218
+ when "garage::parking" then 0.34 #
1219
+ when "storage::common" then 0.75
1220
+ when "washroom::common" then 0.75
1221
+ when "supplies::health" then 0.75
1222
+ when "exam::health" then 0.75
1223
+ when "operating::health" then 0.75
1224
+ when "patient::health" then 0.75
1225
+ when "therapy::health" then 0.75
1226
+ when "equipment::manufacturing" then 0.75
1227
+ when "restoration::museum" then 0.75
1228
+ when "exhibit::museum" then 0.75
1229
+ when "refectory::religious" then 0.75
1230
+ when "pulpit::religious" then 0.75
1231
+ when "dressing::retail" then 0.75
1232
+ when "chapel::religious" then 0.75
1233
+ when "recreation::common" then 0.75
1234
+ when "bulk::warehouse" then 0.34 #
1235
+ when "fine::warehouse" then 0.34 #
1236
+ else 0.10
1237
+ end
1238
+
1239
+ # Reset cOCC if small enclosed offices.
1240
+ cOCC = 0.30 if activity == "office::common" && space.floorArea < 25
1241
+
1242
+ 1.0 - cA * cOCC
1243
+ end
1244
+
1245
+ ##
1246
+ # Returns a space's "factor for personal control" of lighting (Fpers),
1247
+ # per NECB 4.3.2.10.2 (2015-2025).
1248
+ #
1249
+ # @param space [OpenStudio::Model::Space] a space
1250
+ #
1251
+ # @return [Float] space's Fpers of lighting - 1.0 if N/A.
1252
+ def fPERS(space = nil)
1253
+ lgs = @feedback[:logs]
1254
+ mth = "BTAP::Activity::#{__callee__}"
1255
+ cl = OpenStudio::Model::Space
1256
+
1257
+ unless space.is_a?(OpenStudio::Model::Space)
1258
+ lgs << "Invalid or empty OpenStudio space (#{mth})"
1259
+ return 1.0
1260
+ end
1261
+
1262
+ unless @activities.key?(space)
1263
+ lgs << "Unlisted space #{space.nameString} (#{mth})"
1264
+ return 1.0
1265
+ end
1266
+
1267
+ activity = @activities[space][:keyword]
1268
+
1269
+ return 0.9 if activity == "teachinglab::common"
1270
+ return 0.9 if activity == "openplan::common"
1271
+ return 0.9 if activity == "office::common"
1272
+ return 0.9 if activity == "patient::health"
1273
+
1274
+ 1.0
1275
+ end
479
1276
  end
480
1277
  end