update plant simulation

main
MrWaradana 2 months ago
parent 78ef25d708
commit effe624bd4

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,816 @@
import os
import sys
from typing import Dict, List, Any
# Pastikan bisa import get_connection seperti run2.py
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from config import get_connection # type: ignore
from openpyxl import Workbook
from openpyxl.utils import get_column_letter
from openpyxl.styles import PatternFill
from dataclasses import dataclass
@dataclass
class Param:
value: Any
description: Any
unit: Any
# Mapping nama kolom di DB -> label yang tampil di Excel (Sheet Results & Chart)
# LENGKAPI SENDIRI SESUAI KEBUTUHAN
LABEL_MAP: Dict[str, str] = {
"net_capacity_factor": "Net Capacity Factor",
"eaf": "Equivalent Availability Factor (EAF)",
"production_bruto": "Production Bruto",
"production_netto": "Production Netto",
"energy_sales": "Energy Sales",
"fuel_consumption": "Fuel Consumption",
"revenue_a": "Revenue A",
"revenue_b": "Revenue B",
"revenue_c": "Revenue C",
"revenue_d": "Revenue D",
"revenue_total": "Total Revenue",
"revenue_pv": "Total Revenue PV",
"revenue_annualized": "Annualized Revenue",
"cost_a_replacement": "Cost A - Replacement",
"cost_a_pm": "Cost A - Periodic Maintenance (MI)",
# "cost_a_pinjaman": "Cost A - Loan Cost",
# "cost_a_depreciation": "Cost A - Depreciation",
"cost_a_acquisition": "Cost A - Acquisition",
"cost_a_pv": "Cost A (PV)",
"cost_a_annualized": "Cost A (EAC)",
"cost_c_fuel": "Cost C - Fuel",
"cost_c_pv": "Cost C (PV)",
"cost_c_annualized": "Cost C (EAC)",
"cost_bd_om": "Cost BD - Cost O&M",
"cost_bd_pm_nonmi": "Cost BD - Periodic Maintenance (Non MI)",
"cost_bd_bd": "Cost BD - Cost BD",
"cost_bd_total": "Cost BD - Total",
"cost_bd_pv": "Cost BD (PV)",
"cost_bd_annualized": "Cost BD (EAC)",
"total_expense": "Total Expense",
"total_cost_eac": "Total Cost (EAC)",
"total_profit_loss": "Total Profit/Loss",
"total_residual_value": "Total Residual Value",
"calc_depreciation": "Calculated Depreciation",
"calc_interest_payment": "Interest Payment",
"calc_principal_payment": "Principal Payment",
"calc_dept_amount": "Debt Amount",
"calc2_ebitda": "EBITDA",
"calc2_earning_before_tax": "Earning Before Tax",
"calc2_tax": "Tax",
"calc2_earning_after_tax": "Earning After Tax",
"calc2_nopat": "NOPAT",
"calc3_interest_after_tax": "Interest After Tax",
"calc3_free_cash_flow_on_project": "Free Cash Flow On Project",
"calc3_discounted_fcf_on_project": "Discounted FCF On Project",
"calc4_principal_repayment": "Principal Repayment (Equity)",
"calc4_free_cash_flow_on_equity": "Free Cash Flow On Equity",
"calc4_discounted_fcf_on_equity": "Discounted FCF On Equity",
# Chart breakdowns (boleh diubah sesuai selera)
"chart_total_revenue": "Chart - Total Revenue",
"chart_revenue_a": "Chart - Revenue A",
"chart_revenue_b": "Chart - Revenue B",
"chart_revenue_c": "Chart - Revenue C",
"chart_revenue_d": "Chart - Revenue D",
"chart_revenue_annualized": "Chart - Annualized Revenue",
"chart_fuel_cost_component_c": "Chart - Fuel Cost Component C",
"chart_fuel_cost": "Chart - Fuel Cost",
"chart_fuel_cost_annualized": "Chart - Annualized Fuel Cost",
"chart_oem_component_bd": "Chart - O&M Component BD",
"chart_oem_bd_cost": "Chart - O&M BD Cost",
"chart_oem_periodic_maintenance_cost": "Chart - Periodic Maintenance Cost",
"chart_oem_annualized": "Chart - Annualized O&M Cost",
"chart_capex_component_a": "Chart - Capex Component A",
"chart_capex_biaya_investasi_tambahan": "Chart - Additional Investment Cost",
"chart_capex_acquisition_cost": "Chart - Capex Acquisition Cost",
"chart_capex_annualized": "Chart - Annualized Capex",
}
def get_label(name: str) -> str:
"""Ambil label manusi dari LABEL_MAP, fallback ke nama aslinya kalau belum di-mapping."""
return LABEL_MAP.get(name, name)
def fetch_param_map(cur) -> Dict[str, Param]:
cur.execute(
"""
SELECT name, value_num AS value, description, unit_of_measurement
FROM lcc_ms_master
WHERE name IN (
'auxiliary','corporate_tax_rate','daya_mampu_netto','discount_rate',
'electricity_price_a','electricity_price_b','electricity_price_c','electricity_price_d',
'equity','equity_portion','harga_bahan_bakar','interest_rate','loan','loan_portion',
'principal_interest_payment','sfc','susut_trafo','total_project_cost','umur_teknis',
'wacc_on_equity','wacc_on_project','calc_on_equity_irr','calc_on_equity_npv',
'calc_on_project_irr','calc_on_project_npv','calc_roa_all','calc_roa_current'
)
ORDER BY name
"""
)
rows = cur.fetchall()
return {
name: Param(value=value, description=description, unit=unit_of_measurement)
for (name, value, description, unit_of_measurement) in rows
}
def export_to_excel(output_path: str):
conn = get_connection()
if conn is None:
raise RuntimeError("Koneksi ke database gagal.")
try:
cur = conn.cursor()
# 1) Ambil semua parameter sebagai param_map
param_map = fetch_param_map(cur)
# Ambil tahun_cod jika nanti butuh hitung tahun saat kolom 'tahun' tidak ada
tahun_cod = None
try:
tahun_cod = float(param_map["tahun_cod"].value)
except Exception:
tahun_cod = None
# 2) Ambil data hasil perhitungan (mengikuti alur run2.py -> dari lcc_plant_tr_data)
cur.execute(
"""
SELECT *
FROM lcc_plant_tr_data
ORDER BY seq
"""
)
col_names = [d[0] for d in cur.description]
rows = cur.fetchall()
# Siapkan list tahun dan seq untuk header
seq_list: List[Any] = []
tahun_list: List[Any] = []
is_actual_list: List[Any] = []
# Deteksi apakah ada kolom 'tahun'
ada_kolom_tahun = "tahun" in col_names
# Kumpulan nama kolom hasil perhitungan (mengacu pada kolom yang di-UPDATE di run2.py)
calc_columns = [
# produksi dan penjualan
"net_capacity_factor", "eaf", "production_bruto", "production_netto",
"energy_sales", "fuel_consumption",
# revenue
"revenue_a", "revenue_b", "revenue_c", "revenue_d", "revenue_total",
"revenue_pv", "revenue_annualized",
# cost A (capex related)
"cost_a_replacement", "cost_a_pm",
# "cost_a_pinjaman",
# "cost_a_depreciation",
"cost_a_acquisition", "cost_a_pv", "cost_a_annualized",
# cost C (fuel)
"cost_c_fuel", "cost_c_pv", "cost_c_annualized",
# cost BD (O&M)
"cost_bd_om", "cost_bd_pm_nonmi", "cost_bd_bd", "cost_bd_total",
"cost_bd_pv", "cost_bd_annualized",
# totals
"total_expense", "total_cost_eac", "total_profit_loss", "total_residual_value",
# kalkulasi keuangan
"calc_depreciation", "calc_interest_payment", "calc_principal_payment", "calc_dept_amount",
"calc2_ebitda", "calc2_earning_before_tax", "calc2_tax", "calc2_earning_after_tax",
"calc2_nopat", "calc3_interest_after_tax", "calc3_free_cash_flow_on_project",
"calc3_discounted_fcf_on_project",
"On Project", "IRR", "NPV",
"calc4_principal_repayment",
"calc4_free_cash_flow_on_equity", "calc4_discounted_fcf_on_equity",
# chart breakdowns
"chart_total_revenue", "chart_revenue_a", "chart_revenue_b", "chart_revenue_c",
"chart_revenue_d", "chart_revenue_annualized", "chart_fuel_cost_component_c",
"chart_fuel_cost", "chart_fuel_cost_annualized", "chart_oem_component_bd",
"chart_oem_bd_cost", "chart_oem_periodic_maintenance_cost", "chart_oem_annualized",
"chart_capex_component_a", "chart_capex_biaya_investasi_tambahan",
"chart_capex_acquisition_cost", "chart_capex_annualized",
]
# Hanya gunakan kolom yang benar-benar ada di tabel saat ini
calc_columns = [c for c in calc_columns if c in col_names]
# Pisahkan kolom chart_ ke sheet khusus
chart_columns = [c for c in calc_columns if c.startswith('chart_')]
calc_columns = [c for c in calc_columns if not c.startswith('chart_')]
# Siapkan struktur data kolom -> list nilai per tahun (urut seq)
data_by_col: Dict[str, List[Any]] = {c: [] for c in calc_columns}
chart_data_by_col: Dict[str, List[Any]] = {c: [] for c in chart_columns}
# Ambil nilai seq/tahun + semua kolom perhitungan
idx_map = {name: i for i, name in enumerate(col_names)}
for row in rows:
# seq selalu ada karena digunakan di run2.py
seq = row[idx_map.get("seq")]
seq_list.append(seq)
if ada_kolom_tahun:
tahun = row[idx_map.get("tahun")]
else:
# fallback: jika tidak ada kolom tahun, coba bangun dari tahun_cod + seq
if tahun_cod is not None and seq is not None:
try:
tahun = int(float(tahun_cod) + int(seq))
except Exception:
tahun = None
else:
tahun = None
tahun_list.append(tahun)
# simpan is_actual per seq jika tersedia
try:
is_actual_val = row[idx_map.get("is_actual")]
except Exception:
is_actual_val = None
is_actual_list.append(is_actual_val)
for c in calc_columns:
data_by_col[c].append(row[idx_map.get(c)])
for c in chart_columns:
chart_data_by_col[c].append(row[idx_map.get(c)])
# 3) Tulis ke Excel
wb = Workbook()
# Sheet 1: Param Map
ws1 = wb.active
ws1.title = "Params"
ws1.append(["Parameters", "Unit/Satuan", "Value"])
for name, param in param_map.items():
if name.lower().startswith("calc_"):
continue
ws1.append([param.description, param.unit, param.value])
# Sheet 2: Hasil per tahun
ws2 = wb.create_sheet("Results")
# Header baris 1: label kolom A, kolom B bertuliskan "Year", dan tahun mulai kolom C
ws2.cell(row=1, column=1, value="kolom")
ws2.cell(row=1, column=2, value="Year")
for j, tahun in enumerate(tahun_list, start=3):
ws2.cell(row=1, column=j, value=tahun)
# Header baris 2: Seq (mulai kolom C)
ws2.cell(row=2, column=1, value="seq")
for j, seq in enumerate(seq_list, start=3):
ws2.cell(row=2, column=j, value=seq)
# Data mulai baris ke-3
current_row = 3
row_map: Dict[str, int] = {}
# Tambahkan jeda baris sebelum kelompok data tertentu
one_blank_before = {
'revenue_a', 'cost_a_replacement', 'cost_c_fuel', 'cost_bd_om',
'total_expense', 'calc_depreciation', 'calc2_ebitda', 'calc3_interest_after_tax'
}
five_blank_before = {'calc4_principal_repayment'}
# Pemetaan unit untuk kolom-kolom tertentu
def unit_for(name: str) -> str:
if name == 'net_capacity_factor':
return '%'
if name in ('production_bruto', 'production_netto', 'energy_sales'):
return 'MW'
if name == 'fuel_consumption':
return 'TON'
return 'Rp. (Juta)'
for col_name in data_by_col.keys():
# Jeda 1 baris untuk kelompok yang ditentukan
if col_name in one_blank_before:
current_row += 1
# Jeda 5 baris khusus sebelum calc4_principal_repayment
if col_name in five_blank_before:
# Tambah 5 baris seperti sebelumnya
current_row += 5
ws2.append([""])
ws2.append(["On Project"])
ws2.append(["IRR", "%", float(param_map["calc_on_project_irr"].value)])
ws2.append(["NPV", "Rp. (Juta)", float(param_map["calc_on_project_npv"].value)])
# Kolom pertama: pakai label dari LABEL_MAP (atau fallback ke nama aslinya)
ws2.cell(row=current_row, column=1, value=get_label(col_name))
# Kolom B: unit
ws2.cell(row=current_row, column=2, value=unit_for(col_name))
row_map[col_name] = current_row
values = data_by_col[col_name]
for j, v in enumerate(values, start=3):
ws2.cell(row=current_row, column=j, value=v)
current_row += 1
ws2.append([""])
ws2.append(["On Equity"])
ws2.append(["IRR", "%", float(param_map["calc_on_equity_irr"].value)])
ws2.append(["NPV", "Rp. (Juta)", float(param_map["calc_on_equity_npv"].value)])
ws2.append([""])
ws2.append(["RoA", "", "(all)", "", "(sampai L)"])
ws2.append([
"RoA", "%", float(param_map["calc_roa_all"].value),
"", float(param_map["calc_roa_current"].value)
])
# Sheet 3: Chart - pindahkan semua kolom chart_ ke sheet ini
ws3 = wb.create_sheet("Chart")
# Header baris 1: Tahun (mulai kolom C agar sejalan dengan Results)
ws3.cell(row=1, column=1, value="kolom")
for j, tahun in enumerate(tahun_list, start=3):
ws3.cell(row=1, column=j, value=tahun)
# Header baris 2: Seq (mulai kolom C)
ws3.cell(row=2, column=1, value="seq")
for j, seq in enumerate(seq_list, start=3):
ws3.cell(row=2, column=j, value=seq)
# Data chart mulai baris ke-3
current_row_chart = 3
chart_row_map: Dict[str, int] = {}
for col_name in chart_data_by_col.keys():
# Kolom pertama: label human-readable juga
ws3.cell(row=current_row_chart, column=1, value=get_label(col_name))
chart_row_map[col_name] = current_row_chart
values = chart_data_by_col[col_name]
for j, v in enumerate(values, start=2):
ws3.cell(row=current_row_chart, column=j, value=v)
current_row_chart += 1
# Helper untuk referensi Params via INDEX/MATCH (mengembalikan string formula tanpa '=')
def p(name: str) -> str:
# INDEX nilai kolom B berdasarkan nama di kolom A
return f"IFERROR(INDEX(Params!$B:$B, MATCH(\"{name}\", Params!$A:$A, 0)), 0)"
# Siapkan ekspresi parameter yang sering dipakai
rate = f"({p('discount_rate')}/100)"
daya_mampu_netto_expr = p('daya_mampu_netto')
auxiliary_expr = p('auxiliary')
susut_trafo_expr = p('susut_trafo')
sfc_expr = p('sfc')
price_a_expr = p('electricity_price_a')
price_b_expr = p('electricity_price_b')
price_c_expr = p('electricity_price_c')
price_d_expr = p('electricity_price_d')
total_project_cost_expr = p('total_project_cost')
# Helper untuk ambil alamat sel di Results berdasarkan nama baris
def addr(name: str, col_idx: int) -> str:
r = row_map.get(name)
if not r:
return ""
return f"{get_column_letter(col_idx)}{r}"
# Aturan pengisian dari DB vs formula
always_db_rows = {
'net_capacity_factor', 'eaf', 'cost_a_replacement', 'cost_a_pm',
# 'cost_a_pinjaman', 'cost_a_depreciation',
'cost_bd_om', 'cost_bd_pm_nonmi', 'cost_bd_bd'
}
conditional_db_rows = {
'production_bruto', 'production_netto', 'fuel_consumption', 'cost_c_fuel',
'revenue_a', 'revenue_b', 'revenue_c', 'revenue_d'
}
orange_db_rows = {
'net_capacity_factor', 'eaf', 'production_bruto', 'fuel_consumption','energy_sales',
'revenue_a','revenue_b','revenue_c','revenue_d','revenue_total','revenue_pv','revenue_annualized',
'cost_a_replacement', 'cost_a_pm','cost_c_fuel'
'cost_bd_om', 'cost_bd_pm_nonmi', 'cost_bd_bd'
}
def is_actual_at(col_idx: int) -> bool:
try:
idx = col_idx - 3 # data mulai di kolom C (j=3)
v = is_actual_list[idx]
return bool(v == 1)
except Exception:
return False
def can_write_formula(row_name: str, col_idx: int) -> bool:
if row_name in always_db_rows:
return False
if row_name in conditional_db_rows and is_actual_at(col_idx):
return False
return True
# Loop setiap kolom (tiap tahun/seq) data mulai kolom C
DO_FORMULAS = False # ubah ke True jika ingin menulis rumus lagi
for j in range(3, 3 + len(seq_list)):
if not DO_FORMULAS:
continue
colL = get_column_letter(j)
prevL = get_column_letter(j - 1)
def seq_cell(col_idx: int) -> str:
return f"{get_column_letter(col_idx)}2"
# Rumus-rumus dasar produksi dan konsumsi
if 'production_netto' in row_map and 'net_capacity_factor' in row_map:
if can_write_formula('production_netto', j):
netcap = addr('net_capacity_factor', j)
formula = f"={netcap}*8760*{daya_mampu_netto_expr}/100"
ws2.cell(row=row_map['production_netto'], column=j, value=formula)
if 'production_bruto' in row_map:
if can_write_formula('production_bruto', j):
prod_net = addr('production_netto', j)
formula = f"={prod_net}/(100-({auxiliary_expr}+{susut_trafo_expr}))/100"
ws2.cell(row=row_map['production_bruto'], column=j, value=formula)
if 'energy_sales' in row_map and 'production_netto' in row_map:
ws2.cell(row=row_map['energy_sales'], column=j, value=f"={addr('production_netto', j)}")
if 'fuel_consumption' in row_map and 'production_bruto' in row_map:
if can_write_formula('fuel_consumption', j):
ws2.cell(row=row_map['fuel_consumption'], column=j, value=f"={addr('production_bruto', j)}*{sfc_expr}")
# Tambahkan cost_c_fuel = fuel_consumption * harga_bahan_bakar / 1e6
harga_bb_expr = p('harga_bahan_bakar')
if 'cost_c_fuel' in row_map and 'fuel_consumption' in row_map:
if can_write_formula('cost_c_fuel', j):
ws2.cell(row=row_map['cost_c_fuel'], column=j,
value=f"={addr('fuel_consumption', j)}*{harga_bb_expr}/1000000")
# Revenue A-D
if 'revenue_a' in row_map and 'eaf' in row_map:
if can_write_formula('revenue_a', j):
eaf_cell = addr('eaf', j)
ws2.cell(row=row_map['revenue_a'], column=j,
value=f"=({price_a_expr}*{eaf_cell}*{daya_mampu_netto_expr}*1000*12/100)/1000000")
if 'revenue_b' in row_map and 'eaf' in row_map:
if can_write_formula('revenue_b', j):
eaf_cell = addr('eaf', j)
ws2.cell(row=row_map['revenue_b'], column=j,
value=f"=({price_b_expr}*{eaf_cell}*{daya_mampu_netto_expr}*1000*12/100)/1000000")
if 'revenue_c' in row_map and 'production_netto' in row_map:
if can_write_formula('revenue_c', j):
prod_net = addr('production_netto', j)
ws2.cell(row=row_map['revenue_c'], column=j,
value=f"={price_c_expr}*{prod_net}*1000/1000000")
if 'revenue_d' in row_map and 'production_netto' in row_map:
if can_write_formula('revenue_d', j):
prod_net = addr('production_netto', j)
ws2.cell(row=row_map['revenue_d'], column=j,
value=f"={price_d_expr}*{prod_net}*1000/1000000")
if 'revenue_total' in row_map:
parts = [n for n in ['revenue_a', 'revenue_b', 'revenue_c', 'revenue_d'] if n in row_map]
if parts:
sum_expr = "+".join(addr(n, j) for n in parts)
ws2.cell(row=row_map['revenue_total'], column=j, value=f"={sum_expr}")
# revenue_pv kumulatif diskonto berdasarkan seq
if 'revenue_pv' in row_map and 'revenue_total' in row_map:
seq_here = seq_cell(j)
disc_term = f"/POWER(1+{rate},{seq_here})"
if j == 3:
base_expr = f"IF({seq_here}>0,{addr('revenue_total', j)}{disc_term},0)"
else:
prev_pv = addr('revenue_pv', j - 1)
base_expr = f"IF({seq_here}>0,{prev_pv}+{addr('revenue_total', j)}{disc_term},{prev_pv})"
ws2.cell(row=row_map['revenue_pv'], column=j, value=f"={base_expr}")
if 'revenue_annualized' in row_map and 'revenue_pv' in row_map:
seq_here = seq_cell(j)
ws2.cell(row=row_map['revenue_annualized'], column=j,
value=f"=IF({seq_here}>0,-PMT({rate},{seq_here},{addr('revenue_pv', j)}),0)")
# COST A
if 'cost_a_acquisition' in row_map:
if can_write_formula('cost_a_acquisition', j):
# parts = [n for n in ['cost_a_replacement', 'cost_a_pm', 'cost_a_pinjaman', 'cost_a_depreciation'] if n in row_map]
parts = [n for n in ['cost_a_replacement', 'cost_a_pm'] if
n in row_map]
sum_expr = "+".join(addr(n, j) for n in parts) if parts else '0'
seq_here = seq_cell(j)
formula = f"=IF({seq_here}>0,{sum_expr},{total_project_cost_expr})"
ws2.cell(row=row_map['cost_a_acquisition'], column=j, value=formula)
if 'cost_a_pv' in row_map and 'cost_a_acquisition' in row_map:
seq_here = seq_cell(j)
disc_acq = f"{addr('cost_a_acquisition', j)}/POWER(1+{rate},{seq_here})"
if j == 3:
expr = f"IF({seq_here}>0,{total_project_cost_expr}+{disc_acq},0)"
else:
prev_pv = addr('cost_a_pv', j - 1)
prev_seq = seq_cell(j - 1)
expr = f"IF({seq_here}>0,IF({prev_seq}<=0,{total_project_cost_expr}+{disc_acq},{prev_pv}+{disc_acq}),{prev_pv})"
ws2.cell(row=row_map['cost_a_pv'], column=j, value=f"={expr}")
if 'cost_a_annualized' in row_map and 'cost_a_pv' in row_map:
seq_here = seq_cell(j)
ws2.cell(row=row_map['cost_a_annualized'], column=j,
value=f"=IF({seq_here}>0,-PMT({rate},{seq_here},{addr('cost_a_pv', j)}),0)")
# COST C - fuel
if 'cost_c_pv' in row_map and 'cost_c_fuel' in row_map:
seq_here = seq_cell(j)
disc = f"{addr('cost_c_fuel', j)}/POWER(1+{rate},{seq_here})"
if j == 3:
expr = f"IF({seq_here}>0,{disc},0)"
else:
prev_pv = addr('cost_c_pv', j - 1)
expr = f"IF({seq_here}>0,{prev_pv}+{disc},{prev_pv})"
ws2.cell(row=row_map['cost_c_pv'], column=j, value=f"={expr}")
if 'cost_c_annualized' in row_map and 'cost_c_pv' in row_map:
seq_here = seq_cell(j)
ws2.cell(row=row_map['cost_c_annualized'], column=j,
value=f"=IF({seq_here}>0,-PMT({rate},{seq_here},{addr('cost_c_pv', j)}),0)")
# COST BD (O&M)
if 'cost_bd_bd' in row_map:
seq_here = seq_cell(j)
if 'cost_bd_om' in row_map and 'cost_bd_pm_nonmi' in row_map:
ws2.cell(row=row_map['cost_bd_bd'], column=j,
value=f"=IF({seq_here}>0,{addr('cost_bd_om', j)}+{addr('cost_bd_pm_nonmi', j)},0)")
if 'cost_bd_pv' in row_map and 'cost_bd_bd' in row_map:
seq_here = seq_cell(j)
disc = f"{addr('cost_bd_bd', j)}/POWER(1+{rate},{seq_here})"
if j == 3:
expr = f"IF({seq_here}>0,{disc},0)"
else:
prev_pv = addr('cost_bd_pv', j - 1)
expr = f"IF({seq_here}>0,{prev_pv}+{disc},{prev_pv})"
ws2.cell(row=row_map['cost_bd_pv'], column=j, value=f"={expr}")
if 'cost_bd_annualized' in row_map and 'cost_bd_pv' in row_map:
seq_here = seq_cell(j)
ws2.cell(row=row_map['cost_bd_annualized'], column=j,
value=f"=IF({seq_here}>0,-PMT({rate},{seq_here},{addr('cost_bd_pv', j)}),0)")
# TOTALS and P/L
if 'total_expense' in row_map:
parts = []
if 'cost_c_fuel' in row_map:
parts.append(addr('cost_c_fuel', j))
if 'cost_bd_bd' in row_map:
parts.append(addr('cost_bd_bd', j))
if parts:
ws2.cell(row=row_map['total_expense'], column=j, value=f"={'+' .join(parts)}")
if 'total_cost_eac' in row_map:
parts = []
if 'cost_a_annualized' in row_map:
parts.append(addr('cost_a_annualized', j))
if 'cost_c_annualized' in row_map:
parts.append(addr('cost_c_annualized', j))
if 'cost_bd_annualized' in row_map:
parts.append(addr('cost_bd_annualized', j))
if parts:
ws2.cell(row=row_map['total_cost_eac'], column=j, value=f"={'+' .join(parts)}")
if 'total_profit_loss' in row_map and 'revenue_annualized' in row_map and 'total_cost_eac' in row_map:
ws2.cell(row=row_map['total_profit_loss'], column=j,
value=f"={addr('revenue_annualized', j)}-{addr('total_cost_eac', j)}")
# DEPRESIASI & NILAI SISA (state across columns)
umur_teknis_expr = p('umur_teknis')
if 'calc_depreciation' in row_map and 'total_residual_value' in row_map:
seq_here = seq_cell(j)
if j == 3:
# seq=0 => depresiasi 0
ws2.cell(row=row_map['calc_depreciation'], column=j, value=f"=IF({seq_here}>0,0,0)")
# total_residual_value awal = total_project_cost
ws2.cell(row=row_map['total_residual_value'], column=j, value=f"={total_project_cost_expr}")
else:
prev_res = addr('total_residual_value', j - 1)
# depresiasi periode saat ini berdasarkan prev_res
dep_expr = f"=IF({seq_here}>0,{prev_res}/({umur_teknis_expr}-{seq_here}+1),0)"
ws2.cell(row=row_map['calc_depreciation'], column=j, value=dep_expr)
# total_residual_value sekarang = prev_res + cost_a_replacement - depreciation
repl = addr('cost_a_replacement', j) if 'cost_a_replacement' in row_map else '0'
trv_expr = f"={prev_res}+{repl}-{addr('calc_depreciation', j)}"
ws2.cell(row=row_map['total_residual_value'], column=j, value=trv_expr)
# HUTANG & BUNGA
loan_expr = p('loan')
loan_portion_expr = f"({p('loan_portion')}/100)"
principal_interest_payment_expr = p('principal_interest_payment')
if 'calc_dept_amount' in row_map and 'calc_principal_payment' in row_map and 'calc_interest_payment' in row_map:
seq_here = seq_cell(j)
if j == 3:
# awal
ws2.cell(row=row_map['calc_dept_amount'], column=j, value=f"={loan_expr}")
ws2.cell(row=row_map['calc_interest_payment'], column=j, value=f"=0")
ws2.cell(row=row_map['calc_principal_payment'], column=j, value=f"=0")
else:
prev_debt = addr('calc_dept_amount', j - 1)
# interest uses loan_portion as in run2.py (meski biasanya interest_rate)
int_expr = f"=IF({seq_here}>0,{loan_portion_expr}*{prev_debt},0)"
ws2.cell(row=row_map['calc_interest_payment'], column=j, value=int_expr)
prin_expr = f"=IF({seq_here}>0,{principal_interest_payment_expr}-{addr('calc_interest_payment', j)},0)"
ws2.cell(row=row_map['calc_principal_payment'], column=j, value=prin_expr)
debt_expr = f"={prev_debt}-{addr('calc_principal_payment', j)}"
ws2.cell(row=row_map['calc_dept_amount'], column=j, value=debt_expr)
# LAPORAN LABA RUGI & ARUS KAS
corp_tax_expr = f"({p('corporate_tax_rate')}/100)"
if 'calc2_ebitda' in row_map and 'revenue_total' in row_map and 'total_expense' in row_map:
ws2.cell(row=row_map['calc2_ebitda'], column=j,
value=f"={addr('revenue_total', j)}-{addr('total_expense', j)}")
if 'calc2_earning_before_tax' in row_map:
parts_minus = []
if 'calc2_ebitda' in row_map:
parts_minus.append(addr('calc2_ebitda', j))
minus_parts = []
if 'cost_a_depreciation' in row_map:
minus_parts.append(addr('cost_a_depreciation', j))
if 'calc_interest_payment' in row_map:
minus_parts.append(addr('calc_interest_payment', j))
if parts_minus:
ws2.cell(row=row_map['calc2_earning_before_tax'], column=j,
value=f"={'+'.join(parts_minus)}-{'-'.join(['0']) if not minus_parts else '+'.join(minus_parts)}")
if 'calc2_tax' in row_map and 'calc2_earning_before_tax' in row_map:
ebt = addr('calc2_earning_before_tax', j)
ws2.cell(row=row_map['calc2_tax'], column=j, value=f"=IF({ebt}>0,{ebt}*{corp_tax_expr},0)")
if 'calc2_earning_after_tax' in row_map and 'calc2_earning_before_tax' in row_map and 'calc2_tax' in row_map:
ws2.cell(row=row_map['calc2_earning_after_tax'], column=j,
value=f"={addr('calc2_earning_before_tax', j)}-{addr('calc2_tax', j)}")
if 'calc3_interest_after_tax' in row_map and 'calc_interest_payment' in row_map:
ws2.cell(row=row_map['calc3_interest_after_tax'], column=j,
value=f"={addr('calc_interest_payment', j)}*(1-{corp_tax_expr})")
if 'calc2_nopat' in row_map and 'calc2_earning_before_tax' in row_map and 'calc3_interest_after_tax' in row_map:
ws2.cell(row=row_map['calc2_nopat'], column=j,
value=f"={addr('calc2_earning_before_tax', j)}-{addr('calc3_interest_after_tax', j)}")
if 'calc3_free_cash_flow_on_project' in row_map:
parts = []
if 'calc2_earning_after_tax' in row_map:
parts.append(addr('calc2_earning_after_tax', j))
if 'calc3_interest_after_tax' in row_map:
parts.append(addr('calc3_interest_after_tax', j))
if 'calc_depreciation' in row_map:
parts.append(addr('calc_depreciation', j))
minus = addr('cost_a_replacement', j) if 'cost_a_replacement' in row_map else '0'
if parts:
ws2.cell(row=row_map['calc3_free_cash_flow_on_project'], column=j,
value=f"={'+'.join(parts)}-{minus}")
# Discounted FCF Project/Equity
wacc_project = f"({p('wacc_on_project')}/100)"
wacc_equity = f"({p('wacc_on_equity')}/100)"
if 'calc3_discounted_fcf_on_project' in row_map and 'calc3_free_cash_flow_on_project' in row_map:
ws2.cell(row=row_map['calc3_discounted_fcf_on_project'], column=j,
value=f"=-PV({wacc_project},{seq_cell(j)},0,{addr('calc3_free_cash_flow_on_project', j)})")
if 'calc4_principal_repayment' in row_map and 'calc_principal_payment' in row_map:
ws2.cell(row=row_map['calc4_principal_repayment'], column=j,
value=f"=-{addr('calc_principal_payment', j)}")
equity_expr = p('equity')
if 'calc4_free_cash_flow_on_equity' in row_map:
seq_here = seq_cell(j)
parts = []
if 'calc4_principal_repayment' in row_map:
parts.append(addr('calc4_principal_repayment', j))
if 'calc2_earning_after_tax' in row_map:
parts.append(addr('calc2_earning_after_tax', j))
if 'calc_depreciation' in row_map:
parts.append(addr('calc_depreciation', j))
minus = addr('cost_a_replacement', j) if 'cost_a_replacement' in row_map else '0'
plus_expr = '+'.join(parts) if parts else '0'
ws2.cell(row=row_map['calc4_free_cash_flow_on_equity'], column=j,
value=f"=IF({seq_here}>0,{plus_expr}-{minus},-{equity_expr})")
if 'calc4_discounted_fcf_on_equity' in row_map and 'calc4_free_cash_flow_on_equity' in row_map:
ws2.cell(row=row_map['calc4_discounted_fcf_on_equity'], column=j,
value=f"=-PV({wacc_equity},{seq_cell(j)},0,{addr('calc4_free_cash_flow_on_equity', j)})")
# CHART fields: tulis ke sheet Chart (ws3), referensi dari Results (ws2)
def addr_chart(name: str, col_idx: int) -> str:
r = chart_row_map.get(name)
if not r:
return ""
return f"{get_column_letter(col_idx)}{r}"
def set_chart_if_exists(target: str, source: str):
if target in chart_row_map and source in row_map:
ws3.cell(row=chart_row_map[target], column=j, value=f"=Results!{addr(source, j)}")
set_chart_if_exists('chart_total_revenue', 'revenue_total')
set_chart_if_exists('chart_revenue_a', 'revenue_a')
set_chart_if_exists('chart_revenue_b', 'revenue_b')
set_chart_if_exists('chart_revenue_c', 'revenue_c')
set_chart_if_exists('chart_revenue_d', 'revenue_d')
set_chart_if_exists('chart_revenue_annualized', 'revenue_annualized')
set_chart_if_exists('chart_fuel_cost_component_c', 'cost_c_fuel')
set_chart_if_exists('chart_fuel_cost', 'cost_c_fuel')
set_chart_if_exists('chart_fuel_cost_annualized', 'cost_c_annualized')
set_chart_if_exists('chart_oem_component_bd', 'cost_bd_bd')
set_chart_if_exists('chart_oem_bd_cost', 'cost_bd_om')
set_chart_if_exists('chart_oem_periodic_maintenance_cost', 'cost_bd_pm_nonmi')
set_chart_if_exists('chart_oem_annualized', 'cost_bd_annualized')
# Capex charts khusus ke Chart sheet
if 'chart_capex_component_a' in chart_row_map:
seq_here = seq_cell(j)
val_if = addr('cost_a_acquisition', j) if 'cost_a_acquisition' in row_map else '0'
ws3.cell(row=chart_row_map['chart_capex_component_a'], column=j,
value=f"=IF({seq_here}>0,{val_if},{total_project_cost_expr})")
if 'chart_capex_annualized' in chart_row_map and 'cost_a_annualized' in row_map:
seq_here = seq_cell(j)
ws3.cell(row=chart_row_map['chart_capex_annualized'], column=j,
value=f"=IF({seq_here}>0,{addr('cost_a_annualized', j)},0)")
if 'chart_capex_biaya_investasi_tambahan' in chart_row_map:
ws3.cell(row=chart_row_map['chart_capex_biaya_investasi_tambahan'], column=j, value="=0")
if 'chart_capex_acquisition_cost' in chart_row_map:
ws3.cell(row=chart_row_map['chart_capex_acquisition_cost'], column=j, value="=0")
# Auto width sederhana untuk kolom A di Results (pakai label yang sudah di-mapping)
try:
all_labels = [get_label(x) for x in data_by_col.keys()] + ["kolom", "seq"]
max_len = max((len(str(x)) for x in all_labels), default=10)
ws2.column_dimensions[get_column_letter(1)].width = min(max_len + 2, 60)
except Exception:
pass
# Auto width untuk sheet Chart kolom A (pakai label mapping)
try:
all_chart_labels = [get_label(x) for x in chart_data_by_col.keys()] + ["kolom", "seq"]
max_len_chart = max((len(str(x)) for x in all_chart_labels), default=10)
ws3.column_dimensions[get_column_letter(1)].width = min(max_len_chart + 2, 60)
except Exception:
pass
# Pewarnaan sel sesuai ketentuan
try:
# Define fills
fill_db = PatternFill(start_color="FFE699", end_color="FFE699", fill_type="solid")
fill_formula = PatternFill(start_color="757171", end_color="757171", fill_type="solid")
fill_params_value = PatternFill(start_color="757171", end_color="757171", fill_type="solid")
fill_chart_annualized = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")
# ORANGE khusus untuk seq=0 (kolom ke-3) pada baris always_db_rows
fill_db_seq0_orange = PatternFill(start_color="C65911", end_color="C65911", fill_type="solid")
# 1) Sheet Results: warna DB vs formula
last_col = 2 + len(seq_list) # kolom terakhir data (mulai C..)
for row_name, r in row_map.items():
for j in range(3, last_col + 1):
cell = ws2.cell(row=r, column=j)
# Tentukan apakah sel seharusnya formula (abu-abu) atau DB (kuning)
is_db = False
if row_name in always_db_rows:
is_db = True
elif row_name in conditional_db_rows and is_actual_at(j):
is_db = True
# Khusus: jika baris termasuk always_db_rows dan kolom = 3 (seq = 0) → orange
if row_name in orange_db_rows and j == 3:
cell.fill = fill_db_seq0_orange
else:
# Semua selain itu: DB = kuning, selain itu = abu-abu
if is_db:
cell.fill = fill_db
else:
cell.fill = fill_formula
# 2) Sheet Params: kolom value (C) diberi warna abu-abu
for i in range(2, ws1.max_row + 1):
ws1.cell(row=i, column=3).fill = fill_params_value
# 3) Sheet Chart: baris dengan nama berakhir _annualized diwarnai kuning
last_col_chart = 2 + len(seq_list) # data mulai kolom C
for rname, r in chart_row_map.items():
if rname.endswith("_annualized"):
# warnai juga label (kolom A) agar seluruh baris jelas
ws3.cell(row=r, column=1).fill = fill_chart_annualized
for j in range(3, last_col_chart + 1):
ws3.cell(row=r, column=j).fill = fill_chart_annualized
except Exception:
# Jangan gagalkan export hanya karena styling
pass
# Pastikan folder output ada
os.makedirs(os.path.dirname(output_path), exist_ok=True)
wb.save(output_path)
cur.close()
conn.close()
print(f"Export selesai: {output_path}")
except Exception:
try:
conn.close()
except Exception:
pass
raise
if __name__ == "__main__":
# Default output ke folder hasil seperti file-file lain
base_dir = os.path.dirname(__file__)
default_output = os.path.join(base_dir, "hasil", "export_python.xlsx")
export_to_excel(default_output)

