|
|
|
@ -497,13 +497,13 @@ async def get_simulation_report(db_session: DbSession, simulation_id: str):
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/forecast/eaf", response_model=StandardResponse[dict])
|
|
|
|
@router.get("/forecast/eaf", response_model=StandardResponse[str])
|
|
|
|
async def get_forecast_eaf(
|
|
|
|
async def get_forecast_eaf(
|
|
|
|
db_session: DbSession,
|
|
|
|
db_session: DbSession,
|
|
|
|
time_range: str = Query(..., alias="range", regex="^(weekly|monthly)$"),
|
|
|
|
time_range: str = Query(..., alias="range", regex="^(weekly|monthly)$"),
|
|
|
|
):
|
|
|
|
):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Generate a forecasted EAF value for the next week or month.
|
|
|
|
Trigger a forecasted EAF simulation for the next week or month using Temporal.
|
|
|
|
Calculates offset from 'Last Overhaul Date' in master data.
|
|
|
|
Calculates offset from 'Last Overhaul Date' in master data.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
from src.dashboard_model.service import _get_or_create_master_data
|
|
|
|
from src.dashboard_model.service import _get_or_create_master_data
|
|
|
|
@ -528,19 +528,19 @@ async def get_forecast_eaf(
|
|
|
|
oh_date = datetime(now.year, 1, 1).replace(tzinfo=None)
|
|
|
|
oh_date = datetime(now.year, 1, 1).replace(tzinfo=None)
|
|
|
|
|
|
|
|
|
|
|
|
# 3. Calculate Age in Hours (Offset)
|
|
|
|
# 3. Calculate Age in Hours (Offset)
|
|
|
|
# Using the helper from utils.py
|
|
|
|
|
|
|
|
offset_hours = hours_between(oh_date, now)
|
|
|
|
offset_hours = hours_between(oh_date, now)
|
|
|
|
|
|
|
|
|
|
|
|
# start date is oh + offset
|
|
|
|
|
|
|
|
start_date = oh_date + timedelta(hours=offset_hours)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 4. Define Forecast Window
|
|
|
|
# 4. Define Forecast Window
|
|
|
|
duration = 168 if time_range == "weekly" else 720
|
|
|
|
duration = 168 if time_range == "weekly" else 720
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
start_date = oh_date + timedelta(hours=offset_hours)
|
|
|
|
|
|
|
|
end_date = start_date + timedelta(hours=duration)
|
|
|
|
|
|
|
|
project = await get_project(db_session=db_session)
|
|
|
|
|
|
|
|
|
|
|
|
# 5. Create Simulation Request
|
|
|
|
# 5. Create Simulation Request
|
|
|
|
sim_in = SimulationInput(
|
|
|
|
sim_in = SimulationInput(
|
|
|
|
SchematicName="- TJB - Unit 3 -",
|
|
|
|
SchematicName="- TJB - Unit 3 -",
|
|
|
|
SimulationName=f"Simulation{time_range}_{now.strftime('%Y%m%d')}",
|
|
|
|
SimulationName=f"Simulation_{time_range}_{now.strftime('%Y%m%d')}",
|
|
|
|
SimDuration=duration,
|
|
|
|
SimDuration=duration,
|
|
|
|
DurationUnit="UHour",
|
|
|
|
DurationUnit="UHour",
|
|
|
|
OffSet=offset_hours,
|
|
|
|
OffSet=offset_hours,
|
|
|
|
@ -549,52 +549,37 @@ async def get_forecast_eaf(
|
|
|
|
IsDefault=False
|
|
|
|
IsDefault=False
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 6. Run simulation (blocking call to engine)
|
|
|
|
# 6. Initialize Simulation in DB
|
|
|
|
try:
|
|
|
|
simulation = await create_simulation(
|
|
|
|
raw_result = await execute_simulation(
|
|
|
|
db_session=db_session,
|
|
|
|
db_session=db_session,
|
|
|
|
simulation_in=sim_in,
|
|
|
|
sim_data=sim_in.model_dump(),
|
|
|
|
current_user=None
|
|
|
|
is_saved=False
|
|
|
|
)
|
|
|
|
)
|
|
|
|
simulation_id = simulation.id
|
|
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
forecast_msg = (
|
|
|
|
|
|
|
|
f"Forecast generated successfully, "
|
|
|
|
|
|
|
|
f"start date: {start_date.strftime('%Y-%m-%d %H:%M:%S')}, "
|
|
|
|
|
|
|
|
f"end_date: {(start_date + timedelta(hours=duration)).strftime('%Y-%m-%d %H:%M:%S')}"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return StandardResponse(
|
|
|
|
# 7. Start Temporal Workflow
|
|
|
|
data={
|
|
|
|
temporal_client = await Client.connect(TEMPORAL_URL)
|
|
|
|
"range": time_range,
|
|
|
|
|
|
|
|
"eaf_predicted": float(eaf) if eaf is not None else 0.0,
|
|
|
|
sim_data = sim_in.model_dump()
|
|
|
|
"efor_predicted": float(efor),
|
|
|
|
sim_data["HubCnnId"] = str(simulation_id)
|
|
|
|
"reliability_age_hours": offset_hours,
|
|
|
|
sim_data["projectName"] = project.project_name
|
|
|
|
"sim_duration_hours": duration,
|
|
|
|
|
|
|
|
"reference_oh_date": oh_date_str
|
|
|
|
await temporal_client.start_workflow(
|
|
|
|
},
|
|
|
|
SimulationWorkflow.run,
|
|
|
|
message=forecast_msg
|
|
|
|
sim_data,
|
|
|
|
)
|
|
|
|
id=f"simulation-{simulation_id}",
|
|
|
|
except Exception as e:
|
|
|
|
task_queue="simulation-task-queue",
|
|
|
|
import logging
|
|
|
|
)
|
|
|
|
logging.getLogger(__name__).error(f"Forecast failed: {str(e)}")
|
|
|
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Simulation failed: {str(e)}")
|
|
|
|
forecast_msg = (
|
|
|
|
|
|
|
|
f"Forecast simulation started via Temporal. "
|
|
|
|
|
|
|
|
f"Period: {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return StandardResponse(
|
|
|
|
|
|
|
|
data=str(simulation_id),
|
|
|
|
|
|
|
|
message=forecast_msg
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|