feat: Implement acquisition data change detection, historical archiving, and conditional re-insertion before simulation.

rest-api
MrWaradana 1 month ago
parent 47c333b318
commit 0af5cceabd

@ -89,8 +89,6 @@ async def simulate_equipment(db_session: DbSession, assetnum: str):
await update_initial_simulation_data(db_session=db_session, assetnum=assetnum)
yield f"data: {json.dumps({'status':'completed','step':'checking update','message':'Initial data check completed'})}\n\n"
except Exception as e:
# Log error but proceed? Or maybe yield an error?
# For now, we proceed as this is an enhancement check.
print(f"Update simulation data failed: {e}")
yield f"data: {json.dumps({'status':'error','step':'checking update','message':f'Update simulation data failed: {str(e)}'})}\n\n"

@ -650,10 +650,11 @@ async def delete(*, db_session: DbSession, equipment_id: str):
await db_session.commit()
async def update_initial_simulation_data(db_session: DbSession, assetnum: str):
async def check_and_update_acquisition_data(db_session: DbSession, assetnum: str) -> bool:
"""
Update acquisition_year and acquisition_cost from wo_maximo and set default forecasting_target_year
before running simulation.
Check if acquisition year/cost in Maximo differs from local DB.
If changed, archive history, delete transaction data, update master, and return True.
Otherwise return False.
"""
conn = get_production_connection()
first_year = None
@ -685,6 +686,8 @@ async def update_initial_simulation_data(db_session: DbSession, assetnum: str):
except:
pass
updates_performed = False
if first_year:
# Fetch equipment to update
eq = await get_by_assetnum(db_session=db_session, assetnum=assetnum)
@ -794,7 +797,17 @@ async def update_initial_simulation_data(db_session: DbSession, assetnum: str):
eq.forecasting_target_year = first_year + current_life
await db_session.commit()
# await db_session.refresh(eq)
updates_performed = True
return updates_performed
async def update_initial_simulation_data(db_session: DbSession, assetnum: str):
"""
Update acquisition_year and acquisition_cost from wo_maximo and set default forecasting_target_year
before running simulation.
"""
updated = await check_and_update_acquisition_data(db_session, assetnum)
if updated:
# Re-insert actual data using query_data
await query_data(target_assetnum=assetnum)

