urbanopt-scenario 0.2.0 → 0.4.2

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 (72) hide show
  1. checksums.yaml +5 -5
  2. data/.github/pull_request_template.md +2 -2
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +1 -1
  5. data/CHANGELOG.md +53 -0
  6. data/CONTRIBUTING.md +1 -1
  7. data/Gemfile +27 -14
  8. data/Jenkinsfile +1 -1
  9. data/LICENSE.md +1 -1
  10. data/RDOC_MAIN.md +1 -1
  11. data/README.md +1 -1
  12. data/Rakefile +2 -2
  13. data/docs/README.md +1 -1
  14. data/docs/package-lock.json +2499 -2322
  15. data/docs/package.json +13 -9
  16. data/lib/urbanopt-scenario.rb +1 -1
  17. data/lib/urbanopt/scenario.rb +2 -1
  18. data/lib/urbanopt/scenario/default_reports.rb +3 -8
  19. data/lib/urbanopt/scenario/extension.rb +1 -1
  20. data/lib/urbanopt/scenario/logger.rb +1 -1
  21. data/lib/urbanopt/scenario/scenario_base.rb +1 -1
  22. data/lib/urbanopt/scenario/scenario_csv.rb +22 -9
  23. data/lib/urbanopt/scenario/scenario_datapoint_base.rb +8 -1
  24. data/lib/urbanopt/scenario/scenario_post_processor_base.rb +1 -1
  25. data/lib/urbanopt/scenario/scenario_post_processor_default.rb +110 -8
  26. data/lib/urbanopt/scenario/scenario_post_processor_opendss.rb +6 -7
  27. data/lib/urbanopt/scenario/scenario_runner_base.rb +2 -2
  28. data/lib/urbanopt/scenario/scenario_runner_osw.rb +23 -9
  29. data/lib/urbanopt/scenario/scenario_visualization.rb +236 -0
  30. data/lib/urbanopt/scenario/simulation_dir_base.rb +1 -1
  31. data/lib/urbanopt/scenario/simulation_dir_osw.rb +2 -9
  32. data/lib/urbanopt/scenario/simulation_mapper_base.rb +1 -1
  33. data/lib/urbanopt/scenario/version.rb +2 -2
  34. data/package-lock.json +3 -0
  35. data/urbanopt-scenario-gem.gemspec +14 -14
  36. metadata +71 -77
  37. data/doc_templates/LICENSE.md +0 -27
  38. data/doc_templates/README.md.erb +0 -42
  39. data/doc_templates/copyright_erb.txt +0 -31
  40. data/doc_templates/copyright_js.txt +0 -4
  41. data/doc_templates/copyright_ruby.txt +0 -29
  42. data/lib/change_log.rb +0 -147
  43. data/lib/measures/.rubocop.yml +0 -5
  44. data/lib/measures/default_feature_reports/LICENSE.md +0 -27
  45. data/lib/measures/default_feature_reports/README.md +0 -56
  46. data/lib/measures/default_feature_reports/README.md.erb +0 -42
  47. data/lib/measures/default_feature_reports/measure.rb +0 -979
  48. data/lib/measures/default_feature_reports/measure.xml +0 -143
  49. data/lib/measures/default_feature_reports/tests/USA_CO_Golden-NREL.724666_TMY3.epw +0 -8768
  50. data/lib/measures/default_feature_reports/tests/default_feature_reports_test.rb +0 -238
  51. data/lib/measures/default_feature_reports/tests/example_model.osm +0 -4378
  52. data/lib/urbanopt/scenario/default_reports/construction_cost.rb +0 -169
  53. data/lib/urbanopt/scenario/default_reports/date.rb +0 -97
  54. data/lib/urbanopt/scenario/default_reports/distributed_generation.rb +0 -379
  55. data/lib/urbanopt/scenario/default_reports/end_use.rb +0 -159
  56. data/lib/urbanopt/scenario/default_reports/end_uses.rb +0 -140
  57. data/lib/urbanopt/scenario/default_reports/feature_report.rb +0 -267
  58. data/lib/urbanopt/scenario/default_reports/generator.rb +0 -92
  59. data/lib/urbanopt/scenario/default_reports/location.rb +0 -99
  60. data/lib/urbanopt/scenario/default_reports/logger.rb +0 -44
  61. data/lib/urbanopt/scenario/default_reports/power_distribution.rb +0 -102
  62. data/lib/urbanopt/scenario/default_reports/program.rb +0 -266
  63. data/lib/urbanopt/scenario/default_reports/reporting_period.rb +0 -301
  64. data/lib/urbanopt/scenario/default_reports/scenario_report.rb +0 -317
  65. data/lib/urbanopt/scenario/default_reports/schema/README.md +0 -33
  66. data/lib/urbanopt/scenario/default_reports/schema/scenario_csv_columns.txt +0 -32
  67. data/lib/urbanopt/scenario/default_reports/schema/scenario_schema.json +0 -853
  68. data/lib/urbanopt/scenario/default_reports/solar_pv.rb +0 -93
  69. data/lib/urbanopt/scenario/default_reports/storage.rb +0 -105
  70. data/lib/urbanopt/scenario/default_reports/timeseries_csv.rb +0 -299
  71. data/lib/urbanopt/scenario/default_reports/validator.rb +0 -97
  72. data/lib/urbanopt/scenario/default_reports/wind.rb +0 -92
