feat: Adds proposed kw interpolation.
Some checks failed
CI / Ubuntu (push) Failing after 23m10s

This commit is contained in:
2025-03-13 12:38:42 -04:00
parent 5f6d2a7b6c
commit 5b2e20921c
4 changed files with 191 additions and 20 deletions

View File

@@ -0,0 +1,61 @@
import CoreFoundation
import Models
import Validations
extension ProposedKWInterpolation.Request {
private static let oversizingLimit = 175
func respond() async throws -> ProposedKWInterpolation.Response {
try await validate()
let (requiredKW, optionalHeatPump) = try await requiredKW()
let capacityAsPercentOfLoad = normalizePercentage(proposedKW, requiredKW)
var failures = [String]()
if capacityAsPercentOfLoad > Self.oversizingLimit {
failures.append("Oversizing failure.")
}
return .init(
failures: failures.isEmpty ? nil : failures,
supplementalHeatRequired: requiredKW,
capacityAsPercentOfLoad: capacityAsPercentOfLoad,
oversizingLimit: Self.oversizingLimit,
altitudeAdjustmentMultiplier: optionalHeatPump?.altitudeAdjustmentMultiplier,
balancePointTemperature: optionalHeatPump?.balancePointTemperature,
capacityAtDesign: optionalHeatPump?.capacityAtDesign,
finalCapacity: optionalHeatPump?.finalCapacity
)
}
private var heatPumpRequest: HeatPumpHeatingInterpolation.Request? {
guard let winterDesignTemperature, let climateType, let capacity else {
return nil
}
return .init(
winterDesignTemperature: winterDesignTemperature,
heatLoss: heatLoss,
climateType: climateType,
capacity: capacity
)
}
private func requiredKW() async throws -> (Int, HeatPumpHeatingInterpolation.Response?) {
guard let heatPumpRequest = heatPumpRequest else {
let requiredKW = try await RequiredKW.Request(heatLoss: heatLoss).respond()
return (Int(requiredKW.requiredKW), nil)
}
let heatPumpResponse = try await heatPumpRequest.respond()
return (heatPumpResponse.supplementalHeatRequired, heatPumpResponse)
}
}
extension ProposedKWInterpolation.Request: AsyncValidatable {
public var body: some AsyncValidation<Self> {
AsyncValidator.accumulating {
AsyncValidator.greaterThan(\.heatLoss, 0)
AsyncValidator.greaterThan(\.proposedKW, 0)
}
}
}

View File

@@ -12,35 +12,39 @@ public extension DependencyValues {
@DependencyClient @DependencyClient
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 interpolate: Interpolations
public var requiredKW: @Sendable (RequiredKW.Request) async throws -> RequiredKW.Response
public var sizingLimits: @Sendable (SizingLimits.Request) async throws -> SizingLimits.Response
}
public var coolingInterpolation: @DependencyClient
@Sendable (CoolingInterpolation.Request) async throws -> CoolingInterpolation.Response public struct Interpolations: Sendable {
public var cooling: @Sendable (CoolingInterpolation.Request) async throws -> CoolingInterpolation.Response
public var furnaceInterpolation: public var furnace: @Sendable (FurnaceInterpolation.Request) async throws -> FurnaceInterpolation.Response
@Sendable (FurnaceInterpolation.Request) async throws -> FurnaceInterpolation.Response
public var heatPumpHeatingInterpolation: public var heatPumpHeating:
@Sendable (HeatPumpHeatingInterpolation.Request) async throws -> HeatPumpHeatingInterpolation.Response @Sendable (HeatPumpHeatingInterpolation.Request) async throws -> HeatPumpHeatingInterpolation.Response
public var requiredKW: @Sendable (RequiredKW.Request) async throws -> RequiredKW.Response public var proposeKW: @Sendable (ProposedKWInterpolation.Request) async throws -> ProposedKWInterpolation.Response
public var sizingLimits: @Sendable (SizingLimits.Request) async throws -> SizingLimits.Response
} }
extension ManualS: DependencyKey { extension ManualS: DependencyKey {
public static let liveValue = Self( public static let liveValue = Self(
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() }, interpolate: .init(
furnaceInterpolation: { try await $0.respond() }, cooling: { try await $0.respond() },
heatPumpHeatingInterpolation: { try await $0.respond() }, furnace: { try await $0.respond() },
heatPumpHeating: { try await $0.respond() },
proposeKW: { try await $0.respond() }
),
requiredKW: { try await $0.respond() }, requiredKW: { try await $0.respond() },
sizingLimits: { try await $0.respond() } sizingLimits: { try await $0.respond() }
) )
} }
extension ManualS: TestDependencyKey { extension ManualS: TestDependencyKey {
public static let testValue: ManualS = Self() public static let testValue: ManualS = Self(interpolate: .init())
} }

