From f095298a15adfabb32edd6e8e3dc51a6101ca748 Mon Sep 17 00:00:00 2001 From: MrWaradana Date: Fri, 21 Nov 2025 17:27:57 +0700 Subject: [PATCH] update formula to follow SPLN --- src/modules/equipment/Eac.py | 18 +- src/modules/equipment/Prediksi.py | 286 +++++++++++++++--- .../equipment/__pycache__/Eac.cpython-311.pyc | Bin 15306 -> 15309 bytes .../__pycache__/Prediksi.cpython-311.pyc | Bin 41436 -> 50105 bytes 4 files changed, 259 insertions(+), 45 deletions(-) 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 3053e28fc92731127b2ab198703386b3975a2827..16527957be36a8d099b4f4e580af5c396f10037b 100644 GIT binary patch delta 208 zcmX?Aezu%WQ7_}$gmvLdwxhSE1MM8VBmaNfc6KP{cSw*#_$xG5#G?_=;Na xMTL+n3LzKdLniP{ZjsRt@wq6bc|}Zft?LHui#m2!bnGsQ*FpXz7senmrKd9$uH2pD5g7kwv-&B#^ha6F6@~XB{Z%`XiV0XHQH=0 zZOkYur@Yj7iTMh<9p(qTFDeIIQ4Y8$8+b)F@PbSr(2RB&4H3_aVro~!)YgiwsJ^Ia TeMQsyqL?+NiJNt07svqsHfldB diff --git a/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc b/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc index 98468baf2d8e9a2185386cc9e18ff2a2dee4b230..3c53d9e73961d0b0b0dd0bb55a091649ce4c0b70 100644 GIT binary patch delta 11706 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 delta 5131 zcmcgwdvH`&8NX*=B-v~pWOwr*2{(i!yAYo8NMaz_gcL_0)sdO1-*@ir=7~5? z?49J>bH4MPbAI1<&iCE#yXT%U-neYY8noFm1bF&yI|EhEAI>Tu_ES|mNVh?d3kQJz zT^!^O5e&j=!EfCw_%jC#FarPj(^?MFLg(yc-*5A0jgzR5EBvJ0;LqMmS}Wx$e@+*l zO^aYA>2xJ$La&nV%08N%&(<2OB$sV69Io^w1@L!TqVr(~G`HbXF>bJtbP0nn$__Ow!TW!#`^4(3XS^k{8LTk34tQXvQu~&>PV#$JaXbyYF4y6&d#To5huHmUP&rr#tg}1DZ&XeVx3ucOfax1uZd9Bm$#S{ zuxojqnKN-zL{f%i7Lsx#X-HBHU(zJH1e*qb zJFRAi?Rgd_cI~Wj(9Yhl&m=9e-`Ia)0H|m3=i5CnrltcClMi*WzvtJJUbdvbXDWaZ z_NLVtJ5^vdkRo=rXs~-Io#Oe^`+~+(jaz(5C=}2rhdu4Su;ZD2IVfwgcUx4ZyJ$5` zRq?bEXeh^C?DZz3B2;GY%~(zj#2m#rd0_R*1xM3J9(#THJ8%$(YaSdK*i+BiJ_u1Hfs(rnx<+NE&T}HrJRg=K=vWEYT)pqE2zEyi@i?H8~`OchQG| z<96tVsYMYy)VNtwy5#V`!2wWYRNrCbl zRo2AOW9+M;{BA1k@cKdkLX&Ag3VMBts*M&BtEmqKg3=a+@=c{d`U8h=$$OqEC^6vg(sv zE){N&PZMGNsO$|#Lt;BsLSoS8^M+Iatis;njt&{F&P!bmCrn-Fc8l(@;MvbZ&H!SI zJV-|nn5bilIZC4xZ=y!5zUxy{qD}qe+Q3nOr}zUtEwEi43pZs}(ZyceS~x9Xem30T zw2iU)lwq8a#3-WW$C||FX_N8OlfM4kti<@7i6jL`d^DN5yKY{K}f}Q{@LX}Rq zZeu+G?t1Dz@>`S;(j>KOtmH%+fU-ewrlim4>zMFjvx^;$Zrx=-LiH+9EfUq>Xal}c zZj*E_ju?(`(ZYobC!kg$GL^J|5+1t^?1FGpqY1|z1#+_Wp*#+!&mTkirz$iU;ih{?KUE|4bgbwwAfn$_Lsj%?hg$fLQ&0i2h z5^L$2GbJ&ja?-15~N;oA4muw`aJ&c4qKuOfmi@8BZ`9(lf|KX`J@l?u<*%%~-w4NoeDL@~hL?)e94W5E8sdJkFO`+HUz zzCmI$zcx(B`dH3WjmGX{IQ}w{F9LBJ=oV~mK*EzvOlYYc$P+p116#xM0%5UT3It_; zo#?jF%{clvkhm!VuO^ymN>~92)X#Q{iTKrBv!TmeKWDMX$L=B_9a~O%SaNcv|@XdDvTafy1mH6tw+G? z=s0%NbW#O4c7*M==ztL>9YDhE6vS3F5Y+-i$H|KlDntK?lC_v-b*TZ?cF1A4PGUO_ zIc=6lagq|db?jNf#6FW1`+xcuJXB!|`sSJ9hH!*QeIi-S_VmpvsDoj3lo_BT0ZzQ| zNPi)Fw{I0Ov7-JLNI~pOe~fgW#I?VVWsV9m z@w8-eL%+xVJkaUJ1CHChNvnIkSQtelDuw8NTzDmp8=+ESPY#w_unwjTY;bUiLn8Y@x86q2QI99$|QL*4duK1~V+=_WoH2r52a!s&1kecYlcyxyx}%Z`VwybNCl zeEKXqaeR^ZIyQfT^9(hRqpWwRI1BHTZtN0hu{&CETBCmM zPIj};$^I}@X84fA(!TSWW%>$`02h7C!3e+|i1fmu$?Uh_L_{~pN) zNUkG!3dz$*jw8WaOYb6Chy?3<{W@o23rQjTtA7*NS58IB)|zX~<-