Newer
Older
// const https = require('https') //optional for ssl issue
const getAccountId = require('./helpers/getAccountId')
const moment = require('moment')
require('moment-timezone')
moment.locale('fr') // set the language
moment.tz.setDefault('Europe/Paris') // set the timezone
const startDate = moment()
.startOf('day')
.subtract(4, 'day')
.format('YYYY-MM-DD')
const endDate = moment().format('YYYY-MM-DD')
const rangeDate = {
day: {
doctype: 'com.grandlyon.grdf.day',
keys: ['year', 'month', 'day']
},
month: {
doctype: 'com.grandlyon.grdf.month',
keys: ['year', 'month']
},
year: {
doctype: 'com.grandlyon.grdf.year',
keys: ['year']
}
}
const client_id = 'metropole_de_lyon_grdf'
const redirectUrl = 'https://grdf.pchugo.wf.alpha.grandlyon.com'*/
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, cozyParameters, doRetry = true) {
// var grdfPCE = ''
log('debug', 'Starting grdf adict konnector')
// if (cozyParameters) log('info', 'CozyParameters found')
// const grdfToken = await authenticate(fields.login, fields.password)
const accountId = getAccountId()
try {
// const low_scope_access_token = fields.access_token // To use for /donnees-techniques -> fréquence du compteur
const high_scope_access_token = await authenticate(
fields.client_id,
fields.client_secret
)
// is account necessary ? maybe id_token is already in fields from the begining
log('debug', 'KONNECTOR FIELDS : ' + high_scope_access_token)
log(
'debug',
'KONNECTOR ACCOUNT : ' + this._account.oauth_callback_results.id_token
)
if (
this._account &&
this._account.oauth_callback_results &&
this._account.oauth_callback_results.id_token
) {
log('debug', 'time to decode jwt')
const id_pce = await getPCEid(
this._account.oauth_callback_results.id_token
) //19108248691849 PCEid FOR TESTING PURPOSE
log('debug', 'THE ID_PCE SENDING TO GETDATA : ' + id_pce)
const grdfData = await getData(high_scope_access_token, id_pce)
if (grdfData.consommation) {
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')
}
// await addData(data, "io.cozy.accounts", accountId);
// return start(fields, cozyParameters, true);
} 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) {
if (
err.statusCode === 403 ||
err.code === 403 ||
err.statusCode === 401 ||
err.code === 401
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
144
) {
if (doRetry) {
log('debug', 'asking refresh from the stack')
let body
try {
body = await cozyClient.fetchJSON(
'POST',
`/accounts/grdf-konnector/${accountId}/refresh`
)
} catch (err) {
log('debug', `Error during refresh ${err.message}`)
throw errors.USER_ACTION_NEEDED_OAUTH_OUTDATED
}
log('debug', 'refresh response')
log('debug', JSON.stringify(body))
fields.access_token = body.attributes.oauth.access_token
return start(fields, cozyParameters, false)
}
log('error', `Error during authentication: ${err.message}`)
throw errors.VENDOR_DOWN
} else {
log('error', 'caught an unexpected error')
log('error', err.message)
throw errors.VENDOR_DOWN
}
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
async function authenticate(client_id, client_secret) {
let myHeaders = new Headers()
myHeaders.append('Content-Type', 'application/x-www-form-urlencoded')
myHeaders.append('Authorization', 'Basic cG9jX2FwaTpwb2NfYXBp')
let urlencoded = new URLSearchParams()
urlencoded.append('grant_type', 'client_credentials')
urlencoded.append('client_id', client_id)
urlencoded.append('client_secret', client_secret)
urlencoded.append('scope', '/adict/v1')
let requestOptions = {
method: 'POST',
headers: myHeaders,
body: urlencoded,
redirect: 'follow'
}
const rep = await fetch(
'https://sofit-sso-oidc.grdf.fr/openam/oauth2/realms/externeGrdf/access_token',
requestOptions
)
.then(response => response.text())
.then(result => {
result = JSON.parse(result)
log('info', result)
return result.access_token
})
.catch(error => log('error', error))
return rep
}
async function getPCEid(tokenToDecrypt) {
// verify a token symmetric - synchronous
const decodedConsentements = JSON.parse(decoded.consentements)
return decodedConsentements[0].pce
}
var myHeaders = new Headers()
myHeaders.append('Content-Type', 'application/x-ndjson')
myHeaders.append('Authorization', 'Bearer ' + token)
var requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
}
var url =
'https://api.grdf.fr/adict/v1/pce/' +
idPCE +
'/donnees_consos_informatives?date_debut=' +
startDate +
'&date_fin=' +
endDate
}
/**
* 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 intervalData = data.consommation
const formatedData = await formateData(intervalData)
const dataArray = [formatedData]
const filteredData = await hydrateAndFilter(dataArray, doctype, {
keys: filterKeys
})
// Store new day data
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 await addData(filteredDocuments, doctype)
}
/**
* Format data for DB storage
* Remove bad data
*/
let date = moment(data.date_debut_consommation, 'YYYY/MM/DD h:mm:ss')
let load =
data.energie !== 0
? data.energie
: data.volume_brut * data.coeff_calcul.coeff_conversion
//TRAITEMENT DES MINUTES
// const load =
// doctype === 'com.grandlyon.grdf.minute'
// ? record.value / 2
// : record.value
// if (doctype === 'com.grandlyon.grdf.minute') {
// date = date.subtract(30, 'minute')
// }
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 load data (every 30 min) to Hourly data
*/
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
// Sum year and month values into object with year or year-month as keys
if (data && data.length > 0) {
let hourData = {}
data.forEach(element => {
let key =
element.year +
'-' +
element.month +
'-' +
element.day +
'-' +
element.hour
key in hourData
? (hourData[key] += element.load)
: (hourData[key] = element.load)
})
// Agregation for Month data
const agregatedMonthData = await buildAgregatedData(
hourData,
'com.grandlyon.grdf.hour'
)
await storeData(agregatedMonthData, 'com.grandlyon.grdf.hour', [
'year',
'month',
'day',
'hour'
])
}
/**
* Agregate data from daily data to monthly and yearly data
*/
async function agregateMonthAndYearData(data) {
//console.log('agregateMonthAndYearData')
//console.log(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)
: (yearData[element.year] = element.load)
})
//console.log(monthData)
//console.log(yearData)
// Agregation for Month data
const agregatedMonthData = await buildAgregatedData(
monthData,
//console.log('agregatedMonthData')
//console.log(agregatedMonthData)
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'])
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
}
}
/**
* 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
}