urbanopt-reopt 0.12.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,156 @@
1
+ # *********************************************************************************
2
+ # URBANopt (tm), Copyright (c) Alliance for Sustainable Energy, LLC.
3
+ # See also https://github.com/urbanopt/urbanopt-reopt-gem/blob/develop/LICENSE.md
4
+ # *********************************************************************************
5
+
6
+ require 'net/https'
7
+ require 'openssl'
8
+ require 'uri'
9
+ require 'json'
10
+ require 'securerandom'
11
+ require_relative '../../../developer_nrel_key'
12
+ require 'urbanopt/reopt/reopt_logger'
13
+
14
+ module URBANopt # :nodoc:
15
+ module REopt # :nodoc:
16
+ class REoptLiteGHPAPI
17
+
18
+ def initialize(reopt_input_file, nrel_developer_key = nil, reopt_output_file, use_localhost)
19
+
20
+ # Store developer key
21
+ if [nil, '', '<insert your key here>'].include? nrel_developer_key
22
+ if [nil, '', '<insert your key here>'].include? DEVELOPER_NREL_KEY
23
+ raise 'A developer.nrel.gov API key is required. Please see https://developer.nrel.gov/signup/ then update the file urbanopt-reopt-gem/developer_nrel_key.rb'
24
+ else
25
+ #Store the NREL developer key
26
+ nrel_developer_key = DEVELOPER_NREL_KEY
27
+ end
28
+ end
29
+
30
+ @use_localhost = use_localhost
31
+ if @use_localhost
32
+ @root_url = "http://localhost:8000/v3"
33
+ else
34
+ @root_url = "https://developer.nrel.gov/api/reopt/v3"
35
+ end
36
+ # add REopt URL
37
+ @nrel_developer_key = nrel_developer_key
38
+ @reopt_input_file = reopt_input_file
39
+ @reopt_output_file = reopt_output_file
40
+ # initialize @@logger
41
+ @@logger ||= URBANopt::REopt.reopt_logger
42
+ @@logger.level = Logger::INFO
43
+ end
44
+
45
+
46
+ def get_api_results(run_id=nil)
47
+
48
+ reopt_input_file = @reopt_input_file
49
+ nrel_developer_key = @nrel_developer_key
50
+ root_url = @root_url
51
+ reopt_output_file = @reopt_output_file
52
+
53
+ if run_id.nil?
54
+ run_id = get_run_uuid(reopt_input_file, nrel_developer_key, reopt_output_file)
55
+ end
56
+ if !run_id.nil?
57
+ results_url = "#{@root_url}/job/#{run_id}/results/?api_key=#{nrel_developer_key}"
58
+ puts "This is results URL #{results_url}"
59
+ results = reopt_request(results_url)
60
+
61
+ File.open(reopt_output_file, 'w') do |f|
62
+ f.write(JSON.pretty_generate(results))
63
+ @@logger.info("Saved results to #{reopt_output_file}")
64
+ end
65
+ else
66
+ results = nil
67
+ @@logger.error("Unable to get results: no UUID returned.")
68
+ end
69
+ results
70
+ end
71
+
72
+ def get_run_uuid(reopt_input_file, nrel_developer_key, root_url)
73
+
74
+ reopt_input_file = @reopt_input_file
75
+ nrel_developer_key = @nrel_developer_key
76
+ root_url = @root_url
77
+ post_url = "#{root_url}/job/?api_key=#{nrel_developer_key}"
78
+ puts "This is URL: #{post_url}"
79
+ @@logger.info("Connecting to #{post_url}")
80
+
81
+ # Parse the URL and prepare the HTTP request
82
+ uri = URI.parse(post_url)
83
+ request = Net::HTTP::Post.new(uri)
84
+ request.content_type = 'application/json'
85
+
86
+ # Add the JSON payload (assuming 'post' is the body data)
87
+ request.body = reopt_input_file.to_json
88
+
89
+ # Send the HTTP request
90
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
91
+ http.request(request)
92
+ end
93
+
94
+ run_id = nil
95
+
96
+ if !response.is_a?(Net::HTTPSuccess)
97
+ @@logger.error("Status code #{response.code}. #{response.body}")
98
+ @@logger.error("Status code #{response.code}")
99
+ else
100
+ @@logger.info("Response OK from #{post_url}.")
101
+ run_id_dict = JSON.parse(response.body)
102
+
103
+ begin
104
+ run_id = run_id_dict['run_uuid']
105
+ rescue KeyError
106
+ msg = "Response from #{post_url} did not contain run_uuid."
107
+ @@logger.error(msg)
108
+ end
109
+ end
110
+ # Return run_id
111
+ run_id
112
+ end
113
+
114
+ def reopt_request(results_url, poll_interval = 5, max_timeout = 300)
115
+
116
+ key_error_count = 0
117
+ key_error_threshold = 3
118
+ status = "Optimizing..."
119
+ @@logger.info("Polling #{results_url} for results with interval of #{poll_interval}...")
120
+ resp_dict = {}
121
+ start_time = Time.now
122
+
123
+ loop do
124
+ uri = URI.parse(results_url)
125
+ response = Net::HTTP.get_response(uri)
126
+ resp_dict = JSON.parse(response.body)
127
+
128
+ begin
129
+ status = resp_dict['status']
130
+ rescue KeyError
131
+ key_error_count += 1
132
+ @@logger.info("KeyError count: #{key_error_count}")
133
+ if key_error_count > key_error_threshold
134
+ @@logger.info("Breaking polling loop due to KeyError count threshold of #{key_error_threshold} exceeded.")
135
+ break
136
+ end
137
+ end
138
+
139
+ if status != "Optimizing..."
140
+ break
141
+ end
142
+
143
+ if Time.now - start_time > max_timeout
144
+ @@logger.info("Breaking polling loop due to max timeout of #{max_timeout} seconds exceeded.")
145
+ break
146
+ end
147
+
148
+ sleep(poll_interval)
149
+
150
+ end
151
+ resp_dict
152
+ end
153
+
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,27 @@
1
+ {
2
+ "Site": {
3
+ "latitude": 42.81428490645775,
4
+ "longitude": -78.84701778930912
5
+ },
6
+ "SpaceHeatingLoad": {
7
+ },
8
+ "DomesticHotWaterLoad": {
9
+ },
10
+ "ElectricLoad": {
11
+ "year": 2017
12
+ },
13
+ "ElectricTariff": {
14
+ "urdb_label": "594976725457a37b1175d089"
15
+ },
16
+ "GHP":{
17
+ "installed_cost_heatpump_per_ton": 1075,
18
+ "installed_cost_ghx_per_ft": 14,
19
+ "installed_cost_building_hydronic_loop_per_sqft": 1.7,
20
+ "om_cost_per_sqft_year": 0,
21
+ "macrs_bonus_fraction": 0.6,
22
+ "macrs_itc_reduction": 0.5,
23
+ "federal_itc_fraction": 0.3
24
+ },
25
+ "ExistingBoiler": {
26
+ }
27
+ }
@@ -0,0 +1,149 @@
1
+ # *********************************************************************************
2
+ # URBANopt (tm), Copyright (c) Alliance for Sustainable Energy, LLC.
3
+ # See also https://github.com/urbanopt/urbanopt-reopt-gem/blob/develop/LICENSE.md
4
+ # *********************************************************************************
5
+
6
+ require 'bundler/setup'
7
+ require 'urbanopt/reopt/reopt_logger'
8
+ require 'urbanopt/reopt/reopt_ghp_api'
9
+ require 'csv'
10
+ require 'json'
11
+ require 'fileutils'
12
+
13
+ module URBANopt # :nodoc:
14
+ module REopt # :nodoc:
15
+ class REoptGHPPostProcessor
16
+ def initialize(run_dir, system_parameter, modelica_result, reopt_ghp_assumptions = nil, nrel_developer_key = nil, localhost)
17
+ # initialize @@logger
18
+ @@logger ||= URBANopt::REopt.reopt_logger
19
+
20
+ @nrel_developer_key = nrel_developer_key
21
+ @localhost = localhost
22
+ @reopt_ghp_output_district = nil
23
+ @reopt_ghp_output_building = []
24
+ @reopt_ghp_assumptions_hash = nil
25
+ @reopt_ghp_assumptions = nil
26
+ @system_parameter = nil
27
+ @system_parameter_hash = nil
28
+ @modelica_result = nil
29
+ @building_ids = nil
30
+ @run_dir = run_dir
31
+
32
+ if !reopt_ghp_assumptions.nil?
33
+ @reopt_ghp_assumptions = reopt_ghp_assumptions
34
+ File.open(reopt_ghp_assumptions, 'r') do |file|
35
+ @reopt_ghp_assumptions_input_hash = JSON.parse(file.read, symbolize_names: true)
36
+ end
37
+ end
38
+
39
+ if !system_parameter.nil?
40
+ @system_parameter = system_parameter
41
+ File.open(system_parameter, 'r') do |file|
42
+ @system_parameter_input_hash = JSON.parse(file.read, symbolize_names: true)
43
+ end
44
+ # Determine loop order
45
+ loop_order = File.join(File.dirname(system_parameter), '_loop_order.json')
46
+ if File.exist?(loop_order)
47
+ File.open(loop_order, 'r') do |file|
48
+ loop_order_input = JSON.parse(file.read, symbolize_names: true)
49
+ # Check the type of the parsed data
50
+ if loop_order_input.is_a?(Array)
51
+ @loop_order_input_hash = loop_order_input
52
+ @loop_order_input_hash.each do |item|
53
+ puts "Building IDs in group: #{item[:list_bldg_ids_in_group].inspect}"
54
+ puts "GHE IDs in group: #{item[:list_ghe_ids_in_group].inspect}"
55
+ end
56
+ elsif loop_order_input.is_a?(Hash)
57
+ @loop_order_input_hash = [loop_order_input] # Wrap in array if a single object
58
+ @loop_order_input_hash.each do |item|
59
+ puts "Building IDs in group: #{item[:list_bldg_ids_in_group].inspect}"
60
+ puts "GHE IDs in group: #{item[:list_ghe_ids_in_group].inspect}"
61
+ end
62
+ else
63
+ puts 'Unexpected JSON structure'
64
+ end
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ if !modelica_result.nil?
71
+ @modelica_result_input = modelica_result
72
+ end
73
+ end
74
+
75
+ attr_accessor :run_dir, :system_parameter_input_hash, :reopt_ghp_assumptions_input_hash, :loop_order_input_hash, :modelica_result_input
76
+
77
+ # # Create REopt input and output building report
78
+ def run_reopt_lcca(system_parameter_hash: nil, reopt_ghp_assumptions_hash: nil, modelica_result: nil)
79
+ adapter = URBANopt::REopt::REoptGHPAdapter.new
80
+
81
+ # if these arguments are specified, use them
82
+ if !system_parameter_hash.nil?
83
+ @system_parameter_input_hash = system_parameter_hash
84
+ end
85
+
86
+ if !reopt_ghp_assumptions_hash.nil?
87
+ @reopt_ghp_assumptions_input_hash = reopt_ghp_assumptions_hash
88
+ end
89
+
90
+ if !modelica_result.nil?
91
+ @modelica_result_input = modelica_result
92
+ end
93
+
94
+ # Create folder for REopt input files only if they dont exist
95
+ reopt_ghp_dir = File.join(@run_dir, 'reopt_ghp')
96
+ reopt_ghp_input = File.join(reopt_ghp_dir, 'reopt_ghp_inputs')
97
+ unless Dir.exist?(reopt_ghp_dir)
98
+ FileUtils.mkdir_p(reopt_ghp_dir)
99
+ end
100
+ unless Dir.exist?(reopt_ghp_input)
101
+ FileUtils.mkdir_p(reopt_ghp_input)
102
+ end
103
+
104
+ reopt_ghp_output = File.join(reopt_ghp_dir, 'reopt_ghp_outputs')
105
+ unless Dir.exist?(reopt_ghp_output)
106
+ FileUtils.mkdir_p(reopt_ghp_output)
107
+ end
108
+
109
+ # get building IDs from _loop_order.json
110
+ building_ids = []
111
+ ghp_ids = []
112
+ @loop_order_input_hash.each do |loop|
113
+ building_ids.concat(loop[:list_bldg_ids_in_group].flatten)
114
+ ghp_ids.concat(loop[:list_ghe_ids_in_group].flatten)
115
+ end
116
+
117
+ building_ids.each do |building_id|
118
+ # create REopt building input file for all buildings in loop order list
119
+ reopt_input_building = adapter.create_reopt_input_building(@run_dir, @system_parameter_input_hash, @reopt_ghp_assumptions_input_hash, building_id, @modelica_result_input)
120
+ end
121
+ ghp_ids.each do |ghp_id|
122
+ # create REopt district input file
123
+ reopt_input_district = adapter.create_reopt_input_district(@run_dir, @system_parameter_input_hash, @reopt_ghp_assumptions_input_hash, ghp_id, @modelica_result_input)
124
+ end
125
+
126
+ Dir.foreach(reopt_ghp_input) do |input_file|
127
+ # Skip '.' and '..' (current and parent directory entries)
128
+ next if input_file == '.' || input_file == '..'
129
+
130
+ reopt_ghp_input_file_path = File.join(reopt_ghp_input, input_file)
131
+
132
+ reopt_input_data = nil
133
+
134
+ File.open(reopt_ghp_input_file_path, 'r') do |f|
135
+ reopt_input_data = JSON.parse(f.read)
136
+ end
137
+
138
+ base_name = File.basename(input_file, '.json')
139
+
140
+ # reopt_ghp_output_file
141
+ reopt_output_file = File.join(reopt_ghp_output, "#{base_name}_output.json")
142
+ # call the REopt API
143
+ api = URBANopt::REopt::REoptLiteGHPAPI.new(reopt_input_data, DEVELOPER_NREL_KEY, reopt_output_file, @localhost)
144
+ api.get_api_results
145
+ end
146
+ end
147
+ end # REoptGHPPostProcessor
148
+ end # REopt
149
+ end # URBANopt
@@ -8,7 +8,6 @@ require 'openssl'
8
8
  require 'uri'