@@ -1,27 +0,0 @@
1
- URBANopt, Copyright (c) 2019-2020, 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.
@@ -1,42 +0,0 @@
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 %>
@@ -1,31 +0,0 @@
1
- <%
2
- # *********************************************************************************
3
- # URBANopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
4
- # contributors. All rights reserved.
5
- #
6
- # Redistribution and use in source and binary forms, with or without modification,
7
- # are permitted provided that the following conditions are met:
8
- #
9
- # Redistributions of source code must retain the above copyright notice, this list
10
- # of conditions and the following disclaimer.
11
- #
12
- # Redistributions in binary form must reproduce the above copyright notice, this
13
- # list of conditions and the following disclaimer in the documentation and/or other
14
- # materials provided with the distribution.
15
- #
16
- # Neither the name of the copyright holder nor the names of its contributors may be
17
- # used to endorse or promote products derived from this software without specific
18
- # prior written permission.
19
- #
20
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21
- # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
- # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23
- # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24
- # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25
- # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26
- # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
- # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
28
- # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29
- # OF THE POSSIBILITY OF SUCH DAMAGE.
30
- # *********************************************************************************
31
- %>
@@ -1,4 +0,0 @@
1
- /* @preserve
2
- * URBANopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
3
- * Use of this source code is governed by the BSD 3-Clause license.
4
- */
@@ -1,29 +0,0 @@
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
- # *********************************************************************************
@@ -1,147 +0,0 @@
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(', ') + ')'
@@ -1,5 +0,0 @@
1
- AllCops:
2
- Exclude:
3
- - 'spec/test_measures/**/*'
4
- inherit_from:
5
- - http://s3.amazonaws.com/openstudio-resources/styles/rubocop.yml
@@ -1,27 +0,0 @@
1
- URBANopt, Copyright (c) 2019-2020, 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.
@@ -1,56 +0,0 @@
1
-
2
-
3
- ###### (Automatically generated documentation)
4
-
5
- # DefaultFeatureReports
6
-
7
- ## Description
8
- Writes default_feature_reports.json and default_feature_reports.csv files 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
-
@@ -1,42 +0,0 @@
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 %>
@@ -1,979 +0,0 @@
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 and default_feature_reports.csv files 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 << 'Zone Timestep'
81
- reporting_frequency_chs << 'BillingPeriod' # match it to utility bill object
82
- ## Utility report here to report the start and end for each fueltype
83
- reporting_frequency_chs << 'Monthly'
84
- reporting_frequency_chs << 'Runperiod'
85
-
86
- reporting_frequency = OpenStudio::Measure::OSArgument.makeChoiceArgument('reporting_frequency', reporting_frequency_chs, true)
87
- reporting_frequency.setDisplayName('Reporting Frequency')
88
- reporting_frequency.setDescription('The frequency at which to report timeseries output data.')
89
- reporting_frequency.setDefaultValue('Timestep')
90
- args << reporting_frequency
91
-
92
- return args
93
- end
94
-
95
- # define fuel types
96
- def fuel_types
97
- fuel_types = [
98
- 'Electricity',
99
- 'Gas',
100
- 'AdditionalFuel',
101
- 'DistrictCooling',
102
- 'DistrictHeating',
103
- 'Water'
104
- ]
105
-
106
- return fuel_types
107
- end
108
-
109
- # define enduses
110
- def end_uses
111
- end_uses = [
112
- 'Heating',
113
- 'Cooling',
114
- 'InteriorLights',
115
- 'ExteriorLights',
116
- 'InteriorEquipment',
117
- 'ExteriorEquipment',
118
- 'Fans',
119
- 'Pumps',
120
- 'HeatRejection',
121
- 'Humidifier',
122
- 'HeatRecovery',
123
- 'WaterSystems',
124
- 'Refrigeration',
125
- 'Generators',
126
- 'Facility'
127
- ]
128
-
129
- return end_uses
130
- end
131
-
132
- # format datetime
133
- def format_datetime(date_time)
134
- date_time.tr!('-', '/')
135
- date_time.gsub!('Jan', '01')
136
- date_time.gsub!('Feb', '02')
137
- date_time.gsub!('Mar', '03')
138
- date_time.gsub!('Apr', '04')
139
- date_time.gsub!('May', '05')
140
- date_time.gsub!('Jun', '06')
141
- date_time.gsub!('Jul', '07')
142
- date_time.gsub!('Aug', '08')
143
- date_time.gsub!('Sep', '09')
144
- date_time.gsub!('Oct', '10')
145
- date_time.gsub!('Nov', '11')
146
- date_time.gsub!('Dec', '12')
147
- return date_time
148
- end
149
-
150
- # return a vector of IdfObject's to request EnergyPlus objects needed by the run method
151
- # rubocop:disable Naming/MethodName
152
- def energyPlusOutputRequests(runner, user_arguments)
153
- super(runner, user_arguments)
154
-
155
- result = OpenStudio::IdfObjectVector.new
156
-
157
- reporting_frequency = runner.getStringArgumentValue('reporting_frequency', user_arguments)
158
-
159
- # Request the output for each end use/fuel type combination
160
- end_uses.each do |end_use|
161
- fuel_types.each do |fuel_type|
162
- variable_name = if end_use == 'Facility'
163
- "#{fuel_type}:#{end_use}"
164
- else
165
- "#{end_use}:#{fuel_type}"
166
- end
167
- result << OpenStudio::IdfObject.load("Output:Meter,#{variable_name},#{reporting_frequency};").get
168
- end
169
- end
170
-
171
- # Request the output for each end use/fuel type combination
172
- result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Electricity:Facility,#{reporting_frequency};").get
173
- result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,ElectricityProduced:Facility,#{reporting_frequency};").get
174
- result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Gas:Facility,#{reporting_frequency};").get
175
- result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,DistrictCooling:Facility,#{reporting_frequency};").get
176
- result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,DistrictHeating:Facility,#{reporting_frequency};").get
177
- # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Cooling:Electricity,#{reporting_frequency};").get
178
- # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Heating:Electricity,#{reporting_frequency};").get
179
- # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,InteriorLights:Electricity,#{reporting_frequency};").get
180
- # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,ExteriorLights:Electricity,#{reporting_frequency};").get
181
- # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,InteriorEquipment:Electricity,#{reporting_frequency};").get
182
- # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Fans:Electricity,#{reporting_frequency};").get
183
- # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Pumps:Electricity,#{reporting_frequency};").get
184
- # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,WaterSystems:Electricity,#{reporting_frequency};").get
185
- # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,Heating:Gas,#{reporting_frequency};").get
186
- # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,WaterSystems:Gas,#{reporting_frequency};").get
187
- # result << OpenStudio::IdfObject.load("Output:Meter:MeterFileOnly,InteriorEquipment:Gas,#{reporting_frequency};").get
188
-
189
- timeseries_data = ['District Cooling Chilled Water Rate', 'District Cooling Mass Flow Rate',
190
- 'District Cooling Inlet Temperature', 'District Cooling Outlet Temperature',
191
- 'District Heating Hot Water Rate', 'District Heating Mass Flow Rate',
192
- 'District Heating Inlet Temperature', 'District Heating Outlet Temperature']
193
-
194
- timeseries_data.each do |ts|
195
- result << OpenStudio::IdfObject.load("Output:Variable,*,#{ts},#{reporting_frequency};").get
196
- end
197
-
198
- # use the built-in error checking
199
- if !runner.validateUserArguments(arguments, user_arguments)
200
- return result
201
- end
202
-
203
- return result
204
- end
205
-
206
- # sql_query method
207
- def sql_query(runner, sql, report_name, query)
208
- val = nil
209
- result = sql.execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='#{report_name}' AND #{query}")
210
- if result.empty?
211
- runner.registerWarning("Query failed for #{report_name} and #{query}")
212
- else
213
- begin
214
- val = result.get
215
- rescue StandardError
216
- val = nil
217
- runner.registerWarning('Query result.get failed')
218
- end
219
- end
220
-
221
- val
222
- end
223
-
224
- # unit conversion method
225
- def convert_units(value, from_units, to_units)
226
- # apply unit conversion
227
- value_converted = OpenStudio.convert(value, from_units, to_units)
228
- if value_converted.is_initialized
229
- value = value_converted.get
230
- else
231
- @runner.registerError("Was not able to convert #{value} from #{from_units} to #{to_units}.")
232
- value = nil
233
- end
234
- return value
235
- end
236
-
237
- # define what happens when the measure is run
238
- # rubocop:disable Metrics/AbcSize
239
- def run(runner, user_arguments)
240
- super(runner, user_arguments)
241
-
242
- # use the built-in error checking
243
- unless runner.validateUserArguments(arguments, user_arguments)
244
- return false
245
- end
246
-
247
- # use the built-in error checking
248
- if !runner.validateUserArguments(arguments, user_arguments)
249
- return false
250
- end
251
-
252
- feature_id = runner.getStringArgumentValue('feature_id', user_arguments)
253
- feature_name = runner.getStringArgumentValue('feature_name', user_arguments)
254
- feature_type = runner.getStringArgumentValue('feature_type', user_arguments)
255
-
256
- # Assign the user inputs to variables
257
- reporting_frequency = runner.getStringArgumentValue('reporting_frequency', user_arguments)
258
-
259
- # BilingPeriod reporting frequency not implemented yet
260
- if reporting_frequency == 'BillingPeriod'
261
- @@logger.error('BillingPeriod frequency is not implemented yet')
262
- end
263
-
264
- # cache runner for this instance of the measure
265
- @runner = runner
266
-
267
- # get the WorkflowJSON object
268
- workflow = runner.workflow
269
-
270
- # get the last model and sql file
271
- model = runner.lastOpenStudioModel
272
- if model.empty?
273
- runner.registerError('Cannot find last model.')
274
- return false
275
- end
276
- model = model.get
277
-
278
- sql_file = runner.lastEnergyPlusSqlFile
279
- if sql_file.empty?
280
- runner.registerError('Cannot find last sql file.')
281
- return false
282
- end
283
- sql_file = sql_file.get
284
- model.setSqlFile(sql_file)
285
-
286
- # get building from model
287
- building = model.getBuilding
288
-
289
- # get surfaces from model
290
- surfaces = model.getSurfaces
291
-
292
- # get epw_file
293
- epw_file = runner.lastEpwFile
294
- if epw_file.empty?
295
- runner.registerError('Cannot find last epw file.')
296
- return false
297
- end
298
- epw_file = epw_file.get
299
-
300
- # create output feature_report report object
301
- feature_report = URBANopt::Scenario::DefaultReports::FeatureReport.new
302
- feature_report.id = feature_id
303
- feature_report.name = feature_name
304
- feature_report.feature_type = feature_type
305
- feature_report.directory_name = workflow.absoluteRunDir
306
-
307
- timesteps_per_hour = model.getTimestep.numberOfTimestepsPerHour
308
- feature_report.timesteps_per_hour = timesteps_per_hour
309
-
310
- feature_report.simulation_status = 'Complete'
311
-
312
- feature_report.reporting_periods << URBANopt::Scenario::DefaultReports::ReportingPeriod.new
313
-
314
- ###########################################################################
315
- ##
316
- # Get Location information and store in the feature_report
317
- ##
318
-
319
- # latitude
320
- latitude = epw_file.latitude
321
- feature_report.location.latitude = latitude
322
-
323
- # longitude
324
- longitude = epw_file.longitude
325
- feature_report.location.longitude = longitude
326
-
327
- # surface_elevation
328
- elev = sql_query(runner, sql_file, 'InputVerificationandResultsSummary', "TableName='General' AND RowName='Elevation' AND ColumnName='Value'")
329
- feature_report.location.surface_elevation = elev
330
-
331
- ##########################################################################
332
- ##
333
- # Get program information and store in the feature_report
334
- ##
335
-
336
- # floor_area
337
- floor_area = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Building Area' AND RowName='Total Building Area' AND ColumnName='Area'")
338
- feature_report.program.floor_area = convert_units(floor_area, 'm^2', 'ft^2')
339
-
340
- # conditioned_area
341
- conditioned_area = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Building Area' AND RowName='Net Conditioned Building Area' AND ColumnName='Area'")
342
- feature_report.program.conditioned_area = convert_units(conditioned_area, 'm^2', 'ft^2')
343
-
344
- # unconditioned_area
345
- unconditioned_area = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Building Area' AND RowName='Unconditioned Building Area' AND ColumnName='Area'")
346
- feature_report.program.unconditioned_area = convert_units(unconditioned_area, 'm^2', 'ft^2')
347
-
348
- # footprint_area
349
- feature_report.program.footprint_area = convert_units(floor_area, 'm^2', 'ft^2')
350
-
351
- # maximum_number_of_stories
352
- number_of_stories = building.standardsNumberOfStories.get if building.standardsNumberOfStories.is_initialized
353
- number_of_stories ||= 1
354
- feature_report.program.maximum_number_of_stories = number_of_stories
355
-
356
- # maximum_roof_height
357
- floor_to_floor_height = building.nominalFloortoFloorHeight.to_f
358
- maximum_roof_height = number_of_stories * floor_to_floor_height
359
- feature_report.program.maximum_roof_height = maximum_roof_height
360
-
361
- # maximum_number_of_stories_above_ground
362
- number_of_stories_above_ground = building.standardsNumberOfAboveGroundStories.get if building.standardsNumberOfAboveGroundStories.is_initialized
363
- number_of_stories_above_ground ||= 1
364
- feature_report.program.maximum_number_of_stories_above_ground = number_of_stories_above_ground
365
-
366
- # number_of_residential_units
367
- number_of_living_units = building.standardsNumberOfLivingUnits.to_i.get if building.standardsNumberOfLivingUnits.is_initialized
368
- number_of_living_units ||= 1
369
- feature_report.program.number_of_residential_units = number_of_living_units
370
-
371
- ## building_types
372
-
373
- # get an array of the model spaces
374
- spaces = model.getSpaces
375
-
376
- # get array of model space types
377
- space_types = model.getSpaceTypes
378
-
379
- # create a hash for space_type_areas (spcace types as keys and their areas as values)
380
- space_type_areas = {}
381
- model.getSpaceTypes.each do |space_type|
382
- building_type = space_type.standardsBuildingType
383
- if building_type.empty?
384
- building_type = 'unknown'
385
- else
386
- building_type = building_type.get
387
- end
388
- space_type_areas[building_type] = 0 if space_type_areas[building_type].nil?
389
- space_type_areas[building_type] += convert_units(space_type.floorArea, 'm^2', 'ft^2')
390
- end
391
-
392
- # create a hash for space_type_occupancy (spcace types as keys and their occupancy as values)
393
- space_type_occupancy = {}
394
- spaces.each do |space|
395
- if space.spaceType.empty?
396
- raise 'space.spaceType is empty. Make sure spaces have a space type'
397
- else
398
- building_type = space.spaceType.get.standardsBuildingType
399
- end
400
- if building_type.empty?
401
- building_type = 'unknown'
402
- else
403
- building_type = building_type.get
404
- end
405
- space_type_occupancy[building_type] = 0 if space_type_occupancy[building_type].nil?
406
- space_type_occupancy[building_type] += space.numberOfPeople
407
- end
408
-
409
- # combine all in a building_types array
410
- building_types = []
411
- for i in 0..(space_type_areas.size - 1)
412
- building_types << { building_type: space_type_areas.keys[i], floor_area: space_type_areas.values[i], maximum_occupancy: space_type_occupancy.values[i] }
413
- end
414
- # add results to the feature report JSON
415
- feature_report.program.building_types = building_types
416
-
417
- ## window_area
418
- # north_window_area
419
- 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
420
- feature_report.program.window_area[:north_window_area] = convert_units(north_window_area, 'm^2', 'ft^2')
421
- # south_window_area
422
- 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
423
- feature_report.program.window_area[:south_window_area] = convert_units(south_window_area, 'm^2', 'ft^2')
424
- # east_window_area
425
- 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
426
- feature_report.program.window_area[:east_window_area] = convert_units(east_window_area, 'm^2', 'ft^2')
427
- # west_window_area
428
- 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
429
- feature_report.program.window_area[:west_window_area] = convert_units(west_window_area, 'm^2', 'ft^2')
430
- # total_window_area
431
- total_window_area = north_window_area + south_window_area + east_window_area + west_window_area
432
- feature_report.program.window_area[:total_window_area] = convert_units(total_window_area, 'm^2', 'ft^2')
433
-
434
- ## wall_area
435
- # north_wall_area
436
- 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
437
- feature_report.program.wall_area[:north_wall_area] = convert_units(north_wall_area, 'm^2', 'ft^2')
438
- # south_wall_area
439
- 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
440
- feature_report.program.wall_area[:south_wall_area] = convert_units(south_wall_area, 'm^2', 'ft^2')
441
- # east_wall_area
442
- 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
443
- feature_report.program.wall_area[:east_wall_area] = convert_units(east_wall_area, 'm^2', 'ft^2')
444
- # west_wall_area
445
- 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
446
- feature_report.program.wall_area[:west_wall_area] = convert_units(west_wall_area, 'm^2', 'ft^2')
447
- # total_wall_area
448
- total_wall_area = north_wall_area + south_wall_area + east_wall_area + west_wall_area
449
- feature_report.program.wall_area[:total_wall_area] = convert_units(total_wall_area, 'm^2', 'ft^2')
450
-
451
- # total_roof_area
452
- total_roof_area = 0.0
453
- surfaces.each do |surface|
454
- if (surface.outsideBoundaryCondition == 'Outdoors') && (surface.surfaceType == 'RoofCeiling')
455
- total_roof_area += surface.netArea
456
- end
457
- end
458
- feature_report.program.roof_area[:total_roof_area] = convert_units(total_roof_area, 'm^2', 'ft^2')
459
-
460
- # orientation
461
- # RK: a more robust method should be implemented to find orientation(finding main axis of the building using aspect ratio)
462
- building_rotation = model.getBuilding.northAxis
463
- feature_report.program.orientation = building_rotation
464
-
465
- # aspect_ratio
466
- 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)'")
467
- 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)'")
468
- aspect_ratio = north_wall_area / east_wall_area if north_wall_area != 0 && east_wall_area != 0
469
- aspect_ratio ||= nil
470
- feature_report.program.aspect_ratio = aspect_ratio
471
-
472
- # total_construction_cost
473
- total_construction_cost = sql_query(runner, sql_file, 'Life-Cycle Cost Report', "TableName='Present Value for Recurring, Nonrecurring and Energy Costs (Before Tax)' AND RowName='LCC_MAT - BUILDING - LIFE CYCLE COSTS' AND ColumnName='Cost'")
474
- feature_report.program.total_construction_cost = total_construction_cost
475
-
476
- ############################################################################
477
- ##
478
- # Get Reporting Periods information and store in the feature_report
479
- ##
480
-
481
- # start_date
482
- # month
483
- begin_month = model.getRunPeriod.getBeginMonth
484
- feature_report.reporting_periods[0].start_date.month = begin_month
485
- # day_of_month
486
- begin_day_of_month = model.getRunPeriod.getBeginDayOfMonth
487
- feature_report.reporting_periods[0].start_date.day_of_month = begin_day_of_month
488
- # year
489
- begin_year = model.getYearDescription.calendarYear
490
- feature_report.reporting_periods[0].start_date.year = begin_year
491
-
492
- # end_date
493
- # month
494
- end_month = model.getRunPeriod.getEndMonth
495
- feature_report.reporting_periods[0].end_date.month = end_month
496
- # day_of_month
497
- end_day_of_month = model.getRunPeriod.getEndDayOfMonth
498
- feature_report.reporting_periods[0].end_date.day_of_month = end_day_of_month
499
- # year
500
- end_year = model.getYearDescription.calendarYear
501
- feature_report.reporting_periods[0].end_date.year = end_year
502
-
503
- # total_site_energy
504
- total_site_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Total Site Energy' AND ColumnName='Total Energy'")
505
- feature_report.reporting_periods[0].total_site_energy = convert_units(total_site_energy, 'GJ', 'kBtu')
506
-
507
- # total_source_energy
508
- total_source_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Total Source Energy' AND ColumnName='Total Energy'")
509
- feature_report.reporting_periods[0].total_source_energy = convert_units(total_source_energy, 'GJ', 'kBtu')
510
-
511
- # net_site_energy
512
- net_site_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Net Site Energy' AND ColumnName='Total Energy'")
513
- feature_report.reporting_periods[0].net_site_energy = convert_units(net_site_energy, 'GJ', 'kBtu')
514
-
515
- # net_source_energy
516
- net_source_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Net Source Energy' AND ColumnName='Total Energy'")
517
- feature_report.reporting_periods[0].net_source_energy = convert_units(net_source_energy, 'GJ', 'kBtu')
518
-
519
- # electricity
520
- electricity = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Electricity'")
521
- feature_report.reporting_periods[0].electricity = convert_units(electricity, 'GJ', 'kBtu')
522
-
523
- # natural_gas
524
- natural_gas = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Natural Gas'")
525
- feature_report.reporting_periods[0].natural_gas = convert_units(natural_gas, 'GJ', 'kBtu')
526
-
527
- # additional_fuel
528
- additional_fuel = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Additional Fuel'")
529
- feature_report.reporting_periods[0].additional_fuel = convert_units(additional_fuel, 'GJ', 'kBtu')
530
-
531
- # district_cooling
532
- district_cooling = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='District Cooling'")
533
- feature_report.reporting_periods[0].district_cooling = convert_units(district_cooling, 'GJ', 'kBtu')
534
-
535
- # district_heating
536
- district_heating = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='District Heating'")
537
- feature_report.reporting_periods[0].district_heating = convert_units(district_heating, 'GJ', 'kBtu')
538
-
539
- # water
540
- water = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='Total End Uses' AND ColumnName='Water'")
541
- # feature_report.reporting_periods[0].water = convert_units(water, 'm3', 'ft3')
542
- feature_report.reporting_periods[0].water = water
543
-
544
- # electricity_produced
545
- electricity_produced = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Electric Loads Satisfied' AND RowName='Total On-Site and Utility Electric Sources' AND ColumnName='Electricity'")
546
- feature_report.reporting_periods[0].electricity_produced = convert_units(electricity_produced, 'GJ', 'kBtu')
547
-
548
- ## end_uses
549
-
550
- # get fuel type as listed in the sql file
551
- fuel_type = ['Electricity', 'Natural Gas', 'Additional Fuel', 'District Cooling', 'District Heating', 'Water']
552
-
553
- # get enduses as listed in the sql file
554
- enduses = ['Heating', 'Cooling', 'Interior Lighting', 'Exterior Lighting', 'Interior Equipment', 'Exterior Equipment', 'Fans', 'Pumps',
555
- 'Heat Rejection', 'Humidification', 'Heat Recovery', 'Water Systems', 'Refrigeration', 'Generators']
556
-
557
- # loop through fuel types and enduses to fill in sql_query method
558
- fuel_type.each do |ft|
559
- enduses.each do |eu|
560
- sql_r = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='End Uses' AND RowName='#{eu}' AND ColumnName='#{ft}'")
561
-
562
- # report each query in its corresponding feature report obeject
563
- if ft.include? ' '
564
- x = ft.tr(' ', '_').downcase
565
- m = feature_report.reporting_periods[0].end_uses.send(x)
566
- else
567
- m = feature_report.reporting_periods[0].end_uses.send(ft.downcase)
568
-
569
- end
570
-
571
- if eu.include? ' '
572
- y = eu.tr(' ', '_').downcase
573
- m.send("#{y}=", convert_units(sql_r, 'GJ', 'kBtu'))
574
- else
575
- m.send("#{eu.downcase}=", convert_units(sql_r, 'GJ', 'kBtu'))
576
- end
577
- end
578
- end
579
-
580
- ### energy_production
581
- ## electricity_produced
582
- # photovoltaic
583
- photovoltaic_power = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Electric Loads Satisfied' AND RowName='Photovoltaic Power' AND ColumnName='Electricity'")
584
- feature_report.reporting_periods[0].energy_production[:electricity_produced][:photovoltaic] = convert_units(photovoltaic_power, 'GJ', 'kBtu')
585
-
586
- ## comfort_result
587
- # time_setpoint_not_met_during_occupied_cooling
588
- 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'")
589
- feature_report.reporting_periods[0].comfort_result[:time_setpoint_not_met_during_occupied_cooling] = time_setpoint_not_met_during_occupied_cooling
590
-
591
- # time_setpoint_not_met_during_occupied_heating
592
- 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'")
593
- feature_report.reporting_periods[0].comfort_result[:time_setpoint_not_met_during_occupied_heating] = time_setpoint_not_met_during_occupied_heating
594
-
595
- # time_setpoint_not_met_during_occupied_hour
596
- time_setpoint_not_met_during_occupied_hours = time_setpoint_not_met_during_occupied_heating + time_setpoint_not_met_during_occupied_cooling
597
- feature_report.reporting_periods[0].comfort_result[:time_setpoint_not_met_during_occupied_hours] = time_setpoint_not_met_during_occupied_hours
598
-
599
- ######################################## Reporting TImeseries Results FOR CSV File ######################################
600
-
601
- # Get the weather file run period (as opposed to design day run period)
602
- ann_env_pd = nil
603
- sql_file.availableEnvPeriods.each do |env_pd|
604
- env_type = sql_file.environmentType(env_pd)
605
- if env_type.is_initialized
606
- if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')
607
- ann_env_pd = env_pd
608
- end
609
- end
610
- end
611
-
612
- if ann_env_pd == false
613
- runner.registerError("Can't find a weather runperiod, make sure you ran an annual simulation, not just the design days.")
614
- return false
615
- end
616
-
617
- # timeseries we want to report
618
- requested_timeseries_names = [
619
- 'Electricity:Facility',
620
- 'ElectricityProduced:Facility',
621
- 'Gas:Facility',
622
- 'Cooling:Electricity',
623
- 'Heating:Electricity',
624
- 'InteriorLights:Electricity',
625
- 'ExteriorLights:Electricity',
626
- 'InteriorEquipment:Electricity',
627
- 'Fans:Electricity',
628
- 'Pumps:Electricity',
629
- 'WaterSystems:Electricity',
630
- 'Heating:Gas',
631
- 'WaterSystems:Gas',
632
- 'InteriorEquipment:Gas',
633
- 'DistrictCooling:Facility',
634
- 'DistrictHeating:Facility',
635
- 'District Cooling Chilled Water Rate',
636
- 'District Cooling Mass Flow Rate',
637
- 'District Cooling Inlet Temperature',
638
- 'District Cooling Outlet Temperature',
639
- 'District Heating Hot Water Rate',
640
- 'District Heating Mass Flow Rate',
641
- 'District Heating Inlet Temperature',
642
- 'District Heating Outlet Temperature'
643
- ]
644
-
645
- # add thermal comfort timeseries
646
- comfortTimeseries = ['Zone Thermal Comfort Fanger Model PMV', 'Zone Thermal Comfort Fanger Model PPD']
647
- requested_timeseries_names += comfortTimeseries
648
-
649
- # add additional power timeseries (for calculating transformer apparent power to compare to rating ) in VA
650
- powerTimeseries = ['Net Electric Energy', 'Electricity:Facility Power', 'ElectricityProduced:Facility Power', 'Electricity:Facility Apparent Power', 'ElectricityProduced:Facility Apparent Power', 'Net Power', 'Net Apparent Power']
651
- requested_timeseries_names += powerTimeseries
652
-
653
- # register info all timeseries
654
- runner.registerInfo("All timeseries: #{requested_timeseries_names}")
655
-
656
- # timeseries variables to keep to calculate power
657
- tsToKeep = ['Electricity:Facility', 'ElectricityProduced:Facility']
658
- tsToKeepIndexes = {}
659
-
660
- ### powerFactor ###
661
- # use power_factor default: 0.9
662
- # TODO: Set powerFactor default based on building type
663
- powerFactor = 0.9
664
-
665
- ### power_conversion ###
666
- # divide values by total_seconds to convert J to W (W = J/sec)
667
- # divide values by total_hours to convert kWh to kW (kW = kWh/hrs)
668
- total_seconds = (60 / timesteps_per_hour.to_f) * 60 # make sure timesteps_per_hour is a float in the division
669
- total_hours = 1 / timesteps_per_hour.to_f # make sure timesteps_per_hour is a float in the division
670
- # set power_conversion
671
- power_conversion = total_hours # we set the power conversio to total_hours since we want to convert lWh to kW
672
- puts "Power Converion: to convert kWh to kW values will be divided by #{power_conversion}"
673
-
674
- # number of values in each timeseries
675
- n = nil
676
- # all numeric timeseries values, transpose of CSV file (e.g. values[key_cnt] is column, values[key_cnt][i] is column and row)
677
- values = []
678
- tmpArray = []
679
- # since schedule value will have a bunch of key_values, we need to keep track of these as additional timeseries
680
- key_cnt = 0
681
- # this is recording the name of these final timeseries to write in the header of the CSV
682
- final_timeseries_names = []
683
-
684
- # loop over requested timeseries
685
- # rubocop: disable Metrics/BlockLength
686
- requested_timeseries_names.each_index do |i|
687
- timeseries_name = requested_timeseries_names[i]
688
- runner.registerInfo("TIMESERIES: #{timeseries_name}")
689
-
690
- # get all the key values that this timeseries can be reported for (e.g. if PMV is requested for each zone)
691
- key_values = sql_file.availableKeyValues('RUN PERIOD 1', 'Zone Timestep', timeseries_name)
692
- runner.registerInfo("KEY VALUES: #{key_values}")
693
- if key_values.empty?
694
- key_values = ['']
695
- end
696
-
697
- # sort keys
698
- sorted_keys = key_values.sort
699
- requested_keys = requested_timeseries_names
700
- final_keys = []
701
- # make sure aggregated timeseries are listed in sorted order before all individual feature timeseries
702
- sorted_keys.each do |k|
703
- if requested_keys.include? k
704
- final_keys << k
705
- end
706
- end
707
- sorted_keys.each do |k|
708
- if !requested_keys.include? k
709
- final_keys << k
710
- end
711
- end
712
-
713
- # loop over final keys
714
- final_keys.each_with_index do |key_value, key_i|
715
- new_timeseries_name = ''
716
-
717
- runner.registerInfo("!! TIMESERIES NAME: #{timeseries_name} AND key_value: #{key_value}")
718
-
719
- # check if we have to come up with a new name for the timeseries in our CSV header
720
- if key_values.size == 1
721
- # use timeseries name when only 1 keyvalue
722
- new_timeseries_name = timeseries_name
723
- else
724
- # use key_value name
725
- # special case for Zone Thermal Comfort: use both timeseries_name and key_value
726
- if timeseries_name.include? 'Zone Thermal Comfort'
727
- new_timeseries_name = timeseries_name + ' ' + key_value
728
- else
729
- new_timeseries_name = key_value
730
- end
731
- end
732
- # final_timeseries_names << new_timeseries_name
733
-
734
- # get the actual timeseries
735
- ts = sql_file.timeSeries(ann_env_pd.to_s, reporting_frequency.to_s, timeseries_name, key_value)
736
-
737
- if n.nil?
738
- # first timeseries should always be set
739
- runner.registerInfo('First timeseries')
740
- values[key_cnt] = ts.get.values
741
- n = values[key_cnt].size
742
- elsif ts.is_initialized
743
- runner.registerInfo('Is Initialized')
744
- values[key_cnt] = ts.get.values
745
- else
746
- runner.registerInfo('Is NOT Initialized')
747
- values[key_cnt] = Array.new(n, 0)
748
- end
749
-
750
- # unit conversion
751
- old_unit = ts.get.units if ts.is_initialized
752
-
753
- if timeseries_name.include? 'Gas'
754
- new_unit = 'kBtu'
755
- else
756
- new_unit = case old_unit.to_s
757
- when 'J'
758
- 'kWh'
759
- when 'kBtu'
760
- 'kWh'
761
- when 'gal'
762
- 'm3'
763
- end
764
- end
765
- # loop through each value and apply unit conversion
766
- os_vec = values[key_cnt]
767
- if !timeseries_name.include? 'Zone Thermal Comfort'
768
- for i in 0..os_vec.length - 1
769
- unless new_unit == old_unit || !ts.is_initialized
770
- os_vec[i] = OpenStudio.convert(os_vec[i], old_unit, new_unit).get
771
- end
772
- end
773
- end
774
-
775
- # keep certain timeseries to calculate power
776
- if tsToKeep.include? timeseries_name
777
- tsToKeepIndexes[timeseries_name] = key_cnt
778
- end
779
-
780
- # special processing: power
781
- if powerTimeseries.include? timeseries_name
782
- # special case: net series (subtract generation from load)
783
- if timeseries_name.include? 'Net'
784
-
785
- newVals = Array.new(n, 0)
786
- # Apparent power calculation
787
-
788
- if timeseries_name.include?('Apparent')
789
- (0..n - 1).each do |j|
790
- newVals[j] = (values[tsToKeepIndexes['Electricity:Facility']][j].to_f - values[tsToKeepIndexes['ElectricityProduced:Facility']][j].to_f) / power_conversion / powerFactor
791
- j += 1
792
- end
793
- new_unit = 'kVA'
794
- elsif timeseries_name.include? 'Net Electric Energy'
795
- (0..n - 1).each do |j|
796
- newVals[j] = (values[tsToKeepIndexes['Electricity:Facility']][j].to_f - values[tsToKeepIndexes['ElectricityProduced:Facility']][j].to_f)
797
- j += 1
798
- end
799
- new_unit = 'kWh'
800
- else
801
- runner.registerInfo('Power calc')
802
- # Power calculation
803
- (0..n - 1).each do |j|
804
- newVals[j] = (values[tsToKeepIndexes['Electricity:Facility']][j].to_f - values[tsToKeepIndexes['ElectricityProduced:Facility']][j].to_f) / power_conversion
805
- j += 1
806
- end
807
- new_unit = 'kW'
808
- end
809
-
810
- values[key_cnt] = newVals
811
- else
812
- tsToKeepIndexes.each do |key, indexValue|
813
- if timeseries_name.include? key
814
- runner.registerInfo("timeseries_name: #{timeseries_name}, key: #{key}")
815
- # use this timeseries
816
- newVals = Array.new(n, 0)
817
- # Apparent power calculation
818
- if timeseries_name.include?('Apparent')
819
- (0..n - 1).each do |j|
820
- newVals[j] = values[indexValue][j].to_f / power_conversion / powerFactor
821
- j += 1
822
- end
823
- new_unit = 'kVA'
824
- else
825
- # Power calculation
826
- (0..n - 1).each do |j|
827
- newVals[j] = values[indexValue][j].to_f / power_conversion
828
- j += 1
829
- end
830
- new_unit = 'kW'
831
- end
832
- values[key_cnt] = newVals
833
- end
834
- end
835
- end
836
- end
837
-
838
- # append units to headers
839
- new_timeseries_name += "(#{new_unit})"
840
- final_timeseries_names << new_timeseries_name
841
-
842
- # TODO: DELETE PUTS
843
- # puts " *********timeseries_name = #{timeseries_name}******************"
844
- # if timeseries_name.include? 'Power'
845
- # puts "values = #{values[key_cnt]}"
846
- # puts "units = #{new_unit}"
847
- # end
848
-
849
- # comfort results usually have multiple timeseries (per zone), aggregate into a single series with consistent name and use worst value at each timestep
850
- if comfortTimeseries.include? timeseries_name
851
-
852
- # set up array if 1st key_value
853
- if key_i == 0
854
- runner.registerInfo("SETTING UP NEW ARRAY FOR: #{timeseries_name}")
855
- tmpArray = Array.new(n, 0)
856
- end
857
-
858
- # add to array (keep max value at each timestep)
859
- (0..(n - 1)).each do |ind|
860
- # process negative and positive values differently
861
- tVal = values[key_cnt][ind].to_f
862
- if tVal < 0
863
- tmpArray[ind] = [tVal, tmpArray[ind]].min
864
- else
865
- tmpArray[ind] = [tVal, tmpArray[ind]].max
866
- end
867
- end
868
-
869
- # aggregate and save when all keyvalues have been processed
870
- if key_i == final_keys.size - 1
871
-
872
- hrsOutOfBounds = 0
873
- if timeseries_name === 'Zone Thermal Comfort Fanger Model PMV'
874
- (0..(n - 1)).each do |ind|
875
- # -0.5 < x < 0.5 is within bounds
876
- if values[key_cnt][ind].to_f > 0.5 || values[key_cnt][ind].to_f < -0.5
877
- hrsOutOfBounds += 1
878
- end
879
- end
880
- hrsOutOfBounds = hrsOutOfBounds.to_f / timesteps_per_hour
881
- elsif timeseries_name === 'Zone Thermal Comfort Fanger Model PPD'
882
- (0..(n - 1)).each do |ind|
883
- # > 20 is outside bounds
884
- if values[key_cnt][ind].to_f > 20
885
- hrsOutOfBounds += 1
886
- end
887
- end
888
- hrsOutOfBounds = hrsOutOfBounds.to_f / timesteps_per_hour
889
- else
890
- # this one is already scaled by timestep, no need to divide total
891
- (0..(n - 1)).each do |ind|
892
- hrsOutOfBounds += values[key_cnt][ind].to_f if values[key_cnt][ind].to_f > 0
893
- end
894
- end
895
-
896
- # save variable to feature_reports hash
897
- runner.registerInfo("timeseries #{timeseries_name}: hours out of bounds: #{hrsOutOfBounds}")
898
- if timeseries_name === 'Zone Thermal Comfort Fanger Model PMV'
899
- feature_report.reporting_periods[0].comfort_result[:hours_out_of_comfort_bounds_PMV] = hrsOutOfBounds
900
- elsif timeseries_name == 'Zone Thermal Comfort Fanger Model PPD'
901
- feature_report.reporting_periods[0].comfort_result[:hours_out_of_comfort_bounds_PPD] = hrsOutOfBounds
902
- end
903
-
904
- end
905
-
906
- end
907
-
908
- # increment key_cnt in new_keys loop
909
- key_cnt += 1
910
- end
911
- end
912
-
913
- # Add datime column
914
- datetimes = []
915
- # check what timeseries is available
916
- available_ts = sql_file.availableTimeSeries
917
- # get the timeseries for any of available timeseries
918
- ts_d = sql_file.timeSeries(ann_env_pd.to_s, reporting_frequency.to_s, available_ts[0], '')
919
- timeseries_d = ts_d.get
920
- # get formated datetimes
921
- timeseries_d.dateTimes.each do |datetime|
922
- datetimes << format_datetime(datetime.to_s)
923
- end
924
- # insert datetimes to values
925
- values.insert(0, datetimes)
926
- # insert datetime header to names
927
- final_timeseries_names.insert(0, 'Datetime')
928
-
929
- # rubocop: enable Metrics/BlockLength
930
- runner.registerInfo("new final_timeseries_names size: #{final_timeseries_names.size}")
931
-
932
- # Save the 'default_feature_reports.csv' file
933
- File.open('default_feature_reports.csv', 'w') do |file|
934
- file.puts(final_timeseries_names.join(','))
935
- (0...n).each do |l|
936
- line = []
937
- values.each_index do |j|
938
- line << values[j][l]
939
- end
940
- file.puts(line.join(','))
941
- end
942
- end
943
-
944
- # closing the sql file
945
- sql_file.close
946
-
947
- ############################# Adding timeseries_csv info to json report and saving CSV ################################
948
- # add csv info to feature_report
949
- feature_report.timeseries_csv.path = File.join(Dir.pwd, 'default_feature_reports.csv')
950
- feature_report.timeseries_csv.first_report_datetime = '0'
951
- feature_report.timeseries_csv.column_names = final_timeseries_names
952
-
953
- ##### Save the 'default_feature_reports.json' file
954
-
955
- feature_report_hash = feature_report.to_hash
956
-
957
- File.open('default_feature_reports.json', 'w') do |f|
958
- f.puts JSON.pretty_generate(feature_report_hash)
959
- # make sure data is written to the disk one way or the other
960
- begin
961
- f.fsync
962
- rescue StandardError
963
- f.flush
964
- end
965
- end
966
-
967
- # reporting final condition
968
- runner.registerFinalCondition('Default Feature Reports generated successfully.')
969
-
970
- true
971
- # end the run method
972
- end
973
- # end the measure
974
- end
975
- # rubocop:enable Metrics/AbcSize
976
- # rubocop:enable Naming/MethodName
977
-
978
- # register the measure to be used by the application
979
- DefaultFeatureReports.new.registerWithApplication