diff --git a/src/aeros_simulation/service.py b/src/aeros_simulation/service.py index fed0044..ab3770b 100644 --- a/src/aeros_simulation/service.py +++ b/src/aeros_simulation/service.py @@ -1048,11 +1048,17 @@ async def get_forecast_trending( """ Return EAF/EFOR/SOF/EDH for the last `limit` completed weekly or monthly forecast simulations, oldest first (for chronological chart rendering). + + Strategy: + 1. Try simulations named Simulation_weekly_* / Simulation_monthly_* (scheduled forecasts). + 2. If fewer than `limit` found, fall back to any completed simulation, grouped + by week or month bucket (latest sim per bucket), so the chart is populated + even before automated forecast simulations exist. """ - name_prefix = f"Simulation_{time_range}_" plant_node_name = "- TJB - Unit 3 -" + name_prefix = f"Simulation_{time_range}_" - query = ( + base_join = ( select(AerosSimulation, AerosSimulationCalcResult) .join( AerosSimulationCalcResult, @@ -1060,24 +1066,56 @@ async def get_forecast_trending( ) .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) + ) + + # First pass: named forecast simulations + named_q = ( + base_join + .where(AerosSimulation.simulation_name.like(f"{name_prefix}%")) .order_by(AerosSimulation.completed_at.desc()) .limit(limit) ) + result = await db_session.execute(named_q) + named_rows = result.all() + + def _format(rows): + 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 rows + ] + + if len(named_rows) >= limit: + return _format(reversed(named_rows)) + + # Fallback: any completed sim, group by week/month bucket (latest per bucket) + fallback_q = ( + base_join + .order_by(AerosSimulation.completed_at.desc()) + .limit(limit * 20) + ) + result = await db_session.execute(fallback_q) + all_rows = result.all() - 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 - ] + buckets: dict = {} + for sim, calc in all_rows: + ts = sim.completed_at + if ts is None: + continue + if time_range == "weekly": + bucket = ts.strftime("%G-W%V") # ISO week + else: + bucket = ts.strftime("%Y-%m") + if bucket not in buckets: + buckets[bucket] = (sim, calc) + + sorted_items = sorted(buckets.items(), key=lambda x: x[0])[-limit:] + return _format((sim, calc) for _, (sim, calc) in sorted_items)