feat: Adds equivalent rectangular duct conversion.

This commit is contained in:
2025-12-19 12:49:50 -05:00
parent 0aabd612b2
commit b3502fc79b
5 changed files with 115 additions and 4 deletions

View File

@@ -29,7 +29,10 @@ let package = Package(
), ),
.testTarget( .testTarget(
name: "ManualDClientTests", name: "ManualDClientTests",
dependencies: ["ManualDClient"] dependencies: [
"ManualDClient",
.product(name: "DependenciesTestSupport", package: "swift-dependencies"),
]
), ),
.testTarget( .testTarget(
name: "swift-manual-dTests", name: "swift-manual-dTests",

View File

@@ -4,3 +4,9 @@ import ManualDCore
extension ComponentPressureLosses { extension ComponentPressureLosses {
var totalLosses: Double { values.reduce(0) { $0 + $1 } } var totalLosses: Double { values.reduce(0) { $0 + $1 } }
} }
extension Array where Element == EffectiveLengthGroup {
var totalEffectiveLength: Int {
reduce(0) { $0 + $1.effectiveLength }
}
}

View File

@@ -1,4 +1,5 @@
import Dependencies import Dependencies
import Foundation
import ManualDCore import ManualDCore
extension ManualDClient: DependencyKey { extension ManualDClient: DependencyKey {
@@ -13,6 +14,31 @@ extension ManualDClient: DependencyKey {
let availableStaticPressure = request.externalStaticPressure - totalComponentLosses let availableStaticPressure = request.externalStaticPressure - totalComponentLosses
let frictionRate = availableStaticPressure * 100.0 / Double(request.totalEffectiveLength) let frictionRate = availableStaticPressure * 100.0 / Double(request.totalEffectiveLength)
return .init(availableStaticPressure: availableStaticPressure, frictionRate: frictionRate) return .init(availableStaticPressure: availableStaticPressure, frictionRate: frictionRate)
},
totalEffectiveLength: { request in
let trunkLengths = request.trunkLengths.reduce(0) { $0 + $1 }
let runoutLengths = request.runoutLengths.reduce(0) { $0 + $1 }
let groupLengths = request.effectiveLengthGroups.totalEffectiveLength
return trunkLengths + runoutLengths + groupLengths
},
equivalentRectangularDuct: { request in
let width = (Double.pi * (pow(Double(request.roundSize) / 2.0, 2.0))) / Double(request.height)
guard let widthStr = numberFormatter.string(for: width),
let widthInt = Int(widthStr)
else {
throw ManualDError(
message: "Failed to convert to to rectangular duct size, width: \(width)"
)
}
return .init(height: request.height, width: widthInt)
} }
) )
} }
private let numberFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 0
formatter.minimumFractionDigits = 0
formatter.roundingMode = .ceiling
return formatter
}()

View File

@@ -5,6 +5,9 @@ import ManualDCore
@DependencyClient @DependencyClient
public struct ManualDClient: Sendable { public struct ManualDClient: Sendable {
public var frictionRate: @Sendable (FrictionRateRequest) async throws -> FrictionRateResponse public var frictionRate: @Sendable (FrictionRateRequest) async throws -> FrictionRateResponse
public var totalEffectiveLength: @Sendable (TotalEffectiveLengthRequest) async throws -> Int
public var equivalentRectangularDuct:
@Sendable (EquivalentRectangularDuctRequest) async throws -> EquivalentRectangularDuctResponse
} }
extension ManualDClient: TestDependencyKey { extension ManualDClient: TestDependencyKey {
@@ -48,3 +51,46 @@ extension ManualDClient {
} }
} }
} }
// MARK: Total Effective Length
extension ManualDClient {
public struct TotalEffectiveLengthRequest: Codable, Equatable, Sendable {
public let trunkLengths: [Int]
public let runoutLengths: [Int]
public let effectiveLengthGroups: [EffectiveLengthGroup]
public init(
trunkLengths: [Int],
runoutLengths: [Int],
effectiveLengthGroups: [EffectiveLengthGroup]
) {
self.trunkLengths = trunkLengths
self.runoutLengths = runoutLengths
self.effectiveLengthGroups = effectiveLengthGroups
}
}
}
// MARK: Equivalent Rectangular Duct
extension ManualDClient {
public struct EquivalentRectangularDuctRequest: Codable, Equatable, Sendable {
public let roundSize: Int
public let height: Int
public init(round roundSize: Int, height: Int) {
self.roundSize = roundSize
self.height = height
}
}
public struct EquivalentRectangularDuctResponse: Codable, Equatable, Sendable {
public let height: Int
public let width: Int
public init(height: Int, width: Int) {
self.height = height
self.width = width
}
}
}

View File

@@ -1,12 +1,19 @@
import Dependencies import Dependencies
import DependenciesTestSupport
import Foundation import Foundation
import ManualDClient import ManualDClient
import ManualDCore import ManualDCore
import Testing import Testing
@Suite("ManualDClient Tests") @Suite(
.dependencies {
$0.manualD = ManualDClient.liveValue
}
)
struct ManualDClientTests { struct ManualDClientTests {
@Dependency(\.manualD) var manualD
var numberFormatter: NumberFormatter { var numberFormatter: NumberFormatter {
let formatter = NumberFormatter() let formatter = NumberFormatter()
formatter.minimumFractionDigits = 2 formatter.minimumFractionDigits = 2
@@ -17,7 +24,6 @@ struct ManualDClientTests {
@Test @Test
func frictionRate() async throws { func frictionRate() async throws {
let manualD = ManualDClient.liveValue
let response = try await manualD.frictionRate( let response = try await manualD.frictionRate(
.init( .init(
externalStaticPressure: 0.5, externalStaticPressure: 0.5,
@@ -32,7 +38,6 @@ struct ManualDClientTests {
@Test @Test
func frictionRateFails() async throws { func frictionRateFails() async throws {
await #expect(throws: ManualDError.self) { await #expect(throws: ManualDError.self) {
let manualD = ManualDClient.liveValue
_ = try await manualD.frictionRate( _ = try await manualD.frictionRate(
.init( .init(
externalStaticPressure: 0.5, externalStaticPressure: 0.5,
@@ -42,4 +47,29 @@ struct ManualDClientTests {
) )
} }
} }
@Test
func totalEffectiveLength() async throws {
let response = try await manualD.totalEffectiveLength(
.init(
trunkLengths: [25],
runoutLengths: [10],
effectiveLengthGroups: [
// NOTE: These are made up and may not correspond to actual manual-d group tel's.
EffectiveLengthGroup(group: 1, letter: "a", effectiveLength: 20, category: .supply),
EffectiveLengthGroup(group: 2, letter: "a", effectiveLength: 30, category: .supply),
EffectiveLengthGroup(group: 3, letter: "a", effectiveLength: 10, category: .supply),
EffectiveLengthGroup(group: 12, letter: "a", effectiveLength: 10, category: .supply),
]
)
)
#expect(response == 105)
}
@Test
func equivalentRectangularDuct() async throws {
let response = try await manualD.equivalentRectangularDuct(.init(round: 7, height: 8))
#expect(response.height == 8)
#expect(response.width == 5)
}
} }