feat: Adds thermal balance point, still need to implement economic balance point.
This commit is contained in:
File diff suppressed because one or more lines are too long
158
Sources/ApiController/Extensions/HeatingBalancePoint.swift
Normal file
158
Sources/ApiController/Extensions/HeatingBalancePoint.swift
Normal file
@@ -0,0 +1,158 @@
|
||||
import CoreModels
|
||||
import Foundation
|
||||
import Logging
|
||||
import Routes
|
||||
|
||||
public extension HeatingBalancePoint.Request {
|
||||
|
||||
func respond(logger: Logger) async throws -> HeatingBalancePoint.Response {
|
||||
switch self {
|
||||
case let .thermal(request):
|
||||
logger.debug("Calculating thermal balance point: \(request)")
|
||||
return try await request.respond(logger: logger)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension HeatingBalancePoint.Request.Thermal {
|
||||
|
||||
static let heatPumpDeratingFactor = 0.014
|
||||
|
||||
func respond(logger: Logger) async throws -> HeatingBalancePoint.Response {
|
||||
try validate()
|
||||
let (at47, at17) = getCapacities()
|
||||
let heatingDesignTemperature = getHeatingDesignTemperature()
|
||||
let buildingHeatLoss = getBuildingHeatLoss(designTemperature: heatingDesignTemperature)
|
||||
|
||||
let balancePoint = await thermalBalancePoint(
|
||||
heatLoss: buildingHeatLoss,
|
||||
at47: at47,
|
||||
at17: at17,
|
||||
designTemperature: heatingDesignTemperature
|
||||
)
|
||||
|
||||
let warnings = getWarnings()
|
||||
|
||||
return .thermal(.init(
|
||||
capacityAt47: at47,
|
||||
capacityAt17: at17,
|
||||
balancePointTemperature: balancePoint,
|
||||
heatLoss: buildingHeatLoss,
|
||||
heatLossMode: self.buildingHeatLoss.mode,
|
||||
heatingDesignTemperature: heatingDesignTemperature,
|
||||
warnings: warnings
|
||||
))
|
||||
}
|
||||
|
||||
func getWarnings() -> [String] {
|
||||
var warnings = [String]()
|
||||
if capacityAt17 == nil || capacityAt47 == nil {
|
||||
warnings.append(
|
||||
"Heat pump capacities are estimated based on system size - include actual capacities for higher accuracy."
|
||||
)
|
||||
}
|
||||
if case .estimated = buildingHeatLoss {
|
||||
warnings.append(
|
||||
"Building heat loss is estimated based on climate zone - include actual heat loss for higher accuracy"
|
||||
)
|
||||
}
|
||||
if heatingDesignTemperature == nil {
|
||||
warnings.append(
|
||||
"""
|
||||
Heating outdoor design temperature is based on average for climate zone - include outdoor design temperature
|
||||
for higher accuracy.
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
return warnings
|
||||
}
|
||||
|
||||
func getBuildingHeatLoss(designTemperature: Double) -> Double {
|
||||
switch buildingHeatLoss {
|
||||
case let .known(btu: btu): return btu
|
||||
case let .estimated(squareFeet: squareFeet):
|
||||
return squareFeet * climateZone!.averageHeatLossPerSquareFoot * (70 - designTemperature)
|
||||
}
|
||||
}
|
||||
|
||||
func getCapacities() -> (at47: Double, at17: Double) {
|
||||
let at47 = capacityAt47 ?? systemSize * 12000
|
||||
let at17 = capacityAt17 ?? at47 * (1 - Self.heatPumpDeratingFactor * (47 - 17))
|
||||
return (at47, at17)
|
||||
}
|
||||
|
||||
func getHeatingDesignTemperature() -> Double {
|
||||
guard let heatingDesignTemperature else {
|
||||
return climateZone!.averageHeatingDesignTemperature
|
||||
}
|
||||
return heatingDesignTemperature
|
||||
}
|
||||
|
||||
func validate() throws {
|
||||
guard systemSize > 0 else {
|
||||
throw ValidationError(message: "System size should be greater than 0.")
|
||||
}
|
||||
switch buildingHeatLoss {
|
||||
case let .known(btu: btu):
|
||||
guard btu > 0 else {
|
||||
throw ValidationError(message: "Building heat loss btu's should be greater than 0.")
|
||||
}
|
||||
case let .estimated(squareFeet: squareFeet):
|
||||
guard squareFeet > 0 else {
|
||||
throw ValidationError(message: "Building squareFeet should be greater than 0.")
|
||||
}
|
||||
guard climateZone != nil else {
|
||||
throw ValidationError(message: "Climate zone is required when estimating heat loss.")
|
||||
}
|
||||
}
|
||||
if let capacityAt47 {
|
||||
guard capacityAt47 > 0 else {
|
||||
throw ValidationError(message: "Heat pump capacity @ 47 should be greater than 0.")
|
||||
}
|
||||
}
|
||||
if let capacityAt17 {
|
||||
guard capacityAt17 > 0 else {
|
||||
throw ValidationError(message: "Heat pump capacity @ 17 should be greater than 0.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ClimateZone {
|
||||
|
||||
var averageHeatLossPerSquareFoot: Double {
|
||||
switch self {
|
||||
case .one: return 0.08
|
||||
case .two: return 0.1
|
||||
case .three: return 0.125
|
||||
case .four: return 0.15
|
||||
case .five: return 0.19
|
||||
case .six: return 0.25
|
||||
case .seven: return 0.3
|
||||
}
|
||||
}
|
||||
|
||||
var averageHeatingDesignTemperature: Double {
|
||||
switch self {
|
||||
case .one: return 45
|
||||
case .two: return 35
|
||||
case .three: return 27
|
||||
case .four: return 17
|
||||
case .five: return 7
|
||||
case .six: return -5
|
||||
case .seven: return -15
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func thermalBalancePoint(
|
||||
heatLoss: Double,
|
||||
at47: Double,
|
||||
at17: Double,
|
||||
designTemperature: Double
|
||||
) async -> Double {
|
||||
(30.0 * (((designTemperature - 65.0) * at47) + (65.0 * heatLoss))
|
||||
- ((designTemperature - 65.0) * (at47 - at17) * 47.0))
|
||||
/ ((30.0 * heatLoss) - ((designTemperature - 65.0) * (at47 - at17)))
|
||||
}
|
||||
@@ -8,6 +8,7 @@ public enum ClimateZone: String, CaseIterable, Codable, Equatable, Sendable {
|
||||
case four = "CZ4"
|
||||
case five = "CZ5"
|
||||
case six = "CZ6"
|
||||
case seven = "CZ7"
|
||||
|
||||
public var label: String { rawValue }
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ private struct IntermediateResponse: Codable, Equatable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Need to add climate zone 7 (-17).
|
||||
private func estimatedHeatingTemperature(_ coordinates: Coordinates) -> Double {
|
||||
let latitude = coordinates.latitude
|
||||
let longitude = coordinates.longitude
|
||||
@@ -103,6 +104,7 @@ private func estimatedHeatingTemperature(_ coordinates: Coordinates) -> Double {
|
||||
return 0
|
||||
}
|
||||
|
||||
// TODO: Need to add climate zone 7 (89).
|
||||
private func estimatedCoolingTemperature(_ coordinates: Coordinates) -> Double {
|
||||
let latitude = coordinates.latitude
|
||||
let longitude = coordinates.longitude
|
||||
@@ -121,6 +123,7 @@ private func estimatedCoolingTemperature(_ coordinates: Coordinates) -> Double {
|
||||
return 85
|
||||
}
|
||||
|
||||
// TODO: Need to add climate zone 7.
|
||||
private func determineClimateZone(heatingTemperature: Double, stateCode: String) -> ClimateZone {
|
||||
let hotHumidStates = ["FL", "LA", "TX", "MS", "AL", "GA", "SC"]
|
||||
if hotHumidStates.contains(stateCode.uppercased()) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import CoreModels
|
||||
|
||||
public enum HeatingBalancePoint {
|
||||
|
||||
public static let description: String = """
|
||||
@@ -17,21 +19,24 @@ public enum HeatingBalancePoint {
|
||||
public let systemSize: Double
|
||||
public let capacityAt47: Double?
|
||||
public let capacityAt17: Double?
|
||||
public let heatingDesignTemperature: Double
|
||||
public let heatingDesignTemperature: Double?
|
||||
public let buildingHeatLoss: HeatingBalancePoint.HeatLoss
|
||||
public let climateZone: ClimateZone?
|
||||
|
||||
public init(
|
||||
systemSize: Double,
|
||||
capacityAt47: Double? = nil,
|
||||
capacityAt17: Double? = nil,
|
||||
heatingDesignTemperature: Double,
|
||||
buildingHeatLoss: HeatingBalancePoint.HeatLoss
|
||||
heatingDesignTemperature: Double? = nil,
|
||||
buildingHeatLoss: HeatingBalancePoint.HeatLoss,
|
||||
climateZone: ClimateZone? = nil
|
||||
) {
|
||||
self.systemSize = systemSize
|
||||
self.capacityAt47 = capacityAt47
|
||||
self.capacityAt17 = capacityAt17
|
||||
self.heatingDesignTemperature = heatingDesignTemperature
|
||||
self.buildingHeatLoss = buildingHeatLoss
|
||||
self.climateZone = climateZone
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,11 +49,27 @@ public enum HeatingBalancePoint {
|
||||
public let capacityAt47: Double
|
||||
public let capacityAt17: Double
|
||||
public let balancePointTemperature: Double
|
||||
public let heatLoss: Double
|
||||
public let heatLossMode: HeatLoss.Mode
|
||||
public let heatingDesignTemperature: Double
|
||||
public let warnings: [String]
|
||||
|
||||
public init(capacityAt47: Double, capacityAt17: Double, balancePointTemperature: Double) {
|
||||
public init(
|
||||
capacityAt47: Double,
|
||||
capacityAt17: Double,
|
||||
balancePointTemperature: Double,
|
||||
heatLoss: Double,
|
||||
heatLossMode: HeatLoss.Mode,
|
||||
heatingDesignTemperature: Double,
|
||||
warnings: [String]
|
||||
) {
|
||||
self.capacityAt47 = capacityAt47
|
||||
self.capacityAt17 = capacityAt17
|
||||
self.balancePointTemperature = balancePointTemperature
|
||||
self.heatLoss = heatLoss
|
||||
self.heatLossMode = heatLossMode
|
||||
self.heatingDesignTemperature = heatingDesignTemperature
|
||||
self.warnings = warnings
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,5 +83,36 @@ public enum HeatingBalancePoint {
|
||||
|
||||
case known(btu: Double)
|
||||
case estimated(squareFeet: Double)
|
||||
|
||||
public var mode: Mode {
|
||||
switch self {
|
||||
case .known: return .known
|
||||
case .estimated: return .estimated
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public extension HeatingBalancePoint.Response {
|
||||
|
||||
static func mock(mode: HeatingBalancePoint.Mode) -> Self {
|
||||
switch mode {
|
||||
case .economic:
|
||||
fatalError()
|
||||
case .thermal:
|
||||
return .thermal(.init(
|
||||
capacityAt47: 24600,
|
||||
capacityAt17: 15100,
|
||||
balancePointTemperature: 38.5,
|
||||
heatLoss: 49667,
|
||||
heatLossMode: .known,
|
||||
heatingDesignTemperature: 5,
|
||||
warnings: [
|
||||
"Design temperature is estimated based on climate zone."
|
||||
]
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -286,7 +286,10 @@ public extension SiteRoute {
|
||||
}
|
||||
|
||||
public enum HeatingBalancePoint: Equatable, Sendable {
|
||||
case index(mode: Routes.HeatingBalancePoint.Mode? = nil, heatLossMode: Routes.HeatingBalancePoint.HeatLoss.Mode? = nil)
|
||||
case index(
|
||||
mode: Routes.HeatingBalancePoint.Mode? = nil,
|
||||
heatLossMode: Routes.HeatingBalancePoint.HeatLoss.Mode? = nil
|
||||
)
|
||||
case heatLossFields(mode: Routes.HeatingBalancePoint.HeatLoss.Mode)
|
||||
case submit(Routes.HeatingBalancePoint.Request)
|
||||
|
||||
@@ -317,15 +320,16 @@ public extension SiteRoute {
|
||||
OneOf {
|
||||
FormData {
|
||||
Field("systemSize") { Double.parser() }
|
||||
Optionally { Field("capcityAt47") { Double.parser() } }
|
||||
Optionally { Field("capcityAt17") { Double.parser() } }
|
||||
Field("heatingDesignTemperature") { Double.parser() }
|
||||
Optionally { Field("capacityAt47") { Double.parser() } }
|
||||
Optionally { Field("capacityAt17") { Double.parser() } }
|
||||
Optionally { Field("heatingDesignTemperature") { Double.parser() } }
|
||||
OneOf {
|
||||
Field("knownHeatLoss") { Double.parser() }
|
||||
.map(.case(Routes.HeatingBalancePoint.HeatLoss.known))
|
||||
Field("simplifiedHeatLoss") { Double.parser() }
|
||||
.map(.case(Routes.HeatingBalancePoint.HeatLoss.estimated))
|
||||
}
|
||||
Optionally { Field("climateZone") { ClimateZone.parser() } }
|
||||
}
|
||||
.map(.memberwise(Routes.HeatingBalancePoint.Request.Thermal.init))
|
||||
.map(.case(Routes.HeatingBalancePoint.Request.thermal))
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import Foundation
|
||||
|
||||
private let numberFormatter: NumberFormatter = {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.numberStyle = .decimal
|
||||
formatter.maximumFractionDigits = 2
|
||||
return formatter
|
||||
}()
|
||||
|
||||
public extension String.StringInterpolation {
|
||||
mutating func appendInterpolation(double: Double, fractionDigits: Int = 2) {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.numberStyle = .decimal
|
||||
formatter.maximumFractionDigits = fractionDigits
|
||||
appendInterpolation(numberFormatter.string(from: NSNumber(value: double))!)
|
||||
appendInterpolation(formatter.string(from: NSNumber(value: double))!)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,17 +110,17 @@ public struct Toggle: HTML {
|
||||
switch isOn {
|
||||
case true:
|
||||
SecondaryButton(label: onLabel)
|
||||
.attributes(.class("rounded-s-lg"), .disabled)
|
||||
.attributes(.class("rounded-s-lg"), .disabled, .type(.button))
|
||||
|
||||
PrimaryButton(label: offLabel)
|
||||
.attributes(contentsOf: offAttributes + [.class("rounded-e-lg")])
|
||||
.attributes(contentsOf: offAttributes + [.class("rounded-e-lg"), .type(.button)])
|
||||
|
||||
case false:
|
||||
PrimaryButton(label: onLabel)
|
||||
.attributes(contentsOf: onAttributes + [.class("rounded-s-lg")])
|
||||
.attributes(contentsOf: onAttributes + [.class("rounded-s-lg"), .type(.button)])
|
||||
|
||||
SecondaryButton(label: offLabel)
|
||||
.attributes(.class("rounded-e-lg"), .disabled)
|
||||
.attributes(.class("rounded-e-lg"), .disabled, .type(.button))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public struct WarningBox<Header: HTML>: HTML, Sendable {
|
||||
div(.id("warnings")) {
|
||||
if warnings.count > 0 {
|
||||
header(warnings)
|
||||
ul(.class("list-disc mx-10")) {
|
||||
ul(.class("list-disc mx-10 mt-4")) {
|
||||
for warning in warnings {
|
||||
li { warning }
|
||||
}
|
||||
@@ -52,7 +52,7 @@ public extension WarningBox where Header == HTMLElement<HTMLTag.span, HTMLText>
|
||||
self.init(
|
||||
warnings: warnings,
|
||||
header: { warnings in
|
||||
span(.class("font-semibold mb-4 border-b")) { "Warning\(warnings.count > 1 ? "s:" : ":")" }
|
||||
span(.class("font-semibold mb-4 border-b border-amber-500")) { "Warning\(warnings.count > 1 ? "s:" : ":")" }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -112,13 +112,16 @@ extension ViewController: DependencyKey {
|
||||
case let .heatingBalancePoint(route):
|
||||
switch route {
|
||||
case let .index(mode: mode, heatLossMode: heatLossMode):
|
||||
return request.respond(HeatingBalancePointForm(mode: mode, heatLossMode: heatLossMode, response: nil))
|
||||
return request.respond(HeatingBalancePointForm(
|
||||
mode: mode,
|
||||
heatLossMode: heatLossMode,
|
||||
response: nil
|
||||
))
|
||||
case let .heatLossFields(mode: mode):
|
||||
logger.debug("Heat loss mode: \(mode)")
|
||||
return HeatingBalancePointForm.HeatLossFields(mode: mode)
|
||||
case .submit:
|
||||
// FIX:
|
||||
fatalError()
|
||||
case let .submit(request):
|
||||
let response = try await request.respond(logger: logger)
|
||||
return HeatingBalancePointResponse(response: response)
|
||||
}
|
||||
|
||||
case let .hvacSystemPerformance(route):
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user