Skip to content
Snippets Groups Projects
index.js 6.09 KiB
Newer Older
Hugo SUBTIL's avatar
Hugo SUBTIL committed
const {
  BaseKonnector,
  log,
Yoan VALLET's avatar
Yoan VALLET committed
  addData,
Hugo SUBTIL's avatar
Hugo SUBTIL committed
  hydrateAndFilter,
Yoan VALLET's avatar
Yoan VALLET committed
  cozyClient
Hugo SUBTIL's avatar
Hugo SUBTIL committed
} = require('cozy-konnector-libs')
const moment = require('moment')
const rp = require('request-promise')
Yoan VALLET's avatar
Yoan VALLET committed
require('moment-timezone')
Hugo SUBTIL's avatar
Hugo SUBTIL committed

Yoan VALLET's avatar
Yoan VALLET committed
moment.locale('fr') // set the language
moment.tz.setDefault('Europe/Paris') // set the timezone

/*** Connector Constants ***/
Hugo SUBTIL's avatar
Hugo SUBTIL committed
const startDate = moment()
Yoan VALLET's avatar
Yoan VALLET committed
  .subtract(32, 'month')
Hugo SUBTIL's avatar
Hugo SUBTIL committed
  .format('YYYY-MM-DD')
const endDate = moment().format('YYYY-MM-DD')
const baseUrl = 'https://gw.hml.api.enedis.fr'

// 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.
Hugo SUBTIL's avatar
Hugo SUBTIL committed
async function start(fields) {
  try {
    const { access_token } = fields
Yoan VALLET's avatar
Yoan VALLET committed
    const usage_point_id = 22516914714270
Hugo SUBTIL's avatar
Hugo SUBTIL committed
    log('info', 'Fetching enedis data')
Yoan VALLET's avatar
Yoan VALLET committed
    const fetchedData = await getDailyData(access_token, usage_point_id)
    log('info', 'Process data')
    await processData(fetchedData)
Hugo SUBTIL's avatar
Hugo SUBTIL committed
    log('info', 'Saving data to Cozy')
  } catch (err) {
    log('error', err.message)
  }
Hugo SUBTIL's avatar
Hugo SUBTIL committed
}

Yoan VALLET's avatar
Yoan VALLET committed
// Retrieve data from the API
// Format: { value: "Wh", "date": "YYYY-MM-DD" }
async function getDailyData(token, usagePointID) {
Hugo SUBTIL's avatar
Hugo SUBTIL committed
  const dataRequest = {
    method: 'GET',
    uri:
      baseUrl +
Yoan VALLET's avatar
Yoan VALLET committed
      '/v4/metering_data/daily_consumption?start=' +
Hugo SUBTIL's avatar
Hugo SUBTIL committed
      startDate +
      '&end=' +
      endDate +
      '&usage_point_id=' +
      usagePointID,
    headers: {
      Accept: 'application/json',
      Authorization: 'Bearer ' + token
    }
  }
  try {
    const response = await rp(dataRequest)
    return response
  } catch (error) {
    throw error
  }
}

Yoan VALLET's avatar
Yoan VALLET committed
async function processData(data) {
  const parsedData = JSON.parse(data)
  const intervalData = parsedData.meter_reading.interval_reading
  const formatedData = await formateData(intervalData)
  // Remove data for existing days into the DB
  const filteredData = await hydrateAndFilter(formatedData, 'io.enedis.day', {
    keys: ['year', 'month', 'day']
Hugo SUBTIL's avatar
Hugo SUBTIL committed
  })
Yoan VALLET's avatar
Yoan VALLET committed
  // Store new day data
  await storeData(filteredData, 'io.enedis.day', ['year', 'month', 'day'])
  // Sum year and month values into object with year or year-month as keys
  if (filteredData && filteredData.length > 0) {
    let yearData = {}
    let monthData = {}
    filteredData.forEach(element => {
      element.year in yearData
        ? (yearData[element.year] += element.load)
        : (yearData[element.year] = element.load)
      element.year + '-' + element.month in monthData
        ? (monthData[element.year + '-' + element.month] += element.load)
        : (monthData[element.year + '-' + element.month] = element.load)
    })

    const agregatedYearData = await agregateData('io.enedis.year', yearData)
    await storeData(agregatedYearData, 'io.enedis.year', ['year'])

    const agregatedMonthData = await agregateData('io.enedis.month', monthData)
    await storeData(agregatedMonthData, 'io.enedis.month', ['year', 'month'])
  }
}

/**
 * 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)
Hugo SUBTIL's avatar
Hugo SUBTIL committed
}

Yoan VALLET's avatar
Yoan VALLET committed
/**
 * Format data for DB storage
 * Remove bad data
 */
async function formateData(data) {
  log('info', 'Formating data')
  log('debug', start, 'Start date')
  return data.map(record => {
    const date = moment(record.date, 'YYYY/MM/DD')
    if (record.value != -2) {
      return {
        load: parseFloat(record.value / 1000),
        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'))
      }
    }
  })
}

/**
 * Retrieve and remove old data for a specific doctype
 * Return an Array of agregated data
 */
async function agregateData(doctype, data) {
  let agregatedData = []
  for (let [key, value] of Object.entries(data)) {
    const data = await buildDataFromKey(doctype, key, value)
    const oldValue = await resetInProgressAggregatedData(doctype, data)
    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
  if (doctype === 'io.enedis.year') {
    year = key
    month = 1
  } else {
    const split = key.split('-')
    year = split[0]
    month = split[1]
  }
  return {
    load: Math.round(value * 1000) / 1000,
    year: parseInt(year),
    month: parseInt(month),
    day: 0,
    hour: 0,
    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 io.enedis.year :
 * { load: 76.712, year: 2020, ... } need to be replace by
 * { load: 82.212, year: 2020, ... } after enedis data reprocess
 */
async function resetInProgressAggregatedData(doctype, data) {
  // /!\ 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 === 'io.enedis.year') {
      // Yearly case
      filtered = result.filter(function(el) {
        return el.year == data.year
      })
    } else {
      // Monthly case
      filtered = result.filter(function(el) {
        return el.year == data.year && el.month == data.month
      })
    }
    // 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
Hugo SUBTIL's avatar
Hugo SUBTIL committed
}
Hugo SUBTIL's avatar
Hugo SUBTIL committed

module.exports = new BaseKonnector(start)