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