feat: Implement reliability-based CM cost prediction using historical cost per failure and document the new prediction logic.

main
MrWaradana 4 weeks ago
parent fbab85ff50
commit 498244052b

@ -674,6 +674,39 @@ class Prediksi:
print(f"HTTP error occurred: {e}") print(f"HTTP error occurred: {e}")
return {} 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"): def __get_man_hour_rate(self, staff_level: str = "Junior"):
connection = None connection = None
try: try:
@ -755,7 +788,8 @@ class Prediksi:
rate, max_year = self.__get_rate_and_max_year(assetnum) rate, max_year = self.__get_rate_and_max_year(assetnum)
man_hour_rate = self.__get_man_hour_rate() # Defaults to 'junior' 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 # Prediksi untuk setiap kolom
for column in df.columns: for column in df.columns:
@ -807,16 +841,32 @@ class Prediksi:
preds_list.append(cost) preds_list.append(cost)
preds = np.array(preds_list, dtype=float) preds = np.array(preds_list, dtype=float)
elif recent_vals.empty:
avg = 0.0
preds = np.repeat(float(avg), n_future)
else: else:
avg = pd.to_numeric(recent_vals, errors="coerce").fillna(0).mean() # Use pre-fetched cost per failure
avg = 0.0 if pd.isna(avg) else float(avg) preds_list = []
preds = np.repeat(float(avg), n_future) 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: 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 # 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: if "is_actual" in df.columns and not df[df["is_actual"] == 1].empty:
last_actual_year_series = df[df["is_actual"] == 1]["year"] last_actual_year_series = df[df["is_actual"] == 1]["year"]

@ -6,6 +6,14 @@ This file consolidates the core mathematical/financial formulas used across:
- `insert_actual_data.py` (aggregation formulas, man-hour conversion) - `insert_actual_data.py` (aggregation formulas, man-hour conversion)
- `Prediksi.py` (future value / fv wrappers) - `Prediksi.py` (future value / fv wrappers)
### Prediction Logic Summary
| Category | Logic Type | Formula Basis |
| :--- | :--- | :--- |
| **CM Labor** | **Reliability-Based** | `Failures x 3.0 x 1.0 x ManPowerRate` |
| **CM Other** | **Reliability-Based** | `Failures x CostPerFailure (from Production SQL)` |
| **PM / OH / PDM** | **Last Scenario** | `Value from Last Actual Year` (Carry Forward) |
| **Total Risk Cost** | **Aggregated** | `Sum of above + Asset Criticality Multiplier` |
Keep these functions pure and well-documented to make debugging and Keep these functions pure and well-documented to make debugging and
comparisons easier. comparisons easier.
""" """

Loading…
Cancel
Save