openstudio-extension 0.1.0 → 0.1.1

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -0
  3. data/.rubocop.yml +9 -0
  4. data/Gemfile +3 -1
  5. data/Jenkinsfile +10 -0
  6. data/README.md +230 -12
  7. data/Rakefile +88 -3
  8. data/bin/console +3 -3
  9. data/doc_templates/LICENSE.md +27 -0
  10. data/doc_templates/README.md.erb +42 -0
  11. data/doc_templates/copyright_erb.txt +36 -0
  12. data/doc_templates/copyright_js.txt +4 -0
  13. data/doc_templates/copyright_ruby.txt +34 -0
  14. data/init_templates/README.md +37 -0
  15. data/init_templates/gemspec.txt +32 -0
  16. data/init_templates/openstudio_module.rb +50 -0
  17. data/init_templates/spec.rb +47 -0
  18. data/init_templates/spec_helper.rb +49 -0
  19. data/init_templates/template_gemfile.txt +17 -0
  20. data/init_templates/template_rakefile.txt +15 -0
  21. data/init_templates/version.rb +40 -0
  22. data/lib/files/openstudio-extension-gem-test.ddy +536 -0
  23. data/lib/files/openstudio-extension-gem-test.epw +8768 -0
  24. data/lib/files/openstudio-extension-gem-test.stat +554 -0
  25. data/lib/measures/openstudio_extension_test_measure/LICENSE.md +27 -0
  26. data/lib/measures/openstudio_extension_test_measure/README.md +26 -0
  27. data/lib/measures/openstudio_extension_test_measure/README.md.erb +42 -0
  28. data/lib/measures/openstudio_extension_test_measure/measure.rb +72 -0
  29. data/lib/measures/openstudio_extension_test_measure/measure.xml +83 -0
  30. data/lib/measures/openstudio_extension_test_measure/resources/os_lib_helper_methods.rb +399 -0
  31. data/lib/measures/openstudio_extension_test_measure/tests/OpenStudioExtensionTestMeasure_Test.rb +75 -0
  32. data/lib/openstudio/extension.rb +220 -0
  33. data/lib/openstudio/extension/core/CreateResults.rb +879 -0
  34. data/lib/openstudio/extension/core/check_air_sys_temps.rb +190 -0
  35. data/lib/openstudio/extension/core/check_calibration.rb +155 -0
  36. data/lib/openstudio/extension/core/check_cond_zns.rb +84 -0
  37. data/lib/openstudio/extension/core/check_domestic_hot_water.rb +334 -0
  38. data/lib/openstudio/extension/core/check_envelope_conductance.rb +453 -0
  39. data/lib/openstudio/extension/core/check_eui_by_end_use.rb +162 -0
  40. data/lib/openstudio/extension/core/check_eui_reasonableness.rb +135 -0
  41. data/lib/openstudio/extension/core/check_fan_pwr.rb +98 -0
  42. data/lib/openstudio/extension/core/check_internal_loads.rb +393 -0
  43. data/lib/openstudio/extension/core/check_mech_sys_capacity.rb +226 -0
  44. data/lib/openstudio/extension/core/check_mech_sys_efficiency.rb +326 -0
  45. data/lib/openstudio/extension/core/check_mech_sys_part_load_eff.rb +464 -0
  46. data/lib/openstudio/extension/core/check_mech_sys_type.rb +139 -0
  47. data/lib/openstudio/extension/core/check_part_loads.rb +451 -0
  48. data/lib/openstudio/extension/core/check_placeholder.rb +75 -0
  49. data/lib/openstudio/extension/core/check_plant_cap.rb +123 -0
  50. data/lib/openstudio/extension/core/check_plant_temps.rb +159 -0
  51. data/lib/openstudio/extension/core/check_plenum_loads.rb +87 -0
  52. data/lib/openstudio/extension/core/check_pump_pwr.rb +108 -0
  53. data/lib/openstudio/extension/core/check_sch_coord.rb +241 -0
  54. data/lib/openstudio/extension/core/check_schedules.rb +311 -0
  55. data/lib/openstudio/extension/core/check_simultaneous_heating_and_cooling.rb +158 -0
  56. data/lib/openstudio/extension/core/check_supply_air_and_thermostat_temp_difference.rb +148 -0
  57. data/lib/openstudio/extension/core/check_weather_files.rb +132 -0
  58. data/lib/openstudio/extension/core/deer_vintages.rb +311 -0
  59. data/lib/openstudio/extension/core/os_lib_aedg_measures.rb +491 -0
  60. data/lib/openstudio/extension/core/os_lib_cofee.rb +259 -0
  61. data/lib/openstudio/extension/core/os_lib_constructions.rb +378 -0
  62. data/lib/openstudio/extension/core/os_lib_geometry.rb +1022 -0
  63. data/lib/openstudio/extension/core/os_lib_helper_methods.rb +399 -0
  64. data/lib/openstudio/extension/core/os_lib_hvac.rb +2171 -0
  65. data/lib/openstudio/extension/core/os_lib_lighting_and_equipment.rb +214 -0
  66. data/lib/openstudio/extension/core/os_lib_model_generation.rb +817 -0
  67. data/lib/openstudio/extension/core/os_lib_model_simplification.rb +1049 -0
  68. data/lib/openstudio/extension/core/os_lib_outdoorair_and_infiltration.rb +165 -0
  69. data/lib/openstudio/extension/core/os_lib_reporting.rb +4652 -0
  70. data/lib/openstudio/extension/core/os_lib_reporting_qaqc.rb +200 -0
  71. data/lib/openstudio/extension/core/os_lib_schedules.rb +963 -0
  72. data/lib/openstudio/extension/rake_task.rb +149 -0
  73. data/lib/openstudio/extension/runner.rb +644 -0
  74. data/lib/openstudio/extension/version.rb +40 -0
  75. data/openstudio-extension.gemspec +20 -15
  76. metadata +150 -14
  77. data/.travis.yml +0 -7
  78. data/lib/OpenStudio/Extension/rake_task.rb +0 -84
  79. data/lib/OpenStudio/Extension/version.rb +0 -33
  80. data/lib/OpenStudio/extension.rb +0 -65
