From 2320bdf26ebde31e87f44813a3a9823cbc47ddb8 Mon Sep 17 00:00:00 2001 From: Nathan Moore Date: Mon, 6 May 2024 10:35:42 -0600 Subject: [PATCH] v0.10.0 (#152) * Additional reporting tests (#147) * wip: tests for distributed generation reports * details so 2 more tests pass * continuing to clean up merging dgen test * fix how I instantiate the dgen class * yet moar tests! * remove testing print statements * update formatting and version in export_modelica_loads (#149) Co-authored-by: Nathan Moore * Update feature reports for Reopt v3 (#148) * start of mods for v3 * update to remove `us_dollars` from reopt outputs * change `_pct` to `_fraction` * fix copy paste typo --------- Co-authored-by: kflemin <2205659+kflemin@users.noreply.github.com> * Prep 0.10.0 (#150) * bump to version 0.10.0 * update changelog from github automated output * rubocop * fix test after earlier refactor * enable manually running CI on demand (once merged to develop) --------- Co-authored-by: Nicholas Long <1907354+nllong@users.noreply.github.com> Co-authored-by: kflemin <2205659+kflemin@users.noreply.github.com> --- .github/workflows/nightly_ci_build.yml | 1 + CHANGELOG.md | 9 + .../export_modelica_loads/measure.xml | 2 +- .../default_reports/distributed_generation.rb | 130 +++---- .../default_reports/feature_report.rb | 3 +- .../reporting/default_reports/qaqc_flags.rb | 15 +- .../default_reports/reporting_period.rb | 14 +- .../scenario_power_distribution_cost.rb | 328 +++++++++--------- .../schema/scenario_schema.json | 69 +++- lib/urbanopt/reporting/version.rb | 2 +- spec/urbanopt/reporting/reporting_spec.rb | 43 ++- 11 files changed, 339 insertions(+), 277 deletions(-) diff --git a/.github/workflows/nightly_ci_build.yml b/.github/workflows/nightly_ci_build.yml index 15abfb0..af4ac75 100644 --- a/.github/workflows/nightly_ci_build.yml +++ b/.github/workflows/nightly_ci_build.yml @@ -2,6 +2,7 @@ name: Reporting-gem CI on: # push: + workflow_dispatch: schedule: # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule # 5 am UTC (11pm MDT the day before) every weekday night in MDT diff --git a/CHANGELOG.md b/CHANGELOG.md index 269519f..5ab3478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # URBANopt Reporting Gem +## Version 0.10.0 +## What's Changed +* Additional reporting tests by @vtnate in https://github.com/urbanopt/urbanopt-reporting-gem/pull/147 +* Update formatting and version in export_modelica_loads by @nllong in https://github.com/urbanopt/urbanopt-reporting-gem/pull/149 +* Update feature reports for Reopt v3 by @vtnate in https://github.com/urbanopt/urbanopt-reporting-gem/pull/148 + + +**Full Changelog**: https://github.com/urbanopt/urbanopt-reporting-gem/compare/v0.9.1...0.10.0 + ## Version 0.9.1 * downgrade json-schema to 2.7 by @vtnate in https://github.com/urbanopt/urbanopt-reporting-gem/pull/145 diff --git a/lib/measures/export_modelica_loads/measure.xml b/lib/measures/export_modelica_loads/measure.xml index 27ffee7..11a09a9 100644 --- a/lib/measures/export_modelica_loads/measure.xml +++ b/lib/measures/export_modelica_loads/measure.xml @@ -1,6 +1,6 @@ - 3.1 + 3.0 export_modelica_loads 7051db01-2e55-4223-b5b5-fee615b68dd0 e682f518-4785-4917-802e-600023816cc3 diff --git a/lib/urbanopt/reporting/default_reports/distributed_generation.rb b/lib/urbanopt/reporting/default_reports/distributed_generation.rb index d493140..3b4068a 100644 --- a/lib/urbanopt/reporting/default_reports/distributed_generation.rb +++ b/lib/urbanopt/reporting/default_reports/distributed_generation.rb @@ -20,67 +20,67 @@ class DistributedGeneration ## # _Float_ - Lifecycle costs for the complete distributed generation system in US Dollars # - attr_accessor :lcc_us_dollars + attr_accessor :lcc ## # _Float_ - Lifecycle costs for the complete distributed generation system in US Dollars # - attr_accessor :lcc_bau_us_dollars + attr_accessor :lcc_bau ## # _Float_ - Net present value of the complete distributed generation system in US Dollars # - attr_accessor :npv_us_dollars + attr_accessor :npv ## # _Float_ - Total amount paid for utility energy in US Dollars in the first year of operation # - attr_accessor :year_one_energy_cost_us_dollars + attr_accessor :year_one_energy_cost_before_tax ## # _Float_ - Total amount paid in utility demand charges in US Dollars in the first year of operation # - attr_accessor :year_one_demand_cost_us_dollars + attr_accessor :year_one_demand_cost_before_tax ## # _Float_ - Total amount paid to the utility in US Dollars in the first year of operation # - attr_accessor :year_one_bill_us_dollars + attr_accessor :year_one_bill_before_tax ## # _Float_ - Total energy costs in US Dollars over the life of the system after tax # - attr_accessor :total_energy_cost_us_dollars + attr_accessor :lifecycle_energy_cost_after_tax ## # _Float_ - Total demand costs in US Dollars over the life of the system after tax # - attr_accessor :total_demand_cost_us_dollars + attr_accessor :lifecycle_demand_cost_after_tax ## - # _Float_ - Year one energy cost in the business as usual scenario (i.e no new system) after tax, us dollars + # _Float_ - Year one energy cost in the business as usual scenario (i.e no new system) before tax, us dollars # - attr_accessor :year_one_energy_cost_bau_us_dollars + attr_accessor :year_one_energy_cost_before_tax_bau ## # _Float_ - Year one demand cost in the business as usual scenario (i.e no new system), us dollars # - attr_accessor :year_one_demand_cost_bau_us_dollars + attr_accessor :year_one_demand_cost_before_tax_bau ## # _Float_ - Year one demand energy bill in the business as usual scenario (i.e no new system), us dollars # - attr_accessor :year_one_bill_bau_us_dollars + attr_accessor :year_one_bill_before_tax_bau ## # _Float_ - Total lifetime demand costs in the business as usual scenario (i.e no new system) after tax, us dollars # - attr_accessor :total_demand_cost_bau_us_dollars + attr_accessor :lifecycle_demand_cost_after_tax_bau ## # _Float_ - Total lifetime energy costs in the business as usual scenario (i.e no new system) after tax, us dollars # - attr_accessor :total_energy_cost_bau_us_dollars + attr_accessor :lifecycle_energy_cost_after_tax_bau ## # _Array_ - List of _SolarPV_ systems @@ -162,16 +162,16 @@ class DistributedGeneration attr_accessor :reopt_assumptions_file_path ## - # _Float_ - Annual percentage of electricity supplied by renewable sources + # _Float_ - Annual fraction of electricity supplied by renewable sources # - attr_accessor :annual_renewable_electricity_pct + attr_accessor :renewable_electricity_fraction ## # Initialize distributed generation system design and financial metrics. # # * Technologies include +:solar_pv+, +:wind+, +:generator+, and +:storage+. - # * Financial metrics include +:lcc_us_dollars+, +:npv_us_dollars+, +:year_one_energy_cost_us_dollars+, +:year_one_demand_cost_us_dollars+, - # +:year_one_bill_us_dollars+, and +:total_energy_cost_us_dollars+ + # * Financial metrics include +:lcc+, +:npv+, +:year_one_energy_cost_before_tax+, +:year_one_demand_cost_before_tax+, + # +:year_one_bill_before_tax+, and +:lifecycle_energy_cost_after_tax+ ## # [parameters:] # @@ -180,20 +180,20 @@ class DistributedGeneration def initialize(hash = {}) hash.delete_if { |k, v| v.nil? } - @annual_renewable_electricity_pct = hash[:annual_renewable_electricity_pct] - @lcc_us_dollars = hash[:lcc_us_dollars] - @lcc_bau_us_dollars = hash[:lcc_bau_us_dollars] - @npv_us_dollars = hash[:npv_us_dollars] - @year_one_energy_cost_us_dollars = hash[:year_one_energy_cost_us_dollars] - @year_one_energy_cost_bau_us_dollars = hash[:year_one_energy_cost_bau_us_dollars] - @year_one_demand_cost_us_dollars = hash[:year_one_demand_cost_us_dollars] - @year_one_demand_cost_bau_us_dollars = hash[:year_one_demand_cost_bau_us_dollars] - @year_one_bill_us_dollars = hash[:year_one_bill_us_dollars] - @year_one_bill_bau_us_dollars = hash[:year_one_bill_bau_us_dollars] - @total_energy_cost_us_dollars = hash[:total_energy_cost_us_dollars] - @total_energy_cost_bau_us_dollars = hash[:total_energy_cost_bau_us_dollars] - @total_demand_cost_us_dollars = hash[:total_demand_cost_us_dollars] - @total_demand_cost_bau_us_dollars = hash[:total_demand_cost_bau_us_dollars] + @renewable_electricity_fraction = hash[:renewable_electricity_fraction] + @lcc = hash[:lcc] + @lcc_bau = hash[:lcc_bau] + @npv = hash[:npv] + @year_one_energy_cost_before_tax = hash[:year_one_energy_cost_before_tax] + @year_one_energy_cost_before_tax_bau = hash[:year_one_energy_cost_before_tax_bau] + @year_one_demand_cost_before_tax = hash[:year_one_demand_cost_before_tax] + @year_one_demand_cost_before_tax_bau = hash[:year_one_demand_cost_before_tax_bau] + @year_one_bill_before_tax = hash[:year_one_bill_before_tax] + @year_one_bill_before_tax_bau = hash[:year_one_bill_before_tax_bau] + @lifecycle_energy_cost_after_tax = hash[:lifecycle_energy_cost_after_tax] + @lifecycle_energy_cost_after_tax_bau = hash[:lifecycle_energy_cost_after_tax_bau] + @lifecycle_demand_cost_after_tax = hash[:lifecycle_demand_cost_after_tax] + @lifecycle_demand_cost_after_tax_bau = hash[:lifecycle_demand_cost_after_tax_bau] @resilience_hours_min = hash[:resilience_hours_min] @resilience_hours_max = hash[:resilience_hours_max] @@ -345,22 +345,22 @@ def add_tech(name, tech) def to_hash result = {} result[:reopt_assumptions_file_path] = @reopt_assumptions_file_path if @reopt_assumptions_file_path - result[:annual_renewable_electricity_pct] = @annual_renewable_electricity_pct if @annual_renewable_electricity_pct - result[:lcc_us_dollars] = @lcc_us_dollars if @lcc_us_dollars - result[:lcc_bau_us_dollars] = @lcc_bau_us_dollars if @lcc_bau_us_dollars - result[:npv_us_dollars] = @npv_us_dollars if @npv_us_dollars - - result[:year_one_energy_cost_us_dollars] = @year_one_energy_cost_us_dollars if @year_one_energy_cost_us_dollars - result[:year_one_demand_cost_us_dollars] = @year_one_demand_cost_us_dollars if @year_one_demand_cost_us_dollars - result[:year_one_bill_us_dollars] = @year_one_bill_us_dollars if @year_one_bill_us_dollars - result[:total_demand_cost_us_dollars] = @total_demand_cost_us_dollars if @total_demand_cost_us_dollars - result[:total_energy_cost_us_dollars] = @total_energy_cost_us_dollars if @total_energy_cost_us_dollars - - result[:year_one_energy_cost_bau_us_dollars] = @year_one_energy_cost_bau_us_dollars if @year_one_energy_cost_bau_us_dollars - result[:year_one_demand_cost_bau_us_dollars] = @year_one_demand_cost_bau_us_dollars if @year_one_demand_cost_bau_us_dollars - result[:year_one_bill_bau_us_dollars] = @year_one_bill_bau_us_dollars if @year_one_bill_bau_us_dollars - result[:total_energy_cost_bau_us_dollars] = @total_energy_cost_bau_us_dollars if @total_energy_cost_bau_us_dollars - result[:total_demand_cost_bau_us_dollars] = @total_demand_cost_bau_us_dollars if @total_demand_cost_bau_us_dollars + result[:renewable_electricity_fraction] = @renewable_electricity_fraction if @renewable_electricity_fraction + result[:lcc] = @lcc if @lcc + result[:lcc_bau] = @lcc_bau if @lcc_bau + result[:npv] = @npv if @npv + + result[:year_one_energy_cost_before_tax] = @year_one_energy_cost_before_tax if @year_one_energy_cost_before_tax + result[:year_one_demand_cost_before_tax] = @year_one_demand_cost_before_tax if @year_one_demand_cost_before_tax + result[:year_one_bill_before_tax] = @year_one_bill_before_tax if @year_one_bill_before_tax + result[:lifecycle_demand_cost_after_tax] = @lifecycle_demand_cost_after_tax if @lifecycle_demand_cost_after_tax + result[:lifecycle_energy_cost_after_tax] = @lifecycle_energy_cost_after_tax if @lifecycle_energy_cost_after_tax + + result[:year_one_energy_cost_before_tax_bau] = @year_one_energy_cost_before_tax_bau if @year_one_energy_cost_before_tax_bau + result[:year_one_demand_cost_before_tax_bau] = @year_one_demand_cost_before_tax_bau if @year_one_demand_cost_before_tax_bau + result[:year_one_bill_before_tax_bau] = @year_one_bill_before_tax_bau if @year_one_bill_before_tax_bau + result[:lifecycle_energy_cost_after_tax_bau] = @lifecycle_energy_cost_after_tax_bau if @lifecycle_energy_cost_after_tax_bau + result[:lifecycle_demand_cost_after_tax_bau] = @lifecycle_demand_cost_after_tax_bau if @lifecycle_demand_cost_after_tax_bau result[:total_solar_pv_kw] = @total_solar_pv_kw if @total_solar_pv_kw result[:total_wind_kw] = @total_wind_kw if @total_wind_kw @@ -415,22 +415,22 @@ def self.add_values(existing_value, new_value) #:nodoc: # Merge a distributed generation system with a new system ## def self.merge_distributed_generation(existing_dgen, new_dgen) - existing_dgen.annual_renewable_electricity_pct = add_values(existing_dgen.annual_renewable_electricity_pct, new_dgen.annual_renewable_electricity_pct) - existing_dgen.lcc_us_dollars = add_values(existing_dgen.lcc_us_dollars, new_dgen.lcc_us_dollars) - existing_dgen.lcc_bau_us_dollars = add_values(existing_dgen.lcc_bau_us_dollars, new_dgen.lcc_bau_us_dollars) - existing_dgen.npv_us_dollars = add_values(existing_dgen.npv_us_dollars, new_dgen.npv_us_dollars) - - existing_dgen.year_one_energy_cost_us_dollars = add_values(existing_dgen.year_one_energy_cost_us_dollars, new_dgen.year_one_energy_cost_us_dollars) - existing_dgen.year_one_demand_cost_us_dollars = add_values(existing_dgen.year_one_demand_cost_us_dollars, new_dgen.year_one_demand_cost_us_dollars) - existing_dgen.year_one_bill_us_dollars = add_values(existing_dgen.year_one_bill_us_dollars, new_dgen.year_one_bill_us_dollars) - existing_dgen.total_energy_cost_us_dollars = add_values(existing_dgen.total_energy_cost_us_dollars, new_dgen.total_energy_cost_us_dollars) - existing_dgen.total_demand_cost_us_dollars = add_values(existing_dgen.total_demand_cost_us_dollars, new_dgen.total_demand_cost_us_dollars) - - existing_dgen.year_one_energy_cost_bau_us_dollars = add_values(existing_dgen.year_one_energy_cost_bau_us_dollars, new_dgen.year_one_energy_cost_bau_us_dollars) - existing_dgen.year_one_demand_cost_bau_us_dollars = add_values(existing_dgen.year_one_demand_cost_bau_us_dollars, new_dgen.year_one_demand_cost_bau_us_dollars) - existing_dgen.year_one_bill_bau_us_dollars = add_values(existing_dgen.year_one_bill_bau_us_dollars, new_dgen.year_one_bill_bau_us_dollars) - existing_dgen.total_energy_cost_bau_us_dollars = add_values(existing_dgen.total_energy_cost_bau_us_dollars, new_dgen.total_energy_cost_bau_us_dollars) - existing_dgen.total_demand_cost_bau_us_dollars = add_values(existing_dgen.total_demand_cost_bau_us_dollars, new_dgen.total_demand_cost_bau_us_dollars) + existing_dgen.renewable_electricity_fraction = add_values(existing_dgen.renewable_electricity_fraction, new_dgen.renewable_electricity_fraction) + existing_dgen.lcc = add_values(existing_dgen.lcc, new_dgen.lcc) + existing_dgen.lcc_bau = add_values(existing_dgen.lcc_bau, new_dgen.lcc_bau) + existing_dgen.npv = add_values(existing_dgen.npv, new_dgen.npv) + + existing_dgen.year_one_energy_cost_before_tax = add_values(existing_dgen.year_one_energy_cost_before_tax, new_dgen.year_one_energy_cost_before_tax) + existing_dgen.year_one_demand_cost_before_tax = add_values(existing_dgen.year_one_demand_cost_before_tax, new_dgen.year_one_demand_cost_before_tax) + existing_dgen.year_one_bill_before_tax = add_values(existing_dgen.year_one_bill_before_tax, new_dgen.year_one_bill_before_tax) + existing_dgen.lifecycle_energy_cost_after_tax = add_values(existing_dgen.lifecycle_energy_cost_after_tax, new_dgen.lifecycle_energy_cost_after_tax) + existing_dgen.lifecycle_demand_cost_after_tax = add_values(existing_dgen.lifecycle_demand_cost_after_tax, new_dgen.lifecycle_demand_cost_after_tax) + + existing_dgen.year_one_energy_cost_before_tax_bau = add_values(existing_dgen.year_one_energy_cost_before_tax_bau, new_dgen.year_one_energy_cost_before_tax_bau) + existing_dgen.year_one_demand_cost_before_tax_bau = add_values(existing_dgen.year_one_demand_cost_before_tax_bau, new_dgen.year_one_demand_cost_before_tax_bau) + existing_dgen.year_one_bill_before_tax_bau = add_values(existing_dgen.year_one_bill_before_tax_bau, new_dgen.year_one_bill_before_tax_bau) + existing_dgen.lifecycle_energy_cost_after_tax_bau = add_values(existing_dgen.lifecycle_energy_cost_after_tax_bau, new_dgen.lifecycle_energy_cost_after_tax_bau) + existing_dgen.lifecycle_demand_cost_after_tax_bau = add_values(existing_dgen.lifecycle_demand_cost_after_tax_bau, new_dgen.lifecycle_demand_cost_after_tax_bau) existing_dgen.resilience_hours_min = add_values(existing_dgen.resilience_hours_min, new_dgen.resilience_hours_min) existing_dgen.resilience_hours_max = add_values(existing_dgen.resilience_hours_max, new_dgen.resilience_hours_max) @@ -470,7 +470,7 @@ def self.merge_distributed_generation(existing_dgen, new_dgen) new_dgen.generator.each do |generator| existing_dgen.generator.push generator - if existing_dgen.total_wind_kw.nil? + if existing_dgen.total_generator_kw.nil? existing_dgen.total_generator_kw = generator.size_kw else existing_dgen.total_generator_kw += generator.size_kw diff --git a/lib/urbanopt/reporting/default_reports/feature_report.rb b/lib/urbanopt/reporting/default_reports/feature_report.rb index 3a91855..01ccbf0 100644 --- a/lib/urbanopt/reporting/default_reports/feature_report.rb +++ b/lib/urbanopt/reporting/default_reports/feature_report.rb @@ -55,7 +55,7 @@ def initialize(hash = {}) # design_parameters to add later @construction_costs = [] hash[:construction_costs].each do |cc| - @constructiion_costs << ConstructionCost.new(cc) + @construction_costs << ConstructionCost.new(cc) end @reporting_periods = [] @@ -212,7 +212,6 @@ def to_hash def save(file_name = 'default_feature_report') # reassign the initialize local variable @file_name to the file name input. @file_name = file_name - # save the feature reports csv and json data old_timeseries_path = nil if !@timeseries_csv.path.nil? diff --git a/lib/urbanopt/reporting/default_reports/qaqc_flags.rb b/lib/urbanopt/reporting/default_reports/qaqc_flags.rb index 3fb20f9..6ffaa1c 100644 --- a/lib/urbanopt/reporting/default_reports/qaqc_flags.rb +++ b/lib/urbanopt/reporting/default_reports/qaqc_flags.rb @@ -16,9 +16,9 @@ class QAQC ## # _Hash_ - Hash of flags raised by QAQC measure for this feature during this reporting period # - attr_accessor :eui_reasonableness,:end_use_by_category,:mechanical_system_part_load_efficiency, - :simultaneous_heating_and_cooling , :internal_loads , :schedules, :envelope_r_value, - :domestic_hot_water , :mechanical_system_efficiency , :supply_and_zone_air_temperature, + attr_accessor :eui_reasonableness, :end_use_by_category, :mechanical_system_part_load_efficiency, + :simultaneous_heating_and_cooling, :internal_loads, :schedules, :envelope_r_value, + :domestic_hot_water, :mechanical_system_efficiency, :supply_and_zone_air_temperature, :total_qaqc_flags ## @@ -46,14 +46,11 @@ def initialize(hash = {}) @mechanical_system_efficiency = hash[:mechanical_system_efficiency] @total_qaqc_flags = hash[:total_qaqc_flags] - # initialize class variables @@validator and @@schema @@validator ||= Validator.new @@schema ||= @@validator.schema - end - ## # Assigns default values if values do not exist. ## @@ -72,9 +69,9 @@ def defaults hash[:mechanical_system_efficiency] = nil hash[:total_qaqc_flags] = nil - return hash end + ## # Convert to a Hash equivalent for JSON serialization ## @@ -99,7 +96,6 @@ def to_hash end return result - end ## @@ -126,7 +122,6 @@ def add_values(existing_value, new_value) #:nodoc: # +other+ - _QAQC_ - An object of Program class. ## def add_qaqc_flags(other) - @eui_reasonableness = add_values(@eui_reasonableness, other.eui_reasonableness) @end_use_by_category = add_values(@end_use_by_category, other.end_use_by_category) @mechanical_system_part_load_efficiency = add_values(@mechanical_system_part_load_efficiency, other.mechanical_system_part_load_efficiency) @@ -138,9 +133,7 @@ def add_qaqc_flags(other) @domestic_hot_water = add_values(@domestic_hot_water, other.domestic_hot_water) @mechanical_system_efficiency = add_values(@mechanical_system_efficiency, other.mechanical_system_efficiency) @total_qaqc_flags = add_values(@total_qaqc_flags, other.total_qaqc_flags) - end - end end end diff --git a/lib/urbanopt/reporting/default_reports/reporting_period.rb b/lib/urbanopt/reporting/default_reports/reporting_period.rb index fa988e1..0460a58 100644 --- a/lib/urbanopt/reporting/default_reports/reporting_period.rb +++ b/lib/urbanopt/reporting/default_reports/reporting_period.rb @@ -24,9 +24,9 @@ class ReportingPeriod :fuel_type, :total_cost_dollar, :usage_cost_dollar, :demand_cost_dollar, :comfort_result, :time_setpoint_not_met_during_occupied_cooling, :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, :emissions, :future_annual_electricity_emissions_mt, :future_hourly_electricity_emissions_mt, :historical_annual_electricity_emissions_mt, :historical_hourly_electricity_emissions_mt, - :future_annual_electricity_emissions_intensity_kg_per_ft2, :future_hourly_electricity_emissions_intensity_kg_per_ft2, :historical_annual_electricity_emissions_intensity_kg_per_ft2, :historical_hourly_electricity_emissions_intensity_kg_per_ft2 , + :future_annual_electricity_emissions_intensity_kg_per_ft2, :future_hourly_electricity_emissions_intensity_kg_per_ft2, :historical_annual_electricity_emissions_intensity_kg_per_ft2, :historical_hourly_electricity_emissions_intensity_kg_per_ft2, :natural_gas_emissions_mt, :natural_gas_emissions_intensity_kg_per_ft2, :propane_emissions_mt, :propane_emissions_intensity_kg_per_ft2, - :fueloil_no2_emissions_mt, :fueloil_no2_emissions_intensity_kg_per_ft2 #:nodoc: + :fueloil_no2_emissions_mt, :fueloil_no2_emissions_intensity_kg_per_ft2 #:nodoc: # ReportingPeriod class initializes the reporting period attributes: # +:id+ , +:name+ , +:multiplier+ , +:start_date+ , +:end_date+ , +:month+ , +:day_of_month+ , +:year+ , +:total_site_energy_kwh+ , +:total_source_energy_kwh+ , +:site_EUI_kwh_per_m2+, +:site_EUI_kbtu_per_ft2+, +:source_EUI_kwh_per_m2+, +:source_EUI_kbtu_per_ft2+, @@ -123,11 +123,11 @@ def defaults 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, hours_out_of_comfort_bounds_PMV: nil, hours_out_of_comfort_bounds_PPD: nil } hash[:emissions] = { future_annual_electricity_emissions_mt: nil, future_hourly_electricity_emissions_mt: nil, historical_annual_electricity_emissions_mt: nil, - historical_hourly_electricity_emissions_mt: nil, future_annual_electricity_emissions_intensity_kg_per_ft2: nil, - future_hourly_electricity_emissions_intensity_kg_per_ft2: nil, historical_annual_electricity_emissions_intensity_kg_per_ft2: nil, - historical_hourly_electricity_emissions_intensity_kg_per_ft2: nil, natural_gas_emissions_mt: nil, - natural_gas_emissions_intensity_kg_per_ft2: nil, propane_emissions_mt: nil, propane_emissions_intensity_kg_per_ft2: nil, - fueloil_no2_emissions_mt: nil, fueloil_no2_emissions_intensity_kg_per_ft2: nil } + historical_hourly_electricity_emissions_mt: nil, future_annual_electricity_emissions_intensity_kg_per_ft2: nil, + future_hourly_electricity_emissions_intensity_kg_per_ft2: nil, historical_annual_electricity_emissions_intensity_kg_per_ft2: nil, + historical_hourly_electricity_emissions_intensity_kg_per_ft2: nil, natural_gas_emissions_mt: nil, + natural_gas_emissions_intensity_kg_per_ft2: nil, propane_emissions_mt: nil, propane_emissions_intensity_kg_per_ft2: nil, + fueloil_no2_emissions_mt: nil, fueloil_no2_emissions_intensity_kg_per_ft2: nil } return hash end diff --git a/lib/urbanopt/reporting/default_reports/scenario_power_distribution_cost.rb b/lib/urbanopt/reporting/default_reports/scenario_power_distribution_cost.rb index 410e3b5..007802a 100644 --- a/lib/urbanopt/reporting/default_reports/scenario_power_distribution_cost.rb +++ b/lib/urbanopt/reporting/default_reports/scenario_power_distribution_cost.rb @@ -9,169 +9,165 @@ require 'json-schema' module URBANopt - module Reporting - module DefaultReports - ## - # scenario_power_distribution_cost include eletrical power distribution system violation and - # upgrade cost information. - ## - class ScenarioPowerDistributionCost - attr_accessor :results, :outputs, :violation_summary, :costs_per_equipment, :equipment - - ## - # ScenarioPowerDistributionCost class initializes all - # scenario_power_distribution_cost attributes: - # +:results+, +:outputs+, +:violation_summary+, +:costs_per_equipment+, +:equipment+ - ## - def initialize(hash = {}) - hash.delete_if { |k, v| v.nil? } - hash = defaults.merge(hash) - - @results = hash[:results] - @outputs = hash[:outputs] - @violation_summary = hash[:violation_summary] - @costs_per_equipment = hash[:costs_per_equipment] - @equipment = hash[:equipment] - - # initialize class variables @@validator and @@schema - @@validator ||= Validator.new - @@schema ||= @@validator.schema - end - - ## - # Assigns default values if attribute values do not exist.## - def defaults - hash = {} - hash[:results] = [] - hash[:outputs] = {} - hash[:violation_summary] = [] - hash[:costs_per_equipment] = [] - hash[:equipment] = [] - - return hash - end - - ## - # Converts to a Hash equivalent for JSON serialization. - ## - # - Exclude attributes with nil values. - # - Validate power_distribution_cost hash properties against schema. - ## - def to_hash - result = {} - result[:results] = @results if @results - result[:outputs] = @outputs if !@outputs.empty? - result[:violation_summary] = @violation_summary if @violation_summary - result[:costs_per_equipment] = @costs_per_equipment if @costs_per_equipment - result[:equipment] = @equipment if @equipment - - # validate power_distribution_cost properties against schema - if @@validator.validate(@@schema[:definitions][:ScenarioPowerDistributionCost][:properties], result).any? - raise "scenario_power_distribution_cost properties does not match schema: #{@@validator.validate(@@schema[:definitions][:ScenarioPowerDistributionCost][:properties], result)}" - end - - return result - end - - ## - # Add a result - ## - def add_result(hash = {}) - hash.delete_if { |k, v| v.nil? } - hash = defaults.merge(hash) - result = {} - result['num_violations'] = hash[:num_violations] - @results << result - end - - ## - ## Add outputs - ## - def add_outputs(hash = {}) - hash.delete_if { |k, v| v.nil? } - hash = defaults.merge(hash) - output = {} - output['log_file'] = hash[:log_file] - output['jobs'] = [] - hash[:jobs].each do |job| - output['jobs'] << job - end - @outputs = output - end - - ## - ## Add a violation summary - ## - def add_violation_summary(hash = {}) - hash.delete_if { |k, v| v.nil? } - hash = defaults.merge(hash) - violation_summary = {} - violation_summary['scenario'] = hash[:scenario] - violation_summary['stage'] = hash[:stage] - violation_summary['upgrade_type'] = hash[:upgrade_type] - violation_summary['simulation_time_s'] = hash[:simulation_time_s] - violation_summary['thermal_violations_present'] = hash[:thermal_violations_present] - violation_summary['voltage_violations_present'] = hash[:voltage_violations_present] - violation_summary['max_bus_voltage'] = hash[:max_bus_voltage] - violation_summary['min_bus_voltage'] = hash[:min_bus_voltage] - violation_summary['num_voltage_violation_buses'] = hash[:num_voltage_violation_buses] - violation_summary['num_overvoltage_violation_buses'] = hash[:num_overvoltage_violation_buses] - violation_summary['voltage_upper_limit'] = hash[:voltage_upper_limit] - violation_summary['num_undervoltage_violation_buses'] = hash[:num_undervoltage_violation_buses] - violation_summary['voltage_lower_limit'] = hash[:voltage_lower_limit] - violation_summary['max_line_loading'] = hash[:max_line_loading] - violation_summary['max_transformer_loading'] = hash[:max_transformer_loading] - violation_summary['num_line_violations'] = hash[:num_line_violations] - violation_summary['line_upper_limit'] = hash[:line_upper_limit] - violation_summary['num_transformer_violations'] = hash[:num_transformer_violations] - violation_summary['transformer_upper_limit'] = hash[:transformer_upper_limit] - - @violation_summary << violation_summary - end - - ## - # Add costs per equipment - ## - def add_costs_per_equipment - hash.delete_if { |k, v| v.nil? } - hash = defaults.merge(hash) - costs_per_equipment = {} - costs_per_equipment['name'] = hash[:name] - costs_per_equipment['type'] = hash[:type] - costs_per_equipment['count'] = hash[:count] - costs_per_equipment['total_cost_usd'] = hash[:costs_per_equipment] - - @costs_per_equipment << costs_per_equipment - end - - ## - # Add equipment - ## - def add_equipment - hash.delete_if { |k, v| v.nil? } - hash = defaults.merge(hash) - equipment = {} - equipment['equipment_type'] = hash[:equipment_type] - equipment['equipment_name'] = hash[:equipment_name] - equipment['status'] = hash[:status] - equipment['parameter1_name'] = hash[:parameter1_name] - equipment['parameter1_original'] = hash[:parameter1_original] - equipment['parameter1_upgraded'] = hash[:parameter1_upgraded] - equipment['parameter2_name'] = hash[:parameter2_name] - equipment['parameter2_original'] = hash[:parameter2_original] - equipment['parameter2_upgraded'] = hash[:parameter2_upgraded] - equipment['parameter3_name'] = hash[:parameter3_name] - equipment['parameter3_original'] = hash[:parameter3_original] - equipment['parameter3_upgraded'] = hash[:parameter3_upgraded] - equipment['name'] = hash[:name] - - @equipment << equipment - end - - - - end # ScenarioPowerDistributionCost - - end # DefaultReports - end # Reporting -end # URBANopt + module Reporting + module DefaultReports + ## + # scenario_power_distribution_cost include eletrical power distribution system violation and + # upgrade cost information. + ## + class ScenarioPowerDistributionCost + attr_accessor :results, :outputs, :violation_summary, :costs_per_equipment, :equipment + + ## + # ScenarioPowerDistributionCost class initializes all + # scenario_power_distribution_cost attributes: + # +:results+, +:outputs+, +:violation_summary+, +:costs_per_equipment+, +:equipment+ + ## + def initialize(hash = {}) + hash.delete_if { |k, v| v.nil? } + hash = defaults.merge(hash) + + @results = hash[:results] + @outputs = hash[:outputs] + @violation_summary = hash[:violation_summary] + @costs_per_equipment = hash[:costs_per_equipment] + @equipment = hash[:equipment] + + # initialize class variables @@validator and @@schema + @@validator ||= Validator.new + @@schema ||= @@validator.schema + end + + ## + # Assigns default values if attribute values do not exist.## + def defaults + hash = {} + hash[:results] = [] + hash[:outputs] = {} + hash[:violation_summary] = [] + hash[:costs_per_equipment] = [] + hash[:equipment] = [] + + return hash + end + + ## + # Converts to a Hash equivalent for JSON serialization. + ## + # - Exclude attributes with nil values. + # - Validate power_distribution_cost hash properties against schema. + ## + def to_hash + result = {} + result[:results] = @results if @results + result[:outputs] = @outputs if !@outputs.empty? + result[:violation_summary] = @violation_summary if @violation_summary + result[:costs_per_equipment] = @costs_per_equipment if @costs_per_equipment + result[:equipment] = @equipment if @equipment + + # validate power_distribution_cost properties against schema + if @@validator.validate(@@schema[:definitions][:ScenarioPowerDistributionCost][:properties], result).any? + raise "scenario_power_distribution_cost properties does not match schema: #{@@validator.validate(@@schema[:definitions][:ScenarioPowerDistributionCost][:properties], result)}" + end + + return result + end + + ## + # Add a result + ## + def add_result(hash = {}) + hash.delete_if { |k, v| v.nil? } + hash = defaults.merge(hash) + result = {} + result['num_violations'] = hash[:num_violations] + @results << result + end + + ## + ## Add outputs + ## + def add_outputs(hash = {}) + hash.delete_if { |k, v| v.nil? } + hash = defaults.merge(hash) + output = {} + output['log_file'] = hash[:log_file] + output['jobs'] = [] + hash[:jobs].each do |job| + output['jobs'] << job + end + @outputs = output + end + + ## + ## Add a violation summary + ## + def add_violation_summary(hash = {}) + hash.delete_if { |k, v| v.nil? } + hash = defaults.merge(hash) + violation_summary = {} + violation_summary['scenario'] = hash[:scenario] + violation_summary['stage'] = hash[:stage] + violation_summary['upgrade_type'] = hash[:upgrade_type] + violation_summary['simulation_time_s'] = hash[:simulation_time_s] + violation_summary['thermal_violations_present'] = hash[:thermal_violations_present] + violation_summary['voltage_violations_present'] = hash[:voltage_violations_present] + violation_summary['max_bus_voltage'] = hash[:max_bus_voltage] + violation_summary['min_bus_voltage'] = hash[:min_bus_voltage] + violation_summary['num_voltage_violation_buses'] = hash[:num_voltage_violation_buses] + violation_summary['num_overvoltage_violation_buses'] = hash[:num_overvoltage_violation_buses] + violation_summary['voltage_upper_limit'] = hash[:voltage_upper_limit] + violation_summary['num_undervoltage_violation_buses'] = hash[:num_undervoltage_violation_buses] + violation_summary['voltage_lower_limit'] = hash[:voltage_lower_limit] + violation_summary['max_line_loading'] = hash[:max_line_loading] + violation_summary['max_transformer_loading'] = hash[:max_transformer_loading] + violation_summary['num_line_violations'] = hash[:num_line_violations] + violation_summary['line_upper_limit'] = hash[:line_upper_limit] + violation_summary['num_transformer_violations'] = hash[:num_transformer_violations] + violation_summary['transformer_upper_limit'] = hash[:transformer_upper_limit] + + @violation_summary << violation_summary + end + + ## + # Add costs per equipment + ## + def add_costs_per_equipment + hash.delete_if { |k, v| v.nil? } + hash = defaults.merge(hash) + costs_per_equipment = {} + costs_per_equipment['name'] = hash[:name] + costs_per_equipment['type'] = hash[:type] + costs_per_equipment['count'] = hash[:count] + costs_per_equipment['total_cost_usd'] = hash[:costs_per_equipment] + + @costs_per_equipment << costs_per_equipment + end + + ## + # Add equipment + ## + def add_equipment + hash.delete_if { |k, v| v.nil? } + hash = defaults.merge(hash) + equipment = {} + equipment['equipment_type'] = hash[:equipment_type] + equipment['equipment_name'] = hash[:equipment_name] + equipment['status'] = hash[:status] + equipment['parameter1_name'] = hash[:parameter1_name] + equipment['parameter1_original'] = hash[:parameter1_original] + equipment['parameter1_upgraded'] = hash[:parameter1_upgraded] + equipment['parameter2_name'] = hash[:parameter2_name] + equipment['parameter2_original'] = hash[:parameter2_original] + equipment['parameter2_upgraded'] = hash[:parameter2_upgraded] + equipment['parameter3_name'] = hash[:parameter3_name] + equipment['parameter3_original'] = hash[:parameter3_original] + equipment['parameter3_upgraded'] = hash[:parameter3_upgraded] + equipment['name'] = hash[:name] + + @equipment << equipment + end + end + end + end +end diff --git a/lib/urbanopt/reporting/default_reports/schema/scenario_schema.json b/lib/urbanopt/reporting/default_reports/schema/scenario_schema.json index c140d0f..01efcf5 100644 --- a/lib/urbanopt/reporting/default_reports/schema/scenario_schema.json +++ b/lib/urbanopt/reporting/default_reports/schema/scenario_schema.json @@ -412,32 +412,73 @@ "description": "File path of REopt assumptions file used to generate results, if known", "type": "string" }, - "annual_renewable_electricity_pct": { - "description": "Percentage of annual renewable electricity generation", + "renewable_electricity_fraction": { + "description": "Fraction of annual renewable electricity generation", "type": "number" }, - "lcc_us_dollars": { - "description": "Optimal lifecycle cost", + "lcc": { + "description": "Lifecycle costs for the complete distributed generation system", + "units": "dollars", "type": "number" }, - "npv_us_dollars": { - "description": "Net present value of savings realized by the project", + "lcc_bau": { + "description": "Lifecycle cost in the business as usual scenario (i.e no new system)", + "units": "dollars", "type": "number" }, - "year_one_energy_cost_us_dollars": { - "description": "Optimal year one utility energy cost", + "npv": { + "description": "Net present value of the complete distributed generation system", + "units": "dollars", "type": "number" }, - "year_one_demand_cost_us_dollars": { - "description": "Optimal year one utility demand cost", + "lifecycle_demand_cost_after_tax": { + "description": "Total demand costs in US Dollars over the life of the system after tax", + "units": "dollars", "type": "number" }, - "year_one_bill_us_dollars": { - "description": "Optimal year one utility bill", + "year_one_energy_cost_before_tax": { + "description": "Total amount paid for utility energy in US Dollars in the first year of operation", + "units": "dollars", "type": "number" }, - "total_energy_cost_us_dollars": { - "description": "Total utility energy cost over the lifecycle, after-tax", + "year_one_energy_cost_before_tax_bau": { + "description": "Year one energy cost in the business as usual scenario (i.e no new system) before tax", + "units": "dollars", + "type": "number" + }, + "year_one_demand_cost_before_tax": { + "description": "Total amount paid in utility demand charges in US Dollars in the first year of operation", + "units": "dollars", + "type": "number" + }, + "year_one_demand_cost_before_tax_bau": { + "description": "Year one demand cost in the business as usual scenario (i.e no new system) before tax", + "units": "dollars", + "type": "number" + }, + "year_one_bill_before_tax": { + "description": "Total amount paid to the utility in US Dollars in the first year of operation", + "units": "dollars", + "type": "number" + }, + "year_one_bill_before_tax_bau": { + "description": "Year one bill in the business as usual scenario (i.e no new system) before tax", + "units": "dollars", + "type": "number" + }, + "lifecycle_energy_cost_after_tax": { + "description": "Total energy costs in US Dollars over the life of the system after tax", + "units": "dollars", + "type": "number" + }, + "lifecycle_demand_cost_after_tax_bau": { + "description": "Total demand costs in US Dollars over the life of the system in the business as usual scenario (i.e no new system) after tax", + "units": "dollars", + "type": "number" + }, + "lifecycle_energy_cost_after_tax_bau": { + "description": "Total energy costs in US Dollars over the life of the system in the business as usual scenario (i.e no new system) after tax", + "units": "dollars", "type": "number" }, "SolarPV": { diff --git a/lib/urbanopt/reporting/version.rb b/lib/urbanopt/reporting/version.rb index a9a7f6b..3ebcacc 100644 --- a/lib/urbanopt/reporting/version.rb +++ b/lib/urbanopt/reporting/version.rb @@ -5,6 +5,6 @@ module URBANopt module Reporting - VERSION = '0.9.1'.freeze + VERSION = '0.10.0'.freeze end end diff --git a/spec/urbanopt/reporting/reporting_spec.rb b/spec/urbanopt/reporting/reporting_spec.rb index 7e68dc7..dbed76b 100644 --- a/spec/urbanopt/reporting/reporting_spec.rb +++ b/spec/urbanopt/reporting/reporting_spec.rb @@ -100,9 +100,6 @@ existing_costs << URBANopt::Reporting::DefaultReports::ConstructionCost.new(category: 'HVACComponent', item_name: 'hvac', unit_cost: 1, cost_units: 'CostPerEach', item_quantity: 1, total_cost: 1) - # puts "existing_costs = #{existing_costs}" - # puts "new_costs = #{new_costs}" - construction_cost = URBANopt::Reporting::DefaultReports::ConstructionCost.merge_construction_costs(existing_costs, new_costs) if construction_cost[0].item_name == 'wall' @@ -128,8 +125,32 @@ expect(construction_cost[2].item_quantity).to eq(1) expect(construction_cost[2].total_cost).to eq(1) end + end + + context 'with distributed generation' do + it 'can add values in distributed generation' do + expect(URBANopt::Reporting::DefaultReports::DistributedGeneration.add_values(1, 2)).to eq(3) + end + + it 'can handle only a single value when adding' do + expect(URBANopt::Reporting::DefaultReports::DistributedGeneration.add_values(nil, 4)).to eq(4) + end + + it 'can merge distributed generation systems together' do + existing_dgen = URBANopt::Reporting::DefaultReports::DistributedGeneration.new(renewable_electricity_fraction: 0, year_one_energy_cost_us_dollars: 100_000) + new_dgen = URBANopt::Reporting::DefaultReports::DistributedGeneration.new(renewable_electricity_fraction: 50, year_one_energy_cost_us_dollars: 50_000) + + distributed_generation = URBANopt::Reporting::DefaultReports::DistributedGeneration.merge_distributed_generation(existing_dgen, new_dgen) + + expect(distributed_generation.renewable_electricity_fraction).to eq(50) + end + end - # puts "final cost = #{existing_costs}" + it 'can add generator sizes' do + generator = URBANopt::Reporting::DefaultReports::Generator.new(size_kw: 5) + new_generator = URBANopt::Reporting::DefaultReports::Generator.new(size_kw: 8) + total_generator = URBANopt::Reporting::DefaultReports::Generator.add_generator(generator, new_generator) + expect(total_generator.size_kw).to eq(13) end it 'can merge end uses' do @@ -169,9 +190,6 @@ end_date: { month: 1, day_of_month: 31, year: 2019 }, total_site_energy_kwh: 1, total_source_energy_kwh: 1, end_uses: { electricity_kwh: { heating: 1, cooling: 1, fans: 1, pumps: 1 } }, utility_costs_dollar: [{ fuel_type: 'Electricity', total_cost: 1, usage_cost: 1, demand_cost: 1 }]) - # puts "\nexisting periods: #{existing_periods}" - # puts "\nnew periods: #{new_periods}" - reporting_period = URBANopt::Reporting::DefaultReports::ReportingPeriod.merge_reporting_periods(existing_periods, new_periods) expect(reporting_period[0].id).to eq(5) @@ -205,20 +223,25 @@ expect(reporting_period[1].end_uses.electricity_kwh.cooling).to eq(2) expect(reporting_period[1].end_uses.electricity_kwh.fans).to eq(2) expect(reporting_period[1].end_uses.electricity_kwh.pumps).to eq(2) - - # puts "\nfinal periods: #{existing_periods}" end it 'can report solarPV results' do solar_pv = URBANopt::Reporting::DefaultReports::SolarPV.new({ size_kw: 100, id: 1, location: 'roof' }) expect(solar_pv.size_kw).to eq 100 expect(solar_pv.location).to eq 'roof' + second_pv_array = URBANopt::Reporting::DefaultReports::SolarPV.new({ size_kw: 101, id: 1, location: 'ground' }) + expect(URBANopt::Reporting::DefaultReports::SolarPV.add_pv(solar_pv, second_pv_array).size_kw).to eq 201 end it 'can report power distribution cost results' do - distribution_cost = URBANopt::Reporting::DefaultReports::ScenarioPowerDistributionCost.new({ "results": [ {"name": "baseline_scenario","num_violations": 0,"total_cost_usd": 546950.36419523}], "violation_summary": [ { "name": "baseline_scenario"}]}) + distribution_cost = URBANopt::Reporting::DefaultReports::ScenarioPowerDistributionCost.new({ "results": [{ "name": 'baseline_scenario', "num_violations": 0, "total_cost_usd": 546950.36419523 }], "violation_summary": [{ "name": 'baseline_scenario' }] }) expect(distribution_cost.results[0][:name]).to eq 'baseline_scenario' expect(distribution_cost.violation_summary[0][:name]).to eq 'baseline_scenario' end + it 'can report location results' do + location = URBANopt::Reporting::DefaultReports::Location.new({ latitude_deg: 13, longitude_deg: -61.24 }) + expect(location.latitude_deg).to eq 13 + expect(location.longitude_deg).to eq(-61.24) + end end