Fix daily planner: pass forecast + budget to plan_day()
Browse files
backend/workers/daily_planner.py
CHANGED
|
@@ -33,16 +33,69 @@ logging.basicConfig(
|
|
| 33 |
log = logging.getLogger("daily_planner")
|
| 34 |
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
def main():
|
| 37 |
from src.day_ahead_planner import DayAheadPlanner
|
| 38 |
from src.data.redis_cache import get_redis
|
| 39 |
-
from config.settings import DAILY_PLAN_PATH
|
| 40 |
|
| 41 |
target = date.today()
|
| 42 |
log.info("Computing day-ahead plan for %s", target)
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
planner = DayAheadPlanner()
|
| 45 |
-
plan = planner.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
plan_dict = plan.to_dict() if hasattr(plan, "to_dict") else {"raw": str(plan)}
|
| 48 |
plan_dict["_computed_at"] = datetime.now(timezone.utc).isoformat()
|
|
|
|
| 33 |
log = logging.getLogger("daily_planner")
|
| 34 |
|
| 35 |
|
| 36 |
+
def _get_forecast(target: date):
|
| 37 |
+
"""Build 96-slot (15-min) forecast arrays from IMS cache or defaults."""
|
| 38 |
+
try:
|
| 39 |
+
from src.data.ims_client import IMSClient
|
| 40 |
+
import pandas as pd
|
| 41 |
+
|
| 42 |
+
client = IMSClient()
|
| 43 |
+
df = client.load_cached()
|
| 44 |
+
if df.empty:
|
| 45 |
+
raise ValueError("No IMS cache available")
|
| 46 |
+
|
| 47 |
+
# Get the most recent day's pattern as a proxy for tomorrow
|
| 48 |
+
df["hour"] = pd.to_datetime(df["timestamp_utc"]).dt.hour
|
| 49 |
+
# Build 96 slots of temp and GHI
|
| 50 |
+
temps = []
|
| 51 |
+
ghis = []
|
| 52 |
+
for slot in range(96):
|
| 53 |
+
h = slot // 4
|
| 54 |
+
# Use mean values per hour from recent data
|
| 55 |
+
hour_data = df[df["hour"] == h]
|
| 56 |
+
t = hour_data["air_temperature_c"].mean() if "air_temperature_c" in df.columns and not hour_data.empty else 25.0
|
| 57 |
+
g = hour_data["ghi_w_m2"].mean() if "ghi_w_m2" in df.columns and not hour_data.empty else 0.0
|
| 58 |
+
temps.append(float(t) if not pd.isna(t) else 25.0)
|
| 59 |
+
ghis.append(float(g) if not pd.isna(g) else 0.0)
|
| 60 |
+
return temps, ghis
|
| 61 |
+
except Exception as exc:
|
| 62 |
+
log.warning("Could not build forecast from IMS: %s — using defaults", exc)
|
| 63 |
+
# Default: sinusoidal temperature and GHI profile for Sde Boker
|
| 64 |
+
import math
|
| 65 |
+
temps = []
|
| 66 |
+
ghis = []
|
| 67 |
+
for slot in range(96):
|
| 68 |
+
h = slot / 4 # fractional hour
|
| 69 |
+
# Temp: 18°C at night, peaks ~35°C at 14:00
|
| 70 |
+
t = 26.5 + 8.5 * math.sin(math.pi * (h - 6) / 12) if 6 <= h <= 18 else 20.0
|
| 71 |
+
# GHI: 0 at night, peaks ~900 W/m² at noon
|
| 72 |
+
g = max(0, 900 * math.sin(math.pi * (h - 6) / 12)) if 6 <= h <= 18 else 0.0
|
| 73 |
+
temps.append(round(t, 1))
|
| 74 |
+
ghis.append(round(g, 1))
|
| 75 |
+
return temps, ghis
|
| 76 |
+
|
| 77 |
+
|
| 78 |
def main():
|
| 79 |
from src.day_ahead_planner import DayAheadPlanner
|
| 80 |
from src.data.redis_cache import get_redis
|
| 81 |
+
from config.settings import DAILY_PLAN_PATH, MAX_ENERGY_REDUCTION_PCT
|
| 82 |
|
| 83 |
target = date.today()
|
| 84 |
log.info("Computing day-ahead plan for %s", target)
|
| 85 |
|
| 86 |
+
# Build forecast inputs
|
| 87 |
+
forecast_temps, forecast_ghi = _get_forecast(target)
|
| 88 |
+
|
| 89 |
+
# Compute a reasonable daily budget (5% of ~25 kWh potential = ~1.25 kWh)
|
| 90 |
+
daily_budget_kwh = 25.0 * MAX_ENERGY_REDUCTION_PCT / 100.0
|
| 91 |
+
|
| 92 |
planner = DayAheadPlanner()
|
| 93 |
+
plan = planner.plan_day(
|
| 94 |
+
target_date=target,
|
| 95 |
+
forecast_temps=forecast_temps,
|
| 96 |
+
forecast_ghi=forecast_ghi,
|
| 97 |
+
daily_budget_kwh=daily_budget_kwh,
|
| 98 |
+
)
|
| 99 |
|
| 100 |
plan_dict = plan.to_dict() if hasattr(plan, "to_dict") else {"raw": str(plan)}
|
| 101 |
plan_dict["_computed_at"] = datetime.now(timezone.utc).isoformat()
|