From 074fdf1276e748d06a58e1e67326c2a7ec4d118d Mon Sep 17 00:00:00 2001 From: Cizz22 Date: Mon, 13 Apr 2026 12:21:09 +0700 Subject: [PATCH] feat: add EAF forecasting endpoint and initialize Last Overhaul Date in master data service --- src/aeros_simulation/router.py | 90 ++++++++++++++++++++++++++++++++++ src/dashboard_model/service.py | 1 + 2 files changed, 91 insertions(+) diff --git a/src/aeros_simulation/router.py b/src/aeros_simulation/router.py index 423d40a..10984bc 100644 --- a/src/aeros_simulation/router.py +++ b/src/aeros_simulation/router.py @@ -494,6 +494,96 @@ async def get_simulation_report(db_session: DbSession, simulation_id: str): headers=headers, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ) + + +@router.get("/forecast/eaf", response_model=StandardResponse[dict]) +async def get_forecast_eaf( + db_session: DbSession, + time_range: str = Query(..., alias="range", regex="^(weekly|monthly)$"), +): + """ + Generate a forecasted EAF value for the next week or month. + Calculates offset from 'Last Overhaul Date' in master data. + """ + from src.dashboard_model.service import _get_or_create_master_data + from .schema import SimulationInput + + # 1. Get current time + now = datetime.now() + + # 2. Determine Last Overhaul Date from Master Data + oh_record = await _get_or_create_master_data(db_session, "Last Overhaul Date", 0) + + # If not set, use a fallback (e.g., start of current year) + if not oh_record.value_string: + oh_date_str = f"{now.year}-01-01T00:00:00" + else: + oh_date_str = oh_record.value_string + + try: + oh_date = datetime.fromisoformat(oh_date_str) + except Exception: + oh_date = datetime(now.year, 1, 1) + + # 3. Calculate Age in Hours (Offset) + # Using the helper from utils.py + offset_hours = hours_between(oh_date, now) + + # 4. Define Forecast Window + duration = 168 if time_range == "weekly" else 720 + + # 5. Create Simulation Request + sim_in = SimulationInput( + SchematicName="- TJB - Unit 3 -", + SimulationName=f"Simulation{time_range}_{now.strftime('%Y%m%d')}", + SimDuration=duration, + DurationUnit="UHour", + OffSet=offset_hours, + SimSeed=1, + SimNumRun=1, + IsDefault=False + ) + + # 6. Run simulation (blocking call to engine) + try: + raw_result = await execute_simulation( + db_session=db_session, + sim_data=sim_in.model_dump(), + is_saved=False + ) + + # 7. Extract Root Node Results + node_results = raw_result.get("nodeResultOuts", []) + plant_result = next((r for r in node_results if r.get("nodeName") == "- TJB - Unit 3 -"), None) + + if not plant_result: + # Fallback to any schematic result if name doesn't match perfectly + plant_result = next((r for r in node_results if r.get("nodeType") == "Schematic"), None) + + if not plant_result: + raise HTTPException(status_code=500, detail="Plant node results not found in simulation output") + + # 8. Return predicted metrics + eaf = plant_result.get("eaf") + total_dt = plant_result.get("totalDowntime", 0) + total_ut = plant_result.get("totalUpTime", 0) + efor = (total_dt / (total_dt + total_ut)) * 100 if (total_dt + total_ut) > 0 else 0 + + return StandardResponse( + data={ + "range": time_range, + "eaf_predicted": float(eaf) if eaf is not None else 0.0, + "efor_predicted": float(efor), + "reliability_age_hours": offset_hours, + "sim_duration_hours": duration, + "reference_oh_date": oh_date_str + }, + message="Forecast generated successfully" + ) + except Exception as e: + import logging + logging.getLogger(__name__).error(f"Forecast failed: {str(e)}") + raise HTTPException(status_code=500, detail=f"Simulation failed: {str(e)}") diff --git a/src/dashboard_model/service.py b/src/dashboard_model/service.py index 5af2793..9f547d0 100644 --- a/src/dashboard_model/service.py +++ b/src/dashboard_model/service.py @@ -36,6 +36,7 @@ async def _get_or_create_master_data(db_session: DbSession, name: str, default_f async def get_master_data_list(db_session: DbSession): # Ensure Current EAF exists await _get_or_create_master_data(db_session, "Current EAF Realization", 85.62) + await _get_or_create_master_data(db_session, "Last Overhaul Date", 0) query = select(RBDMasterData) result = await db_session.execute(query) return result.scalars().all()