feat: Dynamically update equipment acquisition year from production data and link prediction records to this reference.

rest-api
MrWaradana 1 month ago
parent 9cda4512c4
commit 4ff7e29d31

@ -60,6 +60,7 @@ class EquipmentTransactionRecords(Base, DefaultMixin, IdentityMixin):
tahun = Column(Integer, nullable=False) tahun = Column(Integer, nullable=False)
seq = Column(Integer, nullable=False) seq = Column(Integer, nullable=False)
is_actual = 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_interval = Column(Float, nullable=False)
raw_cm_material_cost = Column(Float, nullable=False) raw_cm_material_cost = Column(Float, nullable=False)
raw_cm_labor_time = Column(Float, nullable=False) raw_cm_labor_time = Column(Float, nullable=False)

@ -38,6 +38,7 @@ from src.database.service import CommonParameters, search_filter_sort_paginate
from src.database.core import DbSession, CollectorDbSession from src.database.core import DbSession, CollectorDbSession
from src.auth.service import CurrentUser, Token from src.auth.service import CurrentUser, Token
from src.models import CommonParams, StandardResponse from src.models import CommonParams, StandardResponse
from src.modules.config import get_production_connection
router = APIRouter() router = APIRouter()
@ -118,6 +119,66 @@ async def simulate_equipment(db_session: DbSession, assetnum: str):
return StreamingResponse(event_generator(), media_type='text/event-stream') return StreamingResponse(event_generator(), media_type='text/event-stream')
async def update_initial_simulation_data(db_session: DbSession, assetnum: str):
"""
Update acquisition_year from wo_maximo and set default forecasting_target_year
before running simulation.
"""
conn = get_production_connection()
first_year = None
if conn:
try:
cursor = conn.cursor()
# Query the first year from wo_maximo
query = """
select DATE_PART('year', a.reportdate) AS year
from wo_maximo a
where a.asset_replacecost > 0
and a.asset_assetnum = %s
order by a.reportdate asc
limit 1;
"""
cursor.execute(query, (assetnum,))
result = cursor.fetchone()
if result:
first_year = int(result[0])
cursor.close()
conn.close()
except Exception as e:
print(f"Error fetching acquisition year for {assetnum}: {e}")
if conn:
try:
conn.close()
except:
pass
if first_year:
# Fetch equipment to update
eq = await get_by_assetnum(db_session=db_session, assetnum=assetnum)
if eq:
# Check if forecasting_target_year matches the "default" logic (acquisition + design_life)
# using the OLD acquisition year.
current_acq = eq.acquisition_year
current_life = eq.design_life
current_target = eq.forecasting_target_year
# If current_target is logically "default", we update it.
# If user changed it to something else, we might want to preserve it?
# User request: "must be default acquisition_year+design_life if not changes"
# We interpret "if not changes" as: if it currently holds the default value (based on old acq year).
is_valid_default = current_target == (current_acq + current_life)
# Apply updates
if eq.acquisition_year != first_year:
eq.acquisition_year = first_year
if is_valid_default:
eq.forecasting_target_year = first_year + current_life
await db_session.commit()
# await db_session.refresh(eq)
@router.get("/simulate-all") @router.get("/simulate-all")
async def simulate_all_equipment(db_session: DbSession): async def simulate_all_equipment(db_session: DbSession):
"""Run simulation (prediksi + EAC) for ALL equipment. """Run simulation (prediksi + EAC) for ALL equipment.
@ -142,6 +203,9 @@ async def simulate_all_equipment(db_session: DbSession):
yield f"data: {json.dumps({'status':'working', 'step':f'Processing {idx}/{total}', 'assetnum': assetnum})}\\n\\n" yield f"data: {json.dumps({'status':'working', 'step':f'Processing {idx}/{total}', 'assetnum': assetnum})}\\n\\n"
try: try:
# Update acquisition year and target year
await update_initial_simulation_data(db_session=db_session, assetnum=assetnum)
# Prediksi # Prediksi
await prediksi_main(assetnum=assetnum) await prediksi_main(assetnum=assetnum)
# EAC # EAC

