Two-layer price-aware energy management: a planner that pre-computes optimal schedules, and a dispatcher that executes them in real-time.
Key Differences at a Glance
| Feature | v1 Original | v2 Improved |
|---|---|---|
| SOC target formula | Readiness + price penalty | + arbitrage boost + pre-charge |
| Moderate slot throttle | 100% idle rate | 20-60% (risk-scaled) |
| Arbitrage (cheap > expensive) | No | Yes (RTE-adjusted) |
| Pre-charging in cheap slots | No | Yes (spread-justified) |
| EV serving in expensive slots | PV > Grid > Batt | PV > Batt > Grid |
| RTE in thresholds | Not factored | All thresholds adjusted |
Currently active: v1 (Original). Click "Run Simulation" in the sidebar to apply changes.
Uses only data knowable 12-24h ahead. Produces a schedule that optimizes battery charging cost and readiness. Includes overnight pre-charge ramp for multi-day continuity.
Inputs (known ahead)
Outputs (schedule for dispatcher)
Day continuity model (06:00 reference)
The simulation day anchors at 06:00 with a reference SOC of 46%. Day continuity means: SOC at 06:00 start = SOC at 06:00 end.Pre-dawn ramp (00:00-08:22): From the reference SOC at midnight, the target ramps to the demand-start target. Cheap overnight hours ensure the battery is fully charged before the first EVs arrive.Active day (08:22-18:51): Readiness + price formula drives the dynamic target. Battery discharges to serve EVs and exploits price arbitrage.Evening recharge (18:51-05:59): After demand drops, the target ramps back toward reference SOC (46%) using cheap night rates, closing the daily cycle.Dispatcher start: SmartEMS starts at reference SOC (46%). Reactive BMS starts at its own configured target (80%).
Same reactive engine as BMS Reactive, but with 3 targeted upgrades.
Change 1: Dynamic SOC target
Fixed idleTargetSoc = 80% replaced by socTargetCurve[minute] which varies 46-86% across the day.
Change 2: Price-gated grid imports
Grid-to-battery recharging is rate-limited or blocked during expensive/peak price slots. Moderate slots at full idle rate (reduced only at risk > 70). Emergency override below 30% SOC.
Change 3: Smart PV routing
PV surplus is stored in battery when future import costs exceed current export revenue; exported otherwise.
Honest Tradeoff: Readiness vs. Cost
SmartEMS does NOT guarantee the same battery buffer as Reactive. During expensive price windows, SOC may be lower. Safety nets: (1) readiness boost always overrides at least 80% of price penalty (risk-scaled), (2) emergency override at 30% SOC forces grid charging regardless of price.The comparison page shows the exact risk metrics.
Hourly Arrival Probability (Saturday, Supermarket HPC)
Readiness Score (0-1, after 45-min look-ahead convolution)
// Day continuity: overnight ramp to morning readiness
// Simulates multi-day steady state without running multiple days.
// Overnight: baseSoc → ramp up → ready before demand → active day → formula handles evening naturally
baseSoc = (20 + 90) / 2 + (-0.75 * 12) = 46.0% // midpoint of SOC range - risk shift
// Find first meaningful demand window
// Scan readiness curve to find the first minute where readiness >= 0.3
// This is when arrival probability becomes significant (~25%+)
// The battery must be ready BEFORE this point, not scrambling during it
demandStart = first minute where readiness[m] > 0.3
morningTarget = baseSoc + readiness[demandStart] * 25 + max(0, priceMod[demandStart])
// morningTarget includes the readiness boost at demand start -- this is the
// SOC level needed to serve the first EVs without immediately drawing grid
// Phase 1: Overnight ramp (00:00 → demandStart)
overnightFloor[m] = baseSoc + (m / demandStart) * (morningTarget - baseSoc)
socTarget[m] = max(formula_target, overnightFloor[m])
// Gradually charges from ~46% using cheap overnight electricity
// Battery arrives at morningTarget just as the first EVs are expected
// Phase 2: Active day (demandStart → 19:59)
socTarget[m] = baseSoc + readinessBoost + priceMod + arbitrageBoost
// Readiness drives SOC up before expected arrivals (high readiness = high SOC)
// Price signal drives SOC down during expensive slots (avoid costly grid import)
// Safety clamp prevents price from erasing more than 80% of readiness boost
// Evening (20:00+): no artificial wind-down
// readiness drops to 0 → readinessBoost = 0
// prices are expensive/peak → priceMod = negative
// Formula naturally produces baseSoc-level targets
// Evening recharge: target ramps back toward referenceSoc (46%) using cheap night rates
// Ensures day continuity: SOC at 06:00 start ≈ SOC at 06:00 end
// SmartEMS dispatcher starts at referenceSoc (46%), not fixed 80%
// Reactive BMS starts at its own configured target (80%) -- each uses its natural steady state
// Risk factor modulation (risk = 75):
riskNorm = 75 / 100 = 0.75
baseSocShift = -riskNorm * 12 = -9.0 pct pts
safetyClamp = 0.5 + riskNorm * 0.4 = 0.80 // price can override this fraction of readiness
// For each minute m:
baseSoc = (socFloor + socCeiling) / 2 + baseSocShift = (20 + 90) / 2 + (-9.0) = 46.0%
readinessBoost = readiness[m] * 25 // 0 to +25%
priceMod = { cheap: +15, moderate: 0, expensive: -15, peak: -20 }
// NEW: Opportunistic pre-charging in cheap slots
if (zone == "cheap") {
headroom = socCeiling - baseSoc = 90 - 46.0 = 44.0%
cheapFill = headroom * (0.6 + riskNorm * 0.38) = 38.9%
priceMod = max(priceMod, cheapFill) // overrides standard boost
}
// NEW: Price spread arbitrage (6-hour look-ahead)
if (currentCost < medianCost * 0.7 && priceSpread > 0.03 EUR/kWh) {
spreadNorm = clamp((priceSpread - 0.03) / 0.12, 0, 1)
maxArbitrageBoost = usableRange * (0.3 + riskNorm * 0.2) = 31.5% max
arbitrageBoost = spreadNorm * maxArbitrageBoost
}
// Safety clamp: price can override at most 80% of readiness boost
if (priceMod < 0 && readinessBoost > 0)
priceMod = max(priceMod, -(readinessBoost * 0.80))
raw = clamp(baseSoc + readinessBoost + priceMod + arbitrageBoost, 23%, 90%)
// Then apply overnight ramp floor (00:00 to demandStart)
socTarget[m] = clamp(max(raw, overnightFloor[m]), 23%, 90%)
// NEW: Demand charge shaving (dispatcher, during EV sessions)
// Expensive/peak slots: priority = PV > Battery > Grid (capped)
// Cheap slots: priority = PV > Grid > Battery (standard)
if (zone == "expensive" || zone == "peak") {
batteryAssist = min(evNeed, dischargeRate * 1.0) // battery first
gridCap = gridLimit * max(0.5, 1 - riskNorm * 0.5) = 63% of grid limit
} else if (zone == "moderate") {
batteryAssist = min(evNeed, dischargeRate * (0.3 + riskNorm * 0.4)) = 60% of discharge rate
}
// Result: lower peak 15-min demand → reduced annual Leistungspreis