Skip to content
Snippets Groups Projects
index.js 9.93 KiB
Newer Older
Romain CREY's avatar
Romain CREY committed
const {
  BaseKonnector,
  log,
  errors,
  addData,
  hydrateAndFilter,
  cozyClient
unknown's avatar
unknown committed
} = require("cozy-konnector-libs");
Hugo's avatar
Hugo committed

unknown's avatar
unknown committed
// const fetch = require('node-fetch')
const rp = require("request-promise");
const moment = require("moment");
require("moment-timezone");
Romain CREY's avatar
Romain CREY committed

unknown's avatar
unknown committed
moment.locale("fr"); // set the language
moment.tz.setDefault("Europe/Paris"); // set the timezone
Romain CREY's avatar
Romain CREY committed

Hugo's avatar
Hugo committed
const manualExecution =
unknown's avatar
unknown committed
  process.env.COZY_JOB_MANUAL_EXECUTION === "true" ? true : false;
Hugo's avatar
Hugo committed

const startDate = manualExecution
unknown's avatar
unknown committed
      .subtract(1, "year")
      .format("MM/DD/YYYY")
unknown's avatar
unknown committed
      .subtract(3, "year")
      .format("MM/DD/YYYY");
Hugo's avatar
Hugo committed

unknown's avatar
unknown committed
const endDate = moment().format("MM/DD/YYYY");
Hugo's avatar
Hugo committed
// const timeRange = ['day', 'month', 'year']
const rangeDate = {
  day: {
unknown's avatar
unknown committed
    doctype: "com.grandlyon.egl.day",
    keys: ["year", "month", "day"]
unknown's avatar
unknown committed
    doctype: "com.grandlyon.egl.month",
    keys: ["year", "month"]
unknown's avatar
unknown committed
    doctype: "com.grandlyon.egl.year",
    keys: ["year"]
unknown's avatar
unknown committed
};
Romain CREY's avatar
Romain CREY committed

unknown's avatar
unknown committed
module.exports = new BaseKonnector(start);
Romain CREY's avatar
Romain CREY 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
async function start(fields, cozyParameters) {
  try {
Hugo's avatar
Hugo committed
    // const baseUrl = fields.eglBaseURL
    // const apiAuthKey = fields.eglAPIAuthKey
unknown's avatar
unknown committed
    const baseUrl = cozyParameters.secret.eglBaseURL;
    const apiAuthKey = cozyParameters.secret.eglAPIAuthKey;
    log("info", "Authenticating ...");
Romain CREY's avatar
Romain CREY committed
    const response = await authenticate(
      fields.login,
      fields.password,
      baseUrl,
      apiAuthKey
unknown's avatar
unknown committed
    );
    log("info", "Successfully logged in");
Hugo's avatar
Hugo committed

unknown's avatar
unknown committed
    const eglData = await getData(response, baseUrl, apiAuthKey);
Hugo's avatar
Hugo committed
    if (eglData) {
unknown's avatar
unknown committed
      log("debug", "Process egl daily data");
Hugo's avatar
Hugo committed
      const processedLoadData = await processData(
        eglData,
        rangeDate.day.doctype,
        rangeDate.day.keys
unknown's avatar
unknown committed
      );
      log("debug", "Agregate egl load data for month and year");
      await agregateMonthAndYearData(processedLoadData);
Hugo's avatar
Hugo committed
    } else {
unknown's avatar
unknown committed
      log("debug", "No data found");
Hugo's avatar
Hugo committed
    }
Romain CREY's avatar
Romain CREY committed
  } catch (error) {
unknown's avatar
unknown committed
    throw new Error(error.message);
Romain CREY's avatar
Romain CREY committed
  }
}
Hugo's avatar
Hugo committed

/**
 * Parse data
 * Remove existing data from DB using hydrateAndFilter
 * Store filtered data
 * Return the list of filtered data
 */
async function processData(data, doctype, filterKeys) {
  // const formatedData = await formateData(data)
unknown's avatar
unknown committed
  log("debug", "processData - data formated");
Hugo's avatar
Hugo committed
  // Remove data for existing days into the DB
  const filteredData = await hydrateAndFilter(data, doctype, {
    keys: filterKeys
unknown's avatar
unknown committed
  });
  log("debug", "processData - data filtered");
Hugo's avatar
Hugo committed
  // Store new day data
unknown's avatar
unknown committed
  await storeData(filteredData, doctype, filterKeys);
  return filteredData;
Hugo's avatar
Hugo committed
}

/**
 * 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) {
unknown's avatar
unknown committed
    let monthData = {};
    let yearData = {};
Hugo's avatar
Hugo committed

    data.forEach(element => {
unknown's avatar
unknown committed
      element.year + "-" + element.month in monthData
        ? (monthData[element.year + "-" + element.month] += element.load)
        : (monthData[element.year + "-" + element.month] = element.load);
Hugo's avatar
Hugo committed
      element.year in yearData
        ? (yearData[element.year] += element.load)
unknown's avatar
unknown committed
        : (yearData[element.year] = element.load);
    });
Hugo's avatar
Hugo committed
    // Agregation for Month data
    const agregatedMonthData = await buildAgregatedData(
      monthData,
unknown's avatar
unknown committed
      "com.grandlyon.egl.month"
    );
    await storeData(agregatedMonthData, "com.grandlyon.egl.month", [
      "year",
      "month"
    ]);
Hugo's avatar
Hugo committed
    // Agregation for Year data
    const agregatedYearData = await buildAgregatedData(
      yearData,
unknown's avatar
unknown committed
      "com.grandlyon.egl.year"
    );
    await storeData(agregatedYearData, "com.grandlyon.egl.year", ["year"]);
Romain CREY's avatar
Romain CREY committed

Hugo's avatar
Hugo committed
/**
 * Retrieve and remove old data for a specific doctype
 * Return an Array of agregated data
 */
async function buildAgregatedData(data, doctype) {
unknown's avatar
unknown committed
  log("info", "entering buildAgregatedData");
  let agregatedData = [];
Hugo's avatar
Hugo committed
  for (let [key, value] of Object.entries(data)) {
unknown's avatar
unknown committed
    const data = await buildDataFromKey(doctype, key, value);
    const oldValue = await resetInProgressAggregatedData(data, doctype);
    log("info", "Dataload + oldvalue is " + data.load + " + " + oldValue);
    data.load += oldValue;
    agregatedData.push(data);
Hugo's avatar
Hugo committed
  }
unknown's avatar
unknown committed
  return agregatedData;
Hugo's avatar
Hugo committed
}

Romain CREY's avatar
Romain CREY committed
async function authenticate(login, password, baseUrl, apiAuthKey) {
  const authRequest = {
    method: "POST",
unknown's avatar
unknown committed
    uri: baseUrl + "/connect.aspx",
Romain CREY's avatar
Romain CREY committed
    headers: {
      AuthKey: apiAuthKey,
      "Content-Type": "application/x-www-form-urlencoded"
Romain CREY's avatar
Romain CREY committed
    },
unknown's avatar
unknown committed
    formData: {
Romain CREY's avatar
Romain CREY committed
      login: login,
      pass: password
unknown's avatar
unknown committed
    },
    json: true
  };
  const response = await rp(authRequest);
Yoan VALLET's avatar
Yoan VALLET committed
  if (response.codeRetour === 100) {
unknown's avatar
unknown committed
    return response;
Yoan VALLET's avatar
Yoan VALLET committed
  } else {
unknown's avatar
unknown committed
    throw new Error(errors.LOGIN_FAILED);
Romain CREY's avatar
Romain CREY committed
  }
}

async function getData(response, baseUrl, apiAuthKey) {
unknown's avatar
unknown committed
  log("debug", "Start date : " + startDate);
  log("debug", "End date : " + endDate);
Romain CREY's avatar
Romain CREY committed
  const dataRequest = {
    method: "POST",
unknown's avatar
unknown committed
    uri: baseUrl + "/getAllAgregatsByAbonnement.aspx",
Romain CREY's avatar
Romain CREY committed
    headers: {
      AuthKey: apiAuthKey,
unknown's avatar
unknown committed
      "Content-Type": "application/x-www-form-urlencoded"
Romain CREY's avatar
Romain CREY committed
    },
unknown's avatar
unknown committed
    form: {
Romain CREY's avatar
Romain CREY committed
      token: response.resultatRetour.token,
      num_abt: response.resultatRetour.num_abt,
      date_debut: startDate,
      date_fin: endDate
unknown's avatar
unknown committed
    },
    json: true
  };
Romain CREY's avatar
Romain CREY committed
  try {
unknown's avatar
unknown committed

    const responseEgl = await rp(dataRequest).then(eglRawData => {
      eglRawData.resultatRetour.sort(function(a, b) {
        return new Date(a.DateReleve) - new Date(b.DateReleve);
      });
      return eglRawData;
    });
Yoan VALLET's avatar
Yoan VALLET committed
    switch (responseEgl.codeRetour) {
Romain CREY's avatar
Romain CREY committed
      case 100:
unknown's avatar
unknown committed
        return format(responseEgl);
Romain CREY's avatar
Romain CREY committed
      case -2:
unknown's avatar
unknown committed
        throw errors.LOGIN_FAILED;
Romain CREY's avatar
Romain CREY committed
      case -1:
unknown's avatar
unknown committed
        throw errors.VENDOR_DOWN;
Romain CREY's avatar
Romain CREY committed
      default:
unknown's avatar
unknown committed
        throw errors.UNKNOWN_ERROR;
Romain CREY's avatar
Romain CREY committed
    }
  } catch (error) {
unknown's avatar
unknown committed
    log("debug", "Error from getAllAgregatsByAbonnement");
    throw new Error(errors.VENDOR_DOWN);
Romain CREY's avatar
Romain CREY committed
  }
}

function format(response) {
unknown's avatar
unknown committed
  log("info", "origin response size is : " + response.resultatRetour.length);
  // Store first value as reference for index processing
unknown's avatar
unknown committed
  let refValue = response.resultatRetour[0];
  // Create copy of data without first value
Hugo's avatar
Hugo committed
  const data = response.resultatRetour
    .slice(1)
unknown's avatar
unknown committed
    .filter(value => value.ValeurIndex);
  log("info", "filtered size is : " + data.length);
unknown's avatar
unknown committed
    const time = moment(value.DateReleve, moment.ISO_8601);
    const procesedLoad = value.ValeurIndex - refValue.ValeurIndex;
unknown's avatar
unknown committed
        "error",
        `processing load for day ${parseInt(time.format("D"))}/${parseInt(
          time.format("M")
        )}/${parseInt(time.format("YYYY"))}, value is : ${procesedLoad}`
      );
      throw errors.VENDOR_DOWN;
unknown's avatar
unknown committed
    refValue = value;
unknown's avatar
unknown committed
      year: parseInt(time.format("YYYY")),
      month: parseInt(time.format("M")),
      day: parseInt(time.format("D")),
      hour: 0,
      minute: 0,
      type: value.TypeAgregat
unknown's avatar
unknown committed
    };
  });
