diff --git a/src/equipment/model.py b/src/equipment/model.py index 8152bde..454c16c 100644 --- a/src/equipment/model.py +++ b/src/equipment/model.py @@ -60,7 +60,6 @@ class EquipmentTransactionRecords(Base, DefaultMixin, IdentityMixin): tahun = Column(Integer, nullable=False) seq = Column(Integer, nullable=False) is_actual = Column(Integer, nullable=False) - acquisition_year_ref = Column(Integer, nullable=True) raw_cm_interval = Column(Float, nullable=False) raw_cm_material_cost = Column(Float, nullable=False) raw_cm_labor_time = Column(Float, nullable=False) diff --git a/src/modules/equipment/Eac.py b/src/modules/equipment/Eac.py index 030804f..0ad48ee 100644 --- a/src/modules/equipment/Eac.py +++ b/src/modules/equipment/Eac.py @@ -18,12 +18,9 @@ class Eac: try: # Mendapatkan koneksi dari config.py connections = get_connection() - if isinstance(connections, tuple): - connection, connection_wo_db = connections - else: - connection = connections - connection_wo_db = None - + connection = ( + connections[0] if isinstance(connections, tuple) else connections + ) if connection is None: print("Database connection failed.") return None @@ -31,15 +28,15 @@ class Eac: # Membuat cursor menggunakan DictCursor cursor = connection.cursor(cursor_factory=DictCursor) - # Query untuk mendapatkan data2 dasar dari LCCA DB + # Query untuk mendapatkan data2 dasar query_inflation_rate = """ select (SELECT value_num / 100 FROM lcc_ms_master WHERE name = 'inflation_rate') as inflation_rate , (SELECT value_num / 100 FROM lcc_ms_master WHERE name = 'discount_rate') as discount_rate - , (SELECT acquisition_cost FROM lcc_ms_equipment_data WHERE assetnum = %s) as rc_total_cost_0 + , (select COALESCE(rc_total_cost,0) from lcc_equipment_tr_data ltd where assetnum = %s and seq = 0) as rc_total_cost_0 , (SELECT value_num / 100 FROM lcc_ms_master WHERE name = 'history_inflation_rate') as history_inflation_rate , (SELECT value_num / 100 FROM lcc_ms_master WHERE name = 'history_future_inflation_rate_annual') as history_future_inflation_rate_annual - , (SELECT acquisition_year FROM lcc_ms_equipment_data WHERE assetnum = %s) as acquisition_year_fallback + , (select COALESCE(forecasting_target_year, 2056) from lcc_ms_equipment_data where assetnum = %s) as forecasting_target_year ; """ cursor.execute(query_inflation_rate, (equipment_id, equipment_id)) @@ -48,36 +45,13 @@ class Eac: if not inflation_rate_result: print("Inflation rate tidak ditemukan.") return None - - # Fetch acquisition year from Maximo/Production DB if available - acquisition_year_maximo = None - if connection_wo_db: - try: - cursor_wo = connection_wo_db.cursor() - query_maximo = """ - SELECT CAST(DATE_PART('year', max(reportdate)) AS INTEGER) - FROM wo_maximo wm - WHERE wm.asset_assetnum = %s AND wm.asset_replacecost > 0 - GROUP BY CAST(DATE_PART('year', reportdate) AS INTEGER) - ORDER BY CAST(DATE_PART('year', reportdate) AS INTEGER) ASC - LIMIT 1 - """ - cursor_wo.execute(query_maximo, (equipment_id,)) - res_wo = cursor_wo.fetchone() - if res_wo and res_wo[0]: - acquisition_year_maximo = res_wo[0] - cursor_wo.close() - except Exception as e: - print(f"Error fetching from maximo DB: {e}") - - # Use Maximo year if available, else fallback - acquisition_year = acquisition_year_maximo if acquisition_year_maximo else inflation_rate_result["acquisition_year_fallback"] + inflation_rate = inflation_rate_result["inflation_rate"] history_inflation_rate = inflation_rate_result["history_inflation_rate"] history_future_inflation_rate = inflation_rate_result["history_future_inflation_rate_annual"] disc_rate = inflation_rate_result["discount_rate"] rc_total_cost_0 = inflation_rate_result["rc_total_cost_0"] - + forecasting_target_year = inflation_rate_result["forecasting_target_year"] last_seq = 0 last_npv = 0 @@ -85,13 +59,13 @@ class Eac: query_data_actual = """ SELECT assetnum, tahun, seq, is_actual, rc_total_cost FROM lcc_equipment_tr_data - WHERE is_actual = 1 AND seq != 0 AND tahun >= %s + WHERE is_actual = 1 AND seq != 0 AND assetnum = %s ORDER BY seq; """ - cursor.execute(query_data_actual, (acquisition_year, equipment_id)) + cursor.execute(query_data_actual, (equipment_id,)) data_actual = cursor.fetchall() - + # Variabel untuk menyimpan hasil NPV dan PMT per baris npv_results = [] cumulative_values = [] # Menyimpan nilai kumulatif hingga baris ke-n @@ -105,36 +79,24 @@ class Eac: value / ((1 + history_inflation_rate) ** (i + 1)) for i, value in enumerate(cumulative_values) ) - # Recalculate seq based on new acquisition year - current_seq = row["tahun"] - acquisition_year - - if current_seq <= 0: - current_seq = 0 # Avoid negative or zero periods for PMT if year <= acquisition_year - # Menghitung PMT biaya maintenance # Rumus PMT: PMT = PV * [r(1 + r)^n] / [(1 + r)^n – 1] - # dimana PV = final_value, r = history_future_inflation_rate, n = current_seq - if current_seq > 0: - pmt_mnt_cost = -npf.pmt(history_future_inflation_rate, current_seq, final_value) - else: - pmt_mnt_cost = 0.0 + # dimana PV = final_value, r = history_future_inflation_rate, n = row["seq"] + pmt_mnt_cost = -npf.pmt(history_future_inflation_rate, row["seq"], final_value) # Menghitung PMT biaya disposal # Rumus PMT: PMT = PV * [r(1 + r)^n] / [(1 + r)^n – 1] - # dimana PV = 0.05 * rc_total_cost_0, r = disc_rate, n = current_seq - # rc_total_cost_0 adalah biaya akuisisi awal - eac_disposal_cost = -npf.pmt(disc_rate, current_seq, 0, 0.05 * rc_total_cost_0) if current_seq > 0 else 0.0 + # dimana PV = 0.05 * rc_total_cost_0, r = disc_rate, n = row["seq"] + # rc_total_cost_0 adalah biaya akuisisi awal (seq = 0) + eac_disposal_cost = -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 = current_seq - # rc_total_cost_0 adalah biaya akuisisi awal + # dimana PV = rc_total_cost_0, r = disc_rate, n = row["seq"] + # rc_total_cost_0 adalah biaya akuisisi awal (seq = 0) # disc_rate adalah discount rate dari database - # current_seq adalah periode ke-n - if current_seq > 0: - pmt_aq_cost = -npf.pmt(disc_rate, current_seq, rc_total_cost_0) - else: - pmt_aq_cost = 0.0 + # row["seq"] adalah periode ke-n + pmt_aq_cost = -npf.pmt(disc_rate, row["seq"], rc_total_cost_0) eac = pmt_mnt_cost + pmt_aq_cost npv_results.append( @@ -169,31 +131,32 @@ class Eac: ), ) - last_seq = row["seq"] # Keep logical linking if needed, but calculations rely on current_seq + last_seq = row["seq"] last_npv = float(final_value) # Commit perubahan connection.commit() # Query untuk mendapatkan data dengan seq dan is_actual = 0 - # Filter by acquisition_year_ref to match current acquisition_year query_data_proyeksi = """ SELECT assetnum, tahun, seq, is_actual, rc_total_cost FROM lcc_equipment_tr_data - WHERE assetnum = %s AND is_actual = 0 - AND (acquisition_year_ref = %s OR acquisition_year_ref IS NULL) + WHERE assetnum = %s AND is_actual = 0 AND tahun <= %s ORDER BY seq; """ - cursor.execute(query_data_proyeksi, (equipment_id, acquisition_year)) + cursor.execute(query_data_proyeksi, (equipment_id, forecasting_target_year)) data_proyeksi = cursor.fetchall() - cumulative_values = [] # Menghitung NPV dan PMT secara bertahap untuk data proyeksi + # NOTE: sebelumnya kode mencoba menggeser PV proyeksi menggunakan npf.pv + sign flips, + # yang dapat menghasilkan nilai pemeliharaan yang sangat besar (meledak). Sebaiknya hitung + # nilai diskonto dari biaya proyeksi menggunakan offset waktu yang benar (last_seq) dan + # tambahkan ke last_npv. Kemudian hitung pembayaran tahunan tingkat (PMT) selama sisa + # jumlah periode (remaining_periods). Ini menjaga nilai pemeliharaan tahunan proyeksi tetap konsisten dan mencegah lonjakan eksponensial. for idx, row in enumerate(data_proyeksi): # Menyimpan nilai kumulatif hingga baris ke-n cumulative_values.append(row["rc_total_cost"]) - # Nilai proyeksi yang didiskontokan menggunakan offset eksponen dari urutan aktual terakhir # sehingga offset tahun berlanjut dari aktual yang sudah diproses. # Rumus NPV: NPV = Σ [Ct / (1 + r)^t] @@ -208,43 +171,37 @@ class Eac: # Total NPV pada titik proyeksi ini = NPV aktual terakhir + biaya proyeksi yang didiskontokan final_value = float(last_npv) + float(discounted_proj) - # Recalculate seq based on new acquisition year - current_seq = row["tahun"] - acquisition_year - if current_seq <= 0: - current_seq = 1 # Fallback, though for projection it should be > 0 - # Gunakan seq penuh (jumlah periode dari akuisisi) untuk menghitung pembayaran tahunan pemeliharaan. # Menggunakan hanya selisih dari seq aktual terakhir # (sisa_periode) mengamortisasi seluruh nilai sekarang selama # sejumlah periode yang sangat kecil untuk proyeksi pertama dan menghasilkan lonjakan. - # Menggunakan current_seq menjaga periode amortisasi konsisten dengan perhitungan lain + # Menggunakan row["seq"] menjaga periode amortisasi konsisten dengan perhitungan lain # dan mencegah lonjakan setelah tahun berjalan. # amortisasi adalah proses pencatatan biaya aset selama masa manfaatnya. - periods = int(current_seq) + periods = int(row["seq"]) if int(row.get("seq", 0)) > 0 else 1 # Menghitung PMT # Rumus PMT: PMT = PV * [r(1 + r)^n] / [(1 + r)^n – 1] - # dimana PV = final_value, r = disc_rate, n = current_seq + # dimana PV = final_value, r = disc_rate, n = row["seq"] # periods adalah jumlah periode # final_value adalah PV pada titik proyeksi periods pmt_mnt_cost = -float(npf.pmt(disc_rate, periods, final_value)) - eac_disposal_cost_proyeksi = -npf.pmt(disc_rate, current_seq, 0, 0.05 * rc_total_cost_0) if current_seq > 0 else 0.0 + 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 = current_seq - # rc_total_cost_0 adalah biaya akuisisi awal + # dimana PV = rc_total_cost_0, r = disc_rate, n = row["seq"] + # rc_total_cost_0 adalah biaya akuisisi awal (seq = 0) # disc_rate adalah discount rate dari database - # current_seq adalah periode ke-n - pmt_aq_cost = -float(npf.pmt(disc_rate, current_seq, rc_total_cost_0)) + # row["seq"] adalah periode ke-n + pmt_aq_cost = -float(npf.pmt(disc_rate, row["seq"], rc_total_cost_0)) eac = float(pmt_mnt_cost) + float(pmt_aq_cost) - npv_results.append( { - "seq": current_seq, + "seq": row["seq"], "year": row["tahun"], "npv": final_value, "pmt": pmt_mnt_cost, @@ -323,7 +280,6 @@ class Eac: } else: positives = [r for r in rslt if float(r.get("eac", 0)) > 0] - if positives: lowest_eac_record = min(positives, key=lambda x: float(x["eac"])) else: diff --git a/src/modules/equipment/Prediksi.py b/src/modules/equipment/Prediksi.py index e897268..92b2a5b 100644 --- a/src/modules/equipment/Prediksi.py +++ b/src/modules/equipment/Prediksi.py @@ -151,7 +151,7 @@ class Prediksi: # connection.close() # Fungsi untuk menyimpan data proyeksi ke database - async def __insert_predictions_to_db(self, data, equipment_id, token, acquisition_year_ref=None): + async def __insert_predictions_to_db(self, data, equipment_id, token): try: connection, connection_wo_db = get_connection() if connection is None: @@ -173,7 +173,6 @@ class Prediksi: id, seq, is_actual, - acquisition_year_ref, tahun, assetnum, rc_cm_material_cost, rc_cm_labor_cost, @@ -185,7 +184,7 @@ class Prediksi: rc_predictive_labor_cost, created_by, created_at ) VALUES ( - %s, %s, 0, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'Sys', NOW() + %s, %s, 0, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'Sys', NOW() ) """ @@ -236,7 +235,7 @@ class Prediksi: check_existence_query = """ SELECT id FROM lcc_equipment_tr_data - WHERE assetnum = %s AND tahun = %s AND is_actual = 0 AND (acquisition_year_ref = %s OR (acquisition_year_ref IS NULL AND %s IS NULL)) + WHERE assetnum = %s AND tahun = %s AND is_actual = 0 """ update_query = """ @@ -257,10 +256,10 @@ class Prediksi: for _, row in data.iterrows(): # Check if data exists - cursor.execute(check_existence_query, (equipment_id, int(row["year"]), acquisition_year_ref, acquisition_year_ref)) + cursor.execute(check_existence_query, (equipment_id, int(row["year"]))) existing_record = cursor.fetchone() - if existing_record: + # print("Update existing record") # Update existing record record_id = existing_record[0] cursor.execute(update_query, ( @@ -281,7 +280,6 @@ class Prediksi: ( str(uuid4()), # id int(max_seq), # seq - int(acquisition_year_ref) if acquisition_year_ref is not None else None, # acquisition_year_ref 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, @@ -835,7 +833,6 @@ class Prediksi: try: # Update acquisition year first acquisition_year_ref = await self.__update_equipment_acquisition_year(assetnum) - # Mengambil data dari database df = self.__fetch_data_from_db(assetnum) if df is None: @@ -846,6 +843,7 @@ class Prediksi: # Tahun proyeksi future_years = list(range(df["year"].max() + 1, par_tahun_target + 1)) + print("future_years", future_years) # Hasil prediksi predictions = {"year": future_years} @@ -1090,7 +1088,7 @@ class Prediksi: # Insert hasil prediksi ke database try: await self.__insert_predictions_to_db( - predictions_df, assetnum, token, acquisition_year_ref + predictions_df, assetnum, token ) except Exception as e: print(f"Error saat insert data ke database: {e}") diff --git a/src/modules/equipment/__pycache__/Eac.cpython-311.pyc b/src/modules/equipment/__pycache__/Eac.cpython-311.pyc index 35cc311..3371312 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 4eed65b..a3c4aa3 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/insert_actual_data.py b/src/modules/equipment/insert_actual_data.py index 0b3ecc5..0748136 100644 --- a/src/modules/equipment/insert_actual_data.py +++ b/src/modules/equipment/insert_actual_data.py @@ -940,9 +940,14 @@ async def query_data(target_assetnum: str = None): # 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" + query_main = """ + SELECT em.assetnum, ed.forecasting_start_year, ed.acquisition_year + FROM ms_equipment_master em + LEFT JOIN lcc_ms_equipment_data ed ON em.assetnum = ed.assetnum + WHERE ed.assetnum IS NOT NULL + """ if target_assetnum: - query_main += " WHERE assetnum = %s" + query_main += " AND em.assetnum = %s" cursor.execute(query_main, (target_assetnum,)) else: cursor.execute(query_main) @@ -966,8 +971,14 @@ async def query_data(target_assetnum: str = None): if not assetnum or str(assetnum).strip() == "": print(f"[{idx}/{total_assets}] Skipping empty assetnum") continue - forecasting_start_year = row["forecasting_start_year"] - 1 if row["forecasting_start_year"] else 2014 - # forecasting_start_year = 2014 + + forecasting_start_year_db = row.get("forecasting_start_year") + acquisition_year = row.get("acquisition_year") + + if forecasting_start_year_db: + forecasting_start_year = forecasting_start_year_db + else: + forecasting_start_year = (acquisition_year-1) if acquisition_year else 2014 asset_start = datetime.now() processed_assets += 1