Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/environment

src/format.ts

160 lines · 5.2 KB · TypeScript
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'),
	}
}