purchasing-power-parity-advanced
Version:
An advanced PPP conversion calculator with optional flags and country names, and country code listing functions.
157 lines (147 loc) • 6.18 kB
JavaScript
#!/usr/bin/env node
const fetch = require('node-fetch').default;
const { parse } = require('csv-parse/sync');
const { countries } = require('country-data');
// build a lookup map from ISO3 to country metadata
const countryMap = {};
if (countries.all && Array.isArray(countries.all)) {
countries.all.forEach(c => { if (c.alpha3) countryMap[c.alpha3] = c; });
}
const emojiFlags = require('emoji-flags');
const currencyCodes = require('currency-codes');
const getSymbolFromCurrency = require('currency-symbol-map');
// Async loader for PPP data from remote CSV
const PPP_CSV_URL = 'https://raw.githubusercontent.com/datasets/ppp/main/data/ppp-gdp.csv';
let cachedPPP = null;
async function loadPPPData() {
if (cachedPPP) return cachedPPP;
const res = await fetch(PPP_CSV_URL);
if (!res.ok) throw new Error(`Failed to fetch PPP CSV: ${res.statusText}`);
const text = await res.text();
const records = parse(text, { columns: true, skip_empty_lines: true });
// build a map from alpha2 to alpha3 for country codes
const alpha2To3 = {};
countries.all.forEach(c => { if (c.alpha2 && c.alpha3) alpha2To3[c.alpha2] = c.alpha3; });
const map = {};
for (const rec of records) {
const code2 = rec['Country ID'];
const code = alpha2To3[code2];
const year = parseInt(rec['Year'], 10);
const pppVal = parseFloat(rec['PPP']);
if (!code || isNaN(pppVal)) continue;
if (!map[code] || year > map[code].date) {
map[code] = { date: year, ppp: pppVal };
}
}
cachedPPP = map;
return map;
}
/**
* Convert an amount from origin to target countries using PPP factors.
* @param {string} originCode - ISO3 code of the origin country.
* @param {number} amount - Amount in the origin country's currency.
* @param {string[]} targetCodes - Array of ISO3 codes for target countries.
* @param {object} [options] - Optional settings.
* @param {boolean} [options.includeFlags=true] - Whether to include country flag emojis.
* @param {boolean} [options.includeNames=true] - Whether to include full country names.
* @returns {Promise<{origin:object, results:object[]}>}
*/
async function convertPPP(originCode, amount, targetCodes, options = {}) {
const { includeFlags = true, includeNames = true } = options;
const latestPPP = await loadPPPData();
const origin = latestPPP[originCode];
if (!origin) {
throw new Error(`PPP data not available for origin country: ${originCode}`);
}
// gather origin metadata
const originCountry = countryMap[originCode] || {};
const originAlpha2 = originCountry.alpha2;
const originFlag = includeFlags && originAlpha2 && emojiFlags.countryCode(originAlpha2) ? emojiFlags.countryCode(originAlpha2).emoji : null;
const originCurrencyCode = originCountry.currencies ? originCountry.currencies[0] : null;
const originCurrencyInfo = originCurrencyCode && currencyCodes.code(originCurrencyCode);
const originCurrencyName = originCurrencyInfo ? originCurrencyInfo.currency : null;
const originCurrencySymbol = originCurrencyCode ? getSymbolFromCurrency(originCurrencyCode) : null;
const originFullName = includeNames ? originCountry.name : undefined;
const results = targetCodes.map(code => {
const target = latestPPP[code];
if (!target) {
return { code, amount: null,
flag: includeFlags ? null : undefined,
currencyCode: null,
currencyName: null,
currencySymbol: null,
countryName: includeNames ? null : undefined
};
}
const intlDollars = amount / origin.ppp;
const converted = intlDollars * target.ppp;
// gather target metadata
const targetCountry = countryMap[code] || {};
const targetAlpha2 = targetCountry.alpha2;
const targetFlag = includeFlags && targetAlpha2 && emojiFlags.countryCode(targetAlpha2) ? emojiFlags.countryCode(targetAlpha2).emoji : null;
const targetCurrencyCode = targetCountry.currencies ? targetCountry.currencies[0] : null;
const targetCurrencyInfo = targetCurrencyCode && currencyCodes.code(targetCurrencyCode);
const targetCurrencyName = targetCurrencyInfo ? targetCurrencyInfo.currency : null;
const targetCurrencySymbol = targetCurrencyCode ? getSymbolFromCurrency(targetCurrencyCode) : null;
const targetFullName = includeNames ? targetCountry.name : undefined;
return {
code,
amount: converted,
flag: targetFlag,
currencyCode: targetCurrencyCode,
currencyName: targetCurrencyName,
currencySymbol: targetCurrencySymbol,
countryName: targetFullName
};
});
return {
origin: {
code: originCode,
amount,
flag: originFlag,
currencyCode: originCurrencyCode,
currencyName: originCurrencyName,
currencySymbol: originCurrencySymbol,
countryName: originFullName
},
results
};
}
/**
* List all available country ISO3 codes in the PPP dataset.
* @returns {string[]} Sorted array of ISO3 country codes
*/
async function listCountries() {
const latestPPP = await loadPPPData();
return Object.keys(latestPPP).sort();
}
/**
* List all countries with ISO3 and ISO2 codes and full country names.
* @returns {object[]} Array of objects: { iso3, iso2, name }
*/
function listCountryCodes() {
return Object.values(countryMap)
.map(c => ({ iso3: c.alpha3, iso2: c.alpha2, name: c.name }))
.sort((a, b) => a.name.localeCompare(b.name));
}
// CLI interface (async)
if (require.main === module) {
(async () => {
const args = process.argv.slice(2);
if (args.length < 3) {
console.error('Usage: ppp-calculator <ORIG_CODE> <AMOUNT> <TARGET1,TARGET2,...>');
process.exit(1);
}
const [orig, amtStr, targets] = args;
const amount = parseFloat(amtStr);
const targetCodes = targets.split(',').map(s => s.trim().toUpperCase());
try {
const result = await convertPPP(orig.toUpperCase(), amount, targetCodes);
console.log(JSON.stringify(result, null, 2));
} catch (err) {
console.error(err.message);
process.exit(1);
}
})();
}
module.exports = { convertPPP, listCountries, listCountryCodes };