urbanopt-reporting 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/.rubocop.yml +10 -0
  4. data/CHANGELOG.md +7 -0
  5. data/CONTRIBUTING.md +58 -0
  6. data/Gemfile +18 -0
  7. data/Jenkinsfile +10 -0
  8. data/LICENSE.md +27 -0
  9. data/README.md +40 -0
  10. data/Rakefile +45 -0
  11. data/doc_templates/LICENSE.md +27 -0
  12. data/doc_templates/README.md.erb +42 -0
  13. data/doc_templates/copyright_erb.txt +31 -0
  14. data/doc_templates/copyright_js.txt +4 -0
  15. data/doc_templates/copyright_ruby.txt +29 -0
  16. data/lib/measures/.rubocop.yml +5 -0
  17. data/lib/measures/default_feature_reports/LICENSE.md +27 -0
  18. data/lib/measures/default_feature_reports/README.md +26 -0
  19. data/lib/measures/default_feature_reports/README.md.erb +42 -0
  20. data/lib/measures/default_feature_reports/measure.rb +1012 -0
  21. data/lib/measures/default_feature_reports/measure.xml +160 -0
  22. data/lib/urbanopt/reporting.rb +37 -0
  23. data/lib/urbanopt/reporting/default_reports.rb +44 -0
  24. data/lib/urbanopt/reporting/default_reports/construction_cost.rb +169 -0
  25. data/lib/urbanopt/reporting/default_reports/date.rb +97 -0
  26. data/lib/urbanopt/reporting/default_reports/distributed_generation.rb +379 -0
  27. data/lib/urbanopt/reporting/default_reports/end_use.rb +159 -0
  28. data/lib/urbanopt/reporting/default_reports/end_uses.rb +140 -0
  29. data/lib/urbanopt/reporting/default_reports/extension.rb +15 -0
  30. data/lib/urbanopt/reporting/default_reports/feature_report.rb +266 -0
  31. data/lib/urbanopt/reporting/default_reports/generator.rb +92 -0
  32. data/lib/urbanopt/reporting/default_reports/location.rb +99 -0
  33. data/lib/urbanopt/reporting/default_reports/logger.rb +44 -0
  34. data/lib/urbanopt/reporting/default_reports/power_distribution.rb +103 -0
  35. data/lib/urbanopt/reporting/default_reports/program.rb +265 -0
  36. data/lib/urbanopt/reporting/default_reports/reporting_period.rb +300 -0
  37. data/lib/urbanopt/reporting/default_reports/scenario_report.rb +317 -0
  38. data/lib/urbanopt/reporting/default_reports/schema/README.md +33 -0
  39. data/lib/urbanopt/reporting/default_reports/schema/scenario_csv_columns.txt +34 -0
  40. data/lib/urbanopt/reporting/default_reports/schema/scenario_schema.json +857 -0
  41. data/lib/urbanopt/reporting/default_reports/solar_pv.rb +93 -0
  42. data/lib/urbanopt/reporting/default_reports/storage.rb +105 -0
  43. data/lib/urbanopt/reporting/default_reports/timeseries_csv.rb +300 -0
  44. data/lib/urbanopt/reporting/default_reports/validator.rb +112 -0
  45. data/lib/urbanopt/reporting/default_reports/wind.rb +92 -0
  46. data/lib/urbanopt/reporting/derived_extension.rb +63 -0
  47. data/lib/urbanopt/reporting/version.rb +35 -0
  48. data/urbanopt-reporting-gem.gemspec +33 -0
  49. metadata +176 -0
@@ -0,0 +1,93 @@
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 'json'
32
+ require 'json-schema'
33
+
34
+ module URBANopt
35
+ module Reporting
36
+ module DefaultReports
37
+ ##
38
+ # Onsite solar PV system attributes
39
+ ##
40
+ class SolarPV
41
+ ##
42
+ # _Float_ - power capacity in kilowatts
43
+ #
44
+ attr_accessor :size_kw
45
+
46
+ ##
47
+ # Initialize SolarPV attributes from a hash. Solar PV attributes currently are limited to power capacity.
48
+ ##
49
+ # [parameters:]
50
+ #
51
+ # * +hash+ - _Hash_ - A hash containting a +:size_kw+ key/value pair which represents the nameplate capacity in kilowatts (kW)
52
+ #
53
+ def initialize(hash = {})
54
+ hash.delete_if { |k, v| v.nil? }
55
+
56
+ @size_kw = hash[:size_kw]
57
+ @id = hash[:id]
58
+
59
+ # initialize class variables @@validator and @@schema
60
+ @@validator ||= Validator.new
61
+ @@schema ||= @@validator.schema
62
+
63
+ # initialize @@logger
64
+ @@logger ||= URBANopt::Scenario::DefaultReports.logger
65
+ end
66
+
67
+ ##
68
+ # Convert to a Hash equivalent for JSON serialization
69
+ ##
70
+ def to_hash
71
+ result = {}
72
+
73
+ result[:size_kw] = @size_kw if @size_kw
74
+
75
+ return result
76
+ end
77
+
78
+ ##
79
+ # Merge PV systems
80
+ ##
81
+ def self.add_pv(existing_pv, new_pv)
82
+ if existing_pv.size_kw.nil? && new_pv.size_kw.nil?
83
+ existing_pv.size_kw = nil
84
+ else
85
+ existing_pv.size_kw = (existing_pv.size_kw || 0) + (new_pv.size_kw || 0)
86
+ end
87
+
88
+ return existing_pv
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,105 @@
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 'json'
32
+ require 'json-schema'
33
+
34
+ module URBANopt
35
+ module Reporting
36
+ module DefaultReports
37
+ ##
38
+ # Onsite storage system attributes
39
+ ##
40
+ class Storage
41
+ ##
42
+ # _Float_ - power capacity in kilowatts
43
+ #
44
+ attr_accessor :size_kw
45
+
46
+ ##
47
+ # _Float_ - storage capacity in kilowatt-hours
48
+ #
49
+ attr_accessor :size_kwh
50
+
51
+ ##
52
+ # Initialize Storage attributes from a hash. Storage attributes currently are limited to power and storage capacity.
53
+ ##
54
+ # [parameters:]
55
+ #
56
+ # * +hash+ - _Hash_ - A hash containting +:size_kw+ and +:size_kwh+ key/value pair which represents the power and storage capacity in kilowatts (kW) and kilowatt-hours respectively.
57
+ #
58
+ def initialize(hash = {})
59
+ hash.delete_if { |k, v| v.nil? }
60
+
61
+ @size_kw = hash[:size_kw]
62
+ @size_kwh = hash[:size_kwh]
63
+
64
+ # initialize class variables @@validator and @@schema
65
+ @@validator ||= Validator.new
66
+ @@schema ||= @@validator.schema
67
+
68
+ # initialize @@logger
69
+ @@logger ||= URBANopt::Scenario::DefaultReports.logger
70
+ end
71
+
72
+ ##
73
+ # Convert to a Hash equivalent for JSON serialization
74
+ ##
75
+ def to_hash
76
+ result = {}
77
+
78
+ result[:size_kw] = @size_kw if @size_kw
79
+ result[:size_kwh] = @size_kwh if @size_kwh
80
+
81
+ return result
82
+ end
83
+
84
+ ##
85
+ # Merge Storage systems
86
+ ##
87
+ def self.add_storage(existing_storage, new_storage)
88
+ if existing_storage.size_kw.nil?
89
+ existing_storage.size_kw = new_storage.size_kw
90
+ else
91
+ existing_storage.size_kw = (existing_storage.size_kw || 0) + (new_storage.size_kw || 0)
92
+ end
93
+
94
+ if existing_storage.size_kw.nil?
95
+ existing_storage.size_kwh = new_storage.size_kwh
96
+ else
97
+ existing_storage.size_kwh = (existing_storage.size_kwh || 0) + (new_storage.size_kwh || 0)
98
+ end
99
+
100
+ return existing_storage
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,300 @@
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_relative 'validator'
32
+ require_relative 'logger'
33
+
34
+ require 'csv'
35
+ require 'pathname'
36
+ require 'json-schema'
37
+
38
+ module URBANopt
39
+ module Reporting
40
+ module DefaultReports
41
+ ##
42
+ # TimeseriesCSV include timesries reults reported in a CSV file.
43
+ ##
44
+ class TimeseriesCSV
45
+ attr_accessor :path, :first_report_datetime, :column_names #:nodoc:
46
+
47
+ ##
48
+ # TimeseriesCSV class initializes timeseries csv attributes: +:path+ , +:first_report_datetime+ , +:column_names+
49
+ ##
50
+ # +hash+ - _Hash_ - A hash which may contain a deserialized timeseries_csv.
51
+ ##
52
+ def initialize(hash = {})
53
+ hash.delete_if { |k, v| v.nil? }
54
+ hash = defaults.merge(hash)
55
+
56
+ @run_dir = ''
57
+
58
+ @path = hash[:path]
59
+ @first_report_datetime = hash[:first_report_datetime]
60
+
61
+ # from scenario csv shema get required reults to be aggregated
62
+ @required_column_names = load_scenario_csv_schema_headers
63
+
64
+ @column_names = hash[:column_names]
65
+ @column_names.delete_if { |x| !@required_column_names.include? x.split('(')[0] }
66
+
67
+ # hash of column_name to array of values, does not get serialized to hash
68
+ @mutex = Mutex.new
69
+ @data = nil
70
+
71
+ # initialize class variables @@validator and @@schema
72
+ @@validator ||= Validator.new
73
+ @@schema ||= @@validator.schema
74
+
75
+ # initialize @@logger
76
+ @@logger ||= URBANopt::Reporting::DefaultReports.logger
77
+ end
78
+
79
+ ##
80
+ # load required scenario report csv headers from reports schema
81
+ ##
82
+ def load_scenario_csv_schema_headers
83
+ # rubocop: disable Security/Open
84
+ scenario_csv_schema = open(File.expand_path('../default_reports/schema/scenario_csv_columns.txt', File.dirname(__FILE__)))
85
+ # rubocop: enable Security/Open
86
+
87
+ scenario_csv_schema_headers = []
88
+ File.readlines(scenario_csv_schema).each do |line|
89
+ l = line.delete("\n")
90
+ a = l.delete("\t")
91
+ r = a.delete("\r")
92
+ scenario_csv_schema_headers << r
93
+ end
94
+ return scenario_csv_schema_headers
95
+ end
96
+
97
+ ##
98
+ # Assigns default values if values does not exist.
99
+ ##
100
+ def defaults
101
+ hash = {}
102
+ hash[:path] = nil
103
+ hash[:column_names] = []
104
+ return hash
105
+ end
106
+
107
+ ##
108
+ # Gets run directory.
109
+ ##
110
+ # [parameters:]
111
+ # +name+ - _String_ - The name of the scenario (+directory_name+).
112
+ ##
113
+ def run_dir_name(name)
114
+ @run_dir = name
115
+ end
116
+
117
+ ##
118
+ # Converts to a Hash equivalent for JSON serialization.
119
+ ##
120
+ # - Exclude attributes with nil values.
121
+ # - Validate reporting_period hash properties against schema.
122
+ ##
123
+ def to_hash
124
+ result = {}
125
+ directory_path = Pathname.new File.expand_path(@run_dir.to_s, File.dirname(__FILE__)) if @run_dir
126
+ csv_path = Pathname.new @path if @path
127
+
128
+ relative_path = csv_path.to_s.sub(directory_path.to_s, '')
129
+
130
+ result[:path] = relative_path if @path
131
+ result[:first_report_datetime] = @first_report_datetime if @first_report_datetime
132
+ result[:column_names] = @column_names if @column_names
133
+
134
+ # validate timeseries_csv properties against schema
135
+ if @@validator.validate(@@schema[:definitions][:TimeseriesCSV][:properties], result).any?
136
+ raise "scenario_report properties does not match schema: #{@@validator.validate(@@schema[:definitions][:TimeseriesCSV][:properties], result)}"
137
+ end
138
+
139
+ return result
140
+ end
141
+
142
+ ##
143
+ # Reloads data from the CSV file.
144
+ ##
145
+ def reload_data(new_data)
146
+ @mutex.synchronize do
147
+ @data = {}
148
+ @column_names = []
149
+ new_data.each do |row|
150
+ if @column_names.empty?
151
+ @column_names = row
152
+ @column_names.each do |column_name|
153
+ @data[column_name] = []
154
+ end
155
+ else
156
+ row.each_with_index do |value, i|
157
+ if i == 0
158
+ @data[@column_names[i]] << value
159
+ else
160
+ @data[@column_names[i]] << value.to_f
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ ##
169
+ # Loads data from the CSV file.
170
+ ##
171
+ def load_data
172
+ @mutex.synchronize do
173
+ if @data.nil?
174
+ @data = {}
175
+ @column_names = []
176
+ CSV.foreach(@path) do |row|
177
+ if @column_names.empty?
178
+ @column_names = row
179
+ @column_names.each do |column_name|
180
+ @data[column_name] = []
181
+ end
182
+ else
183
+ row.each_with_index do |value, i|
184
+ @data[@column_names[i]] << value
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ ##
193
+ # Gets data for each column name in the CSV file.
194
+ ##
195
+ # [parameters:]
196
+ # +column_name+ - _String_ - The header of each column in the CSV file.
197
+ ##
198
+ def get_data(column_name)
199
+ load_data
200
+ return @data[column_name]
201
+ end
202
+
203
+ ##
204
+ # Saves data to the the scenario report CSV file.
205
+ ##
206
+ # [parameters:]
207
+ # +path+ - _String_ - The path of the scenario report CSV (default_scenario_report.csv).
208
+ ##
209
+ def save_data(path = nil)
210
+ if path.nil?
211
+ path = @path
212
+ end
213
+
214
+ File.open(path, 'w') do |f|
215
+ f.puts @column_names.join(',')
216
+ n = @data[@column_names[0]].size - 1
217
+
218
+ (0..n).each do |i|
219
+ line = []
220
+ @column_names.each do |column_name|
221
+ line << @data[column_name][i]
222
+ end
223
+ f.puts line.join(',')
224
+ end
225
+ begin
226
+ f.fsync
227
+ rescue StandardError
228
+ f.flush
229
+ end
230
+ end
231
+ end
232
+
233
+ ##
234
+ # Merges timeseries csv to each other.
235
+ ##
236
+ # - initialize first_report_datetime with the incoming first_report_datetime if its nil.
237
+ # - checks if first_report_datetime are identical.
238
+ # - merge the column names
239
+ # - merge the column data
240
+ ##
241
+ # [parameters:]
242
+ # +other+ - _TimeseriesCSV_ - An object of TimeseriesCSV class.
243
+ ##
244
+ def add_timeseries_csv(other)
245
+ # initialize first_report_datetime with the incoming first_report_datetime if its nil.
246
+ if @first_report_datetime.nil? || @first_report_datetime == ''
247
+ @first_report_datetime = other.first_report_datetime
248
+ end
249
+
250
+ # checks if first_report_datetime are identical.
251
+ if @first_report_datetime != other.first_report_datetime
252
+ raise "first_report_datetime '#{@first_report_datetime}' does not match other.first_report_datetime '#{other.first_report_datetime}'"
253
+ end
254
+
255
+ # merge the column names
256
+ other_column_names = []
257
+ other.column_names.each do |n|
258
+ if !n[0, 4].casecmp('ZONE').zero?
259
+ other_column_names << n
260
+ end
261
+ end
262
+
263
+ @column_names = @column_names.concat(other_column_names).uniq
264
+
265
+ # merge the column data
266
+ other.column_names.each do |column_name|
267
+ if !column_name[0, 4].casecmp('ZONE').zero?
268
+ if !@column_names.include? column_name
269
+ @column_names.push column_name
270
+ end
271
+
272
+ new_values = other.get_data(column_name)
273
+
274
+ if @data.nil?
275
+ @data = {}
276
+ end
277
+
278
+ current_values = @data[column_name]
279
+
280
+ if current_values
281
+ if current_values.size != new_values.size
282
+ raise 'Values of different sizes in add_timeseries_csv'
283
+ end
284
+ new_values.each_with_index do |value, i|
285
+ # aggregate all columns except Datime column
286
+ if column_name != 'Datetime'
287
+ new_values[i] = value.to_f + current_values[i].to_f
288
+ end
289
+ end
290
+ @data[column_name] = new_values
291
+ else
292
+ @data[column_name] = new_values
293
+ end
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end
299
+ end
300
+ end