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
99 lines
3.8 KiB
Go
99 lines
3.8 KiB
Go
package llm
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
)
|
||
|
||
const summarizeSystemPrompt = `You are a heat preparedness assistant. You receive computed heat model data for a specific day.
|
||
Generate exactly 3 concise bullet points summarizing the key drivers and risks.
|
||
Rules:
|
||
- Reference ONLY the data provided below. Do not invent or assume additional information.
|
||
- Use preparedness language (comfort, planning). Never give medical advice or diagnoses.
|
||
- Each bullet: max 20 words, plain language, actionable insight.
|
||
- Format: "- [bullet text]" (markdown list)`
|
||
|
||
const rewriteActionSystemPrompt = `You are a heat preparedness assistant. Rewrite the given technical action into a clear, friendly, plain-language instruction.
|
||
Rules:
|
||
- One sentence, max 25 words.
|
||
- Reference the provided temperature and time context.
|
||
- Use preparedness language. Never give medical advice.
|
||
- Return only the rewritten sentence, nothing else.`
|
||
|
||
const heatPlanSystemPrompt = `You are a heat preparedness assistant. Generate a 1-page plain-language heat plan document.
|
||
Rules:
|
||
- Reference ONLY the data provided below. Do not invent information.
|
||
- Use preparedness language (comfort, planning). Never give medical advice or diagnoses.
|
||
- Structure: Brief overview (2-3 sentences), then hour-by-hour key actions, then care reminders.
|
||
- Use simple language anyone can understand.
|
||
- Keep total length under 500 words.
|
||
- Format as markdown with headers.`
|
||
|
||
// BuildSummaryPrompt constructs the user message for Summarize.
|
||
func BuildSummaryPrompt(input SummaryInput) string {
|
||
var b strings.Builder
|
||
fmt.Fprintf(&b, "Date: %s\n", input.Date)
|
||
fmt.Fprintf(&b, "Peak temperature: %.1f°C\n", input.PeakTempC)
|
||
fmt.Fprintf(&b, "Minimum night temperature: %.1f°C\n", input.MinNightTempC)
|
||
fmt.Fprintf(&b, "Overall risk level: %s\n", input.RiskLevel)
|
||
fmt.Fprintf(&b, "AC headroom: %.0f BTU/h\n", input.ACHeadroomBTUH)
|
||
fmt.Fprintf(&b, "Budget status: %s\n", input.BudgetStatus)
|
||
|
||
if len(input.TopHeatSources) > 0 {
|
||
b.WriteString("Top heat sources:\n")
|
||
for _, s := range input.TopHeatSources {
|
||
fmt.Fprintf(&b, " - %s: %.0fW\n", s.Name, s.Watts)
|
||
}
|
||
}
|
||
if len(input.ActiveWarnings) > 0 {
|
||
b.WriteString("Active warnings:\n")
|
||
for _, w := range input.ActiveWarnings {
|
||
fmt.Fprintf(&b, " - %s\n", w)
|
||
}
|
||
}
|
||
if len(input.RiskWindows) > 0 {
|
||
b.WriteString("Risk windows:\n")
|
||
for _, rw := range input.RiskWindows {
|
||
fmt.Fprintf(&b, " - %02d:00–%02d:00, peak %.1f°C, level: %s\n", rw.StartHour, rw.EndHour, rw.PeakTempC, rw.Level)
|
||
}
|
||
}
|
||
return b.String()
|
||
}
|
||
|
||
// BuildRewriteActionPrompt constructs the user message for RewriteAction.
|
||
func BuildRewriteActionPrompt(input ActionInput) string {
|
||
return fmt.Sprintf("Action: %s\nDescription: %s\nCurrent temperature: %.1f°C\nHour: %02d:00",
|
||
input.ActionName, input.Description, input.TempC, input.Hour)
|
||
}
|
||
|
||
// BuildHeatPlanPrompt constructs the user message for GenerateHeatPlan.
|
||
func BuildHeatPlanPrompt(input HeatPlanInput) string {
|
||
var b strings.Builder
|
||
b.WriteString(BuildSummaryPrompt(input.Summary))
|
||
b.WriteString("\nTimeline:\n")
|
||
for _, s := range input.Timeline {
|
||
actions := "none"
|
||
if len(s.Actions) > 0 {
|
||
actions = strings.Join(s.Actions, ", ")
|
||
}
|
||
fmt.Fprintf(&b, " %02d:00 | %.1f°C | risk: %s | budget: %s | actions: %s\n",
|
||
s.Hour, s.TempC, s.RiskLevel, s.BudgetStatus, actions)
|
||
}
|
||
if len(input.CareChecklist) > 0 {
|
||
b.WriteString("\nCare checklist:\n")
|
||
for _, c := range input.CareChecklist {
|
||
fmt.Fprintf(&b, " - %s\n", c)
|
||
}
|
||
}
|
||
return b.String()
|
||
}
|
||
|
||
// SummarizeSystemPrompt returns the system prompt for Summarize.
|
||
func SummarizeSystemPrompt() string { return summarizeSystemPrompt }
|
||
|
||
// RewriteActionSystemPrompt returns the system prompt for RewriteAction.
|
||
func RewriteActionSystemPrompt() string { return rewriteActionSystemPrompt }
|
||
|
||
// HeatPlanSystemPrompt returns the system prompt for GenerateHeatPlan.
|
||
func HeatPlanSystemPrompt() string { return heatPlanSystemPrompt }
|