urbanopt-scenario 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +25 -0
  3. data/.rdoc_options +36 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +10 -0
  6. data/.travis.yml +23 -0
  7. data/CHANGELOG.md +5 -0
  8. data/Gemfile +42 -0
  9. data/Jenkinsfile +10 -0
  10. data/LICENSE.md +27 -0
  11. data/RDOC_MAIN.md +39 -0
  12. data/README.md +39 -0
  13. data/Rakefile +51 -0
  14. data/deploy_docs.sh +5 -0
  15. data/doc_templates/LICENSE.md +27 -0
  16. data/doc_templates/README.md.erb +42 -0
  17. data/doc_templates/copyright_erb.txt +31 -0
  18. data/doc_templates/copyright_js.txt +4 -0
  19. data/doc_templates/copyright_ruby.txt +29 -0
  20. data/docs/.gitignore +3 -0
  21. data/docs/.vuepress/components/InnerJsonSchema.vue +84 -0
  22. data/docs/.vuepress/components/JsonSchema.vue +12 -0
  23. data/docs/.vuepress/components/ScenarioSchema.vue +12 -0
  24. data/docs/.vuepress/components/StaticLink.vue +8 -0
  25. data/docs/.vuepress/config.js +15 -0
  26. data/docs/.vuepress/highlight.js +8 -0
  27. data/docs/.vuepress/public/custom_rdoc_styles.css +74 -0
  28. data/docs/.vuepress/utils.js +17 -0
  29. data/docs/README.md +39 -0
  30. data/docs/package-lock.json +11791 -0
  31. data/docs/package.json +22 -0
  32. data/docs/schemas/scenario-schema.md +3 -0
  33. data/lib/measures/.rubocop.yml +5 -0
  34. data/lib/measures/default_feature_reports/LICENSE.md +27 -0
  35. data/lib/measures/default_feature_reports/README.md +56 -0
  36. data/lib/measures/default_feature_reports/README.md.erb +42 -0
  37. data/lib/measures/default_feature_reports/measure.rb +731 -0
  38. data/lib/measures/default_feature_reports/measure.xml +139 -0
  39. data/lib/measures/default_feature_reports/tests/USA_CO_Golden-NREL.724666_TMY3.epw +8768 -0
  40. data/lib/measures/default_feature_reports/tests/default_feature_reports_test.rb +238 -0
  41. data/lib/measures/default_feature_reports/tests/example_model.osm +4378 -0
  42. data/lib/urbanopt-scenario.rb +31 -0
  43. data/lib/urbanopt/scenario.rb +45 -0
  44. data/lib/urbanopt/scenario/default_reports.rb +40 -0
  45. data/lib/urbanopt/scenario/default_reports/construction_cost.rb +169 -0
  46. data/lib/urbanopt/scenario/default_reports/date.rb +97 -0
  47. data/lib/urbanopt/scenario/default_reports/end_use.rb +159 -0
  48. data/lib/urbanopt/scenario/default_reports/end_uses.rb +140 -0
  49. data/lib/urbanopt/scenario/default_reports/feature_report.rb +207 -0
  50. data/lib/urbanopt/scenario/default_reports/location.rb +99 -0
  51. data/lib/urbanopt/scenario/default_reports/logger.rb +44 -0
  52. data/lib/urbanopt/scenario/default_reports/program.rb +261 -0
  53. data/lib/urbanopt/scenario/default_reports/reporting_period.rb +298 -0
  54. data/lib/urbanopt/scenario/default_reports/scenario_report.rb +276 -0
  55. data/lib/urbanopt/scenario/default_reports/schema/README.md +33 -0
  56. data/lib/urbanopt/scenario/default_reports/schema/scenario_csv_columns.txt +13 -0
  57. data/lib/urbanopt/scenario/default_reports/schema/scenario_schema.json +742 -0
  58. data/lib/urbanopt/scenario/default_reports/timeseries_csv.rb +231 -0
  59. data/lib/urbanopt/scenario/default_reports/validator.rb +97 -0
  60. data/lib/urbanopt/scenario/extension.rb +63 -0
  61. data/lib/urbanopt/scenario/logger.rb +42 -0
  62. data/lib/urbanopt/scenario/scenario_base.rb +79 -0
  63. data/lib/urbanopt/scenario/scenario_csv.rb +122 -0
  64. data/lib/urbanopt/scenario/scenario_datapoint_base.rb +162 -0
  65. data/lib/urbanopt/scenario/scenario_post_processor_base.rb +69 -0
  66. data/lib/urbanopt/scenario/scenario_post_processor_default.rb +97 -0
  67. data/lib/urbanopt/scenario/scenario_runner_base.rb +63 -0
  68. data/lib/urbanopt/scenario/scenario_runner_osw.rb +158 -0
  69. data/lib/urbanopt/scenario/simulation_dir_base.rb +90 -0
  70. data/lib/urbanopt/scenario/simulation_dir_osw.rb +261 -0
  71. data/lib/urbanopt/scenario/simulation_mapper_base.rb +47 -0
  72. data/lib/urbanopt/scenario/version.rb +35 -0
  73. data/urbanopt-scenario-gem.gemspec +36 -0
  74. metadata +227 -0
