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 linear_forecast_from_history(values): """ Melakukan proyeksi linier (OLS) dari seluruh data history. Mengembalikan nilai forecast untuk index berikutnya. """ # ambil hanya nilai valid (>0) y = [v for v in values if v is not None and v > 0] n = len(y) if n == 0: return 0.0 if n == 1: return y[0] # belum bisa regresi x = list(range(n)) sum_x = sum(x) sum_y = sum(y) sum_x2 = sum(i * i for i in x) sum_xy = sum(i * y[i] for i in x) denominator = (n * sum_x2 - sum_x ** 2) if denominator == 0: return y[-1] b = (n * sum_xy - sum_x * sum_y) / denominator a = (sum_y - b * sum_x) / n # forecast untuk index berikutnya next_x = n forecast = a + b * next_x # safety clamp return max(forecast, 0.0) 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: pass cur = conn.cursor() # Check for simulation ID simulation_id = os.environ.get("PLANT_SIMULATION_ID") is_simulation = bool(simulation_id) table_master = "lcc_ms_master_simulations" if is_simulation else "lcc_ms_master" table_tr_data = "lcc_plant_tr_data_simulations" if is_simulation else "lcc_plant_tr_data" def get_filter_where(): return f"WHERE simulation_id = '{simulation_id}'" if is_simulation else "" def get_filter_and(): return f"AND simulation_id = '{simulation_id}'" if is_simulation else "" # ### LOCKING: kunci tabel lcc_plant_tr_data cur.execute(f"LOCK TABLE {table_tr_data} IN SHARE ROW EXCLUSIVE MODE") # 0 Mendapatkan master parameter dari tabel master cur.execute(f""" SELECT name, value_num AS value FROM {table_master} {get_filter_where()} """) 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(f""" DELETE FROM {table_tr_data} WHERE is_actual = 0 {get_filter_and()} """) # Hitung kebutuhan jumlah baris projection baru agar total (actual + projection) # sama dengan parameter umur_teknis cur.execute(f""" SELECT COALESCE(COUNT(*), 0) FROM {table_tr_data} WHERE is_actual = 1 {get_filter_and()} """) 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(f"SELECT COALESCE(MAX(seq), 0) FROM {table_tr_data} {get_filter_where()}") last_seq = int(cur.fetchone()[0]) cur.execute(f"SELECT COALESCE(MAX(tahun), 0) FROM {table_tr_data} {get_filter_where()}") 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 if is_simulation: insert_sql = ( f"INSERT INTO {table_tr_data} (id, seq, tahun, is_actual, created_at, created_by, simulation_id) " f"VALUES (%s, %s, %s, 0, CURRENT_TIMESTAMP, 'SYS', '{simulation_id}')" ) else: insert_sql = ( f"INSERT INTO {table_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 = f""" SELECT * FROM {table_tr_data} {get_filter_where()} 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 = f""" UPDATE {table_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 {get_filter_and()} """ # 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 = [] sfc_history = [] auxiliary_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 = validate_number(data.get("energy_sales")) if validate_number( data.get("energy_sales")) is not None else 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"]) sfc_history.append(fuel_consumption / production_bruto if production_bruto != 0 else 0) auxiliary_history.append( (production_bruto - production_netto) / production_bruto * 100 if production_bruto != 0 else 0) else: # last_3_sfc = sfc_history[-3:] # avg_last_3_sfc = sum(last_3_sfc) / len(last_3_sfc) if len(last_3_sfc) > 0 else 0 # sfc = avg_last_3_sfc # sfc_history.append(avg_last_3_sfc) # last_3_auxiliary = auxiliary_history[-3:] # avg_last_3_auxiliary = sum(last_3_auxiliary) / len(last_3_auxiliary) if len(last_3_auxiliary) > 0 else 0 # auxiliary = avg_last_3_auxiliary # auxiliary_history.append(avg_last_3_auxiliary) sfc = linear_forecast_from_history(sfc_history) sfc_history.append(sfc) auxiliary = linear_forecast_from_history(auxiliary_history) auxiliary_history.append(auxiliary) 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 # ++++++ 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 = f""" UPDATE {table_master} SET value_num = %s WHERE name = %s {get_filter_and()} """ 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()