Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RTE Tempo API #166

Merged
merged 3 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
});
});
});
Loading