- 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.
204 lines
5.3 KiB
Go
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")
|
|
}
|
|
}
|