package cli import ( "context" "fmt" "time" "github.com/cnachtigall/heatwave-autopilot/internal/heat" "github.com/cnachtigall/heatwave-autopilot/internal/llm" "github.com/cnachtigall/heatwave-autopilot/internal/risk" "github.com/spf13/cobra" ) var summaryDate string func init() { summaryCmd := &cobra.Command{ Use: "summary", Short: "Generate a 3-bullet AI summary of the heat model", RunE: func(cmd *cobra.Command, args []string) error { p, err := getActiveProfile() if err != nil { return err } provider := getLLMProvider() if provider.Name() == "none" { return fmt.Errorf("LLM not configured. Set llm.provider in config or use --llm flag") } dateStr := summaryDate if dateStr == "" { dateStr = time.Now().Format("2006-01-02") } date, err := time.Parse("2006-01-02", dateStr) if err != nil { return fmt.Errorf("invalid date: %s", dateStr) } loc, _ := time.LoadLocation(p.Timezone) from := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, loc) to := from.Add(24 * time.Hour) forecasts, err := db.GetForecasts(p.ID, from, to, "") if err != nil || len(forecasts) == 0 { return fmt.Errorf("no forecast data for %s", dateStr) } hourlyData := buildHourlyData(forecasts, loc) dayRisk := risk.AnalyzeDay(hourlyData, risk.DefaultThresholds()) // Build heat sources from devices devices, _ := db.ListAllDevices(p.ID) var sources []llm.HeatSource for _, d := range devices { sources = append(sources, llm.HeatSource{ Name: d.Name, Watts: d.WattsTypical * d.DutyCycle, }) } // AC headroom (simplified — sum all AC units) acUnits, _ := db.ListACUnits(p.ID) var totalACBTU float64 for _, ac := range acUnits { totalACBTU += ac.CapacityBTU } var totalGainW float64 for _, s := range sources { totalGainW += s.Watts } headroom := totalACBTU - heat.WattsToBTUH(totalGainW) // Warnings warnings, _ := db.GetActiveWarnings(p.ID, time.Now()) var warningStrs []string for _, w := range warnings { warningStrs = append(warningStrs, w.Headline) } // Risk windows var riskWindows []llm.RiskWindowSummary for _, w := range dayRisk.Windows { riskWindows = append(riskWindows, llm.RiskWindowSummary{ StartHour: w.StartHour, EndHour: w.EndHour, PeakTempC: w.PeakTempC, Level: w.Level.String(), }) } input := llm.SummaryInput{ Date: dateStr, PeakTempC: dayRisk.PeakTempC, MinNightTempC: dayRisk.MinNightTempC, RiskLevel: dayRisk.Level.String(), TopHeatSources: sources, ACHeadroomBTUH: headroom, BudgetStatus: heat.Comfortable.String(), ActiveWarnings: warningStrs, RiskWindows: riskWindows, } result, err := provider.Summarize(context.Background(), input) if err != nil { fmt.Fprintf(cmd.ErrOrStderr(), "LLM call failed: %v\nFalling back to raw data:\n", err) fmt.Printf("Peak: %.1f°C | Night min: %.1f°C | Risk: %s\n", dayRisk.PeakTempC, dayRisk.MinNightTempC, dayRisk.Level) return nil } fmt.Println(result) return nil }, } summaryCmd.Flags().StringVar(&summaryDate, "date", "", "date (YYYY-MM-DD)") rootCmd.AddCommand(summaryCmd) }