diff --git a/.changeset/stable-energyplan-default.md b/.changeset/stable-energyplan-default.md new file mode 100644 index 00000000..3a30aa80 --- /dev/null +++ b/.changeset/stable-energyplan-default.md @@ -0,0 +1,5 @@ +--- +"ftw": patch +--- + +Use Energyplan by default on stable releases too, as betas already do. A beta box that moves to stable keeps the planner it has run, and a new stable install starts with Energyplan. Core DP still runs as shadow and fallback, and `planner.engine: core` still selects it. diff --git a/config.example.yaml b/config.example.yaml index e346fa3a..11b44b81 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -257,11 +257,11 @@ fleet_ping: # ftw-drivers-2026-01: MX+j27UBkyM099hTyJlmMLK9qlTTDUJsaK/vH12fFKc= # Refresh only updates the signed catalog. Install and activation stay explicit. -# Energyplan ships with Core and runs first in supported beta builds. +# Energyplan ships with Core and runs first in supported beta and stable builds. # Core DP provides shadow comparisons and validated fallback plans. # planner: # enabled: true -# # engine: energyplan # beta default; core selects the Go planner +# # engine: energyplan # release default; core selects the Go planner # mode: passive_arbitrage # forecast_trust: balanced # cautious | balanced | bold (first boot; live value is SQLite) # battery_export: unknown # unknown | not_allowed | allowed (unknown = no battery sale) diff --git a/docs/architecture.md b/docs/architecture.md index f0a681e4..8bfe7ff4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -222,14 +222,14 @@ remains explicit and atomic. See ## Optimizer -Beta releases use the bundled Energyplan worker when `planner.engine` is unset +Beta and stable releases use the bundled Energyplan worker when `planner.engine` is unset on a supported host. It solves Core's downside PV forecast. Core validates its plan before publishing it, then runs a bounded Core DP shadow on the same input. A worker error, timeout or rejected plan invokes Core DP fallback. Core validates fallback plans too; a failed validation leaves the prior plan in place. `planner.engine: core` or `energyplan` selects an engine explicitly. -Stable and development builds default to Core. Older `engine: python` values +Development builds and other hosts default to Core. Older `engine: python` values migrate to Energyplan; retired optimizer settings are ignored and omitted when the configuration is saved. Energyplan ships as compiled binaries with its own license; source and builds diff --git a/go/cmd/ftw/energyplan_test.go b/go/cmd/ftw/energyplan_test.go index 3fc90ebf..ddf70162 100644 --- a/go/cmd/ftw/energyplan_test.go +++ b/go/cmd/ftw/energyplan_test.go @@ -7,10 +7,11 @@ import ( "github.com/srcfl/ftw/go/internal/config" ) -func TestEnergyplanBetaSelection(t *testing.T) { +func TestEnergyplanReleaseSelection(t *testing.T) { for _, tc := range []struct{ version, engine, want string }{ {"v2.15.0-beta.1", "", "energyplan"}, - {"v2.15.0", "", "core"}, + {"v2.15.0", "", "energyplan"}, + {"v0.140.1-dev.83867213", "", "core"}, {"dev", "", "core"}, {"dev-beta.invalid", "", "core"}, {"v2.15.0-beta.1", "core", "core"}, @@ -64,7 +65,7 @@ func TestBuildMPCBatterylessEngineAdmission(t *testing.T) { want bool }{ {"explicit Core in beta", "v3.1.0-beta.1", "core", false}, - {"stable default", "v3.1.0", "", false}, + {"stable default", "v3.1.0", "", energyplanSupported(runtime.GOOS, runtime.GOARCH)}, {"development default", "dev", "", false}, {"invalid beta default", "dev-beta.invalid", "", false}, {"explicit Energyplan", "v3.1.0", "energyplan", true}, diff --git a/go/internal/api/api_mpc_unavailable_test.go b/go/internal/api/api_mpc_unavailable_test.go index defd21ad..567020e3 100644 --- a/go/internal/api/api_mpc_unavailable_test.go +++ b/go/internal/api/api_mpc_unavailable_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net/http" "net/http/httptest" + "runtime" "testing" "github.com/srcfl/ftw/go/internal/config" @@ -12,6 +13,12 @@ import ( func TestMPCDisabledEndpointsNameTheSkipReason(t *testing.T) { t.Parallel() + // Releases default to Energyplan where the worker ships, and Energyplan + // plans without a battery; elsewhere the default is Core DP. + stableDefault := mpc.ReasonNoBatteryCapacity + if config.SupportsBundledEnergyplan(runtime.GOOS, runtime.GOARCH) { + stableDefault = "" + } cases := []struct { name string deps *Deps @@ -47,7 +54,7 @@ func TestMPCDisabledEndpointsNameTheSkipReason(t *testing.T) { Planner: &config.Planner{Enabled: true}, Price: &config.Price{Provider: "elprisetjustnu"}, }}, - want: mpc.ReasonNoBatteryCapacity, + want: stableDefault, }, { name: "batteryless development default", diff --git a/go/internal/config/config.go b/go/internal/config/config.go index 026e130e..6eb5f2e5 100644 --- a/go/internal/config/config.go +++ b/go/internal/config/config.go @@ -764,7 +764,7 @@ const ( PlannerEngineEnergyplan = "energyplan" ) -// EngineName resolves an explicit engine. The launcher applies beta defaults +// EngineName resolves an explicit engine. The launcher applies release defaults // before calling this method; an unset value here keeps Core for other callers. func (p *Planner) EngineName() string { if p == nil { diff --git a/go/internal/config/planner_engine.go b/go/internal/config/planner_engine.go index cbb9110b..fbe37613 100644 --- a/go/internal/config/planner_engine.go +++ b/go/internal/config/planner_engine.go @@ -5,15 +5,17 @@ import ( "strings" ) -var plannerBetaVersion = regexp.MustCompile(`^v[0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+$`) +var plannerReleaseVersion = regexp.MustCompile(`^v[0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?$`) // EngineForBuild applies the same release default for planner admission and // API diagnostics. Explicit choices, including legacy aliases, take precedence. +// Beta and stable releases default to the bundled Energyplan worker on a +// supported host; development builds default to Core. func (p *Planner) EngineForBuild(version, goos, goarch string) string { if p != nil && strings.TrimSpace(p.Engine) != "" { return p.EngineName() } - if plannerBetaVersion.MatchString(version) && SupportsBundledEnergyplan(goos, goarch) { + if plannerReleaseVersion.MatchString(version) && SupportsBundledEnergyplan(goos, goarch) { return PlannerEngineEnergyplan } return PlannerEngineCore diff --git a/go/internal/config/planner_engine_test.go b/go/internal/config/planner_engine_test.go index 7b510e93..27cb963b 100644 --- a/go/internal/config/planner_engine_test.go +++ b/go/internal/config/planner_engine_test.go @@ -12,7 +12,11 @@ func TestPlannerEngineForBuild(t *testing.T) { {"v3.1.0-beta.1", "", "windows", "amd64", PlannerEngineCore}, {"v3.1.0-beta.1", "", "linux", "arm", PlannerEngineCore}, {"v3.1.0-beta.1", "core", "linux", "arm64", PlannerEngineCore}, - {"v3.1.0", "", "linux", "arm64", PlannerEngineCore}, + {"v3.1.0", "", "linux", "arm64", PlannerEngineEnergyplan}, + {"v0.140.1", "", "linux", "amd64", PlannerEngineEnergyplan}, + {"v0.140.1", "", "darwin", "arm64", PlannerEngineCore}, + {"v0.140.1", "core", "linux", "arm64", PlannerEngineCore}, + {"v0.140.1-dev.83867213", "", "linux", "arm64", PlannerEngineCore}, {"dev", "", "linux", "arm64", PlannerEngineCore}, {"dev-beta.invalid", "", "linux", "arm64", PlannerEngineCore}, {"v3.1.0-beta.", "", "linux", "arm64", PlannerEngineCore}, diff --git a/web/settings/tabs/planner.js b/web/settings/tabs/planner.js index e3fd11c8..daf961ef 100644 --- a/web/settings/tabs/planner.js +++ b/web/settings/tabs/planner.js @@ -102,7 +102,7 @@ var options = [["", "Automatic (release default)"], ["energyplan", "Energyplan"], ["core", "Core DP"]]; return '