From 48d84356e916e902838bf31711b626a891e1444f Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 6 Apr 2025 19:05:35 +0200 Subject: [PATCH] Tariffs: rename price to value (BC) (#19182) --- api/rates.go | 4 +- api/rates_test.go | 6 +- .../ChargingPlans/PlansSettings.vue | 6 +- .../ChargingPlans/Preview.stories.ts | 8 +- .../components/ChargingPlans/Preview.test.ts | 22 +-- .../js/components/ChargingPlans/Preview.vue | 37 +++-- assets/js/components/ChargingPlans/types.d.ts | 11 -- assets/js/components/Forecast/ActiveSlot.vue | 16 +-- assets/js/components/Forecast/Chart.vue | 26 ++-- assets/js/components/Forecast/Details.vue | 18 +-- .../js/components/Forecast/ForecastModal.vue | 6 +- assets/js/components/Savings/Savings.vue | 2 +- .../js/components/Tariff/SmartCostLimit.vue | 135 +++++++++--------- assets/js/components/Tariff/TariffChart.vue | 58 ++++---- assets/js/types/evcc.ts | 21 ++- assets/js/utils/convertRates.ts | 14 ++ assets/js/utils/forecast.ts | 14 +- assets/js/views/Energy.vue | 6 +- cmd/tariff.go | 2 +- core/loadpoint.go | 2 +- core/loadpoint_plan.go | 2 +- core/loadpoint_smartcost.go | 4 +- core/planner/helper.go | 2 +- core/planner/planner_test.go | 2 +- core/planner/sort.go | 4 +- core/planner/sort_test.go | 6 +- core/site_battery.go | 4 +- core/timeseries.go | 2 +- tariff/amber.go | 4 +- tariff/awattar.go | 2 +- tariff/combined.go | 2 +- tariff/combined_test.go | 2 +- tariff/edf-tempo.go | 2 +- tariff/electricitymaps.go | 2 +- tariff/elering.go | 2 +- tariff/energinet.go | 2 +- tariff/entsoe.go | 2 +- tariff/fixed.go | 2 +- tariff/fixed_test.go | 12 +- tariff/groupe-e.go | 2 +- tariff/gruenstromindex.go | 2 +- tariff/helper_test.go | 2 +- tariff/ngeso.go | 2 +- tariff/octopus.go | 2 +- tariff/ostrom.go | 4 +- tariff/pun.go | 2 +- tariff/smartenergy.go | 2 +- tariff/solcast.go | 4 +- tariff/tariff.go | 4 +- tariff/tariffs.go | 2 +- tariff/tibber.go | 2 +- templates/definition/tariff/allinpower.yaml | 2 +- .../definition/tariff/energy-charts-api.yaml | 2 +- templates/definition/tariff/enever.yaml | 4 +- .../definition/tariff/forecast-solar.yaml | 2 +- templates/definition/tariff/nordpool.yaml | 4 +- templates/definition/tariff/spottyenergy.yaml | 2 +- 57 files changed, 271 insertions(+), 249 deletions(-) create mode 100644 assets/js/utils/convertRates.ts diff --git a/api/rates.go b/api/rates.go index 59891322c..f5baa8997 100644 --- a/api/rates.go +++ b/api/rates.go @@ -10,12 +10,12 @@ import ( type Rate struct { Start time.Time `json:"start"` End time.Time `json:"end"` - Price float64 `json:"price"` + Value float64 `json:"value"` } // IsZero returns is the rate is the zero value func (r Rate) IsZero() bool { - return r.Start.IsZero() && r.End.IsZero() && r.Price == 0 + return r.Start.IsZero() && r.End.IsZero() && r.Value == 0 } // Rates is a slice of (future) tariff rates diff --git a/api/rates_test.go b/api/rates_test.go index 97baa1313..bdda2b087 100644 --- a/api/rates_test.go +++ b/api/rates_test.go @@ -14,7 +14,7 @@ func TestRates(t *testing.T) { return Rate{ Start: clock.Now().Add(time.Duration(start) * time.Hour), End: clock.Now().Add(time.Duration(start+1) * time.Hour), - Price: val, + Value: val, } } @@ -26,11 +26,11 @@ func TestRates(t *testing.T) { for i := 1; i <= 4; i++ { r, err := rr.At(clock.Now().Add(time.Duration(i) * time.Hour)) assert.NoError(t, err) - assert.Equal(t, float64(i), r.Price) + assert.Equal(t, float64(i), r.Value) r, err = rr.At(clock.Now().Add(time.Duration(i)*time.Hour + 30*time.Minute)) assert.NoError(t, err) - assert.Equal(t, float64(i), r.Price) + assert.Equal(t, float64(i), r.Value) } _, err = rr.At(clock.Now().Add(5 * time.Hour)) diff --git a/assets/js/components/ChargingPlans/PlansSettings.vue b/assets/js/components/ChargingPlans/PlansSettings.vue index 9f181cf48..8716838a0 100644 --- a/assets/js/components/ChargingPlans/PlansSettings.vue +++ b/assets/js/components/ChargingPlans/PlansSettings.vue @@ -75,9 +75,9 @@ import collector from "../../mixins/collector.js"; import api, { allowClientError } from "../../api.js"; import CustomSelect from "../Helper/CustomSelect.vue"; import deepEqual from "../../utils/deepEqual.js"; +import convertRates from "../../utils/convertRates"; import { defineComponent, type PropType } from "vue"; import type { Vehicle, PartialBy, Timeout, Tariff, SelectOption, CURRENCY } from "../../types/evcc"; - import type { StaticPlan, RepeatingPlan, @@ -316,10 +316,10 @@ export default defineComponent({ return; } - const tariffRes = await api.get(`tariff/planner`, allowClientError); + const res = await api.get(`tariff/planner`, allowClientError); this.tariff = { - rates: tariffRes.status === 404 ? [] : tariffRes.data.result.rates, + rates: res.status === 404 ? [] : convertRates(res.data.result.rates), lastUpdate: new Date(), }; }, diff --git a/assets/js/components/ChargingPlans/Preview.stories.ts b/assets/js/components/ChargingPlans/Preview.stories.ts index a27047c56..ed4e69017 100644 --- a/assets/js/components/ChargingPlans/Preview.stories.ts +++ b/assets/js/components/ChargingPlans/Preview.stories.ts @@ -10,7 +10,7 @@ function createDate(hoursFromNow: number) { return result; } -function createRate(price: number, hoursFromNow: number, durationHours = 1): Rate { +function createRate(value: number, hoursFromNow: number, durationHours = 1): Rate { const start = new Date(now.getTime()); start.setHours(start.getHours() + hoursFromNow); start.setMinutes(0); @@ -21,7 +21,7 @@ function createRate(price: number, hoursFromNow: number, durationHours = 1): Rat end.setMinutes(0); end.setSeconds(0); end.setMilliseconds(0); - return { start, end, price }; + return { start, end, value }; } // Scenario data @@ -30,7 +30,7 @@ const co2Data = { 545, 518, 545, 518, 0, 545, 527, 527, 536, 518, 400, 336, 336, 339, 344, 336, 336, 336, 372, 400, 555, 555, 545, 555, 564, 545, 555, 545, 536, 545, 527, 536, 518, 545, 509, 336, 336, 336, - ].map((price, i) => createRate(price, i)), + ].map((value, i) => createRate(value, i)), duration: 8695, plan: [createRate(213, 4), createRate(336, 11), createRate(336, 12)], smartCostType: "co2", @@ -72,7 +72,7 @@ const unknownData = { const dynamicData = { rates: [ 0.12, 0.15, 0, -0.05, -0.11, -0.24, -0.08, 0.12, 0.25, 0.29, 0.22, 0.31, 0.31, 0.33, - ].map((price, i) => createRate(price, i)), + ].map((value, i) => createRate(value, i)), duration: 8695, plan: [createRate(0.23, 2, 5)], smartCostType: "price", diff --git a/assets/js/components/ChargingPlans/Preview.test.ts b/assets/js/components/ChargingPlans/Preview.test.ts index 629133e1d..e17454277 100644 --- a/assets/js/components/ChargingPlans/Preview.test.ts +++ b/assets/js/components/ChargingPlans/Preview.test.ts @@ -1,7 +1,7 @@ import { mount, config } from "@vue/test-utils"; import { beforeAll, describe, expect, test } from "vitest"; import Preview from "./Preview.vue"; -import type { Slot } from "./types"; +import type { Slot } from "../../types/evcc.js"; config.global.mocks["$i18n"] = { locale: "de-DE" }; config.global.mocks["$t"] = (a: any) => a; @@ -13,14 +13,14 @@ describe("basics", () => { { start: new Date("2023-01-11T11:00:00+01:00"), end: new Date("2023-01-22T00:00:00+01:00"), - price: 0.4, + value: 0.4, }, ]; const PLAN = [ { start: new Date("2023-01-11T12:00:00+01:00"), end: new Date("2023-01-11T13:00:00+01:00"), - price: 0.2, + value: 0.2, }, ]; @@ -75,8 +75,8 @@ describe("basics", () => { expect(result[3].charging).eq(false); }); - test("all slots have the same fixed price", () => { - result.forEach((slot) => expect(slot.price).eq(0.4)); + test("all slots have the same fixed value", () => { + result.forEach((slot) => expect(slot.value).eq(0.4)); }); }); @@ -87,24 +87,24 @@ describe("zoned tariffs", () => { { start: new Date("2023-01-11T11:00:00+01:00"), end: new Date("2023-01-11T12:00:00+01:00"), - price: 0.2, + value: 0.2, }, { start: new Date("2023-01-11T12:00:00+01:00"), end: new Date("2023-01-22T00:00:00+01:00"), - price: 0.4, + value: 0.4, }, ]; const PLAN = [ { start: new Date("2023-01-11T11:30:00+01:00"), end: new Date("2023-01-11T13:00:00+01:00"), - price: 0.3, + value: 0.3, }, { start: new Date("2023-01-11T14:30:00+01:00"), end: new Date("2023-01-11T16:00:00+01:00"), - price: 0.2, + value: 0.2, }, ]; @@ -134,7 +134,7 @@ describe("zoned tariffs", () => { test("first slot is cheap, others are expensive", () => { const [first, ...others] = result; - expect(first.price).eq(0.2); - others.forEach((slot) => expect(slot.price).eq(0.4)); + expect(first.value).eq(0.2); + others.forEach((slot) => expect(slot.value).eq(0.4)); }); }); diff --git a/assets/js/components/ChargingPlans/Preview.vue b/assets/js/components/ChargingPlans/Preview.vue index 2f59b4f54..ee6833008 100644 --- a/assets/js/components/ChargingPlans/Preview.vue +++ b/assets/js/components/ChargingPlans/Preview.vue @@ -21,7 +21,7 @@ {{ $t("main.targetChargePlan.priceLabel") }}
- {{ fmtAvgPrice }} + {{ fmtAvgValue }}
@@ -39,8 +39,7 @@ import { defineComponent, type PropType } from "vue"; import formatter from "../../mixins/formatter.js"; import { CO2_TYPE } from "../../units.js"; import TariffChart from "../Tariff/TariffChart.vue"; -import type { CURRENCY, Rate } from "assets/js/types/evcc.js"; -import type { Slot } from "./types.js"; +import type { CURRENCY, Rate, Slot } from "../../types/evcc.js"; export default defineComponent({ name: "ChargingPlanPreview", @@ -87,29 +86,29 @@ export default defineComponent({ hasTariff(): boolean { return (this.rates?.length || 0) > 1; }, - avgPrice(): number | undefined { + avgValue(): number | undefined { let hourSum = 0; - let priceSum = 0; + let valueSum = 0; this.convertDates(this.plan).forEach((slot) => { const hours = (slot.end.getTime() - slot.start.getTime()) / 3600000; - if (slot.price) { + if (slot.value) { hourSum += hours; - priceSum += hours * slot.price; + valueSum += hours * slot.value; } }); - return hourSum ? priceSum / hourSum : undefined; + return hourSum ? valueSum / hourSum : undefined; }, - fmtAvgPrice(): string { + fmtAvgValue(): string { if (this.duration === 0) { return "—"; } - const price = this.activeSlot ? this.activeSlot.price : this.avgPrice; - if (price === undefined) { + const value = this.activeSlot ? this.activeSlot.value : this.avgValue; + if (value === undefined) { return this.$t("main.targetChargePlan.unknownPrice"); } return this.isCo2 - ? this.fmtCo2Medium(price) - : this.fmtPricePerKWh(price, this.currency); + ? this.fmtCo2Medium(value) + : this.fmtPricePerKWh(value, this.currency); }, activeSlot(): Slot | null { return this.activeIndex ? this.slots[this.activeIndex] : null; @@ -122,10 +121,8 @@ export default defineComponent({ } return null; }, - targetHourOffset(): number | null { - if (!this.targetTime) { - return null; - } + targetHourOffset(): number | undefined { + if (!this.targetTime) return; const start = new Date(this.startTime); start.setMinutes(0); start.setSeconds(0); @@ -155,7 +152,7 @@ export default defineComponent({ const day = this.weekdayShort(start); const toLate = this.targetTime && this.targetTime <= start; // TODO: handle multiple matching time slots - const price = this.findSlotInRange(start, end, rates)?.price; + const value = this.findSlotInRange(start, end, rates)?.value; const isTarget = this.targetTime && start <= this.targetTime && end > this.targetTime; const charging = this.findSlotInRange(start, end, plan) != null; @@ -167,7 +164,7 @@ export default defineComponent({ this.targetTime < this.endTime; result.push({ day, - price, + value, startHour, endHour, charging, @@ -193,7 +190,7 @@ export default defineComponent({ return { start: new Date(item.start), end: new Date(item.end), - price: item.price, + value: item.value, }; }); }, diff --git a/assets/js/components/ChargingPlans/types.d.ts b/assets/js/components/ChargingPlans/types.d.ts index 92f8727ac..8a7475bff 100644 --- a/assets/js/components/ChargingPlans/types.d.ts +++ b/assets/js/components/ChargingPlans/types.d.ts @@ -32,14 +32,3 @@ export interface StaticEnergyPlan { energy: number; time: Date; } - -export interface Slot { - day: string; - price?: number; - startHour: number; - endHour: number; - charging: boolean; - toLate?: boolean | null; - warning?: boolean | null; - isTarget?: boolean | null; -} diff --git a/assets/js/components/Forecast/ActiveSlot.vue b/assets/js/components/Forecast/ActiveSlot.vue index 60c4fc61a..56c34c0ba 100644 --- a/assets/js/components/Forecast/ActiveSlot.vue +++ b/assets/js/components/Forecast/ActiveSlot.vue @@ -11,32 +11,32 @@