View File

@@ -0,0 +1,66 @@
public enum ProposedKWInterpolation {
public struct Request: Codable, Equatable, Sendable {
public let elevation: Int?
// These are required if also doing a heat pump heating interpolation.
public let winterDesignTemperature: Int?
public let climateType: SystemType.ClimateType?
public let capacity: Capacity.HeatPumpHeating?
public let heatLoss: Int
public let proposedKW: Int
public init(
elevation: Int? = nil,
winterDesignTemperature: Int? = nil,
climateType: SystemType.ClimateType? = nil,
capacity: Capacity.HeatPumpHeating? = nil,
heatLoss: Int,
proposedKW: Int
) {
self.elevation = elevation
self.winterDesignTemperature = winterDesignTemperature
self.climateType = climateType
self.capacity = capacity
self.heatLoss = heatLoss
self.proposedKW = proposedKW
}
}
public struct Response: Codable, Equatable, Sendable {
public let failed: Bool
public let failures: [String]?
public let supplementalHeatRequired: Int
public let capacityAsPercentOfLoad: Int
public let oversizingLimit: Int
// These are if a heat pump interpolation is also done.
public let altitudeAdjustmentMultiplier: Double?
public let balancePointTemperature: Int?
public let capacityAtDesign: Int?
public let finalCapacity: Capacity.HeatPumpHeating?
public init(
failures: [String]? = nil,
supplementalHeatRequired: Int,
capacityAsPercentOfLoad: Int,
oversizingLimit: Int,
altitudeAdjustmentMultiplier: Double? = nil,
balancePointTemperature: Int? = nil,
capacityAtDesign: Int? = nil,
finalCapacity: Capacity.HeatPumpHeating? = nil
) {
self.failed = failures == nil ? false : !failures!.isEmpty
self.failures = failures
self.supplementalHeatRequired = supplementalHeatRequired
self.capacityAsPercentOfLoad = capacityAsPercentOfLoad
self.oversizingLimit = oversizingLimit
self.altitudeAdjustmentMultiplier = altitudeAdjustmentMultiplier
self.balancePointTemperature = balancePointTemperature
self.capacityAtDesign = capacityAtDesign
self.finalCapacity = finalCapacity
}
}
}

View File

