Skip to content

Commit

Permalink
Replace savings with session based stats (evcc-io#10341)
Browse files Browse the repository at this point in the history
  • Loading branch information
naltatis authored Oct 21, 2023
1 parent eaea20a commit 58e1238
Show file tree
Hide file tree
Showing 22 changed files with 574 additions and 441 deletions.
26 changes: 26 additions & 0 deletions assets/js/co2Reference.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Data source: gCO2eq/kWh
const source = "https://ourworldindata.org/grapher/carbon-intensity-electricity?tab=table";

// This is a manual selection of countries. If yours is missing, please add it with data from the source above.
const regions = [
{ name: "Australia", co2: 503 },
{ name: "Austria", co2: 158 },
{ name: "Canada", co2: 128 },
{ name: "Czech Republic", co2: 415 },
{ name: "Denmark", co2: 181 },
{ name: "Estonia", co2: 464 },
{ name: "Europe", co2: 278 },
{ name: "Finland", co2: 131 },
{ name: "France", co2: 85 },
{ name: "Germany", co2: 385 },
{ name: "Netherlands", co2: 356 },
{ name: "Norway", co2: 29 },
{ name: "Poland", co2: 635 },
{ name: "Sweden", co2: 45 },
{ name: "Switzerland", co2: 46 },
{ name: "United Kingdom", co2: 257 },
{ name: "United States", co2: 367 },
{ name: "World", co2: 436 },
];

export default { regions, source };
6 changes: 6 additions & 0 deletions assets/js/components/AnimatedNumber.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,9 @@ export default {
},
};
</script>
<style scoped>
span {
font-variant-numeric: tabular-nums;
}
</style>
5 changes: 3 additions & 2 deletions assets/js/components/CustomSelect.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<label class="position-relative d-block">
<select :value="selected" class="custom-select" @change="change">
<label class="position-relative d-block" :for="id">
<select :id="id" :value="selected" class="custom-select" @change="change">
<option
v-for="{ name, value, count, disabled } in options"
:key="value"
Expand All @@ -20,6 +20,7 @@ export default {
props: {
options: { type: Array },
selected: { type: String },
id: { type: String },
},
emits: ["change"],
methods: {
Expand Down
2 changes: 1 addition & 1 deletion assets/js/components/LiveCommunity.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

<SavingsTile
class="text-accent3"
icon="eco"
icon="lightning"
:title="$t('footer.community.greenEnergy')"
:value="greenEnergy.value"
:valueFmt="fmtAnimation"
Expand Down
5 changes: 4 additions & 1 deletion assets/js/components/LoadpointSessionInfo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</CustomSelect>
</template>
<template #value>
<div data-testid="sessionInfoValue" @click="nextSessionInfo">
<div class="value" data-testid="sessionInfoValue" @click="nextSessionInfo">
<div :class="{ 'd-none d-sm-block': showSm }">{{ value }}</div>
<div v-if="showSm" class="d-block d-sm-none">{{ valueSm }}</div>
</div>
Expand Down Expand Up @@ -155,4 +155,7 @@ export default {
user-select: none;
-webkit-user-select: none;
}
.value {
font-variant-numeric: tabular-nums;
}
</style>
242 changes: 182 additions & 60 deletions assets/js/components/Savings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<div>
<button
class="btn btn-link pe-0 text-decoration-none evcc-default-text text-nowrap d-flex align-items-end"
data-testid="savings-button"
@click="openModal"
>
<span class="d-inline d-sm-none text-decoration-underline">{{
Expand Down Expand Up @@ -65,18 +66,13 @@
<SavingsTile
class="text-accent1"
icon="sun"
data-testid="savings-tile-solar"
:title="$t('footer.savings.percentTitle')"
:value="selfConsumptionPercent"
:valueFmt="fmtAnimation"
:value="fmtNumber(solarPercentage, 1)"
unit="%"
:sub1="
$t('footer.savings.percentSelf', {
self: fmtKw(
selfConsumptionCharged * 1000,
true,
false,
0
),
self: fmtKw(solarCharged * 1000, true, false, 0),
})
"
:sub2="
Expand All @@ -89,44 +85,101 @@
<SavingsTile
class="text-accent2"
icon="receivepayment"
data-testid="savings-tile-price"
:title="$t('footer.savings.priceTitle')"
:value="effectivePriceFormatted.value"
:unit="effectivePriceFormatted.unit"
:value="priceConfigured ? avgPriceFormatted.value : '__'"
:unit="avgPriceFormatted.unit"
:sub1="
$t('footer.savings.priceFeedIn', {
feedInPrice: fmtPricePerKWh(feedInPrice, currency),
})
"
:sub2="
$t('footer.savings.priceGrid', {
gridPrice: fmtPricePerKWh(gridPrice, currency),
})
priceConfigured
? `${fmtMoney(
(referenceGrid - avgPrice) * totalCharged,
currency,
false
)} ${fmtCurrencySymbol(currency)} ${$t(
'footer.savings.moneySaved'
)}`
: ''
"
/>

<SavingsTile
class="text-accent3"
icon="coinjar"
:title="$t('footer.savings.savingsTitle')"
:value="fmtMoney(amount, currency, amount < 100)"
:unit="fmtCurrencySymbol(currency)"
:sub1="$t('footer.savings.savingsComparedToGrid')"
:sub2="
$t('footer.savings.savingsTotalEnergy', {
total: fmtKw(totalCharged * 1000, true, false, 0),
})
icon="eco"
data-testid="savings-tile-co2"
:title="$t('footer.savings.co2Title')"
:value="co2Configured ? fmtNumber(avgCo2, 0) : '__'"
unit="g/kWh"
:sub1="
region && co2Configured
? `${fmtNumber(
((region.co2 - avgCo2) * totalCharged) /
1000,
0,
'kilogram'
)} ${$t('footer.savings.co2Saved')}`
: ''
"
/>
</div>
<p class="my-3 lh-2">
<small>
{{
$t("footer.savings.since", {
since: startDate,
})
}}
</small>
</p>
<div class="my-3 lh-2">
<div class="d-flex">
<label for="savingsPeriod" class="me-1">
{{ $t("footer.savings.periodLabel") }}
</label>
<CustomSelect
id="savingsPeriod"
:selected="period"
:options="periodOptions"
data-testid="savings-period-select"
@change="selectPeriod($event.target.value)"
>
<span class="text-decoration-underline evcc-gray">
{{ $t(`footer.savings.period.${period}`) }}
</span>
</CustomSelect>
</div>
<div
v-if="region"
class="d-flex flex-wrap"
data-testid="savings-reference"
>
<div class="me-1">
{{ $t("footer.savings.referenceLabel") }}
</div>
<div class="evcc-gray me-1">
{{
priceConfigured
? fmtPricePerKWh(referenceGrid, currency)
: "___"
}}
({{ $t("footer.savings.referenceGrid") }}),
</div>
<div class="evcc-gray d-flex">
<div class="me-1">⌀ {{ fmtCo2Medium(region.co2) }}</div>
<CustomSelect
class="me-1 evcc-gray"
:selected="region.name"
:options="regionOptions"
data-testid="savings-region-select"
@change="selectRegion($event.target.value)"
>
(<span class="text-decoration-underline">{{
region.name
}}</span
>)
</CustomSelect>
</div>
</div>
<div v-if="!priceConfigured || !co2Configured">
<a
href="https://docs.evcc.io/en/docs/reference/configuration/tariffs/"
class="evcc-gray"
target="_blank"
>
{{ $t("footer.savings.configurePriceCo2") }}
</a>
</div>
</div>
</div>
<div v-else class="my-4">
<LiveCommunity />
Expand All @@ -149,41 +202,91 @@ import Sponsor from "./Sponsor.vue";
import SavingsTile from "./SavingsTile.vue";
import LiveCommunity from "./LiveCommunity.vue";
import TelemetrySettings from "./TelemetrySettings.vue";
import CustomSelect from "./CustomSelect.vue";
import co2Reference from "../co2Reference";
import settings from "../settings";
import api from "../api";
export default {
name: "Savings",
components: { Sponsor, SavingsTile, LiveCommunity, TelemetrySettings },
components: { Sponsor, SavingsTile, LiveCommunity, TelemetrySettings, CustomSelect },
mixins: [formatter],
props: {
selfConsumptionPercent: Number,
since: String,
stats: { type: Object, default: () => ({}) },
co2Configured: Boolean,
sponsor: String,
amount: { type: Number, default: 0 },
effectivePrice: { type: Number, default: 0 },
totalCharged: { type: Number, default: 0 },
gridCharged: { type: Number, default: 0 },
selfConsumptionCharged: { type: Number, default: 0 },
gridPrice: { type: Number },
feedInPrice: { type: Number },
currency: String,
},
data() {
return { communityView: false, telemetryEnabled: false };
return {
communityView: false,
telemetryEnabled: false,
period: settings.savingsPeriod || "30d",
selectedRegion: settings.savingsRegion || "Germany",
referenceGrid: undefined,
};
},
computed: {
percent() {
return Math.round(this.selfConsumptionPercent) || 0;
return Math.round(this.solarPercentage) || 0;
},
regionOptions() {
return co2Reference.regions.map((r) => ({
value: r.name,
name: `${r.name} (${this.fmtCo2Short(r.co2)})`,
}));
},
region() {
// previously selected region
if (this.selectedRegion) {
const result = co2Reference.regions.find((r) => r.name === this.selectedRegion);
if (result) {
return result;
}
}
// first region
return co2Reference.regions[0];
},
periodOptions() {
return ["30d", "365d", "total"].map((p) => ({
value: p,
name: this.$t(`footer.savings.period.${p}`),
}));
},
effectivePriceFormatted() {
const value = this.fmtPricePerKWh(this.effectivePrice, this.currency, false, false);
avgPriceFormatted() {
const value = this.fmtPricePerKWh(
this.currentStats.avgPrice,
this.currency,
false,
false
);
const unit = this.pricePerKWhUnit(this.currency);
return { value, unit };
},
startDate() {
if (this.since) {
return this.fmtDayMonthYear(new Date(this.since));
}
return "";
currentStats() {
return this.stats[this.period] || {};
},
totalCharged() {
return this.currentStats.chargedKWh;
},
solarPercentage() {
return this.currentStats.solarPercentage;
},
solarCharged() {
return (this.solarPercentage / 100) * this.totalCharged;
},
gridCharged() {
return this.totalCharged - this.solarCharged;
},
avgPrice() {
return this.currentStats.avgPrice;
},
avgCo2() {
return this.currentStats.avgCo2;
},
priceConfigured() {
return this.referenceGrid !== undefined;
},
},
methods: {
Expand All @@ -196,12 +299,31 @@ export default {
openModal() {
const modal = Modal.getOrCreateInstance(document.getElementById("savingsModal"));
modal.show();
this.updateReferenceGrid();
},
fmtAnimation(number) {
let decimals = 0;
if (number < 100) decimals = 1;
if (number < 10) decimals = 2;
return this.fmtNumber(number, decimals);
selectPeriod(period) {
this.period = period;
settings.savingsPeriod = period;
},
selectRegion(region) {
this.selectedRegion = region;
settings.savingsRegion = region;
},
updateReferenceGrid: async function () {
try {
const res = await api.get(`tariff/grid`, {
validateStatus: (status) => status >= 200 && status < 500,
});
const { rates } = res.data.result;
this.referenceGrid =
rates.reduce((acc, slot) => {
acc += slot.price;
return acc;
}, 0) / rates.length;
} catch (e) {
this.referenceGrid = undefined;
console.error(e);
}
},
},
};
Expand Down
Loading

0 comments on commit 58e1238

Please sign in to comment.