Add server-side rendered setup UI accessible via `heatwave web`. The dashboard is now re-rendered per request and includes a nav bar linking to the new /setup page. Setup provides full CRUD for profiles, rooms, devices, occupants, AC units (with room assignment), scenario toggles, and forecast fetching — all via POST/redirect/GET forms. - Add ShowNav field to DashboardData for conditional nav bar - Extract fetchForecastForProfile() for reuse by web handler - Create setup.html.tmpl with Tailwind-styled entity sections - Create web_handlers.go with 15 route handlers and flash cookies - Switch web.go from pre-rendered to per-request dashboard rendering - Graceful dashboard fallback when no forecast data exists
94 lines
2.2 KiB
Go
94 lines
2.2 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 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")
|
|
}
|
|
}
|