Skip to content

Commit

Permalink
derUtilityCost & derConsumer: Removed existing BESS installation cost…
Browse files Browse the repository at this point in the history
…s from financial outputs; Added PV inputs
  • Loading branch information
astronobri committed Nov 10, 2024
1 parent 28dd33e commit d36c4c6
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 44 deletions.
34 changes: 5 additions & 29 deletions omf/models/derConsumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,9 @@ def create_REopt_jl_jsonFile(modelDir, inputDict):
}
}

## Add a PV section if enabled
if inputDict['PV'] == 'Yes':
scenario['PV'] = {
##TODO: Add options here, if needed
}
scenario['PV'] = {
##TODO: Add options here, if needed
}

## Add a Battery Energy Storage System (BESS) section if enabled
if inputDict['chemBESSgridcharge'] == 'Yes':
Expand All @@ -87,7 +85,7 @@ def create_REopt_jl_jsonFile(modelDir, inputDict):
'max_kwh': float(inputDict['max_kwh']), ## Battery Energy Capacity maximum
'can_grid_charge': can_grid_charge_bool,
'total_rebate_per_kw': float(inputDict['total_rebate_per_kw']),
'macrs_option_years': float(inputDict['macrs_option_years']),
'macrs_option_years': float(inputDict['batteryMacrs_option_years']),
#'macrs_bonus_fraction': float(inputDict['macrs_bonus_fraction']),
'replace_cost_per_kw': float(inputDict['replace_cost_per_kw']),
'replace_cost_per_kwh': float(inputDict['replace_cost_per_kwh']),
Expand All @@ -97,13 +95,6 @@ def create_REopt_jl_jsonFile(modelDir, inputDict):
'inverter_replacement_year': float(inputDict['inverter_replacement_year']),
'battery_replacement_year': float(inputDict['battery_replacement_year']),
}


## Add a Diesel Generator section if enabled
if inputDict['generator'] == 'Yes':
scenario['Generator'] = {
##TODO: Add options here, if needed
}

## Save the scenario file
## NOTE: reopt_jl currently requires a path for the input file, so the file must be saved to a location
Expand Down Expand Up @@ -286,8 +277,6 @@ def work(modelDir, inputDict):

## DER Overview plot ###################################################################################################################################################################
grid_to_load = reoptResults['ElectricUtility']['electric_to_load_series_kw']
if 'Generator' in reoptResults:
generator = reoptResults['Generator']['electric_to_load_series_kw']

if 'PV' in reoptResults: ## PV
PV = reoptResults['PV']['electric_to_load_series_kw']
Expand Down Expand Up @@ -422,18 +411,6 @@ def work(modelDir, inputDict):
showlegend=showlegend
))

## Generator serving load piece
if (inputDict['generator'] == 'Yes'):
fig.add_trace(go.Scatter(x=timestamps,
y=np.asarray(generator),
yaxis='y1',
mode='none',
fill='tozeroy',
name='Generator Serving Load (kW)',
fillcolor='rgba(0,137,83,1)',
showlegend=showlegend))
fig.update_traces(fillpattern_shape='/', selector=dict(name='BESS Serving Load (kW)'))

