Skip to content
Snippets Groups Projects
index.js 20.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • // @ts-check
    
    require('./instrument.js') // Sentry initialization
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    const {
      BaseKonnector,
      log,
    
      hydrateAndFilter,
    
      errors,
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
      updateOrCreate,
    
    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')
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    const {
      parseSgeXmlData,
      formateDataForDoctype,
      parseTags,
      parseValue,
    
      parseValueHalfHour,
    
      parsePointId,
    
      parseUserOffPeakHours,
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    const {
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
      consultationMesuresDetailleesMaxPower,
      consultationMesuresDetaillees,
    
      consulterDonneesTechniquesContractuelles,
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
    } = require('./requests/sge')
    const {
    
      updateBoConsent,
      createBoConsent,
      getBoConsent,
      deleteBoConsent,
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
    } = require('./requests/bo')
    
    const {
      verifyUserIdentity,
      activateContract,
      verifyContract,
      terminateContract,
    } = require('./core')
    const { getAccount, saveAccountData } = require('./requests/cozy')
    
    const { isLocal } = require('./helpers/env')
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
    const Sentry = require('@sentry/node')
    
    const { catchRequestReject } = require('./helpers/catch')
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
    const { applyPrices, getPrices } = require('./helpers/prices.js')
    const { rangeDate } = require('./constants.js')
    const {
      aggregateMonthlyLoad,
      filterFirstMonthlyLoad,
      aggregateYearlyLoad,
      filterFirstYearlyLoad,
    } = require('./helpers/aggregate.js')
    
    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'
    let startDate = manualExecution
    
      ? moment().subtract(12, 'month').add(1, 'day')
      : moment().subtract(36, 'month').add(1, 'day')
    
    let startDateString = startDate.format('YYYY-MM-DD')
    const startHalfHourDate = moment().subtract(7, 'day')
    
    const endDate = moment()
    const endDateString = endDate.format('YYYY-MM-DD')
    
    const ACCOUNT_ID = isLocal() ? 'default_account_id' : 'enedissgegrandlyon'
    
    const NO_DATA = process.env.NO_DATA === 'true'
    
    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.
     * @param {fields} fields
     * @param {{secret: fields}} cozyParameters
     */
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    async function start(fields, cozyParameters) {
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
      try {
        log('info', 'Konnector configuration ...')
        log('info', `isManual execution: ${manualExecution}`)
    
    
        if (NO_DATA) {
          log(
            'debug',
            'NO_DATA is enabled, konnector will stop after verifyUserIdentity()'
          )
        }
    
        const pointId = parsePointId(parseInt(fields.pointId))
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
        let baseUrl = fields.wso2BaseUrl
        let apiAuthKey = fields.apiToken
        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
          contractId = cozyParameters.secret.contractId
          sgeLogin = cozyParameters.secret.sgeLogin
          boBaseUrl = cozyParameters.secret.boBaseUrl
          boToken = cozyParameters.secret.boToken
        }
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
        // Prevent missing configuration
        if (
          !baseUrl ||
          !apiAuthKey ||
          !contractId ||
          !sgeLogin ||
          !boToken ||
          !boBaseUrl
        ) {
          const errorMessage = 'Missing configuration secrets'
          log('error', errorMessage)
    
          Sentry.captureException(errorMessage, {
            tags: { section: 'start' },
          })
          throw new Error(errors.VENDOR_DOWN)
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
        /**
         * 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...')
    
    
        const boUrlSGE = new URL('/api/sge', boBaseUrl).href
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
        if (isFirstStart(await getAccount(ACCOUNT_ID))) {
          log('info', 'First start...')
          const user = await verifyUserIdentity(
            fields,
            baseUrl,
            apiAuthKey,
            sgeLogin
          )
    
          exitIfDebug(user)
    
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
          let consent = await createBoConsent(
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
            boToken,
            pointId,
            user.lastname,
            user.firstname,
            user.address,
            user.postalCode,
            user.inseeCode,
            user.city,
    
            user.hasBeenThroughSafetyOnBoarding
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
          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(
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
            user.pointId
          )
          if (!serviceId) {
            serviceId = await activateContract(
              baseUrl,
              apiAuthKey,
              sgeLogin,
              contractId,
              user.lastname,
              user.pointId,
              contractStartDate,
              contractEndDate
    
              await deleteBoConsent(boUrlSGE, boToken, consent.ID)
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
          }
          consent = await updateBoConsent(
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
            consent,
            serviceId.toString()
          )
          // Save bo id into account
          const accountData = await getAccount(ACCOUNT_ID)
    
          await saveAccountData(ACCOUNT_ID, {
            ...accountData.data,
            consentId: consent.ID,
            expirationDate: contractEndDate,
            inseeCode: user.inseeCode,
          })
        } else {
          log('info', 'Alternate start...')
          const accountData = await getAccount(ACCOUNT_ID)
          const userConsent = await getBoConsent(
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
            boToken,
            accountData.data.consentId
          )
          const user = await verifyUserIdentity(
            fields,
            baseUrl,
            apiAuthKey,
            sgeLogin,
            true,
            accountData.data.inseeCode
    
          exitIfDebug(user)
    
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
          if (!userConsent) {
            const errorMessage = 'No user consent found'
            log('error', errorMessage)
    
            Sentry.captureException(errorMessage, {
              tags: { section: 'start' },
            })
            throw new Error(errors.VENDOR_DOWN)
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
          }
    
          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,
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
              boToken,
              consentEndDate < today
            )
          }
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
        log('info', 'Successfully logged in')
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
        await gatherData(baseUrl, apiAuthKey, sgeLogin, pointId, boBaseUrl)
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
    
        log('info', 'Konnector success')
      } catch (error) {
    
        const errorMessage = `SGE konnector encountered an error. Response data: ${JSON.stringify(
          error.message
        )}`
        Sentry.captureMessage(errorMessage, {
          tags: {
            section: 'start',
          },
        })
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
        await Sentry.flush()
        throw error
    
    }
    
    /**
     * Delete User Consent
     * @param {Consent} userConsent
     * @param {string} baseUrl
     * @param {string} apiAuthKey
     * @param {string} sgeLogin
     * @param {string} contractId
    
     * @param {string} pointId
    
     * @param {string} boBaseUrl
     * @param {string} boToken
    
     */
    async function deleteConsent(
      userConsent,
      baseUrl,
      apiAuthKey,
      sgeLogin,
      contractId,
      pointId,
      boBaseUrl,
    
    ) {
      log('error', `Invalid or not found consent for user`)
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
      Sentry.captureMessage(`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 {
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
        const errorMessage = `No service id retrieved from BO`
        log('error', errorMessage)
    
        Sentry.captureException(errorMessage, {
          tags: { section: 'start' },
        })
        throw new Error(errors.VENDOR_DOWN)
    
        Sentry.captureException('Consent expired', {
          tags: { section: 'start' },
        })
        throw new Error(errors.USER_ACTION_NEEDED_OAUTH_OUTDATED)
    
      throw new Error(errors.TERMS_VERSION_MISMATCH)
    
    }
    
    /**
     * Main method for gathering data
     * @param {string} baseUrl
     * @param {string} apiAuthKey
    
     * @param {string} pointId
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
     * @param {string} boBaseUrl
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
    async function gatherData(baseUrl, apiAuthKey, sgeLogin, pointId, boBaseUrl) {
    
      log('info', 'Querying data...')
    
      const measuresUrl = new URL(
        '/enedis_SGE_ConsultationMesuresDetaillees_v3/1.0',
        baseUrl
      ).href
    
      const contractUrl = new URL(
        '/enedis_SGE_ConsultationDonneesTechniquesContractuelles/1.0',
        baseUrl
      ).href
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
    
      log('info', 'Fetching BO prices')
      const prices = await getPrices(boBaseUrl)
    
      await getDailyData(measuresUrl, apiAuthKey, sgeLogin, pointId, prices)
    
      await getMaxPowerData(measuresUrl, apiAuthKey, sgeLogin, pointId)
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
      await getDataHalfHour(measuresUrl, apiAuthKey, sgeLogin, pointId, prices)
    
      await getOffPeakHours(contractUrl, apiAuthKey, sgeLogin, pointId)
    
      log('info', 'Querying data: done')
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    }
    
    /**
     * Get hour data
     * @param {string} url
     * @param {string} apiAuthKey
     * @param {string} userLogin
     * @param {string} pointId
     */
    async function getOffPeakHours(url, apiAuthKey, userLogin, pointId) {
      log('info', 'Fetching off-peak hours')
      const sgeHeaders = {
        'Content-Type': 'text/xml;charset=UTF-8',
        apikey: apiAuthKey,
      }
    
      const { response } = await soapRequest({
        url: url,
        headers: sgeHeaders,
        xml: consulterDonneesTechniquesContractuelles(pointId, userLogin, false),
      }).catch(err => {
        log('error', 'consulterDonneesTechniquesContractuelles')
        log('error', err)
        Sentry.captureException(
          `consulterDonneesTechniquesContractuelles: ${err}`,
          {
            tags: { section: 'getOffPeakHour' },
            extra: {
              pointId: pointId,
            },
          }
        )
        return err
      })
    
    
      catchRequestReject(response.body)
    
    
      const result = await xml2js.parseStringPromise(response.body, {
        tagNameProcessors: [parseTags],
        valueProcessors: [parseValue],
        explicitArray: false,
      })
    
      try {
        const offPeakHours = parseUserOffPeakHours(result)
        log(
          'debug',
          `Found off-peak hours : ${offPeakHours}, store them in account data`
        )
        const accountData = await getAccount(ACCOUNT_ID)
        await saveAccountData(ACCOUNT_ID, {
          ...accountData.data,
          offPeakHours,
        })
      } catch (error) {
        log('debug', 'Off-peak hours not found, remove them from account data')
        let accountData = await getAccount(ACCOUNT_ID)
        delete accountData.data.offPeakHours
        await saveAccountData(ACCOUNT_ID, {
          ...accountData.data,
        })
      }
    }
    
    
     * @param {string} url
     * @param {string} apiAuthKey
     * @param {string} userLogin
    
     * @param {string} pointId
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
    async function getDailyData(url, apiAuthKey, userLogin, pointId, prices) {
    
      log('info', 'Fetching daily data')
    
      const sgeHeaders = {
    
        '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: consultationMesuresDetaillees(
    
          pointId,
          userLogin,
    
    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)
    
        Sentry.captureException(`consultationMesuresDetaillees: ${err}`, {
          tags: { section: 'getData' },
        })
    
        return err
    
      catchRequestReject(response.body)
    
    
      xml2js.parseString(
        response.body,
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
        {
    
          tagNameProcessors: [parseTags],
          valueProcessors: [parseValue],
          explicitArray: false,
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
        },
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
        processDailyData(prices)
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    /**
     * Get Max power data
     * @param {string} url
     * @param {string} apiAuthKey
     * @param {string} userLogin
    
     * @param {string} pointId
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
     */
    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,
      }
    
      const { response } = await soapRequest({
        url: url,
    
        headers: sgeHeaders,
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
        xml: consultationMesuresDetailleesMaxPower(
          pointId,
          userLogin,
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
          endDateString
        ),
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
      }).catch(err => {
        log('error', 'getMaxPowerData')
        log('error', err)
    
        Sentry.captureException(`getMaxPowerData: ${err}`, {
          tags: { section: 'getMaxPowerData' },
        })
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
        return err
      })
    
    
      catchRequestReject(response.body)
    
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
      xml2js.parseString(
        response.body,
        {
          tagNameProcessors: [parseTags],
          valueProcessors: [parseValue],
          explicitArray: false,
        },
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
        processMaxPowerData()
    
    /**
     * Get half-hour data
     * @param {string} url
     * @param {string} apiAuthKey
     * @param {string} userLogin
    
     * @param {string} pointId
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
    async function getDataHalfHour(url, apiAuthKey, userLogin, pointId, prices) {
    
      log('info', 'Fetching half-hour data')
    
      const sgeHeaders = {
    
        'Content-Type': 'text/xml;charset=UTF-8',
        apikey: apiAuthKey,
      }
    
    
      // If manual execution, retrieve only 1 week otherwise retrieve 4 weeks
      const MAX_HISTO = manualExecution ? 1 : 4
    
    
      for (let i = 0; i < MAX_HISTO; i++) {
    
        log('info', 'launch process with history')
    
        const incrementedStartDateString = moment(startHalfHourDate)
    
          .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,
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
            incrementedStartDateString,
    
            incrementedEndDateString,
            'COURBE',
            'PA'
          ),
        }).catch(err => {
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
          log('error', 'consultationMesuresDetaillees half-hour')
    
          log('error', err)
    
          Sentry.captureException(
            `consultationMesuresDetaillees half-hour: ${err}`,
            {
              tags: { section: 'getDataHalfHour' },
            }
          )
    
        catchRequestReject(response.body)
    
    
        xml2js.parseString(
          response.body,
          {
            tagNameProcessors: [parseTags],
    
            valueProcessors: [parseValueHalfHour],
    
            explicitArray: false,
          },
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
          processHalfHourData(prices)
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
     * @param {Price[] | null} prices
    
     * @returns
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
    function processDailyData(prices) {
    
      return async (err, result) => {
        if (err) {
          log('error', err)
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
          Sentry.captureException('error while processing daily data')
    
          throw err
        }
        // Return only needed part of info
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
        log('info', `Processing ${rangeDate.day.doctype} data`)
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
        try {
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
          let dailyData = await formateDataForDoctype(data)
    
          if (prices && prices.length > 0) {
            log('info', 'Found BO prices, applying them to enedis data')
            dailyData = await applyPrices(dailyData, prices)
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
    
          const filterDayKeys = [...rangeDate.day.keys, 'load']
          if (prices) filterDayKeys.push('price')
          const daysToUpdate = await hydrateAndFilter(
            dailyData,
            rangeDate.day.doctype,
            { keys: filterDayKeys }
          )
          log('debug', 'Store enedis daily load data')
          await updateOrCreate(
            daysToUpdate,
            rangeDate.day.doctype,
            rangeDate.day.keys
          )
    
          const { year: firstYear, month: firstMonth } = dailyData[0]
    
          log('debug', 'Aggregate enedis monthly load data')
          const monthlyLoads = aggregateMonthlyLoad(dailyData)
    
          log('debug', 'Filter first month aggregate if already in database')
          const filteredMonthlyLoads = await filterFirstMonthlyLoad(
            firstMonth,
            firstYear,
            monthlyLoads
          )
    
          const filterMonthKeys = [...rangeDate.month.keys, 'load']
          if (prices) filterMonthKeys.push('price')
          const monthsToUpdate = await hydrateAndFilter(
            filteredMonthlyLoads,
            rangeDate.month.doctype,
            { keys: filterMonthKeys }
          )
    
          log('debug', 'Store aggregated enedis monthly load data')
          await updateOrCreate(
            monthsToUpdate,
            rangeDate.month.doctype,
            rangeDate.month.keys
          )
    
          log('debug', 'Aggregate enedis yearly load data')
          const yearlyLoads = aggregateYearlyLoad(monthlyLoads)
    
          log('debug', 'Filter first year aggregate if already in database')
          const filteredYearlyLoads = await filterFirstYearlyLoad(
            firstYear,
            yearlyLoads
          )
    
          const filterYearKeys = [...rangeDate.year.keys, 'load']
          if (prices) filterYearKeys.push('price')
          const yearsToUpdate = await hydrateAndFilter(
            filteredYearlyLoads,
            rangeDate.year.doctype,
            { keys: filterYearKeys }
          )
    
          log('debug', 'Store aggregated enedis yearly load data')
          await updateOrCreate(
            yearsToUpdate,
            rangeDate.year.doctype,
            rangeDate.year.keys
          )
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
          log('warn', `Unknown error ${e}`)
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
        }
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
    function processMaxPowerData() {
      return async (err, result) => {
        if (err) {
          log('error', err)
          Sentry.captureException('error while processing daily data')
          throw err
        }
        // Return only needed part of info
        log('info', `Processing ${rangeDate.maxPower.doctype} data`)
        try {
          const data = parseSgeXmlData(result)
          const maxPowerData = await formateDataForDoctype(data)
    
          const filterMaxPowerKeys = [...rangeDate.maxPower.keys, 'load']
          const maxPowerToUpdate = await hydrateAndFilter(
            maxPowerData,
            rangeDate.maxPower.doctype,
            { keys: filterMaxPowerKeys }
          )
          log('debug', 'Store Enedis max power load data')
          await updateOrCreate(
            maxPowerToUpdate,
            rangeDate.maxPower.doctype,
            rangeDate.maxPower.keys
          )
        } catch (e) {
          log('warn', `Unknown error ${e}`)
        }
      }
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
     * @param {Price[] | null} prices
     * @returns
    
    Rémi PAILHAREY's avatar
    Rémi PAILHAREY committed
    function processHalfHourData(prices) {
      return async (err, result) => {
        if (err) {
          log('error', err)
          Sentry.captureException('error while processing half-hour data')
          throw err
        }
        // Return only needed part of info
        log('info', `Processing ${rangeDate.minute.doctype} data`)
        try {
          const data = parseSgeXmlData(result)
          let minuteData = await formateDataForDoctype(data)
    
          if (
            (rangeDate.minute.doctype === 'com.grandlyon.enedis.day' ||
              rangeDate.minute.doctype === 'com.grandlyon.enedis.minute') &&
            prices &&
            prices.length > 0
          ) {
            log('info', 'Found BO prices, applying them to enedis data')
            minuteData = await applyPrices(minuteData, prices)
          }
    
          const filterMinuteKeys = [...rangeDate.minute.keys, 'load']
          if (prices) filterMinuteKeys.push('price')
          const minutesToUpdate = await hydrateAndFilter(
            minuteData,
            rangeDate.minute.doctype,
            { keys: filterMinuteKeys }
          )
          log('debug', 'Store Enedis minute load data')
          await updateOrCreate(
            minutesToUpdate,
            rangeDate.minute.doctype,
            rangeDate.minute.keys
          )
        } catch (e) {
          const errorMessage = `No half-hour activated. Issue: ${result.Envelope.Body.Fault.faultstring}`
          Sentry.captureMessage(errorMessage, {
            tags: { section: 'processData' },
          })
          log('warn', errorMessage)
        }
    
    
    /**
     * @returns {boolean}
     */
    
      if (account?.data?.consentId) {
    
        log('info', 'Konnector not first start')
        return false
      }
      log('info', 'Konnector first start')
    
      return true
    
    
    /**
     * Check if konnector is launched in local with NO_DATA option
     * If so, logs result from verifyUserIdentity() and stops the konnector before getting any data
     * @param {User} user - The user object to log
     */
    function exitIfDebug(user) {
      if (NO_DATA) {
        log(
          'debug',
          `Stopping konnector before getting data, user found from verifyUserIdentity():`
        )
        log('debug', user)
        process.exit()
      }
    }