@ -151,7 +151,7 @@ class Prediksi:
# connection.close() # connection.close()
# Fungsi untuk menyimpan data proyeksi ke database # Fungsi untuk menyimpan data proyeksi ke database
async def __insert_predictions_to_db(self, data, equipment_id, token): async def __insert_predictions_to_db(self, data, equipment_id, token, acquisition_year_ref=None):
try: try:
connection, connection_wo_db = get_connection() connection, connection_wo_db = get_connection()
if connection is None: if connection is None:
@ -173,6 +173,7 @@ class Prediksi:
id, id,
seq, seq,
is_actual, is_actual,
acquisition_year_ref,
tahun, assetnum, tahun, assetnum,
rc_cm_material_cost, rc_cm_material_cost,
rc_cm_labor_cost, rc_cm_labor_cost,
@ -184,7 +185,7 @@ class Prediksi:
rc_predictive_labor_cost, rc_predictive_labor_cost,
created_by, created_at created_by, created_at
) VALUES ( ) VALUES (
%s, %s, 0, %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, %s, 'Sys', NOW()
) )
""" """
@ -235,7 +236,7 @@ class Prediksi:
check_existence_query = """ check_existence_query = """
SELECT id FROM lcc_equipment_tr_data SELECT id FROM lcc_equipment_tr_data
WHERE assetnum = %s AND tahun = %s AND is_actual = 0 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))
""" """
update_query = """ update_query = """
@ -256,7 +257,7 @@ class Prediksi:
for _, row in data.iterrows(): for _, row in data.iterrows():
# Check if data exists # Check if data exists
cursor.execute(check_existence_query, (equipment_id, int(row["year"]))) cursor.execute(check_existence_query, (equipment_id, int(row["year"]), acquisition_year_ref, acquisition_year_ref))
existing_record = cursor.fetchone() existing_record = cursor.fetchone()
if existing_record: if existing_record:
@ -280,6 +281,7 @@ class Prediksi:
( (
str(uuid4()), # id str(uuid4()), # id
int(max_seq), # seq int(max_seq), # seq
int(acquisition_year_ref) if acquisition_year_ref is not None else None, # acquisition_year_ref
int(row["year"]), int(row["year"]),
equipment_id, equipment_id,
float(row.get("rc_cm_material_cost", 0)) if not pd.isna(row.get("rc_cm_material_cost", 0)) else 0.0, float(row.get("rc_cm_material_cost", 0)) if not pd.isna(row.get("rc_cm_material_cost", 0)) else 0.0,
@ -743,8 +745,97 @@ class Prediksi:
if connection: if connection:
connection.close() connection.close()
async def __update_equipment_acquisition_year(self, assetnum: str):
"""
Update acquisition_year from wo_maximo and set default forecasting_target_year
using raw SQL connections.
"""
try:
# 1. Fetch first acquisition year from wo_maximo (production db)
prod_conn = get_production_connection()
if not prod_conn:
print("Failed to connect to production DB for acquisition year update.")
return None
first_year = None
try:
p_cursor = prod_conn.cursor()
query = """
select DATE_PART('year', a.reportdate) AS year
from wo_maximo a
where a.asset_replacecost > 0
and a.asset_assetnum = %s
order by a.reportdate asc
limit 1;
"""
p_cursor.execute(query, (assetnum,))
res = p_cursor.fetchone()
if res and res[0] is not None:
first_year = int(res[0])
p_cursor.close()
finally:
prod_conn.close()
if not first_year:
# No data, skip update
return None
# 2. Update local DB
conn, _ = get_connection()
if not conn:
return None
updated_acq = None
try:
cursor = conn.cursor(cursor_factory=DictCursor)
# Fetch current values
cursor.execute("SELECT acquisition_year, design_life, forecasting_target_year FROM lcc_ms_equipment_data WHERE assetnum = %s", (assetnum,))
current = cursor.fetchone()
if current:
curr_acq = int(current["acquisition_year"])
curr_life = int(current["design_life"])
curr_target = int(current["forecasting_target_year"])
# Logic: if current_target matches the "old default" (old_acq + life), then update it too.
is_valid_default = (curr_target == (curr_acq + curr_life))
if curr_acq != first_year:
new_target = curr_target
if is_valid_default:
new_target = first_year + curr_life
update_q = """
UPDATE lcc_ms_equipment_data
SET acquisition_year = %s, forecasting_target_year = %s
WHERE assetnum = %s
"""
cursor.execute(update_q, (first_year, new_target, assetnum))
conn.commit()
print(f"Updated acquisition_year for {assetnum}: {curr_acq} -> {first_year}")
updated_acq = first_year
else:
updated_acq = curr_acq
else:
# Logic if equipment not found? Unlikely here.
pass
cursor.close()
finally:
conn.close()
return updated_acq
except Exception as e:
print(f"Error updating acquisition year for {assetnum}: {e}")
return None
async def predict_equipment_data(self, assetnum, token): async def predict_equipment_data(self, assetnum, token):
try: try:
# Update acquisition year first
acquisition_year_ref = await self.__update_equipment_acquisition_year(assetnum)
# Mengambil data dari database # Mengambil data dari database
df = self.__fetch_data_from_db(assetnum) df = self.__fetch_data_from_db(assetnum)
if df is None: if df is None:
@ -999,7 +1090,7 @@ class Prediksi:
# Insert hasil prediksi ke database # Insert hasil prediksi ke database
try: try:
await self.__insert_predictions_to_db( await self.__insert_predictions_to_db(
predictions_df, assetnum, token predictions_df, assetnum, token, acquisition_year_ref
) )
except Exception as e: except Exception as e:
print(f"Error saat insert data ke database: {e}") print(f"Error saat insert data ke database: {e}")

@ -966,8 +966,8 @@ async def query_data(target_assetnum: str = None):
if not assetnum or str(assetnum).strip() == "": if not assetnum or str(assetnum).strip() == "":
print(f"[{idx}/{total_assets}] Skipping empty assetnum") print(f"[{idx}/{total_assets}] Skipping empty assetnum")
continue continue
# forecasting_start_year = row["forecasting_start_year"] - 1 forecasting_start_year = row["forecasting_start_year"] - 1 if row["forecasting_start_year"] else 2014
forecasting_start_year = 2014 # forecasting_start_year = 2014
asset_start = datetime.now() asset_start = datetime.now()
processed_assets += 1 processed_assets += 1

Loading…
Cancel
Save