@ -0,0 +1,529 @@
import os
import sys
# Tambah path ke config.py (seperti di kode-kode kamu sebelumnya)
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from config import get_connection # harus mengembalikan koneksi psycopg2
from math import pow
import numpy_financial as npf
def validate_number(n):
return n if n is not None else 0
def cumulative_npv(values, rate, initial_cf0=0.0):
"""
Penggunaan:
discount_rate = 0.12 # setara Params!C2
cashflows = [10000, 15000, 20000, 18000]
result = cumulative_npv(cashflows, discount_rate, initial_cf0=0)
"""
cumulative_results = []
running_npv = 0.0
for i, cf in enumerate(values, start=1):
running_npv += cf / pow(1 + rate, i)
cumulative_results.append(initial_cf0 + running_npv)
return cumulative_results
def pmt_excel_style(rate, periods, pv):
"""
Fungsi ini menghasilkan nilai setara Excel:
=-PMT(rate, periods, pv)
rate : discount_rate (contoh: 0.12)
periods : jumlah periode (contoh: 1,2,3,... seperti E2)
pv : present value (contoh: E17 hasil NPV cumulative)
Output : nilai positif seperti yang muncul di Excel
"""
if periods <= 0:
return 0
# Jika rate == 0, maka PMT hanya pembagian sederhana
if rate == 0:
return pv / periods
# Rumus Excel PMT:
# PMT = pv * (rate / (1 - (1 + rate)^(-periods)))
payment = pv * (rate / (1 - pow(1 + rate, -periods)))
# Excel memberi hasil negatif, tapi rumusmu pakai -PMT, maka hasilnya positif
return abs(payment)
def hitung_pv(rate, nper, fv):
pv = npf.pv(rate, nper, pmt=0, fv=fv)
return -pv
def hitung_irr(cashflows: list):
return npf.irr(cashflows)
def main():
conn = get_connection()
if conn is None:
print("Koneksi ke database gagal.")
sys.exit(1)
try:
cur = conn.cursor()
# 1. Ambil data awal
select_sql = """
SELECT
*
FROM lcc_plant_tr_data
ORDER BY seq
"""
cur.execute(select_sql)
col_names = [desc[0] for desc in cur.description]
rows = cur.fetchall()
print(f"Jumlah baris yang akan di-update: {len(rows)}")
# 2. Siapkan data untuk bulk UPDATE
update_sql = """
UPDATE lcc_plant_tr_data
SET
net_capacity_factor = %s,
eaf = %s,
production_bruto = %s,
production_netto = %s,
energy_sales = %s,
fuel_consumption = %s,
revenue_a = %s,
revenue_b = %s,
revenue_c = %s,
revenue_d = %s,
revenue_total = %s,
revenue_pv = %s,
revenue_annualized = %s,
cost_a_replacement = %s,
cost_a_pm = %s,
cost_a_acquisition = %s,
cost_a_pinjaman = %s,
cost_a_depreciation = %s,
cost_a_total = %s,
cost_a_pv = %s,
cost_a_annualized = %s,
cost_c_fuel = %s,
cost_c_pv = %s,
cost_c_annualized = %s,
cost_bd_om = %s,
cost_bd_pm_nonmi = %s,
cost_bd_bd = %s,
cost_bd_total = %s,
cost_bd_pv = %s,
cost_bd_annualized = %s,
total_expense = %s,
total_cost_eac = %s,
total_profit_loss = %s,
total_residual_value = %s,
calc_depreciation = %s,
calc_interest_payment = %s,
calc_principal_payment = %s,
calc_dept_amount = %s,
calc2_ebitda = %s,
calc2_earning_before_tax = %s,
calc2_tax = %s,
calc2_earning_after_tax = %s,
calc2_nopat = %s,
calc3_interest_after_tax = %s,
calc3_free_cash_flow_on_project = %s,
calc3_discounted_fcf_on_project = %s,
calc4_principal_repayment = %s,
calc4_free_cash_flow_on_equity = %s,
calc4_discounted_fcf_on_equity = %s,
chart_total_revenue = %s,
chart_revenue_a = %s,
chart_revenue_b = %s,
chart_revenue_c = %s,
chart_revenue_d = %s,
chart_revenue_annualized = %s,
chart_fuel_cost_component_c = %s,
chart_fuel_cost = %s,
chart_fuel_cost_annualized = %s,
chart_oem_component_bd = %s,
chart_oem_bd_cost = %s,
chart_oem_periodic_maintenance_cost = %s,
chart_oem_annualized = %s,
chart_capex_component_a = %s,
chart_capex_biaya_investasi_tambahan = %s,
chart_capex_acquisition_cost = %s,
chart_capex_annualized = %s
WHERE seq = %s
"""
cur.execute("""
SELECT name,
value_num AS value
FROM lcc_ms_master
""")
param_rows = cur.fetchall()
param_map = {name: val for (name, val) in param_rows}
# helper biar aman
def get_param(name, default=0.0):
v = param_map.get(name, default)
return float(v) if v is not None else float(default)
# Ambil parameter dari tabel (fungsi get_param sudah kamu buat sebelumnya)
discount_rate = get_param("discount_rate") / 100
total_project_cost = get_param("total_project_cost")
daya_mampu_netto = get_param("daya_mampu_netto")
auxiliary = get_param("auxiliary")
susut_trafo = get_param("susut_trafo")
sfc = get_param("sfc")
# Harga listrik berdasarkan tipe
price_a = get_param("electricity_price_a")
price_b = get_param("electricity_price_b")
price_c = get_param("electricity_price_c")
price_d = get_param("electricity_price_d")
# Parameter lain
harga_bahan_bakar = get_param("harga_bahan_bakar")
inflation_rate = get_param("inflation_rate") / 100
loan_portion = get_param("loan_portion") / 100
equity_portion = get_param("equity_portion") / 100
interest_rate = get_param("interest_rate") / 100
loan_tenor = get_param("loan_tenor")
loan = get_param("loan")
corporate_tax_rate = get_param("corporate_tax_rate") / 100
wacc_on_equity = get_param("wacc_on_equity") / 100
wacc_on_project = get_param("wacc_on_project") / 100
manhours_rate = get_param("manhours_rate")
principal_interest_payment = get_param("principal_interest_payment")
umur_teknis = get_param("umur_teknis")
tahun_cod = get_param("tahun_cod")
daya_terpasang = get_param("daya_terpasang")
equity = get_param("equity")
params = []
revenue_total_array = []
cost_a_acquisition_array = []
cost_c_fuel_array = []
cost_bd_total_array = []
total_residual_value = 0 # nilai awal dari total_residual_value
calc_dept_amount = 0 # nilai awal dari calc_dept_amount
revenue_total_start = 0 # nilai awal dari revenue_total_start
calc4_free_cash_flow_on_equity = 0 # nilai awal dari calc4_free_cash_flow_on_equity
calc3_free_cash_flow_on_project_array = []
calc4_free_cash_flow_on_equity_array = []
total_residual_value_array = []
calc2_earning_after_tax_array = []
total_residual_value_array_sampai_sekarang = []
calc2_earning_after_tax_array_sampai_sekarang = []
net_capacity_factor = 0
eaf = 0
cost_bd_om = 0
cost_bd_pm_nonmi = 0
cost_bd_bd = 0
cost_a_replacement = 0
cost_a_pm = 0
cost_a_pinjaman = 0
cost_a_depreciation = 0
for row in rows:
# row adalah tuple sesuai urutan select_sql
data = dict(zip(col_names, row))
seq = data["seq"] # primary key / unique key untuk WHERE
if data["is_actual"] == 1:
net_capacity_factor = validate_number(data["net_capacity_factor"])
eaf = validate_number(data["eaf"])
production_bruto = validate_number(data["production_bruto"])
production_netto = validate_number(data["production_netto"])
energy_sales = production_netto
fuel_consumption = validate_number(data["fuel_consumption"])
revenue_a = validate_number(data["revenue_a"])
revenue_b = validate_number(data["revenue_b"])
revenue_c = validate_number(data["revenue_c"])
revenue_d = validate_number(data["revenue_d"])
cost_c_fuel = validate_number(data["cost_c_fuel"])
cost_bd_om = validate_number(data["cost_bd_om"])
cost_bd_pm_nonmi = validate_number(data["cost_bd_pm_nonmi"])
cost_bd_bd = validate_number(data["cost_bd_bd"])
else:
net_capacity_factor = net_capacity_factor #last value
eaf = eaf #last value
production_netto = net_capacity_factor * 8760 * daya_mampu_netto / 100
production_bruto = production_netto/(100-(auxiliary+susut_trafo))*100
energy_sales = production_netto
fuel_consumption = production_bruto * sfc
revenue_a = (price_a * eaf * daya_mampu_netto * 1000 * 12 / 100) / 1000000
revenue_b = (price_b * eaf * daya_mampu_netto * 1000 * 12 / 100) / 1000000
revenue_c = price_c * production_netto * 1000 / 1000000
revenue_d = price_d * production_netto * 1000 / 1000000
cost_c_fuel = fuel_consumption * harga_bahan_bakar / 1000000
cost_bd_om = cost_bd_om #last value
cost_bd_pm_nonmi = cost_bd_pm_nonmi #last value
cost_bd_bd = cost_bd_bd #last value
# ++++++ REVENUE +++++++
revenue_total = revenue_a + revenue_b + revenue_c + revenue_d
if seq > 0:
revenue_total_array.append(revenue_total)
revenue_pv = cumulative_npv(revenue_total_array, discount_rate)[-1] + revenue_total_start
revenue_annualized = pmt_excel_style(discount_rate, seq, revenue_pv)
else:
revenue_annualized = 0
revenue_pv = 0
revenue_total_start = revenue_total
# print(revenue_total_array)
# print(discount_rate)
# print(revenue_pv)
chart_total_revenue = revenue_total
chart_revenue_a = revenue_a
chart_revenue_b = revenue_b
chart_revenue_c = revenue_c
chart_revenue_d = revenue_d
chart_revenue_annualized = revenue_annualized
# ===== COST A =====
if seq > 0:
if data["is_actual"] == 1:
cost_a_replacement = validate_number(data["cost_a_replacement"])
cost_a_pm = validate_number(data["cost_a_pm"])
cost_a_pinjaman = 0 # validate_number(data["cost_a_pinjaman"])
cost_a_depreciation = 0 # validate_number(data["cost_a_depreciation"])
else:
cost_a_replacement = cost_a_replacement
cost_a_pm = cost_a_pm
cost_a_pinjaman = 0 #cost_a_pinjaman
cost_a_depreciation = 0 # cost_a_depreciation
cost_a_total = validate_number(data["cost_a_total"])
cost_a_acquisition = (
cost_a_replacement
+ cost_a_pm
# + cost_a_pinjaman
# + cost_a_depreciation
)
else:
cost_a_replacement = 0
cost_a_pm = 0
cost_a_pinjaman = 0
cost_a_depreciation = 0
cost_a_total = 0
cost_a_acquisition = total_project_cost
if seq > 0:
cost_a_acquisition_array.append(cost_a_acquisition)
cost_a_pv = cumulative_npv(cost_a_acquisition_array, discount_rate)[-1] + total_project_cost
cost_a_annualized = pmt_excel_style(discount_rate, seq, cost_a_pv)
chart_capex_component_a = cost_a_acquisition
chart_capex_annualized = cost_a_annualized
else:
chart_capex_component_a = total_project_cost
chart_capex_annualized = 0
cost_a_pv = 0
cost_a_annualized = 0
chart_capex_biaya_investasi_tambahan = 0
chart_capex_acquisition_cost = 0
# ===== COST C =====
cost_c_fuel_start = 0
if seq > 0:
cost_c_fuel_array.append(cost_c_fuel)
cost_c_pv = cumulative_npv(cost_c_fuel_array, discount_rate)[-1] + cost_c_fuel_start
cost_c_annualized = pmt_excel_style(discount_rate, seq, cost_c_pv)
else:
cost_c_fuel_start = cost_c_fuel
cost_c_pv = 0
cost_c_annualized = 0
chart_fuel_cost_component_c = cost_c_fuel
chart_fuel_cost = cost_c_fuel
chart_fuel_cost_annualized = cost_c_annualized
# ===== COST BD =====
cost_bd_total_start = 0
if seq > 0:
cost_bd_total = cost_bd_om + cost_bd_pm_nonmi + cost_bd_bd
cost_bd_total_array.append(cost_bd_total)
cost_bd_pv = cumulative_npv(cost_bd_total_array, discount_rate)[-1] + cost_bd_total_start
cost_bd_annualized = pmt_excel_style(discount_rate, seq, cost_bd_pv)
else:
cost_bd_total = 0
cost_bd_total_start = cost_bd_om + cost_bd_pm_nonmi
cost_bd_pv = 0
cost_bd_annualized = 0
chart_oem_component_bd = cost_bd_total
chart_oem_bd_cost = cost_bd_om
chart_oem_periodic_maintenance_cost = cost_bd_pm_nonmi
chart_oem_annualized = cost_bd_annualized
# ===== TOTAL EXPENSE & PROFIT/LOSS =====
if seq > 0:
calc_depreciation = total_residual_value / (umur_teknis - seq + 1)
total_residual_value = total_residual_value + cost_a_replacement - calc_depreciation
calc_interest_payment = interest_rate * calc_dept_amount
calc_principal_payment = principal_interest_payment - calc_interest_payment
calc_dept_amount = calc_dept_amount - calc_principal_payment
else:
calc_depreciation = 0
total_residual_value = total_project_cost
calc_interest_payment = 0
calc_principal_payment = 0
calc_dept_amount = loan
total_residual_value_array.append(total_residual_value)
if data["is_actual"] == 1:
total_residual_value_array_sampai_sekarang.append(total_residual_value)
total_expense = cost_c_fuel + cost_bd_total
total_cost_eac = cost_a_annualized + cost_c_annualized + cost_bd_annualized
total_profit_loss = revenue_annualized - total_cost_eac
calc2_ebitda = revenue_total - total_expense
calc2_earning_before_tax = calc2_ebitda - cost_a_depreciation - calc_interest_payment
calc2_tax = calc2_earning_before_tax * corporate_tax_rate if calc2_earning_before_tax > 0 else 0
calc2_earning_after_tax = calc2_earning_before_tax - calc2_tax
calc2_earning_after_tax_array.append(calc2_earning_after_tax)
if data["is_actual"] == 1: calc2_earning_after_tax_array_sampai_sekarang.append(calc2_earning_after_tax)
calc3_interest_after_tax = calc_interest_payment * (1 - corporate_tax_rate)
calc2_nopat = calc2_earning_before_tax - calc3_interest_after_tax
if seq > 0:
calc3_free_cash_flow_on_project = calc2_earning_after_tax + calc3_interest_after_tax + calc_depreciation - cost_a_replacement
else:
calc3_free_cash_flow_on_project = -total_project_cost
calc3_free_cash_flow_on_project_array.append(calc3_free_cash_flow_on_project)
calc3_discounted_fcf_on_project = hitung_pv(wacc_on_project, seq, calc3_free_cash_flow_on_project)
calc4_principal_repayment = -calc_principal_payment
if seq > 0:
calc4_free_cash_flow_on_equity = calc4_principal_repayment+calc2_earning_after_tax+calc_depreciation - cost_a_replacement
else:
calc4_free_cash_flow_on_equity = -equity
calc4_free_cash_flow_on_equity_array.append(calc4_free_cash_flow_on_equity)
calc4_discounted_fcf_on_equity = hitung_pv(wacc_on_equity, seq, calc4_free_cash_flow_on_equity)
params.append((
net_capacity_factor,
eaf,
production_bruto,
production_netto,
energy_sales,
fuel_consumption,
revenue_a,
revenue_b,
revenue_c,
revenue_d,
revenue_total,
revenue_pv,
revenue_annualized,
cost_a_replacement,
cost_a_pm,
cost_a_acquisition,
cost_a_pinjaman,
cost_a_depreciation,
cost_a_total,
cost_a_pv,
cost_a_annualized,
cost_c_fuel,
cost_c_pv,
cost_c_annualized,
cost_bd_om,
cost_bd_pm_nonmi,
cost_bd_bd,
cost_bd_total,
cost_bd_pv,
cost_bd_annualized,
total_expense,
total_cost_eac,
total_profit_loss,
total_residual_value,
calc_depreciation,
calc_interest_payment,
calc_principal_payment,
calc_dept_amount,
calc2_ebitda,
calc2_earning_before_tax,
calc2_tax,
calc2_earning_after_tax,
calc2_nopat,
calc3_interest_after_tax,
calc3_free_cash_flow_on_project,
calc3_discounted_fcf_on_project,
calc4_principal_repayment,
calc4_free_cash_flow_on_equity,
calc4_discounted_fcf_on_equity,
chart_total_revenue,
chart_revenue_a,
chart_revenue_b,
chart_revenue_c,
chart_revenue_d,
chart_revenue_annualized,
chart_fuel_cost_component_c,
chart_fuel_cost,
chart_fuel_cost_annualized,
chart_oem_component_bd,
chart_oem_bd_cost,
chart_oem_periodic_maintenance_cost,
chart_oem_annualized,
chart_capex_component_a,
chart_capex_biaya_investasi_tambahan,
chart_capex_acquisition_cost,
chart_capex_annualized,
seq # <-- penting: ini untuk WHERE
))
# 3. Bulk update dengan executemany
if params:
cur.executemany(update_sql, params)
conn.commit()
print("Bulk update selesai dan sudah di-commit.")
else:
print("Tidak ada data untuk di-update.")
# ===========================================================================
# ----- ==== HITUNGAN TERAKHIR LCC PLANT ==== -----
# ===========================================================================
IRR_ON_PROJECT = hitung_irr(calc3_free_cash_flow_on_project_array) # dalam %
NPV_ON_PROJECT = cumulative_npv(calc3_free_cash_flow_on_project_array[1:], wacc_on_project)[-1] + \
calc3_free_cash_flow_on_project_array[0]
IRR_ON_EQUITY = hitung_irr(calc4_free_cash_flow_on_equity_array) # dalam %
NPV_ON_EQUITY = cumulative_npv(calc4_free_cash_flow_on_equity_array[1:], wacc_on_equity)[-1] + \
calc4_free_cash_flow_on_equity_array[0]
ROA_ALL = sum(calc2_earning_after_tax_array) / sum(total_residual_value_array) * 100 # dalam %
ROA_TO_L = sum(calc2_earning_after_tax_array_sampai_sekarang) / sum(total_residual_value_array_sampai_sekarang) * 100 # dalam %
update_kpi_sql = """
UPDATE lcc_ms_master
SET value_num = %s
WHERE name = %s \
"""
kpi_params = [
(IRR_ON_EQUITY * 100, "calc_on_equity_irr"),
(NPV_ON_EQUITY, "calc_on_equity_npv"),
(IRR_ON_PROJECT * 100, "calc_on_project_irr"),
(NPV_ON_PROJECT, "calc_on_project_npv"),
(ROA_ALL, "calc_roa_all"),
(ROA_TO_L, "calc_roa_current"),
]
cur.executemany(update_kpi_sql, kpi_params)
conn.commit()
# ===========================================================================
cur.close()
conn.close()
except Exception as e:
conn.rollback()
print(f"Terjadi error, transaksi di-rollback. Error: {e}")
try:
cur.close()
except Exception:
pass
conn.close()
if __name__ == "__main__":
main()

