Files
HeatGuard/internal/llm/prompt_test.go
vikingowl 84d645ff21 feat: fix cold-weather thermal logic, add comfort mode, and dashboard forecast refresh
- Fix ComputeRoomBudget: no-AC rooms check if open-window ventilation
  can offset gains instead of defaulting to Overloaded. Net-cooling
  rooms are now Comfortable; ventilation-solvable rooms are Marginal.
- Add "comfort" cool mode for hours where outdoor is >5°C below indoor
  and budget is not overloaded (winter/cold scenarios).
- Reorder determineCoolMode: sealed now before overloaded, fixing
  humid+cold+no-AC giving "overloaded" instead of "sealed".
- Update LLM prompts: document comfort coolMode, add cold-weather
  guidance for summary and actions generation.
- Add dashboard forecast refresh button: fetches fresh forecast +
  warnings, then re-runs compute and LLM pipelines.
- Extract forecast fetch into shared fetchForecastForProfile() in db.js,
  deduplicating logic between setup.js and dashboard.js.
- Add indoor humidity support, pressure display, and cool mode sealed
  integration test.
2026-02-10 04:26:53 +01:00

204 lines
5.3 KiB
Go

package llm
import (
"strings"
"testing"
)
func testSummaryInput() SummaryInput {
return SummaryInput{
Date: "2025-07-15",
PeakTempC: 37.2,
MinNightTempC: 22.5,
RiskLevel: "high",
TopHeatSources: []HeatSource{{Name: "Gaming PC", Watts: 200}, {Name: "Monitor", Watts: 80}},
ACHeadroomBTUH: 4500,
BudgetStatus: "marginal",
ActiveWarnings: []string{"DWD: Amtliche WARNUNG vor HITZE"},
RiskWindows: []RiskWindowSummary{{StartHour: 11, EndHour: 18, PeakTempC: 37.2, Level: "high"}},
}
}
func TestBuildSummaryPrompt_ContainsAllFields(t *testing.T) {
p := BuildSummaryPrompt(testSummaryInput())
checks := []string{
"2025-07-15",
"37.2",
"22.5",
"high",
"Gaming PC",
"200W",
"4500 BTU/h",
"marginal",
"WARNUNG",
"11:00",
"18:00",
}
for _, c := range checks {
if !strings.Contains(p, c) {
t.Errorf("prompt missing %q", c)
}
}
}
func TestBuildRewriteActionPrompt(t *testing.T) {
p := BuildRewriteActionPrompt(ActionInput{
ActionName: "Close south-facing shutters",
Description: "Block direct sun",
TempC: 34.5,
Hour: 9,
})
if !strings.Contains(p, "Close south-facing shutters") {
t.Error("missing action name")
}
if !strings.Contains(p, "34.5") {
t.Error("missing temperature")
}
if !strings.Contains(p, "09:00") {
t.Error("missing hour")
}
}
func TestBuildHeatPlanPrompt(t *testing.T) {
input := HeatPlanInput{
Summary: testSummaryInput(),
Timeline: []TimelineSlotSummary{
{Hour: 12, TempC: 35, RiskLevel: "high", BudgetStatus: "marginal", Actions: []string{"Hydration"}},
},
CareChecklist: []string{"Check elderly occupants at 14:00"},
}
p := BuildHeatPlanPrompt(input)
if !strings.Contains(p, "Timeline:") {
t.Error("missing timeline section")
}
if !strings.Contains(p, "Hydration") {
t.Error("missing actions")
}
if !strings.Contains(p, "Care checklist:") {
t.Error("missing care checklist")
}
}
func testActionsInput() ActionsInput {
return ActionsInput{
Date: "2025-07-15",
Language: "English",
IndoorTempC: 25.0,
PeakTempC: 37.2,
MinNightTempC: 22.5,
PoorNightCool: true,
RiskLevel: "high",
RiskWindows: []RiskWindowSummary{{StartHour: 11, EndHour: 18, PeakTempC: 37.2, Level: "high"}},
Timeline: []ActionsTimelineSlot{
{Hour: 6, TempC: 20, HumidityPct: 60, BudgetStatus: "comfortable", CoolMode: "ventilate", GainsW: 150},
{Hour: 14, TempC: 37, HumidityPct: 35, BudgetStatus: "overloaded", CoolMode: "overloaded", GainsW: 800},
},
Rooms: []ActionsRoom{
{Name: "Office", Orientation: "S", ShadingType: "shutters", HasAC: true},
{Name: "Bedroom", Orientation: "N", ShadingType: "blinds", HasAC: false},
},
}
}
func TestBuildActionsPrompt_ContainsAllFields(t *testing.T) {
p := BuildActionsPrompt(testActionsInput())
checks := []string{
"2025-07-15",
"English",
"25.0",
"37.2",
"22.5",
"true",
"high",
"11:00",
"18:00",
"Office",
"Bedroom",
"shutters",
"ventilate",
"overloaded",
"800W",
}
for _, c := range checks {
if !strings.Contains(p, c) {
t.Errorf("prompt missing %q", c)
}
}
}
func TestGenerateActionsSystemPrompt_NotEmpty(t *testing.T) {
if GenerateActionsSystemPrompt() == "" {
t.Error("empty generate actions system prompt")
}
if !strings.Contains(GenerateActionsSystemPrompt(), "JSON array") {
t.Error("system prompt should mention JSON array format")
}
}
func TestGenerateActionsSystemPrompt_ContainsLowRiskGuidance(t *testing.T) {
p := GenerateActionsSystemPrompt()
if !strings.Contains(p, "below 22°C") {
t.Error("system prompt should mention below 22°C threshold")
}
if !strings.Contains(p, "empty array") {
t.Error("system prompt should instruct returning empty array for low risk")
}
}
func TestGenerateActionsSystemPrompt_ContainsComfortMode(t *testing.T) {
p := GenerateActionsSystemPrompt()
if !strings.Contains(p, `"comfort"`) {
t.Error("system prompt should document comfort coolMode")
}
if !strings.Contains(p, "no active cooling needed") {
t.Error("system prompt should explain comfort mode meaning")
}
}
func TestSummarizeSystemPrompt_ContainsMarginalColdGuidance(t *testing.T) {
p := SummarizeSystemPrompt()
if !strings.Contains(p, "marginal") {
t.Error("system prompt should mention marginal status in cold weather")
}
}
func TestBuildActionsPrompt_ComfortMode(t *testing.T) {
input := ActionsInput{
Date: "2025-02-10",
IndoorTempC: 23.0,
PeakTempC: 6.0,
RiskLevel: "low",
Timeline: []ActionsTimelineSlot{
{Hour: 8, TempC: -2, HumidityPct: 50, BudgetStatus: "marginal", CoolMode: "comfort", GainsW: 300},
},
}
p := BuildActionsPrompt(input)
if !strings.Contains(p, "comfort") {
t.Error("actions prompt should include comfort coolMode from timeline")
}
}
func TestSummarizeSystemPrompt_ContainsLowRiskGuidance(t *testing.T) {
p := SummarizeSystemPrompt()
if !strings.Contains(p, "below 22°C") {
t.Error("system prompt should mention below 22°C threshold")
}
if !strings.Contains(p, "no heat risk") {
t.Error("system prompt should mention no heat risk for low temps")
}
}
func TestSystemPrompts_NotEmpty(t *testing.T) {
if SummarizeSystemPrompt() == "" {
t.Error("empty summarize system prompt")
}
if RewriteActionSystemPrompt() == "" {
t.Error("empty rewrite system prompt")
}
if HeatPlanSystemPrompt() == "" {
t.Error("empty heatplan system prompt")
}
}