Add ambient temperature gate to heating period detection

Adds heating_min_ambient_c (default 15°C) to season config. When outdoor
temperature is at or above this threshold, SG-Ready is suppressed even if
the calendar month is within the heating season. Prevents unnecessary heat
pump boost activation on warm spring/autumn days.

Logic: heating active = in_heating_month AND ambient < threshold
Zero value (unset) disables the temperature gate (calendar-only, old behaviour).

New test: TestHeatingPeriodAmbientSuppression covers warm-day suppression,
cold-day pass-through, and summer month independence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 11:13:05 +02:00
parent cc33add507
commit 0b51ce5240
4 changed files with 55 additions and 9 deletions

View File

@@ -208,6 +208,7 @@ func TestShutdownReverseOrder(t *testing.T) {
func TestHeatingPeriodDetection(t *testing.T) {
eng := NewEngine(testConfig(), testLogger())
cold := 5.0 // well below any threshold — pure calendar test
tests := []struct {
month time.Month
@@ -230,13 +231,36 @@ func TestHeatingPeriodDetection(t *testing.T) {
for _, tt := range tests {
t.Run(tt.month.String(), func(t *testing.T) {
date := time.Date(2025, tt.month, 15, 12, 0, 0, 0, time.UTC)
if got := eng.isHeatingPeriod(date); got != tt.expected {
if got := eng.isHeatingPeriod(date, cold); got != tt.expected {
t.Errorf("month %s: got %v, want %v", tt.month, got, tt.expected)
}
})
}
}
func TestHeatingPeriodAmbientSuppression(t *testing.T) {
cfg := testConfig()
cfg.Season.HeatingMinAmbientC = 15.0
eng := NewEngine(cfg, testLogger())
// Winter month, but warm day — should be suppressed
warmWinterDay := time.Date(2025, time.January, 15, 12, 0, 0, 0, time.UTC)
if eng.isHeatingPeriod(warmWinterDay, 18.0) {
t.Error("expected heating period suppressed when ambient (18°C) >= threshold (15°C)")
}
// Winter month, cold day — should be active
if !eng.isHeatingPeriod(warmWinterDay, 8.0) {
t.Error("expected heating period active when ambient (8°C) < threshold (15°C)")
}
// Summer month, cold day — still not heating (month gate takes precedence)
summerDay := time.Date(2025, time.July, 15, 12, 0, 0, 0, time.UTC)
if eng.isHeatingPeriod(summerDay, 5.0) {
t.Error("expected heating period inactive in summer regardless of temperature")
}
}
func TestWallboxMutualExclusion(t *testing.T) {
cfg := testConfig()
cfg.Hysteresis.ExportOnDuration = "0s"