Files
HeatGuard/internal/risk/analyzer_test.go
vikingowl 21154d5d7f feat: add heating support with heat pump modeling and cold risk detection
Model heating mode when rooms have net heat loss in cold weather (<10°C).
AC units with heat pump capability (canHeat) provide heating capacity,
with the same 20% headroom threshold used for cooling. Adds cold risk
detection, cold-weather actions, and full frontend support including
heating mode timeline colors, room budget heating display, and i18n.
2026-02-11 00:00:43 +01:00

197 lines
4.6 KiB
Go

package risk
import (
"testing"
)
func makeHours(temps []float64) []HourlyData {
hours := make([]HourlyData, len(temps))
for i, t := range temps {
hours[i] = HourlyData{
Hour: i,
TempC: t,
ApparentC: t,
HumidityPct: 50,
IsDay: i >= 6 && i < 21,
}
}
return hours
}
func TestAnalyzeDay_CoolDay(t *testing.T) {
temps := make([]float64, 24)
for i := range temps {
temps[i] = 20 + float64(i%5) // 20-24C
}
result := AnalyzeDay(makeHours(temps), DefaultThresholds())
if result.Level != Low {
t.Errorf("Level = %v, want Low", result.Level)
}
if len(result.Windows) != 0 {
t.Errorf("Windows = %d, want 0", len(result.Windows))
}
}
func TestAnalyzeDay_ModeratelyHot(t *testing.T) {
temps := make([]float64, 24)
for i := range temps {
temps[i] = 18 // base below poor night cooling threshold
}
// Hot window 11-15
for i := 11; i <= 15; i++ {
temps[i] = 32
}
result := AnalyzeDay(makeHours(temps), DefaultThresholds())
if result.Level != Moderate {
t.Errorf("Level = %v, want Moderate", result.Level)
}
if len(result.Windows) != 1 {
t.Fatalf("Windows = %d, want 1", len(result.Windows))
}
w := result.Windows[0]
if w.StartHour != 11 || w.EndHour != 15 {
t.Errorf("Window = %d-%d, want 11-15", w.StartHour, w.EndHour)
}
if w.PeakTempC != 32 {
t.Errorf("PeakTempC = %v, want 32", w.PeakTempC)
}
}
func TestAnalyzeDay_VeryHot(t *testing.T) {
temps := make([]float64, 24)
for i := range temps {
temps[i] = 18 // below poor night cooling threshold
}
for i := 10; i <= 17; i++ {
temps[i] = 37
}
result := AnalyzeDay(makeHours(temps), DefaultThresholds())
if result.Level != High {
t.Errorf("Level = %v, want High", result.Level)
}
}
func TestAnalyzeDay_Extreme(t *testing.T) {
temps := make([]float64, 24)
for i := range temps {
temps[i] = 25
}
for i := 12; i <= 16; i++ {
temps[i] = 41
}
result := AnalyzeDay(makeHours(temps), DefaultThresholds())
if result.Level != Extreme {
t.Errorf("Level = %v, want Extreme", result.Level)
}
}
func TestAnalyzeDay_PoorNightCoolingElevates(t *testing.T) {
temps := make([]float64, 24)
for i := range temps {
temps[i] = 22
}
// Hot day window
for i := 11; i <= 15; i++ {
temps[i] = 32
}
// Poor night cooling (hour 0-6 and 21-23 above 20C)
for i := 0; i <= 6; i++ {
temps[i] = 22
}
for i := 21; i < 24; i++ {
temps[i] = 22
}
result := AnalyzeDay(makeHours(temps), DefaultThresholds())
if !result.PoorNightCool {
t.Error("expected PoorNightCool = true")
}
// Base Moderate + poor night = High
if result.Level != High {
t.Errorf("Level = %v, want High (elevated from Moderate)", result.Level)
}
}
func TestAnalyzeDay_GoodNightCooling(t *testing.T) {
temps := make([]float64, 24)
for i := range temps {
temps[i] = 18 // cool nights
}
for i := 11; i <= 15; i++ {
temps[i] = 32
}
result := AnalyzeDay(makeHours(temps), DefaultThresholds())
if result.PoorNightCool {
t.Error("expected PoorNightCool = false")
}
if result.Level != Moderate {
t.Errorf("Level = %v, want Moderate (no elevation)", result.Level)
}
}
func TestAnalyzeDay_Empty(t *testing.T) {
result := AnalyzeDay(nil, DefaultThresholds())
if result.Level != Low {
t.Errorf("Level = %v, want Low", result.Level)
}
}
func TestAnalyzeDay_MinNightTemp(t *testing.T) {
temps := make([]float64, 24)
for i := range temps {
temps[i] = 25
}
temps[3] = 16.5 // coldest night hour
result := AnalyzeDay(makeHours(temps), DefaultThresholds())
if result.MinNightTempC != 16.5 {
t.Errorf("MinNightTempC = %v, want 16.5", result.MinNightTempC)
}
}
func TestAnalyzeDay_ColdRiskDetected(t *testing.T) {
temps := make([]float64, 24)
for i := range temps {
temps[i] = 5
}
temps[3] = -2 // below ColdDayC (0)
result := AnalyzeDay(makeHours(temps), DefaultThresholds())
if result.MinTempC != -2 {
t.Errorf("MinTempC = %v, want -2", result.MinTempC)
}
if !result.ColdRisk {
t.Error("expected ColdRisk = true")
}
}
func TestAnalyzeDay_ColdRiskAbsent(t *testing.T) {
temps := make([]float64, 24)
for i := range temps {
temps[i] = 15
}
temps[5] = 5 // above ColdDayC (0)
result := AnalyzeDay(makeHours(temps), DefaultThresholds())
if result.MinTempC != 5 {
t.Errorf("MinTempC = %v, want 5", result.MinTempC)
}
if result.ColdRisk {
t.Error("expected ColdRisk = false")
}
}
func TestRiskLevelString(t *testing.T) {
tests := []struct {
level RiskLevel
want string
}{
{Low, "low"},
{Moderate, "moderate"},
{High, "high"},
{Extreme, "extreme"},
{RiskLevel(99), "unknown"},
}
for _, tt := range tests {
if got := tt.level.String(); got != tt.want {
t.Errorf("RiskLevel(%d).String() = %s, want %s", tt.level, got, tt.want)
}
}
}