Newer
Older
const isVendorDown = require('./helpers/isVendorDown')
const moment = require('moment')
require('moment-timezone')
moment.locale('fr') // set the language
moment.tz.setDefault('Europe/Paris') // set the timezone
const manualExecution =
process.env.COZY_JOB_MANUAL_EXECUTION === 'true' ? true : false
const startDate = manualExecution
? moment()
.subtract(1, 'year')
.format('YYYY-MM-DD')
: moment()
.subtract(3, 'year')
.format('YYYY-MM-DD')
module.exports = new BaseKonnector(start)
// The start function is run by the BaseKonnector instance only when it got all the account
// information (fields). When you run this connector yourself in "standalone" mode or "dev" mode,
// the account information come from ./konnector-dev-config.json file
// cozyParameters are static parameters, independents from the account. Most often, it can be a
// secret api key.
async function start(fields) {
log('debug', 'Starting grdf adict konnector')
const accountId = getAccountId()
let body = ''
body = await cozyClient.fetchJSON(
'POST',
`/accounts/grdfgrandlyon/${accountId}/refresh`
fields.access_token = body.attributes.oauth.access_token
if (
this._account &&
this._account.oauth_callback_results &&
this._account.oauth_callback_results.pce &&
fields.access_token
const grdfData = await getData(fields.access_token, id_pce)
if (grdfData) {
log('debug', 'Process grdf daily data')
const processedLoadData = await processData(
grdfData,
'com.grandlyon.grdf.day',
['year', 'month', 'day']
)
log('debug', 'Agregate grdf load data for month and year')
await agregateMonthAndYearData(processedLoadData)
} else {
log('debug', 'No consent or data for load curve')
}
} else {
log('debug', 'no id_token found in oauth_callback_results')
log(
'debug',
'callback_result contains: ',
this._account.oauth_callback_results
)
throw errors.USER_ACTION_NEEDED_OAUTH_OUTDATED
}
} catch (err) {
log('debug', 'CATCH ERROR : ' + err)
var myHeaders = new Headers()
myHeaders.append('Content-Type', 'application/x-ndjson')
myHeaders.append('Authorization', 'Bearer ' + token)
var requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
'https://api.grdf.fr/adict/v2/pce/' +
'/donnees_consos_informatives?date_debut=' +
startDate +
'&date_fin=' +
endDate
.then(async response => {
if (response.status !== 200) {
throw new Error(response.status + ' - ' + response.statusText)
}
return response.text()
return result.match(/.+/g).map(s => {
result = JSON.parse(s)
'GET DATA THREW AN ERROR : ' +
result.statut_restitution.code +
' -> ' +
' Period: ' +
result.periode.date_debut +
'/' +
result.periode.date_fin
)
/**
* Handle no data issue when retreving grdf data.
* 1000008 code stands for "Il n'y a pas de données correspondant à ce PCE sur la période demandée".
* If there is no data, return null data in order to be filtered before saving
*/
if (result.statut_restitution.code !== '1000008') {
if (isVendorDown(result.statut_restitution.code)) {
throw errors.VENDOR_DOWN
} else {
throw errors.USER_ACTION_NEEDED_OAUTH_OUTDATED
}
} else {
return { energie: null }
return result.consommation
})
})
.catch(error => {
log('debug', 'Error from getData')
throw error
const filteredRep = rep.filter(function(el) {
}
/**
* Parse data
* Remove existing data from DB using hydrateAndFilter
* Store filtered data
* Return the list of filtered data
*/
async function processData(data, doctype, filterKeys) {
const formatedData = await formateData(data)
log('debug', 'processData - data formated')
const filteredData = await hydrateAndFilter(formatedData, doctype, {
await storeData(filteredData, doctype, filterKeys)
return filteredData
}
/**
* Save data in the right doctype db and prevent duplicated keys
*/
async function storeData(data, doctype, filterKeys) {
log('debug', doctype, 'Store into')
const filteredDocuments = await hydrateAndFilter(data, doctype, {
keys: filterKeys
})
return addData(filteredDocuments, doctype)
}
/**
* Format data for DB storage
* Remove bad data
*/
return data.map(record => {
let date = moment(record.date_debut_consommation, 'YYYY/MM/DD h:mm:ss')
let load =
: record.volume_brut * record.coeff_calcul.coeff_conversion
return {
load: parseFloat(load),
year: parseInt(date.format('YYYY')),
month: parseInt(date.format('M')),
day: parseInt(date.format('D')),
hour: parseInt(date.format('H')),
minute: parseInt(date.format('m'))
}
})
}
/**
* Agregate data from daily data to monthly and yearly data
*/
async function agregateMonthAndYearData(data) {
// Sum year and month values into object with year or year-month as keys
data.forEach(element => {
element.year + '-' + element.month in monthData
? (monthData[element.year + '-' + element.month] += element.load)
: (monthData[element.year + '-' + element.month] = element.load)
element.year in yearData
: (yearData[element.year] = element.load)
})
// Agregation for Month data
const agregatedMonthData = await buildAgregatedData(
monthData,
await storeData(agregatedMonthData, 'com.grandlyon.grdf.month', [
'year',
'month'
])
// Agregation for Year data
const agregatedYearData = await buildAgregatedData(
yearData,
await storeData(agregatedYearData, 'com.grandlyon.grdf.year', ['year'])
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
}
}
/**
* Retrieve and remove old data for a specific doctype
* Return an Array of agregated data
*/
async function buildAgregatedData(data, doctype) {
let agregatedData = []
for (let [key, value] of Object.entries(data)) {
const data = await buildDataFromKey(doctype, key, value)
const oldValue = await resetInProgressAggregatedData(data, doctype)
data.load += oldValue
agregatedData.push(data)
}
return agregatedData
}
/**
* Format an entry for DB storage
* using key and value
* For year doctype: key = "YYYY"
* For month doctype: key = "YYYY-MM"
*/
async function buildDataFromKey(doctype, key, value) {
let year, month, day, hour
if (doctype === 'com.grandlyon.grdf.year') {
year = key
month = 1
day = 0
hour = 0
} else if (doctype === 'com.grandlyon.grdf.month') {
const split = key.split('-')
year = split[0]
month = split[1]
day = 0
hour = 0
} else {
const split = key.split('-')
year = split[0]
month = split[1]
day = split[2]
hour = split[3]
}
return {
load: Math.round(value * 10000) / 10000,
year: parseInt(year),
month: parseInt(month),
day: parseInt(day),
hour: parseInt(hour),
minute: 0
}
}
/**
* Function handling special case.
* The temporary aggregated data need to be remove in order for the most recent one te be saved.
* ex for com.grandlyon.grdf.year :
* { load: 76.712, year: 2020, ... } need to be replace by
* { load: 82.212, year: 2020, ... } after grdf data reprocess
*/
async function resetInProgressAggregatedData(data, doctype) {
// /!\ Warning: cannot use mongo queries because not supported for dev by cozy-konnectors-libs
log('debug', doctype, 'Remove aggregated data for')
const result = await cozyClient.data.findAll(doctype)
if (result && result.length > 0) {
// Filter data to remove
var filtered = []
if (doctype === 'com.grandlyon.grdf.year') {
// Yearly case
filtered = result.filter(function(el) {
return el.year == data.year
})
} else if (doctype === 'com.grandlyon.grdf.month') {
// Monthly case
filtered = result.filter(function(el) {
return el.year == data.year && el.month == data.month
})
} else {
// Hourly case
filtered = result.filter(function(el) {
return (
el.year == data.year &&
el.month == data.month &&
el.day == data.day &&
el.hour == data.hour
)
})
}
// Remove data
let sum = 0.0
for (const doc of filtered) {
sum += doc.load
log('debug', doc, 'Removing this entry for ' + doctype)
await cozyClient.data.delete(doctype, doc)
}
return sum
}
return 0.0
}