@@ -0,0 +1,162 @@
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) 2008-2019, Alliance for Sustainable Energy, LLC.
3
+ # All rights reserved.
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # (1) Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # (3) Neither the name of the copyright holder nor the names of any contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission from the respective party.
17
+ #
18
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
19
+ # of modifications or other derivative works may not use the "OpenStudio"
20
+ # trademark, "OS", "os", or any other confusingly similar designation without
21
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
24
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25
+ # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
27
+ # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
28
+ # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
30
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ # *******************************************************************************
35
+
36
+ module OsLib_QAQC
37
+ # include any general notes about QAQC method here
38
+
39
+ # checks the number of unmet hours in the model
40
+ def check_eui_by_end_use(category, target_standard, min_pass, max_pass, name_only = false)
41
+ # summary of the check
42
+ check_elems = OpenStudio::AttributeVector.new
43
+ check_elems << OpenStudio::Attribute.new('name', 'End Use by Category')
44
+ check_elems << OpenStudio::Attribute.new('category', category)
45
+ check_elems << OpenStudio::Attribute.new('description', "Check end use by category against #{target_standard} DOE prototype buildings.")
46
+
47
+ # stop here if only name is requested this is used to populate display name for arguments
48
+ if name_only == true
49
+ results = []
50
+ check_elems.each do |elem|
51
+ results << elem.valueAsString
52
+ end
53
+ return results
54
+ end
55
+
56
+ # Versions of OpenStudio greater than 2.4.0 use a modified version of
57
+ # openstudio-standards with different method calls. These methods
58
+ # require a "Standard" object instead of the standard being passed into method calls.
59
+ # This Standard object is used throughout the QAQC check.
60
+ if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3')
61
+ use_old_gem_code = true
62
+ else
63
+ use_old_gem_code = false
64
+ std = Standard.build(target_standard)
65
+ end
66
+
67
+ begin
68
+ # total building area
69
+ query = 'SELECT Value FROM tabulardatawithstrings WHERE '
70
+ query << "ReportName='AnnualBuildingUtilityPerformanceSummary' and "
71
+ query << "ReportForString='Entire Facility' and "
72
+ query << "TableName='Building Area' and "
73
+ query << "RowName='Total Building Area' and "
74
+ query << "ColumnName='Area' and "
75
+ query << "Units='m2';"
76
+ query_results = @sql.execAndReturnFirstDouble(query)
77
+ if query_results.empty?
78
+ check_elems << OpenStudio::Attribute.new('flag', "Can't calculate EUI, SQL query for building area failed.")
79
+ return OpenStudio::Attribute.new('check', check_elems)
80
+ else
81
+ energy_plus_area = query_results.get
82
+ end
83
+
84
+ # temp code to check OS vs. E+ area
85
+ open_studio_area = @model.getBuilding.floorArea
86
+ if (energy_plus_area - open_studio_area).abs >= 0.1
87
+ check_elems << OpenStudio::Attribute.new('flag', "EnergyPlus reported area is #{energy_plus_area} (m^2). OpenStudio reported area is #{@model.getBuilding.floorArea} (m^2).")
88
+ end
89
+
90
+ # loop through end uses and gather consumption, normalized by floor area
91
+ actual_eui_by_end_use = {}
92
+ OpenStudio::EndUseCategoryType.getValues.each do |end_use|
93
+ # get end uses
94
+ end_use = OpenStudio::EndUseCategoryType.new(end_use).valueDescription
95
+ query_elec = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'Electricity'"
96
+ query_gas = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'Natural Gas'"
97
+ query_add = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'Additional Fuel'"
98
+ query_dc = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'District Cooling'"
99
+ query_dh = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'District Heating'"
100
+ results_elec = @sql.execAndReturnFirstDouble(query_elec).get
101
+ results_gas = @sql.execAndReturnFirstDouble(query_gas).get
102
+ results_add = @sql.execAndReturnFirstDouble(query_add).get
103
+ results_dc = @sql.execAndReturnFirstDouble(query_dc).get
104
+ results_dh = @sql.execAndReturnFirstDouble(query_dh).get
105
+ total_end_use = results_elec + results_gas + results_add + results_dc + results_dh
106
+
107
+ # populate hash for actual end use normalized by area
108
+ actual_eui_by_end_use[end_use] = total_end_use / energy_plus_area
109
+ end
110
+
111
+ # gather target end uses for given standard as hash
112
+ if use_old_gem_code
113
+ target_eui_by_end_use = @model.find_target_eui_by_end_use(target_standard)
114
+ else
115
+ std = Standard.build(target_standard)
116
+ target_eui_by_end_use = std.model_find_target_eui_by_end_use(@model)
117
+ end
118
+
119
+ # units for flag display text and unit conversion
120
+ source_units = 'GJ/m^2'
121
+ target_units = 'kBtu/ft^2'
122
+
123
+ # check acutal vs. target against tolerance
124
+ if !target_eui_by_end_use.nil?
125
+ actual_eui_by_end_use.each do |end_use, value|
126
+ # this should have value of 0 in model. This page change in the future. It doesn't exist in target lookup
127
+ next if end_use == 'Exterior Equipment'
128
+
129
+ # perform check and issue flags as needed
130
+ target_value = target_eui_by_end_use[end_use]
131
+ eui_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(value, source_units, target_units).get, 5, true)
132
+ target_eui_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(target_value, source_units, target_units).get, 1, true)
133
+
134
+ # add in use case specific logic to skip checks when near 0 actual and target
135
+ skip = false
136
+ if (end_use == 'Heat Recovery') && (value < 0.05) && (target_value < 0.05) then skip = true end
137
+ if (end_use == 'Pumps') && (value < 0.05) && (target_value < 0.05) then skip = true end
138
+
139
+ if (value < target_value * (1.0 - min_pass)) && !skip
140
+ check_elems << OpenStudio::Attribute.new('flag', "#{end_use} EUI of #{eui_ip_neat} (#{target_units}) is less than #{min_pass * 100} % below the expected #{end_use} EUI of #{target_eui_ip_neat} (#{target_units}) for #{target_standard}.")
141
+ elsif (value > target_value * (1.0 + max_pass)) && !skip
142
+ check_elems << OpenStudio::Attribute.new('flag', "#{end_use} EUI of #{eui_ip_neat} (#{target_units}) is more than #{max_pass * 100} % above the expected #{end_use} EUI of #{target_eui_ip_neat} (#{target_units}) for #{target_standard}.")
143
+ end
144
+ end
145
+ else
146
+ check_elems << OpenStudio::Attribute.new('flag', "Can't calculate target end use EUIs. Make sure model has expected climate zone and building type.")
147
+ end
148
+ rescue StandardError => e
149
+ # brief description of ruby error
150
+ check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
151
+
152
+ # backtrace of ruby error for diagnostic use
153
+ if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
154
+ end
155
+
156
+ # add check_elms to new attribute
157
+ check_elem = OpenStudio::Attribute.new('check', check_elems)
158
+
159
+ return check_elem
160
+ # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
161
+ end
162
+ end
@@ -0,0 +1,135 @@
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) 2008-2019, Alliance for Sustainable Energy, LLC.
3
+ # All rights reserved.
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # (1) Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # (3) Neither the name of the copyright holder nor the names of any contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission from the respective party.
17
+ #
18
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
19
+ # of modifications or other derivative works may not use the "OpenStudio"
20
+ # trademark, "OS", "os", or any other confusingly similar designation without
21
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
24
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25
+ # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
27
+ # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
28
+ # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
30
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ # *******************************************************************************
35
+
36
+ module OsLib_QAQC
37
+ # include any general notes about QAQC method here
38
+
39
+ # checks the number of unmet hours in the model
40
+ def check_eui_reasonableness(category, target_standard, min_pass, max_pass, name_only = false)
41
+ # summary of the check
42
+ check_elems = OpenStudio::AttributeVector.new
43
+ check_elems << OpenStudio::Attribute.new('name', 'EUI Reasonableness')
44
+ check_elems << OpenStudio::Attribute.new('category', category)
45
+ check_elems << OpenStudio::Attribute.new('description', "Check EUI for model against #{target_standard} DOE prototype buildings.")
46
+
47
+ # stop here if only name is requested this is used to populate display name for arguments
48
+ if name_only == true
49
+ results = []
50
+ check_elems.each do |elem|
51
+ results << elem.valueAsString
52
+ end
53
+ return results
54
+ end
55
+
56
+ # Versions of OpenStudio greater than 2.4.0 use a modified version of
57
+ # openstudio-standards with different method calls. These methods
58
+ # require a "Standard" object instead of the standard being passed into method calls.
59
+ # This Standard object is used throughout the QAQC check.
60
+ if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3')
61
+ use_old_gem_code = true
62
+ else
63
+ use_old_gem_code = false
64
+ std = Standard.build(target_standard)
65
+ end
66
+
67
+ begin
68
+ # total building area
69
+ query = 'SELECT Value FROM tabulardatawithstrings WHERE '
70
+ query << "ReportName='AnnualBuildingUtilityPerformanceSummary' and "
71
+ query << "ReportForString='Entire Facility' and "
72
+ query << "TableName='Building Area' and "
73
+ query << "RowName='Total Building Area' and "
74
+ query << "ColumnName='Area' and "
75
+ query << "Units='m2';"
76
+ query_results = @sql.execAndReturnFirstDouble(query)
77
+ if query_results.empty?
78
+ check_elems << OpenStudio::Attribute.new('flag', "Can't calculate EUI, SQL query for building area failed.")
79
+ return OpenStudio::Attribute.new('check', check_elems)
80
+ else
81
+ energy_plus_area = query_results.get
82
+ end
83
+
84
+ # temp code to check OS vs. E+ area
85
+ open_studio_area = @model.getBuilding.floorArea
86
+ if (energy_plus_area - open_studio_area).abs >= 0.1
87
+ check_elems << OpenStudio::Attribute.new('flag', "EnergyPlus reported area is #{energy_plus_area} (m^2). OpenStudio reported area is #{@model.getBuilding.floorArea} (m^2).")
88
+ end
89
+
90
+ # EUI
91
+ source_units = 'GJ/m^2'
92
+ target_units = 'kBtu/ft^2'
93
+ if energy_plus_area > 0.0 # don't calculate EUI if building doesn't have any area
94
+ # todo - netSiteEnergy deducts for renewable. May want to update this to show gross consumption vs. net consumption
95
+ eui = @sql.netSiteEnergy.get / energy_plus_area
96
+ else
97
+ check_elems << OpenStudio::Attribute.new('flag', "Can't calculate model EUI, building doesn't have any floor area.")
98
+ return OpenStudio::Attribute.new('check', check_elems)
99
+ end
100
+
101
+ # test using new method
102
+ if use_old_gem_code
103
+ target_eui = @model.find_target_eui(target_standard)
104
+ else
105
+ std = Standard.build(target_standard)
106
+ target_eui = std.model_find_target_eui(@model)
107
+ end
108
+
109
+ # check model vs. target for user specified tolerance.
110
+ if !target_eui.nil?
111
+ eui_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(eui, source_units, target_units).get, 1, true)
112
+ target_eui_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(target_eui, source_units, target_units).get, 1, true)
113
+ if eui < target_eui * (1.0 - min_pass)
114
+ check_elems << OpenStudio::Attribute.new('flag', "Model EUI of #{eui_ip_neat} (#{target_units}) is less than #{min_pass * 100} % below the expected EUI of #{target_eui_ip_neat} (#{target_units}) for #{target_standard}.")
115
+ elsif eui > target_eui * (1.0 + max_pass)
116
+ check_elems << OpenStudio::Attribute.new('flag', "Model EUI of #{eui_ip_neat} (#{target_units}) is more than #{max_pass * 100} % above the expected EUI of #{target_eui_ip_neat} (#{target_units}) for #{target_standard}.")
117
+ end
118
+ else
119
+ check_elems << OpenStudio::Attribute.new('flag', "Can't calculate target EUI. Make sure model has expected climate zone and building type.")
120
+ end
121
+ rescue StandardError => e
122
+ # brief description of ruby error
123
+ check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
124
+
125
+ # backtrace of ruby error for diagnostic use
126
+ if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
127
+ end
128
+
129
+ # add check_elms to new attribute
130
+ check_elem = OpenStudio::Attribute.new('check', check_elems)
131
+
132
+ return check_elem
133
+ # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
134
+ end
135
+ end
@@ -0,0 +1,98 @@
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) 2008-2019, Alliance for Sustainable Energy, LLC.
3
+ # All rights reserved.
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # (1) Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # (3) Neither the name of the copyright holder nor the names of any contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission from the respective party.
17
+ #
18
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
19
+ # of modifications or other derivative works may not use the "OpenStudio"
20
+ # trademark, "OS", "os", or any other confusingly similar designation without
21
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
24
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25
+ # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
27
+ # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
28
+ # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
30
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ # *******************************************************************************
35
+
36
+ module OsLib_QAQC
37
+ # Check the fan power (W/cfm) for each air loop fan in the model to identify
38
+ # unrealistically sized fans.
39
+ def check_fan_pwr(category, target_standard, max_pwr_delta = 0.1, name_only = false)
40
+ # summary of the check
41
+ check_elems = OpenStudio::AttributeVector.new
42
+ check_elems << OpenStudio::Attribute.new('name', 'Fan Power')
43
+ check_elems << OpenStudio::Attribute.new('category', category)
44
+ check_elems << OpenStudio::Attribute.new('description', 'Check that fan power vs flow makes sense.')
45
+
46
+ # stop here if only name is requested this is used to populate display name for arguments
47
+ if name_only == true
48
+ results = []
49
+ check_elems.each do |elem|
50
+ results << elem.valueAsString
51
+ end
52
+ return results
53
+ end
54
+
55
+ std = Standard.build(target_standard)
56
+
57
+ begin
58
+ # Check each air loop
59
+ @model.getAirLoopHVACs.each do |plant_loop|
60
+ # Set the expected W/cfm
61
+ expected_w_per_cfm = 1.1
62
+
63
+ # Check the W/cfm for each fan on each air loop
64
+ plant_loop.supplyComponents.each do |sc|
65
+ # Get the W/cfm for the fan
66
+ obj_type = sc.iddObjectType.valueName.to_s
67
+ case obj_type
68
+ when 'OS_Fan_ConstantVolume'
69
+ actual_w_per_cfm = std.fan_rated_w_per_cfm(sc.to_FanConstantVolume.get)
70
+ when 'OS_Fan_OnOff'
71
+ actual_w_per_cfm = std.fan_rated_w_per_cfm(sc.to_FanOnOff.get)
72
+ when 'OS_Fan_VariableVolume'
73
+ actual_w_per_cfm = std.fan_rated_w_per_cfm(sc.to_FanVariableVolume.get)
74
+ else
75
+ next # Skip non-fan objects
76
+ end
77
+
78
+ # Compare W/cfm to expected/typical values
79
+ if ((expected_w_per_cfm - actual_w_per_cfm) / actual_w_per_cfm).abs > max_pwr_delta
80
+ check_elems << OpenStudio::Attribute.new('flag', "For #{sc.name} on #{plant_loop.name}, the actual fan power of #{actual_w_per_cfm.round(1)} W/cfm is more than #{(max_pwr_delta * 100.0).round(2)}% different from the expected #{expected_w_per_cfm} W/cfm.")
81
+ end
82
+ end
83
+ end
84
+ rescue StandardError => e
85
+ # brief description of ruby error
86
+ check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
87
+
88
+ # backtrace of ruby error for diagnostic use
89
+ if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
90
+ end
91
+
92
+ # add check_elms to new attribute
93
+ check_elem = OpenStudio::Attribute.new('check', check_elems)
94
+
95
+ return check_elem
96
+ # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
97
+ end
98
+ end
@@ -0,0 +1,393 @@
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) 2008-2019, Alliance for Sustainable Energy, LLC.
3
+ # All rights reserved.
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # (1) Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # (3) Neither the name of the copyright holder nor the names of any contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission from the respective party.
17
+ #
18
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
19
+ # of modifications or other derivative works may not use the "OpenStudio"
20
+ # trademark, "OS", "os", or any other confusingly similar designation without
21
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
24
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25
+ # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
27
+ # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
28
+ # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
30
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ # *******************************************************************************
35
+
36
+ module OsLib_QAQC
37
+ # include any general notes about QAQC method here
38
+
39
+ # checks the number of unmet hours in the model
40
+ def check_internal_loads(category, target_standard, min_pass, max_pass, name_only = false)
41
+ # summary of the check
42
+ check_elems = OpenStudio::AttributeVector.new
43
+ check_elems << OpenStudio::Attribute.new('name', 'Internal Loads')
44
+ check_elems << OpenStudio::Attribute.new('category', category)
45
+ if target_standard == 'ICC IECC 2015'
46
+ check_elems << OpenStudio::Attribute.new('description', 'Check internal loads against Table R405.5.2(1) in ICC IECC 2015 Residential Provisions.')
47
+ else
48
+ if target_standard.include?('90.1')
49
+ display_standard = "ASHRAE #{target_standard}"
50
+ else
51
+ display_standard = target_standard
52
+ end
53
+ check_elems << OpenStudio::Attribute.new('description', "Check LPD, ventilation rates, occupant density, plug loads, and equipment loads against #{display_standard} and DOE Prototype buildings.")
54
+ end
55
+
56
+ # stop here if only name is requested this is used to populate display name for arguments
57
+ if name_only == true
58
+ results = []
59
+ check_elems.each do |elem|
60
+ results << elem.valueAsString
61
+ end
62
+ return results
63
+ end
64
+
65
+ # Versions of OpenStudio greater than 2.4.0 use a modified version of
66
+ # openstudio-standards with different method calls. These methods
67
+ # require a "Standard" object instead of the standard being passed into method calls.
68
+ # This Standard object is used throughout the QAQC check.
69
+ if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3')
70
+ use_old_gem_code = true
71
+ else
72
+ use_old_gem_code = false
73
+ std = Standard.build(target_standard)
74
+ end
75
+
76
+ begin
77
+ if target_standard == 'ICC IECC 2015'
78
+
79
+ num_people = 0.0
80
+ @model.getSpaceTypes.each do |space_type|
81
+ next if !space_type.standardsSpaceType.is_initialized
82
+ next if space_type.standardsSpaceType.get != 'Apartment' # currently only supports midrise apt space type
83
+ space_type_floor_area = space_type.floorArea
84
+ space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area)
85
+ num_people += space_type_num_people
86
+ end
87
+
88
+ # lookup iecc internal loads for the building
89
+ bedrooms_per_unit = 2.0 # assumption
90
+ num_units = num_people / 2.5 # Avg 2.5 units per person.
91
+ if use_old_gem_code
92
+ target_loads_hash = @model.find_icc_iecc_2015_internal_loads(num_units, bedrooms_per_unit)
93
+ else
94
+ target_loads_hash = std.model_find_icc_iecc_2015_internal_loads(@model, num_units, bedrooms_per_unit)
95
+ end
96
+
97
+ # get model internal gains for lights, elec equipment, and gas equipment
98
+ model_internal_gains_si = 0.0
99
+ query_eleint_lights = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= 'Interior Lighting' and ColumnName= 'Electricity'"
100
+ query_elec_equip = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= 'Interior Equipment' and ColumnName= 'Electricity'"
101
+ query_gas_equip = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= 'Interior Equipment' and ColumnName= 'Natural Gas'"
102
+ model_internal_gains_si += results_elec = @sql.execAndReturnFirstDouble(query_eleint_lights).get
103
+ model_internal_gains_si += results_elec = @sql.execAndReturnFirstDouble(query_elec_equip).get
104
+ model_internal_gains_si += results_elec = @sql.execAndReturnFirstDouble(query_gas_equip).get
105
+ model_internal_gains_si_kbtu_per_day = OpenStudio.convert(model_internal_gains_si, 'GJ', 'kBtu').get / 365.0 # assumes annual run
106
+
107
+ # get target internal loads
108
+ target_igain_btu_per_day = target_loads_hash['igain_btu_per_day']
109
+ target_igain_kbtu_per_day = OpenStudio.convert(target_igain_btu_per_day, 'Btu', 'kBtu').get
110
+
111
+ # check internal loads
112
+ if model_internal_gains_si_kbtu_per_day < target_igain_kbtu_per_day * (1.0 - min_pass)
113
+ check_elems << OpenStudio::Attribute.new('flag', "The model average of #{OpenStudio.toNeatString(model_internal_gains_si_kbtu_per_day, 2, true)} (kBtu/day) is more than #{min_pass * 100} % below the expected value of #{OpenStudio.toNeatString(target_igain_kbtu_per_day, 2, true)} (kBtu/day) for #{target_standard}.")
114
+ elsif model_internal_gains_si_kbtu_per_day > target_igain_kbtu_per_day * (1.0 + max_pass)
115
+ check_elems << OpenStudio::Attribute.new('flag', "The model average of #{OpenStudio.toNeatString(model_internal_gains_si_kbtu_per_day, 2, true)} (kBtu/day) is more than #{max_pass * 100} % above the expected value of #{OpenStudio.toNeatString(target_igain_kbtu_per_day, 2, true)} k(Btu/day) for #{target_standard}.")
116
+ end
117
+
118
+ # get target mech vent
119
+ target_mech_vent_cfm = target_loads_hash['mech_vent_cfm']
120
+
121
+ # get model mech vent
122
+ model_mech_vent_si = 0
123
+ @model.getSpaceTypes.each do |space_type|
124
+ next if space_type.floorArea <= 0
125
+
126
+ # get necessary space type information
127
+ floor_area = space_type.floorArea
128
+ num_people = space_type.getNumberOfPeople(floor_area)
129
+
130
+ # get volume for space type for use with ventilation and infiltration
131
+ space_type_volume = 0.0
132
+ space_type_exterior_area = 0.0
133
+ space_type_exterior_wall_area = 0.0
134
+ space_type.spaces.each do |space|
135
+ space_type_volume += space.volume * space.multiplier
136
+ space_type_exterior_area = space.exteriorArea * space.multiplier
137
+ space_type_exterior_wall_area = space.exteriorWallArea * space.multiplier
138
+ end
139
+
140
+ # get design spec OA object
141
+ if space_type.designSpecificationOutdoorAir.is_initialized
142
+ oa = space_type.designSpecificationOutdoorAir.get
143
+ oa_method = oa.outdoorAirMethod
144
+ oa_per_person = oa.outdoorAirFlowperPerson * num_people
145
+ oa_ach = oa.outdoorAirFlowAirChangesperHour * space_type_volume
146
+ oa_per_area = oa.outdoorAirFlowperFloorArea * floor_area
147
+ oa_flow_rate = oa.outdoorAirFlowRate
148
+ oa_space_type_total = oa_per_person + oa_ach + oa_per_area + oa_flow_rate
149
+
150
+ value_count = 0
151
+ if oa_per_person > 0 then value_count += 1 end
152
+ if oa_ach > 0 then value_count += 1 end
153
+ if oa_per_area > 0 then value_count += 1 end
154
+ if oa_flow_rate > 0 then value_count += 1 end
155
+ if (oa_method != 'Sum') && (value_count > 1)
156
+ check_elems << OpenStudio::Attribute.new('flag', "Outdoor Air Method for #{space_type.name} was #{oa_method}. Expected value was Sum.")
157
+ end
158
+ else
159
+ oa_space_type_total = 0.0
160
+ end
161
+ # add to building total oa
162
+ model_mech_vent_si += oa_space_type_total
163
+ end
164
+
165
+ # check oa
166
+ model_mech_vent_cfm = OpenStudio.convert(model_mech_vent_si, 'm^3/s', 'cfm').get
167
+ if model_mech_vent_cfm < target_mech_vent_cfm * (1.0 - min_pass)
168
+ check_elems << OpenStudio::Attribute.new('flag', "The model mechanical ventilation of #{OpenStudio.toNeatString(model_mech_vent_cfm, 2, true)} cfm is more than #{min_pass * 100} % below the expected value of #{OpenStudio.toNeatString(target_mech_vent_cfm, 2, true)} cfm for #{target_standard}.")
169
+ elsif model_mech_vent_cfm > target_mech_vent_cfm * (1.0 + max_pass)
170
+ check_elems << OpenStudio::Attribute.new('flag', "The model mechanical ventilation of #{OpenStudio.toNeatString(model_mech_vent_cfm, 2, true)} cfm is more than #{max_pass * 100} % above the expected value of #{OpenStudio.toNeatString(target_mech_vent_cfm, 2, true)} cfm for #{target_standard}.")
171
+ end
172
+
173
+ else
174
+
175
+ # loop through all space types used in the model
176
+ @model.getSpaceTypes.each do |space_type|
177
+ next if space_type.floorArea <= 0
178
+
179
+ # get necessary space type information
180
+ floor_area = space_type.floorArea
181
+ num_people = space_type.getNumberOfPeople(floor_area)
182
+
183
+ # load in standard info for this space type
184
+ if use_old_gem_code
185
+ data = space_type.get_standards_data(target_standard)
186
+ else
187
+ data = std.space_type_get_standards_data(space_type)
188
+ end
189
+
190
+ if data.nil? || data.empty?
191
+
192
+ # skip if all spaces using this space type are plenums
193
+ all_spaces_plenums = true
194
+ space_type.spaces.each do |space|
195
+ if use_old_gem_code
196
+ if !space.plenum?
197
+ all_spaces_plenums = false
198
+ next
199
+ end
200
+ else
201
+ if !std.space_plenum?(space)
202
+ all_spaces_plenums = false
203
+ next
204
+ end
205
+ end
206
+ end
207
+
208
+ if !all_spaces_plenums
209
+ check_elems << OpenStudio::Attribute.new('flag', "Unexpected standards type for #{space_type.name}, can't validate internal loads.")
210
+ end
211
+
212
+ next
213
+ end
214
+
215
+ # check lpd for space type
216
+ model_lights_si = space_type.getLightingPowerPerFloorArea(floor_area, num_people)
217
+ data['lighting_per_area'].nil? ? (target_lights_ip = 0.0) : (target_lights_ip = data['lighting_per_area'])
218
+ source_units = 'W/m^2'
219
+ target_units = 'W/ft^2'
220
+ load_type = 'Lighting Power Density'
221
+ model_ip = OpenStudio.convert(model_lights_si, source_units, target_units).get
222
+ target_ip = target_lights_ip.to_f
223
+ model_ip_neat = OpenStudio.toNeatString(model_ip, 2, true)
224
+ target_ip_neat = OpenStudio.toNeatString(target_ip, 2, true)
225
+ if model_ip < target_ip * (1.0 - min_pass)
226
+ check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass * 100} % below the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.")
227
+ elsif model_ip > target_ip * (1.0 + max_pass)
228
+ check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass * 100} % above the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.")
229
+ end
230
+
231
+ # check electric equipment
232
+ model_elec_si = space_type.getElectricEquipmentPowerPerFloorArea(floor_area, num_people)
233
+ data['electric_equipment_per_area'].nil? ? (target_elec_ip = 0.0) : (target_elec_ip = data['electric_equipment_per_area'])
234
+ source_units = 'W/m^2'
235
+ target_units = 'W/ft^2'
236
+ load_type = 'Electric Power Density'
237
+ model_ip = OpenStudio.convert(model_elec_si, source_units, target_units).get
238
+ target_ip = target_elec_ip.to_f
239
+ model_ip_neat = OpenStudio.toNeatString(model_ip, 2, true)
240
+ target_ip_neat = OpenStudio.toNeatString(target_ip, 2, true)
241
+ if model_ip < target_ip * (1.0 - min_pass)
242
+ check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass * 100} % below the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.")
243
+ elsif model_ip > target_ip * (1.0 + max_pass)
244
+ check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass * 100} % above the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.")
245
+ end
246
+
247
+ # check gas equipment
248
+ model_gas_si = space_type.getGasEquipmentPowerPerFloorArea(floor_area, num_people)
249
+ data['gas_equipment_per_area'].nil? ? (target_gas_ip = 0.0) : (target_gas_ip = data['gas_equipment_per_area'])
250
+ source_units = 'W/m^2'
251
+ target_units = 'Btu/hr*ft^2'
252
+ load_type = 'Gas Power Density'
253
+ model_ip = OpenStudio.convert(model_gas_si, source_units, target_units).get
254
+ target_ip = target_gas_ip.to_f
255
+ model_ip_neat = OpenStudio.toNeatString(model_ip, 2, true)
256
+ target_ip_neat = OpenStudio.toNeatString(target_ip, 2, true)
257
+ if model_ip < target_ip * (1.0 - min_pass)
258
+ check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass * 100} % below the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.")
259
+ elsif model_ip > target_ip * (1.0 + max_pass)
260
+ check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass * 100} % above the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.")
261
+ end
262
+
263
+ # check people
264
+ model_occ_si = space_type.getPeoplePerFloorArea(floor_area)
265
+ data['occupancy_per_area'].nil? ? (target_occ_ip = 0.0) : (target_occ_ip = data['occupancy_per_area'])
266
+ source_units = '1/m^2' # people/m^2
267
+ target_units = '1/ft^2' # people per ft^2 (can't add *1000) to the bottom, need to do later
268
+ load_type = 'Occupancy per Area'
269
+ model_ip = OpenStudio.convert(model_occ_si, source_units, target_units).get * 1000.0
270
+ target_ip = target_occ_ip.to_f
271
+ model_ip_neat = OpenStudio.toNeatString(model_ip, 2, true)
272
+ target_ip_neat = OpenStudio.toNeatString(target_ip, 2, true)
273
+ # for people need to update target units just for display. Can't be used for converstion.
274
+ target_units = 'People/1000 ft^2'
275
+ if model_ip < target_ip * (1.0 - min_pass)
276
+ check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass * 100} % below the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.")
277
+ elsif model_ip > target_ip * (1.0 + max_pass)
278
+ check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass * 100} % above the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.")
279
+ end
280
+
281
+ # get volume for space type for use with ventilation and infiltration
282
+ space_type_volume = 0.0
283
+ space_type_exterior_area = 0.0
284
+ space_type_exterior_wall_area = 0.0
285
+ space_type.spaces.each do |space|
286
+ space_type_volume += space.volume * space.multiplier
287
+ space_type_exterior_area = space.exteriorArea * space.multiplier
288
+ space_type_exterior_wall_area = space.exteriorWallArea * space.multiplier
289
+ end
290
+
291
+ # get design spec OA object
292
+ if space_type.designSpecificationOutdoorAir.is_initialized
293
+ oa = space_type.designSpecificationOutdoorAir.get
294
+ oa_method = oa.outdoorAirMethod
295
+ oa_per_person = oa.outdoorAirFlowperPerson
296
+ oa_other = 0.0
297
+ oa_ach = oa.outdoorAirFlowAirChangesperHour * space_type_volume
298
+ oa_per_area = oa.outdoorAirFlowperFloorArea * floor_area
299
+ oa_flow_rate = oa.outdoorAirFlowRate
300
+ oa_total = oa_ach + oa_per_area + oa_flow_rate
301
+
302
+ value_count = 0
303
+ if oa_per_person > 0 then value_count += 1 end
304
+ if oa_ach > 0 then value_count += 1 end
305
+ if oa_per_area > 0 then value_count += 1 end
306
+ if oa_flow_rate > 0 then value_count += 1 end
307
+ if (oa_method != 'Sum') && (value_count > 1)
308
+ check_elems << OpenStudio::Attribute.new('flag', "Outdoor Air Method for #{space_type.name} was #{oa_method}. Expected value was Sum.")
309
+ end
310
+ else
311
+ oa_per_person = 0.0
312
+ oa_other_total = 0.0
313
+ end
314
+
315
+ # get target values for OA
316
+ target_oa_per_person_ip = data['ventilation_per_person'].to_f # ft^3/min*person
317
+ target_oa_ach_ip = data['ventilation_air_changes'].to_f # ach
318
+ target_oa_per_area_ip = data['ventilation_per_area'].to_f # ft^3/min*ft^2
319
+ if target_oa_per_person_ip.nil?
320
+ target_oa_per_person_si = 0.0
321
+ else
322
+ target_oa_per_person_si = OpenStudio.convert(target_oa_per_person_ip, 'cfm', 'm^3/s').get
323
+ end
324
+ if target_oa_ach_ip.nil?
325
+ target_oa_ach_si = 0.0
326
+ else
327
+ target_oa_ach_si = target_oa_ach_ip * space_type_volume
328
+ end
329
+ if target_oa_per_area_ip.nil?
330
+ target_oa_per_area_si = 0.0
331
+ else
332
+ target_oa_per_area_si = OpenStudio.convert(target_oa_per_area_ip, 'cfm/ft^2', 'm^3/s*m^2').get * floor_area
333
+ end
334
+ target_oa_total = target_oa_ach_si + target_oa_per_area_si
335
+
336
+ # check oa per person
337
+ source_units = 'm^3/s'
338
+ target_units = 'cfm'
339
+ load_type = 'Outdoor Air Per Person'
340
+ model_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(oa_per_person, source_units, target_units).get, 2, true)
341
+ target_ip_neat = OpenStudio.toNeatString(target_oa_per_person_ip, 2, true)
342
+ if oa_per_person < target_oa_per_person_si * (1.0 - min_pass)
343
+ check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass * 100} % below the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.")
344
+ elsif oa_per_person > target_oa_per_person_si * (1.0 + max_pass)
345
+ check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass * 100} % above the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.")
346
+ end
347
+
348
+ # check other oa
349
+ source_units = 'm^3/s'
350
+ target_units = 'cfm'
351
+ load_type = 'Outdoor Air (Excluding per Person Value)'
352
+ model_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(oa_total, source_units, target_units).get, 2, true)
353
+ target_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(target_oa_total, source_units, target_units).get, 2, true)
354
+ if oa_total < target_oa_total * (1.0 - min_pass)
355
+ check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass * 100} % below the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.")
356
+ elsif oa_total > target_oa_total * (1.0 + max_pass)
357
+ check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass * 100} % above the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.")
358
+ end
359
+ end
360
+
361
+ # warn if there are spaces in model that don't use space type unless they appear to be plenums
362
+ @model.getSpaces.each do |space|
363
+ if use_old_gem_code
364
+ next if space.plenum?
365
+ else
366
+ next if std.space_plenum?(space)
367
+ end
368
+
369
+ if !space.spaceType.is_initialized
370
+ check_elems << OpenStudio::Attribute.new('flag', "#{space.name} doesn't have a space type assigned, can't validate internal loads.")
371
+ end
372
+ end
373
+
374
+ # TODO: - need to address internal loads where fuel is variable like cooking and laundry
375
+ # todo - For now we are not going to loop through spaces looking for loads beyond what comes from space type
376
+ # todo - space infiltration
377
+
378
+ end
379
+ rescue StandardError => e
380
+ # brief description of ruby error
381
+ check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
382
+
383
+ # backtrace of ruby error for diagnostic use
384
+ if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
385
+ end
386
+
387
+ # add check_elms to new attribute
388
+ check_elem = OpenStudio::Attribute.new('check', check_elems)
389
+
390
+ return check_elem
391
+ # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
392
+ end
393
+ end