Spaces:
Sleeping
Sleeping
Durand D'souza
commited on
Added csv export endpoint
Browse files
main.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
| 1 |
from typing import Annotated, Dict, List, Optional, Tuple
|
| 2 |
from urllib.parse import urlencode
|
| 3 |
-
from fastapi import FastAPI, Query, Request
|
| 4 |
from fastapi.middleware.cors import CORSMiddleware
|
| 5 |
-
from fastapi.responses import RedirectResponse
|
| 6 |
import pandas as pd
|
|
|
|
| 7 |
from capacity_factors import get_solar_capacity_factor
|
| 8 |
from schema import CapacityFactor, Location, SolarPVAssumptions
|
| 9 |
from model import calculate_cashflow_for_renewable_project, calculate_lcoe
|
|
@@ -22,28 +23,22 @@ app.add_middleware(
|
|
| 22 |
allow_headers=["*"],
|
| 23 |
)
|
| 24 |
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
-
# @app.get("/")
|
| 27 |
-
# async def read_main(request: Request):
|
| 28 |
-
# # Get query parameters as dictionary
|
| 29 |
-
# query_params = dict(request.query_params)
|
| 30 |
-
|
| 31 |
-
# # Create new URL with parameters
|
| 32 |
-
# print(request.url.components)
|
| 33 |
-
# print(request.url.scheme)
|
| 34 |
-
# print(request.url.netloc)
|
| 35 |
-
# print("hf.space" in request.url.netloc)
|
| 36 |
-
# redirect_url = (request.url.scheme if "hf.space" not in request.url.netloc else "https") + "://" + request.url.netloc + request.url.path + "ui"
|
| 37 |
-
# print(redirect_url)
|
| 38 |
-
# if query_params:
|
| 39 |
-
# redirect_url += "?" + urlencode(query_params)
|
| 40 |
|
| 41 |
-
|
|
|
|
| 42 |
|
| 43 |
|
| 44 |
-
@app.get("/solarpv/lcoe")
|
| 45 |
-
def
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
|
| 49 |
@app.get("/solarpv/capacity_factor.json")
|
|
@@ -54,4 +49,43 @@ def get_capacity_factor(pv_location: Annotated[Location, Query()]) -> CapacityFa
|
|
| 54 |
)
|
| 55 |
|
| 56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
app = gr.mount_gradio_app(app, interface, path="/")
|
|
|
|
| 1 |
from typing import Annotated, Dict, List, Optional, Tuple
|
| 2 |
from urllib.parse import urlencode
|
| 3 |
+
from fastapi import FastAPI, Query, Request, Response
|
| 4 |
from fastapi.middleware.cors import CORSMiddleware
|
| 5 |
+
from fastapi.responses import PlainTextResponse, RedirectResponse
|
| 6 |
import pandas as pd
|
| 7 |
+
from pydantic import Field, model_validator
|
| 8 |
from capacity_factors import get_solar_capacity_factor
|
| 9 |
from schema import CapacityFactor, Location, SolarPVAssumptions
|
| 10 |
from model import calculate_cashflow_for_renewable_project, calculate_lcoe
|
|
|
|
| 23 |
allow_headers=["*"],
|
| 24 |
)
|
| 25 |
|
| 26 |
+
@app.get("/solarpv/lcoe")
|
| 27 |
+
def get_lcoe(pv_assumptions: Annotated[SolarPVAssumptions, Query()]):
|
| 28 |
+
return calculate_lcoe(pv_assumptions)
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
class SolarPVAssumptionsWithLCOE(SolarPVAssumptions):
|
| 32 |
+
lcoe: Annotated[float, Field(title="Levelized cost of electricity (USD/MWh)", description="The levelized cost of electricity in USD/MWh")]
|
| 33 |
|
| 34 |
|
| 35 |
+
@app.get("/solarpv/lcoe.json")
|
| 36 |
+
def get_lcoe_json(
|
| 37 |
+
pv_assumptions: Annotated[SolarPVAssumptions, Query()]
|
| 38 |
+
) -> SolarPVAssumptionsWithLCOE:
|
| 39 |
+
return SolarPVAssumptionsWithLCOE(
|
| 40 |
+
**{"lcoe": calculate_lcoe(pv_assumptions), **pv_assumptions.model_dump()}
|
| 41 |
+
)
|
| 42 |
|
| 43 |
|
| 44 |
@app.get("/solarpv/capacity_factor.json")
|
|
|
|
| 49 |
)
|
| 50 |
|
| 51 |
|
| 52 |
+
class CashflowParams(SolarPVAssumptions):
|
| 53 |
+
tariff: Annotated[
|
| 54 |
+
Optional[float],
|
| 55 |
+
Query(
|
| 56 |
+
title="Tariff (USD/MWh)",
|
| 57 |
+
description="If a tariff in USD/MWh is supplied, it will be used to calculate the cashflow. If not, the break-even tariff will be calculated from the assumptions.",
|
| 58 |
+
gt=0,
|
| 59 |
+
),
|
| 60 |
+
] = None
|
| 61 |
+
transpose: Annotated[
|
| 62 |
+
bool,
|
| 63 |
+
Query(
|
| 64 |
+
title="Transpose cashflows",
|
| 65 |
+
description="Transpose the cashflow table to have years as columns and cashflows as rows",
|
| 66 |
+
default=False,
|
| 67 |
+
),
|
| 68 |
+
] = False
|
| 69 |
+
|
| 70 |
+
# If tariff is not provided, calculate it from the assumptions
|
| 71 |
+
@model_validator(mode="after")
|
| 72 |
+
@classmethod
|
| 73 |
+
def calculate_tariff(cls, values):
|
| 74 |
+
if values.tariff is None:
|
| 75 |
+
values.tariff = calculate_lcoe(values)
|
| 76 |
+
return values
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
@app.get("/solarpv/cashflow.csv", response_class=PlainTextResponse)
|
| 80 |
+
def get_cashflow(params: Annotated[CashflowParams, Query()]) -> str:
|
| 81 |
+
cashflow = calculate_cashflow_for_renewable_project(
|
| 82 |
+
params, tariff=params.tariff, return_model=True
|
| 83 |
+
)[0]
|
| 84 |
+
if params.transpose:
|
| 85 |
+
cashflow = cashflow.to_pandas().T
|
| 86 |
+
cashflow.columns = cashflow.loc["Period"].astype(int).astype(str)
|
| 87 |
+
return cashflow.drop(["Period"]).to_csv(float_format="%.3f")
|
| 88 |
+
return cashflow.write_csv(float_precision=3)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
app = gr.mount_gradio_app(app, interface, path="/")
|
model.py
CHANGED
|
@@ -280,4 +280,4 @@ def calculate_lcoe(
|
|
| 280 |
# LCOE is too low
|
| 281 |
LCOE_guess += 5
|
| 282 |
lcoe = calculate_lcoe(assumptions, LCOE_guess, iter_count=iter_count + 1)
|
| 283 |
-
return lcoe
|
|
|
|
| 280 |
# LCOE is too low
|
| 281 |
LCOE_guess += 5
|
| 282 |
lcoe = calculate_lcoe(assumptions, LCOE_guess, iter_count=iter_count + 1)
|
| 283 |
+
return float(lcoe)
|
schema.py
CHANGED
|
@@ -4,19 +4,19 @@ from pydantic import BaseModel, Field, computed_field, field_validator, model_va
|
|
| 4 |
|
| 5 |
class SolarPVAssumptions(BaseModel):
|
| 6 |
capacity_mw: Annotated[
|
| 7 |
-
float, Field(
|
| 8 |
] = 30
|
| 9 |
capacity_factor: Annotated[
|
| 10 |
float,
|
| 11 |
Field(
|
| 12 |
ge=0,
|
| 13 |
-
le=
|
| 14 |
title="Capacity factor (%)",
|
| 15 |
description="Capacity factor as a decimal, e.g., 0.2 for 20%",
|
| 16 |
),
|
| 17 |
] = 0.10
|
| 18 |
capital_expenditure_per_kw: Annotated[
|
| 19 |
-
float, Field(
|
| 20 |
] = 670
|
| 21 |
o_m_cost_pct_of_capital_cost: Annotated[
|
| 22 |
float,
|
|
@@ -245,10 +245,6 @@ class CapacityFactor(Location):
|
|
| 245 |
capacity_factor: Annotated[float, Field(
|
| 246 |
title="Capacity Factor",
|
| 247 |
description="The capacity factor at the given location",
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
def check_capacity_factor(cls, value):
|
| 252 |
-
if value < 0 or value > 0.6:
|
| 253 |
-
raise ValueError("Capacity factor must be between 0 and 60%")
|
| 254 |
-
return value
|
|
|
|
| 4 |
|
| 5 |
class SolarPVAssumptions(BaseModel):
|
| 6 |
capacity_mw: Annotated[
|
| 7 |
+
float, Field(gt=0, le=1000, title="Capacity (MW)", description="Capacity in MW")
|
| 8 |
] = 30
|
| 9 |
capacity_factor: Annotated[
|
| 10 |
float,
|
| 11 |
Field(
|
| 12 |
ge=0,
|
| 13 |
+
le=1,
|
| 14 |
title="Capacity factor (%)",
|
| 15 |
description="Capacity factor as a decimal, e.g., 0.2 for 20%",
|
| 16 |
),
|
| 17 |
] = 0.10
|
| 18 |
capital_expenditure_per_kw: Annotated[
|
| 19 |
+
float, Field(gt=0, title="Capital expenditure ($/kW)")
|
| 20 |
] = 670
|
| 21 |
o_m_cost_pct_of_capital_cost: Annotated[
|
| 22 |
float,
|
|
|
|
| 245 |
capacity_factor: Annotated[float, Field(
|
| 246 |
title="Capacity Factor",
|
| 247 |
description="The capacity factor at the given location",
|
| 248 |
+
ge=0,
|
| 249 |
+
le=1,
|
| 250 |
+
)]
|
|
|
|
|
|
|
|
|
|
|
|