feat: add forecast trending endpoint and ForecastTrendPoint model for simulation data

main
Cizz22 1 week ago
parent f6f762f6fb
commit 0d7010aff7

@ -24,6 +24,7 @@ from src.aeros_project.service import get_project
from temporal.workflow import SimulationWorkflow
from .schema import (
AhmMetricInput,
ForecastTrendPoint,
SimulationCalcResult,
SimulationCalcResultQuery,
SimulationInput,
@ -42,6 +43,7 @@ from .service import (
get_all,
get_custom_parameters,
get_default_simulation,
get_forecast_trending,
get_simulation_by_id,
get_simulation_with_calc_result,
get_simulation_with_plot_result,
@ -171,11 +173,10 @@ async def run_yearly_simulation(
overhaul_duration = 1200
if next_overhaul:
if next_overhaul and last_overhaul:
next_oh_start = date_to_utc(next_overhaul["start_date"])
next_oh_duration_hours = next_overhaul["duration_oh"] * 24
overhaul_interval = int(
(next_oh_start - last_oh_dt).total_seconds() // 3600
)
@ -523,12 +524,15 @@ async def get_forecast_eaf(
oh_date_str = oh_record.value_string
try:
# Ensure oh_date is naive
oh_date = datetime.fromisoformat(oh_date_str).replace(tzinfo=None)
next_oh_date = datetime.fromisoformat(next_oh.value_string).replace(tzinfo=None)
except Exception:
oh_date = datetime(now.year, 1, 1).replace(tzinfo=None)
try:
next_oh_date = datetime.fromisoformat(next_oh.value_string).replace(tzinfo=None)
except Exception:
next_oh_date = oh_date + timedelta(hours=8760 * 2) # fallback: 2 years after last OH
oh_interval = hours_between(oh_date, next_oh_date)
duration = 168 if time_range == "weekly" else 720
end_date = now + timedelta(hours=duration)
@ -579,8 +583,29 @@ async def get_forecast_eaf(
data=str(simulation_id),
message=forecast_msg
)
@router.get("/forecast/trending", response_model=StandardResponse[List[ForecastTrendPoint]])
async def get_forecast_trending_endpoint(
db_session: DbSession,
time_range: str = Query(..., alias="range", regex="^(weekly|monthly)$"),
limit: int = Query(12, ge=1, le=52),
):
"""
Return EAF/EFOR/SOF/EDH trending for the last N completed weekly or monthly
forecast simulations, sorted oldest newest for chart rendering.
Query params:
range "weekly" or "monthly"
limit max data points (default 12, max 52)
"""
results = await get_forecast_trending(
db_session=db_session, time_range=time_range, limit=limit
)
return StandardResponse(
data=results,
message=f"{time_range.capitalize()} EAF/EFOR trending retrieved ({len(results)} points)",
)
@router.get("/custom_parameters", response_model=StandardResponse[list])

@ -130,6 +130,16 @@ class YearlySimulationInput(DefultBase):
year: int
class ForecastTrendPoint(DefultBase):
date: Optional[datetime] = None
simulation_id: str
simulation_name: str
eaf: Optional[float] = None
efor: Optional[float] = None
sof: Optional[float] = None
edh: Optional[float] = None
class SimulationQueryModel(CommonParams):
status: Optional[str] = Field(None, max_length=20)

@ -526,9 +526,7 @@ async def process_calc_results_streaming(
# Parse nodeResultOuts array incrementally
calc_results = ijson.items(response_stream, 'nodeResultOuts.item')
raise Exception(calc_results)
async for result in calc_results:
calc_obj = await create_calc_result_object(
simulation_id, result, available_nodes, eq_update, db_session
@ -843,10 +841,7 @@ async def save_simulation_result(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)
)
# db_session.add_all(calc_objects)
# db_session.add_all(plot_objects)
raise Exception(plot_result)
db_session.add_all(plot_objects)
simulation = await get_simulation_by_id(
db_session=db_session, simulation_id=simulation_id
@ -1045,3 +1040,44 @@ async def update_simulation(*, db_session: DbSession, simulation_id: UUID, data:
query = update(AerosSimulation).where(AerosSimulation.id == simulation_id).values(**data)
await db_session.execute(query)
await db_session.commit()
async def get_forecast_trending(
*, db_session: DbSession, time_range: str, limit: int = 12
) -> list:
"""
Return EAF/EFOR/SOF/EDH for the last `limit` completed weekly or monthly
forecast simulations, oldest first (for chronological chart rendering).
"""
name_prefix = f"Simulation_{time_range}_"
plant_node_name = "- TJB - Unit 3 -"
query = (
select(AerosSimulation, AerosSimulationCalcResult)
.join(
AerosSimulationCalcResult,
AerosSimulationCalcResult.aeros_simulation_id == AerosSimulation.id,
)
.join(AerosNode, AerosNode.id == AerosSimulationCalcResult.aeros_node_id)
.where(AerosSimulation.status == "completed")
.where(AerosSimulation.simulation_name.like(f"{name_prefix}%"))
.where(AerosNode.node_name == plant_node_name)
.order_by(AerosSimulation.completed_at.desc())
.limit(limit)
)
result = await db_session.execute(query)
rows = result.all()
return [
{
"date": sim.completed_at,
"simulation_id": str(sim.id),
"simulation_name": sim.simulation_name,
"eaf": calc.eaf,
"efor": calc.efor,
"sof": calc.sof,
"edh": calc.derating_hours,
}
for sim, calc in reversed(rows) # oldest → newest for chart x-axis
]

Loading…
Cancel
Save