9
9
  require 'json'
10
10
  require 'securerandom'
11
- require 'certified'
12
11
  require_relative '../../../developer_nrel_key'
13
12
  require 'urbanopt/reopt/reopt_logger'
14
13
 
@@ -16,7 +15,7 @@ module URBANopt # :nodoc:
16
15
  module REopt # :nodoc:
17
16
  class REoptLiteAPI
18
17
  ##
19
- # \REoptLiteAPI manages submitting optimization tasks to the \REopt API and recieving results.
18
+ # \REoptLiteAPI manages submitting optimization tasks to the \REopt API and receiving results.
20
19
  # Results can either be sourced from the production \REopt API with an API key from developer.nrel.gov, or from
21
20
  # a version running at localhost.
22
21
  ##
@@ -55,7 +54,7 @@ module URBANopt # :nodoc:
55
54
  #
56
55
  # * +run_uuid+ - _String_ - Unique run_uuid obtained from the \REopt job submittal URL for a specific optimization task.
57
56
  #
58
- # [*return:*] _URI_ - Returns URI object for use in calling the \REopt results endpoint for a specifc optimization task.
57
+ # [*return:*] _URI_ - Returns URI object for use in calling the \REopt results endpoint for a specific optimization task.
59
58
  ##
60
59
  def uri_results(run_uuid) # :nodoc:
