Skip to main content

@happyvertical/weather

Weather data provider abstraction for the HAppyVertical SDK.

Overview

The @happyvertical/weather package provides a unified interface for fetching weather forecasts from multiple providers. It follows the same architectural pattern as @happyvertical/ai, @happyvertical/files, and @happyvertical/sql.

Supported Providers

  • Environment Canada - Government weather service for Canadian locations (free)
  • OpenWeatherMap - Global weather data with free and paid tiers
    • Standard API: 5-day/3-hour forecasts (free tier)
    • One Call API 3.0: 48hr hourly + 8-day daily forecasts (paid tier)

Installation

pnpm add @happyvertical/weather

Claude Code Context

Install Claude Code context files for AI-assisted development:

npx have-weather-context

This copies the package's AGENT.md documentation and metadata.json metadata to your project's .claude/ directory, enabling Claude to provide better assistance when working with this package.

Usage

Basic Example

import { getWeatherAdapter } from '@happyvertical/weather';

// Create adapter for Environment Canada
const weather = await getWeatherAdapter({
provider: 'environment-canada'
});

// Fetch forecast for a location (latitude, longitude)
const forecasts = await weather.fetchForLocation(51.0447, -114.0719);

forecasts.forEach(forecast => {
console.log(`${forecast.timestamp}: ${forecast.temperature}°C - ${forecast.conditions}`);
});

OpenWeatherMap (Free Tier)

const weather = await getWeatherAdapter({
provider: 'openweathermap',
apiKey: process.env.OPENWEATHER_API_KEY
});

const forecasts = await weather.fetchForLocation(51.0447, -114.0719);

OpenWeatherMap One Call (Paid Tier)

const weather = await getWeatherAdapter({
provider: 'openweathermap-onecall',
apiKey: process.env.OPENWEATHER_API_KEY
});

const forecasts = await weather.fetchForLocation(51.0447, -114.0719);

Environment Variable Configuration

# .env
HAVE_WEATHER_PROVIDER=openweathermap
OPENWEATHER_API_KEY=your-api-key-here
HAVE_WEATHER_TIMEOUT=15000
// Automatically uses environment variables
const weather = await getWeatherAdapter();
const forecasts = await weather.fetchForLocation(51.0447, -114.0719);

API Reference

getWeatherAdapter(options?): Promise<IWeatherAdapter>

Factory function for creating weather adapters.

Options:

  • provider: 'environment-canada' | 'openweathermap' | 'openweathermap-onecall'
  • apiKey: API key (required for OpenWeatherMap providers)
  • timeout: Request timeout in milliseconds (default: 10000)

IWeatherAdapter

interface IWeatherAdapter {
fetchForLocation(
latitude: number,
longitude: number,
options?: FetchOptions
): Promise<WeatherForecast[]>

testConnection(): Promise<boolean>

supportsLocation(latitude: number, longitude: number): Promise<boolean>
}

WeatherForecast

interface WeatherForecast {
timestamp: Date
temperature: number // °C
feelsLike?: number // °C
temperatureMin?: number // °C
temperatureMax?: number // °C
conditions: string // Human-readable description
humidity: number // Percentage 0-100
windSpeed: number // km/h
windDirection?: number // Degrees 0-360
windGust?: number // km/h
pressure?: number // hPa
cloudCover?: number // Percentage 0-100
visibility?: number // km
precipProbability?: number // Percentage 0-100
precipAmount?: number // mm
confidence?: number // Provider's confidence 0-100
raw: any // Provider-specific raw data
}

Environment Variables

VariableTypeDescription
HAVE_WEATHER_PROVIDERstringProvider name
OPENWEATHER_API_KEYstringOpenWeatherMap API key
HAVE_WEATHER_TIMEOUTnumberRequest timeout (ms)

Testing

The default suite is offline and deterministic, with one key-gated exception. Provider responses come from recorded fixtures in src/__tests__/fixtures/, so on a clean environment no test in test reaches the network:

pnpm turbo run test --filter=@happyvertical/weather

The exception is google-weather.spec.ts. It gates on GOOGLE_API_KEY, per the repository rule that a suite needing a real service gates on a key where one exists — see "Network isolation in the default suites" in .github/CI.md. With the key unset, its live block skips and the offline guarantee holds; that is how CI runs it. With the key exported, it calls the live Google Weather API from test.

Coverage against the other live services lives in *.optional.test.ts and is skipped unless you opt in. OpenWeatherMap gates on its API key; Environment Canada needs no key, so it gates on an explicit flag:

ENVIRONMENT_CANADA_INTEGRATION=1 pnpm --filter @happyvertical/weather test:optional

Run the live suite after re-recording a fixture, to confirm the upstream payload still matches the shape the offline suite asserts. See src/__tests__/fixtures/README.md for how each fixture was recorded.

Provider Comparison

FeatureEnvironment CanadaOpenWeatherMapOWM One Call
CostFreeFree tier availablePaid
CoverageCanada onlyGlobalGlobal
ForecastDay/night3-hour for 5 daysHourly + daily
API KeyNot requiredRequiredRequired
Update Frequency~hourly3 hourshourly

License

MIT