Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement/soft capex #177

Draft
wants to merge 3 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ repos:
hooks:
- id: black
name: black
stages: [commit]
stages: [pre-commit]
exclude: ^ORBIT/api/wisdem

- repo: https://github.com/pre-commit/pre-commit-hooks
Expand Down
287 changes: 263 additions & 24 deletions ORBIT/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,11 +323,21 @@ def compile_input_dict(cls, phases):
"project_lifetime": "yrs (optional, default: 25)",
"discount_rate": "yearly (optional, default: .025)",
"opex_rate": "$/kW/year (optional, default: 150)",
"construction_insurance": "$/kW (optional, default: 44)",
"construction_financing": "$/kW (optional, default: 183)",
"contingency": "$/kW (optional, default: 316)",
"commissioning": "$/kW (optional, default: 44)",
"decommissioning": "$/kW (optional, default: 58)",
"construction_insurance": "$/kW (optional, default: value calculated using construction_insurance_factor)",
"construction_financing": "$/kW (optional, default: value calculated using construction_financing_factor))",
"procurement_contingency": "$/kW (optional, default: value calculated using procurement_contingency_factor)",
"installation_contingency": "$/kW (optional, default: value calculated using installation_contingency_factor)",
"decommissioning": "$/kW (optional, default: value calculated using decommissioning_factor)",
"project_completion": "$/kW (optional, default: value calculated using project_completion_factor)",
"construction_insurance_factor": "float (optional, default: 0.0115)",
"construction_financing_factor": "$/kW (optional, default: value calculated using spend_schedule, tax_rate and interest_during_construction))",
"spend_schedule": "dict (optional, default: {0: 0.25, 1: 0.25, 2: 0.3, 3: 0.1, 4: 0.1, 5: 0.0}",
"tax_rate": "float (optional, default: 0.26",
"interest_during_construction": "float (optional, default: 0.044",
"procurement_contingency_factor": "float (optional, default: 0.0575)",
"installation_contingency_factor": "float (optional, default: 0.345)",
"decommissioning_factor": "float (optional, default: 0.1725)",
"project_completion_factor": "float (optional, default: 0.0115)",
"site_auction_price": "$ (optional, default: 100e6)",
"site_assessment_cost": "$ (optional, default: 50e6)",
"construction_plan_cost": "$ (optional, default: 1e6)",
Expand Down Expand Up @@ -1372,7 +1382,9 @@ def capex_breakdown(self):
outputs[name] = cost

outputs["Turbine"] = self.turbine_capex

outputs["Soft"] = self.soft_capex

outputs["Project"] = self.project_capex

return outputs
Expand All @@ -1386,6 +1398,65 @@ def capex_breakdown_per_kw(self):
for k, v in self.capex_breakdown.items()
}

@property
def capex_detailed_soft_capex_breakdown(self):
"""Returns CapEx breakdown by category with a detailed soft capex breakdown."""

unique = np.unique(
[*self.system_costs.keys(), *self.installation_costs.keys()]
)
categories = {}

for phase in unique:
for base, cat in self._capex_categories.items():
if base in phase:
categories[phase] = cat
break

missing = list(set(unique).difference([*categories]))
if missing:
print(
f"Warning: CapEx category not found for {missing}. "
f"Added to 'Misc.'"
)

for phase in missing:
categories[phase] = "Misc."

outputs = {}
for phase, cost in self.system_costs.items():
name = categories[phase]
if name in outputs:
outputs[name] += cost

else:
outputs[name] = cost

for phase, cost in self.installation_costs.items():
name = categories[phase] + " Installation"
if name in outputs:
outputs[name] += cost

else:
outputs[name] = cost

outputs["Turbine"] = self.turbine_capex

outputs = {**outputs, **self.soft_capex_breakdown}

outputs["Project"] = self.project_capex

return outputs

@property
def capex_detailed_soft_capex_breakdown_per_kw(self):
"""Returns CapEx per kW breakdown by category with a detailed soft capex breakdown."""

return {
k: v / (self.capacity * 1000)
for k, v in self.capex_detailed_soft_capex_breakdown.items()
}

@property
def bos_capex(self):
"""Returns total balance of system CapEx."""
Expand Down Expand Up @@ -1438,38 +1509,206 @@ def overnight_capex(self):

@property
def soft_capex(self):
"""Returns total project cost costs."""
"""Returns Total Soft CapEx."""

return sum(self.soft_capex_breakdown.values())

@property
def soft_capex_per_kw(self):
"""Returns Total Soft CapEx per kW."""

try:
capex = self.soft_capex_per_kw * self.capacity * 1000
capex = sum(self.soft_capex_breakdown.values()) / (self.capacity * 1000)

except TypeError:
capex = None

return capex

@property
def soft_capex_per_kw(self):
def soft_capex_breakdown(self):
"""Returns soft cost breakdown."""

soft_capex = {"Construction Insurance": self.construction_insurance_capex,
"Decommissioning": self.decommissioning_capex,
"Project Completion": self.project_completion_capex,
"Procurement Contingency": self.procurement_contingency_capex,
"Installation Contingency": self.installation_contingency_capex,
"Construction Financing": self.construction_financing_capex
}

return soft_capex



@property
def construction_insurance_capex(self):
"""
Returns project soft costs per kW. Default numbers are based on the
Cost of Energy Review (Stehly and Beiter 2018).
Returns the construction insurance capital cost of the project.
Methodology from ORCA model, default values used in 2022 Cost of Wind Energy Review.
"""

