Files
HeatGuard/internal/heat/external_gains.go
vikingowl 1c9db02334 feat: add web UI with full CRUD setup page
Add server-side rendered setup UI accessible via `heatwave web`.
The dashboard is now re-rendered per request and includes a nav bar
linking to the new /setup page. Setup provides full CRUD for profiles,
rooms, devices, occupants, AC units (with room assignment), scenario
toggles, and forecast fetching — all via POST/redirect/GET forms.

- Add ShowNav field to DashboardData for conditional nav bar
- Extract fetchForecastForProfile() for reuse by web handler
- Create setup.html.tmpl with Tailwind-styled entity sections
- Create web_handlers.go with 15 route handlers and flash cookies
- Switch web.go from pre-rendered to per-request dashboard rendering
- Graceful dashboard fallback when no forecast data exists
2026-02-09 10:39:00 +01:00

104 lines
3.1 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
// SolarParams holds inputs for simplified solar gain calculation.
type SolarParams struct {
AreaSqm float64 // room floor area in m²
WindowFraction float64 // fraction of wall area that is window (e.g., 0.15)
SHGC float64 // solar heat gain coefficient of glazing (e.g., 0.6)
ShadingFactor float64 // 0.0 (fully shaded) to 1.0 (no shading)
OrientationFactor float64 // 0.01.0, varies by hour and wall orientation
CloudFactor float64 // 0.0 (overcast) to 1.0 (clear)
SunshineFraction float64 // fraction of hour with sunshine (0.01.0)
PeakIrradiance float64 // W/m² on the surface (e.g., 800 for direct sun)
}
// OrientationFactor returns a solar exposure factor (0.01.0) based on
// wall orientation and hour of day. This is a simplified proxy.
func OrientationFactor(orientation string, hour int) float64 {
switch orientation {
case "S":
if hour >= 10 && hour <= 16 {
return 1.0
}
if hour >= 8 && hour < 10 || hour > 16 && hour <= 18 {
return 0.5
}
return 0.0
case "E":
if hour >= 6 && hour <= 11 {
return 0.9
}
if hour > 11 && hour <= 14 {
return 0.3
}
return 0.0
case "W":
if hour >= 14 && hour <= 20 {
return 0.9
}
if hour >= 11 && hour < 14 {
return 0.3
}
return 0.0
case "SE":
if hour >= 7 && hour <= 13 {
return 0.9
}
if hour > 13 && hour <= 16 {
return 0.4
}
return 0.0
case "SW":
if hour >= 12 && hour <= 19 {
return 0.9
}
if hour >= 9 && hour < 12 {
return 0.4
}
return 0.0
case "NE":
if hour >= 5 && hour <= 9 {
return 0.5
}
return 0.1
case "NW":
if hour >= 17 && hour <= 21 {
return 0.5
}
return 0.1
case "N":
return 0.1 // minimal direct sun
default:
return 0.5
}
}
// SolarGain returns estimated solar heat gain in watts.
// Formula: irradiance * orientationFactor * windowArea * SHGC * shadingFactor * cloudFactor * sunshineFraction
func SolarGain(p SolarParams) float64 {
windowArea := p.AreaSqm * p.WindowFraction
return p.PeakIrradiance * p.OrientationFactor * windowArea * p.SHGC * p.ShadingFactor * p.CloudFactor * p.SunshineFraction
}
// DefaultRhoCp is the volumetric heat capacity of air in J/(m³·K).
// Approximately 1200 J/(m³·K) at sea level.
const DefaultRhoCp = 1200.0
// VentilationParams holds inputs for ventilation heat gain/loss calculation.
type VentilationParams struct {
ACH float64 // air changes per hour
VolumeCubicM float64 // room volume in m³
OutdoorTempC float64 // outdoor temperature in °C
IndoorTempC float64 // indoor temperature in °C
RhoCp float64 // volumetric heat capacity in kJ/(m³·K); use value such that kJ * 1000 = J, or pass as J directly
}
// VentilationGain returns ventilation heat gain in watts.
// Positive = heating (outdoor hotter), negative = cooling (outdoor cooler).
// Formula: ACH * volume * rhoCp_J * deltaT / 3600
func VentilationGain(p VentilationParams) float64 {
rhoCpJ := p.RhoCp * 1000 // convert kJ/(m³·K) to J/(m³·K)
deltaT := p.OutdoorTempC - p.IndoorTempC
return p.ACH * p.VolumeCubicM * rhoCpJ * deltaT / 3600
}