feat: Adds thermal balance point, still need to implement economic balance point.

This commit is contained in:
2025-03-04 12:46:44 -05:00
parent d22beb9375
commit 6c31a9db09
11 changed files with 362 additions and 40 deletions

View File

@@ -19,8 +19,18 @@ struct HeatingBalancePointForm: HTML, Sendable {
}
var content: some HTML {
FormHeader(label: "Balance Point - \(mode.label)", svg: .scale)
// TODO: Toggle button
div(.class("flex flex-wrap justify-between")) {
FormHeader(label: "Balance Point - \(mode.label)", svg: .scale)
Toggle(
isOn: mode == .thermal,
onLabel: HeatingBalancePoint.Mode.thermal.label,
onAttributes: .hxDefaults(get: .heatingBalancePoint(.index(mode: .thermal, heatLossMode: heatLossMode))),
offLabel: HeatingBalancePoint.Mode.economic.label,
offAttributes: .hxDefaults(get: .heatingBalancePoint(.index(mode: .economic, heatLossMode: heatLossMode)))
)
.attributes(.class("mb-6"))
}
form(
.hx.post(route: .heatingBalancePoint(.index)),
@@ -31,7 +41,11 @@ struct HeatingBalancePointForm: HTML, Sendable {
case .thermal:
ThermalFields(heatLossMode: heatLossMode)
case .economic:
div {}
div {
// FIX:
WarningBox("This is still under development and may not be fully functional.")
.attributes(.class("mb-6"))
}
}
div {
@@ -58,7 +72,7 @@ struct HeatingBalancePointForm: HTML, Sendable {
div {
div(.class("mb-4")) {
h4(.class("text-lg font-bold")) { "Capacities" }
p(.class("text-sm")) {
p(.class("text-xs text-blue-500")) {
"Entering known capacities gives better results, otherwise capacities will be estimated."
}
}
@@ -109,16 +123,32 @@ struct HeatingBalancePointForm: HTML, Sendable {
}
private var simplifiedFields: some HTML {
LabeledContent(label: "Building Size (ft²)") {
Input(id: "simplifiedHeatLoss", placeholder: "Square feet")
.attributes(.type(.number), .min("1"), .step("0.5"), .required)
div(.class("grid grid-cols-1 lg:grid-cols-3 gap-4")) {
LabeledContent(label: "Building Size (ft²)") {
Input(id: "simplifiedHeatLoss", placeholder: "Square feet")
.attributes(.type(.number), .min("1"), .step("0.5"), .required)
}
div {
InputLabel(for: "climateZone") { "Climate Zone" }
Select(for: ClimateZone.self, id: "climateZone") { $0.rawValue }
}
LabeledContent(label: "Outdoor Design Temperature (°F)") {
Input(id: "heatingDesignTemperature", placeholder: "Design temperature (optional)")
.attributes(.type(.number), .step("0.5"))
}
}
}
private var knownFields: some HTML {
LabeledContent(label: "Heat Loss (BTU/h)") {
Input(id: "knownHeatLoss", placeholder: "Heat loss")
.attributes(.type(.number), .min("1"), .step("0.5"), .required)
div(.class("grid grid-cols-1 lg:grid-cols-2 gap-4")) {
LabeledContent(label: "Heat Loss (BTU/h)") {
Input(id: "knownHeatLoss", placeholder: "Heat loss")
.attributes(.type(.number), .min("1"), .step("0.5"), .required)
}
LabeledContent(label: "Outdoor Design Temperature (°F)") {
Input(id: "heatingDesignTemperature", placeholder: "Design temperature")
.attributes(.type(.number), .step("0.5"), .required)
}
}
}
@@ -130,14 +160,92 @@ struct HeatingBalancePointResponse: HTML, Sendable {
let response: HeatingBalancePoint.Response
var content: some HTML {
ResultContainer(reset: .heatingBalancePoint(.index(mode: response.mode, heatLossMode: nil))) {
div(
.class("""
w-full rounded-xl shadow-xl bg-blue-100 text-blue-600 border border-blue-600 py-4
""")
) {
div(.class("flex")) {
SVG(.scale, color: "text-blue-600")
.attributes(.class("px-4"))
p(.class("font-medium")) {
"\(response.mode.label) Balance Point"
}
}
switch response {
case let .thermal(result):
thermalResult(result)
}
}
WarningBox(warnings: response.warnings)
}
}
func thermalResult(_ result: HeatingBalancePoint.Response.Thermal) -> some HTML {
div {
VerticalGroup(
label: "Balance Point",
value: "\(double: result.balancePointTemperature, fractionDigits: 1)",
valueLabel: "°F"
)
.attributes(.class("mb-8"))
div(.class("grid grid-cols-2 space-y-6")) {
div {
VerticalGroup(
label: "Heat Loss - \(result.heatLossMode.label)",
value: "\(double: result.heatLoss, fractionDigits: 0)",
valueLabel: "BTU/h"
)
}
div {
VerticalGroup(
label: "Heating Design Temperature",
value: "\(double: result.heatingDesignTemperature, fractionDigits: 0)",
valueLabel: "°F"
)
}
div {
VerticalGroup(
label: "Capacity @ 47°",
value: "\(double: result.capacityAt47, fractionDigits: 0)",
valueLabel: "BTU/h"
)
}
div {
VerticalGroup(
label: "Capacity @ 17°",
value: "\(double: result.capacityAt17, fractionDigits: 0)",
valueLabel: "BTU/h"
)
}
}
}
}
}
private extension HeatingBalancePoint.Mode {
var label: String { rawValue.capitalized }
}
private extension HeatingBalancePoint.HeatLoss.Mode {
var label: String { rawValue.capitalized }
}
private extension HeatingBalancePoint.Response {
var mode: HeatingBalancePoint.Mode {
switch self {
case .thermal: return .thermal
}
}
var warnings: [String] {
switch self {
case let .thermal(result): return result.warnings
}
}
}