urbanopt-reporting 0.6.2 → 0.7.0

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.
@@ -0,0 +1,212 @@
1
+ # *********************************************************************************
2
+ # URBANopt™, Copyright (c) 2019-2022, 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
+ # Redistribution of this software, without modification, must refer to the software
20
+ # by the same designation. Redistribution of a modified version of this software
21
+ # (i) may not refer to the modified version by the same designation, or by any
22
+ # confusingly similar designation, and (ii) must refer to the underlying software
23
+ # originally provided by Alliance as “URBANopt”. Except to comply with the foregoing,
24
+ # the term “URBANopt”, or any confusingly similar designation may not be used to
25
+ # refer to any modified version of this software or any modified version of the
26
+ # underlying software originally provided by Alliance without the prior written
27
+ # consent of Alliance.
28
+
29
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
30
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
31
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
32
+ # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
34
+ # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
36
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
37
+ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
38
+ # OF THE POSSIBILITY OF SUCH DAMAGE.
39
+ # *********************************************************************************
40
+
41
+ require_relative 'validator'
42
+
43
+ require 'json'
44
+ require 'json-schema'
45
+
46
+ module URBANopt
47
+ module Reporting
48
+ module DefaultReports
49
+ ##
50
+ # scenario_power_distribution_cost include eletrical power distribution system violation and
51
+ # upgrade cost information.
52
+ ##
53
+ class ScenarioPowerDistributionCost
54
+ attr_accessor :results, :outputs, :violation_summary, :costs_per_equipment, :equipment
55
+
56
+ ##
57
+ # ScenarioPowerDistributionCost class initializes all
58
+ # scenario_power_distribution_cost attributes:
59
+ # +:results+, +:outputs+, +:violation_summary+, +:costs_per_equipment+, +:equipment+
60
+ ##
61
+ def initialize(hash = {})
62
+ hash.delete_if { |k, v| v.nil? }
63
+ hash = defaults.merge(hash)
64
+
65
+ @results = hash[:results]
66
+ @outputs = hash[:outputs]
67
+ @violation_summary = hash[:violation_summary]
68
+ @costs_per_equipment = hash[:costs_per_equipment]
69
+ @equipment = hash[:equipment]
70
+
71
+ # initialize class variables @@validator and @@schema
72
+ @@validator ||= Validator.new
73
+ @@schema ||= @@validator.schema
74
+ end
75
+
76
+ ##
77
+ # Assigns default values if attribute values do not exist.##
78
+ def defaults
79
+ hash = {}
80
+ hash[:results] = []
81
+ hash[:outputs] = {}
82
+ hash[:violation_summary] = []
83
+ hash[:costs_per_equipment] = []
84
+ hash[:equipment] = []
85
+
86
+ return hash
87
+ end
88
+
89
+ ##
90
+ # Converts to a Hash equivalent for JSON serialization.
91
+ ##
92
+ # - Exclude attributes with nil values.
93
+ # - Validate power_distribution_cost hash properties against schema.
94
+ ##
95
+ def to_hash
96
+ result = {}
97
+ result[:results] = @results if @results
98
+ result[:outputs] = @outputs if !@outputs.empty?
99
+ result[:violation_summary] = @violation_summary if @violation_summary
100
+ result[:costs_per_equipment] = @costs_per_equipment if @costs_per_equipment
101
+ result[:equipment] = @equipment if @equipment
102
+
103
+ # validate power_distribution_cost properties against schema
104
+ if @@validator.validate(@@schema[:definitions][:ScenarioPowerDistributionCost][:properties], result).any?
105
+ raise "scenario_power_distribution_cost properties does not match schema: #{@@validator.validate(@@schema[:definitions][:ScenarioPowerDistributionCost][:properties], result)}"
106
+ end
107
+
108
+ return result
109
+ end
110
+
111
+ ##
112
+ # Add a result
113
+ ##
114
+ def add_result(hash = {})
115
+ hash.delete_if { |k, v| v.nil? }
116
+ hash = defaults.merge(hash)
117
+ result = {}
118
+ result['num_violations'] = hash[:num_violations]
119
+ @results << result
120
+ end
121
+
122
+ ##
123
+ ## Add outputs
124
+ ##
125
+ def add_outputs(hash = {})
126
+ hash.delete_if { |k, v| v.nil? }
127
+ hash = defaults.merge(hash)
128
+ output = {}
129
+ output['log_file'] = hash[:log_file]
130
+ output['jobs'] = []
131
+ hash[:jobs].each do |job|
132
+ output['jobs'] << job
133
+ end
134
+ @outputs = output
135
+ end
136
+
137
+ ##
138
+ ## Add a violation summary
139
+ ##
140
+ def add_violation_summary(hash = {})
141
+ hash.delete_if { |k, v| v.nil? }
142
+ hash = defaults.merge(hash)
143
+ violation_summary = {}
144
+ violation_summary['scenario'] = hash[:scenario]
145
+ violation_summary['stage'] = hash[:stage]
146
+ violation_summary['upgrade_type'] = hash[:upgrade_type]
147
+ violation_summary['simulation_time_s'] = hash[:simulation_time_s]
148
+ violation_summary['thermal_violations_present'] = hash[:thermal_violations_present]
149
+ violation_summary['voltage_violations_present'] = hash[:voltage_violations_present]
150
+ violation_summary['max_bus_voltage'] = hash[:max_bus_voltage]
151
+ violation_summary['min_bus_voltage'] = hash[:min_bus_voltage]
152
+ violation_summary['num_voltage_violation_buses'] = hash[:num_voltage_violation_buses]
153
+ violation_summary['num_overvoltage_violation_buses'] = hash[:num_overvoltage_violation_buses]
154
+ violation_summary['voltage_upper_limit'] = hash[:voltage_upper_limit]
155
+ violation_summary['num_undervoltage_violation_buses'] = hash[:num_undervoltage_violation_buses]
156
+ violation_summary['voltage_lower_limit'] = hash[:voltage_lower_limit]
157
+ violation_summary['max_line_loading'] = hash[:max_line_loading]
158
+ violation_summary['max_transformer_loading'] = hash[:max_transformer_loading]
159
+ violation_summary['num_line_violations'] = hash[:num_line_violations]
160
+ violation_summary['line_upper_limit'] = hash[:line_upper_limit]
161
+ violation_summary['num_transformer_violations'] = hash[:num_transformer_violations]
162
+ violation_summary['transformer_upper_limit'] = hash[:transformer_upper_limit]
163
+
164
+ @violation_summary << violation_summary
165
+ end
166
+
167
+ ##
168
+ # Add costs per equipment
169
+ ##
170
+ def add_costs_per_equipment
171
+ hash.delete_if { |k, v| v.nil? }
172
+ hash = defaults.merge(hash)
173
+ costs_per_equipment = {}
174
+ costs_per_equipment['name'] = hash[:name]
175
+ costs_per_equipment['type'] = hash[:type]
176
+ costs_per_equipment['count'] = hash[:count]
177
+ costs_per_equipment['total_cost_usd'] = hash[:costs_per_equipment]
178
+
179
+ @costs_per_equipment << costs_per_equipment
180
+ end
181
+
182
+ ##
183
+ # Add equipment
184
+ ##
185
+ def add_equipment
186
+ hash.delete_if { |k, v| v.nil? }
187
+ hash = defaults.merge(hash)
188
+ equipment = {}
189
+ equipment['equipment_type'] = hash[:equipment_type]
190
+ equipment['equipment_name'] = hash[:equipment_name]
191
+ equipment['status'] = hash[:status]
192
+ equipment['parameter1_name'] = hash[:parameter1_name]
193
+ equipment['parameter1_original'] = hash[:parameter1_original]
194
+ equipment['parameter1_upgraded'] = hash[:parameter1_upgraded]
195
+ equipment['parameter2_name'] = hash[:parameter2_name]
196
+ equipment['parameter2_original'] = hash[:parameter2_original]
197
+ equipment['parameter2_upgraded'] = hash[:parameter2_upgraded]
198
+ equipment['parameter3_name'] = hash[:parameter3_name]
199
+ equipment['parameter3_original'] = hash[:parameter3_original]
200
+ equipment['parameter3_upgraded'] = hash[:parameter3_upgraded]
201
+ equipment['name'] = hash[:name]
202
+
203
+ @equipment << equipment
204
+ end
205
+
206
+
207
+
208
+ end # ScenarioPowerDistributionCost
209
+
210
+ end # DefaultReports
211
+ end # Reporting
212
+ end # URBANopt
@@ -47,6 +47,8 @@ require_relative 'timeseries_csv'
47
47
  require_relative 'distributed_generation'