@ -0,0 +1,130 @@
import os
import sys
# Tambah path ke config.py (seperti di kode-kode kamu sebelumnya)
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from config import get_connection, get_connection_db_collect # pastikan fungsi ini ada di config.py
import psycopg2
def get_cost_a_pm(conn2):
"""
Ambil cost_a_pm per tahun dari conn2 (wo_maximo, worktype = 'OH', MI).
"""
query = """
SELECT DATE_PART('year', a.reportdate) AS tahun, SUM(a.actmatcost) AS total_cost
FROM public.wo_maximo AS a
WHERE
a.asset_unit = '3'
AND a.worktype IN ('OH')
AND a.status IN ('CLOSE', 'COMP')
AND a.wonum NOT LIKE 'T%%'
AND a.wojp8 NOT IN ('S1')
GROUP BY DATE_PART('year', a.reportdate)
ORDER BY DATE_PART('year', a.reportdate) ASC;
"""
data = {}
with conn2.cursor() as cur:
cur.execute(query)
for tahun, total_cost in cur.fetchall():
# tahun akan float dari DATE_PART, ubah ke int
tahun_int = int(tahun)
data[tahun_int] = float(total_cost or 0.0)
return data
def get_cost_bd_pm_nonmi(conn2):
"""
Ambil cost_bd_pm_nonmi per tahun dari conn2 (wo_maximo, worktype != 'OH', Non-MI).
"""
query = """
SELECT DATE_PART('year', a.reportdate) AS tahun, SUM(a.actmatcost) AS total_cost
FROM public.wo_maximo AS a
WHERE
a.asset_unit = '3'
AND a.worktype NOT IN ('OH')
AND a.status IN ('CLOSE', 'COMP')
AND a.wonum NOT LIKE 'T%%'
GROUP BY DATE_PART('year', a.reportdate)
ORDER BY DATE_PART('year', a.reportdate) ASC;
"""
data = {}
with conn2.cursor() as cur:
cur.execute(query)
for tahun, total_cost in cur.fetchall():
tahun_int = int(tahun)
data[tahun_int] = float(total_cost or 0.0)
return data
def upsert_lcc_plant_tr_data(conn, tahun, cost_a_pm, cost_bd_pm_nonmi):
"""
Update kalau tahun sudah ada, kalau tidak ada -> insert.
Table: lcc_plant_tr_data (tahun, cost_a_pm, cost_bd_pm_nonmi)
"""
with conn.cursor() as cur:
# Coba update dulu
update_sql = """
UPDATE lcc_plant_tr_data
SET cost_a_pm = %s,
cost_bd_pm_nonmi = %s
WHERE tahun = %s;
"""
cur.execute(update_sql, (cost_a_pm, cost_bd_pm_nonmi, tahun))
# if cur.rowcount == 0:
# insert_sql = """
# INSERT INTO lcc_plant_tr_data (tahun, cost_a_pm, cost_bd_pm_nonmi)
# VALUES (%s, %s, %s);
# """
# cur.execute(insert_sql, (tahun, cost_a_pm, cost_bd_pm_nonmi))
def main():
# Koneksi ke DB1 (utama) untuk table lcc_plant_tr_data
conn = get_connection()
if conn is None:
print("Koneksi ke database (conn) gagal.")
sys.exit(1)
# Koneksi ke DB2 (collect) untuk table wo_maximo
conn2 = get_connection_db_collect()
if conn2 is None:
print("Koneksi ke database 2 (conn2) gagal.")
sys.exit(1)
try:
cost_a_pm_data = get_cost_a_pm(conn2)
cost_bd_pm_nonmi_data = get_cost_bd_pm_nonmi(conn2)
# Gabungkan semua tahun yang muncul di salah satu / keduanya
semua_tahun = sorted(set(cost_a_pm_data.keys()) | set(cost_bd_pm_nonmi_data.keys()))
for tahun in semua_tahun:
cost_a_pm = cost_a_pm_data.get(tahun, 0.0)/1000000
cost_bd_pm_nonmi = cost_bd_pm_nonmi_data.get(tahun, 0.0)/1000000
upsert_lcc_plant_tr_data(conn, tahun, cost_a_pm, cost_bd_pm_nonmi)
print(f"Tahun {tahun}: cost_a_pm={cost_a_pm}, cost_bd_pm_nonmi={cost_bd_pm_nonmi} -> disimpan")
conn.commit()
print("Update lcc_plant_tr_data selesai dan sudah di-commit.")
except Exception as e:
conn.rollback()
print("Terjadi error, transaksi di-rollback:", e)
finally:
try:
conn.close()
except Exception:
pass
try:
conn2.close()
except Exception:
pass
if __name__ == "__main__":
main()

@ -137,7 +137,7 @@ async def create(
) )
# Construct path to the script # Construct path to the script
script_path = os.path.join(directory_path, "run.py") script_path = os.path.join(directory_path, "run2.py")
try: try:
process = await asyncio.create_subprocess_exec( process = await asyncio.create_subprocess_exec(
@ -180,7 +180,7 @@ async def update(
) )
# Construct path to the script # Construct path to the script
script_path = os.path.join(directory_path, "run.py") script_path = os.path.join(directory_path, "run2.py")
try: try:
process = await asyncio.create_subprocess_exec( process = await asyncio.create_subprocess_exec(

Loading…
Cancel
Save