Newer
Older
const soapRequest = require('easy-soap-request')
const moment = require('moment')
require('moment-timezone')
const xml2js = require('xml2js')

Bastien DUMONT
committed
const { buildAgregatedData } = require('./helpers/aggregate')
const {
parseSgeXmlData,
formateDataForDoctype,
parseTags,
parseValue,

Bastien DUMONT
committed
} = require('./helpers/parsing')
consultationMesuresDetailleesMaxPower,
consultationMesuresDetaillees,
} = require('./requests/sge')
const {
updateBoConsent,
createBoConsent,
getBoConsent,
deleteBoConsent,

Bastien DUMONT
committed
const {
verifyUserIdentity,
activateContract,
verifyContract,
terminateContract,
getContractStartDate,
} = require('./core')
const { getAccount, saveAccountData } = require('./requests/cozy')
const { isLocal } = require('./helpers/env')
moment.locale('fr') // set the language
moment.tz.setDefault('Europe/Paris') // set the timezone
/*** Connector Constants ***/
const manualExecution =
process.env.COZY_JOB_MANUAL_EXECUTION === 'true' ? true : false
? moment().subtract(12, 'month')
: moment().subtract(6, 'month')
let startDailyDateString = startDailyDate.format('YYYY-MM-DD')
const startLoadDate = moment().subtract(7, 'day')
const endDate = moment()
const endDateString = endDate.format('YYYY-MM-DD')

Bastien DUMONT
committed
const ACCOUNT_ID = isLocal() ? 'default_account_id' : 'enedis-sge-grandlyon'

Bastien DUMONT
committed
/**
* 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.
* @param {fields} fields
* @param {{secret: fields}} cozyParameters
*/

Bastien DUMONT
committed
log('info', 'Konnector configuration ...')
log('info', `isManual exectuion: ${manualExecution}`)

Bastien DUMONT
committed
const pointId = parseInt(fields.pointId)
let baseUrl = fields.wso2BaseUrl
let apiAuthKey = fields.apiToken

Bastien DUMONT
committed
let contractId = fields.contractId
let sgeLogin = fields.sgeLogin
let boToken = fields.boToken
let boBaseUrl = fields.boBaseUrl
if (cozyParameters && Object.keys(cozyParameters).length !== 0) {
log('debug', 'Found COZY_PARAMETERS')
baseUrl = cozyParameters.secret.wso2BaseUrl
apiAuthKey = cozyParameters.secret.apiToken

Bastien DUMONT
committed
contractId = cozyParameters.secret.contractId
sgeLogin = cozyParameters.secret.sgeLogin
boBaseUrl = cozyParameters.secret.boBaseUrl
boToken = cozyParameters.secret.boToken
}
// Prevent missing configuration
if (
!baseUrl ||
!apiAuthKey ||
!contractId ||
!sgeLogin ||
!boToken ||
!boBaseUrl
) {
log('error', `Missing configuration secrets`)
throw errors.VENDOR_DOWN
/**
* If it's first start we have to do the following operations:
* - verify pdl are matching
* - BO: create backoffice consent
* - get contract start date and store it
* - activate half-hour
* - BO: update consent with service ID
*/
log('info', 'User Logging...')

Bastien DUMONT
committed
if (isFirstStart(await getAccount(ACCOUNT_ID))) {
log('info', 'First start...')

Bastien DUMONT
committed
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
const user = await verifyUserIdentity(fields, baseUrl, apiAuthKey, sgeLogin)
let consent = await createBoConsent(
boBaseUrl,
boToken,
pointId,
user.lastname,
user.firstname,
user.address,
user.postalCode,
user.inseeCode
)
// handle user contract start date in order to preperly request data
const userContractstartDate = await getContractStartDate(
baseUrl,
apiAuthKey,
sgeLogin,
pointId
)
startDailyDate = moment(userContractstartDate, 'YYYY-MM-DD')
startDailyDateString = startDailyDate.format('YYYY-MM-DD')
const contractStartDate = moment().format('YYYY-MM-DD')
const contractEndDate = moment()
.add(1, 'year') // SGE force 1 year duration
.format('YYYY-MM-DD')
let serviceId = await verifyContract(
baseUrl,
apiAuthKey,
sgeLogin,
contractId,
user.pointId
)
if (!serviceId) {
serviceId = await activateContract(
baseUrl,
apiAuthKey,
sgeLogin,
contractId,
user.lastname,
user.pointId,
contractStartDate,
contractEndDate
)
}
consent = await updateBoConsent(
boBaseUrl,
boToken,
consent,
serviceId.toString()
)
// Save bo id into account
const accountData = await getAccount(ACCOUNT_ID)
await saveAccountData(this.accountId, {
...accountData.data,
consentId: consent.ID,
inseeCode: user.inseeCode,

Bastien DUMONT
committed
})
log('info', 'Alternate start...')

Bastien DUMONT
committed
const accountData = await getAccount(ACCOUNT_ID)
const userConsent = await getBoConsent(
boBaseUrl,
boToken,
accountData.data.consentId
)
const user = await verifyUserIdentity(
fields,
baseUrl,
apiAuthKey,
sgeLogin,
true,
accountData.data.inseeCode

Bastien DUMONT
committed
)
if (!userConsent) {
log('error', 'No user consent found')
throw errors.VENDOR_DOWN
}
const consentEndDate = Date.parse(userConsent.endDate)
const today = Date.now()
if (
user.lastname.toLocaleUpperCase() !==
userConsent.lastname.toLocaleUpperCase() ||
!user ||
consentEndDate < today
) {
await deleteConsent(
userConsent,
baseUrl,
apiAuthKey,
sgeLogin,
contractId,
pointId,
boBaseUrl,
boToken
)
throw errors.USER_ACTION_NEEDED_OAUTH_OUTDATED

Bastien DUMONT
committed
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
await gatherData(baseUrl, apiAuthKey, sgeLogin, pointId)
}
/**
* Delete User Consent
* @param {Consent} userConsent
* @param {string} baseUrl
* @param {string} apiAuthKey
* @param {string} sgeLogin
* @param {string} contractId
* @param {number} pointId
* @param {string} boBaseUrl
* @param {string} boToken
*/
async function deleteConsent(
userConsent,
baseUrl,
apiAuthKey,
sgeLogin,
contractId,
pointId,
boBaseUrl,
boToken
) {
log('error', `Invalid or not found consent for user`)
if (userConsent.serviceID) {
await terminateContract(
baseUrl,
apiAuthKey,
sgeLogin,
contractId,
pointId,
userConsent.serviceID
)
await deleteBoConsent(boBaseUrl, boToken, userConsent.ID || 0)
} else {
log('error', `No service id retrieved from BO`)
throw errors.VENDOR_DOWN
}
throw errors.TERMS_VERSION_MISMATCH
}
/**
* Main method for gathering data
* @param {string} baseUrl
* @param {string} apiAuthKey

Bastien DUMONT
committed
* @param {string} sgeLogin

Bastien DUMONT
committed
async function gatherData(baseUrl, apiAuthKey, sgeLogin, pointId) {

Bastien DUMONT
committed
await getContractStartDate(baseUrl, apiAuthKey, sgeLogin, pointId)
await getData(
`${baseUrl}/enedis_SGE_ConsultationMesuresDetaillees/1.0`,
apiAuthKey,

Bastien DUMONT
committed
sgeLogin,
await getMaxPowerData(
`${baseUrl}/enedis_SGE_ConsultationMesuresDetaillees/1.0`,
apiAuthKey,

Bastien DUMONT
committed
sgeLogin,
await getDataHalfHour(
`${baseUrl}/enedis_SGE_ConsultationMesuresDetaillees/1.0`,
apiAuthKey,

Bastien DUMONT
committed
sgeLogin,
* @param {string} url
* @param {string} apiAuthKey
* @param {string} userLogin
* @param {number} pointId
*/
async function getData(url, apiAuthKey, userLogin, pointId) {
log('info', 'Fetching data')
'Content-Type': 'text/xml;charset=UTF-8',
apikey: apiAuthKey,
}
const { response } = await soapRequest({
url: url,
pointId,
userLogin,
startDailyDateString,
tagNameProcessors: [parseTags],
valueProcessors: [parseValue],
explicitArray: false,
/**
* Get Max power data
* @param {string} url
* @param {string} apiAuthKey
* @param {string} userLogin
* @param {number} pointId
*/
async function getMaxPowerData(url, apiAuthKey, userLogin, pointId) {
log('info', 'Fetching Max Power data')
'Content-Type': 'text/xml;charset=UTF-8',
apikey: apiAuthKey,
}
const { response } = await soapRequest({
url: url,
xml: consultationMesuresDetailleesMaxPower(
pointId,
userLogin,
startDailyDateString,
endDateString
),
}).catch(err => {
log('error', 'getMaxPowerData')
log('error', err)
return err
})
xml2js.parseString(
response.body,
{
tagNameProcessors: [parseTags],
valueProcessors: [parseValue],
explicitArray: false,
},
processData('com.grandlyon.enedis.maxpower')
)
}
/**
* If start date exceed the maximum amount of data we can get with one query
* get only 36 month.
* On manual execution, set the start date to one year ago.
*/
function setStartDate() {
if (moment(endDate).diff(startDailyDate, 'months', true) > 36) {
log(
'info',
'Start date exceed 36 month, setting start date to current date minus 36 month'
)
startDailyDate = moment(endDate).subtract(36, 'month')
startDailyDateString = startDailyDate.format('YYYY-MM-DD')
}
if (manualExecution) {
startDailyDate = moment(endDate).subtract(12, 'month')
startDailyDateString = startDailyDate.format('YYYY-MM-DD')
}
/**
* Get half-hour data
* @param {string} url
* @param {string} apiAuthKey
* @param {string} userLogin
* @param {number} pointId
*/
async function getDataHalfHour(url, apiAuthKey, userLogin, pointId) {
log('info', 'Fetching data')
'Content-Type': 'text/xml;charset=UTF-8',
apikey: apiAuthKey,
}
let MAX_HISTO = 4
// If manual execution, retrieve only 1 week
if (manualExecution) {
MAX_HISTO = 1
}
for (var i = 0; i < MAX_HISTO; i++) {
log('info', 'launch process with history')
const increamentedStartDateString = moment(startLoadDate)
.subtract(7 * i, 'day')
.format('YYYY-MM-DD')
const incrementedEndDateString = moment(endDate)
.subtract(7 * i, 'day')
.format('YYYY-MM-DD')

Bastien DUMONT
committed
const { response } = await soapRequest({
url: url,
pointId,
userLogin,
increamentedStartDateString,
incrementedEndDateString,
'COURBE',
'PA'
),
}).catch(err => {
log('error', 'consultationMesuresDetaillees half-hour')
log('error', err)
return err
})
xml2js.parseString(
response.body,
{
tagNameProcessors: [parseTags],
valueProcessors: [parseValue],
explicitArray: false,
},
processData('com.grandlyon.enedis.minute')
)
}
}
function processData(doctype = 'com.grandlyon.enedis.day') {
return async (err, result) => {
if (err) {
log('error', err)
throw err
}
// Return only needed part of info

Bastien DUMONT
committed
log('info', doctype)

Bastien DUMONT
committed
const data = parseSgeXmlData(result)
const processedDailyData = await storeData(
await formateDataForDoctype(data),
doctype,
['year', 'month', 'day', 'hour', 'minute']
)
log('info', 'Agregate enedis daily data for month and year')
if (doctype === 'com.grandlyon.enedis.day') {
log('info', 'Agregating...')
await agregateMonthAndYearData(processedDailyData)
}
} catch (e) {
if (doctype === 'com.grandlyon.enedis.minute') {
log(
'warn',
`No half-hour activated. Issue: ${result.Envelope.Body.Fault.faultstring}`
)
} else {
log('error', `Unkown error ${e}`)
}
/**
* Save data in the right doctype db and prevent duplicated keys
* @param {EnedisKonnectorData[]} data
* @param {string} doctype
* @param {string[]} filterKeys
*/
async function storeData(data, doctype, filterKeys) {
log('debug', doctype, 'Store into')
const filteredDocuments = await hydrateAndFilter(data, doctype, {
keys: filterKeys,
})
await addData(filteredDocuments, doctype)
return filteredDocuments
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
/**
* 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
if (data && data.length > 0) {
let monthData = {}
let yearData = {}
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)
})
// Agregation for Month data
const agregatedMonthData = await buildAgregatedData(
monthData,
'com.grandlyon.enedis.month'
)
await storeData(agregatedMonthData, 'com.grandlyon.enedis.month', [
'year',
'month',
])
// Agregation for Year data
const agregatedYearData = await buildAgregatedData(
yearData,
'com.grandlyon.enedis.year'
)
await storeData(agregatedYearData, 'com.grandlyon.enedis.year', ['year'])
}
}

Bastien DUMONT
committed
function isFirstStart(account) {
if (account && account.data && account.data.consentId) {

Bastien DUMONT
committed
log('info', 'Konnector not first start')
return false
}
log('info', 'Konnector first start')