You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
368 lines
16 KiB
Python
368 lines
16 KiB
Python
import pandas as pd
|
|
import numpy as np
|
|
import numpy_financial as npf # Gunakan numpy-financial untuk fungsi keuangan
|
|
from statsmodels.tsa.holtwinters import ExponentialSmoothing
|
|
from sklearn.linear_model import LinearRegression
|
|
from sklearn.tree import DecisionTreeRegressor
|
|
import matplotlib.pyplot as plt
|
|
from uuid import uuid4
|
|
from modules.config import get_connection
|
|
from psycopg2.extras import DictCursor
|
|
|
|
class Prediksi:
|
|
def __init__(self):
|
|
pass
|
|
|
|
# Fungsi untuk mengambil data dari database
|
|
def __get_param(self, equipment_id):
|
|
try:
|
|
# Mendapatkan koneksi dari config.py
|
|
connection = get_connection()
|
|
if connection is None:
|
|
print("Koneksi ke database gagal.")
|
|
return None
|
|
# Membuat cursor menggunakan DictCursor
|
|
cursor = connection.cursor(cursor_factory=DictCursor)
|
|
# Query untuk mendapatkan data
|
|
query = """
|
|
SELECT
|
|
(select COALESCE(forecasting_target_year, 2060) from lcc_ms_equipment_data where equipment_id = %s) AS forecasting_target_year
|
|
"""
|
|
cursor.execute(query, (equipment_id,))
|
|
par1 = cursor.fetchone()
|
|
return par1["forecasting_target_year"]
|
|
|
|
except Exception as e:
|
|
print(f"Error saat mengambil data dari database: {e}")
|
|
return None
|
|
|
|
finally:
|
|
if connection:
|
|
connection.close()
|
|
|
|
# Fungsi untuk mengambil data dari database
|
|
def __fetch_data_from_db(self, equipment_id):
|
|
try:
|
|
# Mendapatkan koneksi dari config.py
|
|
connection = get_connection()
|
|
if connection is None:
|
|
print("Koneksi ke database gagal.")
|
|
return None
|
|
|
|
# Membuat cursor menggunakan DictCursor
|
|
cursor = connection.cursor(cursor_factory=DictCursor)
|
|
|
|
# Query untuk mendapatkan data
|
|
query = """
|
|
SELECT
|
|
tahun AS year,
|
|
raw_cm_interval AS cm_interval,
|
|
raw_cm_material_cost AS cm_cost,
|
|
raw_cm_labor_time AS cm_labor_time,
|
|
raw_cm_labor_human AS cm_labor_human,
|
|
raw_pm_material_cost AS pm_cost,
|
|
raw_pm_labor_time AS pm_labor_time,
|
|
raw_pm_labor_human AS pm_labor_human,
|
|
raw_oh_material_cost AS oh_cost,
|
|
raw_oh_labor_time AS oh_labor_time,
|
|
raw_oh_labor_human AS oh_labor_human,
|
|
"raw_loss_output_MW" AS loss_output_mw,
|
|
raw_loss_output_price AS loss_price
|
|
FROM lcc_tr_data
|
|
WHERE equipment_id = %s
|
|
and is_actual=1
|
|
;
|
|
"""
|
|
cursor.execute(query, (equipment_id,))
|
|
|
|
# Mengambil hasil dan mengonversi ke DataFrame pandas
|
|
data = cursor.fetchall()
|
|
columns = [desc[0] for desc in cursor.description] # Mengambil nama kolom dari hasil query
|
|
df = pd.DataFrame(data, columns=columns)
|
|
return df
|
|
|
|
except Exception as e:
|
|
print(f"Error saat mengambil data dari database: {e}")
|
|
return None
|
|
|
|
finally:
|
|
if connection:
|
|
connection.close()
|
|
|
|
# Fungsi untuk prediksi menggunakan Future Value (FV)
|
|
def __future_value_predict(self, rate, nper, pmt, pv, years):
|
|
# Hitung Future Value untuk tahun-tahun proyeksi
|
|
fv_values = []
|
|
for i in range(len(years)):
|
|
fv = npf.fv(rate, nper + i, pmt, pv) # Menggunakan numpy_financial.fv
|
|
fv_values.append(fv)
|
|
return fv_values
|
|
|
|
# Fungsi untuk menghapus data proyeksi pada tahun tertentu
|
|
def __delete_predictions_from_db(self, equipment_id):
|
|
try:
|
|
connection = get_connection()
|
|
if connection is None:
|
|
print("Koneksi ke database gagal.")
|
|
return
|
|
|
|
cursor = connection.cursor()
|
|
|
|
# Query untuk menghapus data berdasarkan tahun proyeksi
|
|
delete_query = """
|
|
DELETE FROM lcc_tr_data
|
|
WHERE equipment_id = %s AND is_actual = 0;
|
|
""" # Asumsikan kolom is_actual digunakan untuk membedakan data proyeksi dan data aktual
|
|
|
|
# Eksekusi query delete
|
|
cursor.execute(delete_query, (equipment_id,))
|
|
connection.commit()
|
|
print(f"Data proyeksi untuk tahun {equipment_id} berhasil dihapus.")
|
|
|
|
except Exception as e:
|
|
print(f"Error saat menghapus data proyeksi dari database: {e}")
|
|
|
|
finally:
|
|
if connection:
|
|
connection.close()
|
|
|
|
# Fungsi untuk menyimpan data proyeksi ke database
|
|
def __insert_predictions_to_db(self, data, equipment_id):
|
|
try:
|
|
connection = get_connection()
|
|
if connection is None:
|
|
print("Koneksi ke database gagal.")
|
|
return
|
|
|
|
cursor = connection.cursor()
|
|
|
|
# Query untuk mendapatkan nilai maksimum seq
|
|
get_max_seq_query = """
|
|
SELECT COALESCE(MAX(seq), 0) FROM lcc_tr_data WHERE equipment_id = %s
|
|
"""
|
|
cursor.execute(get_max_seq_query, (equipment_id,))
|
|
max_seq = cursor.fetchone()[0]
|
|
|
|
# Query untuk insert data
|
|
insert_query = """
|
|
INSERT INTO lcc_tr_data (
|
|
id,
|
|
seq,
|
|
is_actual,
|
|
raw_pm_interval,
|
|
tahun, equipment_id,
|
|
raw_cm_interval, raw_cm_material_cost, raw_cm_labor_time, raw_cm_labor_human,
|
|
raw_pm_material_cost, raw_pm_labor_time, raw_pm_labor_human,
|
|
raw_oh_material_cost, raw_oh_labor_time, raw_oh_labor_human,
|
|
"raw_loss_output_MW", raw_loss_output_price
|
|
, created_by, created_at
|
|
) VALUES (
|
|
%s, %s, 0, 1, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'Sys', NOW()
|
|
)
|
|
"""
|
|
|
|
# Menyiapkan data untuk batch insert
|
|
records_to_insert = []
|
|
for _, row in data.iterrows():
|
|
max_seq = max_seq + 1
|
|
records_to_insert.append((
|
|
str(uuid4()), max_seq, row["year"], equipment_id,
|
|
row["cm_interval"] if row["cm_interval"] >= 1 else 1, row["cm_cost"], row["cm_labor_time"],
|
|
row["cm_labor_human"],
|
|
row["pm_cost"], row["pm_labor_time"], row["pm_labor_human"],
|
|
row["oh_cost"], row["oh_labor_time"], row["oh_labor_human"],
|
|
row["loss_output_mw"], row["loss_price"]
|
|
))
|
|
|
|
# Eksekusi batch insert
|
|
cursor.executemany(insert_query, records_to_insert)
|
|
connection.commit()
|
|
print("Data proyeksi berhasil dimasukkan ke database.")
|
|
|
|
except Exception as e:
|
|
print(f"Error saat menyimpan data ke database: {e}")
|
|
|
|
finally:
|
|
if connection:
|
|
connection.close()
|
|
|
|
# Fungsi untuk menghapus data proyeksi pada tahun tertentu
|
|
def __update_date_lcc(self, equipment_id):
|
|
try:
|
|
connection = get_connection()
|
|
if connection is None:
|
|
print("Koneksi ke database gagal.")
|
|
return
|
|
|
|
cursor = connection.cursor()
|
|
|
|
# Query untuk menghapus data berdasarkan tahun proyeksi
|
|
up_query = """
|
|
update lcc_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_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_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_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_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_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_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_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_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 equipment_id = %s;
|
|
|
|
update lcc_tr_data set rc_total_cost = (select acquisition_cost from lcc_ms_equipment_data where equipment_id=lcc_tr_data.equipment_id) where equipment_id = %s and seq=0;
|
|
""" # Asumsikan kolom is_actual digunakan untuk membedakan data proyeksi dan data aktual
|
|
|
|
# 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}")
|
|
|
|
finally:
|
|
if connection:
|
|
connection.close()
|
|
|
|
# Fungsi untuk mengambil parameter dari database
|
|
def __get_rate_and_max_year(self, equipment_id):
|
|
try:
|
|
connection = get_connection()
|
|
if connection is None:
|
|
print("Koneksi ke database gagal.")
|
|
return None, None
|
|
|
|
cursor = connection.cursor(cursor_factory=DictCursor)
|
|
|
|
# Query untuk mendapatkan rate dan max_year
|
|
query = """
|
|
SELECT
|
|
(SELECT value_num / 100 FROM lcc_ms_master where name='inflation_rate') AS rate,
|
|
(SELECT MAX(tahun) FROM lcc_tr_data WHERE is_actual = 1 AND equipment_id = %s) AS max_year
|
|
"""
|
|
cursor.execute(query, (equipment_id,))
|
|
result = cursor.fetchone()
|
|
|
|
# Debug hasil query
|
|
print(f"Result: {result}")
|
|
|
|
rate = result["rate"]
|
|
max_year = result["max_year"]
|
|
|
|
# Validasi nilai rate dan max_year
|
|
if rate is None:
|
|
raise Exception("Nilai 'rate' tidak boleh kosong. Periksa tabel 'lcc_ms_master'.")
|
|
if max_year is None:
|
|
raise Exception("Nilai 'max_year' tidak boleh kosong. Periksa tabel 'lcc_tr_data'.")
|
|
|
|
return rate, max_year
|
|
|
|
except Exception as e:
|
|
print(f"Error saat mendapatkan parameter dari database: {e}")
|
|
raise # Lempar kembali exception agar program berhenti
|
|
|
|
finally:
|
|
if connection:
|
|
connection.close()
|
|
|
|
# ======================================================================================================================================================
|
|
|
|
def predict_equipment_data(self, p_equipment_id):
|
|
try:
|
|
# Mengambil data dari database
|
|
df = self.__fetch_data_from_db(p_equipment_id)
|
|
|
|
if df is None:
|
|
print("Data tidak tersedia untuk prediksi.")
|
|
return
|
|
|
|
# Mendapatkan tahun proyeksi dari DB
|
|
par_tahun_target = self.__get_param(p_equipment_id)
|
|
|
|
# Tahun proyeksi
|
|
future_years = list(range(df["year"].max() + 1, par_tahun_target + 1))
|
|
|
|
# Hasil prediksi
|
|
predictions = {"year": future_years}
|
|
|
|
# Fungsi untuk prediksi menggunakan Linear Regression
|
|
def linear_regression_predict(column, years):
|
|
x = df["year"].values.reshape(-1, 1)
|
|
y = df[column].fillna(0).values
|
|
model = LinearRegression()
|
|
model.fit(x, y)
|
|
future_x = np.array(years).reshape(-1, 1)
|
|
preds = model.predict(future_x)
|
|
return np.abs(preds)
|
|
|
|
# Fungsi untuk prediksi menggunakan Exponential Smoothing
|
|
def exponential_smoothing_predict(column, years):
|
|
data_series = df[column].fillna(0).values
|
|
model = ExponentialSmoothing(data_series, trend="add", seasonal=None, seasonal_periods=None)
|
|
model_fit = model.fit()
|
|
preds = model_fit.forecast(len(years))
|
|
return np.abs(preds)
|
|
|
|
# Fungsi untuk prediksi menggunakan Decision Tree
|
|
def decision_tree_predict(column, years):
|
|
x = df["year"].values.reshape(-1, 1)
|
|
y = df[column].fillna(0).values
|
|
model = DecisionTreeRegressor()
|
|
model.fit(x, y)
|
|
future_x = np.array(years).reshape(-1, 1)
|
|
preds = model.predict(future_x)
|
|
return np.abs(preds)
|
|
|
|
# Mendapatkan rate dan tahun maksimal
|
|
rate, max_year = self.__get_rate_and_max_year(p_equipment_id)
|
|
pmt = 0
|
|
|
|
# Prediksi untuk setiap kolom
|
|
for column in df.columns:
|
|
if column != "year":
|
|
if "cost" in column.lower():
|
|
# Prediksi Future Value
|
|
nper = max_year - df["year"].max()
|
|
pv = -df[column].iloc[-1]
|
|
predictions[column] = self.__future_value_predict(rate, nper, pmt, pv, future_years)
|
|
elif df[column].nunique() < 5:
|
|
predictions[column] = exponential_smoothing_predict(column, future_years)
|
|
elif df[column].isnull().sum() > 0:
|
|
predictions[column] = decision_tree_predict(column, future_years)
|
|
else:
|
|
predictions[column] = linear_regression_predict(column, future_years)
|
|
|
|
# Konversi hasil ke DataFrame
|
|
predictions_df = pd.DataFrame(predictions)
|
|
|
|
# Hapus data prediksi yang ada sebelumnya
|
|
self.__delete_predictions_from_db(p_equipment_id)
|
|
|
|
# Insert hasil prediksi ke database
|
|
self.__insert_predictions_to_db(predictions_df, p_equipment_id)
|
|
|
|
# Update data untuk total RiskCost per tahun
|
|
self.__update_date_lcc(p_equipment_id)
|
|
|
|
except Exception as e:
|
|
print(f"Program dihentikan: {e}")
|
|
|