@@ -0,0 +1,261 @@
1
+ # *********************************************************************************
2
+ # URBANopt, Copyright (c) 2019, 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/validator'
32
+ require 'json-schema'
33
+ require 'json'
34
+
35
+ module URBANopt
36
+ module Scenario
37
+ module DefaultReports
38
+ ##
39
+ # Program includes all building program related information.
40
+ ##
41
+ class Program
42
+ attr_accessor :site_area, :floor_area, :conditioned_area, :unconditioned_area, :footprint_area, :maximum_roof_height,
43
+ :maximum_number_of_stories, :maximum_number_of_stories_above_ground, :parking_area, :number_of_parking_spaces,
44
+ :number_of_parking_spaces_charging, :parking_footprint_area, :maximum_parking_height, :maximum_number_of_parking_stories,
45
+ :maximum_number_of_parking_stories_above_ground, :number_of_residential_units, :building_types, :building_type, :maximum_occupancy,
46
+ :area, :window_area, :north_window_area, :south_window_area, :east_window_area, :west_window_area, :wall_area, :roof_area, :equipment_roof_area,
47
+ :photovoltaic_roof_area, :available_roof_area, :total_roof_area, :orientation, :aspect_ratio # :nodoc:
48
+ # Program class intialize building program attributes: +:site_area+ , +:floor_area+ , +:conditioned_area+ , +:unconditioned_area+ ,
49
+ # +:footprint_area+ , +:maximum_roof_height, +:maximum_number_of_stories+ , +:maximum_number_of_stories_above_ground+ , +:parking_area+ ,
50
+ # +:number_of_parking_spaces+ , +:number_of_parking_spaces_charging+ , +:parking_footprint_area+ , +:maximum_parking_height+ , +:maximum_number_of_parking_stories+ ,
51
+ # +:maximum_number_of_parking_stories_above_ground+ , +:number_of_residential_units+ , +:building_types+ , +:building_type+ , +:maximum_occupancy+ ,
52
+ # +:area+ , +:window_area+ , +:north_window_area+ , +:south_window_area+ , +:east_window_area+ , +:west_window_area+ , +:wall_area+ , +:roof_area+ ,
53
+ # +:equipment_roof_area+ , +:photovoltaic_roof_area+ , +:available_roof_area+ , +:total_roof_area+ , +:orientation+ , +:aspect_ratio+
54
+ ##
55
+ # [parameters:]
56
+ # +hash+ - _Hash_ - A hash which may contain a deserialized program.
57
+ ##
58
+ def initialize(hash = {})
59
+ hash.delete_if { |k, v| v.nil? }
60
+ hash = defaults.merge(hash)
61
+
62
+ @site_area = hash[:site_area]
63
+ @floor_area = hash[:floor_area]
64
+ @conditioned_area = hash[:conditioned_area]
65
+ @unconditioned_area = hash[:unconditioned_area]
66
+ @footprint_area = hash[:footprint_area]
67
+ @maximum_roof_height = hash[:maximum_roof_height]
68
+ @maximum_number_of_stories = hash[:maximum_number_of_stories]
69
+ @maximum_number_of_stories_above_ground = hash[:maximum_number_of_stories_above_ground]
70
+ @parking_area = hash[:parking_area]
71
+ @number_of_parking_spaces = hash[:number_of_parking_spaces]
72
+ @number_of_parking_spaces_charging = hash[:number_of_parking_spaces_charging]
73
+ @parking_footprint_area = hash[:parking_footprint_area]
74
+ @maximum_parking_height = hash[:maximum_parking_height]
75
+ @maximum_number_of_parking_stories = hash[:maximum_number_of_parking_stories]
76
+ @maximum_number_of_parking_stories_above_ground = hash[:maximum_number_of_parking_stories_above_ground]
77
+ @number_of_residential_units = hash[:number_of_residential_units]
78
+ @building_types = hash[:building_types]
79
+ @window_area = hash[:window_area]
80
+ @wall_area = hash[:wall_area]
81
+ @roof_area = hash[:roof_area]
82
+ @orientation = hash[:orientation]
83
+ @aspect_ratio = hash[:aspect_ratio]
84
+
85
+ # initialize class variables @@validator and @@schema
86
+ @@validator ||= Validator.new
87
+ @@schema ||= @@validator.schema
88
+ end
89
+
90
+ ##
91
+ # Assigns default values if values do not exist.
92
+ ##
93
+ def defaults
94
+ hash = {}
95
+ hash[:site_area] = nil
96
+ hash[:floor_area] = nil
97
+ hash[:conditioned_area] = nil
98
+ hash[:unconditioned_area] = nil
99
+ hash[:footprint_area] = nil
100
+ hash[:maximum_roof_height] = nil
101
+ hash[:maximum_number_of_stories] = nil
102
+ hash[:maximum_number_of_stories_above_ground] = nil
103
+ hash[:parking_area] = nil
104
+ hash[:number_of_parking_spaces] = nil
105
+ hash[:number_of_parking_spaces_charging] = nil
106
+ hash[:parking_footprint_area] = nil
107
+ hash[:maximum_parking_height] = nil
108
+ hash[:maximum_number_of_parking_stories] = nil
109
+ hash[:maximum_number_of_parking_stories_above_ground] = nil
110
+ hash[:number_of_residential_units] = nil
111
+ hash[:building_types] = [{ building_type: nil, maximum_occupancy: nil, floor_area: nil }]
112
+ hash[:window_area] = { north_window_area: nil, south_window_area: nil, east_window_area: nil, west_window_area: nil, total_window_area: nil }
113
+ hash[:wall_area] = { north_wall_area: nil, south_wall_area: nil, east_wall_area: nil, west_wall_area: nil, total_wall_area: nil }
114
+ hash[:roof_area] = { equipment_roof_area: nil, photovoltaic_roof_area: nil, available_roof_area: nil, total_roof_area: nil }
115
+ hash[:orientation] = nil
116
+ hash[:aspect_ratio] = nil
117
+ return hash
118
+ end
119
+
120
+ ##
121
+ # Convert to a Hash equivalent for JSON serialization.
122
+ ##
123
+ # - Exclude attributes with nil values.
124
+ # - Validate program hash properties against schema.
125
+ ##
126
+ def to_hash
127
+ result = {}
128
+ result[:site_area] = @site_area if @site_area
129
+ result[:floor_area] = @floor_area if @floor_area
130
+ result[:conditioned_area] = @conditioned_area if @conditioned_area
131
+ result[:unconditioned_area] = @unconditioned_area if @unconditioned_area
132
+ result[:footprint_area] = @footprint_area if @footprint_area
133
+ result[:maximum_roof_height] = @maximum_roof_height if @maximum_roof_height
134
+ result[:maximum_number_of_stories] = @maximum_number_of_stories if @maximum_number_of_stories
135
+ result[:maximum_number_of_stories_above_ground] = @maximum_number_of_stories_above_ground if @maximum_number_of_parking_stories_above_ground
136
+ result[:parking_area] = @parking_area if @parking_area
137
+ result[:number_of_parking_spaces] = @number_of_parking_spaces if @number_of_parking_spaces
138
+ result[:number_of_parking_spaces_charging] = @number_of_parking_spaces_charging if @number_of_parking_spaces_charging
139
+ result[:parking_footprint_area] = @parking_footprint_area if @parking_footprint_area
140
+ result[:maximum_parking_height] = @maximum_parking_height if @maximum_parking_height
141
+ result[:maximum_number_of_parking_stories] = @maximum_number_of_parking_stories if @maximum_number_of_parking_stories
142
+ result[:maximum_number_of_parking_stories_above_ground] = @maximum_number_of_parking_stories_above_ground if @maximum_number_of_parking_stories_above_ground
143
+ result[:number_of_residential_units] = @number_of_residential_units if @number_of_residential_units
144
+
145
+ if @building_types.any?
146
+ result[:building_types] = @building_types
147
+ @building_types.each do |bt|
148
+ bt.delete_if { |k, v| v.nil? } if bt
149
+ end
150
+ end
151
+
152
+ # result[:window_area] = @window_area if @window_area
153
+ window_area_hash = @window_area if @window_area
154
+ window_area_hash.delete_if { |k, v| v.nil? }
155
+ result[:window_area] = window_area_hash if @window_area
156
+
157
+ # result[:wall_area] = @wall_area if @wall_area
158
+ wall_area_hash = @wall_area if @wall_area
159
+ wall_area_hash.delete_if { |k, v| v.nil? }
160
+ result[:wall_area] = wall_area_hash if @wall_area
161
+
162
+ # result[:roof_area] = @roof_area if @roof_area
163
+ roof_area_hash = @roof_area if @roof_area
164
+ roof_area_hash.delete_if { |k, v| v.nil? }
165
+ result[:roof_area] = roof_area_hash if @roof_area
166
+
167
+ result[:orientation] = @orientation if @orientation
168
+ result[:aspect_ratio] = @aspect_ratio if @aspect_ratio
169
+
170
+ # validate program properties against schema
171
+ if @@validator.validate(@@schema[:definitions][:Program][:properties], result).any?
172
+ raise "program properties does not match schema: #{@@validator.validate(@@schema[:definitions][:Program][:properties], result)}"
173
+ end
174
+
175
+ return result
176
+ end
177
+
178
+ ##
179
+ # Return the maximum value from +existing_value+ and +new_value+.
180
+ ##
181
+ # [parameters:]
182
+ # +existing_value+ - _Float_ - A value corresponding to a Program attribute.
183
+ ##
184
+ # +new_value+ - _Float_ - A value corresponding to a Program attribute.
185
+ ##
186
+ def max_value(existing_value, new_value)
187
+ if existing_value && new_value
188
+ [existing_value, new_value].max
189
+ elsif new_value
190
+ existing_value = new_value
191
+ end
192
+ return existing_value
193
+ end
194
+
195
+ ##
196
+ # Adds up +existing_value+ and +new_values+ if not nill.
197
+ ##
198
+ # [parameters:]
199
+ # +existing_value+ - _Float_ - A value corresponding to a Program attribute.
200
+ ##
201
+ # +new_value+ - _Float_ - A value corresponding to a Program attribute.
202
+ ##
203
+ def add_values(existing_value, new_value)
204
+ if existing_value && new_value
205
+ existing_value += new_value
206
+ elsif new_value
207
+ existing_value = new_value
208
+ end
209
+ return existing_value
210
+ end
211
+
212
+ ##
213
+ # Merges program objects to each other by summing up values or taking the maximum value of the attributes.
214
+ ##
215
+ # [parameters:]
216
+ # +other+ - _Program_ - An object of Program class.
217
+ ##
218
+ # rubocop:disable Metrics/AbcSize # :nodoc:
219
+ def add_program(other)
220
+ @site_area = add_values(@site_area, other.site_area)
221
+
222
+ @floor_area = add_values(@floor_area, other.floor_area)
223
+ @conditioned_area = add_values(@conditioned_area, other.conditioned_area)
224
+ @unconditioned_area = add_values(@unconditioned_area, other.unconditioned_area)
225
+ @footprint_area = add_values(@footprint_area, other.footprint_area)
226
+ @maximum_roof_height = max_value(@maximum_roof_height, other.maximum_roof_height)
227
+ @maximum_number_of_stories = max_value(@maximum_number_of_stories, other.maximum_number_of_stories)
228
+ @maximum_number_of_stories_above_ground = max_value(@maximum_number_of_stories_above_ground, other.maximum_number_of_stories_above_ground)
229
+ @parking_area = add_values(@parking_area, other.parking_area)
230
+ @number_of_parking_spaces = add_values(@number_of_parking_spaces, other.number_of_parking_spaces)
231
+ @number_of_parking_spaces_charging = add_values(@number_of_parking_spaces_charging, other.number_of_parking_spaces_charging)
232
+ @parking_footprint_area = add_values(@parkig_footprint_area, other.parking_footprint_area)
233
+ @maximum_parking_height = max_value(@maximum_parking_height, other.maximum_parking_height)
234
+ @maximum_number_of_parking_stories = max_value(@maximum_number_of_parking_stories, other.maximum_number_of_parking_stories)
235
+ @maximum_number_of_parking_stories_above_ground = max_value(maximum_number_of_parking_stories_above_ground, other.maximum_number_of_parking_stories_above_ground)
236
+ @number_of_residential_units = add_values(@number_of_residential_units, other.number_of_residential_units)
237
+
238
+ @building_types = other.building_types
239
+
240
+ @window_area[:north_window_area] = add_values(@window_area[:north_window_area], other.window_area[:north_window_area])
241
+ @window_area[:south_window_area] = add_values(@window_area[:south_window_area], other.window_area[:south_window_area])
242
+ @window_area[:east_window_area] = add_values(@window_area[:east_window_area], other.window_area[:east_window_area])
243
+ @window_area[:west_window_area] = add_values(@window_area[:west_window_area], other.window_area[:west_window_area])
244
+ @window_area[:total_window_area] = add_values(@window_area[:total_window_area], other.window_area[:total_window_area])
245
+
246
+ @wall_area[:north_wall_area] = add_values(@wall_area[:north_wall_area], other.wall_area[:north_wall_area])
247
+ @wall_area[:south_wall_area] = add_values(@wall_area[:south_wall_area], other.wall_area[:south_wall_area])
248
+ @wall_area[:east_wall_area] = add_values(@wall_area[:east_wall_area], other.wall_area[:east_wall_area])
249
+ @wall_area[:west_wall_area] = add_values(@wall_area[:west_wall_area], other.wall_area[:west_wall_area])
250
+ @wall_area[:total_wall_area] = add_values(@wall_area[:total_wall_area], other.wall_area[:total_wall_area])
251
+
252
+ @roof_area[:equipment_roof_area] = add_values(@roof_area[:equipment_roof_area], other.roof_area[:equipment_roof_area])
253
+ @roof_area[:photovoltaic_roof_area] = add_values(@roof_area[:photovoltaic_roof_area], other.roof_area[:photovoltaic_roof_area])
254
+ @roof_area[:available_roof_area] = add_values(@roof_area[:available_roof_area], other.roof_area[:available_roof_area])
255
+ @roof_area[:total_roof_area] = add_values(@roof_area[:total_roof_area], other.roof_area[:total_roof_area])
256
+ end
257
+ # rubocop:enable Metrics/AbcSize
258
+ end
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,298 @@
1
+ # *********************************************************************************
2
+ # URBANopt, Copyright (c) 2019, 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 intializes 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