Skip to content

Commit 2d3527d

Browse files
committed
Re-enable last month feature
1 parent 6f7fe6f commit 2d3527d

File tree

6 files changed

+145
-60
lines changed

6 files changed

+145
-60
lines changed

src/app/Graph/DataBoxes.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ const Consumed = function (props: { consumption: number; totalCost: number; dayC
120120

121121
const renderPeriod = (p: config.PeriodTypes) => {
122122
switch (p) {
123-
// case 'last-month':
124-
// return <span>förra månaden</span>
123+
case 'last-month':
124+
return <span>förra månaden</span>
125125
case 'this-month':
126126
return <span>sedan den 1e i månaden</span>
127127
case 'rolling':
@@ -138,17 +138,17 @@ const Consumed = function (props: { consumption: number; totalCost: number; dayC
138138
switch (configState.periodType) {
139139
case 'last-month':
140140
p = 'rolling'
141-
// if (new Date().getDate() === 1) {
142-
// p = 'rolling'
143-
// } else {
144-
// p = 'this-month'
145-
// }
141+
if (new Date().getDate() === 1) {
142+
p = 'rolling'
143+
} else {
144+
p = 'this-month'
145+
}
146146
break
147147
case 'this-month':
148148
p = 'rolling'
149149
break
150150
case 'rolling':
151-
p = 'this-month'
151+
p = 'last-month'
152152
break
153153
}
154154
dispatch(config.setPeriod(p))

src/app/Graph/GraphLoader.lib.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export interface Period {
2+
from: Date
3+
to: Date
4+
hours: number
5+
}
6+
7+
export function getMonthIntervalFor(month: number): Period {
8+
const now = new Date()
9+
now.setHours(0)
10+
now.setMinutes(0)
11+
now.setSeconds(0)
12+
now.setMilliseconds(0)
13+
14+
// look at previous years numbers if month is ahead of current month
15+
const year = month > now.getMonth() ? -1 : 0
16+
17+
const from = new Date(now)
18+
from.setFullYear(now.getFullYear() + year, month, 1)
19+
20+
const to = new Date(from)
21+
to.setFullYear(now.getFullYear() + year, month + 1, 1)
22+
23+
const ms = to.getTime() - from.getTime()
24+
const hours = Math.floor(ms / 1000 / 60 / 60)
25+
26+
return { from, to, hours }
27+
}

src/app/Graph/GraphLoader.tsx

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useEffect, useState } from 'react'
22
import { match } from 'react-router'
33

4-
import moment from 'moment'
54
import * as tibber from '../../lib/tibber'
65
import * as svk from '../../lib/svk/'
76
import * as dataprep from '../../lib/dataprep'
@@ -16,6 +15,7 @@ import { useDispatch, useSelector } from 'src/lib/hooks'
1615
import { push } from 'connected-react-router'
1716

1817
import { DataSourceContext } from './Graphs'
18+
import { getMonthIntervalFor } from './GraphLoader.lib'
1919

2020
type Params = {
2121
id: string
@@ -39,36 +39,49 @@ export default function GraphLoader(props: Props) {
3939
const { gridAreaCode, priceAreaCode } = props.match.params
4040
const homeId = props.match.params.id
4141

42-
let period: number
43-
switch (configState.periodType) {
44-
case 'last-month': {
45-
const now = moment()
46-
const start = moment().subtract(1, 'month').date(1).hour(0).minute(0).second(0)
47-
const diff = moment.duration(now.diff(start))
48-
period = Math.ceil(diff.as('hours'))
49-
break
50-
}
51-
case 'this-month':
52-
period = new Date().getDate() * 24
53-
break
54-
case 'rolling':
55-
period = period = 32 * 24
56-
break
57-
}
58-
5942
useEffect(() => {
6043
dispatch(snapshotStore.reset())
6144
}, [dispatch])
6245

6346
useEffect(() => {
64-
dispatch(tibber.getConsumption({ homeId, interval: tibber.Interval.Hourly, last: period }))
47+
let first: number | undefined = undefined
48+
let last: number | undefined = undefined
49+
let after: Date | undefined = undefined
50+
const now = new Date()
51+
switch (configState.periodType) {
52+
case 'last-month': {
53+
let prevMonth = now.getMonth() - 1
54+
if (prevMonth < 0) prevMonth = 11
55+
56+
const period = getMonthIntervalFor(prevMonth)
57+
after = period.from
58+
first = period.hours
59+
break
60+
}
61+
case 'this-month': {
62+
const period = getMonthIntervalFor(now.getMonth())
63+
after = period.from
64+
first = period.hours
65+
break
66+
}
67+
case 'rolling':
68+
last = last = 32 * 24
69+
break
70+
}
71+
72+
console.log({ after, last, first })
73+
74+
dispatch(
75+
tibber.getConsumption({ homeId, resolution: tibber.Interval.Hourly, after, first, last })
76+
)
77+
6578
// price is sometimes ahead by 24 hours, so we always add another period on it
66-
dispatch(tibber.getPrice({ homeId, interval: tibber.Interval.Hourly, last: period + 24 }))
79+
dispatch(tibber.getPrice({ homeId, resolution: tibber.Interval.Hourly, after, first, last }))
6780

6881
dispatch(svk.getProfile({ area: gridAreaCode, period: configState.periodType }))
6982

7083
setFirstLoad(false)
71-
}, [dispatch, homeId, period, configState.periodType, gridAreaCode])
84+
}, [dispatch, homeId, configState.periodType, gridAreaCode])
7285

7386
const store = async () => {
7487
dispatch(

src/lib/config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ const getInitialState = (): State => {
1515

1616
switch (savedPeriod) {
1717
case 'last-month':
18-
periodType = 'rolling'
19-
break
2018
case 'this-month':
2119
case 'rolling':
2220
periodType = savedPeriod

src/lib/dataprep.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export function aggregateDays(
100100
const prices = hours.map((hour) => hour.price)
101101
const profiles = hours.map((hour) => hour.profile)
102102

103-
// Skip days where we are missing data. Most likley close to our boundaries.
103+
// Skip days where we are missing data. Most likely close to our boundaries.
104104
let valid = true
105105
for (let i = 0; i < consumptions.length; i++) {
106106
if (!consumptions[i] || !prices[i] || !profiles[i]) {

src/lib/tibber/thunks.ts

Lines changed: 75 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export const getHomes = createAsyncThunk<Home[], void>('tibber/getHomes', async
3636
return result.viewer.homes
3737
})
3838

39+
interface ConsumptionArgs extends RangeOptions {
40+
homeId: string
41+
}
42+
3943
interface ConsumptionResult {
4044
viewer: {
4145
home: {
@@ -46,18 +50,13 @@ interface ConsumptionResult {
4650
}
4751
}
4852

49-
export const getConsumption = createAsyncThunk<
50-
ConsumptionNode[],
51-
{
52-
homeId: string
53-
interval: Interval
54-
last?: number
55-
}
56-
>('tibber/getConsumption', async (args) => {
57-
const result = await doRequest<ConsumptionResult>(`{
53+
export const getConsumption = createAsyncThunk<ConsumptionNode[], ConsumptionArgs>(
54+
'tibber/getConsumption',
55+
async (args) => {
56+
const result = await doRequest<ConsumptionResult>(`{
5857
viewer {
5958
home(id: "${args.homeId}") {
60-
consumption(resolution: ${args.interval}, last: ${args.last || 100}) {
59+
consumption(${rangeParameters(args)}) {
6160
nodes {
6261
from
6362
to
@@ -69,11 +68,16 @@ export const getConsumption = createAsyncThunk<
6968
}
7069
}`)
7170

72-
if (!result.viewer.home.consumption) {
73-
throw new Error('missing consumption data')
71+
if (!result.viewer.home.consumption) {
72+
throw new Error('missing consumption data')
73+
}
74+
return result.viewer.home.consumption.nodes
7475
}
75-
return result.viewer.home.consumption.nodes
76-
})
76+
)
77+
78+
interface PriceArgs extends RangeOptions {
79+
homeId: string
80+
}
7781

7882
interface PriceResult {
7983
viewer: {
@@ -89,20 +93,15 @@ interface PriceResult {
8993
}
9094
}
9195

92-
export const getPrice = createAsyncThunk<
93-
PriceNode[],
94-
{
95-
homeId: string
96-
interval: Interval
97-
last?: number
98-
}
99-
>('tibber/getPrice', async (args) => {
100-
const result = await doRequest<PriceResult>(`{
96+
export const getPrice = createAsyncThunk<PriceNode[], PriceArgs>(
97+
'tibber/getPrice',
98+
async (args) => {
99+
const result = await doRequest<PriceResult>(`{
101100
viewer {
102101
home(id: "${args.homeId}") {
103102
currentSubscription{
104103
priceInfo{
105-
range(resolution: ${args.interval}, last: ${args.last || 100}){
104+
range(${rangeParameters(args)}){
106105
nodes{
107106
startsAt,
108107
total,
@@ -113,11 +112,12 @@ export const getPrice = createAsyncThunk<
113112
}
114113
}
115114
}`)
116-
if (!result.viewer.home.currentSubscription.priceInfo.range) {
117-
throw new Error('no price data found in range')
115+
if (!result.viewer.home.currentSubscription.priceInfo.range) {
116+
throw new Error('no price data found in range')
117+
}
118+
return result.viewer.home.currentSubscription.priceInfo.range.nodes
118119
}
119-
return result.viewer.home.currentSubscription.priceInfo.range.nodes
120-
})
120+
)
121121

122122
async function doRequest<T>(query: string) {
123123
const init: RequestInit = {
@@ -144,3 +144,50 @@ async function doRequest<T>(query: string) {
144144
interface GQLResponse<T = any> {
145145
data: T
146146
}
147+
148+
interface RangeOptions {
149+
resolution: Interval
150+
151+
after?: Date
152+
before?: Date
153+
first?: number
154+
last?: number
155+
}
156+
157+
function rangeParameters(args: RangeOptions): string {
158+
if (args.after && args.before) throw new Error('invalid combination: before && after')
159+
if (args.first && args.last) throw new Error('invalid combination: last && first')
160+
161+
return Object.entries(args)
162+
.filter(([name, value]) => {
163+
if (value === undefined) return false
164+
// Allowlist with the args we are using as options
165+
return ['resolution', 'after', 'before', 'first', 'last'].indexOf(name) !== -1
166+
})
167+
.map<[string, string]>(([name, value]) => {
168+
if (value instanceof Date) {
169+
// Before / after on a hourly resolution is > and <, not >= and <=.
170+
// Decrease the timestamp by a millisecond to include the first or last hour we are looking for.
171+
switch (name) {
172+
case 'after':
173+
value = new Date(value.getTime() - 1)
174+
break
175+
case 'before':
176+
value = new Date(value.getTime() + 1)
177+
break
178+
}
179+
180+
return [name, `"${btoa(value.toISOString())}"`]
181+
} else if (typeof value === 'number') {
182+
return [name, value.toFixed(0)]
183+
} else {
184+
value = '' + value
185+
}
186+
187+
return [name, value]
188+
})
189+
.map(([name, value]) => {
190+
return `${name}: ${value}`
191+
})
192+
.join(', ')
193+
}

0 commit comments

Comments
 (0)