## Plot layout
fig.update_layout(
#title='Residential Data',
Expand Down Expand Up @@ -654,7 +631,6 @@ def new(modelDir):
'criticalLoadSwitch': 'Yes',
'criticalLoadFactor': '0.50',
'PV': 'Yes',
'generator': 'No',

## Financial Inputs
'demandChargeURDB': 'Yes',
Expand All @@ -669,7 +645,7 @@ def new(modelDir):
'min_kwh': '13.5', ## Minimum energy capacity based on Powerwall’s full capacity
'max_kwh': '13.5', ## Maximum energy capacity to use the entire capacity
'total_rebate_per_kw': '10.0', ## Assuming $10/kW incentive
'macrs_option_years': '25', ## Depreciation years
'batteryMacrs_option_years': '25', ## Depreciation years
'macrs_bonus_fraction': '0.4', ## 40% bonus depreciation fraction
'replace_cost_per_kw': '460.0',
'replace_cost_per_kwh': '240.0',
Expand Down
60 changes: 52 additions & 8 deletions omf/models/derUtilityCost.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,6 @@
<label class="tooltip">Year<span class="classic">Specify the year to which the load shape values corresond.</span></label>
<input type="text" id="year" name="year" value="{{allInputDataDict.year}}" pattern="^\d{4}$" required="required"/>
</div>
<div class="shortInput">
<label class="tooltip">Diesel Generator<span class="classic">Include diesel generator in DER analysis.</span></label>
<select id="generator" name="generator" value="{{allInputDataDict.generator}}"/>
<option value="Yes" {{ 'selected' if allInputDataDict.generator == 'Yes' }}>Yes</option>
<option value="No" {{ 'selected' if allInputDataDict.generator == 'No' }}>No</option>
</select>
</div>

<!-- Financial Inputs -->
<div class="wideInput">
Expand Down Expand Up @@ -172,7 +165,7 @@
</div>
<div class="shortInput">
<label class="tooltip">Battery MACRS Years<span class="classic">MACRS schedule for financial analysis. Possible inputs are 0, 5 and 7 years.</span></label>
<select id="macrs_option_years" name="macrs_option_years" value="{{allInputDataDict.macrs_option_years}}"/>
<select id="batteryMacrs_option_years" name="batteryMacrs_option_years" value="{{allInputDataDict.batteryMacrs_option_years}}"/>
<option value="0" {{ 'selected' if allInputDataDict.batteryMacrsOptionYears == '0' }}>0</option>
<option value="5" {{ 'selected' if allInputDataDict.batteryMacrsOptionYears == '5' }}>5</option>
<option value="7" {{ 'selected' if allInputDataDict.batteryMacrsOptionYears == '7' }}>7</option>
Expand Down Expand Up @@ -203,6 +196,57 @@
<input type="text" id="battery_replacement_year" name="battery_replacement_year" value="{{allInputDataDict.battery_replacement_year}}" required="required"/>
</div>

<!-- Photovoltaic Inputs -->
<div class="wideInput">
<p class="inputSectionHeader">Photovoltaic Device Inputs</p>
</div>
<hr style="border-style: solid; border-color: #196b12; margin-top: 10px;">
<div class="shortInput solarDisplayInput">
<label class="tooltip">Existing photovoltaic size (kW)<span class="classic">Specify the total capacity of any pre-existing photovoltaic installation in kW.</span></label>
<input type="text" id="existing_kw_PV" name="existing_kw_PV" value="{{allInputDataDict.existing_kw_PV}}" required="required"/>
</div>
<div class="shortInput">
<label class="tooltip">Additional Utility-Installed PV Size (kW)<span class="classic">Specify the additional desired photovoltaic size amount in kW.</span></label>
<input type="text" id="additional_kw_PV" name="additional_kw_PV" value="{{allInputDataDict.additional_kw_PV}}" required="required"/>
</div>
<div class="shortInput">
<label class="tooltip">Photovoltaic Cost ($/kW)<span class="classic">Specify the cost in $/kW.</span></label>
<input type="text" id="costPV" name="costPV" value="{{allInputDataDict.costPV}}" pattern="^\d+\.?\d*?$" required="required"/>
</div>
<div class="shortInput">
<label class="tooltip">Photovoltaic Minimum Power (kW)<span class="classic">Specify the minimum desired photovoltaic generation in kW.</span></label>
<input type="text" id="min_kw_PV" name="min_kw_PV" value="{{allInputDataDict.min_kw_PV}}" required="required"/>
</div>
<div class="shortInput">
<label class="tooltip">Photovoltaic Maximum Power (kW)<span class="classic">Specify the maximum desired generation in kW. Leave at default for full optimization on solar power.</span></label>
<input type="text" id="max_kw_PV" name="max_kw_PV" value="{{allInputDataDict.max_kw_PV}}" required="required"/>
</div>
<div class="shortInput">
<label class="tooltip">Can photovoltaic be curtailed?<span class="classic">Allows for excess photovoltaic power to be automatically curtailed.</span></label>
<select id="PVCanCurtail" name="PVCanCurtail">
<option value='Yes' {{ 'selected' if allInputDataDict.PVCanCurtail == Yes }}>Yes</option>
<option value='No' {{ 'selected' if allInputDataDict.PVCanCurtail == No }}>No</option>
</select>
</div>
<div class="shortInput">
<label class="tooltip">Can photovoltaic be exported?<span class="classic">Allows for excess photovoltaic power to be sold back to the grid.</span></label>
<select id="PVCanExport" name="PVCanExport">
<option value='Yes' {{ 'selected' if allInputDataDict.PVCanExport == Yes }}>Yes</option>
<option value='No' {{ 'selected' if allInputDataDict.PVCanExport == No }}>No</option>
</select>
</div>
<div class="shortInput">
<label class="tooltip">Photovoltaic MACRS Years<span class="classic">MACRS schedule for financial analysis. Possible inputs are 0, 5 and 7 years.</span></label>
<select id="PVMacrsOptionYears" name="PVMacrsOptionYears" value="{{allInputDataDict.PVMacrsOptionYears}}"/>
<option value="0" {{ 'selected' if allInputDataDict.PVMacrsOptionYears == '0' }}>0</option>
<option value="5" {{ 'selected' if allInputDataDict.PVMacrsOptionYears == '5' }}>5</option>
<option value="7" {{ 'selected' if allInputDataDict.PVMacrsOptionYears == '7' }}>7</option>
</select>
</div>
<div class="shortInput">
<label class="tooltip">Photovoltaic ITC (%)<span class="classic">Please enter a number 0-1 for the photovoltaic investment tax credit. Format 0.XX</span></label>
<input type="number" id="PVItcPercent" name="PVItcPercent" value="{{allInputDataDict.PVItcPercent}}" step="0.01" min="0.0" max="1.0" required="required"/>
</div>

<!-- vbatDispatch Specific Inputs -->
<div class="wideInput">
Expand Down
41 changes: 34 additions & 7 deletions omf/models/derUtilityCost.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,25 @@ def work(modelDir, inputDict):
outData['NPV'] = reoptResults['Financial']['npv']
outData['SPP'] = reoptResults['Financial']['simple_payback_years'] ## TODO: combine these same variables from vbatDispatch as well
outData['cumulativeCashflow'] = reoptResults['Financial']['offtaker_annual_free_cashflows'] #list(accumulate(reoptResults['Financial']['offtaker_annual_free_cashflows']))
outData['netCashflow'] = reoptResults['Financial']['offtaker_annual_free_cashflows'] #list(accumulate(reoptResults['Financial']['offtaker_annual_free_cashflows'])) ## or alternatively: 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

## Temporarily correct financial outputs for any existing BESS
## (Remove the installation cost of any existing BESS since we are including it in the aggregated BESS: existing utility BESS + proposed consumer BESS)
existing_utility_bess_kw = float(inputDict['existing_kw'])
existing_utility_bess_kwh = float(inputDict['existing_kwh'])
installed_cost_per_kw = float(inputDict['installed_cost_per_kw'])
installed_cost_per_kwh = float(inputDict['installed_cost_per_kwh'])
cost_existing_utility_bess_kw = existing_utility_bess_kw * installed_cost_per_kw
cost_existing_utility_bess_kwh = existing_utility_bess_kwh * installed_cost_per_kwh
totalcost_existing_utility_bess = cost_existing_utility_bess_kw + cost_existing_utility_bess_kwh
outData['NPV'] -= totalcost_existing_utility_bess
outData['cumulativeCashflow'] = [cashflow - totalcost_existing_utility_bess for cashflow in reoptResults['Financial']['offtaker_annual_free_cashflows']]
outData['netCashflow'] = [cashflow - totalcost_existing_utility_bess for cashflow in reoptResults['Financial']['offtaker_annual_free_cashflows']]

## Calculate adjusted initial investment and simply payback period (SPP)
adjusted_initial_investment = reoptResults['Financial']['initial_capital_costs'] - totalcost_existing_utility_bess
outData['SPP'] = adjusted_initial_investment / np.sum(outData['netCashflow'])


## NOTE: temporarily comment out the two derConsumer runs to run the code quicker
"""
Expand Down Expand Up @@ -322,7 +340,7 @@ def work(modelDir, inputDict):
fillcolor='rgba(255,246,0,1)',
showlegend=showlegend
))

##vbatDispatch (TESS) piece
fig.add_trace(go.Scatter(x=timestamps,
y=np.asarray(vbat_discharge_flipsign),
Expand Down Expand Up @@ -646,8 +664,6 @@ def new(modelDir):
'tempFileName': 'utility_CO_2018_temperatures.csv',
'demandCurve': demand_curve,
'tempCurve': temp_curve,
'PV': 'Yes',
'generator': 'No',

## Chemical Battery Inputs
'numberBESS': '100', ## Number of residential Tesla Powerwall 2 batteries
Expand All @@ -659,16 +675,27 @@ def new(modelDir):
'min_kwh': '13.5', ## Minimum energy capacity based on Powerwall’s full capacity
'max_kwh': '13.5', ## Maximum energy capacity to use the entire capacity
'total_rebate_per_kw': '10.0', ## Assuming $10/kW incentive
'macrs_option_years': '25', ## Depreciation years
'batteryMacrs_option_years': '25', ## Depreciation years
#'macrs_bonus_fraction': '0.4', ## 40% bonus depreciation fraction
'replace_cost_per_kw': '460.0',
'replace_cost_per_kwh': '240.0',
'installed_cost_per_kw': '480.0', ## Approximate cost per kW installed, based on total price range
'installed_cost_per_kwh': '480.0', ## Cost per kWh reflecting Powerwall’s installed cost
'installed_cost_per_kw': '300.0', ## (Residential: $1,000-$1,500 per kW, Utility: $300-$700 per kW)
'installed_cost_per_kwh': '480.0', ## Cost per kWh reflecting Powerwall’s installed cost (Residential: $400-$900 per kWh, Utility: $200-$400 per kWh)
'total_itc_fraction': '0.0', ## No ITC included unless specified
'inverter_replacement_year': '10',
'battery_replacement_year': '10',

## Photovoltaic Inputs
'existing_kw_PV': '29500.0',
'additional_kw_PV': '0.0',
'costPV': '0.0',
'min_kw_PV': '0',
'max_kw_PV': '29500.0',
'PVCanCurtail': 'Yes',
'PVCanExport': 'Yes',
'PVMacrsOptionYears': '25',
'PVItcPercent': '0.0',

## Financial Inputs
'demandChargeURDB': 'Yes',
'demandChargeCost': '25',
Expand Down

0 comments on commit d36c4c6

Please sign in to comment.