This repository has been archived on 2026-02-12. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
swift-duct-calc/Sources/ManualDCore/EquivalentLength.swift
Michael Housh a51e1b34d0
All checks were successful
CI / Linux Tests (push) Successful in 5m24s
feat: Adds project database tests.
2026-01-30 14:02:58 -05:00

240 lines
7.4 KiB
Swift

import Dependencies
import Foundation
/// Represents the equivalent length of a single duct path.
///
/// These consist of both straight lengths of duct / trunks, as well as the
/// equivalent length of duct fittings. They are used to determine the worst
/// case total equivalent length of duct that the system fan has to move air
/// through.
///
/// There can be many equivalent lengths saved for a project, however the only
/// ones that matter in most calculations are the longest supply path and the
/// the longest return path.
///
/// It is required that project has at least one equivalent length saved for
/// the supply and one saved for return, otherwise duct sizes can not be calculated.
public struct EquivalentLength: Codable, Equatable, Identifiable, Sendable {
/// The id of the equivalent length.
public let id: UUID
/// The project that this equivalent length is associated with.
public let projectID: Project.ID
/// A unique name / label for this equivalent length.
public let name: String
/// The type (supply or return) of the equivalent length.
public let type: EffectiveLengthType
/// The straight lengths of duct for this equivalent length.
public let straightLengths: [Int]
/// The fitting groups associated with this equivalent length.
public let groups: [FittingGroup]
/// When this equivalent length was created in the database.
public let createdAt: Date
/// When this equivalent length was updated in the database.
public let updatedAt: Date
public init(
id: UUID,
projectID: Project.ID,
name: String,
type: EquivalentLength.EffectiveLengthType,
straightLengths: [Int],
groups: [EquivalentLength.FittingGroup],
createdAt: Date,
updatedAt: Date
) {
self.id = id
self.projectID = projectID
self.name = name
self.type = type
self.straightLengths = straightLengths
self.groups = groups
self.createdAt = createdAt
self.updatedAt = updatedAt
}
}
extension EquivalentLength {
/// Represents the data needed to create a new ``EquivalentLength`` in the database.
public struct Create: Codable, Equatable, Sendable {
/// The project that this equivalent length is associated with.
public let projectID: Project.ID
/// A unique name / label for this equivalent length.
public let name: String
/// The type (supply or return) of the equivalent length.
public let type: EffectiveLengthType
/// The straight lengths of duct for this equivalent length.
public let straightLengths: [Int]
/// The fitting groups associated with this equivalent length.
public let groups: [FittingGroup]
public init(
projectID: Project.ID,
name: String,
type: EquivalentLength.EffectiveLengthType,
straightLengths: [Int],
groups: [EquivalentLength.FittingGroup]
) {
self.projectID = projectID
self.name = name
self.type = type
self.straightLengths = straightLengths
self.groups = groups
}
}
/// Represents the data needed to update an ``EquivalentLength`` in the database.
///
/// Only the supplied fields are updated.
public struct Update: Codable, Equatable, Sendable {
/// A unique name / label for this equivalent length.
public let name: String?
/// The type (supply or return) of the equivalent length.
public let type: EffectiveLengthType?
/// The straight lengths of duct for this equivalent length.
public let straightLengths: [Int]?
/// The fitting groups associated with this equivalent length.
public let groups: [FittingGroup]?
public init(
name: String? = nil,
type: EquivalentLength.EffectiveLengthType? = nil,
straightLengths: [Int]? = nil,
groups: [EquivalentLength.FittingGroup]? = nil
) {
self.name = name
self.type = type
self.straightLengths = straightLengths
self.groups = groups
}
}
/// Represents the type of equivalent length, either supply or return.
public enum EffectiveLengthType: String, CaseIterable, Codable, Sendable {
case `return`
case supply
}
/// Represents a Manual-D fitting group.
///
/// These are defined by Manual-D and convert different types of fittings into
/// an equivalent length of straight duct.
public struct FittingGroup: Codable, Equatable, Sendable {
/// The fitting group number.
public let group: Int
/// The fitting group letter.
public let letter: String
/// The equivalent length of the fitting.
public let value: Double
/// The quantity of the fittings in the path.
public let quantity: Int
public init(
group: Int,
letter: String,
value: Double,
quantity: Int = 1
) {
self.group = group
self.letter = letter
self.value = value
self.quantity = quantity
}
}
// TODO: Should these not be optional and we just throw an error or return nil from
// a database query.
/// Represents the max ``EquivalentLength``'s for a project.
///
/// Calculating the duct sizes for a project requires there to be a max supply
/// and a max return equivalent length, so this container represents those values
/// when they exist in the database.
public struct MaxContainer: Codable, Equatable, Sendable {
/// The longest supply equivalent length.
public let supply: EquivalentLength?
/// The longest return equivalent length.
public let `return`: EquivalentLength?
public init(supply: EquivalentLength? = nil, return: EquivalentLength? = nil) {
self.supply = supply
self.return = `return`
}
public var totalEquivalentLength: Double? {
guard let supply else { return nil }
guard let `return` else { return nil }
return supply.totalEquivalentLength + `return`.totalEquivalentLength
}
}
}
extension EquivalentLength {
/// The calculated total equivalent length.
///
/// This is the sum of all the straigth lengths and fitting groups (with quantities).
public var totalEquivalentLength: Double {
straightLengths.reduce(into: 0.0) { $0 += Double($1) }
+ groups.totalEquivalentLength
}
}
extension Array where Element == EquivalentLength.FittingGroup {
/// The calculated total equivalent length for the fitting groups.
public var totalEquivalentLength: Double {
reduce(into: 0.0) {
$0 += ($1.value * Double($1.quantity))
}
}
}
#if DEBUG
extension EquivalentLength {
public static func mock(projectID: Project.ID) -> [Self] {
@Dependency(\.uuid) var uuid
@Dependency(\.date.now) var now
return [
.init(
id: uuid(),
projectID: projectID,
name: "Supply - 1",
type: .supply,
straightLengths: [10, 25],
groups: [
.init(group: 1, letter: "a", value: 20),
.init(group: 2, letter: "b", value: 30, quantity: 1),
.init(group: 3, letter: "a", value: 10, quantity: 1),
.init(group: 12, letter: "a", value: 10, quantity: 1),
],
createdAt: now,
updatedAt: now
),
.init(
id: uuid(),
projectID: projectID,
name: "Return - 1",
type: .return,
straightLengths: [10, 20, 5],
groups: [
.init(group: 5, letter: "a", value: 10),
.init(group: 6, letter: "a", value: 15, quantity: 1),
.init(group: 7, letter: "a", value: 20, quantity: 1),
],
createdAt: now,
updatedAt: now
),
]
}
}
#endif