-
Notifications
You must be signed in to change notification settings - Fork 10
feat(planner): persist forecast trust and battery export permission #968
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "ftw": minor | ||
| --- | ||
|
|
||
| The planner now has a household preference object on the Plan card: follow-the-forecast (cautious / balanced / bold) and a battery-export permission (unknown / not allowed / allowed). Balanced is today's default. Unknown export does not sell from the battery. Sites that were on Active arbitrage must confirm before selling again. Settings keep house reserve on top and bury engine knobs; weather no longer asks for array orientation on the normal path. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| package api | ||
|
|
||
| import ( | ||
| "context" | ||
| "net/http" | ||
|
|
||
| "github.com/srcfl/ftw/go/internal/config" | ||
| "github.com/srcfl/ftw/go/internal/control" | ||
| ) | ||
|
|
||
| func (s *Server) plannerPrefsSnapshot() (trust config.ForecastTrust, export config.BatteryExport, yamlCustom bool, mappedK float64, mappedMode string) { | ||
| trust, export = s.deps.PlannerPrefs.Get() | ||
| var planner *config.Planner | ||
| if s.deps.Cfg != nil { | ||
| s.deps.CfgMu.RLock() | ||
| planner = s.deps.Cfg.Planner | ||
| s.deps.CfgMu.RUnlock() | ||
| } | ||
| yamlCustom = planner.YAMLCustomK() | ||
| mappedK = planner.EffectiveSafetyK(trust) | ||
| mappedMode = export.PlannerModeKey() | ||
| return | ||
| } | ||
|
|
||
| func (s *Server) handleGetPlannerPrefs(w http.ResponseWriter, r *http.Request) { | ||
| trust, export, yamlCustom, mappedK, mappedMode := s.plannerPrefsSnapshot() | ||
| writeJSON(w, 200, map[string]any{ | ||
| "forecast_trust": trust, | ||
| "battery_export": export, | ||
| "yaml_custom": yamlCustom, | ||
| "mapped_k": mappedK, | ||
| "mapped_mode": mappedMode, | ||
| }) | ||
| } | ||
|
|
||
| func (s *Server) handleSetPlannerPrefs(w http.ResponseWriter, r *http.Request) { | ||
| var req struct { | ||
| ForecastTrust string `json:"forecast_trust"` | ||
| BatteryExport string `json:"battery_export"` | ||
| } | ||
| if err := readJSON(r, &req); err != nil { | ||
| writeJSON(w, 400, map[string]string{"error": err.Error()}) | ||
| return | ||
| } | ||
| trust, ok := config.ParseForecastTrust(req.ForecastTrust) | ||
| if !ok || req.ForecastTrust == "" { | ||
| writeJSON(w, 400, map[string]string{"error": "forecast_trust must be cautious, balanced, or bold"}) | ||
| return | ||
| } | ||
| export, ok := config.ParseBatteryExport(req.BatteryExport) | ||
| if !ok { | ||
| writeJSON(w, 400, map[string]string{"error": "battery_export must be unknown, not_allowed, or allowed"}) | ||
| return | ||
| } | ||
| if err := s.applyPlannerPrefs(r.Context(), trust, export); err != nil { | ||
| writeJSON(w, 400, map[string]string{"error": err.Error()}) | ||
| return | ||
| } | ||
| _, _, yamlCustom, mappedK, mappedMode := s.plannerPrefsSnapshot() | ||
| writeJSON(w, 200, map[string]any{ | ||
| "status": "ok", | ||
| "forecast_trust": trust, | ||
| "battery_export": export, | ||
| "yaml_custom": yamlCustom, | ||
| "mapped_k": mappedK, | ||
| "mapped_mode": mappedMode, | ||
| }) | ||
| } | ||
|
|
||
| func (s *Server) applyPlannerPrefs(ctx context.Context, trust config.ForecastTrust, export config.BatteryExport) error { | ||
| if s.deps.PlannerPrefs == nil { | ||
| s.deps.PlannerPrefs = config.NewPlannerPrefs(trust, export) | ||
| } else { | ||
| s.deps.PlannerPrefs.Set(trust, export) | ||
| } | ||
| if s.deps.State != nil { | ||
| if err := s.deps.State.SaveConfig(config.StateKeyForecastTrust, string(trust)); err != nil { | ||
| return err | ||
| } | ||
| if err := s.deps.State.SaveConfig(config.StateKeyBatteryExport, string(export)); err != nil { | ||
| return err | ||
| } | ||
| } | ||
| mapped := control.Mode(export.PlannerModeKey()) | ||
| if s.deps.Ctrl != nil && s.deps.CtrlMu != nil { | ||
| s.deps.CtrlMu.Lock() | ||
| inPlanner := s.deps.Ctrl.Mode.IsPlannerMode() | ||
| s.deps.CtrlMu.Unlock() | ||
| if inPlanner { | ||
| s.deps.CtrlMu.Lock() | ||
| err := s.deps.Ctrl.ApplyMode(mapped) | ||
| s.deps.CtrlMu.Unlock() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if s.deps.State != nil { | ||
| _ = s.deps.State.SaveConfig("mode", string(mapped)) | ||
| } | ||
| if mm, ok := control.PlannerMPCMode(mapped); ok && s.deps.MPC != nil { | ||
| s.deps.MPC.SetMode(ctx, mm) | ||
| } | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefs remap clobbers planner modesHigh Severity
Reviewed by Cursor Bugbot for commit 53e8777. Configure here. |
||
| } | ||
| if s.deps.MPC != nil { | ||
| var planner *config.Planner | ||
| if s.deps.Cfg != nil { | ||
| s.deps.CfgMu.RLock() | ||
| planner = s.deps.Cfg.Planner | ||
| s.deps.CfgMu.RUnlock() | ||
| } | ||
| s.deps.MPC.SetSafetyK(ctx, planner.EffectiveSafetyK(trust)) | ||
| } | ||
| return nil | ||
| } | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When changing an actively arbitraging site to
unknownornot_allowed, any SQLite write failure (for example, a full disk or closed database) returns here before the control mode is changed at line 91. The in-memory preference has already been updated, so/api/statusreports that export is forbidden whileplanner_arbitragecan continue selling from the battery; apply the fail-safe mode transition before this fallible persistence path or roll back the preference and dispatch state together.Useful? React with 👍 / 👎.