Files
HeatGuard/internal/llm/prompt.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

168 lines
7.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
- If peak temperature is below 22°C and risk level is "low", state simply that no heat risk is expected today. Focus on comfort rather than warnings.
- If budget status is "marginal" but peak temperature is below 25°C, this indicates minor internal heat gains easily managed by opening windows — not a cooling concern.
- 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 generateActionsSystemPrompt = `You are a heat preparedness assistant. Generate context-aware cooling actions for a specific day.
Rules:
- Return ONLY a JSON array of action objects. No markdown, no explanation, no wrapping text.
- Each object schema: {"name": string, "description": string, "category": string, "effort": string, "impact": string, "hours": [int]}
- Valid categories: shading, ventilation, internal_gains, ac_strategy, hydration, care
- Valid effort values: none, low, medium, high
- Valid impact values: low, medium, high
- Be specific: reference temperatures, time windows, indoor/outdoor differentials, solar gain timing, and room orientations.
- Use the coolMode data to recommend ventilation vs AC per time window.
- Valid coolMode values: "comfort" (outdoor well below indoor — no active cooling needed, building envelope handles it), "ventilate" (open windows to cool), "sealed" (too humid to ventilate, keep closed), "ac" (use AC), "overloaded" (AC cannot keep up with heat gains).
- Do NOT recommend opening windows or ventilation during "comfort" hours — the room is already comfortable. Only generate actions for hours where active intervention is needed.
- On transitional days where morning/evening hours show "comfort" but midday shows "ventilate" or "ac", focus actions on the warmer hours only.
- If peak outdoor temperature is below 22°C and risk level is "low", return an empty array []. No cooling actions are needed on mild or cold days.
- Generate 5-12 actions covering the most impactful strategies for the day.
- Use preparedness language. Never give medical advice or diagnoses.`
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
if input.Language != "" {
fmt.Fprintf(&b, "Respond in: %s\n\n", input.Language)
}
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()
}
// BuildActionsPrompt constructs the user message for GenerateActions.
func BuildActionsPrompt(input ActionsInput) string {
var b strings.Builder
if input.Language != "" {
fmt.Fprintf(&b, "Respond in: %s\n\n", input.Language)
}
fmt.Fprintf(&b, "Date: %s\n", input.Date)
fmt.Fprintf(&b, "Indoor target: %.1f°C\n", input.IndoorTempC)
fmt.Fprintf(&b, "Peak outdoor temp: %.1f°C\n", input.PeakTempC)
fmt.Fprintf(&b, "Min night temp: %.1f°C\n", input.MinNightTempC)
fmt.Fprintf(&b, "Poor night cooling: %v\n", input.PoorNightCool)
fmt.Fprintf(&b, "Risk level: %s\n", input.RiskLevel)
if len(input.RiskWindows) > 0 {
b.WriteString("\nRisk 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)
}
}
if len(input.Timeline) > 0 {
b.WriteString("\nHourly data:\n")
b.WriteString(" HH:00 | outdoor°C | Δ indoor | humidity% | budget | coolMode | gainsW\n")
for _, s := range input.Timeline {
delta := s.TempC - input.IndoorTempC
fmt.Fprintf(&b, " %02d:00 | %5.1f°C | %+5.1f°C | %5.1f%% | %-10s| %-10s | %.0fW\n",
s.Hour, s.TempC, delta, s.HumidityPct, s.BudgetStatus, s.CoolMode, s.GainsW)
}
}
if len(input.Rooms) > 0 {
b.WriteString("\nRooms:\n")
for _, r := range input.Rooms {
acStr := "no"
if r.HasAC {
acStr = "yes"
}
fmt.Fprintf(&b, " - %s (orientation: %s, shading: %s, AC: %s)\n",
r.Name, r.Orientation, r.ShadingType, acStr)
}
}
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 }
// GenerateActionsSystemPrompt returns the system prompt for GenerateActions.
func GenerateActionsSystemPrompt() string { return generateActionsSystemPrompt }