urbanopt-scenario 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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