insurance = self.project_params.get("construction_insurance", 44)
financing = self.project_params.get("construction_financing", 183)
contingency = self.project_params.get("contingency", 316)
commissioning = self.project_params.get("commissioning", 44)
decommissioning = self.project_params.get("decommissioning", 58)
try:
construction_insurance_per_kW = self.config["project_parameters"]["construction_insurance"]
num_turbines = self.config["plant"]["num_turbines"]
rating = self.config["turbine"]["turbine_rating"]
construction_insurance = construction_insurance_per_kW * num_turbines * rating * 1000

except:
contruction_insurance_factor = self.project_params.get("construction_insurance_factor", 0.0115)
construction_insurance = (self.turbine_capex + self.bos_capex + self.project_capex) *\
contruction_insurance_factor

return construction_insurance

@property
def decommissioning_capex(self):
"""
Returns the decommissioning capital cost of the project.
Methodology from ORCA model, default values used in 2022 Cost of Wind Energy Review.
"""

try:
decommissioning_per_kW = self.config["project_parameters"]["decommissioning"]
num_turbines = self.config["plant"]["num_turbines"]
rating = self.config["turbine"]["turbine_rating"]
decommissioning = decommissioning_per_kW * num_turbines * rating * 1000

except:
decommissioning_factor = self.project_params.get("decommissioning_factor", 0.175)
decommissioning = self.installation_capex * decommissioning_factor

return decommissioning

@property
def project_completion_capex(self):
"""
Returns the project completion capital cost of the project.
Methodology from ORCA model, default values used in 2022 Cost of Wind Energy Review.
"""

try:
project_completion_per_kW = self.config["project_parameters"]["project_completion"]
num_turbines = self.config["plant"]["num_turbines"]
rating = self.config["turbine"]["turbine_rating"]
project_completion = project_completion_per_kW * num_turbines * rating * 1000

except:
project_completion_factor = self.project_params.get("project_completion_factor", 0.0115)
project_completion = (self.turbine_capex + self.bos_capex + self.project_capex) *\
project_completion_factor

return project_completion

@property
def procurement_contingency_capex(self):
"""
Returns the procurement contingency capital cost of the project.
Methodology from ORCA model, default values used in 2022 Cost of Wind Energy Review.
"""

try:
procurement_contingency_per_kW = self.config["project_parameters"]["procurement_contingency"]
num_turbines = self.config["plant"]["num_turbines"]
rating = self.config["turbine"]["turbine_rating"]
procurement_contingency = procurement_contingency_per_kW * num_turbines * rating * 1000

except:
procurement_contingency_factor = self.project_params.get("procurement_contingency_factor", 0.0575)
procurement_contingency = (self.turbine_capex + self.bos_capex + self.project_capex - self.installation_capex) *\
procurement_contingency_factor

return procurement_contingency

@property
def installation_contingency_capex(self):
"""
Returns the installation contingency capital cost of the project.
Methodology from ORCA model, default values used in 2022 Cost of Wind Energy Review.
"""

try:
installation_contingency_per_kW = self.config["project_parameters"]["installation_contingency"]
num_turbines = self.config["plant"]["num_turbines"]
rating = self.config["turbine"]["turbine_rating"]
installation_contingency = installation_contingency_per_kW * num_turbines * rating * 1000

except:
installation_contingency_factor = self.project_params.get("installation_contingency_factor", 0.345)
installation_contingency = self.installation_capex * installation_contingency_factor

return installation_contingency

@property
def construction_financing_factor(self):
"""
Returns the construction finaning factor of the project.
Methodology from ORCA model, default values used in 2022 Cost of Wind Energy Review,
except the spend schedule, which is sourced from collaborations with industry.
"""

try:
spend_schedule = self.config["project_parameters"]["spend_schedule"]
except:
spend_schedule = self.project_params.get("spend_schedule", {0: 0.25,
1: 0.25,
2: 0.3,
3: 0.1,
4: 0.1,
5: 0.0
})

try:
tax_rate = self.config["project_parameters"]["tax_rate"]
except:
tax_rate = self.project_params.get("tax_rate", 0.26)

try:
interest_during_construction = self.config["project_parameters"]["interest_during_construction"]
except:
interest_during_construction = self.project_params.get("interest_during_construction", 0.044)


_check = 0
_construction_financing_factor = 0

for key, val in spend_schedule.items():
_check += val

_construction_financing_factor += val *\
(1 + (1 - tax_rate) * ((1 + interest_during_construction) **\
(key + 0.5) - 1))
if _check != 1.0:
raise Exception("Values in spend_schedule must sum to 1.0")

return _construction_financing_factor

@property
def construction_financing_capex(self):
"""
Returns the construction financing capital cost of the project.
Methodology from ORCA model, default values used in 2022 Cost of Wind Energy Review.
"""

try:
construction_financing_per_kW = self.config["project_parameters"]["construction_financing"]
num_turbines = self.config["plant"]["num_turbines"]
rating = self.config["turbine"]["turbine_rating"]
construction_financing = construction_financing_per_kW * num_turbines * rating * 1000

except:
construction_financing_factor = self.project_params.get("construction_financing_factor", self.construction_financing_factor)
construction_financing = (self.construction_insurance_capex +\
self.decommissioning_capex +\
self.project_completion_capex +\
self.procurement_contingency_capex +\
self.installation_contingency_capex +\
self.bos_capex + self.turbine_capex) * (construction_financing_factor - 1)


return construction_financing


return sum(
[
insurance,
financing,
contingency,
commissioning,
decommissioning,
],
)

@property
def project_capex(self):
Expand Down
Loading
Loading