diff --git a/src/modules/equipment/Eac.py b/src/modules/equipment/Eac.py index 24bbcd8..aa5e842 100644 --- a/src/modules/equipment/Eac.py +++ b/src/modules/equipment/Eac.py @@ -73,10 +73,10 @@ class Eac: value / ((1 + inflation_rate) ** (i + 1)) for i, value in enumerate(cumulative_values) ) - # Menghitung PMT + # Menghitung PMT biaya maintenance # Rumus PMT: PMT = PV * [r(1 + r)^n] / [(1 + r)^n – 1] # dimana PV = final_value, r = inflation_rate, n = row["seq"] - pmt_value = -npf.pmt(inflation_rate, row["seq"], final_value) + pmt_mnt_cost = -npf.pmt(inflation_rate, row["seq"], final_value) # Menghitung PMT biaya akuisisi # Rumus PMT: PMT = PV * [r(1 + r)^n] / [(1 + r)^n – 1] @@ -85,14 +85,14 @@ class Eac: # disc_rate adalah discount rate dari database # row["seq"] adalah periode ke-n pmt_aq_cost = -npf.pmt(disc_rate, row["seq"], rc_total_cost_0) - eac = pmt_value + pmt_aq_cost + eac = pmt_mnt_cost + pmt_aq_cost npv_results.append( { "seq": row["seq"], "year": row["tahun"], "npv": final_value, - "pmt": pmt_value, + "pmt": pmt_mnt_cost, "pmt_aq_cost": pmt_aq_cost, "eac": eac, "is_actual": 1, @@ -110,7 +110,7 @@ class Eac: update_query, ( float(final_value), - float(pmt_value), + float(pmt_mnt_cost), float(pmt_aq_cost), float(eac), equipment_id, @@ -173,7 +173,7 @@ class Eac: # dimana PV = final_value, r = inflation_rate, n = row["seq"] # periods adalah jumlah periode # final_value adalah PV pada titik proyeksi periods - pmt_value = -float(npf.pmt(inflation_rate, periods, final_value)) + pmt_mnt_cost = -float(npf.pmt(inflation_rate, periods, final_value)) # menghitung PMT biaya akuisisi # Rumus PMT: PMT = PV * [r(1 + r)^n] / [(1 + r)^n – 1] @@ -183,14 +183,14 @@ class Eac: # row["seq"] adalah periode ke-n pmt_aq_cost = -float(npf.pmt(disc_rate, row["seq"], rc_total_cost_0)) - eac = float(pmt_value) + float(pmt_aq_cost) + eac = float(pmt_mnt_cost) + float(pmt_aq_cost) npv_results.append( { "seq": row["seq"], "year": row["tahun"], "npv": final_value, - "pmt": pmt_value, + "pmt": pmt_mnt_cost, "pmt_aq_cost": pmt_aq_cost, "eac": eac, "is_actual": 0, @@ -208,7 +208,7 @@ class Eac: update_query, ( float(final_value), - float(pmt_value), + float(pmt_mnt_cost), float(pmt_aq_cost), float(eac), equipment_id, diff --git a/src/modules/equipment/Prediksi.py b/src/modules/equipment/Prediksi.py index f32c883..9d5df56 100644 --- a/src/modules/equipment/Prediksi.py +++ b/src/modules/equipment/Prediksi.py @@ -18,6 +18,8 @@ import os sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) from config import get_connection +from modules.equipment.formula import rc_labor_cost, rc_lost_cost, rc_total_cost +import json load_dotenv() @@ -332,6 +334,80 @@ class Prediksi: finally: if connection: connection.close() + def __get_asset_criticality_params(self, equipment_id): + try: + connections = get_connection() + connection = ( + connections[0] if isinstance(connections, tuple) else connections + ) + if connection is None: + print("Database connection failed.") + return None + + cursor = connection.cursor(cursor_factory=DictCursor) + + # Query untuk mendapatkan asset criticality + query = """ + SELECT row_to_json(t) AS asset_criticality + FROM ( + SELECT + asset_crit_ens_energy_not_served, + asset_crit_bpp_system, + asset_crit_bpp_pembangkit, + asset_crit_dmn_daya_mampu_netto, + asset_crit_marginal_cost, + asset_crit_efdh_equivalent_force_derated_hours, + asset_crit_foh_force_outage_hours, + asset_crit_extra_fuel_cost + FROM lcc_ms_equipment_data + WHERE assetnum = %s + ) t + """ + cursor.execute(query, (equipment_id,)) + result = cursor.fetchone() + asset_crit = result.get("asset_criticality") if result else None + if not asset_crit: + return None + + # asset_crit may already be a dict (from row_to_json) or a JSON string + try: + ac = asset_crit if isinstance(asset_crit, dict) else json.loads(asset_crit) + except Exception: + ac = {} + + def _f(key): + try: + return float(ac.get(key) or 0.0) + except Exception: + return 0.0 + + ens = _f("asset_crit_ens_energy_not_served") # ENS + bpp_syst = _f("asset_crit_bpp_system") # BPP_SYST + dmn = _f("asset_crit_dmn_daya_mampu_netto") # DMN + extra_fuel = _f("asset_crit_extra_fuel_cost") # Extra Fuel Cost + + # Formula from image: + # Asset Criticality = (ENS/1 hour * (7% * BPP_SYST)) + ((DMN - ENS/1 hour) * Extra Fuel Cost) + # ENS/1 hour is ENS (division by 1) + part1 = ens * (0.07 * bpp_syst) + part2 = max(0.0, (dmn - ens)) * extra_fuel + asset_criticality = part1 + part2 + + efdh = _f("asset_crit_efdh_equivalent_force_derated_hours") # EFDH + foh = _f("asset_crit_foh_force_outage_hours") + + return { + "asset_criticality": asset_criticality, + "efdh_oh_sum": efdh + foh, + } + + except Exception as e: + print(f"Error saat mendapatkan asset criticality dari database: {e}") + return None + + finally: + if connection: + connection.close() # Fungsi untuk menghapus data proyeksi pada tahun tertentu def __update_data_lcc(self, equipment_id): @@ -344,46 +420,181 @@ class Prediksi: print("Database connection failed.") return None - cursor = connection.cursor() + cursor = connection.cursor(cursor_factory=DictCursor) - # Query untuk menghapus data berdasarkan tahun proyeksi - up_query = """ - update lcc_equipment_tr_data - set - rc_cm_material_cost = raw_cm_material_cost - ,rc_cm_labor_cost = (raw_cm_interval * raw_cm_labor_time * raw_cm_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) ) - ,rc_pm_material_cost = raw_pm_material_cost - ,rc_pm_labor_cost = (raw_pm_labor_time * raw_pm_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) ) - ,rc_predictive_labor_cost = COALESCE( (raw_predictive_labor_time * raw_predictive_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) ) , 0) - ,rc_oh_material_cost = raw_oh_material_cost - ,rc_oh_labor_cost = (raw_oh_labor_time * raw_oh_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) ) - ,rc_project_material_cost = coalesce(raw_project_task_material_cost, 0) - ,rc_lost_cost = coalesce(("raw_loss_output_MW" * raw_loss_output_price * raw_cm_interval), 0) * 1000 - ,rc_operation_cost = coalesce(raw_operational_cost, 0) - ,rc_maintenance_cost = coalesce(raw_maintenance_cost, 0) - ,rc_total_cost = ( - raw_cm_material_cost - + (raw_cm_interval * raw_cm_labor_time * raw_cm_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) ) - + raw_pm_material_cost - + (raw_pm_labor_time * raw_pm_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) ) - + COALESCE( (raw_predictive_labor_time * raw_predictive_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) ) , 0) - + raw_oh_material_cost - + (raw_oh_labor_time * raw_oh_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) ) - + coalesce(raw_project_task_material_cost, 0) - + coalesce(("raw_loss_output_MW" * raw_loss_output_price * raw_cm_interval), 0) * 1000 - + coalesce(raw_operational_cost, 0) - + coalesce(raw_maintenance_cost, 0) - ) - , updated_by = 'Sys', updated_at = NOW() - where assetnum = %s; + # Ambil semua baris untuk assetnum + select_q = ''' + SELECT id, seq, tahun, + raw_cm_interval, raw_cm_material_cost, raw_cm_labor_time, raw_cm_labor_human, + raw_pm_interval, raw_pm_material_cost, raw_pm_labor_time, raw_pm_labor_human, + raw_predictive_interval, raw_predictive_material_cost, raw_predictive_labor_time, raw_predictive_labor_human, + raw_oh_interval, raw_oh_material_cost, raw_oh_labor_time, raw_oh_labor_human, + raw_predictive_interval, raw_predictive_material_cost, raw_predictive_labor_time, raw_predictive_labor_human, + "raw_loss_output_MW" as raw_loss_output_mw, raw_loss_output_price, + raw_operational_cost, raw_maintenance_cost, rc_material_cost + FROM lcc_equipment_tr_data + WHERE assetnum = %s; + ''' + cursor.execute(select_q, (equipment_id,)) + rows = cursor.fetchall() - update lcc_equipment_tr_data set rc_total_cost = (select acquisition_cost from lcc_ms_equipment_data where assetnum=lcc_equipment_tr_data.assetnum) where assetnum = %s and seq=0; - """ # Asumsikan kolom is_actual digunakan untuk membedakan data proyeksi dan data aktual + # Helper to get man_hour for a year (fallback to master 'manhours_rate') + def _get_man_hour_for_year(year): + try: + cur = connection.cursor() + cur.execute("SELECT man_hour FROM lcc_ms_year_data WHERE year = %s", (year,)) + r = cur.fetchone() + if r and r[0] is not None: + return float(r[0]) + cur.execute("SELECT value_num FROM lcc_ms_master WHERE name='manhours_rate'") + r2 = cur.fetchone() + if r2 and r2[0] is not None: + return float(r2[0]) + except Exception: + pass + return 0.0 + + update_q = ''' + UPDATE lcc_equipment_tr_data + SET rc_cm_material_cost = %s, + rc_cm_labor_cost = %s, + rc_pm_material_cost = %s, + rc_pm_labor_cost = %s, + rc_predictive_material_cost = %s, + rc_predictive_labor_cost = %s, + rc_oh_material_cost = %s, + rc_oh_labor_cost = %s, + rc_lost_cost = %s, + rc_operation_cost = %s, + rc_maintenance_cost = %s, + rc_total_cost = %s, + updated_by = 'Sys', updated_at = NOW() + WHERE id = %s; + ''' + + for r in rows: + try: + yr = r.get("tahun") if isinstance(r, dict) else r[2] + man_hour = _get_man_hour_for_year(yr) + + raw_cm_interval = float(r.get("raw_cm_interval") or 0.0) + raw_cm_labor_time = float(r.get("raw_cm_labor_time") or 0.0) + raw_cm_labor_human = float(r.get("raw_cm_labor_human") or 0.0) + + raw_pm_interval = float(r.get("raw_pm_interval") or 0.0) + raw_pm_material_cost = float(r.get("raw_pm_material_cost") or 0.0) + raw_pm_labor_time = float(r.get("raw_pm_labor_time") or 0.0) + raw_pm_labor_human = float(r.get("raw_pm_labor_human") or 0.0) + + raw_predictive_interval = float(r.get("raw_predictive_interval") or 0.0) + raw_predictive_material_cost = float(r.get("raw_predictive_material_cost") or 0.0) + raw_predictive_labor_time = float(r.get("raw_predictive_labor_time") or 0.0) + raw_predictive_labor_human = float(r.get("raw_predictive_labor_human") or 0.0) + + raw_oh_interval = float(r.get("raw_oh_interval") or 0.0) + raw_oh_material_cost = float(r.get("raw_oh_material_cost") or 0.0) + raw_oh_labor_time = float(r.get("raw_oh_labor_time") or 0.0) + raw_oh_labor_human = float(r.get("raw_oh_labor_human") or 0.0) + + raw_loss_output_mw = float(r.get("raw_loss_output_mw") or 0.0) + raw_loss_output_price = float(r.get("raw_loss_output_price") or 0.0) + + raw_operational_cost = float(r.get("raw_operational_cost") or 0.0) + raw_maintenance_cost = float(r.get("raw_maintenance_cost") or 0.0) + rc_cm_material_cost = float(r.get("rc_cm_material_cost") or 0.0) + + # compute per-column costs using helpers + rc_cm_material = rc_cm_material_cost + rc_cm_labor = rc_labor_cost(raw_cm_interval, raw_cm_labor_time, raw_cm_labor_human, man_hour) + + try: + if np.isfinite(raw_pm_interval) and raw_pm_interval != 0: + rc_pm_material = raw_pm_material_cost * raw_pm_interval + else: + rc_pm_material = raw_pm_material_cost + except Exception: + rc_pm_material = 0.0 + rc_pm_labor = rc_labor_cost(raw_pm_interval, raw_pm_labor_time, raw_pm_labor_human, man_hour) + + try: + if np.isfinite(raw_predictive_interval) and raw_predictive_interval != 0: + rc_predictive_material = raw_predictive_material_cost * raw_predictive_interval + else: + rc_predictive_material = raw_predictive_material_cost + except Exception: + rc_predictive_material = 0.0 + + rc_predictive_labor = raw_predictive_labor_human + try: + rc_predictive_labor = rc_labor_cost(raw_predictive_interval, raw_predictive_labor_time, raw_predictive_labor_human, man_hour) + except Exception: + rc_predictive_labor = 0.0 + + rc_oh_material = raw_oh_material_cost + rc_oh_labor = rc_labor_cost(raw_oh_interval, raw_oh_labor_time, raw_oh_labor_human, man_hour) + + rc_lost = rc_lost_cost(raw_loss_output_mw, raw_loss_output_price, raw_cm_interval) + + rc_operation = raw_operational_cost + rc_maintenance = raw_maintenance_cost + + asset_criticality_data = self.__get_asset_criticality_params(equipment_id) + asset_criticality_value = 0.0 + # Simplify extraction and avoid repeating the multiplication + ac = asset_criticality_data if isinstance(asset_criticality_data, dict) else {} + try: + efdh_oh_sum = float(ac.get("efdh_oh_sum", 0.0)) + except Exception: + efdh_oh_sum = 0.0 + try: + asset_criticality_value = float(ac.get("asset_criticality", 0.0)) + except Exception: + asset_criticality_value = 0.0 + + # single multiplier used for all RC groups + ac_multiplier = efdh_oh_sum * asset_criticality_value + + total = rc_total_cost( + rc_cm=rc_cm_material + rc_cm_labor + ac_multiplier, + rc_pm=rc_pm_material + rc_pm_labor + ac_multiplier, + rc_predictive=rc_predictive_material + rc_predictive_labor + ac_multiplier, + rc_oh=rc_oh_material + rc_oh_labor + ac_multiplier, + rc_lost=rc_lost, + rc_operation=rc_operation, + rc_maintenance=rc_maintenance, + ) + + id_val = r.get("id") if isinstance(r, dict) else r[0] + + cursor.execute( + update_q, + ( + rc_cm_material, + rc_cm_labor, + rc_pm_material, + rc_pm_labor, + rc_predictive_material, + rc_predictive_labor, + rc_oh_material, + rc_oh_labor, + rc_lost, + rc_operation, + rc_maintenance, + total, + id_val, + ), + ) + except Exception: + # ignore row-specific errors and continue + continue + + # For seq=0 rows, set rc_total_cost to acquisition_cost + cursor.execute( + "update lcc_equipment_tr_data set rc_total_cost = (select acquisition_cost from lcc_ms_equipment_data where assetnum=lcc_equipment_tr_data.assetnum) where assetnum = %s and seq=0;", + (equipment_id,) + ) - # Eksekusi query delete - cursor.execute(up_query, (equipment_id, equipment_id)) connection.commit() - # print(f"Data berhasil diupdate.") except Exception as e: print(f"Error saat update data proyeksi dari database: {e}") @@ -440,6 +651,9 @@ class Prediksi: if connection: connection.close() + + + # Authentication: sign-in and refresh helpers async def sign_in(self, username: str = "user14", password: str = "password") -> dict: """Sign in to AUTH_APP_URL/sign-in using provided username/password. diff --git a/src/modules/equipment/__pycache__/Eac.cpython-311.pyc b/src/modules/equipment/__pycache__/Eac.cpython-311.pyc index 3053e28..1652795 100644 Binary files a/src/modules/equipment/__pycache__/Eac.cpython-311.pyc and b/src/modules/equipment/__pycache__/Eac.cpython-311.pyc differ diff --git a/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc b/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc index 98468ba..3c53d9e 100644 Binary files a/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc and b/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc differ