- 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.
93 lines
2.4 KiB
Go
93 lines
2.4 KiB
Go
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 0–20% 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,
|
||
}
|
||
}
|