From 494f6cf9bb1d8e11c9fdfaac2019c124867b83cd Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 16 Jun 2026 12:27:30 +0200 Subject: [PATCH] Optimizer: make grid charging strategy configurable (#30782) --- AGENTS.md | 1 + assets/js/components/MaterialIcon/Lock.vue | 18 + .../Optimize/OptimizeHeader.stories.ts | 40 ++ .../js/components/Optimize/OptimizeHeader.vue | 341 ++++++++++++++++++ assets/js/types/evcc.ts | 3 + assets/js/views/Optimize.vue | 87 ++--- core/keys/site.go | 4 + core/site.go | 10 + core/site/api.go | 5 + core/site_api.go | 39 ++ core/site_optimizer.go | 35 +- core/site_optimizer_test.go | 15 + server/http.go | 2 + server/http_site_handler.go | 5 + 14 files changed, 555 insertions(+), 50 deletions(-) create mode 100644 assets/js/components/MaterialIcon/Lock.vue create mode 100644 assets/js/components/Optimize/OptimizeHeader.stories.ts create mode 100644 assets/js/components/Optimize/OptimizeHeader.vue diff --git a/AGENTS.md b/AGENTS.md index d486e1933..baae681a5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -176,6 +176,7 @@ Deep documentation on specific subsystems is available in `docs/agents/`. Load w ### State Management +- Never access the store from sub-components; keep them stateless and pass the values they need as props (emit events back to the parent). Only top-level views read from the store. This keeps components reusable and testable (e.g. Storybook should never mock the store). - Use `reactive()` from Vue for simple global state - Implement property setters for nested object updates using helper functions - Use localStorage with reactive wrappers for persistent settings diff --git a/assets/js/components/MaterialIcon/Lock.vue b/assets/js/components/MaterialIcon/Lock.vue new file mode 100644 index 000000000..1e7f313bc --- /dev/null +++ b/assets/js/components/MaterialIcon/Lock.vue @@ -0,0 +1,18 @@ + + + diff --git a/assets/js/components/Optimize/OptimizeHeader.stories.ts b/assets/js/components/Optimize/OptimizeHeader.stories.ts new file mode 100644 index 000000000..9399d08c5 --- /dev/null +++ b/assets/js/components/Optimize/OptimizeHeader.stories.ts @@ -0,0 +1,40 @@ +import OptimizeHeader from "./OptimizeHeader.vue"; +import { CURRENCY, OptimizationStatus } from "@/types/evcc"; +import type { Meta, StoryFn } from "@storybook/vue3"; + +export default { + title: "Optimize/OptimizeHeader", + component: OptimizeHeader, + parameters: { + layout: "padded", + }, +} as Meta; + +const base = { + updated: new Date(Date.now() - 2 * 60 * 1000).toISOString(), + horizonHours: 47, + currency: CURRENCY.EUR, + chargingStrategies: ["charge_before_export", "attenuate_grid_peaks", "none"], + selectedStrategy: "charge_before_export", + pending: false, +}; + +const Template: StoryFn = (args) => ({ + components: { OptimizeHeader }, + setup() { + return { args }; + }, + template: '
', +}); + +// you pay the grid (normal case): neutral color +export const Payment = Template.bind({}); +Payment.args = { ...base, status: OptimizationStatus.OPTIMAL, netCost: 12.4 }; + +// solar surplus, you receive a credit: green, signed +export const Credit = Template.bind({}); +Credit.args = { ...base, status: OptimizationStatus.OPTIMAL, netCost: -3.8 }; + +// solver could not produce a plan +export const Infeasible = Template.bind({}); +Infeasible.args = { ...base, status: OptimizationStatus.INFEASIBLE, netCost: 0 }; diff --git a/assets/js/components/Optimize/OptimizeHeader.vue b/assets/js/components/Optimize/OptimizeHeader.vue new file mode 100644 index 000000000..96994eda7 --- /dev/null +++ b/assets/js/components/Optimize/OptimizeHeader.vue @@ -0,0 +1,341 @@ + + + + + diff --git a/assets/js/types/evcc.ts b/assets/js/types/evcc.ts index dfcc4b073..50a340998 100644 --- a/assets/js/types/evcc.ts +++ b/assets/js/types/evcc.ts @@ -128,6 +128,8 @@ export interface State { ocpp?: Ocpp; ocppforwarder?: ConfigStatus; optimizer?: boolean; + optimizerChargingStrategy?: string; + optimizerChargingStrategies?: string[]; mcp?: boolean; } @@ -729,6 +731,7 @@ export type ValueOf = T[keyof T]; // EvOpt interfaces matching OpenAPI spec exactly export interface EvOpt { + updated: string; req: OptimizationInput; res: OptimizationResult; details: OptimizationDetails; diff --git a/assets/js/views/Optimize.vue b/assets/js/views/Optimize.vue index dfcee23c5..671769eb5 100644 --- a/assets/js/views/Optimize.vue +++ b/assets/js/views/Optimize.vue @@ -1,42 +1,25 @@