feat: Introduce chart data retrieval with Break-Even Point calculation and update transaction ID type to UUID.

main
MrWaradana 2 weeks ago
parent 55e41abaa3
commit d84bd0f6ef

@ -1,4 +1,5 @@
from typing import Optional from typing import List, Optional
from uuid import UUID
from fastapi import APIRouter, HTTPException, Query, status from fastapi import APIRouter, HTTPException, Query, status
@ -11,10 +12,12 @@ from .schema import (
PlantFSTransactionDataCreate, PlantFSTransactionDataCreate,
PlantFSTransactionDataImport, PlantFSTransactionDataImport,
PlantFSTransactionDataPagination, PlantFSTransactionDataPagination,
PlantFSTransactionDataRead, PlantFSTransactionDataRead,
PlantFSTransactionDataUpdate, PlantFSTransactionDataUpdate,
PlantFSChartData,
) )
from .service import create, delete, get, get_all, update, update_fs_charts_from_matrix from .service import create, delete, get, get_all, update, update_fs_charts_from_matrix, get_charts
from typing import List from typing import List
@ -43,15 +46,57 @@ async def list_fs_transactions(
) )
@router.post(
"/import/charts",
response_model=StandardResponse[List[PlantFSTransactionDataRead]],
)
async def import_fs_charts(
db_session: DbSession,
payload: PlantFSTransactionDataImport,
current_user: CurrentUser,
):
updated, missing = await update_fs_charts_from_matrix(
db_session=db_session,
payload=payload,
updated_by=getattr(current_user, "user_id", None) if current_user else None,
)
msg = "Data imported successfully."
if missing:
msg += f" Note: Years {missing} were not found."
return StandardResponse(data=updated, message=msg)
@router.get("/charts", response_model=StandardResponse[PlantFSChartData])
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.get( @router.get(
"/{fs_transaction_id}", "/{fs_transaction_id}",
response_model=StandardResponse[PlantFSTransactionDataRead], response_model=StandardResponse[PlantFSTransactionDataRead],
) )
async def retrieve_fs_transaction( async def retrieve_fs_transaction(
db_session: DbSession, db_session: DbSession,
fs_transaction_id: str, fs_transaction_id: UUID,
): ):
record = await get(db_session=db_session, fs_transaction_id=fs_transaction_id) record = await get(db_session=db_session, fs_transaction_id=str(fs_transaction_id))
if not record: if not record:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
@ -82,11 +127,11 @@ async def create_fs_transaction(
) )
async def update_fs_transaction( async def update_fs_transaction(
db_session: DbSession, db_session: DbSession,
fs_transaction_id: str, fs_transaction_id: UUID,
payload: PlantFSTransactionDataUpdate, payload: PlantFSTransactionDataUpdate,
current_user: CurrentUser, current_user: CurrentUser,
): ):
record = await get(db_session=db_session, fs_transaction_id=fs_transaction_id) record = await get(db_session=db_session, fs_transaction_id=str(fs_transaction_id))
if not record: if not record:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
@ -109,39 +154,20 @@ async def update_fs_transaction(
) )
async def delete_fs_transaction( async def delete_fs_transaction(
db_session: DbSession, db_session: DbSession,
fs_transaction_id: str, fs_transaction_id: UUID,
): ):
record = await get(db_session=db_session, fs_transaction_id=fs_transaction_id) record = await get(db_session=db_session, fs_transaction_id=str(fs_transaction_id))
if not record: if not record:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=[{"msg": "A data with this id does not exist."}], detail=[{"msg": "A data with this id does not exist."}],
) )
await delete(db_session=db_session, fs_transaction_id=fs_transaction_id) await delete(db_session=db_session, fs_transaction_id=str(fs_transaction_id))
return StandardResponse(data=record, message="Data deleted successfully") return StandardResponse(data=record, message="Data deleted successfully")
@router.post(
"/import/charts",
response_model=StandardResponse[List[PlantFSTransactionDataRead]],
)
async def import_fs_charts(
db_session: DbSession,
payload: PlantFSTransactionDataImport,
current_user: CurrentUser,
):
updated, missing = await update_fs_charts_from_matrix(
db_session=db_session,
payload=payload,
updated_by=getattr(current_user, "user_id", None) if current_user else None,
)
msg = "Data imported successfully."
if missing:
msg += f" Note: Years {missing} were not found."
return StandardResponse(data=updated, message=msg)

@ -89,3 +89,14 @@ class PlantFSTransactionDataPagination(Pagination):
class PlantFSTransactionDataImport(DefaultBase): class PlantFSTransactionDataImport(DefaultBase):
data: List[List[Any]] data: List[List[Any]]
seq: Optional[int] = None seq: Optional[int] = None
class PlantFSTransactionChart(PlantFSTransactionDataBase):
pass
class PlantFSChartData(DefaultBase):
items: List[PlantFSTransactionChart]
bep_year: Optional[int] = Field(None, nullable=True, ge=0, le=9999)
bep_total_lcc: Optional[float] = Field(None, nullable=True, ge=0, le=1_000_000_000_000_000)

@ -244,3 +244,66 @@ async def update_fs_charts_from_matrix(
await db_session.commit() await db_session.commit()
return updated_records, missing_years return updated_records, missing_years
async def get_charts(
*,
db_session: DbSession,
common,
):
"""Returns all documents."""
query = Select(PlantFSTransactionData).order_by(PlantFSTransactionData.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.fs_chart_capex_annualized)
+ _safe_float(item.fs_chart_oem_annualized)
+ _safe_float(item.fs_chart_fuel_cost_annualized)
)
revenue = _safe_float(item.fs_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

Loading…
Cancel
Save