import { describeWmo } from './weather-codes.ts'
export function buildHourlySeries(data: any, units: 'metric' | 'imperial') {
const tempSuffix = units === 'metric' ? '°C' : '°F'
const windSuffix = units === 'metric' ? 'km/h' : 'mph'
const precipSuffix = units === 'metric' ? 'mm' : 'in'
const rows = []
const hourly = data.hourly || {}
const times = Array.isArray(hourly.time) ? hourly.time : []
for (let index = 0; index < times.length; index += 1) {
rows.push({
time: hourly.time[index],
condition: describeWmo(hourly.weathercode[index]),
temperature: hourly.temperature_2m[index],
temperatureDisplay: Math.round(hourly.temperature_2m[index]) + tempSuffix,
feelsLike: hourly.apparent_temperature[index],
feelsLikeDisplay:
Math.round(hourly.apparent_temperature[index]) + tempSuffix,
humidityPct: hourly.relativehumidity_2m[index],
precipitationProbabilityPct: hourly.precipitation_probability[index],
precipitation: hourly.precipitation[index],
precipitationDisplay: String(hourly.precipitation[index]) + precipSuffix,
windSpeed: hourly.windspeed_10m[index],
windDirectionDeg: hourly.winddirection_10m[index],
windDisplay:
Math.round(hourly.windspeed_10m[index]) +
windSuffix +
' @ ' +
hourly.winddirection_10m[index] +
'°',
uvIndex: hourly.uv_index[index],
})
}
return rows
}
export function buildWeatherResponse(params: any) {
const input = typeof params === 'object' && params ? params : {}
const data = input.data
const units = input.units === 'metric' ? 'metric' : 'imperial'
const tempSuffix = units === 'metric' ? '°C' : '°F'
const windSuffix = units === 'metric' ? 'km/h' : 'mph'
const precipSuffix = units === 'metric' ? 'mm' : 'in'
const daily = data.daily
const current =
data.current && typeof data.current === 'object'
? {
time: data.current.time || null,
weatherCode: data.current.weathercode,
condition: describeWmo(data.current.weathercode),
temperature: data.current.temperature_2m,
temperatureDisplay:
Math.round(data.current.temperature_2m) + tempSuffix,
}
: null
const dailySummary = {
date: input.date,
location: input.displayName,
type: input.isHistorical
? 'historical'
: input.isForecast
? 'forecast'
: 'today',
weatherCode: daily.weathercode[0],
condition: describeWmo(daily.weathercode[0]),
high: daily.temperature_2m_max[0],
low: daily.temperature_2m_min[0],
feelsLikeHigh: daily.apparent_temperature_max[0],
feelsLikeLow: daily.apparent_temperature_min[0],
precipitationTotal: daily.precipitation_sum[0],
precipitationProbabilityMaxPct:
daily.precipitation_probability_max?.[0] ?? null,
windMax: daily.windspeed_10m_max[0],
uvIndexMax: daily.uv_index_max?.[0] ?? null,
sunrise: daily.sunrise[0],
sunset: daily.sunset[0],
display: {
high: Math.round(daily.temperature_2m_max[0]) + tempSuffix,
low: Math.round(daily.temperature_2m_min[0]) + tempSuffix,
feelsLikeHigh: Math.round(daily.apparent_temperature_max[0]) + tempSuffix,
feelsLikeLow: Math.round(daily.apparent_temperature_min[0]) + tempSuffix,
precipitationTotal: String(daily.precipitation_sum[0]) + precipSuffix,
precipitationProbabilityMax:
String(daily.precipitation_probability_max?.[0] ?? 'N/A') + '%',
windMax: Math.round(daily.windspeed_10m_max[0]) + windSuffix,
},
}
const hourlySeries = buildHourlySeries(data, units)
let hourly = null
if (input.hour !== null && input.hour !== undefined) {
const wanted = String(Number(input.hour)).padStart(2, '0')
hourly =
hourlySeries.find(
(entry) =>
typeof entry.time === 'string' &&
entry.time.endsWith('T' + wanted + ':00'),
) || null
}
const previewLimit = Number.isFinite(Number(input.hourlyLimit))
? Math.max(1, Number(input.hourlyLimit))
: 6
const hourlyPreview = hourlySeries.slice(0, previewLimit)
const summaryLines = [
dailySummary.location +
' weather for ' +
dailySummary.date +
' (' +
dailySummary.type +
')',
'Condition: ' + dailySummary.condition,
'High/low: ' + dailySummary.display.high + ' / ' + dailySummary.display.low,
'Feels like: ' +
dailySummary.display.feelsLikeHigh +
' / ' +
dailySummary.display.feelsLikeLow,
'Precipitation: ' +
dailySummary.display.precipitationTotal +
' total, max ' +
dailySummary.display.precipitationProbabilityMax +
' chance',
'Wind max: ' + dailySummary.display.windMax,
'Sunrise/sunset: ' + dailySummary.sunrise + ' / ' + dailySummary.sunset,
]
if (hourly) {
summaryLines.push(
'Hour ' +
String(Number(input.hour)).padStart(2, '0') +
':00: ' +
hourly.condition +
', ' +
hourly.temperatureDisplay +
', feels like ' +
hourly.feelsLikeDisplay +
', precipitation ' +
hourly.precipitationDisplay +
', wind ' +
hourly.windDisplay,
)
}
return {
location: {
displayName: input.displayName,
latitude: input.latitude,
longitude: input.longitude,
source: input.locationSource || 'unknown',
},
date: input.date,
units,
source: input.source || 'open-meteo',
stale: input.stale === true,
attempts: input.attempts ?? null,
recordedAt: input.recordedAt ?? null,
fallbackError: input.fallbackError ?? null,
daily: dailySummary,
current,
hourly,
hourlyPreview,
summary: summaryLines.join('\n'),
}
}