Files
LynkedUpPro_CRM/api/storm-forecast.js
T

119 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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}&timesteps=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: 0100 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([]);
}
}