|
|
|
|
@ -674,6 +674,39 @@ class Prediksi:
|
|
|
|
|
print(f"HTTP error occurred: {e}")
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
def __get_historical_cost_per_failure(self, assetnum):
|
|
|
|
|
connection = None
|
|
|
|
|
try:
|
|
|
|
|
connection = get_production_connection()
|
|
|
|
|
if connection is None:
|
|
|
|
|
return 0.0
|
|
|
|
|
cursor = connection.cursor()
|
|
|
|
|
# Optimized single-pass query: counts and sums in one scan
|
|
|
|
|
query = """
|
|
|
|
|
SELECT
|
|
|
|
|
SUM(a.actmatcost) / NULLIF(COUNT(CASE WHEN a.wonum NOT LIKE 'T%%' THEN 1 END), 0) as cost_failure
|
|
|
|
|
FROM wo_maximo a
|
|
|
|
|
WHERE (a.asset_unit = '3' OR a.asset_unit = '00')
|
|
|
|
|
AND a.status IN ('COMP', 'CLOSE')
|
|
|
|
|
AND a.asset_assetnum = %s
|
|
|
|
|
AND a.worktype IN ('CM', 'PROACTIVE', 'EM')
|
|
|
|
|
AND a.wojp8 != 'S1'
|
|
|
|
|
AND (
|
|
|
|
|
a.description NOT ILIKE '%%U4%%'
|
|
|
|
|
OR (a.description ILIKE '%%U3%%' AND a.description ILIKE '%%U4%%')
|
|
|
|
|
)
|
|
|
|
|
"""
|
|
|
|
|
cursor.execute(query, (assetnum,))
|
|
|
|
|
result = cursor.fetchone()
|
|
|
|
|
cost_failure = float(result[0]) if result and result[0] is not None else 0.0
|
|
|
|
|
return cost_failure
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error fetching historical cost per failure for {assetnum}: {e}")
|
|
|
|
|
return 0.0
|
|
|
|
|
finally:
|
|
|
|
|
if connection:
|
|
|
|
|
connection.close()
|
|
|
|
|
|
|
|
|
|
def __get_man_hour_rate(self, staff_level: str = "Junior"):
|
|
|
|
|
connection = None
|
|
|
|
|
try:
|
|
|
|
|
@ -755,7 +788,8 @@ class Prediksi:
|
|
|
|
|
rate, max_year = self.__get_rate_and_max_year(assetnum)
|
|
|
|
|
man_hour_rate = self.__get_man_hour_rate() # Defaults to 'junior'
|
|
|
|
|
|
|
|
|
|
pmt = 0
|
|
|
|
|
# Pre-fetch cost per failure once per asset to avoid redundant DB queries
|
|
|
|
|
avg_cost_per_failure = self.__get_historical_cost_per_failure(assetnum)
|
|
|
|
|
|
|
|
|
|
# Prediksi untuk setiap kolom
|
|
|
|
|
for column in df.columns:
|
|
|
|
|
@ -807,16 +841,32 @@ class Prediksi:
|
|
|
|
|
preds_list.append(cost)
|
|
|
|
|
preds = np.array(preds_list, dtype=float)
|
|
|
|
|
|
|
|
|
|
elif recent_vals.empty:
|
|
|
|
|
avg = 0.0
|
|
|
|
|
preds = np.repeat(float(avg), n_future)
|
|
|
|
|
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)
|
|
|
|
|
# Use pre-fetched cost per failure
|
|
|
|
|
preds_list = []
|
|
|
|
|
for yr in future_years:
|
|
|
|
|
failures_data = await self._fetch_api_data(assetnum, yr)
|
|
|
|
|
# Interval from predicted number of failures
|
|
|
|
|
interval = 0.0
|
|
|
|
|
if isinstance(failures_data, dict):
|
|
|
|
|
data_list = failures_data.get("data")
|
|
|
|
|
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)
|
|
|
|
|
except Exception:
|
|
|
|
|
interval = 0.0
|
|
|
|
|
|
|
|
|
|
# predicted_cost = predicted_failures * avg_cost_per_failure
|
|
|
|
|
cost = interval * avg_cost_per_failure
|
|
|
|
|
preds_list.append(cost)
|
|
|
|
|
preds = np.array(preds_list, dtype=float)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
# Для 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"]
|
|
|
|
|
|