354 lines
13 KiB
Swift
354 lines
13 KiB
Swift
import CoreFoundation
|
|
import Dependencies
|
|
import ManualS
|
|
import Testing
|
|
|
|
@Suite("ManualSTests")
|
|
struct ManualSTests {
|
|
|
|
@Test
|
|
func coolingInterpolation_noInterpolation() async throws {
|
|
try await withDependencies {
|
|
$0.manualS = .liveValue
|
|
} operation: {
|
|
@Dependency(\.manualS) var manualS
|
|
|
|
let response = try await manualS.coolingInterpolation(.init(
|
|
summerDesignInfo: .mock,
|
|
coolingLoad: .mockCoolingLoad,
|
|
systemType: .airToAir(type: .airConditioner, compressor: .singleSpeed, climate: .mildWinterOrLatentLoad),
|
|
interpolation: .noInterpolation(
|
|
.init(
|
|
airflow: 800,
|
|
capacity: .init(
|
|
dryBulbTemperature: 75,
|
|
wetBulbTemperature: 63,
|
|
outdoorTemperature: 90,
|
|
totalCapacity: 22600,
|
|
sensibleCapacity: 16850
|
|
)
|
|
),
|
|
nil
|
|
)
|
|
))
|
|
|
|
#expect(response.failed)
|
|
#expect(response.failures == ["Oversizing total failure."])
|
|
#expect(response.interpolatedCapacity == .init(total: 22600, sensible: 16850))
|
|
#expect(response.excessLatent == 886)
|
|
#expect(response.finalCapacityAtDesign == .init(total: 22600, sensible: 17736))
|
|
#expect(response.capacityAsPercentOfLoad == .init(total: 126, sensible: 127, latent: 122))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func coolingInterpolation_oneWayIndoor() async throws {
|
|
try await withDependencies {
|
|
$0.manualS = .liveValue
|
|
} operation: {
|
|
@Dependency(\.manualS) var manualS
|
|
|
|
let response = try await manualS.coolingInterpolation(.init(
|
|
summerDesignInfo: .mock,
|
|
coolingLoad: .mockCoolingLoad,
|
|
systemType: .airToAir(type: .airConditioner, compressor: .singleSpeed, climate: .mildWinterOrLatentLoad),
|
|
interpolation: .oneWayIndoor(.init(
|
|
airflow: 800,
|
|
outdoorTemperature: 95,
|
|
capacities: .init(
|
|
aboveDewpoint: .init(wetBulb: 67, totalCapacity: 24828, sensibleCapacity: 15937),
|
|
belowDewpoint: .init(wetBulb: 62, totalCapacity: 23046, sensibleCapacity: 19078)
|
|
)
|
|
))
|
|
))
|
|
|
|
#expect(response.failed)
|
|
#expect(response.failures == ["Oversizing total failure."])
|
|
#expect(response.interpolatedCapacity == .init(total: 23402, sensible: 18450))
|
|
#expect(response.excessLatent == 487)
|
|
#expect(response.finalCapacityAtDesign == .init(total: 23402, sensible: 18937))
|
|
#expect(response.capacityAsPercentOfLoad == .init(total: 130, sensible: 136, latent: 112))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func coolingInterpolation_oneWayOutdoor() async throws {
|
|
try await withDependencies {
|
|
$0.manualS = .liveValue
|
|
} operation: {
|
|
@Dependency(\.manualS) var manualS
|
|
|
|
let response = try await manualS.coolingInterpolation(.init(
|
|
summerDesignInfo: .mock,
|
|
coolingLoad: .mockCoolingLoad,
|
|
systemType: .airToAir(type: .airConditioner, compressor: .singleSpeed, climate: .mildWinterOrLatentLoad),
|
|
interpolation: .oneWayOutdoor(.init(
|
|
airflow: 800,
|
|
wetBulb: 63,
|
|
capacities: .init(
|
|
aboveOutdoor: .init(outdoorTemperature: 95, totalCapacity: 22000, sensibleCapacity: 16600),
|
|
belowOutdoor: .init(outdoorTemperature: 85, totalCapacity: 23200, sensibleCapacity: 17100)
|
|
)
|
|
))
|
|
))
|
|
|
|
#expect(response.failed)
|
|
#expect(response.failures == ["Oversizing total failure."])
|
|
#expect(response.interpolatedCapacity == .init(total: 22600, sensible: 16850))
|
|
#expect(response.excessLatent == 886)
|
|
#expect(response.finalCapacityAtDesign == .init(total: 22600, sensible: 17736))
|
|
#expect(response.capacityAsPercentOfLoad == .init(total: 126, sensible: 127, latent: 122))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func coolingInterpolation_twoWay() async throws {
|
|
try await withDependencies {
|
|
$0.manualS = .liveValue
|
|
} operation: {
|
|
@Dependency(\.manualS) var manualS
|
|
|
|
let response = try await manualS.coolingInterpolation(.init(
|
|
summerDesignInfo: .mock,
|
|
coolingLoad: .mockCoolingLoad,
|
|
systemType: .airToAir(type: .airConditioner, compressor: .singleSpeed, climate: .mildWinterOrLatentLoad),
|
|
interpolation: .twoWay(.init(
|
|
airflow: 800,
|
|
capacities: .init(
|
|
above: .init(
|
|
outdoorTemperature: 95,
|
|
aboveDewPoint: .init(wetBulb: 67, totalCapacity: 24828, sensibleCapacity: 15937),
|
|
belowDewPoint: .init(wetBulb: 62, totalCapacity: 23046, sensibleCapacity: 19078)
|
|
),
|
|
below: .init(
|
|
outdoorTemperature: 85,
|
|
aboveDewPoint: .init(wetBulb: 67, totalCapacity: 25986, sensibleCapacity: 16330),
|
|
belowDewPoint: .init(wetBulb: 62, totalCapacity: 24029, sensibleCapacity: 19605)
|
|
)
|
|
)
|
|
))
|
|
))
|
|
|
|
#expect(response.failed)
|
|
#expect(response.failures == ["Oversizing total failure."])
|
|
#expect(response.interpolatedCapacity == .init(total: 23915, sensible: 18700))
|
|
#expect(response.excessLatent == 618)
|
|
#expect(response.finalCapacityAtDesign == .init(total: 23915, sensible: 19318))
|
|
#expect(response.capacityAsPercentOfLoad == .init(total: 133, sensible: 139, latent: 115))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func balancePoint() async throws {
|
|
try await withDependencies {
|
|
$0.manualS = .liveValue
|
|
} operation: {
|
|
@Dependency(\.manualS) var manualS
|
|
let balancePoint = try await manualS.balancePoint(.init(
|
|
winterDesignTemperature: 5,
|
|
heatLoss: 49667,
|
|
heatPumpCapacity: .init(at47: 24600, at17: 15100)
|
|
))
|
|
let rounded = round(balancePoint.balancePointTemperature * 10) / 10
|
|
#expect(rounded == 38.5)
|
|
}
|
|
}
|
|
|
|
@Test(
|
|
arguments: SystemType.makeAirToAirTestCases(climate: .mildWinterOrLatentLoad)
|
|
)
|
|
func mildWinterSizingLimits(system: SystemType) async throws {
|
|
try await withDependencies {
|
|
$0.manualS = .liveValue
|
|
} operation: {
|
|
@Dependency(\.manualS) var manualS
|
|
|
|
let limits = try await manualS.sizingLimits(.init(systemType: system))
|
|
|
|
#expect(limits.oversizing.coolingLatent == 150)
|
|
#expect(limits.oversizing.coolingTotal == system.compressorType!.mildWinterTotalLimit)
|
|
#expect(limits.undersizing.coolingTotal == 90)
|
|
#expect(limits.undersizing.coolingSensible == 90)
|
|
#expect(limits.undersizing.coolingLatent == 90)
|
|
}
|
|
}
|
|
|
|
@Test(
|
|
arguments: SystemType.makeAirToAirTestCases(climate: .coldWinterOrNoLatentLoad)
|
|
)
|
|
func coldWinterSizingLimits(system: SystemType) async throws {
|
|
try await withDependencies {
|
|
$0.manualS = .liveValue
|
|
} operation: {
|
|
@Dependency(\.manualS) var manualS
|
|
|
|
let limits = try await manualS.sizingLimits(.init(
|
|
systemType: system,
|
|
coolingLoad: .init(total: 17872, sensible: 13894)
|
|
))
|
|
|
|
#expect(limits.oversizing.coolingLatent == 150)
|
|
#expect(limits.oversizing.coolingTotal == 184)
|
|
#expect(limits.undersizing.coolingTotal == 90)
|
|
#expect(limits.undersizing.coolingSensible == 90)
|
|
#expect(limits.undersizing.coolingLatent == 90)
|
|
|
|
await #expect(throws: HouseLoadError()) {
|
|
try await manualS.sizingLimits(.init(systemType: system))
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test(
|
|
arguments: SystemType.HeatingOnlyType.allCases
|
|
)
|
|
func heatingOnlySizingLimits(heatingType: SystemType.HeatingOnlyType) async throws {
|
|
try await withDependencies {
|
|
$0.manualS = .liveValue
|
|
} operation: {
|
|
@Dependency(\.manualS) var manualS
|
|
|
|
let limits = try await manualS.sizingLimits(.init(
|
|
systemType: .heatingOnly(type: heatingType)
|
|
))
|
|
|
|
#expect(limits.oversizing.heating == heatingType.oversizingLimit)
|
|
#expect(limits.undersizing.coolingTotal == 90)
|
|
#expect(limits.undersizing.heating == 90)
|
|
#expect(limits.undersizing.coolingSensible == 90)
|
|
#expect(limits.undersizing.coolingLatent == 90)
|
|
}
|
|
}
|
|
|
|
@Test(
|
|
arguments: [
|
|
(RequiredKW.Request(heatLoss: 49667), 14.55),
|
|
(RequiredKW.Request(capacityAtDesign: 11300, heatLoss: 49667), 11.24)
|
|
]
|
|
)
|
|
func requiredKW(request: RequiredKW.Request, expected: Double) async throws {
|
|
try await withDependencies {
|
|
$0.manualS = .liveValue
|
|
} operation: {
|
|
@Dependency(\.manualS) var manualS
|
|
|
|
let requiredKW = try await manualS.requiredKW(request)
|
|
let rounded = round(requiredKW.requiredKW * 100) / 100
|
|
#expect(rounded == expected)
|
|
}
|
|
}
|
|
|
|
@Test(
|
|
arguments: [
|
|
(elevation: 0, expected: 1.0),
|
|
(elevation: 1000, expected: 0.96),
|
|
(elevation: 2000, expected: 0.92),
|
|
(elevation: 3000, expected: 0.88),
|
|
(elevation: 4000, expected: 0.84),
|
|
(elevation: 5000, expected: 0.8),
|
|
(elevation: 6000, expected: 0.76),
|
|
(elevation: 7000, expected: 0.72),
|
|
(elevation: 8000, expected: 0.68),
|
|
(elevation: 9000, expected: 0.64),
|
|
(elevation: 10000, expected: 0.6),
|
|
(elevation: 11000, expected: 0.56),
|
|
(elevation: 12000, expected: 0.52),
|
|
(elevation: 13000, expected: 0.52)
|
|
]
|
|
)
|
|
func heatingDerating(elevation: Int, expected: Double) async throws {
|
|
try await withDependencies {
|
|
$0.manualS = .liveValue
|
|
} operation: {
|
|
@Dependency(\.manualS) var manualS
|
|
|
|
for heatingType in [SystemType.HeatingOnlyType.boiler, .furnace] {
|
|
let derating = try await manualS.derating(.init(
|
|
elevation: elevation,
|
|
systemType: .heatingOnly(type: heatingType)
|
|
))
|
|
#expect(derating.heating == expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test(
|
|
arguments: [
|
|
(elevation: 0, expected: AdjustmentMultiplier(coolingTotal: 1, coolingSensible: 1, heating: 1)),
|
|
(elevation: 1000, expected: AdjustmentMultiplier(coolingTotal: 0.99, coolingSensible: 0.97, heating: 0.98)),
|
|
(elevation: 2000, expected: AdjustmentMultiplier(coolingTotal: 0.98, coolingSensible: 0.94, heating: 0.97)),
|
|
(elevation: 3000, expected: AdjustmentMultiplier(coolingTotal: 0.98, coolingSensible: 0.91, heating: 0.95)),
|
|
(elevation: 4000, expected: AdjustmentMultiplier(coolingTotal: 0.97, coolingSensible: 0.88, heating: 0.94)),
|
|
(elevation: 5000, expected: AdjustmentMultiplier(coolingTotal: 0.96, coolingSensible: 0.85, heating: 0.92)),
|
|
(elevation: 6000, expected: AdjustmentMultiplier(coolingTotal: 0.95, coolingSensible: 0.82, heating: 0.9)),
|
|
(elevation: 7000, expected: AdjustmentMultiplier(coolingTotal: 0.94, coolingSensible: 0.8, heating: 0.89)),
|
|
(elevation: 8000, expected: AdjustmentMultiplier(coolingTotal: 0.94, coolingSensible: 0.77, heating: 0.87)),
|
|
(elevation: 9000, expected: AdjustmentMultiplier(coolingTotal: 0.93, coolingSensible: 0.74, heating: 0.86)),
|
|
(elevation: 10000, expected: AdjustmentMultiplier(coolingTotal: 0.92, coolingSensible: 0.71, heating: 0.84)),
|
|
(elevation: 11000, expected: AdjustmentMultiplier(coolingTotal: 0.91, coolingSensible: 0.68, heating: 0.82)),
|
|
(elevation: 12000, expected: AdjustmentMultiplier(coolingTotal: 0.9, coolingSensible: 0.65, heating: 0.81)),
|
|
(elevation: 13000, expected: AdjustmentMultiplier(coolingTotal: 0.9, coolingSensible: 0.65, heating: 0.81))
|
|
]
|
|
)
|
|
func airToAirDerating(elevation: Int, expected: AdjustmentMultiplier) async throws {
|
|
try await withDependencies {
|
|
$0.manualS = .liveValue
|
|
} operation: {
|
|
@Dependency(\.manualS) var manualS
|
|
|
|
let derating = try await manualS.derating(.init(
|
|
elevation: elevation,
|
|
systemType: .airToAir(type: .airConditioner, compressor: .singleSpeed, climate: .mildWinterOrLatentLoad)
|
|
))
|
|
#expect(derating == expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension SystemType {
|
|
|
|
static func makeAirToAirTestCases(climate: SystemType.ClimateType) -> [Self] {
|
|
var items: [Self] = []
|
|
for compressor in SystemType.CompressorType.allCases {
|
|
for equipment in SystemType.EquipmentType.allCases {
|
|
items.append(.airToAir(type: equipment, compressor: compressor, climate: climate))
|
|
}
|
|
}
|
|
return items
|
|
}
|
|
|
|
var compressorType: SystemType.CompressorType? {
|
|
switch self {
|
|
case let .airToAir(type: _, compressor: compressor, climate: _): return compressor
|
|
case .heatingOnly: return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
extension SystemType.CompressorType {
|
|
var mildWinterTotalLimit: Int {
|
|
switch self {
|
|
case .singleSpeed: return 115
|
|
case .multiSpeed: return 120
|
|
case .variableSpeed: return 130
|
|
}
|
|
}
|
|
}
|
|
|
|
extension SystemType.HeatingOnlyType {
|
|
var oversizingLimit: Int {
|
|
switch self {
|
|
case .boiler, .furnace: return 140
|
|
case .electric: return 175
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Capacity.Cooling {
|
|
static let mockCoolingLoad = Self(total: 17872, sensible: 13894)
|
|
}
|
|
|
|
extension DesignInfo.Summer {
|
|
static let mock = Self(outdoorTemperature: 90, indoorTemperature: 75, indoorHumidity: 50)
|
|
}
|