diff --git a/src/equipment/__pycache__/service.cpython-311.pyc b/src/equipment/__pycache__/service.cpython-311.pyc index 9559c63..158ac2f 100644 Binary files a/src/equipment/__pycache__/service.cpython-311.pyc and b/src/equipment/__pycache__/service.cpython-311.pyc differ diff --git a/src/equipment/service.py b/src/equipment/service.py index 002f3fc..e3080c9 100644 --- a/src/equipment/service.py +++ b/src/equipment/service.py @@ -301,11 +301,19 @@ async def get_master_by_assetnum( None, ) - # Historical data query + # Historical data query - filter to only one reference (prioritize oldest acquisition year) + oldest_ref_subquery = ( + Select(EquipmentHistoricalTransactionRecords.acquisition_year_ref) + .filter(EquipmentHistoricalTransactionRecords.assetnum == assetnum) + .order_by(EquipmentHistoricalTransactionRecords.acquisition_year_ref.asc()) + .limit(1) + .scalar_subquery() + ) + historical_query = ( Select(EquipmentHistoricalTransactionRecords) - .join(EquipmentHistoricalTransactionRecords.equipment) - .filter(Equipment.assetnum == assetnum) + .filter(EquipmentHistoricalTransactionRecords.assetnum == assetnum) + .filter(EquipmentHistoricalTransactionRecords.acquisition_year_ref == oldest_ref_subquery) .order_by(EquipmentHistoricalTransactionRecords.tahun.asc()) ) historical_result = await db_session.execute(historical_query) @@ -672,7 +680,7 @@ async def check_and_update_acquisition_data(db_session: DbSession, assetnum: str if conn: try: cursor = conn.cursor() - # Query the first year from wo_maximo + # Query the oldest year from wo_maximo to detect the original acquisition query = """ select DATE_PART('year', a.reportdate) AS year, a.asset_replacecost AS cost from wo_maximo a @@ -720,89 +728,96 @@ async def check_and_update_acquisition_data(db_session: DbSession, assetnum: str change_year = (eq.acquisition_year != first_year) change_cost = (first_cost is not None and eq.acquisition_cost != first_cost) - if change_year or change_cost: - print(f"Acquisition change detected for {assetnum}: Year {current_acq}->{first_year}, Cost {current_acq_cost}->{first_cost}. Archiving history.") + # We only archive transaction history if the acquisition year itself changed. + # This prevents redundant history entries for cost-only updates. + if change_year: + print(f"Acquisition year change detected for {assetnum}: {current_acq}->{first_year}. Archiving history.") acq_year_ref = f"{current_acq}_{current_target}" # --- ARCHIVE HISTORICAL DATA --- - # 1. Copy old equipment master data to history - history_ms_query = text(""" - 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, - :acq_year_ref - FROM lcc_ms_equipment_data - WHERE assetnum = :assetnum - """) - await db_session.execute(history_ms_query, {"acq_year_ref": acq_year_ref, "assetnum": assetnum}) - - # 2. Copy old transaction data to lcc_equipment_historical_tr_data - # Format: {acquisition_year}_{forecasting_target_year} + # Check for existing identical archive to prevent duplicates (after calculation failures/retries) + check_hist_query = text("SELECT 1 FROM lcc_ms_equipment_historical_data WHERE assetnum = :assetnum AND acquisition_year_ref = :acq_year_ref LIMIT 1") + hist_exists = (await db_session.execute(check_hist_query, {"assetnum": assetnum, "acq_year_ref": acq_year_ref})).fetchone() - history_tr_query = text(""" - 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(), :acq_year_ref - FROM lcc_equipment_tr_data - WHERE assetnum = :assetnum - """) - await db_session.execute(history_tr_query, {"acq_year_ref": acq_year_ref, "assetnum": assetnum}) + if not hist_exists: + # 1. Copy old equipment master data to history + history_ms_query = text(""" + 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, + :acq_year_ref + FROM lcc_ms_equipment_data + WHERE assetnum = :assetnum + """) + await db_session.execute(history_ms_query, {"acq_year_ref": acq_year_ref, "assetnum": assetnum}) + + # 2. Copy old transaction data to lcc_equipment_historical_tr_data + history_tr_query = text(""" + 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(), :acq_year_ref + FROM lcc_equipment_tr_data + WHERE assetnum = :assetnum + """) + await db_session.execute(history_tr_query, {"acq_year_ref": acq_year_ref, "assetnum": assetnum}) # 3. Delete old data del_query = text("DELETE FROM lcc_equipment_tr_data WHERE assetnum = :assetnum") await db_session.execute(del_query, {"assetnum": assetnum}) - # Update Equipment Master + # Update Equipment Master regardless of if archive was needed/skipped + if change_year or change_cost: if first_cost is not None and eq.acquisition_cost != first_cost: eq.acquisition_cost = first_cost if eq.acquisition_year != first_year: eq.acquisition_year = first_year + eq.forecasting_start_year = first_year # Align start with acquisition if is_valid_default and current_life: eq.forecasting_target_year = first_year + current_life diff --git a/src/modules/equipment/__pycache__/insert_actual_data.cpython-311.pyc b/src/modules/equipment/__pycache__/insert_actual_data.cpython-311.pyc index 2a6f165..11b06a3 100644 Binary files a/src/modules/equipment/__pycache__/insert_actual_data.cpython-311.pyc and b/src/modules/equipment/__pycache__/insert_actual_data.cpython-311.pyc differ diff --git a/src/modules/equipment/insert_actual_data.py b/src/modules/equipment/insert_actual_data.py index c289efc..88f1764 100644 --- a/src/modules/equipment/insert_actual_data.py +++ b/src/modules/equipment/insert_actual_data.py @@ -993,7 +993,7 @@ async def query_data(target_assetnum: str = None): if acquisition_year: # Remove data before acquisition_year cursor.execute("DELETE FROM lcc_equipment_tr_data WHERE assetnum = %s AND tahun < %s", (assetnum, acquisition_year)) - forecasting_start_year = acquisition_year - 1 + forecasting_start_year = acquisition_year elif forecasting_start_year_db: # If no acquisition_year but forecasting_start_year defined in DB forecasting_start_year = forecasting_start_year_db