feat: Adds heat pump heating interpolation.
All checks were successful
CI / Ubuntu (push) Successful in 11m46s

This commit is contained in:
2025-03-13 10:55:15 -04:00
parent ec787b9f4d
commit bd33827e53
9 changed files with 182 additions and 26 deletions

View File

@@ -3,6 +3,7 @@ disabled_rules:
- fuction_body_length - fuction_body_length
- opening_brace - opening_brace
- nesting - nesting
- type_body_length
included: included:
- Sources - Sources

View File

@@ -2,7 +2,6 @@ import Models
import Validations import Validations
extension HouseLoad: AsyncValidatable { extension HouseLoad: AsyncValidatable {
public var body: some AsyncValidation<Self> { public var body: some AsyncValidation<Self> {
AsyncValidator.accumulating { AsyncValidator.accumulating {
AsyncValidator.greaterThan(\.coolingTotal, 0) AsyncValidator.greaterThan(\.coolingTotal, 0)

View File

@@ -222,8 +222,8 @@ private extension CoolingInterpolation.OneWayOutdoor {
private func interpolate( private func interpolate(
outdoorDesignTemperature: Int, outdoorDesignTemperature: Int,
aboveCapacity: KeyPath<CoolingInterpolation.OneWayOutdoor.Capacities.Capacity, Int>, aboveCapacity: KeyPath<CoolingInterpolation.OneWayOutdoor.Capacities.CapacityContainer, Int>,
belowCapacity: KeyPath<CoolingInterpolation.OneWayOutdoor.Capacities.Capacity, Int> belowCapacity: KeyPath<CoolingInterpolation.OneWayOutdoor.Capacities.CapacityContainer, Int>
) async -> Int { ) async -> Int {
return await interpolateOutdoorCapacity( return await interpolateOutdoorCapacity(
outdoorDesignTemperature: outdoorDesignTemperature, outdoorDesignTemperature: outdoorDesignTemperature,
@@ -357,7 +357,7 @@ extension CoolingInterpolation.OneWayOutdoor.Capacities: AsyncValidatable {
} }
} }
extension CoolingInterpolation.OneWayOutdoor.Capacities.Capacity: AsyncValidatable { extension CoolingInterpolation.OneWayOutdoor.Capacities.CapacityContainer: AsyncValidatable {
public var body: some AsyncValidation<Self> { public var body: some AsyncValidation<Self> {
AsyncValidator.accumulating { AsyncValidator.accumulating {
AsyncValidator.greaterThan(\.outdoorTemperature, 0) AsyncValidator.greaterThan(\.outdoorTemperature, 0)

View File

@@ -0,0 +1,80 @@
import CoreFoundation
import Models
import Validations
extension HeatPumpHeatingInterpolation.Request {
func respond() async throws -> HeatPumpHeatingInterpolation.Response {
try await validate()
let altitudeAdjustmentMultiplier = try await altitudeAdjustmentMultiplier()
let finalCapacity = finalCapacity(altitudeAdjustmentMultiplier)
let balancePointTemperature = try await balancePoint(finalCapacity)
let capacityAtDesign = capacityAtDesign(finalCapacity)
let requiredKW = try await requiredKW(capacityAtDesign)
return .init(
altitudeAdjustmentMultiplier: altitudeAdjustmentMultiplier,
balancePointTemperature: balancePointTemperature,
capacityAtDesign: capacityAtDesign,
finalCapacity: finalCapacity,
supplementalHeatRequired: requiredKW
)
}
private func balancePoint(_ finalCapacity: Capacity.HeatPumpHeating) async throws -> Int {
let response = try await BalancePoint.Request(
winterDesignTemperature: winterDesignTemperature,
heatLoss: heatLoss,
heatPumpCapacity: finalCapacity
).respond()
return Int(response.balancePointTemperature)
}
private func altitudeAdjustmentMultiplier() async throws -> Double? {
guard let elevation else { return nil }
let response = try await Derating.Request(
elevation: elevation,
systemType: .airToAir(type: .heatPump, compressor: .singleSpeed, climate: climateType)
).respond()
return response.heating
}
private func finalCapacity(
_ adjustmentMultiplier: Double?
) -> Capacity.HeatPumpHeating {
guard let adjustmentMultiplier else { return capacity }
return .init(
at47: Int(Double(capacity.at47) * adjustmentMultiplier),
at17: Int(Double(capacity.at17) * adjustmentMultiplier)
)
}
private func capacityAtDesign(
_ finalCapacity: Capacity.HeatPumpHeating
) -> Int {
let outdoorTemperature = Double(winterDesignTemperature)
let derating = Double((finalCapacity.at47 - finalCapacity.at17) / 30)
* (17 - outdoorTemperature)
return Int(Double(finalCapacity.at17) - derating)
}
private func requiredKW(_ capacityAtDesign: Int) async throws -> Int {
let response = try await RequiredKW.Request(
capacityAtDesign: capacityAtDesign,
heatLoss: heatLoss
).respond()
return Int(response.requiredKW)
}
}
extension HeatPumpHeatingInterpolation.Request: AsyncValidatable {
public var body: some AsyncValidation<Self> {
AsyncValidator.accumulating {
AsyncValidator.greaterThan(\.heatLoss, 0)
AsyncValidator.validate(\.capacity)
}
}
}

View File

@@ -13,7 +13,11 @@ public extension DependencyValues {
public struct ManualS: Sendable { public struct ManualS: Sendable {
public var balancePoint: @Sendable (BalancePoint.Request) async throws -> BalancePoint.Response public var balancePoint: @Sendable (BalancePoint.Request) async throws -> BalancePoint.Response
public var derating: @Sendable (Derating.Request) async throws -> Derating.Response public var derating: @Sendable (Derating.Request) async throws -> Derating.Response
public var coolingInterpolation: @Sendable (CoolingInterpolation.Request) async throws -> CoolingInterpolation.Response public var coolingInterpolation:
@Sendable (CoolingInterpolation.Request) async throws -> CoolingInterpolation.Response
public var heatPumpHeatingInterpolation:
@Sendable (HeatPumpHeatingInterpolation.Request) async throws -> HeatPumpHeatingInterpolation.Response
public var requiredKW: @Sendable (RequiredKW.Request) async throws -> RequiredKW.Response public var requiredKW: @Sendable (RequiredKW.Request) async throws -> RequiredKW.Response
public var sizingLimits: @Sendable (SizingLimits.Request) async throws -> SizingLimits.Response public var sizingLimits: @Sendable (SizingLimits.Request) async throws -> SizingLimits.Response
} }
@@ -23,6 +27,7 @@ extension ManualS: DependencyKey {
balancePoint: { try await $0.respond() }, balancePoint: { try await $0.respond() },
derating: { try await $0.respond() }, derating: { try await $0.respond() },
coolingInterpolation: { try await $0.respond() }, coolingInterpolation: { try await $0.respond() },
heatPumpHeatingInterpolation: { try await $0.respond() },
requiredKW: { try await $0.respond() }, requiredKW: { try await $0.respond() },
sizingLimits: { try await $0.respond() } sizingLimits: { try await $0.respond() }
) )

View File

@@ -4,7 +4,7 @@ public enum CoolingInterpolation {
public let elevation: Int? public let elevation: Int?
public let summerDesignInfo: DesignInfo.Summer public let summerDesignInfo: DesignInfo.Summer
public let coolingLoad: Models.Capacity.Cooling public let coolingLoad: Capacity.Cooling
public let systemType: SystemType public let systemType: SystemType
public let interpolation: InterpolationRequest public let interpolation: InterpolationRequest
@@ -27,18 +27,18 @@ public enum CoolingInterpolation {
public let failed: Bool public let failed: Bool
public let failures: [String]? public let failures: [String]?
public let interpolatedCapacity: Models.Capacity.Cooling public let interpolatedCapacity: Capacity.Cooling
public let excessLatent: Int public let excessLatent: Int
public let finalCapacityAtDesign: Models.Capacity.Cooling public let finalCapacityAtDesign: Capacity.Cooling
public let altitudeDerating: AdjustmentMultiplier? public let altitudeDerating: AdjustmentMultiplier?
public let capacityAsPercentOfLoad: Models.Capacity.Cooling public let capacityAsPercentOfLoad: Capacity.Cooling
public let sizingLimits: SizingLimits.Response public let sizingLimits: SizingLimits.Response
public init( public init(
failures: [String]? = nil, failures: [String]? = nil,
interpolatedCapacity: Models.Capacity.Cooling, interpolatedCapacity: Capacity.Cooling,
excessLatent: Int, excessLatent: Int,
finalCapacityAtDesign: Models.Capacity.Cooling, finalCapacityAtDesign: Capacity.Cooling,
altitudeDerating: AdjustmentMultiplier? = nil, altitudeDerating: AdjustmentMultiplier? = nil,
capacityAsPercentOfLoad: Capacity.Cooling, capacityAsPercentOfLoad: Capacity.Cooling,
sizingLimits: SizingLimits.Response sizingLimits: SizingLimits.Response
@@ -55,7 +55,7 @@ public enum CoolingInterpolation {
} }
public enum InterpolationRequest: Codable, Equatable, Sendable { public enum InterpolationRequest: Codable, Equatable, Sendable {
case noInterpolation(Models.Capacity.ManufacturersCooling, AdjustmentMultiplier? = nil) case noInterpolation(Capacity.ManufacturersCooling, AdjustmentMultiplier? = nil)
case oneWayIndoor(OneWayIndoor) case oneWayIndoor(OneWayIndoor)
case oneWayOutdoor(OneWayOutdoor) case oneWayOutdoor(OneWayOutdoor)
case twoWay(TwoWay) case twoWay(TwoWay)
@@ -82,12 +82,12 @@ public enum CoolingInterpolation {
public struct Capacities: Codable, Equatable, Sendable { public struct Capacities: Codable, Equatable, Sendable {
public let aboveDewpoint: Models.Capacity.ManufacturersContainer public let aboveDewpoint: Capacity.ManufacturersContainer
public let belowDewpoint: Models.Capacity.ManufacturersContainer public let belowDewpoint: Capacity.ManufacturersContainer
public init( public init(
aboveDewpoint: Models.Capacity.ManufacturersContainer, aboveDewpoint: Capacity.ManufacturersContainer,
belowDewpoint: Models.Capacity.ManufacturersContainer belowDewpoint: Capacity.ManufacturersContainer
) { ) {
self.aboveDewpoint = aboveDewpoint self.aboveDewpoint = aboveDewpoint
self.belowDewpoint = belowDewpoint self.belowDewpoint = belowDewpoint
@@ -116,18 +116,18 @@ public enum CoolingInterpolation {
public struct Capacities: Codable, Equatable, Sendable { public struct Capacities: Codable, Equatable, Sendable {
public let aboveOutdoor: Capacity public let aboveOutdoor: CapacityContainer
public let belowOutdoor: Capacity public let belowOutdoor: CapacityContainer
public init( public init(
aboveOutdoor: CoolingInterpolation.OneWayOutdoor.Capacities.Capacity, aboveOutdoor: CoolingInterpolation.OneWayOutdoor.Capacities.CapacityContainer,
belowOutdoor: CoolingInterpolation.OneWayOutdoor.Capacities.Capacity belowOutdoor: CoolingInterpolation.OneWayOutdoor.Capacities.CapacityContainer
) { ) {
self.aboveOutdoor = aboveOutdoor self.aboveOutdoor = aboveOutdoor
self.belowOutdoor = belowOutdoor self.belowOutdoor = belowOutdoor
} }
public struct Capacity: Codable, Equatable, Sendable { public struct CapacityContainer: Codable, Equatable, Sendable {
public let outdoorTemperature: Int public let outdoorTemperature: Int
public let totalCapacity: Int public let totalCapacity: Int
@@ -174,13 +174,13 @@ public enum CoolingInterpolation {
public struct CapacityContainer: Codable, Equatable, Sendable { public struct CapacityContainer: Codable, Equatable, Sendable {
public let outdoorTemperature: Int public let outdoorTemperature: Int
public let aboveDewPoint: Models.Capacity.ManufacturersContainer public let aboveDewPoint: Capacity.ManufacturersContainer
public let belowDewPoint: Models.Capacity.ManufacturersContainer public let belowDewPoint: Capacity.ManufacturersContainer
public init( public init(
outdoorTemperature: Int, outdoorTemperature: Int,
aboveDewPoint: Models.Capacity.ManufacturersContainer, aboveDewPoint: Capacity.ManufacturersContainer,
belowDewPoint: Models.Capacity.ManufacturersContainer belowDewPoint: Capacity.ManufacturersContainer
) { ) {
self.outdoorTemperature = outdoorTemperature self.outdoorTemperature = outdoorTemperature
self.aboveDewPoint = aboveDewPoint self.aboveDewPoint = aboveDewPoint

View File

@@ -37,7 +37,6 @@ public enum Capacity {
} }
} }
// TODO: Remove.
public struct ManufacturersCooling: Codable, Equatable, Sendable { public struct ManufacturersCooling: Codable, Equatable, Sendable {
public let airflow: Int public let airflow: Int

View File

@@ -0,0 +1,47 @@
public enum HeatPumpHeatingInterpolation {
public struct Request: Codable, Equatable, Sendable {
public let elevation: Int?
public let winterDesignTemperature: Int
public let heatLoss: Int
public let climateType: SystemType.ClimateType
public let capacity: Capacity.HeatPumpHeating
public init(
elevation: Int? = nil,
winterDesignTemperature: Int,
heatLoss: Int,
climateType: SystemType.ClimateType,
capacity: Capacity.HeatPumpHeating
) {
self.elevation = elevation
self.winterDesignTemperature = winterDesignTemperature
self.heatLoss = heatLoss
self.climateType = climateType
self.capacity = capacity
}
}
public struct Response: Codable, Equatable, Sendable {
public let altitudeAdjustmentMultiplier: Double?
public let balancePointTemperature: Int
public let capacityAtDesign: Int
public let finalCapacity: Capacity.HeatPumpHeating
public let supplementalHeatRequired: Int
public init(
altitudeAdjustmentMultiplier: Double? = nil,
balancePointTemperature: Int,
capacityAtDesign: Int,
finalCapacity: Capacity.HeatPumpHeating,
supplementalHeatRequired: Int
) {
self.altitudeAdjustmentMultiplier = altitudeAdjustmentMultiplier
self.balancePointTemperature = balancePointTemperature
self.capacityAtDesign = capacityAtDesign
self.finalCapacity = finalCapacity
self.supplementalHeatRequired = supplementalHeatRequired
}
}
}

View File

@@ -138,6 +138,27 @@ struct ManualSTests {
} }
} }
@Test
func heatPumpHeatingInterpolation() async throws {
try await withDependencies {
$0.manualS = .liveValue
} operation: {
@Dependency(\.manualS) var manualS
let response = try await manualS.heatPumpHeatingInterpolation(.init(
winterDesignTemperature: 5,
heatLoss: 49667,
climateType: .mildWinterOrLatentLoad,
capacity: .mock
))
#expect(response.finalCapacity == .mock)
#expect(response.capacityAtDesign == 11308)
#expect(response.balancePointTemperature == 38)
#expect(response.supplementalHeatRequired == 11)
}
}
@Test @Test
func balancePoint() async throws { func balancePoint() async throws {
try await withDependencies { try await withDependencies {
@@ -351,3 +372,7 @@ extension Capacity.Cooling {
extension DesignInfo.Summer { extension DesignInfo.Summer {
static let mock = Self(outdoorTemperature: 90, indoorTemperature: 75, indoorHumidity: 50) static let mock = Self(outdoorTemperature: 90, indoorTemperature: 75, indoorHumidity: 50)
} }
extension Capacity.HeatPumpHeating {
static let mock = Self(at47: 24600, at17: 15100)
}