feat: Starts manual-d client and adds friction rate calculation and tests.
This commit is contained in:
69
Package.resolved
Normal file
69
Package.resolved
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"originHash" : "426df0aee89a834f20c1c804ecbfbed0bc19ef629c2a1fd2e6260702b97b6f31",
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "combine-schedulers",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/pointfreeco/combine-schedulers",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "fd16d76fd8b9a976d88bfb6cacc05ca8d19c91b6",
|
||||||
|
"version" : "1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "opencombine",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/OpenCombine/OpenCombine.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "8576f0d579b27020beccbccc3ea6844f3ddfc2c2",
|
||||||
|
"version" : "0.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-clocks",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/pointfreeco/swift-clocks",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "cc46202b53476d64e824e0b6612da09d84ffde8e",
|
||||||
|
"version" : "1.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-concurrency-extras",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/pointfreeco/swift-concurrency-extras",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "5a3825302b1a0d744183200915a47b508c828e6f",
|
||||||
|
"version" : "1.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-dependencies",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/pointfreeco/swift-dependencies",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "a10f9feeb214bc72b5337b6ef6d5a029360db4cc",
|
||||||
|
"version" : "1.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-syntax",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/swiftlang/swift-syntax",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "4799286537280063c85a32f09884cfbca301b1a1",
|
||||||
|
"version" : "602.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "xctest-dynamic-overlay",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "31073495cae9caf243c440eac94b3ab067e3d7bc",
|
||||||
|
"version" : "1.8.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 3
|
||||||
|
}
|
||||||
@@ -7,6 +7,10 @@ let package = Package(
|
|||||||
products: [
|
products: [
|
||||||
.library(name: "swift-manual-d", targets: ["swift-manual-d"]),
|
.library(name: "swift-manual-d", targets: ["swift-manual-d"]),
|
||||||
.library(name: "ManualDCore", targets: ["ManualDCore"]),
|
.library(name: "ManualDCore", targets: ["ManualDCore"]),
|
||||||
|
.library(name: "ManualDClient", targets: ["ManualDClient"]),
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0")
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
@@ -15,6 +19,18 @@ let package = Package(
|
|||||||
.target(
|
.target(
|
||||||
name: "ManualDCore"
|
name: "ManualDCore"
|
||||||
),
|
),
|
||||||
|
.target(
|
||||||
|
name: "ManualDClient",
|
||||||
|
dependencies: [
|
||||||
|
"ManualDCore",
|
||||||
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "ManualDClientTests",
|
||||||
|
dependencies: ["ManualDClient"]
|
||||||
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "swift-manual-dTests",
|
name: "swift-manual-dTests",
|
||||||
dependencies: ["swift-manual-d"]
|
dependencies: ["swift-manual-d"]
|
||||||
|
|||||||
6
Sources/ManualDClient/Helpers.swift
Normal file
6
Sources/ManualDClient/Helpers.swift
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import Foundation
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
extension ComponentPressureLosses {
|
||||||
|
var totalLosses: Double { values.reduce(0) { $0 + $1 } }
|
||||||
|
}
|
||||||
18
Sources/ManualDClient/Live.swift
Normal file
18
Sources/ManualDClient/Live.swift
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import Dependencies
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
extension ManualDClient: DependencyKey {
|
||||||
|
public static let liveValue: Self = .init(
|
||||||
|
frictionRate: { request in
|
||||||
|
// Ensure the total effective length is greater than 0.
|
||||||
|
guard request.totalEffectiveLength > 0 else {
|
||||||
|
throw ManualDError(message: "Total Effective Length should be greater than 0.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalComponentLosses = request.componentPressureLosses.totalLosses
|
||||||
|
let availableStaticPressure = request.externalStaticPressure - totalComponentLosses
|
||||||
|
let frictionRate = availableStaticPressure * 100.0 / Double(request.totalEffectiveLength)
|
||||||
|
return .init(availableStaticPressure: availableStaticPressure, frictionRate: frictionRate)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
50
Sources/ManualDClient/ManualDClient.swift
Normal file
50
Sources/ManualDClient/ManualDClient.swift
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import Dependencies
|
||||||
|
import DependenciesMacros
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
public struct ManualDClient: Sendable {
|
||||||
|
public var frictionRate: @Sendable (FrictionRateRequest) async throws -> FrictionRateResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ManualDClient: TestDependencyKey {
|
||||||
|
public static let testValue = Self()
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DependencyValues {
|
||||||
|
public var manualD: ManualDClient {
|
||||||
|
get { self[ManualDClient.self] }
|
||||||
|
set { self[ManualDClient.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Friction Rate
|
||||||
|
extension ManualDClient {
|
||||||
|
public struct FrictionRateRequest: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
public let externalStaticPressure: Double
|
||||||
|
public let componentPressureLosses: ComponentPressureLosses
|
||||||
|
public let totalEffectiveLength: Int
|
||||||
|
|
||||||
|
public init(
|
||||||
|
externalStaticPressure: Double,
|
||||||
|
componentPressureLosses: ComponentPressureLosses,
|
||||||
|
totalEffectiveLength: Int
|
||||||
|
) {
|
||||||
|
self.externalStaticPressure = externalStaticPressure
|
||||||
|
self.componentPressureLosses = componentPressureLosses
|
||||||
|
self.totalEffectiveLength = totalEffectiveLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct FrictionRateResponse: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
public let availableStaticPressure: Double
|
||||||
|
public let frictionRate: Double
|
||||||
|
|
||||||
|
public init(availableStaticPressure: Double, frictionRate: Double) {
|
||||||
|
self.availableStaticPressure = availableStaticPressure
|
||||||
|
self.frictionRate = frictionRate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
Sources/ManualDClient/ManualDError.swift
Normal file
5
Sources/ManualDClient/ManualDError.swift
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct ManualDError: Error {
|
||||||
|
public let message: String
|
||||||
|
}
|
||||||
@@ -2,6 +2,16 @@ import Foundation
|
|||||||
|
|
||||||
public typealias ComponentPressureLosses = [String: Double]
|
public typealias ComponentPressureLosses = [String: Double]
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
extension ComponentPressureLosses {
|
extension ComponentPressureLosses {
|
||||||
public var totalLosses: Double { values.reduce(0) { $0 + $1 } }
|
public static var mock: Self {
|
||||||
|
[
|
||||||
|
"evaporator-coil": 0.2,
|
||||||
|
"filter": 0.1,
|
||||||
|
"supply-outlet": 0.03,
|
||||||
|
"return-grille": 0.03,
|
||||||
|
"balancing-damper": 0.03,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct CoolingLoad: Codable, Equatable {
|
public struct CoolingLoad: Codable, Equatable, Sendable {
|
||||||
public let total: Double
|
public let total: Double
|
||||||
public let sensible: Double
|
public let sensible: Double
|
||||||
public var latent: Double { total - sensible }
|
public var latent: Double { total - sensible }
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Foundation
|
|||||||
|
|
||||||
// TODO: Add other description / label for items that have same group & letter, but
|
// TODO: Add other description / label for items that have same group & letter, but
|
||||||
// different effective length.
|
// different effective length.
|
||||||
public struct EffectiveLengthGroup: Codable, Equatable {
|
public struct EffectiveLengthGroup: Codable, Equatable, Sendable {
|
||||||
public let group: Int
|
public let group: Int
|
||||||
public let letter: String
|
public let letter: String
|
||||||
public let effectiveLength: Int
|
public let effectiveLength: Int
|
||||||
@@ -24,7 +24,7 @@ public struct EffectiveLengthGroup: Codable, Equatable {
|
|||||||
|
|
||||||
extension EffectiveLengthGroup {
|
extension EffectiveLengthGroup {
|
||||||
|
|
||||||
public enum Category: String, Codable, Equatable {
|
public enum Category: String, Codable, Equatable, Sendable {
|
||||||
case any
|
case any
|
||||||
case supply
|
case supply
|
||||||
case `return`
|
case `return`
|
||||||
@@ -32,7 +32,7 @@ extension EffectiveLengthGroup {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public let effectiveLengthsLookup: [String: EffectiveLengthGroup] {
|
public let effectiveLengthsLookup: [String: EffectiveLengthGroup] = {
|
||||||
[
|
[
|
||||||
"1a": .init(group: 1, letter: "a", effectiveLength: 35, category: .supply),
|
"1a": .init(group: 1, letter: "a", effectiveLength: 35, category: .supply),
|
||||||
"1b": .init(group: 1, letter: "b", effectiveLength: 10, category: .supply),
|
"1b": .init(group: 1, letter: "b", effectiveLength: 10, category: .supply),
|
||||||
@@ -625,4 +625,4 @@ public let effectiveLengthsLookup: [String: EffectiveLengthGroup] {
|
|||||||
"12u": .init(group: 12, letter: "u", effectiveLength: 25, category: .any),
|
"12u": .init(group: 12, letter: "u", effectiveLength: 25, category: .any),
|
||||||
"12v": .init(group: 12, letter: "v", effectiveLength: 30, category: .any),
|
"12v": .init(group: 12, letter: "v", effectiveLength: 30, category: .any),
|
||||||
]
|
]
|
||||||
}
|
}()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct Room: Codable, Equatable {
|
public struct Room: Codable, Equatable, Sendable {
|
||||||
public let name: String
|
public let name: String
|
||||||
public let heatingLoad: Double
|
public let heatingLoad: Double
|
||||||
public let coolingLoad: CoolingLoad
|
public let coolingLoad: CoolingLoad
|
||||||
|
|||||||
45
Tests/ManualDClientTests/ManualDClientTests.swift
Normal file
45
Tests/ManualDClientTests/ManualDClientTests.swift
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
import ManualDClient
|
||||||
|
import ManualDCore
|
||||||
|
import Testing
|
||||||
|
|
||||||
|
@Suite("ManualDClient Tests")
|
||||||
|
struct ManualDClientTests {
|
||||||
|
|
||||||
|
var numberFormatter: NumberFormatter {
|
||||||
|
let formatter = NumberFormatter()
|
||||||
|
formatter.minimumFractionDigits = 2
|
||||||
|
formatter.maximumFractionDigits = 2
|
||||||
|
formatter.roundingMode = .halfUp
|
||||||
|
return formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func frictionRate() async throws {
|
||||||
|
let manualD = ManualDClient.liveValue
|
||||||
|
let response = try await manualD.frictionRate(
|
||||||
|
.init(
|
||||||
|
externalStaticPressure: 0.5,
|
||||||
|
componentPressureLosses: .mock,
|
||||||
|
totalEffectiveLength: 185
|
||||||
|
)
|
||||||
|
)
|
||||||
|
#expect(numberFormatter.string(for: response.availableStaticPressure) == "0.11")
|
||||||
|
#expect(numberFormatter.string(for: response.frictionRate) == "0.06")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func frictionRateFails() async throws {
|
||||||
|
await #expect(throws: ManualDError.self) {
|
||||||
|
let manualD = ManualDClient.liveValue
|
||||||
|
_ = try await manualD.frictionRate(
|
||||||
|
.init(
|
||||||
|
externalStaticPressure: 0.5,
|
||||||
|
componentPressureLosses: .mock,
|
||||||
|
totalEffectiveLength: 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user