urbanopt-scenario 0.3.0 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.github/pull_request_template.md +3 -3
  3. data/.gitignore +2 -0
  4. data/CHANGELOG.md +61 -0
  5. data/CONTRIBUTING.md +1 -1
  6. data/Gemfile +33 -17
  7. data/Jenkinsfile +1 -1
  8. data/LICENSE.md +1 -1
  9. data/RDOC_MAIN.md +2 -2
  10. data/README.md +1 -1
  11. data/Rakefile +1 -1
  12. data/docs/.vuepress/components/InnerJsonSchema.vue +7 -15
  13. data/docs/.vuepress/config.js +13 -9
  14. data/docs/.vuepress/highlight.js +1 -1
  15. data/docs/.vuepress/json-schema-deref-loader.js +22 -0
  16. data/docs/README.md +2 -2
  17. data/docs/package-lock.json +2384 -2323
  18. data/docs/package.json +12 -8
  19. data/lib/urbanopt-scenario.rb +1 -1
  20. data/lib/urbanopt/scenario.rb +2 -1
  21. data/lib/urbanopt/scenario/default_reports.rb +3 -8
  22. data/lib/urbanopt/scenario/extension.rb +6 -4
  23. data/lib/urbanopt/scenario/logger.rb +1 -1
  24. data/lib/urbanopt/scenario/scenario_base.rb +5 -5
  25. data/lib/urbanopt/scenario/scenario_csv.rb +29 -17
  26. data/lib/urbanopt/scenario/scenario_datapoint_base.rb +12 -5
  27. data/lib/urbanopt/scenario/scenario_post_processor_base.rb +3 -3
  28. data/lib/urbanopt/scenario/scenario_post_processor_default.rb +120 -11
  29. data/lib/urbanopt/scenario/scenario_post_processor_opendss.rb +13 -14
  30. data/lib/urbanopt/scenario/scenario_runner_base.rb +8 -7
  31. data/lib/urbanopt/scenario/scenario_runner_osw.rb +29 -13
  32. data/lib/urbanopt/scenario/scenario_visualization.rb +235 -0
  33. data/lib/urbanopt/scenario/simulation_dir_base.rb +4 -4
  34. data/lib/urbanopt/scenario/simulation_dir_osw.rb +6 -9
  35. data/lib/urbanopt/scenario/simulation_mapper_base.rb +4 -4
  36. data/lib/urbanopt/scenario/version.rb +2 -2
  37. data/package-lock.json +3 -0
  38. data/urbanopt-scenario-gem.gemspec +10 -6
  39. metadata +78 -52
  40. data/doc_templates/LICENSE.md +0 -27
  41. data/doc_templates/README.md.erb +0 -42
  42. data/doc_templates/copyright_erb.txt +0 -31
  43. data/doc_templates/copyright_js.txt +0 -4
  44. data/doc_templates/copyright_ruby.txt +0 -29
  45. data/docs/.vuepress/components/ScenarioSchema.vue +0 -12
  46. data/docs/schemas/scenario-schema.md +0 -3
  47. data/lib/measures/.rubocop.yml +0 -5
  48. data/lib/measures/default_feature_reports/LICENSE.md +0 -27
  49. data/lib/measures/default_feature_reports/README.md +0 -26
  50. data/lib/measures/default_feature_reports/README.md.erb +0 -42
  51. data/lib/measures/default_feature_reports/measure.rb +0 -1013
  52. data/lib/measures/default_feature_reports/measure.xml +0 -160
  53. data/lib/urbanopt/scenario/default_reports/construction_cost.rb +0 -169
  54. data/lib/urbanopt/scenario/default_reports/date.rb +0 -97
  55. data/lib/urbanopt/scenario/default_reports/distributed_generation.rb +0 -379
  56. data/lib/urbanopt/scenario/default_reports/end_use.rb +0 -159
  57. data/lib/urbanopt/scenario/default_reports/end_uses.rb +0 -140
  58. data/lib/urbanopt/scenario/default_reports/feature_report.rb +0 -267
  59. data/lib/urbanopt/scenario/default_reports/generator.rb +0 -92
  60. data/lib/urbanopt/scenario/default_reports/location.rb +0 -99
  61. data/lib/urbanopt/scenario/default_reports/logger.rb +0 -44
  62. data/lib/urbanopt/scenario/default_reports/power_distribution.rb +0 -102
  63. data/lib/urbanopt/scenario/default_reports/program.rb +0 -265
  64. data/lib/urbanopt/scenario/default_reports/reporting_period.rb +0 -304
  65. data/lib/urbanopt/scenario/default_reports/scenario_report.rb +0 -317
  66. data/lib/urbanopt/scenario/default_reports/schema/README.md +0 -33
  67. data/lib/urbanopt/scenario/default_reports/schema/scenario_csv_columns.txt +0 -34
  68. data/lib/urbanopt/scenario/default_reports/schema/scenario_schema.json +0 -857
  69. data/lib/urbanopt/scenario/default_reports/solar_pv.rb +0 -93
  70. data/lib/urbanopt/scenario/default_reports/storage.rb +0 -105
  71. data/lib/urbanopt/scenario/default_reports/timeseries_csv.rb +0 -299
  72. data/lib/urbanopt/scenario/default_reports/validator.rb +0 -97
  73. data/lib/urbanopt/scenario/default_reports/wind.rb +0 -92
