diff --git a/konnector-dev-config.example.json b/konnector-dev-config.example.json index 749879c8b015e92c69227c6d4894ab781edc9ff4..aefd96ba34a67e8514ee69c72a9ecc3971af6cf3 100644 --- a/konnector-dev-config.example.json +++ b/konnector-dev-config.example.json @@ -1,9 +1,14 @@ { "COZY_URL": "http://cozy.tools:8080/", "fields": { - "eglBaseURL": "", - "eglAPIAuthKey": "", "login": 1234567, "password": "" + }, + "COZY_PARAMETERS": { + "secret": { + "eglBaseURL": "", + "eglAPIAuthKey": "", + "boBaseUrl": "https://ecolyo-agent-rec.apps.grandlyon.com/" + } } -} +} \ No newline at end of file diff --git a/src/helpers/prices.js b/src/helpers/prices.js new file mode 100644 index 0000000000000000000000000000000000000000..d56fdebc8b5030a2406838eb8e2008b0b0de9c9d --- /dev/null +++ b/src/helpers/prices.js @@ -0,0 +1,59 @@ +const { log } = require('cozy-konnector-libs') +const axios = require('axios').default +const Sentry = require('@sentry/node') +const { DateTime } = require('luxon') +require('../types/types') + +/** + * @param {string} boBaseUrl + */ +async function getPrices(boBaseUrl) { + const boWaterPricesUrl = new URL('/api/common/prices/1', boBaseUrl).href + + try { + /** @type {Price[]} */ + const prices = (await axios.get(boWaterPricesUrl)).data + return prices + } catch (error) { + log('error', 'Could not fetch BO prices') + Sentry.captureException(error, { + tags: { + section: 'getPrices', + }, + }) + return null + } +} + +/** + * Apply the given prices to the given data array and return the result. + * @param {FormattedData[]} data + * @param {Price[]} fluidPrices + */ +async function applyPrices(data, fluidPrices) { + // Sort prices by descending start date + fluidPrices.sort( + (a, b) => + DateTime.fromISO(b.startDate, { zone: 'UTC' }).toMillis() - + DateTime.fromISO(a.startDate, { zone: 'UTC' }).toMillis() + ) + + return data.map(load => { + // Select the first price that is before the load date + const loadDate = DateTime.fromObject( + { + year: load.year, + month: load.month, + day: load.day, + }, + { zone: 'UTC' } + ) + const fluidPrice = fluidPrices.find(p => { + const startDate = DateTime.fromISO(p.startDate, { zone: 'UTC' }) + return loadDate >= startDate + }) + if (!fluidPrice) return load + return { ...load, price: fluidPrice.price * load.load } + }) +} +module.exports = { getPrices, applyPrices } diff --git a/src/helpers/prices.spec.js b/src/helpers/prices.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..e874a63f7e5a1f9b875fed23f1bd621050ab14db --- /dev/null +++ b/src/helpers/prices.spec.js @@ -0,0 +1,76 @@ +const axios = require('axios') +const { getPrices, applyPrices } = require('../../src/helpers/prices') + +jest.mock('axios') +jest.mock('cozy-konnector-libs', () => ({ + log: jest.fn(), +})) + +describe('getPrices', () => { + const boBaseUrl = 'https://example.com' + + it('should return null when axios throws an error', async () => { + // Mock axios to throw an error + axios.get.mockRejectedValue(new Error('Network Error')) + + const result = await getPrices(boBaseUrl) + + expect(result).toBeNull() + }) +}) + +describe('applyPrices', () => { + it('should apply prices to data', async () => { + const data = [ + { year: 2024, month: 1, day: 31, hour: 0, minute: 0, load: 10 }, + { year: 2024, month: 2, day: 1, hour: 0, minute: 0, load: 10 }, + { year: 2024, month: 2, day: 2, hour: 0, minute: 0, load: 10 }, + ] + const prices = [ + { startDate: '2024-01-31T00:00:00Z', price: 1 }, + { startDate: '2024-02-01T00:00:00Z', price: 2 }, + { startDate: '2024-02-02T00:00:00Z', price: 3 }, + ] + + const result = await applyPrices(data, prices) + + expect(result).toStrictEqual([ + { + year: 2024, + month: 1, + day: 31, + hour: 0, + minute: 0, + load: 10, + price: 10, + }, + { year: 2024, month: 2, day: 1, hour: 0, minute: 0, load: 10, price: 20 }, + { year: 2024, month: 2, day: 2, hour: 0, minute: 0, load: 10, price: 30 }, + ]) + }) + + it('should not apply prices if data is before prices', async () => { + const data = [ + { year: 2020, month: 1, day: 14, hour: 0, minute: 0, load: 10 }, + ] + const prices = [{ startDate: '2024-01-14T00:00:00Z', price: 1 }] + + const result = await applyPrices(data, prices) + + expect(result).toStrictEqual([ + { year: 2020, month: 1, day: 14, hour: 0, minute: 0, load: 10 }, + ]) + }) + + it('should not apply prices if prices are empty', async () => { + const data = [ + { year: 2024, month: 1, day: 14, hour: 0, minute: 0, load: 10 }, + ] + const prices = [] + const result = await applyPrices(data, prices) + + expect(result).toStrictEqual([ + { year: 2024, month: 1, day: 14, hour: 0, minute: 0, load: 10 }, + ]) + }) +}) diff --git a/src/helpers/utils.js b/src/helpers/utils.js index 1e4f4a08ce56638e18c461300b4974bbfa3f8509..6ccbda985c012cdcc1f0067c0e31b1f14abc3da9 100644 --- a/src/helpers/utils.js +++ b/src/helpers/utils.js @@ -13,12 +13,13 @@ function aggregateMonthlyLoad(data) { const monthlyLoad = {} for (const entry of data) { - const { year, month, load } = entry + const { year, month, load, price } = entry const monthKey = `${year}-${month}` if (!monthlyLoad[monthKey]) { monthlyLoad[monthKey] = { load: load, + price: price, year: year, month: month, day: 0, @@ -28,6 +29,7 @@ function aggregateMonthlyLoad(data) { } } else { monthlyLoad[monthKey].load += load + monthlyLoad[monthKey].price += price } } @@ -45,11 +47,12 @@ function aggregateYearlyLoad(data) { const yearlyLoad = {} for (const entry of data) { - const { year, load } = entry + const { year, load, price } = entry if (!yearlyLoad[year]) { yearlyLoad[year] = { load: load, + price: price, year: year, month: 0, day: 0, @@ -59,6 +62,7 @@ function aggregateYearlyLoad(data) { } } else { yearlyLoad[year].load += load + yearlyLoad[year].price += price } } diff --git a/src/index.js b/src/index.js index 0ae911db30ef54a3e89909d790ca31cb66e290c4..8ffc9f8991d3bee84606246b89ca263d8828e2ac 100644 --- a/src/index.js +++ b/src/index.js @@ -19,6 +19,7 @@ const { filterFirstYearlyLoad, } = require('./helpers/utils') const { format } = require('./helpers/format') +const { getPrices, applyPrices } = require('./helpers/prices.js') require('./types/types') const manualExecution = process.env.COZY_JOB_MANUAL_EXECUTION === 'true' @@ -46,54 +47,70 @@ module.exports = new BaseKonnector(start) */ async function start(fields, cozyParameters) { try { - const baseUrl = fields.eglBaseURL || cozyParameters.secret.eglBaseURL - const apiAuthKey = - fields.eglAPIAuthKey || cozyParameters.secret.eglAPIAuthKey + const eglBaseUrl = cozyParameters.secret.eglBaseURL + const boBaseUrl = cozyParameters.secret.boBaseUrl + const apiAuthKey = cozyParameters.secret.eglAPIAuthKey + log('info', 'Authenticating ...') const response = await authenticate( fields.login, fields.password, - baseUrl, + eglBaseUrl, apiAuthKey ) log('info', 'Successfully logged in') - const eglData = await getData(response, baseUrl, apiAuthKey) - + let eglData = await getData(response, eglBaseUrl, apiAuthKey) if (eglData.length === 0) { log('debug', 'No data found') return } + log('info', 'Fetching BO prices') + const prices = await getPrices(boBaseUrl) + + if (prices && prices.length > 0) { + log('info', 'Found BO prices, applying them to EGL data') + eglData = await applyPrices(eglData, prices) + } + log('debug', 'Process EGL daily data') - const filteredDays = await hydrateAndFilter( + const filterDayKeys = [...rangeDate.day.keys, 'load'] + if (prices) filterDayKeys.push('price') + const daysToUpdate = await hydrateAndFilter( eglData, rangeDate.day.doctype, - { - keys: ['year', 'month', 'day', 'load'], - } + { keys: filterDayKeys } ) log('debug', 'Store EGL daily load data') await updateOrCreate( - filteredDays, + daysToUpdate, rangeDate.day.doctype, rangeDate.day.keys ) const { year: firstYear, month: firstMonth } = eglData[0] - log('debug', 'Aggregate EGL yearly load data') + log('debug', 'Aggregate EGL monthly load data') const monthlyLoads = aggregateMonthlyLoad(eglData) log('debug', 'Filter first month aggregate if already in database') - const filteredMonthlyLoads = filterFirstMonthlyLoad( + 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 EGL monthly load data') await updateOrCreate( - filteredMonthlyLoads, + monthsToUpdate, rangeDate.month.doctype, rangeDate.month.keys ) @@ -102,11 +119,22 @@ async function start(fields, cozyParameters) { const yearlyLoads = aggregateYearlyLoad(monthlyLoads) log('debug', 'Filter first year aggregate if already in database') - const filteredYearlyLoads = filterFirstYearlyLoad(firstYear, yearlyLoads) + 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 EGL yearly load data') await updateOrCreate( - filteredYearlyLoads, + yearsToUpdate, rangeDate.year.doctype, rangeDate.year.keys ) diff --git a/src/types/types.js b/src/types/types.js index a4b60703edb6b18cadaaa8ac533382286724c195..bbd157ba80b359ae5f8db7277b0ec134d80c7ed3 100644 --- a/src/types/types.js +++ b/src/types/types.js @@ -8,6 +8,7 @@ * @property {number} day - The day of the data point. * @property {number} hour - The hour of the data point (in this case, always 0). * @property {number} minute - The minute of the data point (in this case, always 0). + * @property {number} price - The price of the data point. * @property {string} type - The type of the data point. */ @@ -37,3 +38,11 @@ * @property {number} num_abt * @property {string} token */ + +/** + * @typedef {Object} Price + * @property {number} fluidtype + * @property {number} price + * @property {string} startDate + * @property {string} endDate + */