Files
HeatGuard/internal/heat/budget.go
vikingowl 84d645ff21 feat: fix cold-weather thermal logic, add comfort mode, and dashboard forecast refresh
- Fix ComputeRoomBudget: no-AC rooms check if open-window ventilation
  can offset gains instead of defaulting to Overloaded. Net-cooling
  rooms are now Comfortable; ventilation-solvable rooms are Marginal.
- Add "comfort" cool mode for hours where outdoor is >5°C below indoor
  and budget is not overloaded (winter/cold scenarios).
- Reorder determineCoolMode: sealed now before overloaded, fixing
  humid+cold+no-AC giving "overloaded" instead of "sealed".
- Update LLM prompts: document comfort coolMode, add cold-weather
  guidance for summary and actions generation.
- Add dashboard forecast refresh button: fetches fresh forecast +
  warnings, then re-runs compute and LLM pipelines.
- Extract forecast fetch into shared fetchForecastForProfile() in db.js,
  deduplicating logic between setup.js and dashboard.js.
- Add indoor humidity support, pressure display, and cool mode sealed
  integration test.
2026-02-10 04:26:53 +01:00

93 lines
2.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package heat
// BudgetStatus represents the thermal comfort state of a room.
type BudgetStatus int
const (
Comfortable BudgetStatus = iota // headroom > 20% of AC capacity
Marginal // headroom 020% of AC capacity
Overloaded // headroom < 0 (AC can't keep up)
)
func (s BudgetStatus) String() string {
switch s {
case Comfortable:
return "comfortable"
case Marginal:
return "marginal"
case Overloaded:
return "overloaded"
default:
return "unknown"
}
}
// BudgetInput holds all inputs for a room heat budget calculation.
type BudgetInput struct {
Devices []Device
DeviceMode DeviceMode
Occupants []Occupant
Solar SolarParams
Ventilation VentilationParams
ACCapacityBTUH float64
}
// BudgetResult holds the computed heat budget for a room.
type BudgetResult struct {
InternalGainsW float64
SolarGainW float64
VentilationGainW float64
TotalGainW float64
TotalGainBTUH float64
ACCapacityBTUH float64
HeadroomBTUH float64
Status BudgetStatus
}
// ComputeRoomBudget calculates the full heat budget for a room.
func ComputeRoomBudget(in BudgetInput) BudgetResult {
internal := TotalInternalGains(in.Devices, in.DeviceMode, in.Occupants)
solar := SolarGain(in.Solar)
ventilation := VentilationGain(in.Ventilation)
totalW := internal + solar + ventilation
totalBTUH := WattsToBTUH(totalW)
headroom := in.ACCapacityBTUH - totalBTUH
status := Overloaded
if totalBTUH <= 0 {
// Net cooling — room is losing heat, no problem
status = Comfortable
} else if in.ACCapacityBTUH > 0 {
ratio := headroom / in.ACCapacityBTUH
switch {
case ratio > 0.2:
status = Comfortable
case ratio >= 0:
status = Marginal
}
} else {
// No AC, positive gain — can open-window ventilation offset it?
deltaT := in.Ventilation.OutdoorTempC - in.Ventilation.IndoorTempC
if deltaT < 0 {
const openWindowACH = 5.0
rhoCpJ := in.Ventilation.RhoCp * 1000
maxVentW := openWindowACH * in.Ventilation.VolumeCubicM * rhoCpJ * deltaT / 3600
if (internal + solar + maxVentW) <= 0 {
status = Marginal
}
}
}
return BudgetResult{
InternalGainsW: internal,
SolarGainW: solar,
VentilationGainW: ventilation,
TotalGainW: totalW,
TotalGainBTUH: totalBTUH,
ACCapacityBTUH: in.ACCapacityBTUH,
HeadroomBTUH: headroom,
Status: status,
}
}