Skip to content

Commit

Permalink
derUtilityCost: Replaced installed_costs_per_kw/kwh with ongoing/one-…
Browse files Browse the repository at this point in the history
…time operational costs; calculated utility cost-savings cashflows
  • Loading branch information
astronobri committed Nov 28, 2024
1 parent 86837d0 commit e53d794
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 36 deletions.
10 changes: 5 additions & 5 deletions omf/models/derUtilityCost.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,12 @@
</select>
</div>
<div class="shortInput">
<label class="tooltip">Operational Power Capacity Cost ($/kW)<span class="classic">Specify the operational cost per kW of enrolling and controlling a member-consumer's battery (e.g. the API usage cost).</span></label>
<input type="text" id="installed_cost_per_kw" name="installed_cost_per_kw" value="{{allInputDataDict.installed_cost_per_kw}}" pattern="^\d+\.?\d*?$" required="required"/>
<label class="tooltip">Ongoing Operational Costs ($/month)<span class="classic">Specify the monthly ongoing operational costs of controlling all member-consumer batteries (e.g. the API usage cost per month).</span></label>
<input type="text" id="operationalCosts_ongoing" name="operationalCosts_ongoing" value="{{allInputDataDict.operationalCosts_ongoing}}" pattern="^\d+\.?\d*?$" required="required"/>
</div>
<div class="shortInput">
<label class="tooltip">Operational Energy Capacity Cost ($/kWh)<span class="classic">Specify the operational cost per kWh of enrolling and controlling a member-consumer's battery (e.g. the API usage cost)</span></label>
<input type="text" id="installed_cost_per_kwh" name="installed_cost_per_kwh" value="{{allInputDataDict.installed_cost_per_kwh}}" pattern="^\d+\.?\d*?$" required="required"/>
<label class="tooltip">One-time Operational Costs ($)<span class="classic">Specify the total one-time operational costs of controlling all member-consumer batteries (e.g. the contracting agreement with the device manufacturer).</span></label>
<input type="text" id="operationalCosts_onetime" name="operationalCosts_onetime" value="{{allInputDataDict.operationalCosts_onetime}}" pattern="^\d+\.?\d*?$" required="required"/>
</div>
<div class="shortInput">
<label class="tooltip">Battery Power Capacity (kW)<span class="classic">Specify the battery power capacity in kW for each individual battery enrolled by a member-consumer.</span></label>
Expand Down Expand Up @@ -328,7 +328,7 @@
new Highcharts.Chart({"credits":{"enabled":false},
"plotOptions":{"column":{"stacking":'normal'},"series":{"animation":false,"shadow":false},"spline":{"animation":false,"shadow":false}},
"xAxis":{"title":{"text":"Year After Installation","style":{"color":"gray"}},"type":"linear","tickColor":"gray","tickInterval":1,"lineColor":"gray","minorTickColor":"gray", "minorTickInterval":5},
"title":{"text":"NPV:$" + allOutputData.NPV.toFixed(0) + " ; SPP:" + allOutputData.SPP.toFixed(3), "verticalAlign":"top", "align":"right", "y":5, "x":-10, "style":{"color":"#333333", "fontSize":"12px"}},
"title":{"text":"NPV:$" + allOutputData.NPV.toFixed(0) + ", SPP:" + allOutputData.SPP.toFixed(3), "verticalAlign":"top", "align":"right", "y":5, "x":-10, "style":{"color":"#333333", "fontSize":"12px"}},
//"title":{"text":""},
"series":[{"name":"Net Benefits", "data":allOutputData.netCashflow},
{"name":"Cumulative Return", "type":"spline", "data":allOutputData.cumulativeCashflow}
Expand Down
95 changes: 64 additions & 31 deletions omf/models/derUtilityCost.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ def work(modelDir, inputDict):
'can_grid_charge': can_grid_charge_bool,
'total_rebate_per_kw': 0,
'macrs_option_years': 0,
'installed_cost_per_kw': float(inputDict['installed_cost_per_kw']),
'installed_cost_per_kwh': float(inputDict['installed_cost_per_kwh']),
'installed_cost_per_kw': 0,
'installed_cost_per_kwh': 0,
'battery_replacement_year': 0,
'inverter_replacement_year': 0,
'replace_cost_per_kwh': 0.0,
Expand Down Expand Up @@ -145,7 +145,12 @@ def work(modelDir, inputDict):
vbatPower = vbpower_series

subsidyUpfront = float(inputDict['subsidyUpfront'])
subsidyRecurring = float(inputDict['subsidyRecurring'])
subsidyRecurring_1year_total = float(inputDict['subsidyRecurring'])
subsidyRecurring_1month_total = subsidyRecurring_1year_total / 12
subsidyRecurring_total = subsidyRecurring_1year_total * int(inputDict['projectionLength'])
total_subsidy_1year = subsidyUpfront + subsidyRecurring_1year_total
total_subsidy_1year_array = np.full(12, subsidyRecurring_1month_total)
total_subsidy_1year_array[0] += subsidyUpfront

## NOTE: temporarily comment out the two derConsumer runs to run the code quicker
"""
Expand Down Expand Up @@ -293,7 +298,7 @@ def work(modelDir, inputDict):
fillcolor='rgba(100,200,210,1)',
showlegend=showlegend))
## Make original load and its legend name hidden in the plot by default
fig.update_traces(legendgroup='Original Load (kW)', visible='legendonly', selector=dict(name='Original Load (kW)'))
fig.update_traces(legendgroup='Original Load', visible='legendonly', selector=dict(name='Original Load'))

## Additional load (Charging BESS and vbat)
## NOTE: demand is added here for plotting purposes, so that the additional load shows up above the demand curve.
Expand Down Expand Up @@ -557,30 +562,58 @@ def work(modelDir, inputDict):
rateCompensation = float(inputDict['rateCompensation'])
total_residential_BESS_compensation = rateCompensation * np.sum(total_kwh_residential_BESS)

## Update utility savings to include BESS savings
## Currently, outData['savings'] is the vbatDispatch result savings only
BESS = reoptResults['ElectricStorage']['storage_to_load_series_kw']
## Calculate monthly BESS costs and savings
monthHours = [(0, 744), (744, 1416), (1416, 2160), (2160, 2880),
(2880, 3624), (3624, 4344), (4344, 5088), (5088, 5832),
(5832, 6552), (6552, 7296), (7296, 8016), (8016, 8760)]
(2880, 3624), (3624, 4344), (4344, 5088), (5088, 5832),
(5832, 6552), (6552, 7296), (7296, 8016), (8016, 8760)]
BESS = reoptResults['ElectricStorage']['storage_to_load_series_kw']
electricityCost = float(inputDict['electricityCost'])
BESS_savings_monthly = np.array([sum(BESS[s:f])*electricityCost for s, f in monthHours])
savings_from_BESS_TESS = np.array(outData['savings']) + BESS_savings_monthly
outData['savings'] = list(savings_from_BESS_TESS)

## Update the financial cost ouput to include REopt BESS
## TODO: combine these with the same variables from vbatDispatch. Currently this just replaces the vbatDispatch variables.
installed_cost_per_kw = float(inputDict['installed_cost_per_kw'])
installed_cost_per_kwh = float(inputDict['installed_cost_per_kwh'])
operational_costs_per_kw = installed_cost_per_kw * total_kw_residential_BESS
operational_costs_per_kwh = installed_cost_per_kwh * total_kwh_residential_BESS
#total_operational_costs = # can you add operational costs per kW + operational costs per kWh together?
netCashflow = np.sum(savings_from_BESS_TESS) - total_residential_BESS_compensation - operational_costs_per_kwh - subsidyUpfront - (subsidyRecurring*int(inputDict['projectionLength']))
#initialCosts = 1.
outData['NPV'] = netCashflow
#outData['SPP'] = initialCosts / netCashflow
outData['cumulativeCashflow'] = reoptResults['Financial']['offtaker_annual_free_cashflows'] #list(accumulate(reoptResults['Financial']['offtaker_annual_free_cashflows']))
outData['netCashflow'] = reoptResults['Financial']['offtaker_discounted_annual_free_cashflows'] #list(accumulate(reoptResults['Financial']['offtaker_annual_free_cashflows'])) ## or alternatively: offtaker_annual_free_cashflows
BESS_monthly_compensation_to_consumer = np.array([sum(BESS[s:f])*rateCompensation for s, f in monthHours])
BESS_monthly_if_bought_from_grid = np.array([sum(BESS[s:f])*electricityCost for s, f in monthHours])
utilitySavings_from_BESS_TESS = np.array(outData['savings']) + BESS_monthly_if_bought_from_grid

####################################################################################
## Calculate the financial benefit from controlling member-consumer DERs
####################################################################################

## Calculate residential BESS installation costs
installed_cost_per_kw = 20
installed_cost_per_kwh = 80
total_kw_installed_costs = installed_cost_per_kw * total_kw_residential_BESS
total_kwh_installed_costs = installed_cost_per_kwh * total_kwh_residential_BESS
#total_installed_costs = ## TODO: How do we deal with both costs per kw and kWh?

## Total operational costs (e.g. utility costs from API calls)
operationalCosts_ongoing = float(inputDict['operationalCosts_ongoing'])
operationalCosts_onetime = float(inputDict['operationalCosts_onetime'])
total_operationalCosts_1year = operationalCosts_onetime + operationalCosts_ongoing*12
total_operationalCosts_1year_array = np.full(12, operationalCosts_ongoing)
total_operationalCosts_1year_array[0] += operationalCosts_onetime

## Calculating total utility costs
projectionLength = int(inputDict['projectionLength'])
utilityCosts_1year_total = total_operationalCosts_1year + operationalCosts_ongoing + total_subsidy_1year + total_residential_BESS_compensation
## NOTE: utilityCosts_allyears_total (below) assumes that the REopt BESS array will be the same for every year of the entire projectionLength (entire analysis)
utilityCosts_allyears_total = utilityCosts_1year_total + (operationalCosts_ongoing+subsidyRecurring_1year_total+total_residential_BESS_compensation)*(projectionLength-1)
utilityCosts_1year_array = list(np.array(total_operationalCosts_1year_array) + np.array(total_subsidy_1year_array) + np.array(BESS_monthly_compensation_to_consumer))

## Calculating total utility savings
utilitySavings_1year_total = np.sum(utilitySavings_from_BESS_TESS)
utilitySavings_1year_array = utilitySavings_from_BESS_TESS
utilitySavings_allyears_total = utilitySavings_1year_total*projectionLength

## Calculating total utility net savings (savings minus costs)
utilityNetSavings_1year = utilitySavings_1year_total - utilityCosts_1year_total
utilityNetSavings_1year_array = list(np.array(utilitySavings_1year_array) - np.array(utilityCosts_1year_array))
utilityNetSavings_allyears_total = utilitySavings_allyears_total - utilityCosts_allyears_total

## Update financial parameters to include BESS costs
outData['savings'] = utilityNetSavings_1year_array
outData['totalCost'] = list(np.array(outData['totalCost']) + np.array(utilityCosts_1year_array))
outData['NPV'] = utilityNetSavings_allyears_total
outData['SPP'] = utilityCosts_allyears_total / utilitySavings_1year_total
#outData['cumulativeCashflow'] = reoptResults['Financial']['offtaker_annual_free_cashflows'] #list(accumulate(reoptResults['Financial']['offtaker_annual_free_cashflows']))
#outData['netCashflow'] = reoptResults['Financial']['offtaker_discounted_annual_free_cashflows'] #list(accumulate(reoptResults['Financial']['offtaker_annual_free_cashflows'])) ## or alternatively: offtaker_annual_free_cashflows

# Model operations typically ends here.
# Stdout/stderr.
Expand Down Expand Up @@ -617,20 +650,20 @@ def new(modelDir):
'existing_gen_kw': '7000',

## Chemical Battery Inputs
'numberBESS': '100', ## Number of residential Tesla Powerwall 2 batteries
'numberBESS': '1000', ## Number of residential Tesla Powerwall 3 batteries
'chemBESSgridcharge': 'Yes',
'installed_cost_per_kw': '20.0', #'300.0', ## (Residential: $1,000-$1,500 per kW, Utility: $300-$700 per kW)
'installed_cost_per_kwh': '60.0', #'480.0', ## Cost per kWh reflecting Powerwall’s installed cost (Residential: $400-$900 per kWh, Utility: $200-$400 per kWh)
'operationalCosts_ongoing': '20.0',
'operationalCosts_onetime': '1000',
'BESS_kw': '5',
'BESS_kwh': '13.5',

## Financial Inputs
'demandChargeCost': '25',
'demandChargeCost': '25', ## used by vbatDispatch
'projectionLength': '25',
'electricityCost': '0.04',
'rateCompensation': '0.02', ## unit: $/kWh
'subsidyUpfront': '100.0',
'subsidyRecurring': '0',
'subsidyRecurring': '24.0',

## vbatDispatch inputs:
'load_type': '1', ## Air Conditioner
Expand Down

0 comments on commit e53d794

Please sign in to comment.