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()
# ===========================================================================