48
48
  require_relative 'validator'
49
49
  require_relative 'scenario_power_distribution'
50
+ require_relative 'scenario_power_distribution_cost'
51
+ require_relative 'qaqc_flags'
50
52
 
51
53
  require 'json'
52
54
  require 'json-schema'
@@ -64,12 +66,13 @@ module URBANopt
64
66
  attr_accessor :id, :name, :directory_name, :timesteps_per_hour, :number_of_not_started_simulations,
65
67
  :number_of_started_simulations, :number_of_complete_simulations, :number_of_failed_simulations,
66
68
  :timeseries_csv, :location, :program, :construction_costs, :reporting_periods, :feature_reports, :distributed_generation,
67
- :scenario_power_distribution # :nodoc:
69
+ :scenario_power_distribution, :scenario_power_distribution_cost, :qaqc_flags # :nodoc:
68
70
 
69
71
  # ScenarioReport class intializes the scenario report attributes:
70
72
  # +:id+ , +:name+ , +:directory_name+, +:timesteps_per_hour+ , +:number_of_not_started_simulations+ ,
71
73
  # +:number_of_started_simulations+ , +:number_of_complete_simulations+ , +:number_of_failed_simulations+ ,
72
- # +:timeseries_csv+ , +:location+ , +:program+ , +:construction_costs+ , +:reporting_periods+ , +:feature_reports+
74
+ # +:timeseries_csv+ , +:location+ , +:program+ , +:construction_costs+ , +:reporting_periods+ , +:feature_reports+,
75
+ # +:distributed_generation+, +:scenario_power_distribution+, +:qaqc_flags+
73
76
  ##
