Skip to content

Instantly share code, notes, and snippets.

@kaminski-tomasz
Created February 1, 2021 22:42
Show Gist options
  • Save kaminski-tomasz/b5a97766161a11fb822743c889e985b0 to your computer and use it in GitHub Desktop.
Save kaminski-tomasz/b5a97766161a11fb822743c889e985b0 to your computer and use it in GitHub Desktop.
Node.js script that downloads COVID cases for all countries (confirmed/deaths/recovered) and transforms it to tabular form.
const superagent = require("superagent");
const ExcelJS = require('exceljs');
const CORONA_API = 'https://coronavirus-tracker-api.herokuapp.com';
/**
* @typedef {{
* id: number,
* name: string,
* }} Country
* @typedef {{
* id: number,
* timelines: {
* confirmed: Object.<string, number>,
* deaths: Object.<string, number>,
* recovered: Object.<string, number>,
* }
* }} CountryTimeline
*/
/**
* @typedef {{
* id: number,
* name: string,
* }} Country
* @typedef {{
* id: number,
* timelines: {
* confirmed: Object.<string, number>,
* deaths: Object.<string, number>,
* recovered: Object.<string, number>,
* }
* }} CountryTimeline
*/
/**
* @param {Country[]} countries
* @param {CountryTimeline[]} countryTimelines
* @returns {{
* confirmed: {columns: any[], rows: any[][]},
* deaths: {columns: any[], rows: any[][]},
* recovered: {columns: any[], rows: any[][]},
* }} excel rows
*/
function transformTimelines(countries, countryTimelines) {
const sheetColumnDates = {
confirmed: new Set(),
deaths: new Set(),
recovered: new Set(),
};
const rows = {
confirmed: [],
deaths: [],
recovered: [],
}
// columns
countryTimelines.forEach(countryTimeline => {
Object.keys(countryTimeline.timelines).forEach(timelineName => {
Object.keys(countryTimeline.timelines[timelineName])
.forEach(date => sheetColumnDates[timelineName].add(date));
});
});
// rows
countryTimelines.forEach(countryTimeline => {
const countryName = countries.find(country => country.id === countryTimeline.id).name;
Object.keys(countryTimeline.timelines).forEach(timelineName => {
const timeline = countryTimeline.timelines[timelineName];
if (Object.keys(timeline).length === 0 ) {
return;
}
let dailyCases = [];
const countryTimelineDates = new Set(Object.keys(timeline));
const allTimelineDates = [...sheetColumnDates[timelineName]];
allTimelineDates.forEach(timelineDate => {
dailyCases.push(countryTimelineDates.has(timelineDate)? timeline[timelineDate]: null);
});
rows[timelineName].push([countryName, ...dailyCases]);
});
});
/**
* @param {Set.<string>} dates
*/
function transformDatesToColumns(dates) {
const datesSorted = [...dates].map(d => new Date(d));
datesSorted.sort((d1,d2) => d1.getTime() - d2.getTime());
return datesSorted.map(date => (
{header: date, width: 15}
));
}
return {
confirmed: {
columns: [
{header: "Country", width: 30},
...transformDatesToColumns(sheetColumnDates.confirmed),
],
rows: [
...rows.confirmed,
]
},
deaths: {
columns: [
{header: "Country", width: 30},
...transformDatesToColumns(sheetColumnDates.deaths),
],
rows: [
...rows.deaths
]
},
recovered: {
columns: [
{header: "Country", width: 30},
...transformDatesToColumns(sheetColumnDates.recovered),
],
rows: [
...rows.recovered
]
},
};
}
/**
* @returns {Promise<Country[]>}
*/
async function fetchCountries() {
return superagent.get(`${CORONA_API}/v2/locations`)
.then(res => res.body.locations.map(location => ({
id: location.id,
name: location.country + (location.province ? ` (${location.province})` : '')
})));
}
/**
* @param {number} countryId
* @returns {Promise<CountryTimeline[]>}
*/
async function fetchCountryTimelines(countryId) {
return superagent.get(`${CORONA_API}/v2/locations/${countryId}`)
.then(res => ({
id: res.body.location.id,
timelines: {
confirmed: res.body.location.timelines.confirmed.timeline,
deaths: res.body.location.timelines.deaths.timeline,
recovered: res.body.location.timelines.recovered.timeline,
}
}));
}
/**
* @returns {Promise<{countries: Country[], countryTimelines: CountryTimeline[]}>}
*/
async function fetchTimelineData() {
try {
const countries = await fetchCountries();
console.log('countries fetched');
const countryIds = countries.map(country => country.id);
const countryTimelines = await (Promise.all(
countryIds.map(countryId => fetchCountryTimelines(countryId)))
);
console.log('country timelines fetched');
return {
countries: countries,
countryTimelines: countryTimelines
};
} catch (e) {
throw new Error(e);
}
}
fetchTimelineData().then(timelineData => {
const workbook = new ExcelJS.Workbook();
const sheets = {
confirmed: "Confirmed cases",
deaths: "Death cases",
recovered: "Recovery cases",
};
const excelData = transformTimelines(timelineData.countries, timelineData.countryTimelines);
const options = {views: [{state: 'frozen', xSplit: 1, ySplit: 1}]};
Object.keys(sheets).forEach(sheetName => {
const worksheet = workbook.addWorksheet(sheets[sheetName], options);
worksheet.columns = excelData[sheetName].columns;
worksheet.addRows(excelData[sheetName].rows)
});
workbook.xlsx.writeFile("covid-cases.xlsx");
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment