You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

772 lines
33 KiB
Python

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 as np
import numpy_financial as npf
import math
import uuid
def normalize_db_value(value):
"""Convert numpy scalars to native Python types for psycopg2."""
if isinstance(value, np.generic):
return value.item()
return value
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 getproyeksilinier(years, values, iterations=30, target_years=None):
"""
Jika target_years diberikan (list[int]), fungsi mengembalikan prediksi untuk tahun-tahun tersebut.
Jika target_years None, perilaku lama tetap: memproyeksikan dari max(years)+1 sepanjang (iterations - len(years)).
Catatan:
- Jika data historis < 2 titik, fallback pakai nilai terakhir (atau 0).
- Jika semua year sama (degenerate), fallback juga.
"""
if len(years) != len(values):
raise ValueError("Panjang years dan values harus sama")
# bersihkan pasangan (year, value) yang year-nya None
pairs = [(int(y), float(v)) for y, v in zip(years, values) if y is not None]
if not pairs:
# tidak ada data sama sekali
if target_years is None:
return {}
return {int(y): 0.0 for y in target_years}
years_clean = [p[0] for p in pairs]
values_clean = [p[1] for p in pairs]
# fallback kalau data tidak cukup untuk regresi
if len(set(years_clean)) < 2 or len(values_clean) < 2:
last_val = float(values_clean[-1]) if values_clean else 0.0
if target_years is None:
# perilaku lama
n_hist = len(years_clean)
if iterations <= n_hist:
return {}
start_year = max(years_clean) + 1
n_projection = iterations - n_hist
return {start_year + i: last_val for i in range(n_projection)}
return {int(y): last_val for y in target_years}
# regresi linier y = a*x + b
x = np.array(years_clean, dtype=float)
y = np.array(values_clean, dtype=float)
a, b = np.polyfit(x, y, 1)
def _predict(yr: int) -> float:
v = float(a * yr + b)
# optional: kalau tidak boleh negatif, clamp
return max(0.0, v)
# mode target_years: prediksi untuk tahun tertentu
if target_years is not None:
return {int(yr): _predict(int(yr)) for yr in target_years}
# mode lama: generate dari tahun setelah histori
n_hist = len(years_clean)
if iterations <= n_hist:
raise ValueError(
f"iterations ({iterations}) harus lebih besar dari jumlah data historis ({n_hist})"
)
start_year = max(years_clean) + 1
n_projection = iterations - n_hist
return {start_year + i: _predict(start_year + i) for i in range(n_projection)}
def main():
connections = get_connection()
conn = connections[0] if isinstance(connections, tuple) else connections
if conn is None:
print("Koneksi ke database gagal.")
sys.exit(1)
try:
# ### LOCKING: pastikan transaksi manual (non-autocommit)
try:
conn.autocommit = False
except Exception:
# Kalau driver tidak punya autocommit, abaikan
pass
cur = conn.cursor()
# ### LOCKING: kunci tabel lcc_plant_tr_data
# Mode SHARE ROW EXCLUSIVE:
# - Menghalangi INSERT/UPDATE/DELETE di tabel ini
# - Menghalangi lock SHARE ROW EXCLUSIVE lain → script ngantri satu per satu
cur.execute("LOCK TABLE lcc_plant_tr_data IN SHARE ROW EXCLUSIVE MODE")
# 0 Mendapatkan master parameter dari tabel lcc_ms_master
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)
# 0-1 Generate New data Projection (is_actual=0) if not exist
# Hapus data projection lama (is_actual = 0)
cur.execute("""
DELETE
FROM lcc_plant_tr_data
WHERE is_actual = 0
""")
# Hitung kebutuhan jumlah baris projection baru agar total (actual + projection)
# sama dengan parameter umur_teknis
cur.execute("""
SELECT COALESCE(COUNT(*), 0)
FROM lcc_plant_tr_data
WHERE is_actual = 1
""")
count_actual = cur.fetchone()[0] if cur.rowcount != -1 else 0
umur_teknis = int(get_param("umur_teknis"))
proj_needed = max(0, umur_teknis - int(count_actual))
# Ambil seq dan tahun terakhir sebagai titik awal penomoran berikutnya
cur.execute("SELECT COALESCE(MAX(seq), 0) FROM lcc_plant_tr_data")
last_seq = int(cur.fetchone()[0])
cur.execute("SELECT COALESCE(MAX(tahun), 0) FROM lcc_plant_tr_data")
last_year = int(cur.fetchone()[0])
# Jika belum ada tahun sama sekali, gunakan tahun_cod-1 sebagai dasar
if last_year == 0:
try:
last_year = int(get_param("tahun_cod")) - 1
except Exception:
last_year = 0
if proj_needed > 0:
# Siapkan rows untuk INSERT projection baru
values = []
next_seq = last_seq + 1
next_year = last_year + 1
for _ in range(proj_needed):
values.append((str(uuid.uuid4()), next_seq, next_year))
next_seq += 1
next_year += 1
insert_sql = (
"INSERT INTO lcc_plant_tr_data (id, seq, tahun, is_actual, created_at, created_by) "
"VALUES (%s, %s, %s, 0, CURRENT_TIMESTAMP, 'SYS')"
)
cur.executemany(insert_sql, values)
# 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()
# ============================================================
# PROYEKSI LINIER untuk COST BD berdasarkan histori (is_actual=1)
# ============================================================
hist_years_om, hist_vals_om = [], []
hist_years_pm, hist_vals_pm = [], []
hist_years_bd, hist_vals_bd = [], []
projection_years = []
for r in rows:
d = dict(zip(col_names, r))
yr = d.get("tahun")
if yr is None:
continue
yr = int(yr)
if d.get("is_actual") == 1:
# ambil histori (boleh 0 kalau null)
hist_years_om.append(yr)
hist_vals_om.append(validate_number(d.get("cost_bd_om")))
hist_years_pm.append(yr)
hist_vals_pm.append(validate_number(d.get("cost_bd_pm_nonmi")))
hist_years_bd.append(yr)
hist_vals_bd.append(validate_number(d.get("cost_bd_bd")))
else:
# tahun-tahun projection yang ingin diprediksi
projection_years.append(yr)
# buat mapping prediksi per tahun untuk masing-masing komponen cost BD
proj_cost_bd_om = getproyeksilinier(hist_years_om, hist_vals_om, target_years=projection_years)
proj_cost_bd_pm = getproyeksilinier(hist_years_pm, hist_vals_pm, target_years=projection_years)
proj_cost_bd_bd = getproyeksilinier(hist_years_bd, hist_vals_bd, target_years=projection_years)
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,
cost_disposal_cost = %s
WHERE seq = %s \
"""
# 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
net_capacity_factor_v = 0
eaf_v = 0
eaf_history = []
cf_history = []
# Prefetch master data CF dan EAF sekali saja di luar loop
cur.execute("""
SELECT year as tahun, cf, eaf
FROM lcc_ms_year_data
order by year asc
""")
year_rows = cur.fetchall()
year_data_map = {int(t): (validate_number(cf), validate_number(eaf)) for (t, cf, eaf) in year_rows if
t is not None}
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
yr = int(data["tahun"]) if data.get("tahun") is not None else None
# Ambil net_capacity_factor dan eaf dari cache berdasarkan tahun
cf_eaf = year_data_map.get(int(data["tahun"])) if data.get("tahun") is not None else None
if cf_eaf:
net_capacity_factor_v, eaf_v = cf_eaf
else:
net_capacity_factor_v = 0
eaf_v = 0
# rata-rata 3 tahun terakhir untuk net_capacity_factor
if data["is_actual"] == 1:
eaf_history.append(eaf_v)
cf_history.append(net_capacity_factor_v)
net_capacity_factor = net_capacity_factor_v
eaf = eaf_v
else:
last_3_eaf = eaf_history[-3:]
last_3_cf = cf_history[-3:]
avg_eaf_3 = sum(last_3_eaf) / len(last_3_eaf) if last_3_eaf else 0
avg_cf_3 = sum(last_3_cf) / len(last_3_cf) if last_3_cf else 0
selisih_eaf_cf = abs(avg_cf_3 - avg_eaf_3)
eaf = eaf_v
net_capacity_factor = eaf_v - selisih_eaf_cf
eaf_history.append(eaf_v)
cf_history.append(net_capacity_factor)
if data["is_actual"] == 1:
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"])
net_capacity_factor = net_capacity_factor_v
eaf = eaf_v
else:
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
# default fallback tetap pakai last value kalau tahun kosong / prediksi tidak ada
if yr is not None:
cost_bd_om = proj_cost_bd_om.get(yr, cost_bd_om)
cost_bd_pm_nonmi = proj_cost_bd_pm.get(yr, cost_bd_pm_nonmi)
cost_bd_bd = proj_cost_bd_bd.get(yr, cost_bd_bd)
else:
cost_bd_om = cost_bd_om
cost_bd_pm_nonmi = cost_bd_pm_nonmi
cost_bd_bd = cost_bd_bd
eaf = eaf_v
net_capacity_factor = eaf_v - selisih_eaf_cf
# ++++++ 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
cost_disposal_cost = -npf.pmt(discount_rate, seq, 0, 0.05 * total_project_cost)
else:
chart_capex_component_a = total_project_cost
chart_capex_annualized = 0
cost_a_pv = 0
cost_a_annualized = 0
cost_disposal_cost = 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)
row_params = (
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,
cost_disposal_cost,
seq # <-- penting: ini untuk WHERE
)
params.append(tuple(normalize_db_value(v) for v in row_params))
# 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_raw = [
(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"),
]
kpi_params = [
(None if (value is None or isinstance(value, float) and math.isnan(value)) else value, key)
for value, key in kpi_params_raw
]
cur.executemany(update_kpi_sql, kpi_params)
conn.commit()
# ===========================================================================
cur.close()
conn.close()
except Exception as e:
if conn:
conn.rollback()
print(f"Terjadi error, transaksi di-rollback. Error: {e}")
try:
cur.close()
except Exception:
pass
if conn:
conn.close()
if __name__ == "__main__":
main()