61
60
  if @use_localhost
@@ -73,7 +72,7 @@ module URBANopt # :nodoc:
73
72
  #
74
73
  # * +run_uuid+ - _String_ - Resilience statistics for a unique run_uuid obtained from the \REopt job submittal URL for a specific optimization task.
75
74
  #
76
- # [*return:*] _URI_ - Returns URI object for use in calling the \REopt resilience statistics endpoint for a specifc optimization task.
75
+ # [*return:*] _URI_ - Returns URI object for use in calling the \REopt resilience statistics endpoint for a specific optimization task.
77
76
  ##
78
77
  def uri_resilience(run_uuid) # :nodoc:
79
78
  if @use_localhost
@@ -112,11 +111,11 @@ module URBANopt # :nodoc:
112
111
  end
113
112
  tries = max_tries
114
113
  rescue StandardError => e
115
- @@logger.debug("error from REopt API: #{e}")
114
+ @@logger.error("error from REopt API: #{e}")
116
115
  if tries + 1 < max_tries
117
116
  @@logger.debug('trying again...')
118
117
  else
119
- @@logger.debug('max tries reached!')
118
+ @@logger.warn('max tries reached!')
120
119
  return result
121
120
  end
122
121
  tries += 1
@@ -133,7 +132,7 @@ module URBANopt # :nodoc:
133
132
  #
134
133
  # * +data+ - _Hash_ - Default \REopt formatted post containing at least all the required parameters.
135
134
  #
136
- # [*return:*] _Bool_ - Returns true if the post succeeeds. Otherwise returns false.
135
+ # [*return:*] _Bool_ - Returns true if the post succeeds. Otherwise returns false.
137
136
  ##
138
137
  def check_connection(data)
139
138
  header = { 'Content-Type' => 'application/json' }
@@ -166,7 +165,7 @@ module URBANopt # :nodoc:
166
165
  # * +reopt_input+ - _Hash_ - \REopt formatted post containing at least required parameters.
167
166
  # * +filename+ - _String_ - Path to file that will be created containing the full \REopt response.
168
167
  #
169
- # [*return:*] _Bool_ - Returns true if the post succeeeds. Otherwise returns false.
168
+ # [*return:*] _Bool_ - Returns true if the post succeeds. Otherwise returns false.
170
169
  ##
171
170
  def resilience_request(run_uuid, filename)
172
171
  if File.directory? filename
@@ -246,7 +245,7 @@ module URBANopt # :nodoc:
246
245
  # * +reopt_input+ - _Hash_ - \REopt formatted post containing at least required parameters.
247
246
  # * +filename+ - _String_ - Path to file that will be created containing the full \REopt response.
248
247
  #
249
- # [*return:*] _Bool_ - Returns true if the post succeeeds. Otherwise returns false.
248
+ # [*return:*] _Bool_ - Returns true if the post succeeds. Otherwise returns false.
250
249
  ##
251
250
  def reopt_request(reopt_input, filename)
252
251
  description = reopt_input[:description]
@@ -12,7 +12,7 @@ module URBANopt
12
12
  # Set Logger::DEBUG for development
13
13
  @@reopt_logger.level = Logger::WARN
14
14
  ##
15
- # Definining class variable "@@logger" to log errors, info and warning messages.
15
+ # Defining class variable "@@logger" to log errors, info and warning messages.
16
16
  def self.reopt_logger
17
17
  @@reopt_logger
18
18
  end
@@ -33,7 +33,6 @@ module URBANopt # :nodoc:
33
33
  end
34
34
  @nrel_developer_key = nrel_developer_key
35
35
  @localhost = localhost
36
- @reopt_base_post = { ElectricTariff: {}, ElectricLoad: {}, Wind: { max_kw: 0 } }
37
36
 
38
37
  @scenario_reopt_default_output_file = nil
39
38
  @scenario_timeseries_default_output_file = nil
@@ -182,7 +181,7 @@ module URBANopt # :nodoc:
182
181
  #
183
182
  # [*parameters:*]
184
183
  #
185
- # * +feature_reports+ - _Array_ - An array of _URBANopt::Reporting::DefaultReports::FeatureReport_ objetcs which will each be used to create (and are subsquently updated by) a \REopt opimization response.
184
+ # * +feature_reports+ - _Array_ - An array of _URBANopt::Reporting::DefaultReports::FeatureReport_ objects which will each be used to create (and are subsequently updated by) a \REopt opimization response.
186
185
  # * +reopt_assumptions_hashes+ - _Array_ - Optional. An array of \REopt formatted hashes containing default parameters (i.e. utility rate, escalation rate) which will be updated by the ScenarioReport (i.e. location, roof availability). The number and order of the hashes should match the feature_reports array.
187
186
  # * +reopt_output_files+ - _Array_ - Optional. A array of paths to files at which REpopt responses will be saved. The number and order of the paths should match the feature_reports array.
188
187
  # * +timeseries_csv_path+ - _Array_ - Optional. A array of paths to files at which the new timeseries CSV for the FeatureReports will be saved. The number and order of the paths should match the feature_reports array.
@@ -276,7 +275,7 @@ module URBANopt # :nodoc:
276
275
  #
277
276
  # [*parameters:*]
278
277
  #
279
- # * +scenario_report+ - _Array_ - A _URBANopt::Reporting::DefaultReports::ScenarioReport_ which will each be used to create (and is subsquently updated by) \REopt opimization responses for each of its FeatureReports.
278
+ # * +scenario_report+ - _Array_ - A _URBANopt::Reporting::DefaultReports::ScenarioReport_ which will each be used to create (and is subsequently updated by) \REopt opimization responses for each of its FeatureReports.
280
279
  # * +reopt_assumptions_hashes+ - _Array_ - Optional. An array of \REopt formatted hashes containing default parameters (i.e. utility rate, escalation rate) which will be updated by the ScenarioReport (i.e. location, roof availability). The number and order of the hashes should match the array in ScenarioReport.feature_reports.
281
280
  # * +reopt_output_files+ - _Array_ - Optional. An array of paths to files at which REpopt responses will be saved. The number and order of the paths should match the array in ScenarioReport.feature_reports.