@ -862,183 +862,6 @@ async def insert_acquisition_cost_data():
except Exception:
pass
async def update_equipment_acquisition_year(assetnum: str):
"""
Update acquisition_year from wo_maximo and set default forecasting_target_year
using raw SQL connections.
Refactored to archive historical data if acquisition year changes.
"""
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, a.replacecost
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 * 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_val = current["forecasting_target_year"]
curr_target = int(curr_target_val) if curr_target_val is not None else (curr_acq + curr_life) # Fallback if none
curr_acq_cost = float(current["acquisition_cost"]) if current["acquisition_cost"] is not None else 0.0
# Logic: if current_target matches the "old default" (old_acq + life), then update it too.
is_valid_default = (curr_target == (curr_acq + curr_life))
# Prepare target cost. If we fetched a new cost from WO, use it, else keep old.
# Note: first_acq_cost variable availability check
target_acq_cost = first_acq_cost if 'first_acq_cost' in locals() and first_acq_cost is not None else curr_acq_cost
if curr_acq != first_year or curr_acq_cost != target_acq_cost:
print(f"Acquisition change detected for {assetnum}: Year {curr_acq}->{first_year}, Cost {curr_acq_cost}->{target_acq_cost}. Archiving history.")
# Define reference for history
acq_year_ref = f"{curr_acq}_{curr_target}"
# --- ARCHIVE HISTORICAL DATA ---
# 1. Copy old equipment master data to history
history_ms_query = """
INSERT INTO lcc_ms_equipment_historical_data (
id, assetnum, acquisition_year, acquisition_cost, capital_cost_record_time, design_life,
forecasting_start_year, forecasting_target_year, manhours_rate, created_at, created_by,
updated_at, updated_by, min_eac_info, harga_saat_ini, minimum_eac_seq, minimum_eac_year,
minimum_eac, minimum_npv, minimum_pmt, minimum_pmt_aq_cost, minimum_is_actual,
efdh_equivalent_forced_derated_hours, foh_forced_outage_hours, category_no, proportion,
acquisition_year_ref
)
SELECT
uuid_generate_v4(), assetnum, acquisition_year, acquisition_cost, capital_cost_record_time, design_life,
forecasting_start_year, forecasting_target_year, manhours_rate, created_at, created_by,
updated_at, updated_by, min_eac_info, harga_saat_ini, minimum_eac_seq, minimum_eac_year,
minimum_eac, minimum_npv, minimum_pmt, minimum_pmt_aq_cost, minimum_is_actual,
efdh_equivalent_forced_derated_hours, foh_forced_outage_hours, category_no, proportion,
%s
FROM lcc_ms_equipment_data
WHERE assetnum = %s
"""
cursor.execute(history_ms_query, (acq_year_ref, assetnum))
# 2. Copy old transaction data to lcc_equipment_historical_tr_data
# Format: {acquisition_year}_{forecasting_target_year}
history_tr_query = """
INSERT INTO lcc_equipment_historical_tr_data (
id, assetnum, tahun, seq, is_actual,
raw_cm_interval, raw_cm_material_cost, raw_cm_labor_time, raw_cm_labor_human,
raw_pm_interval, raw_pm_material_cost, raw_pm_labor_time, raw_pm_labor_human,
raw_oh_interval, raw_oh_material_cost, raw_oh_labor_time, raw_oh_labor_human,
raw_predictive_interval, raw_predictive_material_cost, raw_predictive_labor_time, raw_predictive_labor_human,
raw_project_task_material_cost, "raw_loss_output_MW", raw_loss_output_price,
raw_operational_cost, raw_maintenance_cost,
rc_cm_material_cost, rc_cm_labor_cost,
rc_pm_material_cost, rc_pm_labor_cost,
rc_oh_material_cost, rc_oh_labor_cost,
rc_predictive_labor_cost,
rc_project_material_cost, rc_lost_cost, rc_operation_cost, rc_maintenance_cost,
rc_total_cost,
eac_npv, eac_annual_mnt_cost, eac_annual_acq_cost, eac_disposal_cost, eac_eac,
efdh_equivalent_forced_derated_hours, foh_forced_outage_hours,
created_by, created_at, acquisition_year_ref
)
SELECT
uuid_generate_v4(), assetnum, tahun, seq, is_actual,
raw_cm_interval, raw_cm_material_cost, raw_cm_labor_time, raw_cm_labor_human,
raw_pm_interval, raw_pm_material_cost, raw_pm_labor_time, raw_pm_labor_human,
raw_oh_interval, raw_oh_material_cost, raw_oh_labor_time, raw_oh_labor_human,
raw_predictive_interval, raw_predictive_material_cost, raw_predictive_labor_time, raw_predictive_labor_human,
raw_project_task_material_cost, "raw_loss_output_MW", raw_loss_output_price,
raw_operational_cost, raw_maintenance_cost,
rc_cm_material_cost, rc_cm_labor_cost,
rc_pm_material_cost, rc_pm_labor_cost,
rc_oh_material_cost, rc_oh_labor_cost,
rc_predictive_labor_cost,
rc_project_material_cost, rc_lost_cost, rc_operation_cost, rc_maintenance_cost,
rc_total_cost,
eac_npv, eac_annual_mnt_cost, eac_annual_acq_cost, eac_disposal_cost, eac_eac,
efdh_equivalent_forced_derated_hours, foh_forced_outage_hours,
created_by, NOW(), %s
FROM lcc_equipment_tr_data
WHERE assetnum = %s
"""
cursor.execute(history_tr_query, (acq_year_ref, assetnum))
# 3. Delete old data
del_query = "DELETE FROM lcc_equipment_tr_data WHERE assetnum = %s"
cursor.execute(del_query, (assetnum,))
# 4. Update Equipment Master
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, acquisition_cost = %s
WHERE assetnum = %s
"""
cursor.execute(update_q, (first_year, new_target, target_acq_cost, assetnum))
conn.commit()
print(f"Updated acquisition info for {assetnum}. History archived.")
updated_acq = first_year
else:
print(f"No acquisition info update needed for {assetnum}.")
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 query_data(target_assetnum: str = None):
connection = None
connection_wo_db = None
@ -1152,8 +975,16 @@ async def query_data(target_assetnum: str = None):
continue
# Check if there is acquisition year that need to be updated because of new equipment replacement
if assetnum:
await update_equipment_acquisition_year(assetnum)
# Only run this check if running in batch mode (no target_assetnum)
if assetnum and not target_assetnum:
from src.equipment.service import check_and_update_acquisition_data
from src.database.core import get_session
try:
async with get_session() as session:
await check_and_update_acquisition_data(session, assetnum)
except Exception as exc:
print(f"Error checking acquisition data for {assetnum}: {exc}")
forecasting_start_year_db = row.get("forecasting_start_year")

Loading…
Cancel
Save