From 0e954d2dc0532e5615c21b83090ad931fa169c56 Mon Sep 17 00:00:00 2001 From: MrWaradana Date: Tue, 23 Dec 2025 15:27:37 +0700 Subject: [PATCH] feat: Implement upsert functionality for prediction data, updating existing records or inserting new ones, and remove the previous deletion of all predictions. --- src/modules/equipment/Prediksi.py | 116 +++++++++--------- .../__pycache__/Prediksi.cpython-311.pyc | Bin 40721 -> 43567 bytes 2 files changed, 59 insertions(+), 57 deletions(-) diff --git a/src/modules/equipment/Prediksi.py b/src/modules/equipment/Prediksi.py index bd69dae..206cdab 100644 --- a/src/modules/equipment/Prediksi.py +++ b/src/modules/equipment/Prediksi.py @@ -239,57 +239,39 @@ class Prediksi: # print(f"HTTP error occurred: {e}") # return {} - # Menyiapkan data untuk batch insert - # print(f"Data to be inserted: {data}") + # Menyiapkan data untuk batch insert atau update records_to_insert = [] + + check_existence_query = """ + SELECT id FROM lcc_equipment_tr_data + WHERE assetnum = %s AND tahun = %s AND is_actual = 0 + """ + + update_query = """ + 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_oh_material_cost = %s, + rc_oh_labor_cost = %s, + rc_predictive_material_cost = %s, + rc_predictive_labor_cost = %s, + updated_by = 'Sys', + updated_at = NOW() + WHERE id = %s + """ + for _, row in data.iterrows(): - max_seq = max_seq + 1 - # (token already stored before defining fetch_api_data) - # maintain previous cm_interval between iterations using attribute on fetch_api_data - # if not hasattr(fetch_api_data, "prev_cm"): - # fetch_api_data.prev_cm = None - - # Update values from API (current year) - # api_data = await fetch_api_data(equipment_id, row["year"]) - # if api_data and "data" in api_data and isinstance(api_data["data"], list) and len(api_data["data"]) > 0: - # try: - # cur_cm = float(api_data["data"][0].get("num_fail", row.get("cm_interval", 1))) - # except Exception: - # cur_cm = float(row.get("cm_interval", 1)) if not pd.isna(row.get("cm_interval", None)) else 1.0 - # else: - # try: - # val = float(row.get("cm_interval", 1)) - # cur_cm = val if val >= 1 else 1.0 - # except Exception: - # cur_cm = 1.0 - - # Determine previous cm_interval: prefer stored prev_cm, otherwise try API for previous year, else fallback to cur_cm - # if fetch_api_data.prev_cm is not None: - # prev_cm = float(fetch_api_data.prev_cm) - # else: - # try: - # api_prev = await fetch_api_data(equipment_id, int(row["year"]) - 1) - # if api_prev and "data" in api_prev and isinstance(api_prev["data"], list) and len(api_prev["data"]) > 0: - # prev_cm = float(api_prev["data"][0].get("num_fail", cur_cm)) - # else: - # # attempt to use any available previous value from the row if present, otherwise fallback to current - # prev_cm = float(row.get("cm_interval", cur_cm)) if not pd.isna(row.get("cm_interval", None)) else cur_cm - # except Exception: - # prev_cm = cur_cm - - # compute difference: current year interval minus previous year interval - # try: - # cm_interval_diff = float(cur_cm) - float(prev_cm) - # except Exception: - # cm_interval_diff = 0.0 - - # append record using the difference for raw_cm_interval - records_to_insert.append( - ( - str(uuid4()), # id - int(max_seq), # seq - int(row["year"]), - equipment_id, + # Check if data exists + cursor.execute(check_existence_query, (equipment_id, int(row["year"]))) + existing_record = cursor.fetchone() + + if existing_record: + # Update existing record + record_id = existing_record[0] + cursor.execute(update_query, ( float(row.get("rc_cm_material_cost", 0)) if not pd.isna(row.get("rc_cm_material_cost", 0)) else 0.0, float(row.get("rc_cm_labor_cost", 0)) if not pd.isna(row.get("rc_cm_labor_cost", 0)) else 0.0, float(row.get("rc_pm_material_cost", 0)) if not pd.isna(row.get("rc_pm_material_cost", 0)) else 0.0, @@ -298,16 +280,36 @@ class Prediksi: float(row.get("rc_oh_labor_cost", 0)) if not pd.isna(row.get("rc_oh_labor_cost", 0)) else 0.0, float(row.get("rc_predictive_material_cost", 0)) if not pd.isna(row.get("rc_predictive_material_cost", 0)) else 0.0, float(row.get("rc_predictive_labor_cost", 0)) if not pd.isna(row.get("rc_predictive_labor_cost", 0)) else 0.0, + record_id + )) + else: + max_seq = max_seq + 1 + # Prepare for insert + records_to_insert.append( + ( + str(uuid4()), # id + int(max_seq), # seq + int(row["year"]), + equipment_id, + float(row.get("rc_cm_material_cost", 0)) if not pd.isna(row.get("rc_cm_material_cost", 0)) else 0.0, + float(row.get("rc_cm_labor_cost", 0)) if not pd.isna(row.get("rc_cm_labor_cost", 0)) else 0.0, + float(row.get("rc_pm_material_cost", 0)) if not pd.isna(row.get("rc_pm_material_cost", 0)) else 0.0, + float(row.get("rc_pm_labor_cost", 0)) if not pd.isna(row.get("rc_pm_labor_cost", 0)) else 0.0, + float(row.get("rc_oh_material_cost", 0)) if not pd.isna(row.get("rc_oh_material_cost", 0)) else 0.0, + float(row.get("rc_oh_labor_cost", 0)) if not pd.isna(row.get("rc_oh_labor_cost", 0)) else 0.0, + float(row.get("rc_predictive_material_cost", 0)) if not pd.isna(row.get("rc_predictive_material_cost", 0)) else 0.0, + float(row.get("rc_predictive_labor_cost", 0)) if not pd.isna(row.get("rc_predictive_labor_cost", 0)) else 0.0, + ) ) - ) - - # store current cm for next iteration - # fetch_api_data.prev_cm = cur_cm - # Eksekusi batch insert - cursor.executemany(insert_query, records_to_insert) + # Eksekusi batch insert jika ada data baru + if records_to_insert: + cursor.executemany(insert_query, records_to_insert) + connection.commit() - # print("Data proyeksi berhasil dimasukkan ke database.") + + # Recalculate total costs and update asset criticality + self.__update_data_lcc(equipment_id) except Exception as e: print(f"Error saat menyimpan data ke database: {e}") @@ -930,7 +932,7 @@ class Prediksi: predictions_df = pd.DataFrame(predictions) # print(predictions_df) # Hapus data prediksi yang ada sebelumnya - self.__delete_predictions_from_db(assetnum) + # self.__delete_predictions_from_db(assetnum) # Insert hasil prediksi ke database try: diff --git a/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc b/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc index 511bc59a95a97b74a9e136b1e88199ddb0a2b49e..c2dfa406cf957954f5a880dedabf86428f6b3555 100644 GIT binary patch delta 3726 zcmeHKTX0jy89sYt$+9fT$2Uo~C3Nu(85?Y%K*4|wHonOhfiW1H2w6Hd3bG6<2{4h7 zxrFjSh{Mj9lGwyeNnj?^LaUcfGl3)|l(dwboC70<`o-l1X)7!2Tz;WVE zUow5^O6S}E^6kH?|KHg?dhRlPQJ_`lRVoFAo4KXlllL=Ke>Qzhh4}57$l2_2pm0kE zlHIZaWeA`hC3)&9YDl_$u#={!0o8yS?as+cPY%(6bP^!mLTYy+ve)Y9ZW^7;S*9CE z38f6t@K7kl4YD4Rr~!tI1xhrk)JoK2O2k)W0y#1n=B4>_138a)RA{Et43cIJK+W@v zbI`HNGK+kFZu0$aki*6~BZrN%BWq1_lC`GUIcSCY_>H%yRkcSjwYGlsHPzQ_;DHIhjBd1b_ynEFNq}Hf( zp^o|1qKD|21^RzQzw3(TBy}ra0l1#{i8owN>50Q31=OHHb1NO9P&BAn5u#_aeAZkl zF#q8wipry)ejqiF_}Jo33#B4nla-u$Fx4k_Qv$ z6cm9>G*6d{juoWr5G5X80G4BinBejIoT8+M4T#ciZ=W*&YlvCm4?sv#ASSc>SywQ? z_Bwt2&`vDbjuyzeJ-hrKhaVl)gZn1AJ>xXbtkV zy@G8orw@--kL8BX7rs=;6|H?EmA9-HEbDoFm7uTU@HZ5`UDt3!%h_8e>|4j}TexFjYlY)6*2S9c-W<#5=5Q1~o(@9i|#u=*Nk!xsFTK zAXplBy-m>DIK2%s8tE4LwtbU8%G5d0zGJ+72j_f>8#u_fhlTbqj<^w2l8B@u5$nH- z7$()PdIQ(4R}g3#rse^KAxdC6Cgo9#IZ6Rl6_{i+ z6O3h?v5b{-W$Somy}+#JnDtXi&4ki0t~BsUqo6c$N@G+;sZuADmT@Jf^2$;{S;{Hp z_$mGJ;}jigUNE|3TLSf_VQT^IL<#6f#3zwrTaiRmr)|rZh$bnq?zQN_+@4-@mh5-n~;hLln@A`q~(QR zKPp?k7_;5Wo0IlovtNe+-D%dM-z{$jm66;Pzh!_6P1g>CtC8b%Pf9=_@`bGiFoPsU z7l9oFT&TXOXXR-uit>jinh2ms_d`*R&q~M6z959p6Y2{DMhJ`&I7{F~^m)@|8Cd{c z*Tv?-q+u-j^DyAGm`}AV2S1LiXt5-#j}l5Pf!Bz$F*O?RqC2Fn^$7Kxn z@q^o%3M+oYLI=Vdp_$l%X4uYx3XM;=V?|ndqCdFs^kc^K_EF}_|DK%?8cJS5KE~jcl!Noz!&U=J*ewgnej91 zgMT6LC4ooV2-Xwz9{TyQ3h*iV;@CCWH5zZfw#bzqxDxPxk?)`QyWGBt`pLR7`zo~h znN4A=+%y=Q)=F=d(?nhah`hGafECVQXu-G=5P6ltZkF)UG`n8HYo*x2>!rkE))9G? zVxvVeE=}8Lkc=CoScdEHLhzP-overKV{57?cQ|~`Ue@6d6%GfkOwh}sl#?161u*Ad zXu-)mbpPqXBA7z-m&rhq0!S0pO{75rWMjkk37FBrlZVqCv@|zL&A@fiNcyvXmC3pY zhe6Enc3dgnaDCk1Mwf@RGKR$LLAQp>K_AkcE|o6DhF?Y+Poo6z5xVojTi|VUZo~!- qq3Mxy8F4}jQjA(aZKPy$4Jg~L1`DIqx8roo{$QrJ!hlB32LBCn=~zPm delta 2381 zcmah~4Qvx-7{2dry>{E()(+UZ!Zx5mm)@8~(5fmYs7|j+n#=q$IUNbZb?wWh<`JVTC zzW2Vn_r1P0%J-b+<(FhRg=5EhX=PyGb-CTar&zS798H9e5;(gpv$r^HRxQ`fZ$7n& z=eW+)PAO{fn>v`I(-KYTPlo*I)6>)$xJ2Smv%o9a;ln*?yqXR3raP=^_GFCXqLN>P zclX#GQC^+-x4bQFl1C+YcugknH}^X8<@`w%jW_h%Ss~)L{J%Z<7IvmjZHM8U+17|t z{ZD!paAyB+X1L_Mh4qqi3TtoP&7Hk@Qy4*-dvhlXOl2*feG6;(?5V6(g}1O)6*^~H znx*4}<6dEB;vlSs_MVA~{WexHGhklPlERS7$*F~t6F6=TM`v})s`+olZ;i?j&&*Dh zyX26?&vmjZ=tYkZo7Pl(l{Zl=T{v0t4(u$wjZBAJyAv*yrq|Lsrt3mPr4(1wg+NH9 zRmhS8k&ssxS`<|`H3!39RZmrRD!w*VY4wKMX(O9M=P_`l!NM}1jd*l{h9997Op0KQ z!uMtC)0QN0+&Ap^C^yb;;^7N>9oY)YH_aysd|2MFWf{-qJBOv5F-Pu^l0zl2vc9r$ zZn2y(fzTs%lkNp$j{N@Rv3cF*@g~CA3NBiUN36xy?K2PG8LR87i%EUb=k}QgZ;%!J zVqbmSUOZ|q9<$GZBksqc-~E`@?=B(>$Ft=bnG-^~E#pUy!8pQ%!EWQ^X&0s35h?dz zbhu<`T&fH4TJ9>534k)()@u$V&XM{l5L=tj6EJO(0>dbzL2? z?n0jr!HeLBf%Pq<7v^usb3Bj2euM)E2N7Zjhd|kITBJxjz_qc6^g!drE##>7#>Rrw z)ZM7xjqoO1X?}pzYn3e(=FGQId5gi<>}1E%WiFmZp(|8NPQ&@oK5LYTbPht|p5UQy zm6&kB3*nZm0T$}8-a{3CV0$D`8VR(7Jb_S`R(*YcajaIH@ebOySBy&>T6Qyf~F z!&LEkS$ox@_BjKWnO;G@8sn*cuj=(g6dF(>^eQUJtzo|s^fU)ldJW!s_EBd2l58-UA!Nev+s_al$!wcSE(5NH!F$jizM!P3g#zeZPhJyjNwL91V>SIMv z*)z9HcX%e>smIf{gY8f87@G|Ge6Aeh`Rf*MB%-LHwpQ81k*pRJ4 zxB>$`%g8k_^CTqMLD8I2V}e7*^l z3KJ@e?2M0CQ<^G;5l2RoTNrVh7^gMt_E^=Q#Jj{+aM^T=#}o3lDjtuX;_yARSDT2$hIID-Ga?Q;|RIX7khl#X5Qo+=l+3RNm|9B zpGDCqe;G=TZYH^~_vk98A>cx~iM^V@4x{i~GM$Et$(X7cemPo2g0SdVxv889C$;Uz zAc^Sk=kaso44ivuwZmv28#=NO3@=Wlc~E?!fYfUBCzg_mjcKHGoVz(r82XR@j7Dd{ I+b_5M1#5C(8~^|S