282
281
  # * +feature_report_timeseries_csv_paths+ - _Array_ - Optional. An array of paths to files at which the new timeseries CSV for the FeatureReports will be saved. The number and order of the paths should match the array in ScenarioReport.feature_reports.
@@ -0,0 +1,148 @@
1
+ {
2
+ "Site": {
3
+ "latitude": {
4
+ "type": "float",
5
+ "required": true
6
+ },
7
+ "longitude": {
8
+ "type": "float",
9
+ "required": true
10
+ }
11
+ },
12
+ "SpaceHeatingLoad": {
13
+ "fuel_loads_mmbtu_per_hour": {
14
+ "type": "array",
15
+ "required": true,
16
+ "description": "8760 timeseries",
17
+ "note": "This parameter is required to run REopt's GHP module but is not used to calculate URBANopt's GHP system LCCA. Users can set this to small numbers in GHP scenario"
18
+ }
19
+ },
20
+ "DomesticHotWaterLoad": {
21
+ "fuel_loads_mmbtu_per_hour": {
22
+ "type": "array",
23
+ "required": true,
24
+ "description": "8760 timeseries, if GHP not providing DHW set to zeros"
25
+ }
26
+ },
27
+ "ElectricLoad": {
28
+ "load_kw": {
29
+ "type": "array",
30
+ "required": true,
31
+ "description": "8760 timeseries",
32
+ "note": "15 min interval data do not work in GHP module. Please make sure all timeseries are 8760"
33
+ }
34
+ },
35
+ "ElectricTarriff": {
36
+ "urdb_label": "string",
37
+ "required": true
38
+ },
39
+ "GHP": {
40
+ "require_ghp_purchase": {
41
+ "type": "boolean",
42
+ "required": true,
43
+ "description": "choices of 0 or 1. Always set at 1 for URBANopt's LCCA"
44
+ },
45
+ "building_sqft": {
46
+ "type": "float",
47
+ "required": true,
48
+ "description": "to calculate cost of hydronic loop",
49
+ "note": "for the GHX iteration, set building_sqft to a number close to 0 but not exactly 0"
50
+ },
51
+ "heatpump_capacity_sizing_factor_on_peak_load": {
52
+ "type": "float",
53
+ "required": true,
54
+ "description": "always set to 1"
55
+ },
56
+ "ghpghx_responses":{
57
+ "outputs": {
58
+ "heat_pump_configuration": {
59
+ "type": "string",
60
+ "required": true,
61
+ "description": "set as WSHP"
62
+ },
63
+ "peak_combined_heatpump_thermal_ton": {
64
+ "type": "float",
65
+ "required": true,
66
+ "description": "size of GHP in ton",
67
+ "note": "in the GHX iteration, set this value to a number close to 0 but NOT exactly 0S"
68
+ },
69
+ "number_of_boreholes": {
70
+ "type": "int",
71
+ "required": true,
72
+ "note": "in the GHP iteration, set this value to 0"
73
+ },
74
+ "length_boreholes_ft": {
75
+ "type": "float",
76
+ "required": true,
77
+ "note": "in the GHP iteration, set this value to 0"
78
+ },
79
+ "yearly_total_electric_consumption_series_kw": {
80
+ "type": "array",
81
+ "required": true,
82
+ "description": "8760 timeseries of building total electric consumption",
83
+ "note": "in the GHX iteration, set this value to a number close to 0 but not exactly 0"
84
+ },
85
+ "yearly_ghx_pump_electric_consumption_series_kw": {
86
+ "type": "array",
87
+ "required": true,
88
+ "description": "8760 timeseries of ghx's total electric consumption",
89
+ "note": "in the GHP iteration, set this value to 0"
90
+ }
91
+ },
92
+ "inputs": {
93
+ "heating_thermal_load_mmbtu_per_hr": {
94
+ "type": "array",
95
+ "required": true,
96
+ "description": "not used for URBANopt's GHP LCCA but required for formatting, set to number close to 0"
97
+ },
98
+ "cooling_thermal_load_ton": {
99
+ "type": "array",
100
+ "required": true,
101
+ "description": "not used for URBANopt's GHP LCCA but required for formatting, can set as zeros"
102
+ }
103
+ }
104
+ },
105
+ "installed_cost_heatpump_per_ton": {
106
+ "type": "float",
107
+ "required": false,
108
+ "description": "installation cost per unit (ton) of GHP"
109
+ },
110
+ "installed_cost_ghx_per_ft": {
111
+ "type": "float",
112
+ "required": false,
113
+ "description": "installation cost per unit (ft) of GHX"
114
+ },
115
+ "installed_cost_building_hydronic_loop_per_sqft": {
116
+ "type": "float",
117
+ "required": false,
118
+ "description": "installation cost per sqft of building hydronic loop"
119
+ },
120
+ "om_cost_per_sqft_year": {
121
+ "type": "float",
122
+ "required": false,
123
+ "description": "if not specified, the value is -$0.51, capturing saving from HVAC operation. Recommended setting at 0"
124
+ },
125
+ "macrs_bonus_fraction": {
126
+ "type": "float",
127
+ "required": false,
128
+ "description": "Percentage of macrs benefits for GHP. Value between 0 and 1"
129
+ },
130
+ "macrs_itc_reduction": {
131
+ "type": "float",
132
+ "required": false,
133
+ "description": "Percentage of macrs benefits for GHP. Value between 0 and 1"
134
+ },
135
+ "federal_itc_fraction": {
136
+ "type": "float",
137
+ "required": false,
138
+ "description": "Percentage of ITC benefits for GHP. Value between 0 and 1"
139
+ }
140
+ },
141
+ "ExistingBoiler": {
142
+ "fuel_cost_per_mmbtu": {
143
+ "type": "float",
144
+ "required": true,
145
+ "description": "to calculate BAU cost, and required for formatting in GHP scenario"
146
+ }
147
+ }
148
+ }
@@ -101,7 +101,7 @@
101
101
  "min": 0,
