Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/app/Graph/DataBoxes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ const Consumed = function (props: { consumption: number; totalCost: number; dayC

const renderPeriod = (p: config.PeriodTypes) => {
switch (p) {
// case 'last-month':
// return <span>förra månaden</span>
case 'last-month':
return <span>förra månaden</span>
case 'this-month':
return <span>sedan den 1e i månaden</span>
case 'rolling':
Expand All @@ -138,17 +138,17 @@ const Consumed = function (props: { consumption: number; totalCost: number; dayC
switch (configState.periodType) {
case 'last-month':
p = 'rolling'
// if (new Date().getDate() === 1) {
// p = 'rolling'
// } else {
// p = 'this-month'
// }
if (new Date().getDate() === 1) {
p = 'rolling'
} else {
p = 'this-month'
}
break
case 'this-month':
p = 'rolling'
break
case 'rolling':
p = 'this-month'
p = 'last-month'
break
}
dispatch(config.setPeriod(p))
Expand Down
27 changes: 27 additions & 0 deletions src/app/Graph/GraphLoader.lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export interface Period {
from: Date
to: Date
hours: number
}

export function getMonthIntervalFor(month: number): Period {
const now = new Date()
now.setHours(0)
now.setMinutes(0)
now.setSeconds(0)
now.setMilliseconds(0)

// look at previous years numbers if month is ahead of current month
const year = month > now.getMonth() ? -1 : 0

const from = new Date(now)
from.setFullYear(now.getFullYear() + year, month, 1)

const to = new Date(from)
to.setFullYear(now.getFullYear() + year, month + 1, 1)

const ms = to.getTime() - from.getTime()
const hours = Math.floor(ms / 1000 / 60 / 60)

return { from, to, hours }
}
55 changes: 34 additions & 21 deletions src/app/Graph/GraphLoader.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useEffect, useState } from 'react'
import { match } from 'react-router'

import moment from 'moment'
import * as tibber from '../../lib/tibber'
import * as svk from '../../lib/svk/'
import * as dataprep from '../../lib/dataprep'
Expand All @@ -16,6 +15,7 @@ import { useDispatch, useSelector } from 'src/lib/hooks'
import { push } from 'connected-react-router'

import { DataSourceContext } from './Graphs'
import { getMonthIntervalFor } from './GraphLoader.lib'

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

let period: number
switch (configState.periodType) {
case 'last-month': {
const now = moment()
const start = moment().subtract(1, 'month').date(1).hour(0).minute(0).second(0)
const diff = moment.duration(now.diff(start))
period = Math.ceil(diff.as('hours'))
break
}
case 'this-month':
period = new Date().getDate() * 24
break
case 'rolling':
period = period = 32 * 24
break
}

useEffect(() => {
dispatch(snapshotStore.reset())
}, [dispatch])

useEffect(() => {
dispatch(tibber.getConsumption({ homeId, interval: tibber.Interval.Hourly, last: period }))
let first: number | undefined = undefined
let last: number | undefined = undefined
let after: Date | undefined = undefined
const now = new Date()
switch (configState.periodType) {
case 'last-month': {
let prevMonth = now.getMonth() - 1
if (prevMonth < 0) prevMonth = 11

const period = getMonthIntervalFor(prevMonth)
after = period.from
first = period.hours
break
}
case 'this-month': {
const period = getMonthIntervalFor(now.getMonth())
after = period.from
first = period.hours
break
}
case 'rolling':
last = last = 32 * 24
break
}

console.log({ after, last, first })

dispatch(
tibber.getConsumption({ homeId, resolution: tibber.Interval.Hourly, after, first, last })
)

// price is sometimes ahead by 24 hours, so we always add another period on it
dispatch(tibber.getPrice({ homeId, interval: tibber.Interval.Hourly, last: period + 24 }))
dispatch(tibber.getPrice({ homeId, resolution: tibber.Interval.Hourly, after, first, last }))

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

setFirstLoad(false)
}, [dispatch, homeId, period, configState.periodType, gridAreaCode])
}, [dispatch, homeId, configState.periodType, gridAreaCode])

const store = async () => {
dispatch(
Expand Down
2 changes: 0 additions & 2 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ const getInitialState = (): State => {

switch (savedPeriod) {
case 'last-month':
periodType = 'rolling'
break
case 'this-month':
case 'rolling':
periodType = savedPeriod
Expand Down
2 changes: 1 addition & 1 deletion src/lib/dataprep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export function aggregateDays(
const prices = hours.map((hour) => hour.price)
const profiles = hours.map((hour) => hour.profile)

// Skip days where we are missing data. Most likley close to our boundaries.
// Skip days where we are missing data. Most likely close to our boundaries.
let valid = true
for (let i = 0; i < consumptions.length; i++) {
if (!consumptions[i] || !prices[i] || !profiles[i]) {
Expand Down
103 changes: 75 additions & 28 deletions src/lib/tibber/thunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export const getHomes = createAsyncThunk<Home[], void>('tibber/getHomes', async
return result.viewer.homes
})

interface ConsumptionArgs extends RangeOptions {
homeId: string
}

interface ConsumptionResult {
viewer: {
home: {
Expand All @@ -46,18 +50,13 @@ interface ConsumptionResult {
}
}

export const getConsumption = createAsyncThunk<
ConsumptionNode[],
{
homeId: string
interval: Interval
last?: number
}
>('tibber/getConsumption', async (args) => {
const result = await doRequest<ConsumptionResult>(`{
export const getConsumption = createAsyncThunk<ConsumptionNode[], ConsumptionArgs>(
'tibber/getConsumption',
async (args) => {
const result = await doRequest<ConsumptionResult>(`{
viewer {
home(id: "${args.homeId}") {
consumption(resolution: ${args.interval}, last: ${args.last || 100}) {
consumption(${rangeParameters(args)}) {
nodes {
from
to
Expand All @@ -69,11 +68,16 @@ export const getConsumption = createAsyncThunk<
}
}`)

if (!result.viewer.home.consumption) {
throw new Error('missing consumption data')
if (!result.viewer.home.consumption) {
throw new Error('missing consumption data')
}
return result.viewer.home.consumption.nodes
}
return result.viewer.home.consumption.nodes
})
)

interface PriceArgs extends RangeOptions {
homeId: string
}

interface PriceResult {
viewer: {
Expand All @@ -89,20 +93,15 @@ interface PriceResult {
}
}

export const getPrice = createAsyncThunk<
PriceNode[],
{
homeId: string
interval: Interval
last?: number
}
>('tibber/getPrice', async (args) => {
const result = await doRequest<PriceResult>(`{
export const getPrice = createAsyncThunk<PriceNode[], PriceArgs>(
'tibber/getPrice',
async (args) => {
const result = await doRequest<PriceResult>(`{
viewer {
home(id: "${args.homeId}") {
currentSubscription{
priceInfo{
range(resolution: ${args.interval}, last: ${args.last || 100}){
range(${rangeParameters(args)}){
nodes{
startsAt,
total,
Expand All @@ -113,11 +112,12 @@ export const getPrice = createAsyncThunk<
}
}
}`)
if (!result.viewer.home.currentSubscription.priceInfo.range) {
throw new Error('no price data found in range')
if (!result.viewer.home.currentSubscription.priceInfo.range) {
throw new Error('no price data found in range')
}
return result.viewer.home.currentSubscription.priceInfo.range.nodes
}
return result.viewer.home.currentSubscription.priceInfo.range.nodes
})
)

async function doRequest<T>(query: string) {
const init: RequestInit = {
Expand All @@ -144,3 +144,50 @@ async function doRequest<T>(query: string) {
interface GQLResponse<T = any> {
data: T
}

interface RangeOptions {
resolution: Interval

after?: Date
before?: Date
first?: number
last?: number
}

function rangeParameters(args: RangeOptions): string {
if (args.after && args.before) throw new Error('invalid combination: before && after')
if (args.first && args.last) throw new Error('invalid combination: last && first')

return Object.entries(args)
.filter(([name, value]) => {
if (value === undefined) return false
// Allowlist with the args we are using as options
return ['resolution', 'after', 'before', 'first', 'last'].indexOf(name) !== -1
})
.map<[string, string]>(([name, value]) => {
if (value instanceof Date) {
// Before / after on a hourly resolution is > and <, not >= and <=.
// Decrease the timestamp by a millisecond to include the first or last hour we are looking for.
switch (name) {
case 'after':
value = new Date(value.getTime() - 1)
break
case 'before':
value = new Date(value.getTime() + 1)
break
}

return [name, `"${btoa(value.toISOString())}"`]
} else if (typeof value === 'number') {
return [name, value.toFixed(0)]
} else {
value = '' + value
}

return [name, value]
})
.map(([name, value]) => {
return `${name}: ${value}`
})
.join(', ')
}