Add SG-Ready startup grace period and daily PV yield tracking

SG-Ready startup grace period (sg_ready_startup_min: 10):
Heat pump compressor takes ~9 minutes to start after receiving the
SG-Ready signal. Previously, idle detection fired after only 3×2min=6min,
turning off SG-Ready before the compressor had time to respond. The grace
period suppresses idle detection for the configured duration after activation.
0 = disabled (tests and unconfigured deployments are unaffected).

Daily PV yield (photovoltaic_production_cumulated_currentDay):
Reads actual kWh produced today from Prometheus (Wh → kWh, ÷1000).
Logged as pv_today_kwh each cycle. Shown on status page in two places:
- PV card sub-line: "Heute: 31.9 kWh"
- Forecast card alongside forecast: "Ist: 31.9 kWh"
Immediately makes forecast vs reality visible (today: forecast 15.9,
actual 31.9 kWh — explains why SOC bypass triggered).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 21:00:57 +02:00
parent 2728755738
commit db46fcf0c6
5 changed files with 27 additions and 5 deletions

View File

@@ -169,8 +169,20 @@ func (e *Engine) Decide(state collector.SystemState, now time.Time, wwBoostC, fo
}
// --- Compressor idle: release SG-Ready early if heat pump stopped ---
// A startup grace period is applied after activation: the heat pump compressor
// takes several minutes to start after receiving the SG-Ready signal, so idle
// detection is suppressed until the grace period has elapsed.
if cs := e.consumers[ConsumerSGReady]; cs.Active {
if state.CompressorPowerW < float64(e.cfg.Consumers.CompressorIdleW) {
startupMin := e.cfg.Consumers.SGReadyStartupMin
inGrace := startupMin > 0 && !cs.ActivatedAt.IsZero() && now.Sub(cs.ActivatedAt) < time.Duration(startupMin)*time.Minute
if inGrace {
e.logger.Debug("SG-Ready: startup grace period, skipping idle check",
"activated_at", cs.ActivatedAt.Format("15:04"),
"grace_min", startupMin,
"elapsed_min", int(now.Sub(cs.ActivatedAt).Minutes()),
)
} else if state.CompressorPowerW < float64(e.cfg.Consumers.CompressorIdleW) {
cs.LowPowerCycles++
e.logger.Debug("SG-Ready: compressor idle cycle",
"compressor_w", state.CompressorPowerW,