New energy calculation formula (#25242)

* New energy calculation

* more tests and stricter tests. change priority order
This commit is contained in:
karwosts 2025-04-30 09:47:51 -07:00 committed by GitHub
parent 221bc732fb
commit f8d706277d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 234 additions and 105 deletions

View File

@ -959,21 +959,13 @@ const computeConsumptionDataPartial = (
}; };
data.timestamps.forEach((t) => { data.timestamps.forEach((t) => {
const used_total =
(data.from_grid?.[t] || 0) +
(data.solar?.[t] || 0) +
(data.from_battery?.[t] || 0) -
(data.to_grid?.[t] || 0) -
(data.to_battery?.[t] || 0);
outData.used_total[t] = used_total;
outData.total.used_total += used_total;
const { const {
grid_to_battery, grid_to_battery,
battery_to_grid, battery_to_grid,
used_solar, used_solar,
used_grid, used_grid,
used_battery, used_battery,
used_total,
solar_to_battery, solar_to_battery,
solar_to_grid, solar_to_grid,
} = computeConsumptionSingle({ } = computeConsumptionSingle({
@ -984,6 +976,8 @@ const computeConsumptionDataPartial = (
from_battery: data.from_battery && (data.from_battery[t] ?? 0), from_battery: data.from_battery && (data.from_battery[t] ?? 0),
}); });
outData.used_total[t] = used_total;
outData.total.used_total += used_total;
outData.grid_to_battery[t] = grid_to_battery; outData.grid_to_battery[t] = grid_to_battery;
outData.total.grid_to_battery += grid_to_battery; outData.total.grid_to_battery += grid_to_battery;
outData.battery_to_grid![t] = battery_to_grid; outData.battery_to_grid![t] = battery_to_grid;
@ -1017,12 +1011,20 @@ export const computeConsumptionSingle = (data: {
used_solar: number; used_solar: number;
used_grid: number; used_grid: number;
used_battery: number; used_battery: number;
used_total: number;
} => { } => {
const to_grid = data.to_grid; let to_grid = Math.max(data.to_grid || 0, 0);
const to_battery = data.to_battery; let to_battery = Math.max(data.to_battery || 0, 0);
const solar = data.solar; let solar = Math.max(data.solar || 0, 0);
const from_grid = data.from_grid; let from_grid = Math.max(data.from_grid || 0, 0);
const from_battery = data.from_battery; let from_battery = Math.max(data.from_battery || 0, 0);
const used_total =
(from_grid || 0) +
(solar || 0) +
(from_battery || 0) -
(to_grid || 0) -
(to_battery || 0);
let used_solar = 0; let used_solar = 0;
let grid_to_battery = 0; let grid_to_battery = 0;
@ -1031,41 +1033,57 @@ export const computeConsumptionSingle = (data: {
let solar_to_grid = 0; let solar_to_grid = 0;
let used_battery = 0; let used_battery = 0;
let used_grid = 0; let used_grid = 0;
if ((to_grid != null || to_battery != null) && solar != null) {
used_solar = (solar || 0) - (to_grid || 0) - (to_battery || 0);
if (used_solar < 0) {
if (to_battery != null) {
grid_to_battery = used_solar * -1;
if (grid_to_battery > (from_grid || 0)) {
battery_to_grid = grid_to_battery - (from_grid || 0);
grid_to_battery = from_grid || 0;
}
}
used_solar = 0;
}
}
if (from_battery != null) { let used_total_remaining = Math.max(used_total, 0);
used_battery = (from_battery || 0) - battery_to_grid; // Consumption Priority
} // Battery_Out -> Grid_Out
// Solar -> Grid_Out
// Solar -> Battery_In
// Grid_In -> Battery_In
// Solar -> Consumption
// Battery_Out -> Consumption
// Grid_In -> Consumption
if (from_grid != null) { // Battery_Out -> Grid_Out
used_grid = from_grid - grid_to_battery; battery_to_grid = Math.min(from_battery, to_grid);
} from_battery -= battery_to_grid;
to_grid -= battery_to_grid;
if (solar != null) { // Solar -> Grid_Out
if (to_battery != null) { solar_to_grid = Math.min(solar, to_grid);
solar_to_battery = Math.max(0, (to_battery || 0) - grid_to_battery); to_grid -= solar_to_grid;
} solar -= solar_to_grid;
if (to_grid != null) {
solar_to_grid = Math.max(0, (to_grid || 0) - battery_to_grid); // Solar -> Battery_In
} solar_to_battery = Math.min(solar, to_battery);
} to_battery -= solar_to_battery;
solar -= solar_to_battery;
// Grid_In -> Battery_In
grid_to_battery = Math.min(from_grid, to_battery);
from_grid -= grid_to_battery;
to_battery -= to_battery;
// Solar -> Consumption
used_solar = Math.min(used_total_remaining, solar);
used_total_remaining -= used_solar;
solar -= used_solar;
// Battery_Out -> Consumption
used_battery = Math.min(from_battery, used_total_remaining);
from_battery -= used_battery;
used_total_remaining -= used_battery;
// Grid_In -> Consumption
used_grid = Math.min(used_total_remaining, from_grid);
from_grid -= used_grid;
used_total_remaining -= from_grid;
return { return {
used_solar, used_solar,
used_grid, used_grid,
used_battery, used_battery,
used_total,
grid_to_battery, grid_to_battery,
battery_to_grid, battery_to_grid,
solar_to_battery, solar_to_battery,

View File

@ -14,6 +14,47 @@ import {
} from "../../src/data/energy"; } from "../../src/data/energy";
import type { HomeAssistant } from "../../src/types"; import type { HomeAssistant } from "../../src/types";
const checkConsumptionResult = (
input: {
from_grid: number | undefined;
to_grid: number | undefined;
solar: number | undefined;
to_battery: number | undefined;
from_battery: number | undefined;
},
exact = true
): {
grid_to_battery: number;
battery_to_grid: number;
solar_to_battery: number;
solar_to_grid: number;
used_solar: number;
used_grid: number;
used_battery: number;
used_total: number;
} => {
const result = computeConsumptionSingle(input);
if (exact) {
assert.equal(
result.used_total,
result.used_solar + result.used_battery + result.used_grid
);
assert.equal(
input.to_grid || 0,
result.solar_to_grid + result.battery_to_grid
);
assert.equal(
input.to_battery || 0,
result.grid_to_battery + result.solar_to_battery
);
assert.equal(
input.solar || 0,
result.solar_to_battery + result.solar_to_grid + result.used_solar
);
}
return result;
};
describe("Energy Short Format Test", () => { describe("Energy Short Format Test", () => {
// Create default to not have to specify a not relevant TimeFormat over and over again. // Create default to not have to specify a not relevant TimeFormat over and over again.
const defaultLocale: FrontendLocaleData = { const defaultLocale: FrontendLocaleData = {
@ -88,7 +129,7 @@ describe("Energy Usage Calculation Tests", () => {
it("Consuming Energy From the Grid", () => { it("Consuming Energy From the Grid", () => {
[0, 5, 1000].forEach((x) => { [0, 5, 1000].forEach((x) => {
assert.deepEqual( assert.deepEqual(
computeConsumptionSingle({ checkConsumptionResult({
from_grid: x, from_grid: x,
to_grid: undefined, to_grid: undefined,
solar: undefined, solar: undefined,
@ -101,6 +142,7 @@ describe("Energy Usage Calculation Tests", () => {
used_solar: 0, used_solar: 0,
used_grid: x, used_grid: x,
used_battery: 0, used_battery: 0,
used_total: x,
solar_to_battery: 0, solar_to_battery: 0,
solar_to_grid: 0, solar_to_grid: 0,
} }
@ -108,61 +150,78 @@ describe("Energy Usage Calculation Tests", () => {
}); });
}); });
it("Solar production, consuming some and returning the remainder to grid.", () => { it("Solar production, consuming some and returning the remainder to grid.", () => {
[2.99, 3, 10, 100].forEach((s) => { (
[
[2.99, false], // unsolveable : solar < to_grid
[3, true],
[10, true],
[100, true],
] as any
).forEach(([s, exact]) => {
assert.deepEqual( assert.deepEqual(
computeConsumptionSingle({ checkConsumptionResult(
from_grid: 0, {
to_grid: 3, from_grid: 0,
solar: s, to_grid: 3,
to_battery: undefined, solar: s,
from_battery: undefined, to_battery: undefined,
}), from_battery: undefined,
},
exact
),
{ {
grid_to_battery: 0, grid_to_battery: 0,
battery_to_grid: 0, battery_to_grid: 0,
used_solar: Math.max(0, s - 3), used_solar: Math.min(s, Math.max(0, s - 3)),
used_grid: 0, used_grid: 0,
used_battery: 0, used_battery: 0,
used_total: s - 3,
solar_to_battery: 0, solar_to_battery: 0,
solar_to_grid: 3, solar_to_grid: Math.min(3, s),
} }
); );
}); });
}); });
it("Solar production with simultaneous grid consumption. Excess solar returned to the grid.", () => { it("Solar production with simultaneous grid consumption. Excess solar returned to the grid.", () => {
[ (
[0, 0], [
[3, 0], [0, 0, true],
[0, 3], [3, 0, true],
[5, 4], [0, 3, true],
[4, 5], [5, 4, true],
[10, 3], [4, 5, true],
[3, 7], [10, 3, true],
[3, 7.1], [3, 7, true],
].forEach(([from_grid, to_grid]) => { [3, 7.1, false], // unsolveable: to_grid > solar
] as any
).forEach(([from_grid, to_grid, exact]) => {
assert.deepEqual( assert.deepEqual(
computeConsumptionSingle({ checkConsumptionResult(
from_grid, {
to_grid, from_grid,
solar: 7, to_grid,
to_battery: undefined, solar: 7,
from_battery: undefined, to_battery: undefined,
}), from_battery: undefined,
},
exact
),
{ {
grid_to_battery: 0, grid_to_battery: 0,
battery_to_grid: 0, battery_to_grid: 0,
used_solar: Math.max(0, 7 - to_grid), used_solar: Math.max(0, 7 - to_grid),
used_grid: from_grid, used_grid: from_grid - Math.max(0, to_grid - 7),
used_total: from_grid - to_grid + 7,
used_battery: 0, used_battery: 0,
solar_to_battery: 0, solar_to_battery: 0,
solar_to_grid: to_grid, solar_to_grid: Math.min(7, to_grid),
} }
); );
}); });
}); });
it("Charging the battery from the grid", () => { it("Charging the battery from the grid", () => {
assert.deepEqual( assert.deepEqual(
computeConsumptionSingle({ checkConsumptionResult({
from_grid: 5, from_grid: 5,
to_grid: 0, to_grid: 0,
solar: 0, solar: 0,
@ -175,6 +234,7 @@ describe("Energy Usage Calculation Tests", () => {
used_solar: 0, used_solar: 0,
used_grid: 2, used_grid: 2,
used_battery: 0, used_battery: 0,
used_total: 2,
solar_to_battery: 0, solar_to_battery: 0,
solar_to_grid: 0, solar_to_grid: 0,
} }
@ -182,7 +242,7 @@ describe("Energy Usage Calculation Tests", () => {
}); });
it("Consuming from the grid and battery simultaneously", () => { it("Consuming from the grid and battery simultaneously", () => {
assert.deepEqual( assert.deepEqual(
computeConsumptionSingle({ checkConsumptionResult({
from_grid: 5, from_grid: 5,
to_grid: 0, to_grid: 0,
solar: 0, solar: 0,
@ -195,6 +255,7 @@ describe("Energy Usage Calculation Tests", () => {
used_solar: 0, used_solar: 0,
used_grid: 5, used_grid: 5,
used_battery: 5, used_battery: 5,
used_total: 10,
solar_to_battery: 0, solar_to_battery: 0,
solar_to_grid: 0, solar_to_grid: 0,
} }
@ -202,7 +263,7 @@ describe("Energy Usage Calculation Tests", () => {
}); });
it("Consuming some battery and returning some battery to the grid", () => { it("Consuming some battery and returning some battery to the grid", () => {
assert.deepEqual( assert.deepEqual(
computeConsumptionSingle({ checkConsumptionResult({
from_grid: 0, from_grid: 0,
to_grid: 4, to_grid: 4,
solar: 0, solar: 0,
@ -215,15 +276,15 @@ describe("Energy Usage Calculation Tests", () => {
used_solar: 0, used_solar: 0,
used_grid: 0, used_grid: 0,
used_battery: 1, used_battery: 1,
used_total: 1,
solar_to_battery: 0, solar_to_battery: 0,
solar_to_grid: 0, solar_to_grid: 0,
} }
); );
}); });
/* Fails
it("Charging and discharging the battery to/from the grid in the same interval.", () => { it("Charging and discharging the battery to/from the grid in the same interval.", () => {
assert.deepEqual( assert.deepEqual(
computeConsumptionSingle({ checkConsumptionResult({
from_grid: 5, from_grid: 5,
to_grid: 1, to_grid: 1,
solar: 0, solar: 0,
@ -234,15 +295,18 @@ describe("Energy Usage Calculation Tests", () => {
grid_to_battery: 3, grid_to_battery: 3,
battery_to_grid: 1, battery_to_grid: 1,
used_solar: 0, used_solar: 0,
used_grid: 1, used_grid: 2,
used_battery: 0, used_battery: 0,
used_total: 2,
solar_to_battery: 0,
solar_to_grid: 0,
} }
); );
}); */ });
/* Test does not pass, battery is not really correct when solar is not present
it("Charging the battery with no solar sensor.", () => { it("Charging the battery with no solar sensor.", () => {
assert.deepEqual( assert.deepEqual(
computeConsumptionSingle({ checkConsumptionResult({
from_grid: 5, from_grid: 5,
to_grid: 0, to_grid: 0,
solar: undefined, solar: undefined,
@ -255,13 +319,15 @@ describe("Energy Usage Calculation Tests", () => {
used_solar: 0, used_solar: 0,
used_grid: 2, used_grid: 2,
used_battery: 0, used_battery: 0,
used_total: 2,
solar_to_battery: 0,
solar_to_grid: 0,
} }
); );
}); */ });
/* Test does not pass
it("Discharging battery to grid while also consuming from grid.", () => { it("Discharging battery to grid while also consuming from grid.", () => {
assert.deepEqual( assert.deepEqual(
computeConsumptionSingle({ checkConsumptionResult({
from_grid: 5, from_grid: 5,
to_grid: 4, to_grid: 4,
solar: 0, solar: 0,
@ -274,14 +340,16 @@ describe("Energy Usage Calculation Tests", () => {
used_solar: 0, used_solar: 0,
used_grid: 5, used_grid: 5,
used_battery: 0, used_battery: 0,
used_total: 5,
solar_to_grid: 0,
solar_to_battery: 0,
} }
); );
}); });
*/
it("Grid, solar, and battery", () => { it("Grid, solar, and battery", () => {
assert.deepEqual( assert.deepEqual(
computeConsumptionSingle({ checkConsumptionResult({
from_grid: 5, from_grid: 5,
to_grid: 3, to_grid: 3,
solar: 7, solar: 7,
@ -294,12 +362,13 @@ describe("Energy Usage Calculation Tests", () => {
used_solar: 1, used_solar: 1,
used_grid: 5, used_grid: 5,
used_battery: 0, used_battery: 0,
used_total: 6,
solar_to_battery: 3, solar_to_battery: 3,
solar_to_grid: 3, solar_to_grid: 3,
} }
); );
assert.deepEqual( assert.deepEqual(
computeConsumptionSingle({ checkConsumptionResult({
from_grid: 5, from_grid: 5,
to_grid: 3, to_grid: 3,
solar: 7, solar: 7,
@ -308,16 +377,17 @@ describe("Energy Usage Calculation Tests", () => {
}), }),
{ {
grid_to_battery: 0, grid_to_battery: 0,
battery_to_grid: 0, battery_to_grid: 3,
used_solar: 1, used_solar: 4,
used_grid: 5, used_grid: 5,
used_battery: 10, used_battery: 7,
used_total: 16,
solar_to_battery: 3, solar_to_battery: 3,
solar_to_grid: 3, solar_to_grid: 0,
} }
); );
assert.deepEqual( assert.deepEqual(
computeConsumptionSingle({ checkConsumptionResult({
from_grid: 2, from_grid: 2,
to_grid: 7, to_grid: 7,
solar: 7, solar: 7,
@ -325,17 +395,18 @@ describe("Energy Usage Calculation Tests", () => {
from_battery: 1, from_battery: 1,
}), }),
{ {
grid_to_battery: 1, grid_to_battery: 0,
battery_to_grid: 0, battery_to_grid: 1,
used_solar: 0, used_solar: 0,
used_grid: 1, used_grid: 2,
used_battery: 1, used_battery: 0,
solar_to_battery: 0, used_total: 2,
solar_to_grid: 7, solar_to_battery: 1,
solar_to_grid: 6,
} }
); );
assert.deepEqual( assert.deepEqual(
computeConsumptionSingle({ checkConsumptionResult({
from_grid: 2, from_grid: 2,
to_grid: 7, to_grid: 7,
solar: 9, solar: 9,
@ -344,17 +415,17 @@ describe("Energy Usage Calculation Tests", () => {
}), }),
{ {
grid_to_battery: 0, grid_to_battery: 0,
battery_to_grid: 0, battery_to_grid: 1,
used_solar: 1, used_solar: 2,
used_grid: 2, used_grid: 2,
used_battery: 1, used_battery: 0,
used_total: 4,
solar_to_battery: 1, solar_to_battery: 1,
solar_to_grid: 7, solar_to_grid: 6,
} }
); );
/* Test does not pass
assert.deepEqual( assert.deepEqual(
computeConsumptionSingle({ checkConsumptionResult({
from_grid: 5, from_grid: 5,
to_grid: 3, to_grid: 3,
solar: 1, solar: 1,
@ -367,8 +438,48 @@ describe("Energy Usage Calculation Tests", () => {
used_solar: 0, used_solar: 0,
used_grid: 5, used_grid: 5,
used_battery: 0, used_battery: 0,
used_total: 5,
solar_to_battery: 0,
solar_to_grid: 1,
}
);
assert.deepEqual(
checkConsumptionResult({
from_grid: 6,
to_grid: 0,
solar: 3,
to_battery: 6,
from_battery: 6,
}),
{
grid_to_battery: 3,
battery_to_grid: 0,
used_solar: 0,
used_grid: 3,
used_battery: 6,
solar_to_battery: 3,
solar_to_grid: 0,
used_total: 9,
}
);
assert.deepEqual(
checkConsumptionResult({
from_grid: 0,
to_grid: 1,
solar: 1,
to_battery: 1,
from_battery: 1,
}),
{
grid_to_battery: 0,
battery_to_grid: 1,
used_solar: 0,
used_grid: 0,
used_battery: 0,
solar_to_battery: 1,
solar_to_grid: 0,
used_total: 0,
} }
); );
*/
}); });
}); });