@@ -13,7 +13,7 @@ struct ManualSTests {
} operation: { } operation: {
@Dependency(\.manualS) var manualS @Dependency(\.manualS) var manualS
let response = try await manualS.coolingInterpolation(.init( let response = try await manualS.interpolate.cooling(.init(
summerDesignInfo: .mock, summerDesignInfo: .mock,
coolingLoad: .mockCoolingLoad, coolingLoad: .mockCoolingLoad,
systemType: .airToAir(type: .airConditioner, compressor: .singleSpeed, climate: .mildWinterOrLatentLoad), systemType: .airToAir(type: .airConditioner, compressor: .singleSpeed, climate: .mildWinterOrLatentLoad),
@@ -48,7 +48,7 @@ struct ManualSTests {
} operation: { } operation: {
@Dependency(\.manualS) var manualS @Dependency(\.manualS) var manualS
let response = try await manualS.coolingInterpolation(.init( let response = try await manualS.interpolate.cooling(.init(
summerDesignInfo: .mock, summerDesignInfo: .mock,
coolingLoad: .mockCoolingLoad, coolingLoad: .mockCoolingLoad,
systemType: .airToAir(type: .airConditioner, compressor: .singleSpeed, climate: .mildWinterOrLatentLoad), systemType: .airToAir(type: .airConditioner, compressor: .singleSpeed, climate: .mildWinterOrLatentLoad),
@@ -78,7 +78,7 @@ struct ManualSTests {
} operation: { } operation: {
@Dependency(\.manualS) var manualS @Dependency(\.manualS) var manualS
let response = try await manualS.coolingInterpolation(.init( let response = try await manualS.interpolate.cooling(.init(
summerDesignInfo: .mock, summerDesignInfo: .mock,
coolingLoad: .mockCoolingLoad, coolingLoad: .mockCoolingLoad,
systemType: .airToAir(type: .airConditioner, compressor: .singleSpeed, climate: .mildWinterOrLatentLoad), systemType: .airToAir(type: .airConditioner, compressor: .singleSpeed, climate: .mildWinterOrLatentLoad),
@@ -108,7 +108,7 @@ struct ManualSTests {
} operation: { } operation: {
@Dependency(\.manualS) var manualS @Dependency(\.manualS) var manualS
let response = try await manualS.coolingInterpolation(.init( let response = try await manualS.interpolate.cooling(.init(
summerDesignInfo: .mock, summerDesignInfo: .mock,
coolingLoad: .mockCoolingLoad, coolingLoad: .mockCoolingLoad,
systemType: .airToAir(type: .airConditioner, compressor: .singleSpeed, climate: .mildWinterOrLatentLoad), systemType: .airToAir(type: .airConditioner, compressor: .singleSpeed, climate: .mildWinterOrLatentLoad),
@@ -145,7 +145,7 @@ struct ManualSTests {
} operation: { } operation: {
@Dependency(\.manualS) var manualS @Dependency(\.manualS) var manualS
let response = try await manualS.heatPumpHeatingInterpolation(.init( let response = try await manualS.interpolate.heatPumpHeating(.init(
winterDesignTemperature: 5, winterDesignTemperature: 5,
heatLoss: 49667, heatLoss: 49667,
climateType: .mildWinterOrLatentLoad, climateType: .mildWinterOrLatentLoad,
@@ -166,7 +166,7 @@ struct ManualSTests {
} operation: { } operation: {
@Dependency(\.manualS) var manualS @Dependency(\.manualS) var manualS
let response = try await manualS.furnaceInterpolation( let response = try await manualS.interpolate.furnace(
.init( .init(
elevation: nil, elevation: nil,
winterDesignTemperature: 5, winterDesignTemperature: 5,
@@ -189,7 +189,7 @@ struct ManualSTests {
} operation: { } operation: {
@Dependency(\.manualS) var manualS @Dependency(\.manualS) var manualS
let response = try await manualS.furnaceInterpolation( let response = try await manualS.interpolate.furnace(
.init( .init(
elevation: nil, elevation: nil,
winterDesignTemperature: 5, winterDesignTemperature: 5,
@@ -204,6 +204,46 @@ struct ManualSTests {
} }
} }
@Test
func proposedKWInterpolation() async throws {
try await withDependencies {
$0.manualS = .liveValue
} operation: {
@Dependency(\.manualS) var manualS
let response = try await manualS.interpolate.proposeKW(
.init(heatLoss: 49667, proposedKW: 15)
)
#expect(!response.failed)
#expect(response.capacityAsPercentOfLoad == 107)
#expect(response.supplementalHeatRequired == 14)
}
}
@Test
func proposedKWInterpolation_with_heatPump() async throws {
try await withDependencies {
$0.manualS = .liveValue
} operation: {
@Dependency(\.manualS) var manualS
let response = try await manualS.interpolate.proposeKW(
.init(
winterDesignTemperature: 5,
climateType: .mildWinterOrLatentLoad,
capacity: .mock,
heatLoss: 49667,
proposedKW: 15
)
)
#expect(!response.failed)
#expect(response.capacityAsPercentOfLoad == 136)
#expect(response.supplementalHeatRequired == 11)
}
}
@Test @Test
func balancePoint() async throws { func balancePoint() async throws {
try await withDependencies { try await withDependencies {