feat: Adds cooling interpolation tests.

This commit is contained in:
2025-03-13 09:10:52 -04:00
parent ec58ca6364
commit c23e4c5f61
7 changed files with 287 additions and 112 deletions

View File

@@ -1,6 +1,16 @@
import Models import Models
import Validations import Validations
extension Capacity.Cooling: AsyncValidatable {
public var body: some AsyncValidation<Self> {
AsyncValidator.accumulating {
AsyncValidator.greaterThan(\.total, 0)
AsyncValidator.greaterThan(\.sensible, 0)
AsyncValidator.greaterThanOrEquals(\.total, \.sensible)
}
}
}
extension Capacity.ManufacturersCooling: AsyncValidatable { extension Capacity.ManufacturersCooling: AsyncValidatable {
public var body: some AsyncValidation<Self> { public var body: some AsyncValidation<Self> {
AsyncValidator.accumulating { AsyncValidator.accumulating {

View File

@@ -1,32 +1,26 @@
import Models import Models
import Validations import Validations
extension Interpolate.Request { extension CoolingInterpolation.Request {
func respond() async throws -> Interpolate.Response {
func respond() async throws -> CoolingInterpolation.Response {
try await validate() try await validate()
let interpolatedCapacity = await interpolation.interpolatedCapacity( let interpolatedCapacity = await interpolatedCapacity()
outdoorDesignTemperature: designInfo.summer.outdoorTemperature let excessLatent = excessLatent(interpolatedCapacity.latent)
let elevationDeratings = try await elevationDeratings()
let sizingLimits = try await sizingLimits()
let finalCapacity = calculateFinalCapacity(
interpolated: interpolatedCapacity,
excessLatent: excessLatent,
adjustmentMultipliers: interpolation.adjustmentMultipliers,
elevationDeratings: elevationDeratings
) )
let excessLatent = self.excessLatent(interpolatedLatent: interpolatedCapacity.latent)
let elevationDeratings = try await Derating.Request(
elevation: designInfo.elevation,
systemType: systemType
).respond()
let sizingLimits = try await SizingLimits.Request(
systemType: systemType,
houseLoad: houseLoad
).respond()
let finalCapacity = interpolatedCapacity
.applying(excessLatent: excessLatent)
.applying(adjustmentMultipliers: interpolation.adjustmentMultipliers)
.applying(adjustmentMultipliers: elevationDeratings)
let capacityAsPercentOfLoad = Capacity.Cooling( let capacityAsPercentOfLoad = Capacity.Cooling(
total: normalizePercentage(finalCapacity.total, houseLoad.coolingTotal), total: normalizePercentage(finalCapacity.total, coolingLoad.total),
sensible: normalizePercentage(finalCapacity.sensible, houseLoad.coolingSensible), sensible: normalizePercentage(finalCapacity.sensible, coolingLoad.sensible),
latent: normalizePercentage(finalCapacity.latent, houseLoad.coolingLatent) latent: normalizePercentage(finalCapacity.latent, coolingLoad.latent)
) )
return .init( return .init(
@@ -39,6 +33,57 @@ extension Interpolate.Request {
sizingLimits: sizingLimits sizingLimits: sizingLimits
) )
} }
private func interpolatedCapacity() async -> Capacity.Cooling {
await interpolation.interpolatedCapacity(
outdoorDesignTemperature: summerDesignInfo.outdoorTemperature
)
}
private func elevationDeratings() async throws -> AdjustmentMultiplier? {
guard let elevation else { return nil }
return try await Derating.Request(elevation: elevation, systemType: systemType)
.respond()
}
private func sizingLimits() async throws -> SizingLimits.Response {
try await SizingLimits.Request(systemType: systemType, coolingLoad: coolingLoad)
.respond()
}
private func excessLatent(_ interpolatedLatent: Int) -> Int {
(interpolatedLatent - coolingLoad.latent) / 2
}
}
private func calculateFinalCapacity(
interpolated: Capacity.Cooling,
excessLatent: Int,
adjustmentMultipliers: AdjustmentMultiplier?,
elevationDeratings: AdjustmentMultiplier?
) -> Capacity.Cooling {
var total = interpolated.total
var sensible = interpolated.sensible + excessLatent
if let adjustmentMultipliers {
adjustmentMultipliers.apply(total: &total, sensible: &sensible)
}
if let elevationDeratings {
elevationDeratings.apply(total: &total, sensible: &sensible)
}
return .init(total: total, sensible: sensible)
}
private extension AdjustmentMultiplier {
func apply(total: inout Int, sensible: inout Int) {
if let coolingTotal {
total = Int(Double(total) * coolingTotal)
}
if let coolingSensible {
sensible = Int(Double(sensible) * coolingSensible)
}
}
} }
private extension SizingLimits.Response { private extension SizingLimits.Response {
@@ -92,29 +137,7 @@ private func normalizePercentage(
return Int((value * 1000).rounded() / 10.0) return Int((value * 1000).rounded() / 10.0)
} }
private extension Capacity.Cooling { private extension CoolingInterpolation.InterpolationRequest {
func applying(excessLatent: Int) -> Self {
.init(total: total, sensible: sensible + excessLatent)
}
func applying(adjustmentMultipliers: AdjustmentMultiplier?) -> Self {
guard let adjustmentMultipliers else { return self }
return .init(
total: Int(Double(total) * (adjustmentMultipliers.coolingTotal ?? 1)),
sensible: Int(Double(sensible) * (adjustmentMultipliers.coolingSensible ?? 1))
)
}
}
private extension Interpolate.Request {
func excessLatent(interpolatedLatent: Int) -> Int {
(interpolatedLatent - houseLoad.coolingLatent) / 2
}
}
private extension Interpolate.InterpolationRequest {
func interpolatedCapacity(outdoorDesignTemperature: Int) async -> Capacity.Cooling { func interpolatedCapacity(outdoorDesignTemperature: Int) async -> Capacity.Cooling {
switch self { switch self {
@@ -147,20 +170,16 @@ private func interpolateIndoorCapacity(
above: Capacity.ManufacturersContainer, above: Capacity.ManufacturersContainer,
below: Capacity.ManufacturersContainer below: Capacity.ManufacturersContainer
) async -> Capacity.Cooling { ) async -> Capacity.Cooling {
let total = Double(below.totalCapacity) let total = below.totalCapacity
+ ( + ((above.totalCapacity - below.totalCapacity) / (above.wetBulb - below.wetBulb))
Double(above.totalCapacity - below.totalCapacity) * (63 - below.wetBulb)
/ Double(above.wetBulb - below.wetBulb)
)
* Double(63 - below.wetBulb)
let sensible = Double(below.sensibleCapacity) let sensible = Double(below.sensibleCapacity)
+ Double(above.sensibleCapacity - below.sensibleCapacity) + (Double(above.sensibleCapacity) - Double(below.sensibleCapacity))
/ Double(below.totalCapacity - above.totalCapacity) / (Double(below.totalCapacity) - Double(above.totalCapacity))
* Double(below.totalCapacity) * (Double(below.totalCapacity) - Double(total))
- total
return .init(total: Int(total), sensible: Int(sensible)) return .init(total: total, sensible: Int(sensible))
} }
private func interpolateOutdoorCapacity( private func interpolateOutdoorCapacity(
@@ -175,7 +194,7 @@ private func interpolateOutdoorCapacity(
* ((belowCapacity - aboveCapacity) / (aboveOutdoorTemperature - belowOutdoorTemperature)) * ((belowCapacity - aboveCapacity) / (aboveOutdoorTemperature - belowOutdoorTemperature))
} }
private extension Interpolate.OneWayIndoor { private extension CoolingInterpolation.OneWayIndoor {
func interpolatedCapacity() async -> Capacity.Cooling { func interpolatedCapacity() async -> Capacity.Cooling {
return await interpolateIndoorCapacity( return await interpolateIndoorCapacity(
@@ -185,7 +204,7 @@ private extension Interpolate.OneWayIndoor {
} }
} }
private extension Interpolate.OneWayOutdoor { private extension CoolingInterpolation.OneWayOutdoor {
func interpolatedCapacity(outdoorDesignTemperature: Int) async -> Capacity.Cooling { func interpolatedCapacity(outdoorDesignTemperature: Int) async -> Capacity.Cooling {
let total = await interpolate( let total = await interpolate(
outdoorDesignTemperature: outdoorDesignTemperature, outdoorDesignTemperature: outdoorDesignTemperature,
@@ -203,8 +222,8 @@ private extension Interpolate.OneWayOutdoor {
private func interpolate( private func interpolate(
outdoorDesignTemperature: Int, outdoorDesignTemperature: Int,
aboveCapacity: KeyPath<Interpolate.OneWayOutdoor.Capacities.Capacity, Int>, aboveCapacity: KeyPath<CoolingInterpolation.OneWayOutdoor.Capacities.Capacity, Int>,
belowCapacity: KeyPath<Interpolate.OneWayOutdoor.Capacities.Capacity, Int>, belowCapacity: KeyPath<CoolingInterpolation.OneWayOutdoor.Capacities.Capacity, Int>,
) async -> Int { ) async -> Int {
return await interpolateOutdoorCapacity( return await interpolateOutdoorCapacity(
outdoorDesignTemperature: outdoorDesignTemperature, outdoorDesignTemperature: outdoorDesignTemperature,
@@ -216,7 +235,7 @@ private extension Interpolate.OneWayOutdoor {
} }
} }
private extension Interpolate.TwoWay { private extension CoolingInterpolation.TwoWay {
func interpolatedCapacity(outdoorDesignTemperature: Int) async -> Capacity.Cooling { func interpolatedCapacity(outdoorDesignTemperature: Int) async -> Capacity.Cooling {
let aboveIndoorInterpolation = await self.aboveIndoorInterpolation() let aboveIndoorInterpolation = await self.aboveIndoorInterpolation()
@@ -233,7 +252,7 @@ private extension Interpolate.TwoWay {
aboveIndoor: Capacity.Cooling, aboveIndoor: Capacity.Cooling,
belowIndoor: Capacity.Cooling belowIndoor: Capacity.Cooling
) async -> Capacity.Cooling { ) async -> Capacity.Cooling {
let request = Interpolate.OneWayOutdoor( let request = CoolingInterpolation.OneWayOutdoor(
airflow: 0, airflow: 0,
wetBulb: 63, wetBulb: 63,
capacities: .init( capacities: .init(
@@ -271,19 +290,19 @@ private extension Interpolate.TwoWay {
// MARK: - Validations // MARK: - Validations
// Basic validations of the request. // Basic validations of the request.
extension Interpolate.Request: AsyncValidatable { extension CoolingInterpolation.Request: AsyncValidatable {
public var body: some AsyncValidation<Self> { public var body: some AsyncValidation<Self> {
AsyncValidator.accumulating { AsyncValidator.accumulating {
AsyncValidator.validate(\.designInfo) AsyncValidator.validate(\.summerDesignInfo)
AsyncValidator.validate(\.houseLoad) AsyncValidator.validate(\.coolingLoad)
AsyncValidator.validate(\.interpolation) AsyncValidator.validate(\.interpolation)
} }
} }
} }
extension Interpolate.InterpolationRequest: AsyncValidatable { extension CoolingInterpolation.InterpolationRequest: AsyncValidatable {
public typealias Value = Interpolate.InterpolationRequest public typealias Value = CoolingInterpolation.InterpolationRequest
public func validate(_ value: Self) async throws { public func validate(_ value: Self) async throws {
switch value { switch value {
@@ -299,7 +318,7 @@ extension Interpolate.InterpolationRequest: AsyncValidatable {
} }
} }
extension Interpolate.OneWayIndoor: AsyncValidatable { extension CoolingInterpolation.OneWayIndoor: AsyncValidatable {
public var body: some AsyncValidation<Self> { public var body: some AsyncValidation<Self> {
AsyncValidator.accumulating { AsyncValidator.accumulating {
AsyncValidator.greaterThan(\.airflow, 0) AsyncValidator.greaterThan(\.airflow, 0)
@@ -309,7 +328,7 @@ extension Interpolate.OneWayIndoor: AsyncValidatable {
} }
} }
extension Interpolate.OneWayIndoor.Capacities: AsyncValidatable { extension CoolingInterpolation.OneWayIndoor.Capacities: AsyncValidatable {
public var body: some AsyncValidation<Self> { public var body: some AsyncValidation<Self> {
AsyncValidator.accumulating { AsyncValidator.accumulating {
AsyncValidator.validate(\.aboveDewpoint) AsyncValidator.validate(\.aboveDewpoint)
@@ -318,7 +337,7 @@ extension Interpolate.OneWayIndoor.Capacities: AsyncValidatable {
} }
} }
extension Interpolate.OneWayOutdoor: AsyncValidatable { extension CoolingInterpolation.OneWayOutdoor: AsyncValidatable {
public var body: some AsyncValidation<Self> { public var body: some AsyncValidation<Self> {
AsyncValidator.accumulating { AsyncValidator.accumulating {
AsyncValidator.greaterThan(\.airflow, 0) AsyncValidator.greaterThan(\.airflow, 0)
@@ -328,7 +347,7 @@ extension Interpolate.OneWayOutdoor: AsyncValidatable {
} }
} }
extension Interpolate.OneWayOutdoor.Capacities: AsyncValidatable { extension CoolingInterpolation.OneWayOutdoor.Capacities: AsyncValidatable {
public var body: some AsyncValidation<Self> { public var body: some AsyncValidation<Self> {
AsyncValidator.accumulating { AsyncValidator.accumulating {
@@ -338,7 +357,7 @@ extension Interpolate.OneWayOutdoor.Capacities: AsyncValidatable {
} }
} }
extension Interpolate.OneWayOutdoor.Capacities.Capacity: AsyncValidatable { extension CoolingInterpolation.OneWayOutdoor.Capacities.Capacity: 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)
@@ -349,7 +368,7 @@ extension Interpolate.OneWayOutdoor.Capacities.Capacity: AsyncValidatable {
} }
} }
extension Interpolate.TwoWay: AsyncValidatable { extension CoolingInterpolation.TwoWay: AsyncValidatable {
public var body: some AsyncValidation<Self> { public var body: some AsyncValidation<Self> {
AsyncValidator.accumulating { AsyncValidator.accumulating {
AsyncValidator.greaterThan(\.airflow, 0) AsyncValidator.greaterThan(\.airflow, 0)
@@ -358,7 +377,7 @@ extension Interpolate.TwoWay: AsyncValidatable {
} }
} }
extension Interpolate.TwoWay.Capacities: AsyncValidatable { extension CoolingInterpolation.TwoWay.Capacities: AsyncValidatable {
public var body: some AsyncValidation<Self> { public var body: some AsyncValidation<Self> {
AsyncValidator.accumulating { AsyncValidator.accumulating {
AsyncValidator.validate(\.above) AsyncValidator.validate(\.above)
@@ -367,7 +386,7 @@ extension Interpolate.TwoWay.Capacities: AsyncValidatable {
} }
} }
extension Interpolate.TwoWay.Capacities.CapacityContainer: AsyncValidatable { extension CoolingInterpolation.TwoWay.Capacities.CapacityContainer: AsyncValidatable {
public var body: some AsyncValidation<Self> { public var body: some AsyncValidation<Self> {
AsyncValidator.accumulating { AsyncValidator.accumulating {
AsyncValidator.validate(\.aboveDewPoint) AsyncValidator.validate(\.aboveDewPoint)

View File

@@ -4,7 +4,7 @@ import Models
extension SizingLimits.Request { extension SizingLimits.Request {
func respond() async throws -> SizingLimits.Response { func respond() async throws -> SizingLimits.Response {
return try .init( return try .init(
oversizing: .oversizingLimit(systemType: systemType, houseLoad: houseLoad), oversizing: .oversizingLimit(systemType: systemType, houseLoad: coolingLoad),
undersizing: .undersizingLimits undersizing: .undersizingLimits
) )
} }
@@ -20,7 +20,7 @@ private extension SizingLimits.Limits {
static func oversizingLimit( static func oversizingLimit(
systemType: SystemType, systemType: SystemType,
houseLoad: HouseLoad? houseLoad: Capacity.Cooling?
) throws -> Self { ) throws -> Self {
switch systemType { switch systemType {
case let .heatingOnly(type: type): case let .heatingOnly(type: type):
@@ -51,7 +51,7 @@ private extension SystemType.HeatingOnlyType {
} }
private func coolingTotalOversizingLimit( private func coolingTotalOversizingLimit(
houseLoad: HouseLoad?, houseLoad: Capacity.Cooling?,
compressorType: SystemType.CompressorType, compressorType: SystemType.CompressorType,
climateType: SystemType.ClimateType climateType: SystemType.ClimateType
) throws -> Int { ) throws -> Int {
@@ -67,7 +67,7 @@ private func coolingTotalOversizingLimit(
guard let houseLoad else { guard let houseLoad else {
throw HouseLoadError() throw HouseLoadError()
} }
let decimal = Double(houseLoad.coolingTotal + 15000) / Double(houseLoad.coolingTotal) let decimal = Double(houseLoad.total + 15000) / Double(houseLoad.total)
return Int(round(decimal * 100)) return Int(round(decimal * 100))
} }
} }

View File

@@ -13,7 +13,7 @@ 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 interpolate: @Sendable (Interpolate.Request) async throws -> Interpolate.Response public var coolingInterpolation: @Sendable (CoolingInterpolation.Request) async throws -> CoolingInterpolation.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
} }
@@ -22,7 +22,7 @@ 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() },
interpolate: { try await $0.respond() }, coolingInterpolation: { 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

@@ -1,20 +1,23 @@
public enum Interpolate { public enum CoolingInterpolation {
public struct Request: Codable, Equatable, Sendable { public struct Request: Codable, Equatable, Sendable {
public let designInfo: DesignInfo public let elevation: Int?
public let houseLoad: HouseLoad public let summerDesignInfo: DesignInfo.Summer
public let coolingLoad: Models.Capacity.Cooling
public let systemType: SystemType public let systemType: SystemType
public let interpolation: InterpolationRequest public let interpolation: InterpolationRequest
public init( public init(
designInfo: DesignInfo, elevation: Int? = nil,
houseLoad: HouseLoad, summerDesignInfo: DesignInfo.Summer,
coolingLoad: Capacity.Cooling,
systemType: SystemType, systemType: SystemType,
interpolation: InterpolationRequest interpolation: InterpolationRequest
) { ) {
self.designInfo = designInfo self.elevation = elevation
self.houseLoad = houseLoad self.summerDesignInfo = summerDesignInfo
self.coolingLoad = coolingLoad
self.systemType = systemType self.systemType = systemType
self.interpolation = interpolation self.interpolation = interpolation
} }
@@ -24,18 +27,18 @@ public enum Interpolate {
public let failed: Bool public let failed: Bool
public let failures: [String]? public let failures: [String]?
public let interpolatedCapacity: Capacity.Cooling public let interpolatedCapacity: Models.Capacity.Cooling
public let excessLatent: Int public let excessLatent: Int
public let finalCapacityAtDesign: Capacity.Cooling public let finalCapacityAtDesign: Models.Capacity.Cooling
public let altitudeDerating: AdjustmentMultiplier? public let altitudeDerating: AdjustmentMultiplier?
public let capacityAsPercentOfLoad: Capacity.Cooling public let capacityAsPercentOfLoad: Models.Capacity.Cooling
public let sizingLimits: SizingLimits.Response public let sizingLimits: SizingLimits.Response
public init( public init(
failures: [String]? = nil, failures: [String]? = nil,
interpolatedCapacity: Capacity.Cooling, interpolatedCapacity: Models.Capacity.Cooling,
excessLatent: Int, excessLatent: Int,
finalCapacityAtDesign: Capacity.Cooling, finalCapacityAtDesign: Models.Capacity.Cooling,
altitudeDerating: AdjustmentMultiplier? = nil, altitudeDerating: AdjustmentMultiplier? = nil,
capacityAsPercentOfLoad: Capacity.Cooling, capacityAsPercentOfLoad: Capacity.Cooling,
sizingLimits: SizingLimits.Response sizingLimits: SizingLimits.Response
@@ -52,7 +55,7 @@ public enum Interpolate {
} }
public enum InterpolationRequest: Codable, Equatable, Sendable { public enum InterpolationRequest: Codable, Equatable, Sendable {
case noInterpolation(Capacity.ManufacturersCooling, AdjustmentMultiplier? = nil) case noInterpolation(Models.Capacity.ManufacturersCooling, AdjustmentMultiplier? = nil)
case oneWayIndoor(OneWayIndoor) case oneWayIndoor(OneWayIndoor)
case oneWayOutdoor(OneWayOutdoor) case oneWayOutdoor(OneWayOutdoor)
case twoWay(TwoWay) case twoWay(TwoWay)
@@ -68,7 +71,7 @@ public enum Interpolate {
public init( public init(
airflow: Int, airflow: Int,
outdoorTemperature: Int, outdoorTemperature: Int,
capacities: Interpolate.OneWayIndoor.Capacities, capacities: CoolingInterpolation.OneWayIndoor.Capacities,
adjustmentMultipliers: AdjustmentMultiplier? = nil adjustmentMultipliers: AdjustmentMultiplier? = nil
) { ) {
self.airflow = airflow self.airflow = airflow
@@ -79,10 +82,13 @@ public enum Interpolate {
public struct Capacities: Codable, Equatable, Sendable { public struct Capacities: Codable, Equatable, Sendable {
public let aboveDewpoint: Capacity.ManufacturersContainer public let aboveDewpoint: Models.Capacity.ManufacturersContainer
public let belowDewpoint: Capacity.ManufacturersContainer public let belowDewpoint: Models.Capacity.ManufacturersContainer
public init(aboveDewpoint: Capacity.ManufacturersContainer, belowDewpoint: Capacity.ManufacturersContainer) { public init(
aboveDewpoint: Models.Capacity.ManufacturersContainer,
belowDewpoint: Models.Capacity.ManufacturersContainer
) {
self.aboveDewpoint = aboveDewpoint self.aboveDewpoint = aboveDewpoint
self.belowDewpoint = belowDewpoint self.belowDewpoint = belowDewpoint
} }
@@ -99,7 +105,7 @@ public enum Interpolate {
public init( public init(
airflow: Int, airflow: Int,
wetBulb: Int, wetBulb: Int,
capacities: Interpolate.OneWayOutdoor.Capacities, capacities: CoolingInterpolation.OneWayOutdoor.Capacities,
adjustmentMultipliers: AdjustmentMultiplier? = nil adjustmentMultipliers: AdjustmentMultiplier? = nil
) { ) {
self.airflow = airflow self.airflow = airflow
@@ -114,8 +120,8 @@ public enum Interpolate {
public let belowOutdoor: Capacity public let belowOutdoor: Capacity
public init( public init(
aboveOutdoor: Interpolate.OneWayOutdoor.Capacities.Capacity, aboveOutdoor: CoolingInterpolation.OneWayOutdoor.Capacities.Capacity,
belowOutdoor: Interpolate.OneWayOutdoor.Capacities.Capacity belowOutdoor: CoolingInterpolation.OneWayOutdoor.Capacities.Capacity
) { ) {
self.aboveOutdoor = aboveOutdoor self.aboveOutdoor = aboveOutdoor
self.belowOutdoor = belowOutdoor self.belowOutdoor = belowOutdoor
@@ -144,7 +150,7 @@ public enum Interpolate {
public init( public init(
airflow: Int, airflow: Int,
capacities: Interpolate.TwoWay.Capacities, capacities: CoolingInterpolation.TwoWay.Capacities,
adjustmentMultipliers: AdjustmentMultiplier? = nil adjustmentMultipliers: AdjustmentMultiplier? = nil
) { ) {
self.airflow = airflow self.airflow = airflow
@@ -158,8 +164,8 @@ public enum Interpolate {
public let below: CapacityContainer public let below: CapacityContainer
public init( public init(
above: Interpolate.TwoWay.Capacities.CapacityContainer, above: CoolingInterpolation.TwoWay.Capacities.CapacityContainer,
below: Interpolate.TwoWay.Capacities.CapacityContainer below: CoolingInterpolation.TwoWay.Capacities.CapacityContainer
) { ) {
self.above = above self.above = above
self.below = below self.below = below
@@ -168,13 +174,13 @@ public enum Interpolate {
public struct CapacityContainer: Codable, Equatable, Sendable { public struct CapacityContainer: Codable, Equatable, Sendable {
public let outdoorTemperature: Int public let outdoorTemperature: Int
public let aboveDewPoint: Capacity.ManufacturersContainer public let aboveDewPoint: Models.Capacity.ManufacturersContainer
public let belowDewPoint: Capacity.ManufacturersContainer public let belowDewPoint: Models.Capacity.ManufacturersContainer
public init( public init(
outdoorTemperature: Int, outdoorTemperature: Int,
aboveDewPoint: Capacity.ManufacturersContainer, aboveDewPoint: Models.Capacity.ManufacturersContainer,
belowDewPoint: Capacity.ManufacturersContainer belowDewPoint: Models.Capacity.ManufacturersContainer
) { ) {
self.outdoorTemperature = outdoorTemperature self.outdoorTemperature = outdoorTemperature
self.aboveDewPoint = aboveDewPoint self.aboveDewPoint = aboveDewPoint

View File

@@ -3,11 +3,11 @@ public enum SizingLimits {
public struct Request: Codable, Equatable, Sendable { public struct Request: Codable, Equatable, Sendable {
public let systemType: SystemType public let systemType: SystemType
public let houseLoad: HouseLoad? public let coolingLoad: Capacity.Cooling?
public init(systemType: SystemType, houseLoad: HouseLoad? = nil) { public init(systemType: SystemType, coolingLoad: Capacity.Cooling? = nil) {
self.systemType = systemType self.systemType = systemType
self.houseLoad = houseLoad self.coolingLoad = coolingLoad
} }
} }

View File

@@ -6,6 +6,138 @@ import Testing
@Suite("ManualSTests") @Suite("ManualSTests")
struct 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 @Test
func balancePoint() async throws { func balancePoint() async throws {
try await withDependencies { try await withDependencies {
@@ -52,7 +184,7 @@ struct ManualSTests {
let limits = try await manualS.sizingLimits(.init( let limits = try await manualS.sizingLimits(.init(
systemType: system, systemType: system,
houseLoad: .init(coolingTotal: 17872, coolingSensible: 13894, heating: 49667) coolingLoad: .init(total: 17872, sensible: 13894)
)) ))
#expect(limits.oversizing.coolingLatent == 150) #expect(limits.oversizing.coolingLatent == 150)
@@ -211,3 +343,11 @@ extension SystemType.HeatingOnlyType {
} }
} }
} }
extension Capacity.Cooling {
static let mockCoolingLoad = Self(total: 17872, sensible: 13894)
}
extension DesignInfo.Summer {
static let mock = Self(outdoorTemperature: 90, indoorTemperature: 75, indoorHumidity: 50)
}