diff --git a/.gitignore b/.gitignore index 35d328e6d61fa10cf76e954c16d272bb6dd0b0f5..b6c5e5d6cae8c12ec44d39ef7b9bc8cd18b98eb1 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,7 @@ desktop.ini .floo .flooignore .idea/ - +.vscode/ # Default # /!\ KEEP THIS SECTION THE LAST ONE !.gitkeep diff --git a/src/aggregate.js b/src/aggregate.js new file mode 100644 index 0000000000000000000000000000000000000000..808d34f0c36877aa7ab15856851e60a8a9168965 --- /dev/null +++ b/src/aggregate.js @@ -0,0 +1,106 @@ +// @ts-check +const { log, cozyClient } = require('cozy-konnector-libs') + +/** + * Retrieve and remove old data for a specific doctype + * Return an Array of agregated data + */ +async function buildAgregatedData(data, doctype) { + let agregatedData = [] + // eslint-disable-next-line no-unused-vars + 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.enedis.year') { + year = key + month = 1 + day = 0 + hour = 0 + } else if (doctype === 'com.grandlyon.enedis.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.enedis.year : + * { load: 76.712, year: 2020, ... } need to be replace by + * { load: 82.212, year: 2020, ... } after enedis 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.enedis.year') { + // Yearly case + filtered = result.filter(function(el) { + return el.year == data.year + }) + } else if (doctype === 'com.grandlyon.enedis.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 + // eslint-disable-next-line no-unused-vars + 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 +} + +module.exports = { + buildAgregatedData, +} diff --git a/src/index.js b/src/index.js index a44fa7573596c58e671f8c9c7c3b66908734c2ed..ab1d32997f2ddfb06e0bf1023d49b5021eca1fe5 100644 --- a/src/index.js +++ b/src/index.js @@ -9,6 +9,8 @@ const soapRequest = require('easy-soap-request') const moment = require('moment') require('moment-timezone') const xml2js = require('xml2js') +const { buildAgregatedData } = require('./aggregate') +const { userTechnicalData, userMesureDetailles } = require('./request') moment.locale('fr') // set the language moment.tz.setDefault('Europe/Paris') // set the timezone @@ -19,8 +21,7 @@ let startDailyDate = manualExecution ? moment().subtract(12, 'month') : moment().subtract(6, 'month') let startDailyDateString = startDailyDate.format('YYYY-MM-DD') -// const startLoadDate = moment().subtract(7, 'day') -// const startLoadDateString = startLoadDate.format('YYYY-MM-DD') +const startLoadDate = moment().subtract(7, 'day') const endDate = moment() const endDateString = endDate.format('YYYY-MM-DD') @@ -46,8 +47,7 @@ async function start(fields, cozyParameters) { //TODO: authentification ? log('info', 'Successfully logged in') - //TODO: get compteur start data - + log('info', 'Querying data...') await getDataStartDate( `${baseUrl}/enedis_SGE_ConsultationDonneesTechniquesContractuelles/1.0`, apiAuthKey, @@ -60,7 +60,13 @@ async function start(fields, cozyParameters) { loginUtilisateur, fields.pointId ) - log('info', 'Konnector process end') + await getDataHalfHour( + `${baseUrl}/enedis_SGE_ConsultationMesuresDetaillees/1.0`, + apiAuthKey, + loginUtilisateur, + fields.pointId + ) + log('info', 'Querying data: done') } /** * @@ -98,7 +104,7 @@ async function getDataStartDate(url, apiAuthKey, userLogin, pointId) { } /** - * + * Get hour data * @param {string} url * @param {string} apiAuthKey * @param {string} userLogin @@ -111,6 +117,17 @@ async function getData(url, apiAuthKey, userLogin, pointId) { apikey: apiAuthKey, } + // If start date exceed the maximum amount of data we can get with one query + // get only 24 month + if (moment(endDate).diff(startDailyDate, 'months', true) > 24) { + log( + 'info', + 'Start date exceed 24 month, setting start date to current date minus 24 month' + ) + startDailyDate = moment(endDate).subtract(24, 'month') + startDailyDateString = startDailyDate.format('YYYY-MM-DD') + } + const { response } = await soapRequest({ url: url, headers: sampleHeaders, @@ -137,6 +154,62 @@ async function getData(url, apiAuthKey, userLogin, pointId) { ) } +/** + * 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') + const sampleHeaders = { + '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') + const { response } = await soapRequest({ + url: url, + headers: sampleHeaders, + xml: userMesureDetailles( + pointId, + userLogin, + increamentedStartDateString, + incrementedEndDateString, + 'COURBE', + 'PA' + ), + }).catch(err => { + log('error', 'userMesureDetailles half-hour') + log('error', err) + return err + }) + + xml2js.parseString( + response.body, + { + tagNameProcessors: [parseTags], + valueProcessors: [parseValue], + explicitArray: false, + }, + processData('com.grandlyon.enedis.minute') + ) + } +} + /** * Format tag in order to be manipulated easly * @param {string} name @@ -165,8 +238,10 @@ function parseValue(value, name) { /** * Parse data + * @param {string} doctype + * @returns */ -function processData() { +function processData(doctype = 'com.grandlyon.enedis.day') { return async (err, result) => { if (err) { log('error', err) @@ -174,11 +249,18 @@ function processData() { } // Return only needed part of info const data = parseSgeXmlData(result) - return storeData( + const processedDailyData = await storeData( await formateDataForDoctype(data), - 'com.grandlyon.enedis.day', - ['year', 'month', 'day'] + doctype, + ['year', 'month', 'day', 'hour', 'minute'] ) + + log('info', 'Agregate enedis daily data for month and year') + if (doctype === 'com.grandlyon.enedis.day') { + console.log(processedDailyData.length) + + await agregateMonthAndYearData(processedDailyData) + } } } @@ -237,66 +319,8 @@ async function storeData(data, doctype, filterKeys) { const filteredDocuments = await hydrateAndFilter(data, doctype, { keys: filterKeys, }) - return await addData(filteredDocuments, doctype) -} - -/** - * Query SGE in order to get info - * @param {number} pointId - * @param {string} userLogin - * @param {string} startDt - * @param {string} endDt - * @returns {string} - */ -function userMesureDetailles(pointId, userLogin, startDt, endDt) { - log('info', `Query data between ${startDt} and ${endDt}`) - return `<?xml version='1.0' encoding='utf-8'?> - <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" - xmlns:v2="http://www.enedis.fr/sge/b2b/services/consultationmesuresdetaillees/v2.0" - xmlns:v1="http://www.enedis.fr/sge/b2b/technique/v1.0"> - <soapenv:Header/> - <soapenv:Body> - <v2:consulterMesuresDetaillees> - <demande> - <initiateurLogin>${userLogin}</initiateurLogin> - <pointId>${pointId}</pointId> - <mesuresTypeCode>ENERGIE</mesuresTypeCode> - <grandeurPhysique>EA</grandeurPhysique> - <soutirage>true</soutirage> - <injection>false</injection> - <dateDebut>${startDt}</dateDebut> - <dateFin>${endDt}</dateFin> - <mesuresCorrigees>false</mesuresCorrigees> - <accordClient>true</accordClient> - </demande> - </v2:consulterMesuresDetaillees> - </soapenv:Body> - </soapenv:Envelope> - ` -} - -/** - * Get user technical data - * @param {string} pointId - * @param {string} userLogin - * @returns {string} - */ -function userTechnicalData(pointId, userLogin) { - log('info', `Query userMesureDetailles`) - return `<?xml version='1.0' encoding='utf-8'?> - <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" - xmlns:v2="http://www.enedis.fr/sge/b2b/services/consulterdonneestechniquescontractuelles/v1.0" - xmlns:v1="http://www.enedis.fr/sge/b2b/technique/v1.0"> - <soapenv:Header/> - <soapenv:Body> - <v2:consulterDonneesTechniquesContractuelles> - <pointId>${pointId}</pointId> - <loginUtilisateur>${userLogin}</loginUtilisateur> - <autorisationClient>true</autorisationClient> - </v2:consulterDonneesTechniquesContractuelles> - </soapenv:Body> - </soapenv:Envelope> - ` + await addData(filteredDocuments, doctype) + return filteredDocuments } /** @@ -306,10 +330,8 @@ function userTechnicalData(pointId, userLogin) { */ async function formateDataForDoctype(data) { log('info', 'Formating data') - // record return data.map(record => { let date = moment(record.d, 'YYYY/MM/DD h:mm:ss') - return { load: record.v, year: parseInt(date.format('YYYY')), @@ -320,3 +342,37 @@ async function formateDataForDoctype(data) { } }) } + +/** + * 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']) + } +} diff --git a/src/request.js b/src/request.js new file mode 100644 index 0000000000000000000000000000000000000000..cfdef54bd46ce4e0f30edefc664007f6b8460bb0 --- /dev/null +++ b/src/request.js @@ -0,0 +1,104 @@ +// @ts-check +const { log } = require('cozy-konnector-libs') + +/** + * Query SGE in order to get info + * @param {number} pointId + * @param {string} userLogin + * @param {string} startDt + * @param {string} endDt + * @returns {string} + */ +function userMesureDetailles( + pointId, + userLogin, + startDt, + endDt, + mesureType = 'ENERGIE', + unit = 'EA' +) { + log( + 'info', + `Query data ${mesureType}/${unit} between ${startDt} and ${endDt}` + ) + return `<?xml version='1.0' encoding='utf-8'?> + <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" + xmlns:v2="http://www.enedis.fr/sge/b2b/services/consultationmesuresdetaillees/v2.0" + xmlns:v1="http://www.enedis.fr/sge/b2b/technique/v1.0"> + <soapenv:Header/> + <soapenv:Body> + <v2:consulterMesuresDetaillees> + <demande> + <initiateurLogin>${userLogin}</initiateurLogin> + <pointId>${pointId}</pointId> + <mesuresTypeCode>${mesureType}</mesuresTypeCode> + <grandeurPhysique>${unit}</grandeurPhysique> + <soutirage>true</soutirage> + <injection>false</injection> + <dateDebut>${startDt}</dateDebut> + <dateFin>${endDt}</dateFin> + <mesuresCorrigees>false</mesuresCorrigees> + <accordClient>true</accordClient> + </demande> + </v2:consulterMesuresDetaillees> + </soapenv:Body> + </soapenv:Envelope> + ` +} + +function userMesureDetaillesHalfHour(pointId, userLogin, startDt, endDt) { + log('info', `Query data between ${startDt} and ${endDt}`) + return `<?xml version='1.0' encoding='utf-8'?> + <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" + xmlns:v2="http://www.enedis.fr/sge/b2b/services/consultationmesuresdetaillees/v2.0" + xmlns:v1="http://www.enedis.fr/sge/b2b/technique/v1.0"> + <soapenv:Header/> + <soapenv:Body> + <v2:consulterMesuresDetaillees> + <demande> + <initiateurLogin>${userLogin}</initiateurLogin> + <pointId>${pointId}</pointId> + <mesuresTypeCode>COURBE</mesuresTypeCode> + <grandeurPhysique>PA</grandeurPhysique> + <soutirage>true</soutirage> + <injection>false</injection> + <dateDebut>${startDt}</dateDebut> + <dateFin>${endDt}</dateFin> + <mesuresCorrigees>false</mesuresCorrigees> + <accordClient>true</accordClient> + </demande> + </v2:consulterMesuresDetaillees> + </soapenv:Body> + </soapenv:Envelope> + ` +} + +/** + * Get user technical data + * @param {number} pointId + * @param {string} userLogin + * @returns {string} + */ +function userTechnicalData(pointId, userLogin) { + log('info', `Query userMesureDetailles`) + return `<?xml version='1.0' encoding='utf-8'?> + <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" + xmlns:v2="http://www.enedis.fr/sge/b2b/services/consulterdonneestechniquescontractuelles/v1.0" + xmlns:v1="http://www.enedis.fr/sge/b2b/technique/v1.0"> + <soapenv:Header/> + <soapenv:Body> + <v2:consulterDonneesTechniquesContractuelles> + <pointId>${pointId}</pointId> + <loginUtilisateur>${userLogin}</loginUtilisateur> + <autorisationClient>true</autorisationClient> + </v2:consulterDonneesTechniquesContractuelles> + </soapenv:Body> + </soapenv:Envelope> + ` +} + +module.exports = { + userTechnicalData, + userMesureDetaillesHalfHour, + userMesureDetailles, +}