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)
seq = 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_material_cost = 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.auth.service import CurrentUser, Token
from src.models import CommonParams, StandardResponse
from src.modules.config import get_production_connection
router = APIRouter()
@ -118,6 +119,66 @@ async def simulate_equipment(db_session: DbSession, assetnum: str):
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")
async def simulate_all_equipment(db_session: DbSession):
"""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"
try:
# Update acquisition year and target year
await update_initial_simulation_data(db_session=db_session, assetnum=assetnum)
# Prediksi
await prediksi_main(assetnum=assetnum)
# EAC

@ -151,7 +151,7 @@ class Prediksi:
# connection.close()
# 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:
connection, connection_wo_db = get_connection()
if connection is None:
@ -173,6 +173,7 @@ class Prediksi:
id,
seq,
is_actual,
acquisition_year_ref,
tahun, assetnum,
rc_cm_material_cost,
rc_cm_labor_cost,
@ -184,7 +185,7 @@ class Prediksi:
rc_predictive_labor_cost,
created_by, created_at
) 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 = """
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 = """
@ -256,7 +257,7 @@ class Prediksi:
for _, row in data.iterrows():
# 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()
if existing_record:
@ -280,6 +281,7 @@ class Prediksi:
(
str(uuid4()), # id
int(max_seq), # seq
int(acquisition_year_ref) if acquisition_year_ref is not None else None, # acquisition_year_ref
int(row["year"]),
equipment_id,
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:
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):
try:
# Update acquisition year first
acquisition_year_ref = await self.__update_equipment_acquisition_year(assetnum)
# Mengambil data dari database
df = self.__fetch_data_from_db(assetnum)
if df is None:
@ -999,7 +1090,7 @@ class Prediksi:
# Insert hasil prediksi ke database
try:
await self.__insert_predictions_to_db(
predictions_df, assetnum, token
predictions_df, assetnum, token, acquisition_year_ref
)
except Exception as 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() == "":
print(f"[{idx}/{total_assets}] Skipping empty assetnum")
continue
# forecasting_start_year = row["forecasting_start_year"] - 1
forecasting_start_year = 2014
forecasting_start_year = row["forecasting_start_year"] - 1 if row["forecasting_start_year"] else 2014
# forecasting_start_year = 2014
asset_start = datetime.now()
processed_assets += 1

Loading…
Cancel
Save