Add weather compensation tiers for occupied aircon defaults

Warm/hot/heatwave tiers (ascending peak_above_c thresholds) override the
global ac_occupied template when today's forecast peak clears one, picked
once at the occupied-state transition so a guest's later manual adjustment
is never stomped. New lib/weather.js fetches the peak via Met Office
DataHub (if configured in settings) or Open-Meteo (no key needed) as a
fallback, TTL-cached with a stale-fallback window on fetch failure.
Zone-level per-field overrides still apply on top of a picked tier.

Thresholds/templates live in hvac_defaults.ac_occupied_tiers (existing
JSONB config blob, no migration) and are editable on the Settings page's
new Weather Compensation table. Hotel lat/lon and the optional Met Office
API key live centrally in the settings app (hotel_location/met_office
integrations), not hvac-local, for reuse by other apps later.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-08-25 11:01:41 +00:00
parent 48ffebc08d
commit 915fc0039c
7 changed files with 236 additions and 14 deletions

View file

@ -209,6 +209,15 @@ export interface HvacAcTemplate {
fan_speed: string
}
// Weather compensation tier — same shape as HvacAcTemplate plus the forecast-peak
// threshold that activates it. Tiers are stored ascending by peak_above_c; the
// scheduler picks the highest one today's forecast peak clears (see resolveAcTier()
// in backend/src/lib/scheduler.js), falling back to plain ac_occupied below all of them.
export interface HvacWeatherTier extends HvacAcTemplate {
name: string
peak_above_c: number
}
export interface HvacDefaults {
arrival_prep_offset_min: number
departure_prep_offset_min: number
@ -216,6 +225,7 @@ export interface HvacDefaults {
trv_vacant_temp: number
ac_occupied: HvacAcTemplate
ac_vacant: HvacAcTemplate
ac_occupied_tiers: HvacWeatherTier[]
}
export const DEFAULT_HVAC_TEMPLATE: HvacDefaults = {
@ -225,6 +235,11 @@ export const DEFAULT_HVAC_TEMPLATE: HvacDefaults = {
trv_vacant_temp: 16.0,
ac_occupied: { power: 'on', mode: 'cool', setpoint: 22.0, fan_speed: 'medium' },
ac_vacant: { power: 'off', mode: 'cool', setpoint: 18.0, fan_speed: 'low' },
ac_occupied_tiers: [
{ name: 'warm', peak_above_c: 24, power: 'off', mode: 'cool', setpoint: 20.0, fan_speed: 'medium' },
{ name: 'hot', peak_above_c: 28, power: 'on', mode: 'cool', setpoint: 20.0, fan_speed: 'high' },
{ name: 'heatwave', peak_above_c: 32, power: 'on', mode: 'cool', setpoint: 20.0, fan_speed: 'powerful' },
],
}
export interface AppConfig {