@@ -1,304 +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 'json'
32
- require 'urbanopt/scenario/default_reports/end_uses'
33
- require 'urbanopt/scenario/default_reports/end_use'
34
- require 'urbanopt/scenario/default_reports/date'
35
- require 'urbanopt/scenario/default_reports/validator'
36
- require 'json-schema'
37
-
38
- module URBANopt
39
- module Scenario
40
- module DefaultReports
41
- ##
42
- # ReportingPeriod includes all the results of a specific reporting period.
43
- ##
44
- class ReportingPeriod
45
- attr_accessor :id, :name, :multiplier, :start_date, :end_date, :month, :day_of_month, :year, :total_site_energy, :total_source_energy,
46
- :net_site_energy, :net_source_energy, :total_utility_cost, :net_utility_cost, :utility_costs, :electricity, :natural_gas, :additional_fuel, :district_cooling,
47
- :district_heating, :water, :electricity_produced, :end_uses, :energy_production, :photovoltaic,
48
- :fuel_type, :total_cost, :usage_cost, :demand_cost, :comfort_result, :time_setpoint_not_met_during_occupied_cooling,
49
- :time_setpoint_not_met_during_occupied_heating, :time_setpoint_not_met_during_occupied_hours, :hours_out_of_comfort_bounds_PMV, :hours_out_of_comfort_bounds_PPD #:nodoc:
50
- # ReportingPeriod class initializes the reporting period attributes:
51
- # +:id+ , +:name+ , +:multiplier+ , +:start_date+ , +:end_date+ , +:month+ , +:day_of_month+ , +:year+ , +:total_site_energy+ , +:total_source_energy+ ,
52
- # +:net_site_energy+ , +:net_source_energy+ , +:total_utility_cost , +:net_utility_cost+ , +:utility_costs+ , +:electricity+ , +:natural_gas+ , +:additional_fuel+ , +:district_cooling+ ,
53
- # +:district_heating+ , +:water+ , +:electricity_produced+ , +:end_uses+ , +:energy_production+ , +:photovoltaic+ ,
54
- # +:fuel_type+ , +:total_cost+ , +:usage_cost+ , +:demand_cost+ , +:comfort_result+ , +:time_setpoint_not_met_during_occupied_cooling+ ,
55
- # +:time_setpoint_not_met_during_occupied_heating+ , +:time_setpoint_not_met_during_occupied_hours+
56
- ##
57
- # [parameters:]
58
- # +hash+ - _Hash_ - A hash which may contain a deserialized reporting_period.
59
- ##
60
- def initialize(hash = {})
61
- hash.delete_if { |k, v| v.nil? }
62
- hash = defaults.merge(hash)
63
-
64
- @id = hash[:id]
65
- @name = hash[:name]
66
- @multiplier = hash[:multiplier]
67
- @start_date = Date.new(hash[:start_date])
68
- @end_date = Date.new(hash[:end_date])
69
-
70
- @total_site_energy = hash[:total_site_energy]
71
- @total_source_energy = hash[:total_source_energy]
72
- @net_site_energy = hash [:net_site_energy]
73
- @net_source_energy = hash [:net_source_energy]
74
- @net_utility_cost = hash [:net_utility_cost]
75
- @total_utility_cost = hash [:total_utility_cost]
76
- @electricity = hash [:electricity]
77
- @natural_gas = hash [:natural_gas]
78
- @additional_fuel = hash [:additional_fuel]
79
- @district_cooling = hash [:district_cooling]
80
- @district_heating = hash[:district_heating]
81
- @water = hash[:water]
82
- @electricity_produced = hash[:electricity_produced]
83
- @end_uses = EndUses.new(hash[:end_uses])
84
-
85
- @energy_production = hash[:energy_production]
86
-
87
- @utility_costs = hash[:utility_costs]
88
-
89
- @comfort_result = hash[:comfort_result]
90
-
91
- # initialize class variables @@validator and @@schema
92
- @@validator ||= Validator.new
93
- @@schema ||= @@validator.schema
94
- end
95
-
96
- ##
97
- # Assigns default values if values do not exist.
98
- ##
99
- def defaults
100
- hash = {}
101
-
102
- hash[:id] = nil
103
- hash[:name] = nil
104
- hash[:multiplier] = nil
105
- hash[:start_date] = Date.new.to_hash
106
- hash[:end_date] = Date.new.to_hash
107
-
108
- hash[:total_site_energy] = nil
109
- hash[:total_source_energy] = nil
110
- hash [:net_site_energy] = nil
111
- hash [:net_source_energy] = nil
112
- hash [:net_utility_cost] = nil
113
- hash [:total_utility_cost] = nil
114
- hash [:electricity] = nil
115
- hash [:natural_gas] = nil
116
- hash [:additional_fuel] = nil
117
- hash [:district_cooling] = nil
118
- hash[:district_heating] = nil
119
-
120
- hash[:electricity_produced] = nil
121
- hash[:end_uses] = EndUses.new.to_hash
122
- hash[:energy_production] = { electricity_produced: { photovoltaic: nil } }
123
- hash[:utility_costs] = [{ fuel_type: nil, total_cost: nil, usage_cost: nil, demand_cost: nil }]
124
- hash[:comfort_result] = { time_setpoint_not_met_during_occupied_cooling: nil, time_setpoint_not_met_during_occupied_heating: nil,
125
- time_setpoint_not_met_during_occupied_hours: nil, hours_out_of_comfort_bounds_PMV: nil, hours_out_of_comfort_bounds_PPD: nil }
126
-
127
- return hash
128
- end
129
-
130
- ##
131
- # Converts to a Hash equivalent for JSON serialization.
132
- ##
133
- # - Exclude attributes with nil values.
134
- # - Validate reporting_period hash properties against schema.
135
- #
136
- def to_hash
137
- result = {}
138
-
139
- result[:id] = @id if @id
140
- result[:name] = @name if @name
141
- result[:multiplier] = @multiplier if @multiplier
142
- result[:start_date] = @start_date.to_hash if @start_date
143
- result[:end_date] = @end_date.to_hash if @end_date
144
- result[:total_site_energy] = @total_site_energy if @total_site_energy
145
- result[:total_source_energy] = @total_source_energy if @total_source_energy
146
- result[:net_site_energy] = @net_site_energy if @net_site_energy
147
- result[:net_source_energy] = @net_source_energy if @net_source_energy
148
- result[:net_utility_cost] = @net_utility_cost if @net_utility_cost
149
- result[:total_utility_cost] = @total_utility_cost if @total_utility_cost
150
- result[:electricity] = @electricity if @electricity
151
- result[:natural_gas] = @natural_gas if @natural_gas
152
- result[:additional_fuel] = @additional_fuel if @additional_fuel
153
- result[:district_cooling] = @district_cooling if @district_cooling
154
- result[:district_heating] = @district_heating if @district_heating
155
- result[:water] = @water if @water
156
- result[:electricity_produced] = @electricity_produced if @electricity_produced
157
- result[:end_uses] = @end_uses.to_hash if @end_uses
158
-
159
- energy_production_hash = @energy_production if @energy_production
160
- energy_production_hash.delete_if { |k, v| v.nil? }
161
- energy_production_hash.each do |eph|
162
- eph.delete_if { |k, v| v.nil? }
163
- end
164
-
165
- result[:energy_production] = energy_production_hash if @energy_production
166
-
167
- if @utility_costs.any?
168
- result[:utility_costs] = @utility_costs
169
- @utility_costs.each do |uc|
170
- uc.delete_if { |k, v| v.nil? } if uc
171
- end
172
- end
173
-
174
- comfort_result_hash = @comfort_result if @comfort_result
175
- comfort_result_hash.delete_if { |k, v| v.nil? }
176
- result[:comfort_result] = comfort_result_hash if @comfort_result
177
-
178
- # validates +reporting_period+ properties against schema for reporting period.
179
- if @@validator.validate(@@schema[:definitions][:ReportingPeriod][:properties], result).any?
180
- raise "feature_report properties does not match schema: #{@@validator.validate(@@schema[:definitions][:ReportingPeriod][:properties], result)}"
181
- end
182
-
183
- return result
184
- end
185
-
186
- ##
187
- # Adds up +existing_value+ and +new_values+ if not nill.
188
- ##
189
- # [parameter:]
190
- # +existing_value+ - _Float_ - A value corresponding to a ReportingPeriod attribute.
191
- ##
192
- # +new_value+ - _Float_ - A value corresponding to a ReportingPeriod attribute.
193
- ##
194
- def self.add_values(existing_value, new_value)
195
- if existing_value && new_value
196
- existing_value += new_value
197
- elsif new_value
198
- existing_value = new_value
199
- end
200
- return existing_value
201
- end
202
-
203
- ##
204
- # Merges an +existing_period+ with a +new_period+ if not nil.
205
- ##
206
- # [Parameters:]
207
- # +existing_period+ - _ReportingPeriod_ - An object of ReportingPeriod class.
208
- ##
209
- # +new_period+ - _ReportingPeriod_ - An object of ReportingPeriod class.
210
- ##
211
- def self.merge_reporting_period(existing_period, new_period)
212
- # modify the existing_period by summing up the results
213
- existing_period.total_site_energy = add_values(existing_period.total_site_energy, new_period.total_site_energy)
214
- existing_period.total_source_energy = add_values(existing_period.total_source_energy, new_period.total_source_energy)
215
- existing_period.net_source_energy = add_values(existing_period.net_source_energy, new_period.net_source_energy)
216
- existing_period.net_utility_cost = add_values(existing_period.net_utility_cost, new_period.net_utility_cost)
217
- existing_period.total_utility_cost = add_values(existing_period.total_utility_cost, new_period.total_utility_cost)
218
- existing_period.electricity = add_values(existing_period.electricity, new_period.electricity)
219
- existing_period.natural_gas = add_values(existing_period.natural_gas, new_period.natural_gas)
220
- existing_period.additional_fuel = add_values(existing_period.additional_fuel, new_period.additional_fuel)
221
- existing_period.district_cooling = add_values(existing_period.district_cooling, new_period.district_cooling)
222
- existing_period.district_heating = add_values(existing_period.district_heating, new_period.district_heating)
223
- existing_period.water = add_values(existing_period.water, new_period.water)
224
- existing_period.electricity_produced = add_values(existing_period.electricity_produced, new_period.electricity_produced)
225
-
226
- # merge end uses
227
- new_end_uses = new_period.end_uses
228
- existing_period.end_uses.merge_end_uses!(new_end_uses) if existing_period.end_uses
229
-
230
- if existing_period.energy_production
231
- if existing_period.energy_production[:electricity_produced]
232
- existing_period.energy_production[:electricity_produced][:photovoltaic] = add_values(existing_period.energy_production[:electricity_produced][:photovoltaic], new_period.energy_production[:electricity_produced][:photovoltaic])
233
- end
234
- end
235
-
236
- if existing_period.utility_costs
237
- # RK: this need to be updated
238
- existing_period.utility_costs.each_with_index do |item, i|
239
- existing_period.utility_costs[i][:fuel_type] = existing_period.utility_costs[i][:fuel_type]
240
- existing_period.utility_costs[i][:total_cost] = add_values(existing_period.utility_costs[i][:total_cost], new_period.utility_costs[i][:total_cost])
241
- existing_period.utility_costs[i][:usage_cost] = add_values(existing_period.utility_costs[i][:usage_cost], new_period.utility_costs[i][:usage_cost])
242
- existing_period.utility_costs[i][:demand_cost] = add_values(existing_period.utility_costs[i][:demand_cost], new_period.utility_costs[i][:demand_cost])
243
- end
244
-
245
- end
246
-
247
- if existing_period.comfort_result
248
- existing_period.comfort_result[:time_setpoint_not_met_during_occupied_cooling] = add_values(existing_period.comfort_result[:time_setpoint_not_met_during_occupied_cooling], new_period.comfort_result[:time_setpoint_not_met_during_occupied_cooling])
249
- existing_period.comfort_result[:time_setpoint_not_met_during_occupied_heating] = add_values(existing_period.comfort_result[:time_setpoint_not_met_during_occupied_heating], new_period.comfort_result[:time_setpoint_not_met_during_occupied_heating])
250
- existing_period.comfort_result[:time_setpoint_not_met_during_occupied_hours] = add_values(existing_period.comfort_result[:time_setpoint_not_met_during_occupied_hours], new_period.comfort_result[:time_setpoint_not_met_during_occupied_hours])
251
- existing_period.comfort_result[:hours_out_of_comfort_bounds_PMV] = add_values(existing_period.comfort_result[:hours_out_of_comfort_bounds_PMV], new_period.comfort_result[:hours_out_of_comfort_bounds_PMV])
252
- existing_period.comfort_result[:hours_out_of_comfort_bounds_PPD] = add_values(existing_period.comfort_result[:hours_out_of_comfort_bounds_PPD], new_period.comfort_result[:hours_out_of_comfort_bounds_PPD])
253
- end
254
-
255
- return existing_period
256
- end
257
- # rubocop: enable Metrics/AbcSize # :nodoc:
258
-
259
- ##
260
- # Merges multiple reporting periods together.
261
- # - If +existing_periods+ and +new_periods+ ids are equal,
262
- # modify the existing_periods by merging the new periods results
263
- # - If existing periods are empty, initialize with new_periods.
264
- # - Raise an error if the existing periods are not identical with new periods (cannot have different reporting period ids).
265
- ##
266
- # [parameters:]
267
- ##
268
- # +existing_periods+ - _Array_ - An array of ReportingPeriod objects.
269
- ##
270
- # +new_periods+ - _Array_ - An array of ReportingPeriod objects.
271
- ##
272
- def self.merge_reporting_periods(existing_periods, new_periods)
273
- id_list_existing = []
274
- id_list_new = []
275
- id_list_existing = existing_periods.collect(&:id)
276
- id_list_new = new_periods.collect(&:id)
277
-
278
- if id_list_existing == id_list_new
279
-
280
- existing_periods.each_index do |index|
281
- # if +existing_periods+ and +new_periods+ ids are equal,
282
- # modify the existing_periods by merging the new periods results
283
- existing_periods[index] = merge_reporting_period(existing_periods[index], new_periods[index])
284
- end
285
-
286
- elsif existing_periods.empty?
287
-
288
- # if existing periods are empty, initialize with new_periods
289
- # the = operator would link existing_periods and new_periods to the same object in memory
290
- # we want to initialize with a deep clone of new_periods
291
- existing_periods = Marshal.load(Marshal.dump(new_periods))
292
-
293
- else
294
- # raise an error if the existing periods are not identical with new periods (cannot have different reporting period ids)
295
- raise 'cannot merge different reporting periods'
296
-
297
- end
298
-
299
- return existing_periods
300
- end
301
- end
302
- end
303
- end
304
- end
@@ -1,317 +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/construction_cost'
32
- require 'urbanopt/scenario/default_reports/feature_report'
33
- require 'urbanopt/scenario/default_reports/logger'
34
- require 'urbanopt/scenario/default_reports/program'
35
- require 'urbanopt/scenario/default_reports/reporting_period'
36
- require 'urbanopt/scenario/default_reports/timeseries_csv'
37
- require 'urbanopt/scenario/default_reports/distributed_generation'
38
- require 'urbanopt/scenario/default_reports/validator'
39
- require 'json-schema'
40
-
41
- require 'json'
42
- require 'pathname'
43
-
44
- module URBANopt
45
- module Scenario
46
- module DefaultReports
47
- ##
48
- # ScenarioReport can generate two types of reports from a scenario.
49
- # The first is a JSON format saved to 'default_scenario_report.json'.
50
- # The second is a CSV format saved to 'default_scenario_report.csv'.
51
- ##
52
- class ScenarioReport
53
- attr_accessor :id, :name, :directory_name, :timesteps_per_hour, :number_of_not_started_simulations,
54
- :number_of_started_simulations, :number_of_complete_simulations, :number_of_failed_simulations,
55
- :timeseries_csv, :location, :program, :construction_costs, :reporting_periods, :feature_reports, :distributed_generation # :nodoc:
56
- # ScenarioReport class intializes the scenario report attributes:
57
- # +:id+ , +:name+ , +:directory_name+, +:timesteps_per_hour+ , +:number_of_not_started_simulations+ ,
58
- # +:number_of_started_simulations+ , +:number_of_complete_simulations+ , +:number_of_failed_simulations+ ,
59
- # +:timeseries_csv+ , +:location+ , +:program+ , +:construction_costs+ , +:reporting_periods+ , +:feature_reports+
60
- ##
61
- # Each ScenarioReport object corresponds to a single Scenario.
62
- ##
63
- # [parameters:]
64
- # +hash+ - _Hash_ - A hash of a previously serialized ScenarioReport.
65
- ##
66
- def initialize(hash = {})
67
- hash.delete_if { |k, v| v.nil? }
68
- hash = defaults.merge(hash)
69
-
70
- @id = hash[:id]
71
- @name = hash[:name]
72
- @directory_name = hash[:directory_name]
73
- @timesteps_per_hour = hash[:timesteps_per_hour]
74
- @number_of_not_started_simulations = hash[:number_of_not_started_simulations]
75
- @number_of_started_simulations = hash[:number_of_started_simulations]
76
- @number_of_complete_simulations = hash[:number_of_complete_simulations]
77
- @number_of_failed_simulations = hash[:number_of_failed_simulations]
78
- @timeseries_csv = TimeseriesCSV.new(hash[:timeseries_csv])
79
- @location = Location.new(hash[:location])
80
- @program = Program.new(hash[:program])
81
- @distributed_generation = DistributedGeneration.new(hash[:distributed_generation] || {})
82
-
83
- @construction_costs = []
84
- hash[:construction_costs].each do |cc|
85
- @constructiion_costs << ConstructionCost.new(cc)
86
- end
87
-
88
- @reporting_periods = []
89
- hash[:reporting_periods].each do |rp|
90
- @reporting_periods << ReportingPeriod.new(rp)
91
- end
92
-
93
- # feature_report is intialized here to be used in the add_feature_report method
94
- @feature_reports = []
95
- hash[:feature_reports].each do |fr|
96
- @feature_reports << FeatureReport.new(fr)
97
- end
98
-
99
- @file_name = 'default_scenario_report'
100
-
101
- # initialize class variables @@validator and @@schema
102
- @@validator ||= Validator.new
103
- @@schema ||= @@validator.schema
104
-
105
- # initialize @@logger
106
- @@logger ||= URBANopt::Scenario::DefaultReports.logger
107
- end
108
-
109
- ##
110
- # Assigns default values if values do not exist.
111
- ##
112
- def defaults
113
- hash = {}
114
- hash[:id] = nil.to_s
115
- hash[:name] = nil.to_s
116
- hash[:directory_name] = nil.to_s
117
- hash[:timesteps_per_hour] = nil # unknown
118
- hash[:number_of_not_started_simulations] = 0
119
- hash[:number_of_started_simulations] = 0
120
- hash[:number_of_complete_simulations] = 0
121
- hash[:number_of_failed_simulations] = 0
122
- hash[:timeseries_csv] = TimeseriesCSV.new.to_hash
123
- hash[:location] = Location.new.defaults
124
- hash[:program] = Program.new.to_hash
125
- hash[:construction_costs] = []
126
- hash[:reporting_periods] = []
127
- hash[:feature_reports] = []
128
- return hash
129
- end
130
-
131
- ##
132
- # Gets the saved JSON file path.
133
- ##
134
- def json_path
135
- File.join(@directory_name, @file_name + '.json')
136
- end
137
-
138
- ##
139
- # Gets the saved CSV file path.
140
- ##
141
- def csv_path
142
- File.join(@directory_name, @file_name + '.csv')
143
- end
144
-
145
- ##
146
- # Saves the 'default_scenario_report.json' and 'default_scenario_report.csv' files
147
- ##
148
- # [parameters]:
149
- # +file_name+ - _String_ - Assign a name to the saved scenario results file without an extension
150
- def save(file_name = 'default_scenario_report')
151
- # reassign the initialize local variable @file_name to the file name input.
152
- @file_name = file_name
153
-
154
- # save the scenario reports csv and json data
155
- old_timeseries_path = nil
156
- if !@timeseries_csv.path.nil?
157
- old_timeseries_path = @timeseries_csv.path
158
- end
159
-
160
- @timeseries_csv.path = File.join(@directory_name, file_name + '.csv')
161
- @timeseries_csv.save_data
162
-
163
- hash = {}
164
- hash[:scenario_report] = to_hash
165
- hash[:feature_reports] = []
166
- @feature_reports.each do |feature_report|
167
- hash[:feature_reports] << feature_report.to_hash
168
- end
169
-
170
- json_name_path = File.join(@directory_name, file_name + '.json')
171
-
172
- File.open(json_name_path, 'w') do |f|
173
- f.puts JSON.pretty_generate(hash)
174
- # make sure data is written to the disk one way or the other
175
- begin
176
- f.fsync
177
- rescue StandardError
178
- f.flush
179
- end
180
- end
181
-
182
- if !old_timeseries_path.nil?
183
- @timeseries_csv.path = old_timeseries_path
184
- else
185
- @timeseries_csv.path = File.join(@directory_name, file_name + '.csv')
186
- end
187
-
188
- # save the feature reports csv and json data
189
- # @feature_reports.each do |feature_report|
190
- # feature_report.save_feature_report()
191
- # end
192
-
193
- return true
194
- end
195
-
196
- ##
197
- # Converts to a Hash equivalent for JSON serialization.
198
- ##
199
- # - Exclude attributes with nil values.
200
- # - Validate reporting_period hash properties against schema.
201
- ##
202
- def to_hash
203
- result = {}
204
- result[:id] = @id if @id
205
- result[:name] = @name if @name
206
- result[:directory_name] = @directory_name if @directory_name
207
- result[:timesteps_per_hour] = @timesteps_per_hour if @timesteps_per_hour
208
- result[:number_of_not_started_simulations] = @number_of_not_started_simulations if @number_of_not_started_simulations
209
- result[:number_of_started_simulations] = @number_of_started_simulations if @number_of_started_simulations
210
- result[:number_of_complete_simulations] = @number_of_complete_simulations if @number_of_complete_simulations
211
- result[:number_of_failed_simulations] = @number_of_failed_simulations if @number_of_failed_simulations
212
- result[:timeseries_csv] = @timeseries_csv.to_hash if @timeseries_csv
213
- result[:location] = @location.to_hash if @location
214
- result[:program] = @program.to_hash if @program
215
- result[:distributed_generation] = @distributed_generation.to_hash if @distributed_generation
216
-
217
- result[:construction_costs] = []
218
- @construction_costs.each { |cc| result[:construction_costs] << cc.to_hash } if @construction_costs
219
-
220
- result[:reporting_periods] = []
221
- @reporting_periods.each { |rp| result[:reporting_periods] << rp.to_hash } if @reporting_periods
222
-
223
- # result[:feature_reports] = []
224
- # @feature_reports.each { |fr| result[:feature_reports] << fr.to_hash } if @feature_reports
225
-
226
- # validate scenario_report properties against schema
227
- if @@validator.validate(@@schema[:definitions][:ScenarioReport][:properties], result).any?
228
- raise "scenario_report properties does not match schema: #{@@validator.validate(@@schema[:definitions][:ScenarioReport][:properties], result)}"
229
- end
230
-
231
- # have to use the module method because we have not yet initialized the class one
232
- @@logger.info("Scenario name: #{@name}")
233
-
234
- return result
235
- end
236
-
237
- ##
238
- # Add feature reports to each other.
239
- ##
240
- # - check if a feature report have been already added.
241
- # - check feature simulation status
242
- # - merge timeseries_csv information
243
- # - merge program information
244
- # - merge construction_cost information
245
- # - merge reporting_periods information
246
- # - add the array of feature_reports
247
- # - scenario report location takes the location of the first feature in the list
248
- ##
249
- # [parmeters:]
250
- # +feature_report+ - _FeatureReport_ - An object of FeatureReport class.
251
- ##
252
- def add_feature_report(feature_report)
253
- # check if the timesteps_per_hour are identical
254
- if @timesteps_per_hour.nil? || @timesteps_per_hour == ''
255
- @timesteps_per_hour = feature_report.timesteps_per_hour
256
- else
257
- if feature_report.timesteps_per_hour.is_a?(Integer) && feature_report.timesteps_per_hour != @timesteps_per_hour
258
- raise "FeatureReport timesteps_per_hour = '#{feature_report.timesteps_per_hour}' does not match scenario timesteps_per_hour '#{@timesteps_per_hour}'"
259
- end
260
- end
261
-
262
- # check if first report_report_datetime are identical.
263
- if @timeseries_csv.first_report_datetime.nil? || @timeseries_csv.first_report_datetime == ''
264
- @timeseries_csv.first_report_datetime = feature_report.timeseries_csv.first_report_datetime
265
- else
266
- if feature_report.timeseries_csv.first_report_datetime != @timeseries_csv.first_report_datetime
267
- raise "first_report_datetime '#{@first_report_datetime}' does not match other.first_report_datetime '#{feature_report.timeseries_csv.first_report_datetime}'"
268
- end
269
- end
270
-
271
- # check that we have not already added this feature
272
- id = feature_report.id
273
- @feature_reports.each do |existing_feature_report|
274
- if existing_feature_report.id == id
275
- raise "FeatureReport with id = '#{id}' has already been added"
276
- end
277
- end
278
-
279
- # check feature simulation status
280
- if feature_report.simulation_status == 'Not Started'
281
- @number_of_not_started_simulations += 1
282
- elsif feature_report.simulation_status == 'Started'
283
- @number_of_started_simulations += 1
284
- elsif feature_report.simulation_status == 'Complete'
285
- @number_of_complete_simulations += 1
286
- elsif feature_report.simulation_status == 'Failed'
287
- @number_of_failed_simulations += 1
288
- else
289
- raise "Unknown feature_report simulation_status = '#{feature_report.simulation_status}'"
290
- end
291
-
292
- # merge timeseries_csv information
293
- @timeseries_csv.add_timeseries_csv(feature_report.timeseries_csv)
294
-
295
- @timeseries_csv.run_dir_name(@directory_name)
296
-
297
- # merge program information
298
- @program.add_program(feature_report.program)
299
-
300
- # merge construction_cost information
301
- @construction_costs = ConstructionCost.merge_construction_costs(@construction_costs, feature_report.construction_costs)
302
-
303
- # merge reporting_periods information
304
- @reporting_periods = ReportingPeriod.merge_reporting_periods(@reporting_periods, feature_report.reporting_periods)
305
-
306
- @distributed_generation = DistributedGeneration.merge_distributed_generation(@distributed_generation, feature_report.distributed_generation)
307
-
308
- # add feature_report
309
- @feature_reports << feature_report
310
-
311
- # scenario report location takes the location of the first feature in the list
312
- @location = feature_reports[0].location
313
- end
314
- end
315
- end
316
- end
317
- end