Hugo's avatar
Hugo committed
/**
 * Save data in the right doctype db and prevent duplicated keys
 */
async function storeData(data, doctype, filterKeys) {
unknown's avatar
unknown committed
  log("debug", "Store into " + doctype);
  log("debug", "Store into keys : " + filterKeys);
Hugo's avatar
Hugo committed
  // data.map(v => {
  //   log("info", "Saving data " + v.load + " for " + v.day + "/" + v.month + "/" + v.year);
  // });
Hugo's avatar
Hugo committed
  const filteredDocuments = await hydrateAndFilter(data, doctype, {
    keys: filterKeys
unknown's avatar
unknown committed
  });
  return await addData(filteredDocuments, doctype);
Hugo's avatar
Hugo committed
/**
 * 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) {
unknown's avatar
unknown committed
  let year, month, day, hour;
  if (doctype === "com.grandlyon.egl.year") {
    year = key;
    month = 1;
    day = 0;
    hour = 0;
  } else if (doctype === "com.grandlyon.egl.month") {
    const split = key.split("-");
    year = split[0];
    month = split[1];
    day = 0;
    hour = 0;
Hugo's avatar
Hugo committed
  } else {
unknown's avatar
unknown committed
    const split = key.split("-");
    year = split[0];
    month = split[1];
    day = split[2];
    hour = split[3];
Hugo's avatar
Hugo committed
  }
  return {
    load: Math.round(value * 10000) / 10000,
    year: parseInt(year),
    month: parseInt(month),
    day: parseInt(day),
    hour: parseInt(hour),
    minute: 0
unknown's avatar
unknown committed
  };
Hugo's avatar
Hugo committed
}

/**
 * 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.egl.month :
 * { load: 76.712, month: 2020, ... } need to be replace by
Hugo's avatar
Hugo committed
 * { load: 82.212, month: 2020, ... } after egl data reprocess
Hugo's avatar
Hugo committed
async function resetInProgressAggregatedData(data, doctype) {
  // /!\ Warning: cannot use mongo queries because not supported for dev by cozy-konnectors-libs
unknown's avatar
unknown committed
  log("debug", "Remove aggregated data for " + doctype);
  const result = await cozyClient.data.findAll(doctype);
Hugo's avatar
Hugo committed
  if (result && result.length > 0) {
    // Filter data to remove
unknown's avatar
unknown committed
    var filtered = [];
    if (doctype === "com.grandlyon.egl.year") {
      // Yearly case
      filtered = result.filter(function(el) {
unknown's avatar
unknown committed
        return el.year == data.year;
      });
    } else if (doctype === "com.grandlyon.egl.month") {
      // Monthly case
Hugo's avatar
Hugo committed
      filtered = result.filter(function(el) {
unknown's avatar
unknown committed
        return el.year == data.year && el.month == data.month;
      });
Hugo's avatar
Hugo committed
    } else {
      // Hourly case
      filtered = result.filter(function(el) {
        return (
Hugo's avatar
Hugo committed
          el.year == data.year &&
          el.month == data.month &&
          el.day == data.day &&
          el.hour == data.hour
unknown's avatar
unknown committed
        );
      });
unknown's avatar
unknown committed
    let sum = 0.0;
    for (const doc of filtered) {
unknown's avatar
unknown committed
      sum += doc.load;
      log("debug", "Removing this entry for " + doc.load);
      await cozyClient.data.delete(doctype, doc);
unknown's avatar
unknown committed
    return sum;
Romain CREY's avatar
Romain CREY committed
  }
unknown's avatar
unknown committed
  return 0.0;
Romain CREY's avatar
Romain CREY committed
}