Metrics: fixed grid entity title, merge series of swapped grid meters (#31739)

This commit is contained in:
Michael Geers 2026-07-12 21:12:09 +02:00 • committed by GitHub
parent 48606017b4
commit e7a49bc938
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 55 additions and 4 deletions

View file

@ -48,6 +48,11 @@ func SetupSchema() error {
return err 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 // enable FK constraints only here to make sure entity for metric exists
if err := db.Instance.Exec("pragma foreign_keys(1)").Error; err != nil { if err := db.Instance.Exec("pragma foreign_keys(1)").Error; err != nil {
return err return err

View file

@ -198,7 +198,7 @@ func (site *Site) Boot(log *util.Logger, loadpoints []*Loadpoint, tariffs *tarif
return errors.New("missing grid meter instance") 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 { if err != nil {
return err return err
} }

View file

@ -23,7 +23,7 @@ test.describe("api", () => {
const data = await res.json(); const data = await res.json();
expect(data).toHaveLength(2); 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"); const home = data.find((s: { title: string }) => s.title === "home");
expect(grid).toBeDefined(); expect(grid).toBeDefined();
expect(home).toBeDefined(); expect(home).toBeDefined();
@ -46,7 +46,7 @@ test.describe("api", () => {
const data = await res.json(); const data = await res.json();
expect(data).toHaveLength(2); 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"); const home = data.find((s: { title: string }) => s.title === "home");
expect(grid).toBeDefined(); expect(grid).toBeDefined();
expect(home).toBeDefined(); expect(home).toBeDefined();

View file

@ -18,7 +18,7 @@ CREATE UNIQUE INDEX `meters_meter_ts` ON `meters`(`meter`, `ts`);
-- entities (name = stable id, title = display label) -- entities (name = stable id, title = display label)
-- home is a virtual entity; evcc resets its title to "home" on boot -- 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 (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 (3, 'battery', 'battery', 'Battery');
INSERT INTO `entities` (id, `group`, name, title) VALUES (4, 'pv', 'solar', 'Solar'); INSERT INTO `entities` (id, `group`, name, title) VALUES (4, 'pv', 'solar', 'Solar');
INSERT INTO `entities` (id, `group`, name, title) VALUES (5, 'consumer', 'kitchen', 'Kitchen'); 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 (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 (10, 'battery', 'battery-2', 'Battery 2');
INSERT INTO `entities` (id, `group`, name, title) VALUES (11, 'meter', 'submeter', 'Submeter'); 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. -- 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, 1775730600, 0.3, 0);
INSERT INTO `meters` VALUES (11, 1775731500, 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. -- 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. -- Negative values must flip the axis to symmetric (bidirectional) ±2 kW.
INSERT INTO `meters` VALUES (11, 1775815200, 0.5, 0.1); INSERT INTO `meters` VALUES (11, 1775815200, 0.5, 0.1);

View file

@ -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("")
);
});