|
|
|
@ -1046,42 +1046,35 @@ async def get_forecast_trending(
|
|
|
|
*, db_session: DbSession, time_range: str, limit: int = 12
|
|
|
|
*, db_session: DbSession, time_range: str, limit: int = 12
|
|
|
|
) -> list:
|
|
|
|
) -> list:
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Return EAF/EFOR/SOF/EDH for the last `limit` completed weekly or monthly
|
|
|
|
Return EAF/EFOR/SOF/EDH for the last `limit` completed simulations.
|
|
|
|
forecast simulations, oldest first (for chronological chart rendering).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Strategy:
|
|
|
|
Strategy:
|
|
|
|
1. Try simulations named Simulation_weekly_* / Simulation_monthly_* (scheduled forecasts).
|
|
|
|
1. Try simulations named Simulation_weekly_* / Simulation_monthly_* (forecast schedule).
|
|
|
|
2. If fewer than `limit` found, fall back to any completed simulation, grouped
|
|
|
|
2. If fewer than `limit` found, fall back to any completed simulation grouped by
|
|
|
|
by week or month bucket (latest sim per bucket), so the chart is populated
|
|
|
|
week/month bucket so the chart is always populated.
|
|
|
|
even before automated forecast simulations exist.
|
|
|
|
|
|
|
|
|
|
|
|
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}_"
|
|
|
|
name_prefix = f"Simulation_{time_range}_"
|
|
|
|
|
|
|
|
|
|
|
|
base_join = (
|
|
|
|
async def _sims_query(extra_where=None):
|
|
|
|
select(AerosSimulation, AerosSimulationCalcResult)
|
|
|
|
q = (
|
|
|
|
.join(
|
|
|
|
select(AerosSimulation)
|
|
|
|
AerosSimulationCalcResult,
|
|
|
|
.where(AerosSimulation.status == "completed")
|
|
|
|
AerosSimulationCalcResult.aeros_simulation_id == AerosSimulation.id,
|
|
|
|
.order_by(AerosSimulation.completed_at.desc())
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.join(AerosNode, AerosNode.id == AerosSimulationCalcResult.aeros_node_id)
|
|
|
|
if extra_where is not None:
|
|
|
|
.where(AerosSimulation.status == "completed")
|
|
|
|
q = q.where(extra_where)
|
|
|
|
.where(AerosNode.node_name == plant_node_name)
|
|
|
|
return q
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def _fetch_plant(sim) -> dict | None:
|
|
|
|
# First pass: named forecast simulations
|
|
|
|
try:
|
|
|
|
named_q = (
|
|
|
|
calc = await get_plant_calc_result(db_session=db_session, simulation_id=sim.id)
|
|
|
|
base_join
|
|
|
|
if calc is None:
|
|
|
|
.where(AerosSimulation.simulation_name.like(f"{name_prefix}%"))
|
|
|
|
return None
|
|
|
|
.order_by(AerosSimulation.completed_at.desc())
|
|
|
|
return {
|
|
|
|
.limit(limit)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
result = await db_session.execute(named_q)
|
|
|
|
|
|
|
|
named_rows = result.all()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _format(rows):
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
"date": sim.completed_at,
|
|
|
|
"date": sim.completed_at,
|
|
|
|
"simulation_id": str(sim.id),
|
|
|
|
"simulation_id": str(sim.id),
|
|
|
|
"simulation_name": sim.simulation_name,
|
|
|
|
"simulation_name": sim.simulation_name,
|
|
|
|
@ -1090,32 +1083,48 @@ async def get_forecast_trending(
|
|
|
|
"sof": calc.sof,
|
|
|
|
"sof": calc.sof,
|
|
|
|
"edh": calc.derating_hours,
|
|
|
|
"edh": calc.derating_hours,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for sim, calc in rows
|
|
|
|
except Exception:
|
|
|
|
]
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
if len(named_rows) >= limit:
|
|
|
|
|
|
|
|
return _format(reversed(named_rows))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Fallback: any completed sim, group by week/month bucket (latest per bucket)
|
|
|
|
# --- Pass 1: named forecast simulations ---
|
|
|
|
fallback_q = (
|
|
|
|
named_q = await _sims_query(
|
|
|
|
base_join
|
|
|
|
AerosSimulation.simulation_name.like(f"{name_prefix}%")
|
|
|
|
.order_by(AerosSimulation.completed_at.desc())
|
|
|
|
|
|
|
|
.limit(limit * 20)
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
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)
|
|
|
|
result = await db_session.execute(fallback_q)
|
|
|
|
all_rows = result.all()
|
|
|
|
all_sims = result.scalars().all()
|
|
|
|
|
|
|
|
|
|
|
|
buckets: dict = {}
|
|
|
|
buckets: dict = {}
|
|
|
|
for sim, calc in all_rows:
|
|
|
|
for sim in all_sims:
|
|
|
|
ts = sim.completed_at
|
|
|
|
ts = sim.completed_at
|
|
|
|
if ts is None:
|
|
|
|
if ts is None:
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
if time_range == "weekly":
|
|
|
|
bucket = ts.strftime("%G-W%V") if time_range == "weekly" else ts.strftime("%Y-%m")
|
|
|
|
bucket = ts.strftime("%G-W%V") # ISO week
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
bucket = ts.strftime("%Y-%m")
|
|
|
|
|
|
|
|
if bucket not in buckets:
|
|
|
|
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 fallback_points if fallback_points else points
|
|
|
|
return _format((sim, calc) for _, (sim, calc) in sorted_items)
|
|
|
|
|
|
|
|
|