update lcca
parent
77701e795d
commit
5350908c95
@ -0,0 +1,97 @@
|
|||||||
|
from sqlalchemy import Column, Float, Integer, String, UUID
|
||||||
|
from src.database.core import Base
|
||||||
|
from src.models import DefaultMixin, IdentityMixin
|
||||||
|
|
||||||
|
|
||||||
|
class PlantTransactionDataSimulations(Base, DefaultMixin, IdentityMixin):
|
||||||
|
__tablename__ = "lcc_plant_tr_data_simulations"
|
||||||
|
|
||||||
|
version = Column(Integer, nullable=False)
|
||||||
|
label = Column(String, nullable=False)
|
||||||
|
tahun = Column(Integer, nullable=False)
|
||||||
|
is_actual = Column(Integer, nullable=False)
|
||||||
|
seq = Column(Integer, nullable=False)
|
||||||
|
net_capacity_factor = Column(Float, nullable=True)
|
||||||
|
eaf = Column(Float, nullable=True)
|
||||||
|
production_bruto = Column(Float, nullable=True)
|
||||||
|
production_netto = Column(Float, nullable=True)
|
||||||
|
energy_sales = Column(Float, nullable=True)
|
||||||
|
fuel_consumption = Column(Float, nullable=True)
|
||||||
|
revenue_a = Column(Float, nullable=True)
|
||||||
|
revenue_b = Column(Float, nullable=True)
|
||||||
|
revenue_c = Column(Float, nullable=True)
|
||||||
|
revenue_d = Column(Float, nullable=True)
|
||||||
|
revenue_total = Column(Float, nullable=True)
|
||||||
|
revenue_pv = Column(Float, nullable=True)
|
||||||
|
revenue_annualized = Column(Float, nullable=True)
|
||||||
|
cost_a_replacement = Column(Float, nullable=True)
|
||||||
|
cost_a_pm = Column(Float, nullable=True)
|
||||||
|
cost_a_acquisition = Column(Float, nullable=True)
|
||||||
|
cost_a_pinjaman = Column(Float, nullable=True)
|
||||||
|
cost_a_depreciation = Column(Float, nullable=True)
|
||||||
|
cost_a_total = Column(Float, nullable=True)
|
||||||
|
cost_a_pv = Column(Float, nullable=True)
|
||||||
|
cost_a_annualized = Column(Float, nullable=True)
|
||||||
|
cost_c_fuel = Column(Float, nullable=True)
|
||||||
|
cost_c_pv = Column(Float, nullable=True)
|
||||||
|
cost_c_annualized = Column(Float, nullable=True)
|
||||||
|
cost_bd_om = Column(Float, nullable=True)
|
||||||
|
cost_bd_pm_nonmi = Column(Float, nullable=True)
|
||||||
|
cost_bd_bd = Column(Float, nullable=True)
|
||||||
|
cost_bd_total = Column(Float, nullable=True)
|
||||||
|
cost_bd_pv = Column(Float, nullable=True)
|
||||||
|
cost_bd_annualized = Column(Float, nullable=True)
|
||||||
|
cost_disposal_cost = Column(Float, nullable=True)
|
||||||
|
total_expense = Column(Float, nullable=True)
|
||||||
|
total_cost_eac = Column(Float, nullable=True)
|
||||||
|
total_profit_loss = Column(Float, nullable=True)
|
||||||
|
total_residual_value = Column(Float, nullable=True)
|
||||||
|
calc_depreciation = Column(Float, nullable=True)
|
||||||
|
calc_interest_payment = Column(Float, nullable=True)
|
||||||
|
calc_principal_payment = Column(Float, nullable=True)
|
||||||
|
calc_dept_amount = Column(Float, nullable=True)
|
||||||
|
calc2_ebitda = Column(Float, nullable=True)
|
||||||
|
calc2_earning_before_tax = Column(Float, nullable=True)
|
||||||
|
calc2_tax = Column(Float, nullable=True)
|
||||||
|
calc2_earning_after_tax = Column(Float, nullable=True)
|
||||||
|
calc2_nopat = Column(Float, nullable=True)
|
||||||
|
calc3_interest_after_tax = Column(Float, nullable=True)
|
||||||
|
calc3_free_cash_flow_on_project = Column(Float, nullable=True)
|
||||||
|
calc3_discounted_fcf_on_project = Column(Float, nullable=True)
|
||||||
|
calc4_principal_repayment = Column(Float, nullable=True)
|
||||||
|
calc4_free_cash_flow_on_equity = Column(Float, nullable=True)
|
||||||
|
calc4_discounted_fcf_on_equity = Column(Float, nullable=True)
|
||||||
|
chart_total_revenue = Column(Float, nullable=True)
|
||||||
|
chart_revenue_a = Column(Float, nullable=True)
|
||||||
|
chart_revenue_b = Column(Float, nullable=True)
|
||||||
|
chart_revenue_c = Column(Float, nullable=True)
|
||||||
|
chart_revenue_d = Column(Float, nullable=True)
|
||||||
|
chart_revenue_annualized = Column(Float, nullable=True)
|
||||||
|
chart_fuel_cost_component_c = Column(Float, nullable=True)
|
||||||
|
chart_fuel_cost = Column(Float, nullable=True)
|
||||||
|
chart_fuel_cost_annualized = Column(Float, nullable=True)
|
||||||
|
chart_oem_component_bd = Column(Float, nullable=True)
|
||||||
|
chart_oem_bd_cost = Column(Float, nullable=True)
|
||||||
|
chart_oem_periodic_maintenance_cost = Column(Float, nullable=True)
|
||||||
|
chart_oem_annualized = Column(Float, nullable=True)
|
||||||
|
chart_capex_component_a = Column(Float, nullable=True)
|
||||||
|
chart_capex_biaya_investasi_tambahan = Column(Float, nullable=True)
|
||||||
|
chart_capex_acquisition_cost = Column(Float, nullable=True)
|
||||||
|
chart_capex_annualized = Column(Float, nullable=True)
|
||||||
|
fs_chart_total_revenue = Column(Float, nullable=True)
|
||||||
|
fs_chart_revenue_a = Column(Float, nullable=True)
|
||||||
|
fs_chart_revenue_b = Column(Float, nullable=True)
|
||||||
|
fs_chart_revenue_c = Column(Float, nullable=True)
|
||||||
|
fs_chart_revenue_d = Column(Float, nullable=True)
|
||||||
|
fs_chart_revenue_annualized = Column(Float, nullable=True)
|
||||||
|
fs_chart_fuel_cost_component_c = Column(Float, nullable=True)
|
||||||
|
fs_chart_fuel_cost = Column(Float, nullable=True)
|
||||||
|
fs_chart_fuel_cost_annualized = Column(Float, nullable=True)
|
||||||
|
fs_chart_oem_component_bd = Column(Float, nullable=True)
|
||||||
|
fs_chart_oem_bd_cost = Column(Float, nullable=True)
|
||||||
|
fs_chart_oem_periodic_maintenance_cost = Column(Float, nullable=True)
|
||||||
|
fs_chart_oem_annualized = Column(Float, nullable=True)
|
||||||
|
fs_chart_capex_component_a = Column(Float, nullable=True)
|
||||||
|
fs_chart_capex_biaya_investasi_tambahan = Column(Float, nullable=True)
|
||||||
|
fs_chart_capex_acquisition_cost = Column(Float, nullable=True)
|
||||||
|
fs_chart_capex_annualized = Column(Float, nullable=True)
|
||||||
@ -0,0 +1,207 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
from fastapi import APIRouter, HTTPException, status, Query
|
||||||
|
|
||||||
|
from src.plant_transaction_data_simulations.model import PlantTransactionDataSimulations
|
||||||
|
from src.plant_transaction_data_simulations.schema import (
|
||||||
|
PlantTransactionDataPagination,
|
||||||
|
PlantTransactionDataRead,
|
||||||
|
PlantChartData,
|
||||||
|
PlantTransactionChart,
|
||||||
|
PlantTransactionDataCreate,
|
||||||
|
PlantTransactionDataUpdate,
|
||||||
|
PlantTransactionFSImport,
|
||||||
|
)
|
||||||
|
from src.plant_transaction_data_simulations.service import (
|
||||||
|
get,
|
||||||
|
get_all,
|
||||||
|
get_charts,
|
||||||
|
create,
|
||||||
|
update,
|
||||||
|
delete,
|
||||||
|
update_fs_charts_from_matrix,
|
||||||
|
)
|
||||||
|
|
||||||
|
from src.database.service import CommonParameters, search_filter_sort_paginate
|
||||||
|
from src.database.core import DbSession
|
||||||
|
from src.auth.service import CurrentUser
|
||||||
|
from src.models import StandardResponse
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("", response_model=StandardResponse[PlantTransactionDataPagination])
|
||||||
|
async def get_transaction_datas(
|
||||||
|
db_session: DbSession,
|
||||||
|
common: CommonParameters,
|
||||||
|
items_per_page: Optional[int] = Query(5),
|
||||||
|
search: Optional[str] = Query(None),
|
||||||
|
):
|
||||||
|
"""Get all transaction_data pagination."""
|
||||||
|
plant_transaction_data = await get_all(
|
||||||
|
db_session=db_session,
|
||||||
|
items_per_page=items_per_page,
|
||||||
|
search=search,
|
||||||
|
common=common,
|
||||||
|
)
|
||||||
|
# return
|
||||||
|
return StandardResponse(
|
||||||
|
data=plant_transaction_data,
|
||||||
|
message="Data retrieved successfully",
|
||||||
|
)
|
||||||
|
|
||||||
|
@router.get("/charts", response_model=StandardResponse[PlantChartData])
|
||||||
|
async def get_chart_data(db_session: DbSession, common: CommonParameters):
|
||||||
|
chart_data, bep_year, bep_total_lcc = await get_charts(
|
||||||
|
db_session=db_session, common=common
|
||||||
|
)
|
||||||
|
if not chart_data:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="No chart data found.",
|
||||||
|
)
|
||||||
|
return StandardResponse(
|
||||||
|
data={
|
||||||
|
"items": chart_data,
|
||||||
|
"bep_year": bep_year,
|
||||||
|
"bep_total_lcc": bep_total_lcc,
|
||||||
|
},
|
||||||
|
message="Data retrieved successfully",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/charts/fs/import",
|
||||||
|
response_model=StandardResponse[List[PlantTransactionDataRead]],
|
||||||
|
)
|
||||||
|
async def import_fs_chart_data(
|
||||||
|
db_session: DbSession,
|
||||||
|
payload: PlantTransactionFSImport,
|
||||||
|
current_user: CurrentUser,
|
||||||
|
):
|
||||||
|
updated_records, missing_years = await update_fs_charts_from_matrix(
|
||||||
|
db_session=db_session,
|
||||||
|
payload=payload,
|
||||||
|
updated_by=current_user.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not updated_records:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="No plant transaction data found for the supplied years.",
|
||||||
|
)
|
||||||
|
|
||||||
|
message = "FS chart data updated successfully"
|
||||||
|
if missing_years:
|
||||||
|
years_text = ", ".join(str(year) for year in sorted(missing_years))
|
||||||
|
message += f"; missing years: {years_text}"
|
||||||
|
|
||||||
|
return StandardResponse(data=updated_records, message=message)
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/{transaction_data_id}", response_model=StandardResponse[PlantTransactionDataRead]
|
||||||
|
)
|
||||||
|
async def get_transaction_data(db_session: DbSession, transaction_data_id: str):
|
||||||
|
transaction_data = await get(
|
||||||
|
db_session=db_session, transaction_data_id=transaction_data_id
|
||||||
|
)
|
||||||
|
if not transaction_data:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="A data with this id does not exist.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return StandardResponse(
|
||||||
|
data=transaction_data, message="Data retrieved successfully"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("", response_model=StandardResponse[PlantTransactionDataRead])
|
||||||
|
async def create_transaction_data(
|
||||||
|
db_session: DbSession,
|
||||||
|
transaction_data_in: PlantTransactionDataCreate,
|
||||||
|
current_user: CurrentUser,
|
||||||
|
):
|
||||||
|
transaction_data_in.created_by = current_user.name
|
||||||
|
transaction_data = await create(
|
||||||
|
db_session=db_session, transaction_data_in=transaction_data_in
|
||||||
|
)
|
||||||
|
|
||||||
|
return StandardResponse(data=transaction_data, message="Data created successfully")
|
||||||
|
|
||||||
|
@router.put(
|
||||||
|
"/bulk", response_model=StandardResponse[List[PlantTransactionDataRead]]
|
||||||
|
)
|
||||||
|
async def bulk_update_transaction_data(
|
||||||
|
db_session: DbSession,
|
||||||
|
ids: List[str],
|
||||||
|
updates: List[PlantTransactionDataUpdate],
|
||||||
|
current_user: CurrentUser,
|
||||||
|
):
|
||||||
|
if len(ids) != len(updates):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="The number of IDs must match the number of update objects.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set updated_by for each update object
|
||||||
|
for update_obj in updates:
|
||||||
|
update_obj.updated_by = current_user.name
|
||||||
|
|
||||||
|
updated_records = await update(
|
||||||
|
db_session=db_session,
|
||||||
|
ids=ids,
|
||||||
|
updates=updates,
|
||||||
|
)
|
||||||
|
|
||||||
|
return StandardResponse(
|
||||||
|
data=updated_records,
|
||||||
|
message="Bulk update completed successfully",
|
||||||
|
)
|
||||||
|
|
||||||
|
@router.put(
|
||||||
|
"/{transaction_data_id}", response_model=StandardResponse[PlantTransactionDataRead]
|
||||||
|
)
|
||||||
|
async def update_transaction_data(
|
||||||
|
db_session: DbSession,
|
||||||
|
transaction_data_id: str,
|
||||||
|
transaction_data_in: PlantTransactionDataUpdate,
|
||||||
|
current_user: CurrentUser,
|
||||||
|
):
|
||||||
|
transaction_data = await get(
|
||||||
|
db_session=db_session, transaction_data_id=transaction_data_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if not transaction_data:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="A data with this id does not exist.",
|
||||||
|
)
|
||||||
|
transaction_data_in.updated_by = current_user.name
|
||||||
|
|
||||||
|
return StandardResponse(
|
||||||
|
data=await update(
|
||||||
|
db_session=db_session,
|
||||||
|
transaction_data=transaction_data,
|
||||||
|
transaction_data_in=transaction_data_in,
|
||||||
|
),
|
||||||
|
message="Data updated successfully",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete(
|
||||||
|
"/{transaction_data_id}", response_model=StandardResponse[PlantTransactionDataRead]
|
||||||
|
)
|
||||||
|
async def delete_transaction_data(db_session: DbSession, transaction_data_id: str):
|
||||||
|
transaction_data = await get(
|
||||||
|
db_session=db_session, transaction_data_id=transaction_data_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if not transaction_data:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=[{"msg": "A data with this id does not exist."}],
|
||||||
|
)
|
||||||
|
|
||||||
|
await delete(db_session=db_session, transaction_data_id=transaction_data_id)
|
||||||
|
|
||||||
|
return StandardResponse(message="Data deleted successfully", data=transaction_data)
|
||||||
@ -0,0 +1,161 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Any, List, Optional
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from pydantic import Field
|
||||||
|
from src.models import DefaultBase, Pagination
|
||||||
|
|
||||||
|
|
||||||
|
class PlantTransactionDataSimulationsBase(DefaultBase):
|
||||||
|
version: Optional[int] = Field(None, nullable=True, ge=0, le=9_999_999_999)
|
||||||
|
label: Optional[str] = Field(None, nullable=True)
|
||||||
|
tahun: Optional[int] = Field(None, nullable=True, ge=1900, le=9999)
|
||||||
|
is_actual: Optional[int] = Field(None, nullable=True, ge=0, le=1)
|
||||||
|
seq: Optional[int] = Field(None, nullable=True, ge=0, le=9999)
|
||||||
|
net_capacity_factor: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
eaf: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
production_bruto: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
production_netto: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
energy_sales: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fuel_consumption: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
revenue_a: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
revenue_b: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
revenue_c: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
revenue_d: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
revenue_total: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
revenue_pv: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
revenue_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_a_replacement: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_a_pm: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_a_acquisition: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_a_pinjaman: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_a_depreciation: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_a_total: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_a_pv: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_a_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_c_fuel: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_c_pv: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_c_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_bd_om: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_bd_pm_nonmi: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_bd_bd: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_bd_total: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_bd_pv: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_bd_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_disposal_cost: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
total_expense: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
total_cost_eac: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
total_profit_loss: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
total_residual_value: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
total_profit_loss: Optional[float] = Field(None, nullable=True)
|
||||||
|
total_residual_value: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
calc_depreciation: Optional[float] = Field(None, nullable=True)
|
||||||
|
calc_interest_payment: Optional[float] = Field(None, nullable=True)
|
||||||
|
calc_principal_payment: Optional[float] = Field(None, nullable=True)
|
||||||
|
calc_dept_amount: Optional[float] = Field(None, nullable=True)
|
||||||
|
calc2_ebitda: Optional[float] = Field(None, nullable=True)
|
||||||
|
calc2_earning_before_tax: Optional[float] = Field(None, nullable=True)
|
||||||
|
calc2_tax: Optional[float] = Field(None, nullable=True)
|
||||||
|
calc2_earning_after_tax: Optional[float] = Field(None, nullable=True)
|
||||||
|
calc2_nopat: Optional[float] = Field(None, nullable=True)
|
||||||
|
calc3_interest_after_tax: Optional[float] = Field(None, nullable=True)
|
||||||
|
calc3_free_cash_flow_on_project: Optional[float] = Field(None, nullable=True)
|
||||||
|
calc3_discounted_fcf_on_project: Optional[float] = Field(None, nullable=True)
|
||||||
|
calc4_principal_repayment: Optional[float] = Field(None, nullable=True)
|
||||||
|
calc4_free_cash_flow_on_equity: Optional[float] = Field(None, nullable=True)
|
||||||
|
calc4_discounted_fcf_on_equity: Optional[float] = Field(None, nullable=True)
|
||||||
|
created_at: Optional[datetime] = Field(None, nullable=True)
|
||||||
|
updated_at: Optional[datetime] = Field(None, nullable=True)
|
||||||
|
created_by: Optional[str] = Field(None, nullable=True)
|
||||||
|
updated_by: Optional[str] = Field(None, nullable=True)
|
||||||
|
cost_disposal_cost: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_total_revenue: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_revenue_a: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_revenue_b: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_revenue_c: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_revenue_d: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_revenue_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_fuel_cost_component_c: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_fuel_cost: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_fuel_cost_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_oem_component_bd: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_oem_bd_cost: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_oem_periodic_maintenance_cost: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_oem_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_capex_component_a: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_capex_biaya_investasi_tambahan: Optional[float] = Field(
|
||||||
|
None, nullable=True, ge=0, le=1_000_000_000_000_000
|
||||||
|
)
|
||||||
|
fs_chart_capex_acquisition_cost: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_capex_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
|
||||||
|
|
||||||
|
class PlantTransactionChartSimulations(PlantTransactionDataSimulationsBase):
|
||||||
|
tahun: Optional[int] = Field(None, nullable=True, ge=0, le=9999)
|
||||||
|
is_actual: Optional[int] = Field(None, nullable=True, ge=0, le=1)
|
||||||
|
seq: Optional[int] = Field(None, nullable=True, ge=0, le=9999)
|
||||||
|
chart_total_revenue: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_revenue_a: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_revenue_b: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_revenue_c: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_revenue_d: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_revenue_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_fuel_cost_component_c: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_fuel_cost: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_fuel_cost_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_oem_component_bd: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_oem_bd_cost: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_oem_periodic_maintenance_cost: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_oem_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_capex_component_a: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_capex_biaya_investasi_tambahan: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_capex_acquisition_cost: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
chart_capex_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
cost_disposal_cost: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_total_revenue: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_revenue_a: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_revenue_b: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_revenue_c: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_revenue_d: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_revenue_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_fuel_cost_component_c: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_fuel_cost: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_fuel_cost_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_oem_component_bd: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_oem_bd_cost: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_oem_periodic_maintenance_cost: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_oem_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_capex_component_a: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_capex_biaya_investasi_tambahan: Optional[float] = Field(
|
||||||
|
None, nullable=True, ge=0, le=1_000_000_000_000_000
|
||||||
|
)
|
||||||
|
fs_chart_capex_acquisition_cost: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
fs_chart_capex_annualized: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
|
||||||
|
|
||||||
|
class PlantChartDataSimulations(DefaultBase):
|
||||||
|
items: List[PlantTransactionChartSimulations]
|
||||||
|
bep_year: Optional[int] = Field(int, nullable=True, ge=0, le=9999)
|
||||||
|
bep_total_lcc: Optional[float] = Field(float, nullable=True, ge=0, le=1_000_000_000_000_000)
|
||||||
|
|
||||||
|
|
||||||
|
class PlantTransactionFSImportSimulations(DefaultBase):
|
||||||
|
data: List[List[Optional[Any]]]
|
||||||
|
is_actual: Optional[int] = Field(None, nullable=True, ge=0, le=1)
|
||||||
|
seq: Optional[int] = Field(None, nullable=True, ge=0, le=9999)
|
||||||
|
|
||||||
|
|
||||||
|
class PlantTransactionDataSimulationsCreate(PlantTransactionDataSimulationsBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PlantTransactionDataSimulationsUpdate(PlantTransactionDataSimulationsBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PlantTransactionDataSimulationsRead(PlantTransactionDataSimulationsBase):
|
||||||
|
id: UUID
|
||||||
|
|
||||||
|
|
||||||
|
class PlantTransactionDataSimulationsPagination(Pagination):
|
||||||
|
items: List[PlantTransactionDataSimulationsRead] = []
|
||||||
@ -0,0 +1,331 @@
|
|||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from subprocess import PIPE
|
||||||
|
|
||||||
|
from sqlalchemy import Select, Delete, cast, String
|
||||||
|
from src.plant_transaction_data_simulations.model import PlantTransactionDataSimulations
|
||||||
|
from src.plant_transaction_data_simulations.schema import (
|
||||||
|
PlantTransactionDataSimulationsCreate,
|
||||||
|
PlantTransactionDataSimulationsUpdate,
|
||||||
|
PlantTransactionFSImportSimulations,
|
||||||
|
)
|
||||||
|
from src.database.service import search_filter_sort_paginate
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from src.database.core import DbSession
|
||||||
|
from src.auth.service import CurrentUser
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_float(x: object) -> float:
|
||||||
|
"""Safely convert `x` to float, returning 0.0 for None or invalid values."""
|
||||||
|
try:
|
||||||
|
if x is None:
|
||||||
|
return 0.0
|
||||||
|
return float(x)
|
||||||
|
except Exception:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
|
_FS_LABEL_FIELD_MAP: Dict[str, str] = {
|
||||||
|
"Total Revenue": "fs_chart_total_revenue",
|
||||||
|
"Revenue A": "fs_chart_revenue_a",
|
||||||
|
"Revenue B": "fs_chart_revenue_b",
|
||||||
|
"Revenue C": "fs_chart_revenue_c",
|
||||||
|
"Revenue D": "fs_chart_revenue_d",
|
||||||
|
"Revenue Annualized": "fs_chart_revenue_annualized",
|
||||||
|
"Fuel Cost (Component C)": "fs_chart_fuel_cost_component_c",
|
||||||
|
"Fuel Cost": "fs_chart_fuel_cost",
|
||||||
|
"Fuel Cost Annualized": "fs_chart_fuel_cost_annualized",
|
||||||
|
"O and M Cost (Component B and D)": "fs_chart_oem_component_bd",
|
||||||
|
"O and M Cost": "fs_chart_oem_bd_cost",
|
||||||
|
"Periodic Maintenance Cost (NonMI)": "fs_chart_oem_periodic_maintenance_cost",
|
||||||
|
"O and M Cost Annualized": "fs_chart_oem_annualized",
|
||||||
|
"Capex (Component A)": "fs_chart_capex_component_a",
|
||||||
|
"Biaya Investasi Tambahan": "fs_chart_capex_biaya_investasi_tambahan",
|
||||||
|
"Acquisition Cost": "fs_chart_capex_acquisition_cost",
|
||||||
|
"Capex Annualized": "fs_chart_capex_annualized",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_years(header_row: List[Any]) -> List[int]:
|
||||||
|
years: List[int] = []
|
||||||
|
for cell in header_row[2:]:
|
||||||
|
if cell is None:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
years.append(int(float(cell)))
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return years
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_label(row: List[Any]) -> Optional[str]:
|
||||||
|
for candidate in row[:2]:
|
||||||
|
if isinstance(candidate, str):
|
||||||
|
label = candidate.strip()
|
||||||
|
if label:
|
||||||
|
return label
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_fs_year_value_map(matrix: List[List[Any]]) -> Dict[int, Dict[str, float]]:
|
||||||
|
if not matrix:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
header = matrix[0]
|
||||||
|
years = _extract_years(header)
|
||||||
|
if not years:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
year_map: Dict[int, Dict[str, float]] = {year: {} for year in years}
|
||||||
|
|
||||||
|
for row in matrix[1:]:
|
||||||
|
label = _resolve_label(row)
|
||||||
|
if not label:
|
||||||
|
continue
|
||||||
|
|
||||||
|
field_name = _FS_LABEL_FIELD_MAP.get(label)
|
||||||
|
if not field_name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for idx, year in enumerate(years):
|
||||||
|
col_idx = idx + 2
|
||||||
|
if col_idx >= len(row):
|
||||||
|
continue
|
||||||
|
value = row[col_idx]
|
||||||
|
if value is None:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
year_map[year][field_name] = _safe_float(value)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return year_map
|
||||||
|
|
||||||
|
|
||||||
|
async def get(
|
||||||
|
*, db_session: DbSession, transaction_data_id: str
|
||||||
|
) -> Optional[PlantTransactionDataSimulations]:
|
||||||
|
"""Returns a document based on the given document id."""
|
||||||
|
query = Select(PlantTransactionDataSimulations).filter(
|
||||||
|
PlantTransactionDataSimulations.id == transaction_data_id
|
||||||
|
)
|
||||||
|
result = await db_session.execute(query)
|
||||||
|
return result.scalars().one_or_none()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_all(
|
||||||
|
*,
|
||||||
|
db_session: DbSession,
|
||||||
|
items_per_page: Optional[int],
|
||||||
|
search: Optional[str] = None,
|
||||||
|
common,
|
||||||
|
):
|
||||||
|
"""Returns all documents."""
|
||||||
|
query = Select(PlantTransactionDataSimulations).order_by(
|
||||||
|
PlantTransactionDataSimulations.seq.asc(), PlantTransactionDataSimulations.tahun.asc()
|
||||||
|
)
|
||||||
|
if search:
|
||||||
|
query = query.filter(
|
||||||
|
cast(PlantTransactionDataSimulations.tahun, String).ilike(f"%{search}%")
|
||||||
|
)
|
||||||
|
|
||||||
|
common["items_per_page"] = items_per_page
|
||||||
|
results = await search_filter_sort_paginate(model=query, **common)
|
||||||
|
|
||||||
|
# return results.scalars().all()
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
async def get_charts(
|
||||||
|
*,
|
||||||
|
db_session: DbSession,
|
||||||
|
common,
|
||||||
|
):
|
||||||
|
"""Returns all documents."""
|
||||||
|
query = Select(PlantTransactionDataSimulations).order_by(PlantTransactionDataSimulations.tahun.asc())
|
||||||
|
results = await db_session.execute(query)
|
||||||
|
|
||||||
|
chart_data = results.scalars().all()
|
||||||
|
bep_year = None
|
||||||
|
previous_year = None
|
||||||
|
previous_total_cost = None
|
||||||
|
previous_revenue = None
|
||||||
|
bep_total_lcc = 0
|
||||||
|
|
||||||
|
for idx, item in enumerate(chart_data):
|
||||||
|
total_cost = (
|
||||||
|
_safe_float(item.chart_capex_annualized)
|
||||||
|
+ _safe_float(item.chart_oem_annualized)
|
||||||
|
+ _safe_float(item.chart_fuel_cost_annualized)
|
||||||
|
)
|
||||||
|
revenue = _safe_float(item.chart_revenue_annualized)
|
||||||
|
|
||||||
|
if previous_total_cost is not None and previous_revenue is not None:
|
||||||
|
prev_diff = previous_total_cost - previous_revenue
|
||||||
|
curr_diff = total_cost - revenue
|
||||||
|
|
||||||
|
# If signs differ there's a crossing between previous and current point
|
||||||
|
if prev_diff == 0:
|
||||||
|
bep_year = previous_year
|
||||||
|
bep_total_lcc = previous_total_cost
|
||||||
|
break
|
||||||
|
|
||||||
|
if prev_diff * curr_diff < 0:
|
||||||
|
# Interpolate linearly between the two years to estimate BEP year
|
||||||
|
denom = ( (total_cost - previous_total_cost) - (revenue - previous_revenue) )
|
||||||
|
if denom != 0:
|
||||||
|
t = (previous_revenue - previous_total_cost) / denom
|
||||||
|
# clamp t to [0,1]
|
||||||
|
t = max(0.0, min(1.0, t))
|
||||||
|
try:
|
||||||
|
bep_year = previous_year + t * (item.tahun - previous_year)
|
||||||
|
except Exception:
|
||||||
|
bep_year = previous_year
|
||||||
|
bep_total_lcc = previous_total_cost + t * (total_cost - previous_total_cost)
|
||||||
|
else:
|
||||||
|
# fallback if interpolation is not possible
|
||||||
|
if total_cost < revenue:
|
||||||
|
bep_total_lcc = previous_total_cost
|
||||||
|
bep_year = previous_year
|
||||||
|
else:
|
||||||
|
bep_total_lcc = total_cost
|
||||||
|
bep_year = item.tahun
|
||||||
|
break
|
||||||
|
|
||||||
|
previous_total_cost = total_cost
|
||||||
|
previous_revenue = revenue
|
||||||
|
previous_year = item.tahun
|
||||||
|
|
||||||
|
return chart_data, int(bep_year) if bep_year is not None else None, bep_total_lcc
|
||||||
|
|
||||||
|
|
||||||
|
async def create(
|
||||||
|
*, db_session: DbSession, transaction_data_in: PlantTransactionDataSimulationsCreate
|
||||||
|
):
|
||||||
|
"""Creates a new document."""
|
||||||
|
transaction_data = PlantTransactionDataSimulations(**transaction_data_in.model_dump())
|
||||||
|
db_session.add(transaction_data)
|
||||||
|
await db_session.commit()
|
||||||
|
|
||||||
|
# Get the directory of the current file
|
||||||
|
# directory_path = "../modules/plant"
|
||||||
|
directory_path = os.path.abspath(
|
||||||
|
os.path.join(os.path.dirname(__file__), "../modules/plant")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Construct path to the script
|
||||||
|
script_path = os.path.join(directory_path, "run2.py")
|
||||||
|
|
||||||
|
try:
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
"python", script_path, stdout=PIPE, stderr=PIPE, cwd=directory_path
|
||||||
|
)
|
||||||
|
stdout, stderr = await process.communicate()
|
||||||
|
|
||||||
|
# Check if the script executed successfully
|
||||||
|
if process.returncode != 0:
|
||||||
|
print(f"Script execution error: {stderr.decode()}")
|
||||||
|
else:
|
||||||
|
print(f"Script output: {stdout.decode()}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error executing script: {e}")
|
||||||
|
|
||||||
|
return transaction_data
|
||||||
|
|
||||||
|
|
||||||
|
async def update(
|
||||||
|
*,
|
||||||
|
db_session: DbSession,
|
||||||
|
transaction_data: PlantTransactionDataSimulations,
|
||||||
|
transaction_data_in: PlantTransactionDataSimulationsUpdate,
|
||||||
|
):
|
||||||
|
"""Updates a document."""
|
||||||
|
data = transaction_data_in.model_dump()
|
||||||
|
|
||||||
|
update_data = transaction_data_in.model_dump(exclude_defaults=True)
|
||||||
|
|
||||||
|
for field in data:
|
||||||
|
if field in update_data:
|
||||||
|
setattr(transaction_data, field, update_data[field])
|
||||||
|
|
||||||
|
await db_session.commit()
|
||||||
|
|
||||||
|
# Get the directory of the current file
|
||||||
|
# directory_path = "../modules/plant"
|
||||||
|
directory_path = os.path.abspath(
|
||||||
|
os.path.join(os.path.dirname(__file__), "../modules/plant")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Construct path to the script
|
||||||
|
script_path = os.path.join(directory_path, "run2.py")
|
||||||
|
|
||||||
|
try:
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
"python", script_path, stdout=PIPE, stderr=PIPE, cwd=directory_path
|
||||||
|
)
|
||||||
|
stdout, stderr = await process.communicate()
|
||||||
|
|
||||||
|
# Check if the script executed successfully
|
||||||
|
if process.returncode != 0:
|
||||||
|
print(f"Script execution error: {stderr.decode()}")
|
||||||
|
else:
|
||||||
|
print(f"Script output: {stdout.decode()}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error executing script: {e}")
|
||||||
|
|
||||||
|
return transaction_data
|
||||||
|
|
||||||
|
|
||||||
|
async def update_fs_charts_from_matrix(
|
||||||
|
*,
|
||||||
|
db_session: DbSession,
|
||||||
|
payload: PlantTransactionFSImportSimulations,
|
||||||
|
updated_by: Optional[str] = None,
|
||||||
|
):
|
||||||
|
"""Update fs_* chart columns based on a transposed matrix payload."""
|
||||||
|
|
||||||
|
year_value_map = _build_fs_year_value_map(payload.data)
|
||||||
|
if not year_value_map:
|
||||||
|
return [], []
|
||||||
|
|
||||||
|
updated_records: List[PlantTransactionDataSimulations] = []
|
||||||
|
missing_years: List[int] = []
|
||||||
|
|
||||||
|
for year, field_values in year_value_map.items():
|
||||||
|
if not field_values:
|
||||||
|
continue
|
||||||
|
|
||||||
|
query = Select(PlantTransactionDataSimulations).where(PlantTransactionDataSimulations.tahun == year)
|
||||||
|
if payload.is_actual is not None:
|
||||||
|
query = query.where(PlantTransactionDataSimulations.is_actual == payload.is_actual)
|
||||||
|
if payload.seq is not None:
|
||||||
|
query = query.where(PlantTransactionDataSimulations.seq == payload.seq)
|
||||||
|
|
||||||
|
result = await db_session.execute(query)
|
||||||
|
records = result.scalars().all()
|
||||||
|
if not records:
|
||||||
|
missing_years.append(year)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for record in records:
|
||||||
|
for field_name, value in field_values.items():
|
||||||
|
setattr(record, field_name, value)
|
||||||
|
if updated_by:
|
||||||
|
record.updated_by = updated_by
|
||||||
|
updated_records.append(record)
|
||||||
|
|
||||||
|
await db_session.commit()
|
||||||
|
return updated_records, missing_years
|
||||||
|
|
||||||
|
|
||||||
|
async def delete(*, db_session: DbSession, transaction_data_id: str):
|
||||||
|
"""Deletes a document."""
|
||||||
|
query = Delete(PlantTransactionDataSimulations).where(
|
||||||
|
PlantTransactionDataSimulations.id == transaction_data_id
|
||||||
|
)
|
||||||
|
await db_session.execute(query)
|
||||||
|
await db_session.commit()
|
||||||
Loading…
Reference in New Issue