Skip to content
Snippets Groups Projects
index.js 12.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • // @ts-check
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    const {
      BaseKonnector,
      log,
    
      hydrateAndFilter,
      addData,
    
      errors,
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    } = require('cozy-konnector-libs')
    
    const soapRequest = require('easy-soap-request')
    const moment = require('moment')
    require('moment-timezone')
    const xml2js = require('xml2js')
    
    const { buildAgregatedData } = require('./aggregate')
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    const {
      parseSgeXmlData,
      parseSgeXmlTechnicalData,
      formateDataForDoctype,
      parseTags,
      parseValue,
    
      parseUserPdl,
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    } = require('./parsing')
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    const {
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
      consulterDonneesTechniquesContractuelles,
      consultationMesuresDetailleesMaxPower,
      consultationMesuresDetaillees,
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
      rechercherPoint,
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
      commanderCollectePublicationMesures,
      commanderArretServiceSouscritMesures,
    } = require('./requests/sge')
    const {
    
      updateBoConsent,
      createBoConsent,
      getBoConsent,
      deleteBoConsent,
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
    } = require('./requests/bo')
    const { getInseeCode } = require('./requests/insee')
    
    
    moment.locale('fr') // set the language
    moment.tz.setDefault('Europe/Paris') // set the timezone
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    
    
    /*** Connector Constants ***/
    const manualExecution =
      process.env.COZY_JOB_MANUAL_EXECUTION === 'true' ? true : false
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    let startDailyDate = manualExecution
    
      ? moment().subtract(12, 'month')
      : moment().subtract(6, 'month')
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    let startDailyDateString = startDailyDate.format('YYYY-MM-DD')
    
    const startLoadDate = moment().subtract(7, 'day')
    
    const endDate = moment()
    const endDateString = endDate.format('YYYY-MM-DD')
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    
    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) {
    
      log('info', 'Gathering data ...')
      let baseUrl = fields.wso2BaseUrl
      let apiAuthKey = fields.apiToken
      let loginUtilisateur = fields.loginUtilisateur
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
      log('info', 'Authenticating ...')
    
      //TODO: Verify if condition is working in local and on build version
    
      if (cozyParameters && Object.keys(cozyParameters).length !== 0) {
        log('debug', 'Found COZY_PARAMETERS')
        baseUrl = cozyParameters.secret.wso2BaseUrl
        apiAuthKey = cozyParameters.secret.apiToken
        loginUtilisateur = cozyParameters.secret.loginUtilisateur
      }
    
    
      /**
       * 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...')
    
      if (await isFirstStart()) {
    
        if (
          !(await verifyUserIdentity(fields, baseUrl, apiAuthKey, loginUtilisateur))
        ) {
    
          throw errors.LOGIN_FAILED
        }
        await createBoConsent()
        //TODO: remove because useless ? Done later in code
        // const startDate = await getDataStartDate(
        //   baseUrl,
        //   apiAuthKey,
        //   loginUtilisateur,
        //   fields.pointId
        // )
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
        await commanderCollectePublicationMesures()
    
        await updateBoConsent()
      } else {
        await getBoConsent()
        if (!(await verifyUserIdentity(fields))) {
          await deleteBoConsent()
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
          await commanderArretServiceSouscritMesures()
    
          throw errors.TERMS_VERSION_MISMATCH
        }
      }
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
      log('info', 'Successfully logged in')
    
    
      await gatherData(baseUrl, apiAuthKey, loginUtilisateur, fields.pointId)
    }
    
    /**
     * Verify user identity
     * @param {object} fields
    
     * @param {string} baseUrl
     * @param {string} apiAuthKey
     * @param {string} loginUtilisateur
    
    async function verifyUserIdentity(
      fields,
      baseUrl,
      apiAuthKey,
      loginUtilisateur
    ) {
      const inseeCode = await getInseeCode(fields.postalCode)
      if (!inseeCode) throw errors.USER_ACTION_NEEDED
    
      const pdl = await findUserPdl(
        `${baseUrl}/enedis_SDE_recherche-point/1.0`,
        apiAuthKey,
        loginUtilisateur,
    
        fields.name,
        fields.addresse,
        fields.postalCode,
        inseeCode
      )
    
    
      if (fields.pointId != pdl) {
    
        log('error', 'PointId does not match')
    
        throw errors.LOGIN_FAILED
    
      }
      return true
    }
    
    /**
     * Main method for gathering data
     * @param {string} baseUrl
     * @param {string} apiAuthKey
     * @param {string} loginUtilisateur
     * @param {number} pointId
     */
    async function gatherData(baseUrl, apiAuthKey, loginUtilisateur, pointId) {
    
      log('info', 'Querying data...')
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
      await getDataStartDate(
        `${baseUrl}/enedis_SGE_ConsultationDonneesTechniquesContractuelles/1.0`,
        apiAuthKey,
        loginUtilisateur,
    
        pointId
    
      await getData(
        `${baseUrl}/enedis_SGE_ConsultationMesuresDetaillees/1.0`,
        apiAuthKey,
        loginUtilisateur,
    
        pointId
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
      await getMaxPowerData(
        `${baseUrl}/enedis_SGE_ConsultationMesuresDetaillees/1.0`,
        apiAuthKey,
        loginUtilisateur,
    
        pointId
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
      )
    
      await getDataHalfHour(
        `${baseUrl}/enedis_SGE_ConsultationMesuresDetaillees/1.0`,
        apiAuthKey,
        loginUtilisateur,
    
        pointId
    
      )
      log('info', 'Querying data: done')
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    }
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    /**
     *
     * @param {string} url
     * @param {string} apiAuthKey
     * @param {string} userLogin
     * @param {number} pointId
     */
    async function getDataStartDate(url, apiAuthKey, userLogin, pointId) {
      log('info', 'Fetching data start date')
    
      const sgeHeaders = {
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
        'Content-Type': 'text/xml;charset=UTF-8',
        apikey: apiAuthKey,
      }
    
      const { response } = await soapRequest({
        url: url,
    
        headers: sgeHeaders,
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
        xml: consulterDonneesTechniquesContractuelles(pointId, userLogin),
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
      }).catch(err => {
        log('error', 'technicalDataResponse')
        log('error', err)
        return err
      })
    
      xml2js.parseString(
        response.body,
        {
          tagNameProcessors: [parseTags],
          valueProcessors: [parseValue],
          explicitArray: false,
        },
        processStartDate()
      )
    }
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    
    
     * Get hour data
    
     * @param {string} url
     * @param {string} apiAuthKey
     * @param {string} userLogin
     * @param {number} pointId
     */
    async function getData(url, apiAuthKey, userLogin, pointId) {
      log('info', 'Fetching data')
    
      const sgeHeaders = {
    
        'Content-Type': 'text/xml;charset=UTF-8',
        apikey: apiAuthKey,
      }
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
      setStartDate()
    
      const { response } = await soapRequest({
        url: url,
    
        headers: sgeHeaders,
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
        xml: consultationMesuresDetaillees(
    
          pointId,
          userLogin,
          startDailyDateString,
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
          endDateString,
          'ENERGIE',
          'EA'
    
        ),
      }).catch(err => {
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
        log('error', 'consultationMesuresDetaillees')
    
        log('error', err)
        return err
    
      xml2js.parseString(
        response.body,
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
        {
    
          tagNameProcessors: [parseTags],
          valueProcessors: [parseValue],
          explicitArray: false,
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
        },
    
        processData()
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    /**
     * 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')
    
      const sgeHeaders = {
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
        'Content-Type': 'text/xml;charset=UTF-8',
        apikey: apiAuthKey,
      }
    
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
      setStartDate()
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    
      const { response } = await soapRequest({
        url: url,
    
        headers: sgeHeaders,
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
        xml: consultationMesuresDetailleesMaxPower(
          pointId,
          userLogin,
          startDailyDateString,
          endDateString
        ),
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
      }).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')
      )
    }
    
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    /**
     * If start date exceed the maximum amount of data we can get with one query
     * get only 36 month
     */
    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')
      }
    }
    
    
    /**
     * 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 sgeHeaders = {
    
        '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: sgeHeaders,
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
          xml: consultationMesuresDetaillees(
    
            pointId,
            userLogin,
            increamentedStartDateString,
            incrementedEndDateString,
            'COURBE',
            'PA'
          ),
        }).catch(err => {
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
          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')
        )
      }
    }
    
    
    /**
     * Parse data
    
     * @param {string} doctype
     * @returns
    
    function processData(doctype = 'com.grandlyon.enedis.day') {
    
      return async (err, result) => {
        if (err) {
          log('error', err)
          throw err
        }
        // Return only needed part of info
        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') {
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
          log('info', 'Agregating...')
    
          await agregateMonthAndYearData(processedDailyData)
        }
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    /**
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
     * Store an accurate start date based on contrat start
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
     */
    function processStartDate() {
      return async (err, result) => {
        if (err) {
          log('error', err)
          throw err
        }
        // update start Date with contract openning date
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
        try {
          startDailyDate = moment(parseSgeXmlTechnicalData(result), 'YYYY-MM-DD')
          startDailyDateString = startDailyDate.format('YYYY-MM-DD')
        } catch (err) {
          log('error', err)
          //TODO: custom error ?
          throw err
        }
    
    /**
     * Save data in the right doctype db and prevent duplicated keys
     * @param {EnedisKonnectorData[]} data
     * @param {string} doctype
     * @param {string[]} filterKeys
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
     * @returns {Promise<*>}
    
     */
    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
    
    /**
     * 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'])
      }
    }
    
    
    /**
     * @returns {boolean}
     */
    function isFirstStart() {
    
      console.log('isFirstStart')
    
      //TODO: Implement
    
      return true
    
     * @return {Promise<string>} User Pdl
    
    async function findUserPdl(
    
      url,
      apiAuthKey,
      appLogin,
      name,
    
      address,
    
      postalCode,
      inseeCode
    ) {
      log('info', 'Fetching user data')
    
      const sgeHeaders = {
    
        'Content-Type': 'text/xml;charset=UTF-8',
        apikey: apiAuthKey,
      }
      const { response } = await soapRequest({
        url: url,
    
        headers: sgeHeaders,
    
        xml: rechercherPoint(appLogin, name, postalCode, inseeCode, address),
    
      }).catch(err => {
        log('error', 'rechercherPointResponse')
        log('error', err)
        throw errors.LOGIN_FAILED
      })
    
    
      const parsedReply = await xml2js.parseStringPromise(response.body, {
        tagNameProcessors: [parseTags],
        valueProcessors: [parseValue],
        explicitArray: false,
      })
    
      //TODO: handle errors
      return parseUserPdl(parsedReply)