74
77
  # Each ScenarioReport object corresponds to a single Scenario.
75
78
  ##
@@ -93,6 +96,8 @@ module URBANopt
93
96
  @program = Program.new(hash[:program])
94
97
  @distributed_generation = DistributedGeneration.new(hash[:distributed_generation] || {})
95
98
  @scenario_power_distribution = ScenarioPowerDistribution.new(hash[:scenario_power_distribution] || {})
99
+ @scenario_power_distribution_cost = ScenarioPowerDistributionCost.new(hash[:scenario_power_distribution_cost] || {})
100
+ @qaqc_flags = QAQC.new(hash[:qaqc_flags])
96
101
 
97
102
  @construction_costs = []
98
103
  hash[:construction_costs].each do |cc|
@@ -136,6 +141,7 @@ module URBANopt
136
141
  hash[:timeseries_csv] = TimeseriesCSV.new.to_hash
137
142
  hash[:location] = Location.new.defaults
138
143
  hash[:program] = Program.new.to_hash
144
+ hash[:qaqc_flags] = QAQC.new.to_hash
139
145
  hash[:construction_costs] = []
140
146
  hash[:reporting_periods] = []
141
147
  hash[:feature_reports] = []
@@ -161,18 +167,20 @@ module URBANopt
161
167
  ##
162
168
  # [parameters]:
163
169
  # +file_name+ - _String_ - Assign a name to the saved scenario results file without an extension
