From 1f38f1a80f85d194a37c89a3adb876efaf88d613 Mon Sep 17 00:00:00 2001 From: MrWaradana Date: Thu, 29 Jan 2026 15:15:50 +0700 Subject: [PATCH] feat: Standardize equipment API responses to `EquipmentDataMaster` and optimize LCC calculations by pre-fetching parameters and including new material cost fields. --- src/equipment/router.py | 7 +- src/equipment/schema.py | 1 - src/modules/equipment/Prediksi.py | 244 +++++++++--------- .../__pycache__/Prediksi.cpython-311.pyc | Bin 48359 -> 50135 bytes .../insert_actual_data.cpython-311.pyc | Bin 51955 -> 51862 bytes .../equipment/__pycache__/run.cpython-311.pyc | Bin 2998 -> 2998 bytes src/modules/equipment/insert_actual_data.py | 8 +- 7 files changed, 130 insertions(+), 130 deletions(-) diff --git a/src/equipment/router.py b/src/equipment/router.py index c78ac63..23c7aa0 100644 --- a/src/equipment/router.py +++ b/src/equipment/router.py @@ -6,6 +6,7 @@ import json from src.equipment.model import Equipment, EquipmentTransactionRecords from src.equipment.schema import ( EquipmentBase, + EquipmentDataMaster, EquipmentPagination, EquipmentRead, EquipmentCreate, @@ -248,7 +249,7 @@ async def get_equipment(db_session: DbSession, collector_db_session: CollectorDb ) -@router.post("", response_model=StandardResponse[EquipmentCreate]) +@router.post("", response_model=StandardResponse[EquipmentDataMaster]) async def create_equipment( db_session: DbSession, equipment_in: EquipmentCreate, @@ -263,7 +264,7 @@ async def create_equipment( return StandardResponse(data=equipment, message="Data created successfully") -@router.put("/{assetnum}", response_model=StandardResponse[EquipmentUpdate]) +@router.put("/{assetnum}", response_model=StandardResponse[EquipmentDataMaster]) async def update_equipment( db_session: DbSession, assetnum: str, @@ -291,7 +292,7 @@ async def update_equipment( ) -@router.delete("/{equipment_id}", response_model=StandardResponse[EquipmentBase]) +@router.delete("/{equipment_id}", response_model=StandardResponse[EquipmentDataMaster]) async def delete_equipment(db_session: DbSession, equipment_id: str): equipment = await get_by_id(db_session=db_session, equipment_id=equipment_id) diff --git a/src/equipment/schema.py b/src/equipment/schema.py index eebd2c4..0ede5a4 100644 --- a/src/equipment/schema.py +++ b/src/equipment/schema.py @@ -95,7 +95,6 @@ class EquipmentCreate(EquipmentBase): class EquipmentUpdate(EquipmentBase): pass - class EquipmentRead(DefaultBase): equipment_master_record: EquipmentMasterBase equipment_data: EquipmentBase diff --git a/src/modules/equipment/Prediksi.py b/src/modules/equipment/Prediksi.py index c4fd0a7..b23dd56 100644 --- a/src/modules/equipment/Prediksi.py +++ b/src/modules/equipment/Prediksi.py @@ -38,10 +38,7 @@ class Prediksi: def __get_param(self, equipment_id): try: # Mendapatkan koneksi dari config.py - connections = get_connection() - connection = ( - connections[0] if isinstance(connections, tuple) else connections - ) + connection, connection_wo_db = get_connection() if connection is None: print("Database connection failed.") return None @@ -69,10 +66,7 @@ class Prediksi: def __fetch_data_from_db(self, equipment_id): try: # Get connection from config.py (using only the first connection) - connections = get_connection() - connection = ( - connections[0] if isinstance(connections, tuple) else connections - ) + connection, connection_wo_db = get_connection() if connection is None: print("Database connection failed.") return None @@ -159,10 +153,7 @@ class Prediksi: # Fungsi untuk menyimpan data proyeksi ke database async def __insert_predictions_to_db(self, data, equipment_id, token): try: - connections = get_connection() - connection = ( - connections[0] if isinstance(connections, tuple) else connections - ) + connection, connection_wo_db = get_connection() if connection is None: print("Database connection failed.") return None @@ -320,11 +311,8 @@ class Prediksi: def __get_asset_criticality_params(self, equipment_id): try: - connections = get_connection() + connection, connection_wo_db = get_connection() efdh_foh_sum = None - connection = ( - connections[0] if isinstance(connections, tuple) else connections - ) if connection is None: print("Database connection failed.") return None @@ -418,28 +406,42 @@ class Prediksi: # Fungsi untuk menghapus data proyeksi pada tahun tertentu def __update_data_lcc(self, equipment_id): try: - connections = get_connection() + connection, connection_wo_db = get_connection() production_connection = get_production_connection() - connection = ( - connections[0] if isinstance(connections, tuple) else connections - ) - production_connection = ( - production_connection[0] if isinstance(production_connection, tuple) else production_connection - ) if connection is None or production_connection is None: print("Database connection failed.") return None cursor = connection.cursor(cursor_factory=DictCursor) + # --- OPTIMIZATION START: Fetch static data ONCE --- + + # Fetch man_hour_rate (constant for all years currently based on code logic) + man_hour_rate = 0.0 + try: + prod_cur = production_connection.cursor() + prod_cur.execute("""SELECT value_num FROM lcc_ms_master WHERE name='manhours_rate'""") + r2 = prod_cur.fetchone() + if r2 and r2[0] is not None: + man_hour_rate = float(r2[0]) + prod_cur.close() + except Exception: + pass + + # Fetch Asset Criticality (constant for the asset) + asset_criticality_data = self.__get_asset_criticality_params(equipment_id) + ac = asset_criticality_data if isinstance(asset_criticality_data, dict) else {} + asset_criticality_value = float(ac.get("asset_criticality", 0.0)) + + # --- OPTIMIZATION END --- + # Ambil semua baris untuk assetnum select_q = ''' SELECT id, seq, tahun, - raw_cm_interval, raw_cm_material_cost, rc_cm_labor_cost, - raw_pm_interval, raw_pm_material_cost, rc_pm_labor_cost, - raw_predictive_interval, raw_predictive_material_cost, rc_predictive_labor_cost, - raw_oh_interval, raw_oh_material_cost, rc_oh_labor_cost, - raw_predictive_interval, raw_predictive_material_cost, rc_predictive_labor_cost, + raw_cm_interval, raw_cm_material_cost, rc_cm_labor_cost, rc_cm_material_cost, + raw_pm_interval, raw_pm_material_cost, rc_pm_labor_cost, rc_pm_material_cost, + raw_predictive_interval, raw_predictive_material_cost, rc_predictive_labor_cost, rc_predictive_material_cost, + raw_oh_interval, raw_oh_material_cost, rc_oh_labor_cost, rc_oh_material_cost, efdh_equivalent_forced_derated_hours, foh_forced_outage_hours FROM lcc_equipment_tr_data WHERE assetnum = %s; @@ -447,24 +449,6 @@ class Prediksi: cursor.execute(select_q, (equipment_id,)) rows = cursor.fetchall() - # Helper to get man_hour for a year (fallback to master 'manhours_rate') - def _get_man_hour_for_year(year): - try: - # cur = connection.cursor() - prod_cur = production_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'") - prod_cur.execute("""SELECT value_num FROM lcc_ms_master WHERE name='manhours_rate'""") - r2 = prod_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, @@ -479,15 +463,15 @@ class Prediksi: updated_by = 'Sys', updated_at = NOW() WHERE id = %s; ''' + + batch_params = [] for r in rows: try: - yr = r.get("tahun") if isinstance(r, dict) else r[2] - man_hour = _get_man_hour_for_year(yr) - - seq = int(r.get("seq") or 0) if isinstance(r, dict) else int(r[1] or 0) - + # yr = r.get("tahun") if isinstance(r, dict) else r[2] + # man_hour = man_hour_rate # Used pre-fetched value + # seq = int(r.get("seq") or 0) if isinstance(r, dict) else int(r[1] or 0) raw_pm_material_cost = float(r.get("raw_pm_material_cost") or 0.0) raw_predictive_material_cost = float(r.get("raw_predictive_material_cost") or 0.0) @@ -506,39 +490,21 @@ class Prediksi: rc_oh_labor = float(r.get("rc_oh_labor_cost") or 0.0) 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 + rc_pm_material = raw_pm_material_cost if raw_pm_material_cost > 0 else float(r.get("rc_pm_material_cost") or 0.0) except Exception: rc_pm_material = 0.0 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 + rc_predictive_material = raw_predictive_material_cost if raw_predictive_material_cost > 0 else float(r.get("rc_predictive_material_cost") or 0.0) except Exception: rc_predictive_material = 0.0 - rc_oh_material = raw_oh_material_cost + rc_oh_material = raw_oh_material_cost if raw_oh_material_cost > 0 else float(r.get("rc_oh_material_cost") or 0.0) - 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 {} efdh_foh_sum = efdh_equivalent_forced_derated_hours + foh_forced_outage_hours if efdh_equivalent_forced_derated_hours and foh_forced_outage_hours else 0.0 - # try: - # efdh_foh_sum = float(ac.get("efdh_foh_sum", 0.0)) - # except Exception: - # efdh_foh_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_foh_sum * asset_criticality_value @@ -551,24 +517,26 @@ class Prediksi: 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, - total, - id_val, - ), - ) + batch_params.append(( + rc_cm_material, + rc_cm_labor, + rc_pm_material, + rc_pm_labor, + rc_predictive_material, + rc_predictive_labor, + rc_oh_material, + rc_oh_labor, + total, + id_val + )) + except Exception: # ignore row-specific errors and continue continue + + # Execute Batch Update + if batch_params: + cursor.executemany(update_q, batch_params) # For seq=0 rows, set rc_total_cost to acquisition_cost cursor.execute( @@ -584,14 +552,16 @@ class Prediksi: finally: if connection: connection.close() + if 'production_connection' in locals() and production_connection: + try: + production_connection.close() + except Exception: + pass # Fungsi untuk mengambil parameter dari database def __get_rate_and_max_year(self, equipment_id): try: - connections = get_connection() - connection = ( - connections[0] if isinstance(connections, tuple) else connections - ) + connection, connection_wo_db = get_connection() if connection is None: print("Database connection failed.") return None @@ -751,13 +721,10 @@ class Prediksi: print(f"HTTP error occurred: {e}") return {} - def __get_man_hour_rate(self, staff_level: str = "junior"): + def __get_man_hour_rate(self, staff_level: str = "Junior"): connection = None try: - connections = get_connection() - connection = ( - connections[0] if isinstance(connections, tuple) else connections - ) + connection, connection_wo_db = get_connection() if connection is None: return 0.0 @@ -829,6 +796,7 @@ class Prediksi: # Mendapatkan rate dan tahun maksimal rate, max_year = self.__get_rate_and_max_year(assetnum) man_hour_rate = self.__get_man_hour_rate() # Defaults to 'junior' + pmt = 0 # Prediksi untuk setiap kolom @@ -841,7 +809,6 @@ class Prediksi: try: # Case untuk kolom yang terkait dengan corrective maintenance (cm) if "cm" in col_lower: - recent_df = df recent_n = df.shape[0] @@ -852,7 +819,7 @@ class Prediksi: .head(recent_n)[column] .dropna() ) - # print(f"Recent Vals: {recent_vals}") + # Fallback ke semua nilai non-na jika tidak ada recent_vals if recent_vals.empty: recent_vals = df[column].dropna() @@ -865,12 +832,18 @@ class Prediksi: # Interval from number of failures interval = 0.0 if isinstance(failures_data, dict): - val = failures_data.get("data") - if val is not None: - try: - interval = float(val) - except Exception: - interval = 0.0 + data_list = failures_data.get("data") + # data is a list of objects, extract num_fail from first item + if isinstance(data_list, list) and len(data_list) > 0: + first_item = data_list[0] + if isinstance(first_item, dict): + num_fail = first_item.get("num_fail") + if num_fail is not None: + try: + interval = float(num_fail) + print(f"interval for year {yr}: {interval}") + except Exception: + interval = 0.0 # interval * labor_time(3) * labor_human(1) * man_hour_rate cost = rc_labor_cost(interval, 3.0, 1.0, man_hour_rate) @@ -883,11 +856,10 @@ class Prediksi: else: avg = pd.to_numeric(recent_vals, errors="coerce").fillna(0).mean() avg = 0.0 if pd.isna(avg) else float(avg) - preds = np.repeat(float(avg), n_future) - # print(preds) + else: - # Untuk kolom non-cm, gunakan nilai dari last actual year bila ada, + # Для kolom non-cm, gunakan nilai dari last actual year bila ada, # jika tidak ada gunakan last available non-NA value, jika tidak ada pakai 0.0 if "is_actual" in df.columns and not df[df["is_actual"] == 1].empty: last_actual_year_series = df[df["is_actual"] == 1]["year"] @@ -900,6 +872,7 @@ class Prediksi: last_actual_year = int(df["year"].max()) row_vals = df[df["year"] == last_actual_year] + value = None if not row_vals.empty: @@ -920,7 +893,7 @@ class Prediksi: value = 0.0 else: value = 0.0 - + preds = np.repeat(float(value), n_future) except Exception: @@ -1067,8 +1040,7 @@ async def main(RELIABILITY_APP_URL=RELIABILITY_APP_URL, assetnum=None, token=Non return # Otherwise fetch all assetnums from DB and loop - connections = get_connection() - connection = connections[0] if isinstance(connections, tuple) else connections + connection, connection_wo_db = get_connection() if connection is None: print("Database connection failed.") return @@ -1077,17 +1049,37 @@ async def main(RELIABILITY_APP_URL=RELIABILITY_APP_URL, assetnum=None, token=Non query_main = "SELECT DISTINCT(assetnum) FROM ms_equipment_master" cursor.execute(query_main) results = cursor.fetchall() + + # Close connection early as we have the data and don't need it for the async loop + if connection: + connection.close() + connection = None + # Concurrency limit to prevent overwhelming DB/API + MAX_CONCURRENT_TASKS = 10 + sem = asyncio.Semaphore(MAX_CONCURRENT_TASKS) + + total_assets = len(results) + print(f"Starting prediction for {total_assets} assets with {MAX_CONCURRENT_TASKS} concurrent tasks.") + + async def bound_predict(idx, asset): + async with sem: + try: + if not asset or str(asset).strip() == "": + print(f"[{idx}/{total_assets}] Skipping empty assetnum") + return + + print(f"[{idx}/{total_assets}] Predicting assetnum: {asset}") + await prediksi.predict_equipment_data(asset, prediksi.access_token) + except Exception as e: + print(f"Error Predicting {asset}: {e}") + + tasks = [] for idx, row in enumerate(results, start=1): current_asset = row.get("assetnum") if hasattr(row, "get") else row[0] - if not current_asset or str(current_asset).strip() == "": - print(f"[{idx}/{len(results)}] Skipping empty assetnum") - continue - print(f"[{idx}/{len(results)}] Predicting assetnum: {current_asset}") - try: - await prediksi.predict_equipment_data(current_asset, prediksi.access_token) - except Exception as e: - print(f"Error Predicting {current_asset}: {e}") + tasks.append(bound_predict(idx, current_asset)) + + await asyncio.gather(*tasks) print("Selesai.") except Exception as e: @@ -1098,7 +1090,17 @@ async def main(RELIABILITY_APP_URL=RELIABILITY_APP_URL, assetnum=None, token=Non connection.close() if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="Run equipment prediction") + parser.add_argument( + "--assetnum", "-a", + type=str, + default=None, + help="Specific assetnum to predict. If not provided, predicts all assets." + ) + args = parser.parse_args() + asyncio.run( - main() + main(assetnum=args.assetnum) ) - diff --git a/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc b/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc index 47716471e07c0f927814dffa489ac40564b68040..4eed65bad557acd8cf2ad1ac2ef265b7c96fffb7 100644 GIT binary patch delta 13649 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 diff --git a/src/modules/equipment/__pycache__/insert_actual_data.cpython-311.pyc b/src/modules/equipment/__pycache__/insert_actual_data.cpython-311.pyc index e1ed601a17fdaa3cee8571004941fc9d825d6daa..93630fac2ce7f98989af5831bde126b8eb7ac487 100644 GIT binary patch delta 1706 zcmaKse@s(X6vyv*x390gP^|E1p%$>v@rwFFEhsJBSY*#DMFrEZUNh;GBqb%d;Fv&nuT9fi zm87F6n5LxA$p)E@v!24^S=3lA(cb#Bct!hHuU66UUfQ|ACZr#uyVj-~GUhrLDk-er zM$c~W#67ykd;s40l+9)Re{LbG^K&X2^J>KeqI(nmZCG@Bp0(Af@>6Z38u;NkB zNKbTlb+w>xf>~5c$2vAk%fXO3iJngjJ*z5%6^8H%VWU_zwHx8jC_GJWp=4qi_~KDr_f>ZX7NUIJ&4*$tcvG3O$Ybw!qntSeH98IOpjCMz1TlBPU7 zk_${1MP(dt;Ao1e#i@xF0B0v@DVL(90_QT|Twa|QaH#LSSd=LIfz$zIA3(O+@J2Tz z$M|+G;UaOl%w?K>AM(S`s(06306o39d$ni>gpR1XJ*5e-RINHNiov30yk8{1fV%0B zS%9Bt=z~B9?gY3)$?5xXxoo7aJ$&9gI(x>ezl#|zVoBP1y7xcTd;eF3 zUueQ-5B}W)57p|kdJHkTuFnnAG}M=={ej>2BR$z?2M7JOFW)l56A!tV_&hf_4RX0p zlg@322Xz0ro$xD-JHJ}{8&BV*P3O0e70lbtCh9(+4xH~5jW)-r_?tHGkj;D8ci4Bq zM!)ROqrLr~(9-_$Cl=M-{?D~wrq6s+8#1FCX@%>Jn!$O{YDR-bvc6Orv?QqdK`c4UJkM#Dg@_V#K4la&v_+S`M5mNDC38lE65s2hId@Ht>_8^SCE!^l4cz zW`aOI8Z(pDGCXE8wie?tx5zWaBF~gz#^Z@{TMin}A#Gkf?g6ePa=QUd7)Yqyf+tde zYr5R-K@%PlKzO1Om`-ZsfCWujNFWza<^b1xIcULCg4|)&OqqbQnRM8tDI5DmMTtxlbd2$q|+tE^5;xW;Ov?+dF7y9 zGp*yEX*~&=r0FE?nN9}ICQZ;npTF|1Si_d$WA)(`8(8>{wTpjqU3^n+>)P731-5Q! eYwN0^#RHkppf(L?g+#vqi)PSc+CyKxw&zd#6!eV% delta 1802 zcmai!ZA?>F7{||ZPw&0-mTC)^x6&3$p)d+m5XGWkC=(?*d2uKRwovLa-!OwMae7JT zKKQ{coAZRdVGJb3ZQ0B)7Z;;jqAuz*n;=127y|^{;*1Gwx|eLuom2dVb;YB5v$7lKjdHGW|>dhSjwj z4+AaJn|CI$I2!@eE}mW6^-7{*2qf>P3+;l4!v&Jt76qU!`pFD6cWIu?KpO;-m1M=N zSe}bI$szPH{VfbY3L;0k9nlI;m(wHE$t#%jR>wyvu}WgVe2gOp*IPu365EXeR`No1 zKA1^wZAG*qJjn}+hUJN_#>S-_Bd@Ht>Qf_Wo03R-(nv$iQr*JR2}j`d5q2bUz9vp< zkEGI-6moi#lW1yXew%h-H%{_(wS{c2wc`xMO8QFTz^wMxp3;Crt=baC{M~4}d6L<9 z2`uEt#@EC#e$U>v7oS&OYB~qfV|FllzK%=8?Og)3WT$rx>{I)^)c}uI5t>I(E~g7r3ZQ5Rp9 zhAyT_WiEU%52#)?l`p3g*W~z0C%Wd8Dp%lZML_i%yC;QC4C1Hro-NP}=L0Q<3#831 zakxaTixF-aVRN>4D@LkI(cUt1jIF>prA(m3NCredEK7IJ_MtG!N@6%$KCBtey+#y1Ln(#fl^A9AiKOHj3rv6;h zPt174B#~|62BQHczmWI)Uxr_ae5MorAV<%v6aHfMJefMPU8 z)W_#_K(FNS`2qFr7qJ3})&^V~iOK9L!(N=?Q5coE0g4UCI1;}W*g_-dB<4wVVCm=K`F zf+j@C>%%uJae3w@+2qw63}FV@RSyqE|Qz= zXv!`%FTqnTU|J}*Na(gCwIt%(3Bc4M2PAyEK=zB8X#prti;`a!r;QxrSYRw;#)+~& zS2LXx$>d7@VsW}Cl5qp$;z*`cZY_`1%*3$JOsv$JD$ZC~XeI?1r?SvYy4>p4%&drH z+)_XgXEDpn^1xWo1SF!n@&;c?|M8RR@hdhkvF~g0?+3xeTSCY}I ZX;7#Bc~#(IwsMd?hyJ1ah diff --git a/src/modules/equipment/insert_actual_data.py b/src/modules/equipment/insert_actual_data.py index 5f587fd..f7ad447 100644 --- a/src/modules/equipment/insert_actual_data.py +++ b/src/modules/equipment/insert_actual_data.py @@ -606,9 +606,7 @@ async def insert_lcca_maximo_corrective_data(): year, # tahun seq, # seq ( - (1 if year <= current_year - 1 else 0) - if datetime.now().month >= 12 - else (0 if year > current_year + 1 else 1) + 1 if year < current_year else 0 ), # is_actual raw_cm_material_cost, # raw_cm_material_cost raw_cm_labor_time, # raw_cm_labor_time @@ -1053,7 +1051,7 @@ async def query_data(target_assetnum: str = None): assetnum, year, seq, - 1, + 1 if year < current_year else 0, row_values["raw_cm_interval"], row_values["raw_cm_material_cost"], row_values["raw_cm_labor_time"], @@ -1086,7 +1084,7 @@ async def query_data(target_assetnum: str = None): update_query, ( seq, - 1, + 1 if year < current_year else 0, row_values["raw_cm_interval"], row_values["raw_cm_material_cost"], row_values["raw_cm_labor_time"],