Files
HeatGuard/internal/report/sample_test.go
vikingowl 1c9db02334 feat: add web UI with full CRUD setup page
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
2026-02-09 10:39:00 +01:00

103 lines
3.1 KiB
Go

package report
import (
"fmt"
"os"
"testing"
"time"
)
func TestGenerateSampleReport(t *testing.T) {
if os.Getenv("WRITE_SAMPLE") == "" {
t.Skip("set WRITE_SAMPLE=1 to generate sample report")
}
temps := []float64{22, 21, 20.5, 20, 19.5, 19.5, 20, 22, 24, 27, 30, 32, 34, 35.5, 37.2, 36.8, 35, 33, 31, 28, 26, 24.5, 23.5, 22.5}
data := DashboardData{
GeneratedAt: time.Now(),
ProfileName: "home — Berlin",
Date: "2025-07-15",
RiskLevel: "high",
PeakTempC: 37.2,
MinNightTempC: 19.5,
PoorNightCool: true,
Warnings: []WarningData{
{
Headline: "Amtliche WARNUNG vor HITZE",
Severity: "Severe",
Description: "Es tritt eine starke Wärmebelastung auf. Temperaturen bis 37°C erwartet.",
Instruction: "Trinken Sie ausreichend. Vermeiden Sie direkte Sonneneinstrahlung.",
Onset: "2025-07-15 11:00",
Expires: "2025-07-16 19:00",
},
},
RiskWindows: []RiskWindowData{
{StartHour: 10, EndHour: 18, PeakTempC: 37.2, Level: "high", Reason: "very hot daytime temperatures"},
},
RoomBudgets: []RoomBudgetData{
{
RoomName: "Office", InternalGainsW: 300, SolarGainW: 622, VentGainW: 150,
TotalGainW: 1072, TotalGainBTUH: 3658, ACCapacityBTUH: 8000, HeadroomBTUH: 4342, Status: "comfortable",
},
{
RoomName: "Bedroom", InternalGainsW: 100, SolarGainW: 200, VentGainW: 80,
TotalGainW: 380, TotalGainBTUH: 1297, ACCapacityBTUH: 0, HeadroomBTUH: -1297, Status: "overloaded",
},
},
CareChecklist: []string{
"Check elderly occupant (bedroom) at 10:00, 14:00, 18:00",
"Ensure water bottles are filled and accessible",
"Verify medication storage temperature",
},
}
for i, temp := range temps {
slot := TimelineSlotData{
Hour: i,
HourStr: fmt.Sprintf("%02d:00", i),
TempC: temp,
}
switch {
case temp >= 35:
slot.RiskLevel = "high"
slot.BudgetStatus = "marginal"
case temp >= 30:
slot.RiskLevel = "moderate"
slot.BudgetStatus = "comfortable"
default:
slot.RiskLevel = "low"
slot.BudgetStatus = "comfortable"
}
isDay := i >= 6 && i < 21
if i >= 11 && i <= 18 && temp >= 30 {
slot.Actions = append(slot.Actions, ActionData{Name: "Hydration reminder", Category: "hydration", Impact: "medium"})
}
if i >= 10 && i <= 18 && temp >= 30 {
slot.Actions = append(slot.Actions, ActionData{Name: "Check vulnerable occupants", Category: "care", Impact: "high"})
}
if i <= 10 && temp >= 25 {
slot.Actions = append(slot.Actions, ActionData{Name: "Close south-facing shutters", Category: "shading", Impact: "high"})
}
if i >= 6 && i <= 9 {
slot.Actions = append(slot.Actions, ActionData{Name: "Pre-cool rooms with AC", Category: "ac_strategy", Impact: "high"})
}
if !isDay && temp < 25 {
slot.Actions = append(slot.Actions, ActionData{Name: "Night ventilation", Category: "ventilation", Impact: "high"})
}
data.Timeline = append(data.Timeline, slot)
}
f, err := os.Create("/tmp/heatwave-sample-report.html")
if err != nil {
t.Fatal(err)
}
defer f.Close()
if err := Generate(f, data); err != nil {
t.Fatalf("Generate: %v", err)
}
t.Log("Sample report written to /tmp/heatwave-sample-report.html")
}