/** * Proxies Tomorrow.io daily weather forecast for the Plano TX area. * Set TOMORROW_API_KEY in Vercel env vars (and .env locally for vercel dev). * * Response: array of 7-day daily forecast objects, normalised for the frontend. * Cached 1 hour — free tier allows 500 calls/day. */ const PLANO_LAT = 33.055; const PLANO_LNG = -96.752; // Tomorrow.io weather code → label + hail risk flag function interpretCode(code) { const map = { 1000: { label: 'Clear', hailRisk: false }, 1001: { label: 'Cloudy', hailRisk: false }, 1100: { label: 'Mostly Clear', hailRisk: false }, 1101: { label: 'Partly Cloudy', hailRisk: false }, 1102: { label: 'Mostly Cloudy', hailRisk: false }, 2000: { label: 'Fog', hailRisk: false }, 4000: { label: 'Drizzle', hailRisk: false }, 4001: { label: 'Rain', hailRisk: false }, 4200: { label: 'Light Rain', hailRisk: false }, 4201: { label: 'Heavy Rain', hailRisk: false }, 5000: { label: 'Snow', hailRisk: false }, 5001: { label: 'Flurries', hailRisk: false }, 5100: { label: 'Light Snow', hailRisk: false }, 5101: { label: 'Heavy Snow', hailRisk: false }, 6000: { label: 'Freezing Drizzle', hailRisk: true }, 6001: { label: 'Freezing Rain', hailRisk: true }, 6200: { label: 'Lt Freezing Rain', hailRisk: true }, 6201: { label: 'Hvy Freezing Rain',hailRisk: true }, 7000: { label: 'Ice Pellets', hailRisk: true }, 7101: { label: 'Heavy Hail', hailRisk: true }, 7102: { label: 'Light Hail', hailRisk: true }, 8000: { label: 'Thunderstorm', hailRisk: true }, }; return map[code] ?? { label: 'Mixed', hailRisk: false }; } export default async function handler(req, res) { res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=7200'); res.setHeader('Access-Control-Allow-Origin', '*'); if (req.method !== 'GET') { return res.status(405).json({ error: 'Method not allowed' }); } // Trim to tolerate a stray trailing space/newline in the env var value. const key = (process.env.TOMORROW_API_KEY || '').trim(); if (!key) { console.warn('[storm-forecast] TOMORROW_API_KEY not set'); return res.status(200).json([]); } try { const fields = [ 'precipitationProbability', 'weatherCode', 'windSpeedAvg', 'windGustMax', 'temperatureAvg', 'precipitationIntensityAvg', 'humidityAvg', ].join(','); const url = `https://api.tomorrow.io/v4/weather/forecast?location=${PLANO_LAT},${PLANO_LNG}&apikey=${key}×teps=1d&units=imperial&fields=${fields}`; const resp = await fetch(url, { headers: { Accept: 'application/json' }, }); if (!resp.ok) { const txt = await resp.text(); console.warn('[storm-forecast] Tomorrow.io error', resp.status, txt); return res.status(200).json([]); } const data = await resp.json(); const daily = data?.timelines?.daily ?? []; const forecast = daily.slice(0, 7).map(day => { const v = day.values ?? {}; // Daily aggregated fields may come back as plain names or with Avg/Max suffix const precipProb = v.precipitationProbability ?? v.precipitationProbabilityAvg ?? 0; const weatherCode = v.weatherCode ?? v.weatherCodeMax ?? 1000; const windGust = v.windGustMax ?? v.windGustAvg ?? 0; const windSpeed = v.windSpeedAvg ?? 0; const temp = v.temperatureAvg ?? 0; const humidity = v.humidityAvg ?? 0; const { label, hailRisk } = interpretCode(weatherCode); // Storm severity: 0–100 score for how dangerous this day looks const stormScore = Math.min(100, Math.round( precipProb * 0.5 + (hailRisk ? 35 : 0) + (windGust > 40 ? 15 : windGust > 25 ? 8 : 0) )); return { date: day.time, precipProb: Math.round(precipProb), weatherCode, weatherLabel: label, hailRisk, windGust: Math.round(windGust), windSpeed: Math.round(windSpeed), tempF: Math.round(temp), humidity: Math.round(humidity), stormScore, }; }); return res.status(200).json(forecast); } catch (err) { console.error('[storm-forecast] error:', err.message); return res.status(200).json([]); } }