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

@@ -107,7 +107,7 @@ func (e *Engine) Decide(state collector.SystemState, now time.Time, wwBoostC flo
soc := state.BatterySOC
gridW := state.GridPowerW // positive = import, negative = export
heatingPeriod := e.isHeatingPeriod(now)
heatingPeriod := e.isHeatingPeriod(now, state.AmbientTempC)
wwWindow := e.isWWWindow(now)
allowed := e.allowedConsumers(soc)
@@ -502,17 +502,37 @@ func (e *Engine) ApplyOverride(consumer Consumer, on bool, duration time.Duratio
)
}
// isHeatingPeriod returns true if the current month is within the heating season.
func (e *Engine) isHeatingPeriod(now time.Time) bool {
// isHeatingPeriod returns true if heating is appropriate given the current month
// and outdoor temperature. If HeatingMinAmbientC is configured (> 0), ambient
// temperatures above that threshold suppress SG-Ready even within the heating months.
func (e *Engine) isHeatingPeriod(now time.Time, ambientC float64) bool {
month := int(now.Month())
start := e.cfg.Season.HeatingStartMonth
end := e.cfg.Season.HeatingEndMonth
// Handles wrap-around: e.g. October(10) to April(4)
var inMonth bool
if start > end {
return month >= start || month <= end
inMonth = month >= start || month <= end
} else {
inMonth = month >= start && month <= end
}
return month >= start && month <= end
if !inMonth {
return false
}
// Temperature override: warm day within heating months → not a heating day
threshold := e.cfg.Season.HeatingMinAmbientC
if threshold > 0 && ambientC >= threshold {
e.logger.Debug("heating period suppressed by ambient temperature",
"ambient_c", ambientC,
"threshold_c", threshold,
)
return false
}
return true
}
// minRuntime returns the minimum runtime for a consumer.