141 lines
4 KiB
TypeScript
141 lines
4 KiB
TypeScript
/**
|
||
* The one shape the UI understands.
|
||
*
|
||
* Every provider returns wildly different JSON. Each adapter in ./providers
|
||
* translates its provider's response into these types, so the UI never has
|
||
* to know which provider it is talking to.
|
||
*
|
||
* Rules every adapter follows:
|
||
* - Units are metric: °C, km/h, hPa, km, mm.
|
||
* - Times are ISO-8601 instants in UTC ("2026-10-21T23:00:00.000Z").
|
||
* The UI formats them in the location's time zone.
|
||
* - A reading the provider doesn't offer is `null`, and the UI hides it.
|
||
*/
|
||
|
||
export type ProviderId = "openweather" | "weatherapi" | "tomorrow" | "openmeteo";
|
||
|
||
/** A deliberately small set of conditions — one per pixel icon. */
|
||
export type Condition = "clear" | "clouds" | "rain" | "storm" | "snow" | "fog";
|
||
|
||
export interface Place {
|
||
name: string;
|
||
region?: string;
|
||
country?: string;
|
||
lat: number;
|
||
lon: number;
|
||
}
|
||
|
||
export interface CurrentWeather {
|
||
time: string;
|
||
tempC: number;
|
||
feelsLikeC: number;
|
||
condition: Condition;
|
||
description: string; // human words, e.g. "light rain"
|
||
isDay: boolean;
|
||
humidity: number; // %
|
||
windKph: number;
|
||
gustKph: number | null;
|
||
windDeg: number | null; // direction the wind blows FROM, 0 = north
|
||
pressureHpa: number | null;
|
||
dewPointC: number | null;
|
||
visibilityKm: number | null;
|
||
uvIndex: number | null;
|
||
cloudCover: number | null; // %
|
||
precipMm: number | null; // in the last hour
|
||
}
|
||
|
||
export interface HourlyForecast {
|
||
time: string; // start of the hour (or 3-hour block)
|
||
tempC: number;
|
||
feelsLikeC: number | null;
|
||
condition: Condition;
|
||
isDay: boolean;
|
||
precipChance: number | null; // %
|
||
precipMm: number | null;
|
||
windKph: number;
|
||
gustKph: number | null;
|
||
windDeg: number | null;
|
||
humidity: number | null;
|
||
uvIndex: number | null;
|
||
}
|
||
|
||
export interface DailyForecast {
|
||
time: string; // any instant inside that local day (midday where possible)
|
||
highC: number;
|
||
lowC: number;
|
||
condition: Condition;
|
||
summary: string | null; // provider's own one-liner, if it has one
|
||
precipChance: number | null;
|
||
precipMm: number | null;
|
||
windKph: number | null; // daily max
|
||
gustKph: number | null;
|
||
windDeg: number | null;
|
||
uvIndexMax: number | null;
|
||
humidity: number | null; // daily average
|
||
sunrise: string | null;
|
||
sunset: string | null;
|
||
}
|
||
|
||
export type AlertSeverity = "extreme" | "severe" | "moderate" | "minor" | "unknown";
|
||
|
||
export interface WeatherAlert {
|
||
title: string;
|
||
severity: AlertSeverity;
|
||
source: string; // who issued it, e.g. "NWS Twin Cities/Chanhassen MN"
|
||
starts: string | null;
|
||
ends: string | null;
|
||
description: string;
|
||
instruction: string | null;
|
||
}
|
||
|
||
export interface AirQuality {
|
||
usAqi: number; // US EPA scale, 0–500
|
||
pm25: number | null; // µg/m³
|
||
pm10: number | null;
|
||
ozone: number | null;
|
||
no2: number | null;
|
||
}
|
||
|
||
/** What one provider adapter returns. */
|
||
export interface ProviderForecast {
|
||
current: CurrentWeather;
|
||
hourly: HourlyForecast[];
|
||
daily: DailyForecast[];
|
||
hourlyStepHours: 1 | 3;
|
||
timeZone: string | null; // IANA name, if the provider tells us
|
||
alerts: WeatherAlert[];
|
||
}
|
||
|
||
/** What /api/weather returns: the provider's forecast plus extras from other sources. */
|
||
export interface WeatherReport extends ProviderForecast {
|
||
provider: ProviderId;
|
||
timeZone: string | null;
|
||
airQuality: AirQuality | null;
|
||
alertSource: string | null;
|
||
fetchedAt: string;
|
||
}
|
||
|
||
/** The contract each provider adapter fulfils. */
|
||
export interface WeatherProvider {
|
||
id: ProviderId;
|
||
label: string;
|
||
/** Name of the env var holding its key, or null if no key is needed. */
|
||
envKey: string | null;
|
||
/** Forecast horizon available on the configured tier. */
|
||
limits(): { forecastDays: number; hourlyStepHours: 1 | 3 };
|
||
getForecast(lat: number, lon: number): Promise<ProviderForecast>;
|
||
}
|
||
|
||
/** Configured provider metadata exposed to the browser (never includes keys). */
|
||
export interface ProviderInfo {
|
||
id: ProviderId;
|
||
label: string;
|
||
forecastDays: number;
|
||
hourlyStepHours: 1 | 3;
|
||
}
|
||
|
||
/** Map overlays the radar panel can show. */
|
||
export interface RadarFrame {
|
||
time: string;
|
||
url: string; // Leaflet tile template with {z}/{x}/{y}
|
||
}
|