diff --git a/assets/js/components/Sessions/CostHistoryChart.vue b/assets/js/components/Sessions/CostHistoryChart.vue index b7586afcc..59dbe09f0 100644 --- a/assets/js/components/Sessions/CostHistoryChart.vue +++ b/assets/js/components/Sessions/CostHistoryChart.vue @@ -47,6 +47,7 @@ export default { period: { type: String, default: PERIODS.TOTAL }, currency: { type: String, default: "EUR" }, colorMappings: { type: Object, default: () => ({ loadpoint: {}, vehicle: {} }) }, + suggestedMaxAvgCost: { type: Number, default: 0 }, suggestedMaxCost: { type: Number, default: 0 }, }, computed: { @@ -313,12 +314,13 @@ export default { color: colors.muted, maxTicksLimit: 6, }, + suggestedMax: this.suggestedMaxCost, min: 0, }, y1: { position: "left", border: { display: false }, - suggestedMax: this.suggestedMaxCost, + suggestedMax: this.suggestedMaxAvgCost, grid: { drawOnChartArea: false, }, diff --git a/assets/js/views/Sessions.vue b/assets/js/views/Sessions.vue index 6b74a777b..8e07ae841 100644 --- a/assets/js/views/Sessions.vue +++ b/assets/js/views/Sessions.vue @@ -96,6 +96,7 @@ :cost-type="activeType" :currency="currency" :period="period" + :suggested-max-avg-cost="suggestedMaxAvgCost" :suggested-max-cost="suggestedMaxCost" />
@@ -118,7 +119,7 @@ v-else :sessions="currentTypeSessions" :color-mappings="colorMappings" - :suggested-max-price="suggestedMaxCost" + :suggested-max-price="suggestedMaxAvgCost" :group-by="selectedGroup" :cost-type="activeType" :currency="currency" @@ -594,17 +595,42 @@ export default { return (isGrouped && hasMultipleEntries) || (isSolar && isNotMonth && !isGrouped); }, - suggestedMaxPrice() { - // returns the 90th percentile of all prices + suggestedMaxAvgPrice() { + // returns the 98th percentile of avg prices for all sessions const sessionsWithPrice = this.sessions.filter((s) => s.pricePerKWh !== null); const prices = sessionsWithPrice.map((s) => s.pricePerKWh); - return this.percentile(prices, 90); + return this.percentile(prices, 98); }, - suggestedMaxCo2() { - // returns the 90th percentile of all co2 emissions + suggestedMaxAvgCo2() { + // returns the 98th percentile of avg co2 emissions for all sessions const sessionsWithCo2 = this.sessions.filter((s) => s.co2PerKWh !== null); const co2 = sessionsWithCo2.map((s) => s.co2PerKWh); - return this.percentile(co2, 90); + return this.percentile(co2, 98); + }, + suggestedMaxAvgCost() { + return this.activeType === TYPES.PRICE + ? this.suggestedMaxAvgPrice + : this.suggestedMaxAvgCo2; + }, + suggestedMaxCo2() { + // returns the 98th percentile of total co2 emissions by time period + const sessionsWithCo2 = this.sessions.filter((s) => s.co2PerKWh !== null); + const co2Map = sessionsWithCo2.reduce((acc, s) => { + const key = this.dateToPeriodKey(new Date(s.created)); + acc[key] = (acc[key] || 0) + s.co2PerKWh * s.chargedEnergy; + return acc; + }, {}); + return Math.max(this.percentile(Object.values(co2Map), 98), 5); // 5kg default + }, + suggestedMaxPrice() { + // returns the 98th percentile of total price by time period + const sessionsWithPrice = this.sessions.filter((s) => s.price !== null); + const priceMap = sessionsWithPrice.reduce((acc, s) => { + const key = this.dateToPeriodKey(new Date(s.created)); + acc[key] = (acc[key] || 0) + s.price; + return acc; + }, {}); + return Math.max(this.percentile(Object.values(priceMap), 98), 1); // 1 CURRENCY default }, suggestedMaxCost() { return this.activeType === TYPES.PRICE ? this.suggestedMaxPrice : this.suggestedMaxCo2; @@ -636,6 +662,12 @@ export default { } this.$router.push({ query: { ...this.$route.query, period, month, year } }); }, + dateToPeriodKey(date) { + const options = { year: "numeric", month: "numeric", day: "numeric" }; + if (this.period === PERIODS.YEAR) options.day = undefined; + if (this.period === PERIODS.TOTAL) options.month = undefined; + return date.toLocaleDateString(undefined, options); + }, async loadSessions() { const response = await api.get("sessions"); // ensure sessions are sorted by created date