import airQualityLookup from './air-quality.ts' import moonLookup from './moon.ts' import sunLookup from './sun.ts' import weatherLookup from './weather.ts' import { resolveWeatherLocation } from './weather-geocode.ts' export type EnvironmentSnapshotParams = { location?: string latitude?: number longitude?: number label?: string date?: string units?: 'metric' | 'imperial' /** Set false to skip a section and save the upstream calls. All default to true. */ include?: { weather?: boolean airQuality?: boolean sun?: boolean moon?: boolean } } function sectionError(error: unknown): { error: string } { return { error: error instanceof Error ? error.message : String(error) } } /** * One-call outdoor snapshot combining weather, air quality, sun times, and * moon phase for a single location. Sections degrade independently: a failed * upstream lookup yields `{ error }` for that section instead of throwing. */ export async function environmentSnapshot( params: EnvironmentSnapshotParams = {}, ) { const input = typeof params === 'object' && params ? params : {} const include = input.include || {} // Resolve the location once so all sections agree on coordinates and we // only hit the geocoder a single time. const resolvedLocation = await resolveWeatherLocation(input) const shared = { latitude: resolvedLocation.latitude, longitude: resolvedLocation.longitude, label: resolvedLocation.displayName, date: input.date, } const [weather, airQuality, sun, moon] = await Promise.all([ include.weather === false ? Promise.resolve(null) : weatherLookup({ ...shared, units: input.units }).catch(sectionError), include.airQuality === false ? Promise.resolve(null) : airQualityLookup(shared).catch(sectionError), include.sun === false ? Promise.resolve(null) : sunLookup(shared).catch(sectionError), include.moon === false ? Promise.resolve(null) : moonLookup({ date: input.date }).catch(sectionError), ]) const summaryLines: string[] = [ 'Environment snapshot for ' + resolvedLocation.displayName, ] for (const section of [weather, airQuality, sun, moon]) { if (!section) continue if ('error' in section) continue if (typeof (section as { summary?: unknown }).summary === 'string') { summaryLines.push((section as { summary: string }).summary) } } const failed = [] if (weather && 'error' in weather) failed.push('weather: ' + weather.error) if (airQuality && 'error' in airQuality) { failed.push('airQuality: ' + airQuality.error) } if (sun && 'error' in sun) failed.push('sun: ' + sun.error) if (moon && 'error' in moon) failed.push('moon: ' + moon.error) if (failed.length > 0) { summaryLines.push('Failed sections: ' + failed.join('; ')) } return { location: { displayName: resolvedLocation.displayName, latitude: resolvedLocation.latitude, longitude: resolvedLocation.longitude, source: resolvedLocation.source, }, weather, airQuality, sun, moon, summary: summaryLines.join('\n\n'), } } export default environmentSnapshot