-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoctopus.ts
143 lines (131 loc) · 4.57 KB
/
octopus.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
type AccountData = {
properties: {
electricity_meter_points: {
is_export: boolean
mpan: string
meters: { serial_number: string }[]
}[]
}[]
}
type MeterData = {
results: {
consumption: number
interval_start: string
interval_end: string
}[]
}
export type Consumption = { [serialNumber: string]: { periodFrom: Date; periodTo: Date; value: number }[] }
type Tariffs = {
results: {
display_name: string
brand: string
links: { rel: string; href: string }[]
}[]
}
type TariffData = {
single_register_electricity_tariffs: {
[region: string]: { direct_debit_monthly: { links: { rel: string; href: string }[] } }
}
}
type RegionUnitRates = {
results: {
valid_from: string
value_inc_vat: number
}[]
}
export type Rate = { periodFrom: Date; price: number }
const apiCache: { [key: string]: string } = {}
const apiRoot = 'https://api.octopus.energy/v1/'
const getPeriodTo = (periodFrom: Date) => {
const periodTo = new Date(periodFrom)
periodTo.setDate(periodTo.getDate() + 1)
return periodTo
}
const getData = async <T>(path: string, token?: string): Promise<T> => {
const headers: HeadersInit = { 'Content-Type': 'application/json' }
if (token) {
headers.Authorization = `Basic ${btoa(token)}`
}
const cacheKey = `${path} ${JSON.stringify(headers)}`
if (apiCache[cacheKey]) {
return JSON.parse(apiCache[cacheKey]) as T
}
const response = await fetch(path, { headers: headers })
if (response.ok) {
if (Object.keys(apiCache).length >= 10) {
delete apiCache[Object.keys(apiCache)[0]]
}
const data = await response.json()
apiCache[cacheKey] = JSON.stringify(data)
return data as T
}
throw new Error(response.statusText)
}
export const getAccountData = async (account: string, token: string) =>
await getData<AccountData>(`${apiRoot}accounts/${account}/`, token)
export const getMeterConsumption = async (account: string, token: string, periodFrom: Date) => {
const periodTo = getPeriodTo(periodFrom)
const accountData = await getAccountData(account, token)
if (!accountData) return null
const meterPoints = accountData.properties
.flatMap((property) => property.electricity_meter_points)
.filter((point) => !point.is_export)
const meterConsumption: Consumption = {}
const meters = meterPoints.flatMap((point) =>
point.meters.map((meter) => ({ mpan: point.mpan, serialNumber: meter.serial_number }))
)
await Promise.all(
meters.map(async ({ mpan, serialNumber }) => {
const meterData = await getData<MeterData>(
`${apiRoot}electricity-meter-points/${mpan}/meters/${serialNumber}/consumption?period_from=${periodFrom.toISOString()}&period_to=${periodTo.toISOString()}`,
token
)
if (meterData && meterData.results.length) {
meterConsumption[serialNumber] = []
for (const { consumption, interval_start, interval_end } of meterData.results) {
meterConsumption[serialNumber].push({
periodFrom: new Date(interval_start),
periodTo: new Date(interval_end),
value: consumption
})
}
meterConsumption[serialNumber] = meterConsumption[serialNumber]
.sort(
({ periodFrom: periodFrom1 }, { periodFrom: periodFrom2 }) => periodFrom1.getTime() - periodFrom2.getTime()
)
.slice(0, 48)
}
})
)
return Object.keys(meterConsumption).length ? meterConsumption : null
}
export const getRates = async (region: string, periodFrom: Date) => {
const periodTo = getPeriodTo(periodFrom)
const tariffs = await getData<Tariffs>(`${apiRoot}products/`)
const agileTariff = tariffs.results.find(
(tariff) => tariff.display_name === 'Agile Octopus' && tariff.brand === 'OCTOPUS_ENERGY'
)
const agileTariffDataHref = agileTariff?.links.find((link) => link.rel === 'self')?.href
if (!agileTariffDataHref) {
return []
}
const tariffData = await getData<TariffData>(agileTariffDataHref)
const regionTariff = tariffData.single_register_electricity_tariffs[`_${region}`]
if (!regionTariff) {
return []
}
const regionUnitRatesHref =
regionTariff.direct_debit_monthly.links.find((link) => link.rel === 'standard_unit_rates')?.href +
`?period_from=${periodFrom.toISOString()}&period_to=${periodTo.toISOString()}`
if (!regionUnitRatesHref) {
return []
}
const regionUnitRates = await getData<RegionUnitRates>(regionUnitRatesHref)
return regionUnitRates.results.reverse().map(
({ valid_from, value_inc_vat }) =>
({
periodFrom: new Date(valid_from),
price: value_inc_vat
} as Rate)
)
}