diff --git a/core/metrics/db.go b/core/metrics/db.go index e6b8db208..fd6a2a130 100644 --- a/core/metrics/db.go +++ b/core/metrics/db.go @@ -48,6 +48,11 @@ func SetupSchema() error { return err } + // grid: migrate old entities (title was the device ref) to the fixed title + if err := db.Instance.Model(new(entity)).Where(`"group" = ? AND title <> ?`, Grid, Grid).Update("title", Grid).Error; err != nil { + return err + } + // enable FK constraints only here to make sure entity for metric exists if err := db.Instance.Exec("pragma foreign_keys(1)").Error; err != nil { return err diff --git a/core/site.go b/core/site.go index ee7c9398f..415f97404 100644 --- a/core/site.go +++ b/core/site.go @@ -198,7 +198,7 @@ func (site *Site) Boot(log *util.Logger, loadpoints []*Loadpoint, tariffs *tarif return errors.New("missing grid meter instance") } - me, err := metrics.NewCollector(metrics.Grid, site.Meters.GridMeterRef, deviceTitleOrName(dev)) + me, err := metrics.NewCollector(metrics.Grid, site.Meters.GridMeterRef, metrics.Grid) if err != nil { return err } diff --git a/tests/energy-history.spec.ts b/tests/energy-history.spec.ts index 07fe356ee..1ca3e6468 100644 --- a/tests/energy-history.spec.ts +++ b/tests/energy-history.spec.ts @@ -23,7 +23,7 @@ test.describe("api", () => { const data = await res.json(); expect(data).toHaveLength(2); - const grid = data.find((s: { title: string }) => s.title === "Grid"); + const grid = data.find((s: { title: string }) => s.title === "grid"); const home = data.find((s: { title: string }) => s.title === "home"); expect(grid).toBeDefined(); expect(home).toBeDefined(); @@ -46,7 +46,7 @@ test.describe("api", () => { const data = await res.json(); expect(data).toHaveLength(2); - const grid = data.find((s: { title: string }) => s.title === "Grid"); + const grid = data.find((s: { title: string }) => s.title === "grid"); const home = data.find((s: { title: string }) => s.title === "home"); expect(grid).toBeDefined(); expect(home).toBeDefined(); diff --git a/tests/energy-history.sql b/tests/energy-history.sql index 461a169c8..d21972f42 100644 --- a/tests/energy-history.sql +++ b/tests/energy-history.sql @@ -18,7 +18,7 @@ CREATE UNIQUE INDEX `meters_meter_ts` ON `meters`(`meter`, `ts`); -- entities (name = stable id, title = display label) -- home is a virtual entity; evcc resets its title to "home" on boot INSERT INTO `entities` (id, `group`, name, title) VALUES (1, 'home', 'home', 'home'); -INSERT INTO `entities` (id, `group`, name, title) VALUES (2, 'grid', 'grid', 'Grid'); +INSERT INTO `entities` (id, `group`, name, title) VALUES (2, 'grid', 'grid', 'grid'); INSERT INTO `entities` (id, `group`, name, title) VALUES (3, 'battery', 'battery', 'Battery'); INSERT INTO `entities` (id, `group`, name, title) VALUES (4, 'pv', 'solar', 'Solar'); INSERT INTO `entities` (id, `group`, name, title) VALUES (5, 'consumer', 'kitchen', 'Kitchen'); @@ -28,6 +28,8 @@ INSERT INTO `entities` (id, `group`, name, title) VALUES (8, 'pv', 'pv-west', 'P INSERT INTO `entities` (id, `group`, name, title) VALUES (9, 'forecast', 'forecast', 'Forecast'); INSERT INTO `entities` (id, `group`, name, title) VALUES (10, 'battery', 'battery-2', 'Battery 2'); INSERT INTO `entities` (id, `group`, name, title) VALUES (11, 'meter', 'submeter', 'Submeter'); +-- leftover from a swapped grid meter device ref +INSERT INTO `entities` (id, `group`, name, title) VALUES (12, 'grid', 'db:2', 'db:2'); -- ===================================================================== -- API test data: 2026-03-24/25 (existing). Used by the JSON-shape tests. @@ -165,6 +167,17 @@ INSERT INTO `meters` VALUES (11, 1775729700, 0.3, 0); INSERT INTO `meters` VALUES (11, 1775730600, 0.3, 0); INSERT INTO `meters` VALUES (11, 1775731500, 0.3, 0); +-- 2026-04-11 → grid meter swap (history-grid-swap.spec.ts): old entity +-- 11:00-11:45, leftover "db:2" 12:00-12:45. +INSERT INTO `meters` VALUES (2, 1775898000, 0.5, 0.1); +INSERT INTO `meters` VALUES (2, 1775898900, 0.5, 0.1); +INSERT INTO `meters` VALUES (2, 1775899800, 0.5, 0.1); +INSERT INTO `meters` VALUES (2, 1775900700, 0.5, 0.1); +INSERT INTO `meters` VALUES (12, 1775901600, 0.5, 0.1); +INSERT INTO `meters` VALUES (12, 1775902500, 0.5, 0.1); +INSERT INTO `meters` VALUES (12, 1775903400, 0.5, 0.1); +INSERT INTO `meters` VALUES (12, 1775904300, 0.5, 0.1); + -- 2026-04-10 → additional meter with export. Import peak 2 kW, export 0.4 kW. -- Negative values must flip the axis to symmetric (bidirectional) ±2 kW. INSERT INTO `meters` VALUES (11, 1775815200, 0.5, 0.1); diff --git a/tests/history-grid-swap.spec.ts b/tests/history-grid-swap.spec.ts new file mode 100644 index 000000000..7857d9888 --- /dev/null +++ b/tests/history-grid-swap.spec.ts @@ -0,0 +1,33 @@ +import { test, expect } from "@playwright/test"; +import { start, stop, baseUrl } from "./evcc"; + +test.use({ baseURL: baseUrl() }); + +// 2026-04-11: two grid entities from a swapped device ref, old "grid" with +// data 11:00-11:45, leftover "db:2" 12:00-12:45 +test.beforeAll(async () => { + await start("config-grid-only.evcc.yaml", "energy-history.sql"); +}); +test.afterAll(async () => { + await stop(); +}); + +test("tooltip shows one merged grid entity", async ({ page }) => { + await page.goto("/#/history?period=day&year=2026&month=4&day=11"); + + const chart = page.getByTestId("group-chart-grid"); + await expect(chart.locator("svg")).toBeVisible(); + + // hover the center of the 12:00-12:15 slot (48 of 96, plot area spans + // the chart width minus the 36px right margin) + const box = await chart.boundingBox(); + if (!box) throw new Error("grid chart not visible"); + await chart.hover({ position: { x: ((box.width - 36) * 48.5) / 96, y: box.height / 2 } }); + + const tooltip = chart.locator("table"); + await expect(tooltip).toBeVisible(); + // single unnamed row, no per-entity rows + await expect(tooltip).toHaveText( + ["12:00 – 12:15", "imported", "exported", "2.0 kW", "400 W"].join("") + ); +});