diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..1c2fda5 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/be-lcca.iml b/.idea/be-lcca.iml new file mode 100644 index 0000000..2946dc0 --- /dev/null +++ b/.idea/be-lcca.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.agent.xml b/.idea/copilot.data.migration.agent.xml new file mode 100644 index 0000000..2c0ecc2 --- /dev/null +++ b/.idea/copilot.data.migration.agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.edit.xml b/.idea/copilot.data.migration.edit.xml new file mode 100644 index 0000000..bd4a159 --- /dev/null +++ b/.idea/copilot.data.migration.edit.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5aa76b5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..c8397c9 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/modules/plant/export.py b/src/modules/plant/export.py index e824bae..14aaa3b 100644 --- a/src/modules/plant/export.py +++ b/src/modules/plant/export.py @@ -110,7 +110,7 @@ def fetch_param_map(cur) -> Dict[str, Param]: '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 + ORDER BY seq ASC """ ) rows = cur.fetchall() diff --git a/src/modules/plant/run2.py b/src/modules/plant/run2.py index 6234ab9..9d00ff7 100644 --- a/src/modules/plant/run2.py +++ b/src/modules/plant/run2.py @@ -1,14 +1,19 @@ 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 +import math +import uuid def validate_number(n): return n if n is not None else 0 + + def cumulative_npv(values, rate, initial_cf0=0.0): """ Penggunaan: @@ -24,6 +29,8 @@ def cumulative_npv(values, rate, initial_cf0=0.0): cumulative_results.append(initial_cf0 + running_npv) return cumulative_results + + def pmt_excel_style(rate, periods, pv): """ Fungsi ini menghasilkan nilai setara Excel: @@ -46,12 +53,17 @@ def pmt_excel_style(rate, periods, pv): # 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: @@ -59,95 +71,22 @@ def main(): 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() + # ### LOCKING: pastikan transaksi manual (non-autocommit) + try: + conn.autocommit = False + except Exception: + # Kalau driver tidak punya autocommit, abaikan + pass - print(f"Jumlah baris yang akan di-update: {len(rows)}") + cur = conn.cursor() - # 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 - """ + # ### 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 @@ -161,6 +100,141 @@ def main(): 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() + + 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 \ + """ + # 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") @@ -198,10 +272,10 @@ def main(): 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 + 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 = [] @@ -240,10 +314,10 @@ def main(): 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 + 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 + 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 @@ -251,10 +325,9 @@ def main(): 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 - + 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 @@ -283,19 +356,19 @@ def main(): 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"]) + 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_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 + # + cost_a_pinjaman + # + cost_a_depreciation ) else: cost_a_replacement = 0 @@ -395,7 +468,7 @@ def main(): 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 + 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) @@ -482,16 +555,17 @@ def main(): # =========================================================================== # ----- ==== HITUNGAN TERAKHIR LCC PLANT ==== ----- # =========================================================================== - IRR_ON_PROJECT = hitung_irr(calc3_free_cash_flow_on_project_array) # dalam % + 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 % + 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] + 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 % + 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 @@ -499,7 +573,7 @@ def main(): WHERE name = %s \ """ - kpi_params = [ + 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"), @@ -508,6 +582,11 @@ def main(): (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() # ===========================================================================