102
102
  "max": 1000000.0,
103
103
  "type": "number",
104
- "description": "Value placed on unmet site load during grid outages. Units are US dollars per unmet kilowatt-hour. The value of lost load (VoLL) is used to determine the avoided outage costs by multiplying VoLL [$/kWh] with the average number of hours that the critical load can be met by the energy system (determined by simulating outages occuring at every hour of the year), and multiplying by the mean critical load."
104
+ "description": "Value placed on unmet site load during grid outages. Units are US dollars per unmet kilowatt-hour. The value of lost load (VoLL) is used to determine the avoided outage costs by multiplying VoLL [$/kWh] with the average number of hours that the critical load can be met by the energy system (determined by simulating outages occurring at every hour of the year), and multiplying by the mean critical load."
105
105
  },
106
106
  "analysis_years": {
107
107
  "default": 20,
@@ -711,7 +711,7 @@
711
711
  "min": 0,
712
712
  "max": 1,
713
713
  "type": "number",
714
- "description": "Fraction of upfront project costs to depreciate under MACRS in year one in addtion to scheduled depreciation"
714
+ "description": "Fraction of upfront project costs to depreciate under MACRS in year one in addition to scheduled depreciation"
715
715
  },
716
716
  "inverter_replacement_year": {
717
717
  "default": 10,
@@ -103,6 +103,46 @@
103
103
  }
104
104
  }
105
105
  },
106
+ "Financial_GHP": {
107
+ "type": "object",
108
+ "properties": {
109
+ "lcc": {
110
+ "type": "number",
111
+ "description": "Optimal lifecycle cost for GHP",
112
+ "units": "dollars"
113
+ },
114
+ "net_present_cost": {
115
+ "type": "number",
116
+ "description": "",
117
+ "units": "dollars"
118
+ },
119
+ "lifecycle_om_costs_before_tax_bau": {
120
+ "type": "number",
121
+ "description": "",
122
+ "units": "dollars"
123
+ },
124
+ "lifecycle_om_costs_after_tax": {
125
+ "type": "number",
126
+ "description": "",
127
+ "units": "$"
128
+ },
129
+ "year_one_om_costs_before_tax": {
130
+ "type": "number",
131
+ "description": "",
132
+ "units": "$"
133
+ },
134
+ "lifecycle_generation_tech_capital_costs": {
135
+ "type": "number",
136
+ "description": "",
137
+ "units": "$"
138
+ },
139
+ "lifecycle_offgrid_other_annual_costs_after_tax": {
140
+ "type": "number",
141
+ "description": "",
142
+ "units": "$"
143
+ }
144
+ }
145
+ },
106
146
  "PV": {
107
147
  "type": "object",
108
148
  "properties": {