Scaffold Phase 1 hvac app — NewBook-driven room TRV heating scheduler
Ports the state machine, retry/backoff, and guest-override detection from the retired homeassistant-newbook-heating-component, without depending on Home Assistant. Backend (Fastify/pg) + frontend (React/Vite/TS) following standard stack conventions; LXC 128 (127 was already taken by utilities). MHI/Midea/Daikin/boiler drivers and the shared MQTT broker (LXC 104) are later phases/infra, not included here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
commit
276c04f8c6
42 changed files with 5461 additions and 0 deletions
128
frontend/src/types.ts
Normal file
128
frontend/src/types.ts
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
export type ZoneType = 'room' | 'public_area'
|
||||
export type ZoneSource = 'newbook' | 'manual'
|
||||
export type RoomState = 'vacant' | 'booked' | 'heating_up' | 'occupied' | 'cooling_down'
|
||||
export type DeviceType = 'shelly_trv' | 'mhi_modbus' | 'midea' | 'daikin' | 'home_assistant'
|
||||
export type HealthState = 'healthy' | 'degraded' | 'poor' | 'unresponsive' | 'calibration_error'
|
||||
export type PhotoType = 'device' | 'serial_plate'
|
||||
|
||||
export const ROOM_STATE_LABELS: Record<RoomState, string> = {
|
||||
vacant: 'Vacant',
|
||||
booked: 'Booked',
|
||||
heating_up: 'Heating Up',
|
||||
occupied: 'Occupied',
|
||||
cooling_down: 'Cooling Down',
|
||||
}
|
||||
|
||||
export const DEVICE_TYPE_LABELS: Record<DeviceType, string> = {
|
||||
shelly_trv: 'Shelly TRV',
|
||||
mhi_modbus: 'MHI Aircon (Modbus)',
|
||||
midea: 'Midea Split',
|
||||
daikin: 'Daikin Split',
|
||||
home_assistant: 'Home Assistant',
|
||||
}
|
||||
|
||||
// Device types with an implemented Phase 1 driver — everything else in
|
||||
// DeviceType is an enum value only, ready for its Phase 2/3 driver.
|
||||
export const IMPLEMENTED_DEVICE_TYPES: DeviceType[] = ['shelly_trv', 'home_assistant']
|
||||
|
||||
export interface Zone {
|
||||
id: number
|
||||
zone_type: ZoneType
|
||||
source: ZoneSource
|
||||
newbook_site_id: string | null
|
||||
name: string
|
||||
active: boolean
|
||||
occupied_temp: string
|
||||
vacant_temp: string
|
||||
heating_offset_min: number
|
||||
cooling_offset_min: number
|
||||
auto_mode: boolean
|
||||
sync_valves: boolean
|
||||
exclude_bathroom: boolean
|
||||
created_at: string
|
||||
device_count?: number
|
||||
}
|
||||
|
||||
export interface ZoneStatus extends Zone {
|
||||
room_state: RoomState
|
||||
target_temp: string
|
||||
last_transition_at: string | null
|
||||
last_booking_status: string | null
|
||||
devices: DeviceSummary[]
|
||||
}
|
||||
|
||||
export interface DeviceSummary {
|
||||
id: number
|
||||
zone_id: number | null
|
||||
device_type: DeviceType
|
||||
external_ref: string
|
||||
discovered_name: string | null
|
||||
location: string | null
|
||||
health_state: HealthState
|
||||
battery_pct: number | null
|
||||
wifi_rssi: number | null
|
||||
current_target_temp: string | null
|
||||
target_origin: string | null
|
||||
last_seen: string | null
|
||||
}
|
||||
|
||||
export interface Device extends DeviceSummary {
|
||||
zone_name: string | null
|
||||
maintenance_asset_id: number | null
|
||||
device_ip: string | null
|
||||
photo_count: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface DevicePhoto {
|
||||
id: number
|
||||
device_id: number
|
||||
file_name: string
|
||||
file_path: string
|
||||
mime_type: string
|
||||
photo_type: PhotoType
|
||||
uploaded_by: string
|
||||
uploaded_at: string
|
||||
}
|
||||
|
||||
export interface ActivityEntry {
|
||||
id: number
|
||||
zone_id: number | null
|
||||
zone_name: string | null
|
||||
event_type: string
|
||||
from_state: string | null
|
||||
to_state: string | null
|
||||
note: string | null
|
||||
source: string
|
||||
user_email: string | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface NewbookSite {
|
||||
site_id: string
|
||||
site_name: string
|
||||
category_name: string | null
|
||||
excluded: boolean
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
poll_interval_minutes: number
|
||||
default_arrival_time: string
|
||||
default_departure_time: string
|
||||
maintenance_url: string
|
||||
maintenance_api_key: string
|
||||
excluded_site_ids?: string[]
|
||||
}
|
||||
|
||||
export interface User {
|
||||
user_id: number
|
||||
name: string
|
||||
email: string
|
||||
is_admin: boolean
|
||||
caps: string[] // bare slugs — verify?app=hvac strips the prefix
|
||||
}
|
||||
|
||||
export function can(user: User, cap: string): boolean {
|
||||
return user.is_admin || user.caps.includes(cap)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue