/* ============================================================ VYNDR 2.0 — odds formatting + region presets (§9). Plain CommonJS so components import it (allowJs) AND Jest tests it. CRITICAL (the #1 i18n footgun): fmtOdds only converts MONEYLINES — bare signed-integer strings (+150 / -110) or integer numbers (the grade→odds map). Totals/lines/spreads like 228.5, 229, -7.5 PASS THROUGH UNCHANGED. Apply to moneylines/book odds/parlay legs; NEVER to O/U totals or stat lines. This deliberately DEVIATES from the prototype's parseAm, whose regex accepted decimals (`228.5` would have been mis-converted to a fake implied % on O/U cells). The prompt's spec is authoritative here. ============================================================ */ const REGIONS = { US: { label: 'United States', odds: 'american', currency: 'USD' }, UK: { label: 'United Kingdom', odds: 'fractional', currency: 'GBP' }, EU: { label: 'Europe', odds: 'decimal', currency: 'EUR' }, BR: { label: 'Brazil', odds: 'decimal', currency: 'BRL' }, AU: { label: 'Australia', odds: 'decimal', currency: 'AUD' }, }; const CURRENCIES = { USD: '$', GBP: '£', EUR: '€', BRL: 'R$', AUD: 'A$' }; const ODDS_FORMATS = ['american', 'decimal', 'fractional', 'implied']; function gcd(a, b) { a = Math.abs(a); b = Math.abs(b); while (b) { [a, b] = [b, a % b]; } return a || 1; } /** * Return the American-odds integer for a moneyline input, or null if the * value is NOT a moneyline (and must pass through unchanged). * - number → only an integer counts (the grade→odds map); 228.5 → null * - string → only an explicitly SIGNED integer (+150 / -110); "228.5", * "229", "-7.5" → null */ function parseMoneyline(value) { if (typeof value === 'number') return Number.isInteger(value) ? value : null; if (typeof value !== 'string') return null; return /^[+-]\d+$/.test(value.trim()) ? parseInt(value, 10) : null; } function americanToDecimal(am) { return am > 0 ? am / 100 + 1 : 100 / -am + 1; } /** * Format an odds value into the requested style. Non-moneyline inputs are * returned verbatim (the pass-through guarantee). */ function fmtOdds(value, format = 'american') { const am = parseMoneyline(value); if (am === null) return value; if (format === 'american') return (am > 0 ? '+' : '') + Math.round(am); const dec = americanToDecimal(am); if (format === 'decimal') return dec.toFixed(2); if (format === 'implied') return Math.round((1 / dec) * 100) + '%'; // fractional let n; let d; if (am > 0) { n = Math.round(am); d = 100; } else { n = 100; d = Math.round(-am); } const g = gcd(n, d); return `${n / g}/${d / g}`; } /** Region → { odds, currency, currencySymbol } preset (§9). */ function regionPreset(region) { const r = REGIONS[region] || REGIONS.US; return { odds: r.odds, currency: r.currency, currencySymbol: CURRENCIES[r.currency] || '$' }; } function currencySymbol(code) { return CURRENCIES[code] || '$'; } module.exports = { REGIONS, CURRENCIES, ODDS_FORMATS, parseMoneyline, americanToDecimal, fmtOdds, regionPreset, currencySymbol, };