164
- def save(file_name = 'default_scenario_report', save_feature_reports = true)
170
+ def save(file_name = 'default_scenario_report', save_feature_reports = true, save_csv_reports = true)
165
171
  # reassign the initialize local variable @file_name to the file name input.
166
172
  @file_name = file_name
167
173
 
168
- # save the scenario reports csv and json data
169
- old_timeseries_path = nil
170
- if !@timeseries_csv.path.nil?
171
- old_timeseries_path = @timeseries_csv.path
172
- end
174
+ if save_csv_reports == true
175
+ # save the scenario reports csv and json data
176
+ old_timeseries_path = nil
177
+ if !@timeseries_csv.path.nil?
178
+ old_timeseries_path = @timeseries_csv.path
179
+ end
173
180
 
174
- @timeseries_csv.path = File.join(@directory_name, "#{file_name}.csv")
175
- @timeseries_csv.save_data
181
+ @timeseries_csv.path = File.join(@directory_name, "#{file_name}.csv")
182
+ @timeseries_csv.save_data
183
+ end
176
184
 
177
185
  hash = {}
178
186
  hash[:scenario_report] = to_hash
@@ -193,10 +201,12 @@ module URBANopt
193
201
  end
194
202
  end
195
203
 
196
- if !old_timeseries_path.nil?
197
- @timeseries_csv.path = old_timeseries_path
198
- else
199
- @timeseries_csv.path = File.join(@directory_name, "#{file_name}.csv")
204
+ if save_csv_reports == true
205
+ if !old_timeseries_path.nil?
206
+ @timeseries_csv.path = old_timeseries_path
207
+ else
208
+ @timeseries_csv.path = File.join(@directory_name, "#{file_name}.csv")
209
+ end
200
210
  end
201
211
 
202
212
  if save_feature_reports
@@ -233,6 +243,8 @@ module URBANopt
233
243
  result[:program] = @program.to_hash if @program
234
244
  result[:distributed_generation] = @distributed_generation.to_hash if @distributed_generation
235
245
  result[:scenario_power_distribution] = @scenario_power_distribution.to_hash if @scenario_power_distribution
246
+ result[:scenario_power_distribution_cost] = @scenario_power_distribution_cost.to_hash if @scenario_power_distribution_cost
247
+ result[:qaqc_flags] = @qaqc_flags.to_hash if @qaqc_flags
236
248
 
237
249
  result[:construction_costs] = []
238
250
  @construction_costs&.each { |cc| result[:construction_costs] << cc.to_hash }
@@ -249,7 +261,9 @@ module URBANopt
249
261
  end
250
262
 
251
263
  # have to use the module method because we have not yet initialized the class one
252
- @@logger.info("Scenario name: #{@name}")
264
+ unless @name == '' || @name.nil?
265
+ @@logger.info("Scenario name: #{@name}")
266
+ end
253
267
 
254
268
  return result
255
269
  end
@@ -261,6 +275,7 @@ module URBANopt
261
275
  # - check feature simulation status
262
276
  # - merge timeseries_csv information
263
277
  # - merge program information
278
+ # - merge qaqc_flags information
264
279
  # - merge construction_cost information
265
280
  # - merge reporting_periods information
266
281
  # - add the array of feature_reports
@@ -324,8 +339,12 @@ module URBANopt
324
339
  # merge reporting_periods information
325
340
  @reporting_periods = ReportingPeriod.merge_reporting_periods(@reporting_periods, feature_report.reporting_periods)
326
341
 
342
+ # merge distributed_generation information
327
343
  @distributed_generation = DistributedGeneration.merge_distributed_generation(@distributed_generation, feature_report.distributed_generation)
328
344
 
345
+ # merge qaqc_flags information
346
+ @qaqc_flags.add_qaqc_flags(feature_report.qaqc_flags)
347
+
329
348
  # add feature_report
330
349
  @feature_reports << feature_report
331
350