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

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

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

Hugo's avatar
Hugo 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 {
    // resetting data for demo only
    // await resetData()
Hugo's avatar
Hugo 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
Hugo's avatar
Hugo committed
    );
    log("info", "Successfully logged in");
    Promise.all(
      timeRange.map(timeStep =>
        processData(timeStep, response, baseUrl, apiAuthKey)
      )
Hugo's avatar
Hugo committed
    );
Romain CREY's avatar
Romain CREY committed
  } catch (error) {
Hugo's avatar
Hugo committed
    throw new Error(error.message);
Romain CREY's avatar
Romain CREY committed
  }
}
async function processData(timeStep, response, baseUrl, apiAuthKey) {
Hugo's avatar
Hugo committed
  const doctype = rangeDate[timeStep].doctype;
  log("info", "Getting data TIMESTEP : " + timeStep);
  const loadProfile = await getData(response, baseUrl, apiAuthKey);
  log("info", "Saving data to Cozy");
  log("info", "Response length : " + loadProfile.length);
  if (doctype === rangeDate.day.doctype) {
Hugo's avatar
Hugo committed
    await storeData(loadProfile, rangeDate.day.doctype, rangeDate.day.keys);
  } else if (doctype === rangeDate.month.doctype) {
Hugo's avatar
Hugo committed
    await resetInProgressAggregatedData(rangeDate.month.doctype);
    const monthlyData = processMonthlyAggregation(loadProfile, rangeDate.month);
Hugo's avatar
Hugo committed
    log("info", "Saving monthly data");
Hugo's avatar
Hugo committed
    await storeData(monthlyData, rangeDate.month.doctype, rangeDate.month.keys);
  } else if (doctype === rangeDate.year.doctype) {
Hugo's avatar
Hugo committed
    await resetInProgressAggregatedData(rangeDate.year.doctype);
    const yearlyData = processYearAggregation(
      loadProfile,
      rangeDate.year.doctype
Hugo's avatar
Hugo committed
    );
Hugo's avatar
Hugo committed
    log("info", "Saving yearly data");
Hugo's avatar
Hugo committed
    await storeData(yearlyData, rangeDate.year.doctype, rangeDate.year.keys);
Hugo's avatar
Hugo committed
    throw new Error("Unkonw range type: " + doctype);
Romain CREY's avatar
Romain CREY committed

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

async function getData(response, baseUrl, apiAuthKey) {
Hugo's avatar
Hugo committed
  log("debug", "Start date : " + startDate);
  log("debug", "End date : " + endDate);
Romain CREY's avatar
Romain CREY committed
  const dataRequest = {
Hugo's avatar
Hugo committed
    method: "POST",
    uri: baseUrl + "/getAllAgregatsByAbonnement.aspx",
Romain CREY's avatar
Romain CREY committed
    headers: {
      AuthKey: apiAuthKey,
Hugo's avatar
Hugo committed
      "Content-Type": "application/x-www-form-urlencoded"
Romain CREY's avatar
Romain CREY committed
    },
    form: {
      token: response.resultatRetour.token,
      num_abt: response.resultatRetour.num_abt,
      date_debut: startDate,
      date_fin: endDate
    },
    json: true
Hugo's avatar
Hugo committed
  };
Romain CREY's avatar
Romain CREY committed
  try {
Hugo's avatar
Hugo committed
    const responseEgl = await rp(dataRequest);
Yoan VALLET's avatar
Yoan VALLET committed
    switch (responseEgl.codeRetour) {
Romain CREY's avatar
Romain CREY committed
      case 100:
Hugo's avatar
Hugo committed
        return format(responseEgl);
Romain CREY's avatar
Romain CREY committed
      case -2:
Hugo's avatar
Hugo committed
        throw new Error(errors.LOGIN_FAILED);
Romain CREY's avatar
Romain CREY committed
      case -1:
Hugo's avatar
Hugo committed
        throw new Error(errors.VENDOR_DOWN);
Romain CREY's avatar
Romain CREY committed
      default:
Hugo's avatar
Hugo committed
        throw new Error(errors.UNKNOWN_ERROR);
Romain CREY's avatar
Romain CREY committed
    }
  } catch (error) {
Hugo's avatar
Hugo committed
    throw new Error(errors.VENDOR_DOWN);
Romain CREY's avatar
Romain CREY committed
  }
}

function format(response) {
Hugo's avatar
Hugo committed
  log("info", "origin response size is : " + response.resultatRetour.length)
Hugo's avatar
Hugo committed
  const data = response.resultatRetour
    .slice(1)
Hugo's avatar
Hugo committed
    .filter(value => value.ValeurIndex);
  const dataLen = data.length;
Hugo's avatar
Hugo committed
  log("info", "filtered size is : " + dataLen);
Hugo's avatar
Hugo committed
  const mapData = data.map((value, index) => {
    const time = moment(value.DateReleve, moment.ISO_8601);
    if (index + 1 < dataLen) {
Hugo's avatar
Hugo committed
      log(
Hugo's avatar
Hugo committed
        "info",
Hugo's avatar
Hugo committed
        "date is " +
Hugo's avatar
Hugo committed
          value.DateReleve +
Hugo's avatar
Hugo committed
          " SUBSTRACTING : " +
          data[index + 1].ValeurIndex +
          " - " +
Hugo's avatar
Hugo committed
          value.ValeurIndex
Hugo's avatar
Hugo committed
      );
      return {
        load: data[index + 1].ValeurIndex - value.ValeurIndex,
Hugo's avatar
Hugo committed
        year: parseInt(time.format("YYYY")),
        month: parseInt(time.format("M")),
        day: parseInt(time.format("D")),
        hour: 0,
        minute: 0,
        type: value.TypeAgregat
Hugo's avatar
Hugo committed
      };
    } else {
Hugo's avatar
Hugo committed
      log("info", "end of data - date is : " + value.DateReleve);
Hugo's avatar
Hugo committed
      return {
        load: null,
Hugo's avatar
Hugo committed
        year: parseInt(time.format("YYYY")),
        month: parseInt(time.format("M")),
        day: parseInt(time.format("D")),
Hugo's avatar
Hugo committed
        hour: 0,
        minute: 0,
        type: value.TypeAgregat
Hugo's avatar
Hugo committed
      };
Hugo's avatar
Hugo committed
  });
  return mapData;
function processYearAggregation(data, doctype) {
Hugo's avatar
Hugo committed
  log("info", "Start aggregation for : " + doctype);
  const grouped = data.reduce(reduceYearFunction, {});
  return Object.values(grouped);
}

function processMonthlyAggregation(data, range) {
Hugo's avatar
Hugo committed
  log("info", "Start aggregation for : " + range.doctype);
  // Filter by year
Hugo's avatar
Hugo committed
  const tmpData = groupBy(data, "year");
  const keys = Object.keys(tmpData);
  var dataToStore = [];
  // Monthly aggregation
  for (const index in keys) {
    // Get daily data of a year
Hugo's avatar
Hugo committed
    var monthlyData = tmpData[keys[index]];
    // Monthly aggregation
Hugo's avatar
Hugo committed
    var aggregatedData = monthlyData.reduce(reduceMonthFunction, {});
Hugo's avatar
Hugo committed
    dataToStore = dataToStore.concat(Object.values(aggregatedData));
Hugo's avatar
Hugo committed
  return dataToStore;
}

function groupBy(xs, key) {
  return xs.reduce(function(rv, x) {
Hugo's avatar
Hugo committed
    (rv[x[key]] = rv[x[key]] || []).push(x);
    return rv;
  }, {});
}

function reduceYearFunction(acc, x) {
Hugo's avatar
Hugo committed
  var id = acc[x.year];
Hugo's avatar
Hugo committed
    id.load += x.load;
Hugo's avatar
Hugo committed
    acc[x.year] = x;
Hugo's avatar
Hugo committed
  return acc;
}

function reduceMonthFunction(acc, x) {
Hugo's avatar
Hugo committed
  var id = acc[x.month];
Hugo's avatar
Hugo committed
    id.load += x.load;
Hugo's avatar
Hugo committed
    acc[x.month] = x;
Hugo's avatar
Hugo committed
  return acc;
}

async function storeData(data, doctype, keys) {
Hugo's avatar
Hugo committed
  log("debug", "Store into " + doctype);
Hugo's avatar
Hugo committed
  log("debug", "Store into keys : " + keys);
  data.map(v => {
Hugo's avatar
Hugo committed
    log("info", "Saving data " + v.load + " for " + v.day + "/" + v.month + "/" + v.year);
Hugo's avatar
Hugo committed
  });
  return hydrateAndFilter(data, doctype, {
    keys: keys
Romain CREY's avatar
Romain CREY committed
  }).then(filteredDocuments => {
Hugo's avatar
Hugo committed
    addData(filteredDocuments, doctype);
  });
/**
 * 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
 * { load: 82.212, month: 2020, ... } after grdf data reprocess
 */
async function resetInProgressAggregatedData(doctype) {
  // /!\ Warning: cannot use mongo queries because not supported for dev by cozy-konnectors-libs
Hugo's avatar
Hugo committed
  log("debug", doctype, "Remove aggregated data for");
  const result = await cozyClient.data.findAll(doctype);
  if (result.error || result.length <= 0) {
Romain CREY's avatar
Romain CREY committed
    // eslint-disable-next-line no-console
Hugo's avatar
Hugo committed
    console.warn("Error while fetching loads, doctype not found ");
Hugo's avatar
Hugo committed
    const currentDate = moment();
    // Filter data to remove
Hugo's avatar
Hugo committed
    var filtered = [];
    if (doctype === rangeDate.year.doctype) {
      // Yearly case
      filtered = result.filter(function(el) {
Hugo's avatar
Hugo committed
        return el.year == currentDate.year();
      });
    } else {
      // Monthly case
      filtered = result.filter(function(el) {
        return (
          el.year == currentDate.year() &&
Hugo's avatar
Hugo committed
          el.month == parseInt(moment().format("M"))
        );
      });
    }
    // Remove data
    for (const doc of filtered) {
Hugo's avatar
Hugo committed
      log("debug", doc, "Removing this entry for " + doctype);
      await cozyClient.data.delete(doctype, doc);
Romain CREY's avatar
Romain CREY committed
  }
}