diff --git a/src/equipment/__pycache__/service.cpython-311.pyc b/src/equipment/__pycache__/service.cpython-311.pyc index 0ce524a..c619640 100644 Binary files a/src/equipment/__pycache__/service.cpython-311.pyc and b/src/equipment/__pycache__/service.cpython-311.pyc differ diff --git a/src/equipment/service.py b/src/equipment/service.py index c18b2f1..83ecf29 100644 --- a/src/equipment/service.py +++ b/src/equipment/service.py @@ -2,7 +2,7 @@ import os import logging from typing import Optional, TypedDict, Any -from sqlalchemy import Select, Delete, Float, func, cast, String, text, case +from sqlalchemy import Select, Delete, Float, func, cast, String, text, case, asc, desc from sqlalchemy.orm import selectinload from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.ext.asyncio import AsyncSession @@ -392,16 +392,16 @@ async def get_top_10_economic_life(*, db_session: DbSession, common) -> list[Equ query.add_columns( case( ( - (current_year - Equipment.minimum_eac_year) >= 0, - (current_year - Equipment.minimum_eac_year), + (Equipment.minimum_eac_year - current_year) >= 0, + (Equipment.minimum_eac_year - current_year), ), - else_=0, - ).label("remaining_life") - ) - .filter(Equipment.minimum_eac_year != None) - .filter((Equipment.minimum_eac != None) & (Equipment.minimum_eac != 0)) + else_=0, + ).label("remaining_life") + ) + .filter(Equipment.minimum_eac_year != None) + .filter((Equipment.minimum_eac != None) & (Equipment.minimum_eac != 0)) # .filter((current_year - Equipment.minimum_eac_year) >= 0) - .order_by((current_year - Equipment.minimum_eac_year).desc()) + .order_by(desc("remaining_life")) .order_by(func.abs(Equipment.minimum_eac).desc()) ) @@ -424,8 +424,8 @@ async def get_top_10_replacement_priorities(*, db_session: DbSession, common) -> query.add_columns( case( ( - (current_year - Equipment.minimum_eac_year) >= 0, - (current_year - Equipment.minimum_eac_year), + (Equipment.minimum_eac_year - current_year) >= 0, + (Equipment.minimum_eac_year - current_year), ), else_=0, ).label("remaining_life") @@ -433,7 +433,7 @@ async def get_top_10_replacement_priorities(*, db_session: DbSession, common) -> .filter(Equipment.minimum_eac_year != None) .filter((Equipment.minimum_eac != None) & (Equipment.minimum_eac != 0)) # .filter((current_year - Equipment.minimum_eac_year) >= 0) - .order_by((current_year - Equipment.minimum_eac_year).asc()) + .order_by(asc("remaining_life")) .order_by(func.abs(Equipment.minimum_eac).desc()) ) diff --git a/src/modules/equipment/Eac.py b/src/modules/equipment/Eac.py index c26b432..e91a793 100644 --- a/src/modules/equipment/Eac.py +++ b/src/modules/equipment/Eac.py @@ -5,8 +5,7 @@ import json import sys import os -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) -from config import get_connection +from src.modules.config import get_connection import argparse @@ -187,7 +186,8 @@ class Eac: # final_value adalah PV pada titik proyeksi periods pmt_mnt_cost = -float(npf.pmt(disc_rate, periods, final_value)) - eac_disposal_cost_proyeksi = 0.07 * pmt_mnt_cost + eac_disposal_cost_proyeksi = -npf.pmt(disc_rate, row["seq"], 0, 0.05 * rc_total_cost_0) if row["seq"] > 0 else 0.0 + # menghitung PMT biaya akuisisi # Rumus PMT: PMT = PV * [r(1 + r)^n] / [(1 + r)^n – 1] # dimana PV = rc_total_cost_0, r = disc_rate, n = row["seq"] diff --git a/src/modules/equipment/Prediksi.py b/src/modules/equipment/Prediksi.py index 38ae297..206cdab 100644 --- a/src/modules/equipment/Prediksi.py +++ b/src/modules/equipment/Prediksi.py @@ -15,10 +15,8 @@ from dotenv import load_dotenv import sys import os -from equipment.formula import rc_labor_cost, rc_lost_cost, rc_total_cost - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) -from config import get_connection, get_production_connection +from .formula import rc_labor_cost, rc_lost_cost, rc_total_cost +from src.modules.config import get_connection, get_production_connection import json load_dotenv() @@ -97,6 +95,7 @@ class Prediksi: FROM lcc_equipment_tr_data WHERE assetnum = %s and is_actual=1 + and seq != 0 ; """ cursor.execute(query, (equipment_id,)) @@ -240,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, @@ -299,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}") @@ -713,7 +714,6 @@ class Prediksi: if df is None: print("Data tidak tersedia untuk prediksi.") return - # Mendapatkan tahun proyeksi dari DB par_tahun_target = self.__get_param(assetnum) @@ -771,19 +771,14 @@ class Prediksi: try: # Case untuk kolom yang terkait dengan corrective maintenance (cm) if "cm" in col_lower: - # Tentukan jumlah baris recent yang dianggap actual jika kolom is_actual ada - if "is_actual" in df.columns: - recent_df = df[df["is_actual"] == 1] - recent_n = recent_df.shape[0] - avg_recent = recent_df[column].mean() - print(f"avg_recent: {avg_recent}") - else: - recent_df = df - recent_n = df.shape[0] + + recent_df = df + recent_n = df.shape[0] recent_n = max(1, recent_n) recent_vals = ( - recent_df.sort_values("year", ascending=True) + recent_df + .sort_values("year", ascending=True) .head(recent_n)[column] .dropna() ) @@ -796,18 +791,11 @@ class Prediksi: if recent_vals.empty: avg = 0.0 else: - # Pastikan numeric; jika gagal, pakai mean dari yang bisa dikonversi - try: - avg = float(np.nanmean(recent_vals.astype(float))) - except Exception: - # jika conversion gagal gunakan mean pandas (objek mungkin numeric-like) - avg = float(recent_vals.mean()) - - if "interval" in col_lower: - avg = max(0.0, avg) + 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) + # print(preds) else: # Untuk 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 @@ -944,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__/Eac.cpython-311.pyc b/src/modules/equipment/__pycache__/Eac.cpython-311.pyc index 12655c9..08b9fa6 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 c445d9e..c2dfa40 100644 Binary files a/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc and b/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc differ 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 b4a0bc6..1dee995 100644 Binary files a/src/modules/equipment/__pycache__/insert_actual_data.cpython-311.pyc and b/src/modules/equipment/__pycache__/insert_actual_data.cpython-311.pyc differ diff --git a/src/modules/equipment/__pycache__/run.cpython-311.pyc b/src/modules/equipment/__pycache__/run.cpython-311.pyc index c376c1f..ef5c1fd 100644 Binary files a/src/modules/equipment/__pycache__/run.cpython-311.pyc and b/src/modules/equipment/__pycache__/run.cpython-311.pyc differ diff --git a/src/modules/equipment/insert_actual_data.py b/src/modules/equipment/insert_actual_data.py index 226dd8a..a2b16d5 100644 --- a/src/modules/equipment/insert_actual_data.py +++ b/src/modules/equipment/insert_actual_data.py @@ -8,10 +8,8 @@ from datetime import datetime import sys import os import httpx -from where_query_sql import get_where_query_sql -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) -from config import get_connection, get_production_connection - +from src.modules.config import get_connection, get_production_connection +from .where_query_sql import get_where_query_sql async def fetch_api_data( assetnum: str, year: int, RELIABILITY_APP_URL: str, token: str @@ -110,22 +108,13 @@ def get_recursive_query(cursor, assetnum, worktype="CM"): query = f""" select - DATE_PART('year', a.reportdate) as tahun, - COUNT(DISTINCT a.wonum) as raw_{worktype.lower()}_interval, - sum(a.actmatcost) as raw_{worktype.lower()}_material_cost, - ( - ROUND(SUM(EXTRACT(EPOCH FROM (a.actfinish - a.actstart)) / 3600), 2) - ) AS raw_{worktype.lower()}_labor_time, - CASE - WHEN COUNT(DISTINCT b.laborcode) = 0 THEN 3 - ELSE COUNT(DISTINCT b.laborcode) - END AS raw_{worktype.lower()}_labor_human -from public.wo_maximo as a -LEFT JOIN public.wo_maximo_labtrans AS b - ON b.wonum = a.wonum - {where_query} -group by DATE_PART('year', a.reportdate); -""" + DATE_PART('year', a.reportdate) as tahun, + COUNT(DISTINCT a.wonum) as raw_{worktype.lower()}_interval, + sum(a.actmatcost) as raw_{worktype.lower()}_material_cost + from public.wo_maximo as a + {where_query} + group by DATE_PART('year', a.reportdate); + """ # Eksekusi query dan fetch hasil cursor.execute(query) return cursor.fetchall() @@ -459,7 +448,7 @@ async def insert_ms_equipment_data(): try: connection, connection_wo_db = get_connection() cursor_db_app = connection.cursor(cursor_factory=DictCursor) - query_main = "SELECT DISTINCT(assetnum) FROM ms_equipment_master" + query_main = f"SELECT DISTINCT(assetnum) FROM ms_equipment_master WHERE assetnum = 'A27860'" cursor_db_app.execute(query_main) results = cursor_db_app.fetchall() @@ -570,7 +559,7 @@ async def insert_lcca_maximo_corrective_data(): data_corrective_maintenance = get_recursive_query( cursor_production, assetnum, worktype="CM" ) - print(data_corrective_maintenance) + # print(data_corrective_maintenance) start_year = 2015 end_year = 2056 seq = 0 @@ -870,7 +859,7 @@ async def insert_acquisition_cost_data(): except Exception: pass -async def query_data(): +async def query_data(target_assetnum: str = None): connection = None connection_wo_db = None connection_production_wo = None @@ -950,7 +939,11 @@ async def query_data(): # Query untuk mendapatkan semua data dari tabel `lcc_ms_equipment_data` # query_main = "SELECT * FROM lcc_ms_equipment_data" query_main = "SELECT DISTINCT(assetnum) FROM ms_equipment_master" - cursor.execute(query_main) + if target_assetnum: + query_main += " WHERE assetnum = %s" + cursor.execute(query_main, (target_assetnum,)) + else: + cursor.execute(query_main) # Fetch semua hasil query results = cursor.fetchall() @@ -1046,7 +1039,6 @@ async def query_data(): year=year, labour_cost_lookup=labour_cost_lookup, ) - if not data_exists: cursor.execute( insert_query, diff --git a/src/modules/equipment/run.py b/src/modules/equipment/run.py index 5773e38..0766aa8 100644 --- a/src/modules/equipment/run.py +++ b/src/modules/equipment/run.py @@ -1,17 +1,10 @@ import asyncio import time -# prefer package-relative imports, but allow running this file directly as a script -try: - from src.modules.equipment.insert_actual_data import query_data, insert_lcca_maximo_corrective_data, insert_ms_equipment_data, insert_acquisition_cost_data - from src.modules.equipment.Prediksi import Prediksi, main as predict_run - from src.modules.equipment.Eac import Eac, main as eac_run -except ImportError: - # fallback when there's no parent package (e.g., python run.py) - from insert_actual_data import query_data, insert_lcca_maximo_corrective_data, insert_ms_equipment_data, insert_acquisition_cost_data - from Prediksi import Prediksi, main as predict_run - from Eac import Eac, main as eac_run - +# clean absolute imports +from src.modules.equipment.insert_actual_data import query_data +from src.modules.equipment.Prediksi import Prediksi, main as predict_run +from src.modules.equipment.Eac import Eac, main as eac_run # Panggil fungsi async def main(): diff --git a/src/modules/equipment/where_query_sql.py b/src/modules/equipment/where_query_sql.py index f8bb101..e33d325 100644 --- a/src/modules/equipment/where_query_sql.py +++ b/src/modules/equipment/where_query_sql.py @@ -14,7 +14,7 @@ def get_where_query_sql(assetnum, worktype): ) ) """ - + # EM = Emergency Maintainance return where_query def get_where_query_sql_all_worktype(assetnum):