Skip to content

Commit

Permalink
Add RTE Tempo API (#166)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierre-Gilles authored Sep 2, 2024
1 parent c7cfff0 commit a8ea8d2
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
{
"allowSingleLine": false
}
]
],
"newline-per-chained-call": "off"
}
}
3 changes: 3 additions & 0 deletions core/api/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ module.exports.load = function Routes(app, io, controllers, middlewares) {
// ecowatt api
app.get('/ecowatt/v4/signals', asyncMiddleware(controllers.ecowattController.getEcowattSignals));

// EDF tempo api
app.get('/edf/tempo/today', asyncMiddleware(controllers.tempoController.getTempoToday));

// OpenAI ask
app.post(
'/openai/ask',
Expand Down
18 changes: 18 additions & 0 deletions core/api/tempo/tempo.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module.exports = function EcowattController(logger, tempoModel) {
/**
* @api {get} /edf/tempo/today Get tempo data today
* @apiName Get tempo data
* @apiGroup Tempo
*/
async function getTempoToday(req, res) {
logger.info(`Tempo.getDataToday`);
const response = await tempoModel.getDataWithRetry();
const cachePeriodInSecond = 60 * 60;
res.set('Cache-control', `public, max-age=${cachePeriodInSecond}`);
res.json(response);
}

return {
getTempoToday,
};
};
118 changes: 118 additions & 0 deletions core/api/tempo/tempo.model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
const axios = require('axios');
const dayjs = require('dayjs');
const retry = require('async-retry');

const utc = require('dayjs/plugin/utc');
const timezone = require('dayjs/plugin/timezone');
const customParseFormat = require('dayjs/plugin/customParseFormat');

dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(customParseFormat);

const TEMPO_CACHE_KEY = 'tempo';
const TEMPO_REDIS_EXPIRY_IN_SECONDS = 5 * 24 * 60 * 60; // 5 days

module.exports = function TempoModel(logger, redisClient) {
// the key is the same as ecowatt
const { ECOWATT_BASIC_HTTP } = process.env;

async function getDataFromCache(date) {
return redisClient.get(`${TEMPO_CACHE_KEY}:${date}`);
}

async function getDataLiveOrFromCache() {
const todayStartDate = dayjs().tz('Europe/Paris').startOf('day').format('YYYY-MM-DDTHH:mm:ssZ');
const tomorrowStartDate = dayjs().tz('Europe/Paris').add(1, 'day').startOf('day').format('YYYY-MM-DDTHH:mm:ssZ');
const tomorrowEndDate = dayjs().tz('Europe/Paris').add(2, 'day').startOf('day').format('YYYY-MM-DDTHH:mm:ssZ');

// Get today data from cache
let todayData = await getDataFromCache(todayStartDate);
let tomorrowData = await getDataFromCache(tomorrowStartDate);

let accessToken;

if (!todayData || !tomorrowData) {
// Get new access token
const { data: dataToken } = await axios.post('https://digital.iservices.rte-france.com/token/oauth/', null, {
headers: {
authorization: `Basic ${ECOWATT_BASIC_HTTP}`,
},
});
accessToken = dataToken.access_token;
}

if (!todayData) {
try {
const { data: todayLiveData } = await axios.get(
'https://digital.iservices.rte-france.com/open_api/tempo_like_supply_contract/v1/tempo_like_calendars',
{
params: {
start_date: todayStartDate,
end_date: tomorrowStartDate,
},
headers: {
authorization: `Bearer ${accessToken}`,
},
},
);
todayData = todayLiveData.tempo_like_calendars.values[0].value.toLowerCase();
// Set cache
await redisClient.set(`${TEMPO_CACHE_KEY}:${todayStartDate}`, todayData, {
EX: TEMPO_REDIS_EXPIRY_IN_SECONDS,
});
} catch (e) {
logger.debug(e);
todayData = 'unknown';
}
}

if (!tomorrowData) {
try {
const { data: tomorrowLiveData } = await axios.get(
'https://digital.iservices.rte-france.com/open_api/tempo_like_supply_contract/v1/tempo_like_calendars',
{
params: {
start_date: tomorrowStartDate,
end_date: tomorrowEndDate,
},
headers: {
authorization: `Bearer ${accessToken}`,
},
},
);
tomorrowData = tomorrowLiveData.tempo_like_calendars.values[0].value.toLowerCase();
// Set cache
await redisClient.set(`${TEMPO_CACHE_KEY}:${tomorrowStartDate}`, tomorrowData, {
EX: TEMPO_REDIS_EXPIRY_IN_SECONDS,
});
} catch (e) {
logger.debug(e);
tomorrowData = 'unknown';
// Set cache for 30 minutes to avoid querying to much the API
await redisClient.set(`${TEMPO_CACHE_KEY}:${tomorrowStartDate}`, tomorrowData, {
EX: 30 * 60, // null set to 30 minutes
});
}
}

return {
today: todayData,
tomorrow: tomorrowData,
};
}

async function getDataWithRetry() {
const options = {
retries: 3,
factor: 2,
minTimeout: 50,
};
return retry(async () => getDataLiveOrFromCache(), options);
}

return {
getDataLiveOrFromCache,
getDataWithRetry,
};
};
4 changes: 4 additions & 0 deletions core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const GoogleModel = require('./api/google/google.model');
const AlexaModel = require('./api/alexa/alexa.model');
const EnedisModel = require('./api/enedis/enedis.model');
const EcowattModel = require('./api/ecowatt/ecowatt.model');
const TempoModel = require('./api/tempo/tempo.model');

// Controllers
const PingController = require('./api/ping/ping.controller');
Expand All @@ -51,6 +52,7 @@ const GoogleController = require('./api/google/google.controller');
const AlexaController = require('./api/alexa/alexa.controller');
const EnedisController = require('./api/enedis/enedis.controller');
const EcowattController = require('./api/ecowatt/ecowatt.controller');
const TempoController = require('./api/tempo/tempo.controller');
const CameraController = require('./api/camera/camera.controller');
const TTSController = require('./api/tts/tts.controller');

Expand Down Expand Up @@ -178,6 +180,7 @@ module.exports = async (port) => {
alexaModel: AlexaModel(logger, db, redisClient, services.jwtService),
enedisModel: EnedisModel(logger, db, redisClient),
ecowattModel: EcowattModel(logger, redisClient),
tempoModel: TempoModel(logger, redisClient),
};

const controllers = {
Expand Down Expand Up @@ -214,6 +217,7 @@ module.exports = async (port) => {
),
enedisController: EnedisController(logger, models.enedisModel),
ecowattController: EcowattController(logger, models.ecowattModel),
tempoController: TempoController(logger, models.tempoModel),
cameraController: CameraController(
logger,
models.userModel,
Expand Down
169 changes: 169 additions & 0 deletions test/core/api/tempo/tempo.controller.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
const request = require('supertest');
const { expect } = require('chai');
const nock = require('nock');

describe('GET /edf/tempo/today', () => {
it('should return tempo data', async () => {
nock('https://digital.iservices.rte-france.com')
.post('/token/oauth/', () => true)
.reply(200, {
access_token: 'access_token',
expires_in: 100,
});
nock('https://digital.iservices.rte-france.com')
.get('/open_api/tempo_like_supply_contract/v1/tempo_like_calendars')
.query(() => true)
.reply(200, {
tempo_like_calendars: {
start_date: '2024-09-02T00:00:00+02:00',
end_date: '2024-09-03T00:00:00+02:00',
values: [
{
start_date: '2024-09-02T00:00:00+02:00',
end_date: '2024-09-03T00:00:00+02:00',
value: 'BLUE',
updated_date: '2024-09-01T10:20:00+02:00',
},
],
},
});
nock('https://digital.iservices.rte-france.com')
.get('/open_api/tempo_like_supply_contract/v1/tempo_like_calendars')
.query(() => true)
.reply(400, {
error: 'TMPLIKSUPCON_TMPLIKCAL_F04',
error_description:
'The value of "end_date" field is incorrect. It is not possible to recover data to this term.',
error_uri: '',
error_details: {
transaction_id: 'Id-2fc9d566cff9ded9d39d0ee7',
},
});
const response = await request(TEST_BACKEND_APP)
.get('/edf/tempo/today')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200);
expect(response.headers).to.have.property('cache-control', 'public, max-age=3600');
expect(response.body).to.deep.equal({
today: 'blue',
tomorrow: 'unknown',
});
// From cache
const responseFromCache = await request(TEST_BACKEND_APP)
.get('/edf/tempo/today')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200);
expect(responseFromCache.headers).to.have.property('cache-control', 'public, max-age=3600');
expect(responseFromCache.body).to.deep.equal({
today: 'blue',
tomorrow: 'unknown',
});
});
it('should return tempo data with 2 unknown', async () => {
nock('https://digital.iservices.rte-france.com')
.post('/token/oauth/', () => true)
.reply(200, {
access_token: 'access_token',
expires_in: 100,
});
nock('https://digital.iservices.rte-france.com')
.get('/open_api/tempo_like_supply_contract/v1/tempo_like_calendars')
.query(() => true)
.reply(400, {
error: 'TMPLIKSUPCON_TMPLIKCAL_F04',
error_description:
'The value of "end_date" field is incorrect. It is not possible to recover data to this term.',
error_uri: '',
error_details: {
transaction_id: 'Id-2fc9d566cff9ded9d39d0ee7',
},
});
nock('https://digital.iservices.rte-france.com')
.get('/open_api/tempo_like_supply_contract/v1/tempo_like_calendars')
.query(() => true)
.reply(400, {
error: 'TMPLIKSUPCON_TMPLIKCAL_F04',
error_description:
'The value of "end_date" field is incorrect. It is not possible to recover data to this term.',
error_uri: '',
error_details: {
transaction_id: 'Id-2fc9d566cff9ded9d39d0ee7',
},
});
const response = await request(TEST_BACKEND_APP)
.get('/edf/tempo/today')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200);
expect(response.headers).to.have.property('cache-control', 'public, max-age=3600');
expect(response.body).to.deep.equal({
today: 'unknown',
tomorrow: 'unknown',
});
});
it('should return tempo data with 2 blue', async () => {
nock('https://digital.iservices.rte-france.com')
.post('/token/oauth/', () => true)
.reply(200, {
access_token: 'access_token',
expires_in: 100,
});
nock('https://digital.iservices.rte-france.com')
.get('/open_api/tempo_like_supply_contract/v1/tempo_like_calendars')
.query(() => true)
.reply(200, {
tempo_like_calendars: {
start_date: '2024-09-02T00:00:00+02:00',
end_date: '2024-09-03T00:00:00+02:00',
values: [
{
start_date: '2024-09-02T00:00:00+02:00',
end_date: '2024-09-03T00:00:00+02:00',
value: 'BLUE',
updated_date: '2024-09-01T10:20:00+02:00',
},
],
},
});
nock('https://digital.iservices.rte-france.com')
.get('/open_api/tempo_like_supply_contract/v1/tempo_like_calendars')
.query(() => true)
.reply(200, {
tempo_like_calendars: {
start_date: '2024-09-02T00:00:00+02:00',
end_date: '2024-09-03T00:00:00+02:00',
values: [
{
start_date: '2024-09-02T00:00:00+02:00',
end_date: '2024-09-03T00:00:00+02:00',
value: 'BLUE',
updated_date: '2024-09-01T10:20:00+02:00',
},
],
},
});
const response = await request(TEST_BACKEND_APP)
.get('/edf/tempo/today')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200);
expect(response.headers).to.have.property('cache-control', 'public, max-age=3600');
expect(response.body).to.deep.equal({
today: 'blue',
tomorrow: 'blue',
});
// From cache
const responseFromCache = await request(TEST_BACKEND_APP)
.get('/edf/tempo/today')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200);
expect(responseFromCache.headers).to.have.property('cache-control', 'public, max-age=3600');
expect(responseFromCache.body).to.deep.equal({
today: 'blue',
tomorrow: 'blue',
});
});
});

0 comments on commit a8ea8d2

Please sign in to comment.