urbanopt-scenario 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 (84) hide show
  1. checksums.yaml +7 -0
  2. data/.github/CONTRIBUTING.md +58 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
  5. data/.github/pull_request_template.md +23 -0
  6. data/.gitignore +26 -0
  7. data/.rdoc_options +36 -0
  8. data/.rspec +3 -0
  9. data/.rubocop.yml +10 -0
  10. data/.travis.yml +23 -0
  11. data/CHANGELOG.md +19 -0
  12. data/Gemfile +43 -0
  13. data/Jenkinsfile +10 -0
  14. data/LICENSE.md +27 -0
  15. data/RDOC_MAIN.md +39 -0
  16. data/README.md +39 -0
  17. data/Rakefile +51 -0
  18. data/deploy_docs.sh +5 -0
  19. data/doc_templates/LICENSE.md +27 -0
  20. data/doc_templates/README.md.erb +42 -0
  21. data/doc_templates/copyright_erb.txt +31 -0
  22. data/doc_templates/copyright_js.txt +4 -0
  23. data/doc_templates/copyright_ruby.txt +29 -0
  24. data/docs/.gitignore +3 -0
  25. data/docs/.vuepress/components/InnerJsonSchema.vue +84 -0
  26. data/docs/.vuepress/components/JsonSchema.vue +12 -0
  27. data/docs/.vuepress/components/ScenarioSchema.vue +12 -0
  28. data/docs/.vuepress/components/StaticLink.vue +8 -0
  29. data/docs/.vuepress/config.js +15 -0
  30. data/docs/.vuepress/highlight.js +8 -0
  31. data/docs/.vuepress/public/custom_rdoc_styles.css +74 -0
  32. data/docs/.vuepress/utils.js +17 -0
  33. data/docs/README.md +39 -0
  34. data/docs/package-lock.json +11817 -0
  35. data/docs/package.json +26 -0
  36. data/docs/schemas/scenario-schema.md +3 -0
  37. data/lib/change_log.rb +147 -0
  38. data/lib/measures/.rubocop.yml +5 -0
  39. data/lib/measures/default_feature_reports/LICENSE.md +27 -0
  40. data/lib/measures/default_feature_reports/README.md +56 -0
  41. data/lib/measures/default_feature_reports/README.md.erb +42 -0
  42. data/lib/measures/default_feature_reports/measure.rb +742 -0
  43. data/lib/measures/default_feature_reports/measure.xml +139 -0
  44. data/lib/measures/default_feature_reports/tests/USA_CO_Golden-NREL.724666_TMY3.epw +8768 -0
  45. data/lib/measures/default_feature_reports/tests/default_feature_reports_test.rb +238 -0
  46. data/lib/measures/default_feature_reports/tests/example_model.osm +4378 -0
  47. data/lib/urbanopt-scenario.rb +31 -0
  48. data/lib/urbanopt/scenario.rb +45 -0
  49. data/lib/urbanopt/scenario/default_reports.rb +40 -0
  50. data/lib/urbanopt/scenario/default_reports/construction_cost.rb +169 -0
  51. data/lib/urbanopt/scenario/default_reports/date.rb +97 -0
  52. data/lib/urbanopt/scenario/default_reports/distributed_generation.rb +187 -0
  53. data/lib/urbanopt/scenario/default_reports/end_use.rb +159 -0
  54. data/lib/urbanopt/scenario/default_reports/end_uses.rb +140 -0
  55. data/lib/urbanopt/scenario/default_reports/feature_report.rb +213 -0
  56. data/lib/urbanopt/scenario/default_reports/generator.rb +92 -0
  57. data/lib/urbanopt/scenario/default_reports/location.rb +99 -0
  58. data/lib/urbanopt/scenario/default_reports/logger.rb +44 -0
  59. data/lib/urbanopt/scenario/default_reports/program.rb +261 -0
  60. data/lib/urbanopt/scenario/default_reports/reporting_period.rb +298 -0
  61. data/lib/urbanopt/scenario/default_reports/scenario_report.rb +300 -0
  62. data/lib/urbanopt/scenario/default_reports/schema/README.md +34 -0
  63. data/lib/urbanopt/scenario/default_reports/schema/scenario_csv_columns.txt +13 -0
  64. data/lib/urbanopt/scenario/default_reports/schema/scenario_schema.json +830 -0
  65. data/lib/urbanopt/scenario/default_reports/solar_pv.rb +92 -0
  66. data/lib/urbanopt/scenario/default_reports/storage.rb +105 -0
  67. data/lib/urbanopt/scenario/default_reports/timeseries_csv.rb +258 -0
  68. data/lib/urbanopt/scenario/default_reports/validator.rb +97 -0
  69. data/lib/urbanopt/scenario/default_reports/wind.rb +92 -0
  70. data/lib/urbanopt/scenario/extension.rb +63 -0
  71. data/lib/urbanopt/scenario/logger.rb +42 -0
  72. data/lib/urbanopt/scenario/scenario_base.rb +79 -0
  73. data/lib/urbanopt/scenario/scenario_csv.rb +122 -0
  74. data/lib/urbanopt/scenario/scenario_datapoint_base.rb +162 -0
  75. data/lib/urbanopt/scenario/scenario_post_processor_base.rb +69 -0
  76. data/lib/urbanopt/scenario/scenario_post_processor_default.rb +98 -0
  77. data/lib/urbanopt/scenario/scenario_runner_base.rb +63 -0
  78. data/lib/urbanopt/scenario/scenario_runner_osw.rb +158 -0
  79. data/lib/urbanopt/scenario/simulation_dir_base.rb +90 -0
  80. data/lib/urbanopt/scenario/simulation_dir_osw.rb +261 -0
  81. data/lib/urbanopt/scenario/simulation_mapper_base.rb +47 -0
  82. data/lib/urbanopt/scenario/version.rb +35 -0
  83. data/urbanopt-scenario-gem.gemspec +38 -0
  84. metadata +251 -0
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "urbanopt-scenario-gem-docs",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "dev": "vuepress dev",
8
+ "build": "vuepress build",
9
+ "deploy": "gh-pages -d .vuepress/dist"
10
+ },
11
+ "author": "NREL",
12
+ "dependencies": {
13
+ "highlight.js": "^9.15.6",
14
+ "json-schema-ref-parser": "^6.1.0",
15
+ "json-schema-view-js": "git+https://git@github.com/bgschiller/json-schema-view-js.git",
16
+ "vuepress": "^0.14.10",
17
+ "webpack-dev-middleware": "^3.6.0"
18
+ },
19
+ "devDependencies": {
20
+ "braces": ">=2.3.1",
21
+ "gh-pages": "^2.0.1",
22
+ "js-yaml": ">=3.13.1",
23
+ "serialize-javascript": ">=2.1.1",
24
+ "set-value": ">=2.0.1"
25
+ }
26
+ }
@@ -0,0 +1,3 @@
1
+ # Scenario Schema
2
+
3
+ <ScenarioSchema />
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'github_api'
4
+ require 'date'
5
+ require 'optparse'
6
+ require 'optparse/date'
7
+
8
+ # Instructions:
9
+ # Get a token from github's settings (https://github.com/settings/tokens)
10
+ #
11
+ # Example:
12
+ # ruby change_log.rb -t abcdefghijklmnopqrstuvwxyz -s 2017-09-06
13
+ #
14
+
15
+ options = {}
16
+ OptionParser.new do |opts|
17
+ opts.banner = "Usage: change_log.rb [options]\n" \
18
+ 'Prints New, Open, Closed Issues, and number of accepted PRs'
19
+ opts.separator ''
20
+
21
+ # defaults, go back 90 days
22
+ options[:start_date] = Date.today - 90
23
+ options[:end_date] = Date.today
24
+
25
+ opts.on('-s', '--start-date [DATE]', Date, 'Start of data (e.g. 2017-09-06)') do |v|
26
+ options[:start_date] = v
27
+ end
28
+ opts.on('-e', '--end-date [DATE]', Date, 'End of data (e.g. 2017-09-13)') do |v|
29
+ options[:end_date] = v
30
+ end
31
+ opts.on('-t', '--token [String]', String, 'Github API Token') do |v|
32
+ options[:token] = v
33
+ end
34
+ end.parse!
35
+
36
+ # Convert dates to time objects
37
+ options[:start_date] = Time.parse(options[:start_date].to_s)
38
+ options[:end_date] = Time.parse(options[:end_date].to_s)
39
+ puts options
40
+
41
+ ### Repository options
42
+ repo_owner = 'URBANopt'
43
+ repo = 'urbanopt-scenario-gem'
44
+
45
+ github = Github.new
46
+ if options[:token]
47
+ puts 'Using github token'
48
+ github = Github.new oauth_token: options[:token]
49
+ end
50
+
51
+ total_open_issues = []
52
+ total_open_pull_requests = []
53
+ new_issues = []
54
+ closed_issues = []
55
+ accepted_pull_requests = []
56
+
57
+ def get_num(issue)
58
+ issue.html_url.split('/')[-1].to_i
59
+ end
60
+
61
+ def get_issue_num(issue)
62
+ "\##{get_num(issue)}"
63
+ end
64
+
65
+ def get_html_url(issue)
66
+ issue.html_url
67
+ end
68
+
69
+ def get_title(issue)
70
+ issue.title
71
+ end
72
+
73
+ def print_issue(issue)
74
+ is_feature = false
75
+ issue.labels.each { |label| is_feature = true if label.name == 'Feature Request' }
76
+
77
+ if is_feature
78
+ "- Improved [#{get_issue_num(issue)}]( #{get_html_url(issue)} ), #{get_title(issue)}"
79
+ else
80
+ "- Fixed [#{get_issue_num(issue)}]( #{get_html_url(issue)} ), #{get_title(issue)}"
81
+ end
82
+ end
83
+
84
+ # Process Open Issues
85
+ results = -1
86
+ page = 1
87
+ while results != 0
88
+ resp = github.issues.list user: repo_owner, repo: repo, sort: 'created', direction: 'asc',
89
+ state: 'open', per_page: 100, page: page
90
+ results = resp.length
91
+ resp.env[:body].each do |issue, _index|
92
+ created = Time.parse(issue.created_at)
93
+ if !issue.key?(:pull_request)
94
+ total_open_issues << issue
95
+ if created >= options[:start_date] && created <= options[:end_date]
96
+ new_issues << issue
97
+ end
98
+ else
99
+ total_open_pull_requests << issue
100
+ end
101
+ end
102
+
103
+ page += 1
104
+ end
105
+
106
+ # Process Closed Issues
107
+ results = -1
108
+ page = 1
109
+ while results != 0
110
+ resp = github.issues.list user: repo_owner, repo: repo, sort: 'created', direction: 'asc',
111
+ state: 'closed', per_page: 100, page: page
112
+ results = resp.length
113
+ resp.env[:body].each do |issue, _index|
114
+ created = Time.parse(issue.created_at)
115
+ closed = Time.parse(issue.closed_at)
116
+ if !issue.key?(:pull_request)
117
+ if created >= options[:start_date] && created <= options[:end_date]
118
+ new_issues << issue
119
+ end
120
+ if closed >= options[:start_date] && closed <= options[:end_date]
121
+ closed_issues << issue
122
+ end
123
+ elsif closed >= options[:start_date] && closed <= options[:end_date]
124
+ accepted_pull_requests << issue
125
+ end
126
+ end
127
+
128
+ page += 1
129
+ end
130
+
131
+ closed_issues.sort! { |x, y| get_num(x) <=> get_num(y) }
132
+ new_issues.sort! { |x, y| get_num(x) <=> get_num(y) }
133
+ accepted_pull_requests.sort! { |x, y| get_num(x) <=> get_num(y) }
134
+ total_open_pull_requests.sort! { |x, y| get_num(x) <=> get_num(y) }
135
+
136
+ puts "Total Open Issues: #{total_open_issues.length}"
137
+ puts "Total Open Pull Requests: #{total_open_pull_requests.length}"
138
+ puts "\nDate Range: #{options[:start_date].strftime('%m/%d/%y')} - #{options[:end_date].strftime('%m/%d/%y')}:"
139
+ puts "\nNew Issues: #{new_issues.length} (" + new_issues.map { |issue| get_issue_num(issue) }.join(', ') + ')'
140
+
141
+ puts "\nClosed Issues: #{closed_issues.length}"
142
+ closed_issues.each { |issue| puts print_issue(issue) }
143
+
144
+ puts "\nAccepted Pull Requests: #{accepted_pull_requests.length}"
145
+ accepted_pull_requests.each { |issue| puts print_issue(issue) }
146
+
147
+ puts "\nAll Open Issues: #{total_open_issues.length} (" + total_open_issues.map { |issue| get_issue_num(issue) }.join(', ') + ')'
@@ -0,0 +1,5 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'spec/test_measures/**/*'
4
+ inherit_from:
5
+ - http://s3.amazonaws.com/openstudio-resources/styles/rubocop.yml
@@ -0,0 +1,27 @@
1
+ URBANopt, Copyright (c) 2019, Alliance for Sustainable Energy, LLC, and other
2
+ contributors. All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ Redistributions of source code must retain the above copyright notice, this list
8
+ of conditions and the following disclaimer.
9
+
10
+ Redistributions in binary form must reproduce the above copyright notice, this
11
+ list of conditions and the following disclaimer in the documentation and/or other
12
+ materials provided with the distribution.
13
+
14
+ Neither the name of the copyright holder nor the names of its contributors may be
15
+ used to endorse or promote products derived from this software without specific
16
+ prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
26
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
27
+ OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,56 @@
1
+
2
+
3
+ ###### (Automatically generated documentation)
4
+
5
+ # DefaultFeatureReports
6
+
7
+ ## Description
8
+ Writes default_feature_reports.json file used by URBANopt Scenario Default Post Processor
9
+
10
+ ## Modeler Description
11
+ This measure only allows for one feature_report per simulation. If multiple features are simulated in a single simulation, a new measure must be written to disaggregate simulation results to multiple features.
12
+
13
+ ## Measure Type
14
+ ReportingMeasure
15
+
16
+ ## Taxonomy
17
+
18
+
19
+ ## Arguments
20
+
21
+
22
+ ### Feature unique identifier
23
+
24
+ **Name:** feature_id,
25
+ **Type:** String,
26
+ **Units:** ,
27
+ **Required:** false,
28
+ **Model Dependent:** false
29
+
30
+ ### Feature scenario specific name
31
+
32
+ **Name:** feature_name,
33
+ **Type:** String,
34
+ **Units:** ,
35
+ **Required:** false,
36
+ **Model Dependent:** false
37
+
38
+ ### URBANopt Feature Type
39
+
40
+ **Name:** feature_type,
41
+ **Type:** String,
42
+ **Units:** ,
43
+ **Required:** false,
44
+ **Model Dependent:** false
45
+
46
+ ### Reporting Frequency
47
+ The frequency at which to report timeseries output data.
48
+ **Name:** reporting_frequency,
49
+ **Type:** Choice,
50
+ **Units:** ,
51
+ **Required:** true,
52
+ **Model Dependent:** false
53
+
54
+
55
+
56
+
@@ -0,0 +1,42 @@
1
+ <%#= README.md.erb is used to auto-generate README.md. %>
2
+ <%#= To manually maintain README.md throw away README.md.erb and manually edit README.md %>
3
+ ###### (Automatically generated documentation)
4
+
5
+ # <%= name %>
6
+
7
+ ## Description
8
+ <%= description %>
9
+
10
+ ## Modeler Description
11
+ <%= modelerDescription %>
12
+
13
+ ## Measure Type
14
+ <%= measureType %>
15
+
16
+ ## Taxonomy
17
+ <%= taxonomy %>
18
+
19
+ ## Arguments
20
+
21
+ <% arguments.each do |argument| %>
22
+ ### <%= argument[:display_name] %>
23
+ <%= argument[:description] %>
24
+ **Name:** <%= argument[:name] %>,
25
+ **Type:** <%= argument[:type] %>,
26
+ **Units:** <%= argument[:units] %>,
27
+ **Required:** <%= argument[:required] %>,
28
+ **Model Dependent:** <%= argument[:model_dependent] %>
29
+ <% end %>
30
+
31
+ <% if arguments.size == 0 %>
32
+ <%= "This measure does not have any user arguments" %>
33
+ <% end %>
34
+
35
+ <% if outputs.size > 0 %>
36
+ ## Outputs
37
+ <% output_names = [] %>
38
+ <% outputs.each do |output| %>
39
+ <% output_names << output[:display_name] %>
40
+ <% end %>
41
+ <%= output_names.join(", ") %>
42
+ <% end %>
@@ -0,0 +1,742 @@
1
+ # *********************************************************************************
2
+ # URBANopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
3
+ # contributors. All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without modification,
6
+ # are permitted provided that the following conditions are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice, this list
9
+ # of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright notice, this
12
+ # list of conditions and the following disclaimer in the documentation and/or other
13
+ # materials provided with the distribution.
14
+ #
15
+ # Neither the name of the copyright holder nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without specific
17
+ # prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22
+ # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24
+ # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
+ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28
+ # OF THE POSSIBILITY OF SUCH DAMAGE.
29
+ # *********************************************************************************
30
+
31
+ require 'urbanopt/scenario/default_reports/feature_report'
32
+ require 'csv'
33
+ require 'benchmark'
34
+ require 'logger'
35
+
36
+ @@logger = Logger.new(STDOUT)
37
+
38
+ # start the measure
39
+ class DefaultFeatureReports < OpenStudio::Measure::ReportingMeasure
40
+ # human readable name
41
+ def name
42
+ return 'DefaultFeatureReports'
43
+ end
44
+
45
+ # human readable description
46
+ def description
47
+ return 'Writes default_feature_reports.json file used by URBANopt Scenario Default Post Processor'
48
+ end
49
+
50
+ # human readable description of modeling approach
51
+ def modeler_description
52
+ return 'This measure only allows for one feature_report per simulation. If multiple features are simulated in a single simulation, a new measure must be written to disaggregate simulation results to multiple features.'
53
+ end
54
+
55
+ # define the arguments that the user will input
56
+ def arguments
57
+ args = OpenStudio::Measure::OSArgumentVector.new
58
+
59
+ id = OpenStudio::Measure::OSArgument.makeStringArgument('feature_id', false)
60
+ id.setDisplayName('Feature unique identifier')
61
+ id.setDefaultValue('1')
62
+ args << id
63
+
64
+ name = OpenStudio::Measure::OSArgument.makeStringArgument('feature_name', false)
65
+ name.setDisplayName('Feature scenario specific name')
66
+ name.setDefaultValue('name')
67
+ args << name
68
+
69
+ feature_type = OpenStudio::Measure::OSArgument.makeStringArgument('feature_type', false)
70
+ feature_type.setDisplayName('URBANopt Feature Type')
71
+ feature_type.setDefaultValue('Building')
72
+ args << feature_type
73
+
74
+ # make an argument for the frequency
75
+ reporting_frequency_chs = OpenStudio::StringVector.new
76
+ reporting_frequency_chs << 'Detailed'
77
+ reporting_frequency_chs << 'Timestep'
78
+ reporting_frequency_chs << 'Hourly'
79
+ reporting_frequency_chs << 'Daily'
80
+ # reporting_frequency_chs << "BillingPeriod" # match it to utility bill object
81
+ ## Utility report here to report the start and end for each fueltype
82
+ reporting_frequency_chs << 'Monthly'
83
+ reporting_frequency_chs << 'Runperiod'
84
+
85
+ reporting_frequency = OpenStudio::Measure::OSArgument.makeChoiceArgument('reporting_frequency', reporting_frequency_chs, true)
86
+ reporting_frequency.setDisplayName('Reporting Frequency')
87
+ reporting_frequency.setDescription('The frequency at which to report timeseries output data.')
88
+ reporting_frequency.setDefaultValue('Hourly')
89
+ args << reporting_frequency
90
+
91
+ # move this in the run method
92
+ if reporting_frequency.defaultValueDisplayName == 'BillingPeriod'
93
+ @@logger.error('BillingPeriod frequency is not implemented yet')
94
+ end
95
+
96
+ return args
97
+ end
98
+
99
+ def fuel_types
100
+ fuel_types = [
101
+ 'Electricity',
102
+ 'Gas',
103
+ 'AdditionalFuel',
104
+ 'DistrictCooling',
105
+ 'DistrictHeating',
106
+ 'Water'
107
+ ]
108
+
109
+ return fuel_types
110
+ end
111
+
112
+ def end_uses
113
+ end_uses = [
114
+ 'Heating',
115
+ 'Cooling',
116
+ 'InteriorLights',
117
+ 'ExteriorLights',
118
+ 'InteriorEquipment',
119
+ 'ExteriorEquipment',
120
+ 'Fans',
121
+ 'Pumps',
122
+ 'HeatRejection',
123
+ 'Humidifier',
124
+ 'HeatRecovery',
125
+ 'WaterSystems',
126
+ 'Refrigeration',
127
+ 'Generators',
128
+ 'Facility'
129
+ ]
130
+
131
+ return end_uses
132
+ end
133
+
134
+ # return a vector of IdfObject's to request EnergyPlus objects needed by the run method
135
+ # rubocop:disable Naming/MethodName
136
+ def energyPlusOutputRequests(runner, user_arguments)
137
+ super(runner, user_arguments)
138
+
139
+ result = OpenStudio::IdfObjectVector.new
140
+
141
+ reporting_frequency = runner.getStringArgumentValue('reporting_frequency', user_arguments)
142
+
143
+ # Request the output for each end use/fuel type combination
144
+ end_uses.each do |end_use|
145
+ fuel_types.each do |fuel_type|
146
+ variable_name = if end_use == 'Facility'
147
+ "#{fuel_type}:#{end_use}"
148
+ else
149
+ "#{end_use}:#{fuel_type}"
150
+ end
151
+ result << OpenStudio::IdfObject.load("Output:Meter,#{variable_name},#{reporting_frequency};").get
152
+ end
153
+ end
154
+
155
+ ### Request the output for each end use/fuel type combination
156
+ result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Electricity:Facility,#{reporting_frequency};").get
157
+ result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,ElectricityProduced:Facility,#{reporting_frequency};").get
158
+ result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Gas:Facility,#{reporting_frequency};").get
159
+ result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,DistrictCooling:Facility,#{reporting_frequency};").get
160
+ result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,DistrictHeating:Facility,#{reporting_frequency};").get
161
+
162
+ timeseries_data = ['District Cooling Chilled Water Rate', 'District Cooling Mass Flow Rate',
163
+ 'District Cooling Inlet Temperature', 'District Cooling Outlet Temperature',
164
+ 'District Heating Hot Water Rate', 'District Heating Mass Flow Rate',
165
+ 'District Heating Inlet Temperature', 'District Heating Outlet Temperature']
166
+
167
+ timeseries_data.each do |ts|
168
+ result << OpenStudio::IdfObject.load("Output:Variable,*,#{ts},#{reporting_frequency};").get
169
+ end
170
+
171
+ # use the built-in error checking
172
+ if !runner.validateUserArguments(arguments, user_arguments)
173
+ return result
174
+ end
175
+
176
+ return result
177
+ end
178
+
179
+ # sql_query method
180
+ def sql_query(runner, sql, report_name, query)
181
+ val = nil
182
+ result = sql.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='#{report_name}' AND #{query}")
183
+ if result.empty?
184
+ runner.registerWarning("Query failed for #{report_name} and #{query}")
185
+ else
186
+ begin
187
+ val = result.get
188
+ rescue StandardError
189
+ val = nil
190
+ runner.registerWarning('Query result.get failed')
191
+ end
192
+ end
193
+
194
+ val
195
+ end
196
+
197
+ # unit conversion method
198
+ def convert_units(value, from_units, to_units)
199
+ # apply unit conversion
200
+ value_converted = OpenStudio.convert(value, from_units, to_units)
201
+ if value_converted.is_initialized
202
+ value = value_converted.get
203
+ else
204
+ @runner.registerError("Was not able to convert #{value} from #{from_units} to #{to_units}.")
205
+ value = nil
206
+ end
207
+ return value
208
+ end
209
+
210
+ # define what happens when the measure is run
211
+ # rubocop:disable Metrics/AbcSize
212
+ def run(runner, user_arguments)
213
+ super(runner, user_arguments)
214
+
215
+ # use the built-in error checking
216
+ unless runner.validateUserArguments(arguments, user_arguments)
217
+ return false
218
+ end
219
+
220
+ # use the built-in error checking
221
+ if !runner.validateUserArguments(arguments, user_arguments)
222
+ return false
223
+ end
224
+
225
+ feature_id = runner.getStringArgumentValue('feature_id', user_arguments)
226
+ feature_name = runner.getStringArgumentValue('feature_name', user_arguments)
227
+ feature_type = runner.getStringArgumentValue('feature_type', user_arguments)
228
+
229
+ # Assign the user inputs to variables
230
+ reporting_frequency = runner.getStringArgumentValue('reporting_frequency', user_arguments)
231
+
232
+ # cache runner for this instance of the measure
233
+ @runner = runner
234
+
235
+ # get the WorkflowJSON object
236
+ workflow = runner.workflow
237
+
238
+ # get the last model and sql file
239
+ model = runner.lastOpenStudioModel
240
+ if model.empty?
241
+ runner.registerError('Cannot find last model.')
242
+ return false
243
+ end
244
+ model = model.get
245
+
246
+ sql_file = runner.lastEnergyPlusSqlFile
247
+ if sql_file.empty?
248
+ runner.registerError('Cannot find last sql file.')
249
+ return false
250
+ end
251
+ sql_file = sql_file.get
252
+ model.setSqlFile(sql_file)
253
+
254
+ # get building from model
255
+ building = model.getBuilding
256
+
257
+ # get surfaces from model
258
+ surfaces = model.getSurfaces
259
+
260
+ # get epw_file
261
+ epw_file = runner.lastEpwFile
262
+ if epw_file.empty?
263
+ runner.registerError('Cannot find last epw file.')
264
+ return false
265
+ end
266
+ epw_file = epw_file.get
267
+
268
+ # create output feature_report report object
269
+ feature_report = URBANopt::Scenario::DefaultReports::FeatureReport.new
270
+ feature_report.id = feature_id
271
+ feature_report.name = feature_name
272
+ feature_report.feature_type = feature_type
273
+ feature_report.directory_name = workflow.absoluteRunDir
274
+ feature_report.timesteps_per_hour = model.getTimestep.numberOfTimestepsPerHour
275
+ feature_report.simulation_status = 'Complete'
276
+
277
+ feature_report.reporting_periods << URBANopt::Scenario::DefaultReports::ReportingPeriod.new
278
+
279
+ ###########################################################################
280
+ ##
281
+ # Get Location information and store in the feature_report
282
+ ##
283
+
284
+ # latitude
285
+ latitude = epw_file.latitude
286
+ feature_report.location.latitude = latitude
287
+
288
+ # longitude
289
+ longitude = epw_file.longitude
290
+ feature_report.location.longitude = longitude
291
+
292
+ # surface_elevation
293
+ elev = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='General' AND RowName='Elevation' AND ColumnName='Value'")
294
+ feature_report.location.surface_elevation = elev
295
+
296
+ ##########################################################################
297
+ ##
298
+ # Get program information and store in the feature_report
299
+ ##
300
+
301
+ # floor_area
302
+ floor_area = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Building Area' AND RowName='Total Building Area' AND ColumnName='Area'")
303
+ feature_report.program.floor_area = convert_units(floor_area, 'm^2', 'ft^2')
304
+
305
+ # conditioned_area
306
+ conditioned_area = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Building Area' AND RowName='Net Conditioned Building Area' AND ColumnName='Area'")
307
+ feature_report.program.conditioned_area = convert_units(conditioned_area, 'm^2', 'ft^2')
308
+
309
+ # unconditioned_area
310
+ unconditioned_area = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Building Area' AND RowName='Unconditioned Building Area' AND ColumnName='Area'")
311
+ feature_report.program.unconditioned_area = convert_units(unconditioned_area, 'm^2', 'ft^2')
312
+
313
+ # footprint_area
314
+ feature_report.program.footprint_area = convert_units(floor_area, 'm^2', 'ft^2')
315
+
316
+ # maximum_number_of_stories
317
+ number_of_stories = building.standardsNumberOfStories.get if building.standardsNumberOfStories.is_initialized
318
+ number_of_stories ||= 1
319
+ feature_report.program.maximum_number_of_stories = number_of_stories
320
+
321
+ # maximum_roof_height
322
+ floor_to_floor_height = building.nominalFloortoFloorHeight.to_f
323
+ maximum_roof_height = number_of_stories * floor_to_floor_height
324
+ feature_report.program.maximum_roof_height = maximum_roof_height
325
+
326
+ # maximum_number_of_stories_above_ground
327
+ number_of_stories_above_ground = building.standardsNumberOfAboveGroundStories.get if building.standardsNumberOfAboveGroundStories.is_initialized
328
+ number_of_stories_above_ground ||= 1
329
+ feature_report.program.maximum_number_of_stories_above_ground = number_of_stories_above_ground
330
+
331
+ # number_of_residential_units
332
+ number_of_living_units = building.standardsNumberOfLivingUnits.to_i.get if building.standardsNumberOfLivingUnits.is_initialized
333
+ number_of_living_units ||= 1
334
+ feature_report.program.number_of_residential_units = number_of_living_units
335
+
336
+ ## building_types
337
+
338
+ # get an array of the model spaces
339
+ spaces = model.getSpaces
340
+
341
+ # get array of model space types
342
+ space_types = model.getSpaceTypes
343
+
344
+ # create a hash for space_type_areas (spcace types as keys and their areas as values)
345
+ space_type_areas = {}
346
+ model.getSpaceTypes.each do |space_type|
347
+ building_type = space_type.standardsBuildingType
348
+ if building_type.empty?
349
+ building_type = 'unknown'
350
+ else
351
+ building_type = building_type.get
352
+ end
353
+ space_type_areas[building_type] = 0 if space_type_areas[building_type].nil?
354
+ space_type_areas[building_type] += convert_units(space_type.floorArea, 'm^2', 'ft^2')
355
+ end
356
+
357
+ # create a hash for space_type_occupancy (spcace types as keys and their occupancy as values)
358
+ space_type_occupancy = {}
359
+ spaces.each do |space|
360
+ if space.spaceType.empty?
361
+ raise 'space.spaceType is empty. Make sure spaces have a space type'
362
+ else
363
+ building_type = space.spaceType.get.standardsBuildingType
364
+ end
365
+ if building_type.empty?
366
+ building_type = 'unknown'
367
+ else
368
+ building_type = building_type.get
369
+ end
370
+ space_type_occupancy[building_type] = 0 if space_type_occupancy[building_type].nil?
371
+ space_type_occupancy[building_type] += space.numberOfPeople
372
+ end
373
+
374
+ # combine all in a building_types array
375
+ building_types = []
376
+ for i in 0..(space_type_areas.size - 1)
377
+ building_types << { building_type: space_type_areas.keys[i], floor_area: space_type_areas.values[i], maximum_occupancy: space_type_occupancy.values[i] }
378
+ end
379
+ # add results to the feature report JSON
380
+ feature_report.program.building_types = building_types
381
+
382
+ ## window_area
383
+ # north_window_area
384
+ north_window_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Window Opening Area' AND ColumnName='North (315 to 45 deg)'").to_f
385
+ feature_report.program.window_area[:north_window_area] = convert_units(north_window_area, 'm^2', 'ft^2')
386
+ # south_window_area
387
+ south_window_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Window Opening Area' AND ColumnName='South (135 to 225 deg)'").to_f
388
+ feature_report.program.window_area[:south_window_area] = convert_units(south_window_area, 'm^2', 'ft^2')
389
+ # east_window_area
390
+ east_window_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Window Opening Area' AND ColumnName='East (45 to 135 deg)'").to_f
391
+ feature_report.program.window_area[:east_window_area] = convert_units(east_window_area, 'm^2', 'ft^2')
392
+ # west_window_area
393
+ west_window_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Window Opening Area' AND ColumnName='West (225 to 315 deg)'").to_f
394
+ feature_report.program.window_area[:west_window_area] = convert_units(west_window_area, 'm^2', 'ft^2')
395
+ # total_window_area
396
+ total_window_area = north_window_area + south_window_area + east_window_area + west_window_area
397
+ feature_report.program.window_area[:total_window_area] = convert_units(total_window_area, 'm^2', 'ft^2')
398
+
399
+ ## wall_area
400
+ # north_wall_area
401
+ north_wall_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Gross Wall Area' AND ColumnName='North (315 to 45 deg)'").to_f
402
+ feature_report.program.wall_area[:north_wall_area] = convert_units(north_wall_area, 'm^2', 'ft^2')
403
+ # south_wall_area
404
+ south_wall_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Gross Wall Area' AND ColumnName='South (135 to 225 deg)'").to_f
405
+ feature_report.program.wall_area[:south_wall_area] = convert_units(south_wall_area, 'm^2', 'ft^2')
406
+ # east_wall_area
407
+ east_wall_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Gross Wall Area' AND ColumnName='East (45 to 135 deg)'").to_f
408
+ feature_report.program.wall_area[:east_wall_area] = convert_units(east_wall_area, 'm^2', 'ft^2')
409
+ # west_wall_area
410
+ west_wall_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Gross Wall Area' AND ColumnName='West (225 to 315 deg)'").to_f
411
+ feature_report.program.wall_area[:west_wall_area] = convert_units(west_wall_area, 'm^2', 'ft^2')
412
+ # total_wall_area
413
+ total_wall_area = north_wall_area + south_wall_area + east_wall_area + west_wall_area
414
+ feature_report.program.wall_area[:total_wall_area] = convert_units(total_wall_area, 'm^2', 'ft^2')
415
+
416
+ # total_roof_area
417
+ total_roof_area = 0.0
418
+ surfaces.each do |surface|
419
+ if (surface.outsideBoundaryCondition == 'Outdoors') && (surface.surfaceType == 'RoofCeiling')
420
+ total_roof_area += surface.netArea
421
+ end
422
+ end
423
+ feature_report.program.roof_area[:total_roof_area] = convert_units(total_roof_area, 'm^2', 'ft^2')
424
+
425
+ # orientation
426
+ # RK: a more robust method should be implemented to find orientation(finding main axis of the building using aspect ratio)
427
+ building_rotation = model.getBuilding.northAxis
428
+ feature_report.program.orientation = building_rotation
429
+
430
+ # aspect_ratio
431
+ north_wall_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Gross Wall Area' AND ColumnName='North (315 to 45 deg)'")
432
+ east_wall_area = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='Window-Wall Ratio' AND RowName='Gross Wall Area' AND ColumnName='East (45 to 135 deg)'")
433
+ aspect_ratio = north_wall_area / east_wall_area if north_wall_area != 0 && east_wall_area != 0
434
+ aspect_ratio ||= nil
435
+ feature_report.program.aspect_ratio = aspect_ratio
436
+
437
+ ############################################################################
438
+ ##
439
+ # Get Reporting Periods information and store in the feature_report
440
+ ##
441
+
442
+ # start_date
443
+ # month
444
+ begin_month = model.getRunPeriod.getBeginMonth
445
+ feature_report.reporting_periods[0].start_date.month = begin_month
446
+ # day_of_month
447
+ begin_day_of_month = model.getRunPeriod.getBeginDayOfMonth
448
+ feature_report.reporting_periods[0].start_date.day_of_month = begin_day_of_month
449
+ # year
450
+ begin_year = model.getYearDescription.calendarYear
451
+ feature_report.reporting_periods[0].start_date.year = begin_year
452
+
453
+ # end_date
454
+ # month
455
+ end_month = model.getRunPeriod.getEndMonth
456
+ feature_report.reporting_periods[0].end_date.month = end_month
457
+ # day_of_month
458
+ end_day_of_month = model.getRunPeriod.getEndDayOfMonth
459
+ feature_report.reporting_periods[0].end_date.day_of_month = end_day_of_month
460
+ # year
461
+ end_year = model.getYearDescription.calendarYear
462
+ feature_report.reporting_periods[0].end_date.year = end_year
463
+
464
+ # total_site_energy
465
+ total_site_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Total Site Energy' AND ColumnName='Total Energy'")
466
+ feature_report.reporting_periods[0].total_site_energy = convert_units(total_site_energy, 'GJ', 'kBtu')
467
+
468
+ # total_source_energy
469
+ total_source_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Total Source Energy' AND ColumnName='Total Energy'")
470
+ feature_report.reporting_periods[0].total_source_energy = convert_units(total_source_energy, 'GJ', 'kBtu')
471
+
472
+ # net_site_energy
473
+ net_site_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Net Site Energy' AND ColumnName='Total Energy'")
474
+ feature_report.reporting_periods[0].net_site_energy = convert_units(net_site_energy, 'GJ', 'kBtu')
475
+
476
+ # net_source_energy
477
+ net_source_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Net Source Energy' AND ColumnName='Total Energy'")
478
+ feature_report.reporting_periods[0].net_source_energy = convert_units(net_source_energy, 'GJ', 'kBtu')
479
+
480
+ # electricity
481
+ electricity = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Electricity'")
482
+ feature_report.reporting_periods[0].electricity = convert_units(electricity, 'GJ', 'kBtu')
483
+
484
+ # natural_gas
485
+ natural_gas = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Natural Gas'")
486
+ feature_report.reporting_periods[0].natural_gas = convert_units(natural_gas, 'GJ', 'kBtu')
487
+
488
+ # additional_fuel
489
+ additional_fuel = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Additional Fuel'")
490
+ feature_report.reporting_periods[0].additional_fuel = convert_units(additional_fuel, 'GJ', 'kBtu')
491
+
492
+ # district_cooling
493
+ district_cooling = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='District Cooling'")
494
+ feature_report.reporting_periods[0].district_cooling = convert_units(district_cooling, 'GJ', 'kBtu')
495
+
496
+ # district_heating
497
+ district_heating = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='District Heating'")
498
+ feature_report.reporting_periods[0].district_heating = convert_units(district_heating, 'GJ', 'kBtu')
499
+
500
+ # water
501
+ water = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Water'")
502
+ # feature_report.reporting_periods[0].water = convert_units(water, 'm3', 'ft3')
503
+ feature_report.reporting_periods[0].water = water
504
+
505
+ # electricity_produced
506
+ electricity_produced = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Electric Loads Satisfied' AND RowName='Total On-Site and Utility Electric Sources' AND ColumnName='Electricity'")
507
+ feature_report.reporting_periods[0].electricity_produced = convert_units(electricity_produced, 'GJ', 'kBtu')
508
+
509
+ ## end_uses
510
+
511
+ # get fuel type as listed in the sql file
512
+ fuel_type = ['Electricity', 'Natural Gas', 'Additional Fuel', 'District Cooling', 'District Heating', 'Water']
513
+
514
+ # get enduses as listed in the sql file
515
+ enduses = ['Heating', 'Cooling', 'Interior Lighting', 'Exterior Lighting', 'Interior Equipment', 'Exterior Equipment', 'Fans', 'Pumps',
516
+ 'Heat Rejection', 'Humidification', 'Heat Recovery', 'Water Systems', 'Refrigeration', 'Generators']
517
+
518
+ # loop through fuel types and enduses to fill in sql_query method
519
+ fuel_type.each do |ft|
520
+ enduses.each do |eu|
521
+ sql_r = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='#{eu}' AND ColumnName='#{ft}'")
522
+
523
+ # report each query in its corresponding feature report obeject
524
+ if ft.include? ' '
525
+ x = ft.tr(' ', '_').downcase
526
+ m = feature_report.reporting_periods[0].end_uses.send(x)
527
+ else
528
+ m = feature_report.reporting_periods[0].end_uses.send(ft.downcase)
529
+
530
+ end
531
+
532
+ if eu.include? ' '
533
+ y = eu.tr(' ', '_').downcase
534
+ m.send("#{y}=", convert_units(sql_r, 'GJ', 'kBtu'))
535
+ else
536
+ m.send("#{eu.downcase}=", convert_units(sql_r, 'GJ', 'kBtu'))
537
+ end
538
+ end
539
+ end
540
+
541
+ ### energy_production
542
+ ## electricity_produced
543
+ # photovoltaic
544
+ photovoltaic_power = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Electric Loads Satisfied' AND RowName='Photovoltaic Power' AND ColumnName='Electricity'")
545
+ feature_report.reporting_periods[0].energy_production[:electricity_produced][:photovoltaic] = convert_units(photovoltaic_power, 'GJ', 'kBtu')
546
+
547
+ ## comfort_result
548
+ # time_setpoint_not_met_during_occupied_cooling
549
+ time_setpoint_not_met_during_occupied_cooling = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Comfort and Setpoint Not Met Summary' AND RowName='Time Setpoint Not Met During Occupied Cooling' AND ColumnName='Facility'")
550
+ feature_report.reporting_periods[0].comfort_result[:time_setpoint_not_met_during_occupied_cooling] = time_setpoint_not_met_during_occupied_cooling
551
+
552
+ # time_setpoint_not_met_during_occupied_heating
553
+ time_setpoint_not_met_during_occupied_heating = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Comfort and Setpoint Not Met Summary' AND RowName='Time Setpoint Not Met During Occupied Heating' AND ColumnName='Facility'")
554
+ feature_report.reporting_periods[0].comfort_result[:time_setpoint_not_met_during_occupied_heating] = time_setpoint_not_met_during_occupied_heating
555
+
556
+ # time_setpoint_not_met_during_occupied_hour
557
+ time_setpoint_not_met_during_occupied_hours = time_setpoint_not_met_during_occupied_heating + time_setpoint_not_met_during_occupied_cooling
558
+ feature_report.reporting_periods[0].comfort_result[:time_setpoint_not_met_during_occupied_hours] = time_setpoint_not_met_during_occupied_hours
559
+
560
+ ######################################## Reporting TImeseries Results FOR CSV File ######################################
561
+
562
+ # Get the weather file run period (as opposed to design day run period)
563
+ ann_env_pd = nil
564
+ sql_file.availableEnvPeriods.each do |env_pd|
565
+ env_type = sql_file.environmentType(env_pd)
566
+ if env_type.is_initialized
567
+ if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')
568
+ ann_env_pd = env_pd
569
+ end
570
+ end
571
+ end
572
+
573
+ if ann_env_pd == false
574
+ runner.registerError("Can't find a weather runperiod, make sure you ran an annual simulation, not just the design days.")
575
+ return false
576
+ end
577
+
578
+ # timeseries we want to report
579
+ requested_timeseries_names = [
580
+ 'Electricity:Facility',
581
+ 'ElectricityProduced:Facility',
582
+ 'Gas:Facility',
583
+ 'DistrictCooling:Facility',
584
+ 'DistrictHeating:Facility',
585
+ 'District Cooling Chilled Water Rate',
586
+ 'District Cooling Mass Flow Rate',
587
+ 'District Cooling Inlet Temperature',
588
+ 'District Cooling Outlet Temperature',
589
+ 'District Heating Hot Water Rate',
590
+ 'District Heating Mass Flow Rate',
591
+ 'District Heating Inlet Temperature',
592
+ 'District Heating Outlet Temperature'
593
+ ]
594
+
595
+ # number of values in each timeseries
596
+ n = nil
597
+
598
+ # all numeric timeseries values, transpose of CSV file (e.g. values[j] is column, values[j][i] is column and row)
599
+ values = []
600
+
601
+ # Since schedule value will have a bunch of key_values, we need to keep track of these as additional timeseries
602
+ # this is recording the name of these final timeseries to write in the header of the CSV
603
+ final_timeseries_names = []
604
+
605
+ # loop over requested timeseries
606
+ # rubocop: disable Metrics/BlockLength
607
+ requested_timeseries_names.each_with_index do |timeseries_name, j|
608
+ runner.registerInfo("TIMESERIES: #{timeseries_name}")
609
+
610
+ # get all the key values that this timeseries can be reported for (e.g. if power is requested for each zone)
611
+ key_values = sql_file.availableKeyValues(ann_env_pd.to_s, reporting_frequency.to_s, timeseries_name)
612
+ runner.registerInfo("KEY VALUES: #{key_values}")
613
+ if key_values.empty?
614
+ key_values = ['']
615
+ end
616
+
617
+ # sort keys
618
+ sorted_keys = key_values.sort
619
+ requested_keys = ['SUMMED ELECTRICITY:FACILITY', 'SUMMED ELECTRICITY:FACILITY POWER', 'SUMMED ELECTRICITYPRODUCED:FACILITY', 'SUMMED ELECTRICITYPRODUCED:FACILITY POWER', 'SUMMED NET APPARENT POWER', 'SUMMED NET ELECTRIC ENERGY', 'SUMMED NET POWER', 'TRANSFORMER OUTPUT ELECTRIC ENERGY SCHEDULE']
620
+ final_keys = []
621
+ # make sure aggregated timeseries are listed in sorted order before all individual feature timeseries
622
+ sorted_keys.each do |k|
623
+ if requested_keys.include? k
624
+ final_keys << k
625
+ end
626
+ end
627
+ sorted_keys.each do |k|
628
+ if !requested_keys.include? k
629
+ final_keys << k
630
+ end
631
+ end
632
+
633
+ # loop over final keys
634
+ final_keys.each_with_index do |key_value, key_i|
635
+ new_timeseries_name = ''
636
+
637
+ runner.registerInfo("!! TIMESERIES NAME: #{timeseries_name} AND key_value: #{key_value}")
638
+
639
+ # check if we have to come up with a new name for the timeseries in our CSV header
640
+ if key_values.size == 1
641
+ # use timeseries name when only 1 keyvalue
642
+ new_timeseries_name = timeseries_name
643
+ else
644
+ # use key_value name
645
+ # special case for Zone Thermal Comfort: use both timeseries_name and key_value
646
+ if timeseries_name.include? 'Zone Thermal Comfort'
647
+ new_timeseries_name = timeseries_name + ' ' + key_value
648
+ else
649
+ new_timeseries_name = key_value
650
+ end
651
+ end
652
+ final_timeseries_names << new_timeseries_name
653
+
654
+ # get the actual timeseries
655
+ ts = sql_file.timeSeries(ann_env_pd.to_s, reporting_frequency.to_s, timeseries_name, key_value)
656
+
657
+ if n.nil?
658
+ # first timeseries should always be set
659
+ runner.registerInfo('First timeseries')
660
+ values[j] = ts.get.values
661
+ n = values[j].size
662
+ elsif ts.is_initialized
663
+ runner.registerInfo('Is Initialized')
664
+ values[j] = ts.get.values
665
+ else
666
+ runner.registerInfo('Is NOT Initialized')
667
+ values[j] = Array.new(n, 0)
668
+ end
669
+
670
+ # ##Unit conversion
671
+ old_units = ts.get.units if ts.is_initialized
672
+ new_units = case old_units.to_s
673
+ when 'J'
674
+ 'kBtu'
675
+ when 'kWh'
676
+ 'kBtu'
677
+ when 'm3'
678
+ 'gal'
679
+ end
680
+
681
+ # Unit conversion here
682
+ os_vec = values[j]
683
+
684
+ # loop through each value to retrieve it
685
+ for i in 0..os_vec.length - 1
686
+ unless new_units == old_units
687
+ os_vec[i] = OpenStudio.convert(os_vec[i], old_units, new_units).get
688
+ end
689
+ end
690
+ end
691
+ end
692
+ # rubocop: enable Metrics/BlockLength
693
+ runner.registerInfo("new final_timeseries_names size: #{final_timeseries_names.size}")
694
+
695
+ # Save the 'default_feature_reports.csv' file
696
+ File.open('default_feature_reports.csv', 'w') do |file|
697
+ file.puts(final_timeseries_names.join(','))
698
+ (0...n).each do |l|
699
+ line = []
700
+ values.each_index do |j|
701
+ line << values[j][l]
702
+ end
703
+ file.puts(line.join(','))
704
+ end
705
+ end
706
+
707
+ # closing the sql file
708
+ sql_file.close
709
+
710
+ ############################# Adding timeseries_csv info to json report and saving CSV ################################
711
+ # add csv info to feature_report
712
+ feature_report.timeseries_csv.path = File.join(Dir.pwd, 'default_feature_reports.csv')
713
+ feature_report.timeseries_csv.first_report_datetime = '0'
714
+ feature_report.timeseries_csv.column_names = final_timeseries_names
715
+
716
+ ##### Save the 'default_feature_reports.json' file
717
+
718
+ feature_report_hash = feature_report.to_hash
719
+
720
+ File.open('default_feature_reports.json', 'w') do |f|
721
+ f.puts JSON.pretty_generate(feature_report_hash)
722
+ # make sure data is written to the disk one way or the other
723
+ begin
724
+ f.fsync
725
+ rescue StandardError
726
+ f.flush
727
+ end
728
+ end
729
+
730
+ # reporting final condition
731
+ runner.registerFinalCondition('Default Feature Reports generated successfully.')
732
+
733
+ true
734
+ # end the run method
735
+ end
736
+ # end the measure
737
+ end
738
+ # rubocop:enable Metrics/AbcSize
739
+ # rubocop:enable Naming/MethodName
740
+
741
+ # register the measure to be used by the application
742
+ DefaultFeatureReports.new.registerWithApplication