Skip to content
Snippets Groups Projects
index.js 9.93 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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
    }