update labor cost and disposal cost

main
MrWaradana 4 weeks ago
parent 09fe96f1c0
commit 46a2727f69

@ -83,7 +83,12 @@ class Eac:
# 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)
eac_disposal_cost = 0.07 * pmt_mnt_cost
# 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 = 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 = row["seq"]

@ -394,8 +394,8 @@ class Prediksi:
part2 = max(0.0, (dmn - ens)) * extra_fuel
asset_criticality = part1 + part2
efdh = _f("asset_crit_efdh_equivalent_forced_derated_hours") # EFDH per Year
foh = _f("asset_crit_foh_forced_outage_hours") # FOH per Year
# efdh = _f("asset_crit_efdh_equivalent_forced_derated_hours") # EFDH per Year
# foh = _f("asset_crit_foh_forced_outage_hours") # FOH per Year
query_each_equipment = """
SELECT
@ -431,6 +431,113 @@ class Prediksi:
if connection:
connection.close()
# Fungsi untuk mengambil labour cost
def __get_labour_cost(self, equipment_id, worktype, existing_connection=None):
"""Return labour cost totals per tahun for the given worktype."""
labour_costs = {}
connection = existing_connection
cursor = None
close_connection = False
try:
if connection is None:
connections = get_production_connection()
connection = connections[0] if isinstance(connections, tuple) else connections
close_connection = True
if connection is None:
print("Database connection failed.")
return labour_costs
cursor = connection.cursor()
worktype_upper = (worktype or "").upper()
worktype_condition = "AND a.worktype IN ('CM', 'PROACTIVE', 'WA')"
params = [equipment_id]
if worktype_upper != "CM":
worktype_condition = "AND a.worktype = %s"
params.append(worktype_upper)
exclude_condition = "AND a.wojp8 != 'S1'" if worktype_upper == "CM" else ""
query = f"""
SELECT
EXTRACT(YEAR FROM x.reportdate)::int AS tahun,
SUM(x.upah_per_wonum) AS total_upah_per_tahun
FROM (
SELECT
bw.wonum,
bw.reportdate,
bw.jumlah_jam_kerja,
CASE
WHEN COUNT(b.laborcode) FILTER (WHERE b.laborcode IS NOT NULL) > 0 THEN
SUM(
COALESCE(emp_cost.salary_per_hour_idr,
(SELECT salary_per_hour_idr FROM lcc_manpower_cost WHERE UPPER(staff_job_level) = UPPER('Junior') LIMIT 1))
* bw.jumlah_jam_kerja
)
ELSE
3 * (SELECT salary_per_hour_idr FROM lcc_manpower_cost WHERE UPPER(staff_job_level) = UPPER('Junior') LIMIT 1) * bw.jumlah_jam_kerja
END AS upah_per_wonum
FROM (
SELECT
a.wonum,
a.reportdate,
CASE
WHEN (EXTRACT(EPOCH FROM (a.actfinish - a.actstart)) / 3600.0) = 0
THEN 1
ELSE (EXTRACT(EPOCH FROM (a.actfinish - a.actstart)) / 3600.0)
END AS jumlah_jam_kerja
FROM public.wo_maximo a
WHERE
a.asset_unit = '3'
AND a.wonum NOT LIKE 'T%'
AND a.asset_assetnum = %s
AND (EXTRACT(EPOCH FROM (a.actfinish - a.actstart)) / 3600.0) <= 730
{worktype_condition}
{exclude_condition}
) bw
LEFT JOIN public.wo_maximo_labtrans b ON b.wonum = bw.wonum
LEFT JOIN lcc_ms_manpower emp ON UPPER(TRIM(emp."ID Number")) = UPPER(TRIM(b.laborcode))
LEFT JOIN lcc_manpower_cost emp_cost ON UPPER(TRIM(emp_cost.staff_job_level)) = UPPER(TRIM(emp."Position"))
GROUP BY bw.wonum, bw.reportdate, bw.jumlah_jam_kerja
) x
GROUP BY 1
ORDER BY 1;
"""
cursor.execute(query, tuple(params))
rows = cursor.fetchall()
for tahun, total in rows:
try:
year_int = int(tahun) if tahun is not None else None
except (TypeError, ValueError):
year_int = None
if year_int is None:
continue
try:
labour_costs[year_int] = float(total) if total is not None else 0.0
except (TypeError, ValueError):
labour_costs[year_int] = 0.0
return labour_costs
except Exception as e:
print(f"Error saat mendapatkan labour cost dari database: {e}")
return labour_costs
finally:
if cursor:
try:
cursor.close()
except Exception:
pass
if close_connection and connection:
try:
connection.close()
except Exception:
pass
# Fungsi untuk menghapus data proyeksi pada tahun tertentu
def __update_data_lcc(self, equipment_id):
try:
@ -448,6 +555,17 @@ class Prediksi:
cursor = connection.cursor(cursor_factory=DictCursor)
labour_cost_maps = {
"CM": self.__get_labour_cost(equipment_id, "CM", production_connection),
"PM": self.__get_labour_cost(equipment_id, "PM", production_connection),
"PDM": self.__get_labour_cost(equipment_id, "PDM", production_connection),
"OH": self.__get_labour_cost(equipment_id, "OH", production_connection),
}
cm_labour_costs = labour_cost_maps.get("CM", {}) or {}
pm_labour_costs = labour_cost_maps.get("PM", {}) or {}
pdm_labour_costs = labour_cost_maps.get("PDM", {}) or {}
oh_labour_costs = labour_cost_maps.get("OH", {}) or {}
# Ambil semua baris untuk assetnum
select_q = '''
SELECT id, seq, tahun,
@ -493,9 +611,6 @@ class Prediksi:
rc_predictive_labor_cost = %s,
rc_oh_material_cost = %s,
rc_oh_labor_cost = %s,
rc_lost_cost = %s,
rc_operation_cost = %s,
rc_maintenance_cost = %s,
rc_total_cost = %s,
updated_by = 'Sys', updated_at = NOW()
WHERE id = %s;
@ -506,6 +621,8 @@ class Prediksi:
yr = r.get("tahun") if isinstance(r, dict) else r[2]
man_hour = _get_man_hour_for_year(yr)
seq = int(r.get("seq") or 0) if isinstance(r, dict) else int(r[1] or 0)
raw_cm_interval = float(r.get("raw_cm_interval") or 0.0)
raw_cm_labor_time = float(r.get("raw_cm_labor_time") or 0.0)
raw_cm_labor_human = float(r.get("raw_cm_labor_human") or 0.0)
@ -525,11 +642,11 @@ class Prediksi:
raw_oh_labor_time = float(r.get("raw_oh_labor_time") or 0.0)
raw_oh_labor_human = float(r.get("raw_oh_labor_human") or 0.0)
raw_loss_output_mw = float(r.get("raw_loss_output_mw") or 0.0)
raw_loss_output_price = float(r.get("raw_loss_output_price") or 0.0)
# raw_loss_output_mw = float(r.get("raw_loss_output_mw") or 0.0)
# raw_loss_output_price = float(r.get("raw_loss_output_price") or 0.0)
raw_operational_cost = float(r.get("raw_operational_cost") or 0.0)
raw_maintenance_cost = float(r.get("raw_maintenance_cost") or 0.0)
# raw_operational_cost = float(r.get("raw_operational_cost") or 0.0)
# raw_maintenance_cost = float(r.get("raw_maintenance_cost") or 0.0)
efdh_equivalent_forced_derated_hours = float(r.get("efdh_equivalent_forced_derated_hours") or 0.0)
foh_forced_outage_hours = float(r.get("foh_forced_outage_hours") or 0.0)
@ -538,6 +655,12 @@ class Prediksi:
# compute per-column costs using helpers
rc_cm_material = rc_cm_material_cost
rc_cm_labor = rc_labor_cost(raw_cm_interval, raw_cm_labor_time, raw_cm_labor_human, man_hour)
cm_labor_total = cm_labour_costs.get(yr)
if cm_labor_total is not None:
try:
rc_cm_labor = float(cm_labor_total)
except (TypeError, ValueError):
rc_cm_labor = 0.0
try:
# if np.isfinite(raw_pm_interval) and raw_pm_interval != 0:
@ -547,6 +670,12 @@ class Prediksi:
except Exception:
rc_pm_material = 0.0
rc_pm_labor = rc_labor_cost(raw_pm_interval, raw_pm_labor_time, raw_pm_labor_human, man_hour)
pm_labor_total = pm_labour_costs.get(yr)
if pm_labor_total is not None:
try:
rc_pm_labor = float(pm_labor_total)
except (TypeError, ValueError):
rc_pm_labor = 0.0
try:
# if np.isfinite(raw_predictive_interval) and raw_predictive_interval != 0:
@ -561,9 +690,21 @@ class Prediksi:
rc_predictive_labor = rc_labor_cost(raw_predictive_interval, raw_predictive_labor_time, raw_predictive_labor_human, man_hour)
except Exception:
rc_predictive_labor = 0.0
predictive_labor_total = pdm_labour_costs.get(yr)
if predictive_labor_total is not None:
try:
rc_predictive_labor = float(predictive_labor_total)
except (TypeError, ValueError):
rc_predictive_labor = 0.0
rc_oh_material = raw_oh_material_cost
rc_oh_labor = rc_labor_cost(raw_oh_interval, raw_oh_labor_time, raw_oh_labor_human, man_hour)
oh_labor_total = oh_labour_costs.get(yr)
if oh_labor_total is not None:
try:
rc_oh_labor = float(oh_labor_total)
except (TypeError, ValueError):
rc_oh_labor = 0.0
# rc_lost = rc_lost_cost(raw_loss_output_mw, raw_loss_output_price, raw_cm_interval)

@ -145,6 +145,89 @@ def get_data_tahun(cursor):
return cursor.fetchall()
def get_labour_cost_totals(cursor, assetnum: str, worktype: str) -> dict:
"""Return yearly labor cost totals for a worktype using the standardized query."""
if not assetnum or not worktype:
return {}
worktype_condition = "AND a.worktype IN ('CM', 'PROACTIVE', 'WA')"
worktype_params = [assetnum]
if worktype.upper() != "CM":
worktype_condition = "AND a.worktype = %s"
worktype_params.append(worktype.upper())
exclude_condition = "AND a.wojp8 != 'S1'" if worktype.upper() == "CM" else ""
query = f"""
SELECT
EXTRACT(YEAR FROM x.reportdate)::int AS tahun,
SUM(x.upah_per_wonum) AS total_upah_per_tahun
FROM (
SELECT
bw.wonum,
bw.reportdate,
bw.jumlah_jam_kerja,
CASE
WHEN COUNT(b.laborcode) FILTER (WHERE b.laborcode IS NOT NULL) > 0 THEN
SUM(
COALESCE(emp_cost.salary_per_hour_idr,
(SELECT salary_per_hour_idr FROM lcc_manpower_cost WHERE UPPER(staff_job_level) = UPPER('Junior') LIMIT 1))
* bw.jumlah_jam_kerja
)
ELSE
3 * (SELECT salary_per_hour_idr FROM lcc_manpower_cost WHERE UPPER(staff_job_level) = UPPER('Junior') LIMIT 1) * bw.jumlah_jam_kerja
END AS upah_per_wonum
FROM (
SELECT
a.wonum,
a.reportdate,
CASE
WHEN (EXTRACT(EPOCH FROM (a.actfinish - a.actstart)) / 3600.0) = 0
THEN 1
ELSE (EXTRACT(EPOCH FROM (a.actfinish - a.actstart)) / 3600.0)
END AS jumlah_jam_kerja
FROM public.wo_maximo a
WHERE
a.asset_unit = '3'
AND a.wonum NOT LIKE 'T%'
AND a.asset_assetnum = %s
AND (EXTRACT(EPOCH FROM (a.actfinish - a.actstart)) / 3600.0) <= 730
{worktype_condition}
{exclude_condition}
) bw
LEFT JOIN public.wo_maximo_labtrans b ON b.wonum = bw.wonum
LEFT JOIN lcc_ms_manpower emp ON UPPER(TRIM(emp."ID Number")) = UPPER(TRIM(b.laborcode))
LEFT JOIN lcc_manpower_cost emp_cost ON UPPER(TRIM(emp_cost.staff_job_level)) = UPPER(TRIM(emp."Position"))
GROUP BY bw.wonum, bw.reportdate, bw.jumlah_jam_kerja
) x
GROUP BY 1
ORDER BY 1;
"""
try:
cursor.execute(query, tuple(worktype_params))
rows = cursor.fetchall()
except Exception as exc:
print(f"Error fetching labour cost for {assetnum} ({worktype}): {exc}")
return {}
labour_costs = {}
for tahun, total in rows:
try:
year_int = int(tahun) if tahun is not None else None
except (TypeError, ValueError):
year_int = None
if year_int is None:
continue
try:
labour_costs[year_int] = float(total) if total is not None else 0.0
except (TypeError, ValueError):
labour_costs[year_int] = 0.0
return labour_costs
def _parse_decimal(value: str, decimal_separator: str = ".") -> Decimal:
"""Parse numeric strings that may use comma decimal separators."""
if value is None:
@ -215,6 +298,8 @@ def _build_tr_row_values(
data_oh_row,
data_predictive_row,
data_tahunan_row,
year=None,
labour_cost_lookup=None,
):
"""Return sanitized numeric values for equipment transaction rows."""
@ -347,6 +432,38 @@ def _build_tr_row_values(
else 0
)
if labour_cost_lookup and year is not None:
cm_lookup = labour_cost_lookup.get("CM", {})
pm_lookup = labour_cost_lookup.get("PM", {})
oh_lookup = labour_cost_lookup.get("OH", {})
pdm_lookup = labour_cost_lookup.get("PDM", {})
cm_value = cm_lookup.get(year)
pm_value = pm_lookup.get(year)
oh_value = oh_lookup.get(year)
pdm_value = pdm_lookup.get(year)
if cm_value is not None:
try:
rc_cm_labor_cost = float(cm_value)
except (TypeError, ValueError):
rc_cm_labor_cost = 0.0
if pm_value is not None:
try:
rc_pm_labor_cost = float(pm_value)
except (TypeError, ValueError):
rc_pm_labor_cost = 0.0
if oh_value is not None:
try:
rc_oh_labor_cost = float(oh_value)
except (TypeError, ValueError):
rc_oh_labor_cost = 0.0
if pdm_value is not None:
try:
rc_predictive_labor_cost = float(pdm_value)
except (TypeError, ValueError):
rc_predictive_labor_cost = 0.0
return {
"raw_cm_interval": raw_cm_interval,
"raw_cm_material_cost": raw_cm_material_cost,
@ -914,6 +1031,13 @@ async def query_data():
# Data Tahun
data_tahunan = get_data_tahun(cursor)
labour_cost_lookup = {
"CM": get_labour_cost_totals(cursor_wo, assetnum, "CM"),
"PM": get_labour_cost_totals(cursor_wo, assetnum, "PM"),
"PDM": get_labour_cost_totals(cursor_wo, assetnum, "PDM"),
"OH": get_labour_cost_totals(cursor_wo, assetnum, "OH"),
}
seq = 0
# Looping untuk setiap tahun
for year in range(forecasting_start_year, current_year + 1):
@ -961,6 +1085,8 @@ async def query_data():
data_oh_row,
data_predictive_row,
data_tahunan_row,
year=year,
labour_cost_lookup=labour_cost_lookup,
)
if not data_exists:

@ -396,7 +396,8 @@ def main():
+ cost_a_pm
# + cost_a_pinjaman
# + cost_a_depreciation
)
)
else:
cost_a_replacement = 0
cost_a_pm = 0
@ -412,11 +413,15 @@ def main():
chart_capex_component_a = cost_a_acquisition
chart_capex_annualized = cost_a_annualized
cost_disposal_cost = -npf.pmt(discount_rate, seq, 0, 0.05 * total_project_cost)
else:
chart_capex_component_a = total_project_cost
chart_capex_annualized = 0
cost_a_pv = 0
cost_a_annualized = 0
cost_disposal_cost = 0
chart_capex_biaya_investasi_tambahan = 0
chart_capex_acquisition_cost = 0
@ -501,7 +506,7 @@ def main():
calc4_free_cash_flow_on_equity_array.append(calc4_free_cash_flow_on_equity)
calc4_discounted_fcf_on_equity = hitung_pv(wacc_on_equity, seq, calc4_free_cash_flow_on_equity)
cost_disposal_cost = -npf.pmt(discount_rate, seq, cost_a_acquisition) if seq > 0 else 0
row_params = (
net_capacity_factor,

Loading…
Cancel
Save