urbanopt-scenario 0.1.1

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