feat: enhance forecast trending retrieval with improved simulation querying and fallback logic

main
Cizz22 1 week ago
parent dd8fe1ecd7
commit 042e11b06a

@ -1046,42 +1046,35 @@ 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).
Return EAF/EFOR/SOF/EDH for the last `limit` completed simulations.
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.
1. Try simulations named Simulation_weekly_* / Simulation_monthly_* (forecast schedule).
2. If fewer than `limit` found, fall back to any completed simulation grouped by
week/month bucket so the chart is always populated.
Each simulation's plant metrics are fetched via get_plant_calc_result (same path the
dashboard uses) rather than a JOIN, avoiding silent row-drops when CalcResults aren't
yet committed under the plant node name.
"""
plant_node_name = "- TJB - Unit 3 -"
name_prefix = f"Simulation_{time_range}_"
base_join = (
select(AerosSimulation, AerosSimulationCalcResult)
.join(
AerosSimulationCalcResult,
AerosSimulationCalcResult.aeros_simulation_id == AerosSimulation.id,
)
.join(AerosNode, AerosNode.id == AerosSimulationCalcResult.aeros_node_id)
async def _sims_query(extra_where=None):
q = (
select(AerosSimulation)
.where(AerosSimulation.status == "completed")
.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()
if extra_where is not None:
q = q.where(extra_where)
return q
def _format(rows):
return [
{
async def _fetch_plant(sim) -> dict | None:
try:
calc = await get_plant_calc_result(db_session=db_session, simulation_id=sim.id)
if calc is None:
return None
return {
"date": sim.completed_at,
"simulation_id": str(sim.id),
"simulation_name": sim.simulation_name,
@ -1090,32 +1083,48 @@ async def get_forecast_trending(
"sof": calc.sof,
"edh": calc.derating_hours,
}
for sim, calc in rows
]
if len(named_rows) >= limit:
return _format(reversed(named_rows))
except Exception:
return None
# 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)
# --- Pass 1: named forecast simulations ---
named_q = await _sims_query(
AerosSimulation.simulation_name.like(f"{name_prefix}%")
)
named_q = named_q.limit(limit)
result = await db_session.execute(named_q)
named_sims = result.scalars().all()
points = []
for sim in reversed(named_sims): # oldest → newest
pt = await _fetch_plant(sim)
if pt:
points.append(pt)
if len(points) >= limit:
return points
# --- Pass 2: any completed sim, bucket by week/month ---
fallback_q = await _sims_query()
fallback_q = fallback_q.limit(limit * 20)
result = await db_session.execute(fallback_q)
all_rows = result.all()
all_sims = result.scalars().all()
buckets: dict = {}
for sim, calc in all_rows:
for sim in all_sims:
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")
bucket = ts.strftime("%G-W%V") if time_range == "weekly" else ts.strftime("%Y-%m")
if bucket not in buckets:
buckets[bucket] = (sim, calc)
buckets[bucket] = sim
sorted_sims = [sim for _, sim in sorted(buckets.items(), key=lambda x: x[0])]
sorted_sims = sorted_sims[-limit:] # keep last `limit` buckets
fallback_points = []
for sim in sorted_sims:
pt = await _fetch_plant(sim)
if pt:
fallback_points.append(pt)
sorted_items = sorted(buckets.items(), key=lambda x: x[0])[-limit:]
return _format((sim, calc) for _, (sim, calc) in sorted_items)
return fallback_points if fallback_points else points

Loading…
Cancel
Save