feat: Initial commit

This commit is contained in:
2024-05-24 16:44:41 -04:00
commit c1741de3f9
19 changed files with 1560 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
import Foundation
public struct BudgetedPercentEnvelope: Equatable {
public var coilBudget: Percentage
public var filterBudget: Percentage
public var returnPlenumBudget: Percentage
public var supplyPlenumBudget: Percentage
public var preCoilBudget: Percentage {
coilBudget + supplyPlenumBudget
}
public init(
coilBudget: Percentage,
filterBudget: Percentage,
returnPlenumBudget: Percentage,
supplyPlenumBudget: Percentage
) {
self.coilBudget = coilBudget
self.filterBudget = filterBudget
self.returnPlenumBudget = returnPlenumBudget
self.supplyPlenumBudget = supplyPlenumBudget
}
public init(equipmentType: EquipmentType, fanType: FanType) {
switch equipmentType {
case .furnaceAndCoil:
switch fanType {
case .constantSpeed:
self.init(
coilBudget: 40%,
filterBudget: 20%,
returnPlenumBudget: 20%,
supplyPlenumBudget: 20%
)
case .variableSpeed:
self.init(
coilBudget: 30%,
filterBudget: 40%,
returnPlenumBudget: 15%,
supplyPlenumBudget: 15%
)
}
case .airHandler:
self.init(
coilBudget: .zero,
filterBudget: 50%,
returnPlenumBudget: 25%,
supplyPlenumBudget: 25%
)
}
}
}

View File

@@ -0,0 +1,16 @@
import Foundation
public enum CoolingCapacity: Double, Equatable, CaseIterable {
case half = 0.5
case threeQuarter = 0.75
case one = 1
case oneAndAHalf = 1.5
case two = 2
case twoAndAHalf = 2.5
case three = 3
case threeAndAHalf = 3.5
case four = 4
case five = 5
public static var `default`: Self { .three }
}

View File

@@ -0,0 +1,117 @@
import Foundation
public enum EquipmentMeasurement: Equatable {
case airHandler(AirHandler)
case furnaceAndCoil(FurnaceAndCoil)
public var equipmentType: EquipmentType {
switch self {
case .airHandler:
return .airHandler
case .furnaceAndCoil:
return .furnaceAndCoil
}
}
public var externalStaticPressure: Double {
switch self {
case let .airHandler(airHandler):
return airHandler.externalStaticPressure
case let .furnaceAndCoil(furnaceAndCoil):
return furnaceAndCoil.externalStaticPressure
}
}
public struct AirHandler: Equatable {
public var airflow: Double?
@Positive
public var returnPlenumPressure: Double?
@Positive
public var postFilterPressure: Double?
@Positive
public var postCoilPressure: Double?
@Positive
public var supplyPlenumPressure: Double?
public init(
airflow: Double? = nil,
returnPlenumPressure: Double? = nil,
postFilterPressure: Double? = nil,
postCoilPressure: Double? = nil,
supplyPlenumPressure: Double? = nil
) {
self.airflow = airflow
self.returnPlenumPressure = returnPlenumPressure
self.postFilterPressure = postFilterPressure
self.postCoilPressure = postCoilPressure
self.supplyPlenumPressure = supplyPlenumPressure
}
public var externalStaticPressure: Double {
($returnPlenumPressure.positiveValue ?? 0) + ($supplyPlenumPressure.positiveValue ?? 0)
}
}
public struct FurnaceAndCoil: Equatable {
public var airflow: Double?
@Positive
public var returnPlenumPressure: Double?
@Positive
public var postFilterPressure: Double?
@Positive
public var preCoilPressure: Double?
@Positive
public var supplyPlenumPressure: Double?
public init(
airflow: Double? = nil,
returnPlenumPressure: Double? = nil,
postFilterPressure: Double? = nil,
preCoilPressure: Double? = nil,
supplyPlenumPressure: Double? = nil
) {
self.airflow = airflow
self.returnPlenumPressure = returnPlenumPressure
self.postFilterPressure = postFilterPressure
self.preCoilPressure = preCoilPressure
self.supplyPlenumPressure = supplyPlenumPressure
}
public var externalStaticPressure: Double {
($postFilterPressure.positiveValue ?? 0) + ($preCoilPressure.positiveValue ?? 0)
}
}
}
extension EquipmentMeasurement.AirHandler {
public enum Key: String, Equatable, CaseIterable {
case returnPlenumPressure
case postFilterPressure
case postCoilPressure
case supplyPlenumPressure
case airflow
}
}
extension EquipmentMeasurement.FurnaceAndCoil {
public enum Key: String, Equatable, CaseIterable {
case returnPlenumPressure
case postFilterPressure
case preCoilPressure
case supplyPlenumPressure
case airflow
}
}

View File

@@ -0,0 +1,16 @@
import Foundation
public enum EquipmentType: Equatable, CaseIterable, CustomStringConvertible {
case airHandler
case furnaceAndCoil
public var description: String {
switch self {
case .airHandler:
return "Air Handler"
case .furnaceAndCoil:
return "Furnace & Coil"
}
}
}

View File

@@ -0,0 +1,15 @@
import Foundation
public enum FanType: Equatable, CaseIterable, CustomStringConvertible {
case constantSpeed
case variableSpeed
public var description: String {
switch self {
case .constantSpeed:
return "Constant Speed"
case .variableSpeed:
return "Variable Speed"
}
}
}

View File

@@ -0,0 +1,254 @@
import Foundation
public enum FlaggedValue<Value> {
case good(Value, String?)
case warning(Value, String?)
case error(Value, String?)
public init(_ value: Value, key: Key, message: String? = nil) {
switch key {
case .good:
self = .good(value, message)
case .warning:
self = .warning(value, message)
case .error:
self = .error(value, message)
}
}
public var key: Key {
switch self {
case .good:
return .good
case .warning:
return .warning
case .error:
return .error
}
}
public var value: Value {
switch self {
case let .good(value, _):
return value
case let .warning(value, _):
return value
case let .error(value, _):
return value
}
}
public var message: String? {
switch self {
case let .good(_, message):
return message
case let .warning(_, message):
return message
case let .error(_, message):
return message
}
}
public enum Key: String, Equatable, CaseIterable {
case good, warning, error
public var title: String {
self.rawValue.capitalized
}
}
}
extension FlaggedValue: Equatable where Value: Equatable { }
@dynamicMemberLookup
public struct Flagged: Equatable {
public var checkValue: CheckHandler
public var wrappedValue: Double
public init(
wrappedValue: Double,
_ handler: CheckHandler
) {
self.checkValue = handler
self.wrappedValue = wrappedValue
}
public init(
wrappedValue: Double,
_ checkValue: @escaping (Double) -> CheckResult
) {
self.checkValue = .init(checkValue)
self.wrappedValue = wrappedValue
}
public static func error(_ wrappedValue: Double? = nil, message: String) -> Self {
self.init(wrappedValue: wrappedValue ?? 0) { _ in
return .error(message)
}
}
public var projectedValue: FlaggedValue<Double> {
let checkedResult = checkValue(wrappedValue)
let key = checkedResult.key
let message: String?
switch checkedResult {
case let .aboveMaximum(max):
message = "Above maximum: \(doubleString(max))"
case let .belowMinimum(min):
message = "Below minimum: \(doubleString(min))"
case .betweenRange(minimum: let minimum, maximum: let maximum):
message = "Between: \(minimum) and \(maximum)"
case .betweenRatedAndMaximum(rated: let rated, maximum: let maximum):
message = "Between rated: \(doubleString(rated)) and maximum: \(doubleString(maximum))"
case let .good(goodMessage):
message = goodMessage
case let .error(errorMessage):
message = errorMessage
}
return .init(wrappedValue, key: key, message: message)
}
public struct GoodMessageHandler {
let message: (Double) -> String?
public init(message: @escaping (Double) -> String?) {
self.message = message
}
public func callAsFunction(_ double: Double) -> String? {
self.message(double)
}
public static var none: Self {
.init { _ in nil }
}
}
public struct CheckHandler {
let checkValue: (Double) -> CheckResult
public init(_ checkValue: @escaping (Double) -> CheckResult) {
self.checkValue = checkValue
}
public func callAsFunction(_ value: Double) -> CheckResult {
self.checkValue(value)
}
}
public enum CheckResult: Equatable {
case aboveMaximum(Double)
case belowMinimum(Double)
case betweenRange(minimum: Double, maximum: Double)
case betweenRatedAndMaximum(rated: Double, maximum: Double)
case good(String? = nil)
case error(String)
var key: FlaggedValue<Double>.Key {
switch self {
case .aboveMaximum(_):
return .error
case .belowMinimum(_):
return .error
case .betweenRange:
return .warning
case .betweenRatedAndMaximum:
return .warning
case .good(_):
return .good
case .error(_):
return .error
}
}
}
public static func == (lhs: Flagged, rhs: Flagged) -> Bool {
lhs.wrappedValue == rhs.wrappedValue
}
public subscript<T>(dynamicMember keyPath: KeyPath<FlaggedValue<Double>, T>) -> T {
self.projectedValue[keyPath: keyPath]
}
}
// MARK: - Check Handlers
extension Flagged.CheckHandler {
public static func using(
maximum: Double,
minimum: Double,
rated: Double,
goodMessage: Flagged.GoodMessageHandler = .none
) -> Self {
.init { value in
if value < minimum {
return .belowMinimum(minimum)
} else if value > maximum {
return .aboveMaximum(maximum)
} else if value > rated {
return .betweenRatedAndMaximum(rated: rated, maximum: maximum)
} else {
return .good(goodMessage(value))
}
}
}
public static func airflow(
tons: CoolingCapacity,
ratings: RatedAirflowPerTon = .init(),
goodMessage: Flagged.GoodMessageHandler? = nil
) -> Self {
.rated(RatedAirflowLimits(tons: tons, using: ratings), goodMessage: goodMessage)
}
public static func rated<T>(
_ ratings: RatedEnvelope<T>,
goodMessage: Flagged.GoodMessageHandler? = nil
) -> Self {
.using(
maximum: ratings.maximum,
minimum: ratings.minimum,
rated: ratings.rated,
goodMessage: goodMessage ?? .none
)
}
public static func result(_ result: Flagged.CheckResult) -> Self {
self.init { _ in result }
}
public static func velocity(
goodMessage: Flagged.GoodMessageHandler = .none
) -> Self {
.init { value in
if value <= 700 {
return .good(goodMessage(value))
} else if value < 900 {
return .betweenRange(minimum: 700, maximum: 900)
} else {
return .error("Velocity greater than 900 FPM")
}
}
}
}
// MARK: - Helpers
fileprivate let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
return formatter
}()
fileprivate func doubleString(_ double: Double) -> String {
return formatter.string(for: double) ?? "\(double)"
}

View File

@@ -0,0 +1,225 @@
import Foundation
// TODO: Needs updated for when forms are using `minmal` values.
public struct FlaggedEquipmentMeasurement: Equatable {
public var airflow: Flagged
public var coilPressureDrop: Flagged
public var externalStaticPressure: Flagged
public var filterPressureDrop: Flagged
public var returnPlenumPressure: Flagged
public var supplyPlenumPressure: Flagged
public init(
airflow: Flagged,
coilPressureDrop: Flagged,
externalStaticPressure: Flagged,
filterPressureDrop: Flagged,
returnPlenumPressure: Flagged,
supplyPlenumPressure: Flagged
) {
self.airflow = airflow
self.coilPressureDrop = coilPressureDrop
self.externalStaticPressure = externalStaticPressure
self.filterPressureDrop = filterPressureDrop
self.returnPlenumPressure = returnPlenumPressure
self.supplyPlenumPressure = supplyPlenumPressure
}
public init(
budgets: BudgetedPercentEnvelope,
measurement: EquipmentMeasurement,
ratedPressures: RatedStaticPressures,
tons: CoolingCapacity?
) {
switch measurement {
case let .airHandler(airHandler):
self = .airHandler(
budgets: budgets,
measurement: airHandler,
ratedPressures: ratedPressures,
tons: tons
)
case let .furnaceAndCoil(furnaceAndCoil):
self = .furnaceAndCoil(
budgets: budgets,
measurement: furnaceAndCoil,
ratedPressures: ratedPressures,
tons: tons
)
}
}
public static func airHandler(
budgets: BudgetedPercentEnvelope,
measurement: EquipmentMeasurement.AirHandler,
ratedPressures: RatedStaticPressures,
tons: CoolingCapacity?
) -> Self {
.init(
airflow: checkAirflow(value: measurement.airflow, tons: tons),
coilPressureDrop: .init(
wrappedValue: (measurement.$postCoilPressure.positiveValue - measurement.$postFilterPressure.positiveValue) ?? 0,
.result(.good())
),
externalStaticPressure: checkExternalStaticPressure(
value: measurement.externalStaticPressure,
ratedPressures: ratedPressures
),
filterPressureDrop: .init(
value: measurement.$postFilterPressure.positiveValue - measurement.$returnPlenumPressure.positiveValue,
budget: budgets.filterBudget,
ratedPressures: ratedPressures
),
returnPlenumPressure: .init(
value: measurement.$returnPlenumPressure.positiveValue,
budget: budgets.returnPlenumBudget,
ratedPressures: ratedPressures
),
supplyPlenumPressure: .init(
value: measurement.$supplyPlenumPressure.positiveValue ?? 0,
budget: budgets.supplyPlenumBudget,
ratedPressures: ratedPressures
)
)
}
public static func furnaceAndCoil(
budgets: BudgetedPercentEnvelope,
measurement: EquipmentMeasurement.FurnaceAndCoil,
ratedPressures: RatedStaticPressures,
tons: CoolingCapacity?
) -> Self {
.init(
airflow: checkAirflow(value: measurement.airflow, tons: tons),
coilPressureDrop: .init(
value: measurement.$preCoilPressure.positiveValue - measurement.$supplyPlenumPressure.positiveValue,
budget: budgets.coilBudget,
ratedPressures: ratedPressures
),
externalStaticPressure: checkExternalStaticPressure(
value: measurement.externalStaticPressure,
ratedPressures: ratedPressures
),
filterPressureDrop: .init(
value: measurement.$postFilterPressure.positiveValue - measurement.$returnPlenumPressure.positiveValue,
budget: budgets.filterBudget,
ratedPressures: ratedPressures,
ignoreMinimum: true
),
returnPlenumPressure: .init(
value: measurement.$returnPlenumPressure.positiveValue,
budget: budgets.returnPlenumBudget,
ratedPressures: ratedPressures
),
supplyPlenumPressure: .init(
value: measurement.$supplyPlenumPressure.positiveValue,
budget: budgets.supplyPlenumBudget,
ratedPressures: ratedPressures
)
)
}
}
// MARK: - Key
extension FlaggedEquipmentMeasurement {
// NOTE: These need to be kept in display order.
public enum Key: Equatable, CaseIterable {
case returnPlenum
case filterDrop
case coilDrop
case supplyPlenum
case staticPressure
case airflow
public var title: String {
switch self {
case .returnPlenum:
return "Return Plenum"
case .filterDrop:
return "Filter Pressure Drop"
case .coilDrop:
return "Coil Pressure Drop"
case .supplyPlenum:
return "Supply Plenum"
case .staticPressure:
return "External Static Pressure"
case .airflow:
return "System Airflow"
}
}
public var flaggedKeyPath: KeyPath<FlaggedEquipmentMeasurement, Flagged> {
switch self {
case .returnPlenum:
return \.returnPlenumPressure
case .filterDrop:
return \.filterPressureDrop
case .coilDrop:
return \.coilPressureDrop
case .supplyPlenum:
return \.supplyPlenumPressure
case .staticPressure:
return \.externalStaticPressure
case .airflow:
return \.airflow
}
}
}
}
// MARK: - Helpers
fileprivate extension Flagged {
init(
value: Double,
budget: Percentage,
ratedPressures: RatedStaticPressures,
ignoreMinimum: Bool = false
) {
let minimum = ignoreMinimum ? 0 : ratedPressures.minimum * budget.fraction
let maximum = ratedPressures.maximum * budget.fraction
let rated = ratedPressures.rated * budget.fraction
self.init(
wrappedValue: value,
.using(maximum: maximum, minimum: minimum, rated: rated)
)
}
init(
value: Double?,
budget: Percentage,
ratedPressures: RatedStaticPressures,
ignoreMinimum: Bool = false
) {
guard let value else {
self = .error(message: "Value is not set.")
return
}
self.init(
value: value,
budget: budget,
ratedPressures: ratedPressures,
ignoreMinimum: ignoreMinimum
)
}
}
fileprivate func checkExternalStaticPressure(
value: Double,
ratedPressures: RatedStaticPressures
) -> Flagged {
.init(
wrappedValue: value,
.rated(ratedPressures)
)
}
fileprivate func checkAirflow(
value: Double?,
tons: CoolingCapacity?
) -> Flagged {
guard let value, let tons else {
return .init(wrappedValue: value ?? 0, .result(.good()))
}
return .init(wrappedValue: value, .airflow(tons: tons))
}

View File

@@ -0,0 +1,119 @@
import Foundation
extension Optional: ExpressibleByIntegerLiteral where Wrapped: ExpressibleByIntegerLiteral {
public typealias IntegerLiteralType = Wrapped.IntegerLiteralType
public init(integerLiteral value: Wrapped.IntegerLiteralType) {
self = .some(.init(integerLiteral: value))
}
}
extension Optional: ExpressibleByFloatLiteral where Wrapped: ExpressibleByFloatLiteral {
public typealias FloatLiteralType = Wrapped.FloatLiteralType
public init(floatLiteral value: Wrapped.FloatLiteralType) {
self = .some(.init(floatLiteral: value))
}
}
extension Optional: AdditiveArithmetic where Wrapped: AdditiveArithmetic {
public static func - (lhs: Optional<Wrapped>, rhs: Optional<Wrapped>) -> Optional<Wrapped> {
switch (lhs, rhs) {
case let (.some(lhs), .some(rhs)):
return .some(lhs - rhs)
case let (.some(lhs), .none):
return .some(lhs - .zero)
case let (.none, .some(rhs)):
return .some(.zero - rhs)
case (.none, .none):
return .none
}
}
public static func + (lhs: Optional<Wrapped>, rhs: Optional<Wrapped>) -> Optional<Wrapped> {
switch (lhs, rhs) {
case let (.some(lhs), .some(rhs)):
return .some(lhs + rhs)
case let (.some(lhs), .none):
return .some(lhs + .zero)
case let (.none, .some(rhs)):
return .some(.zero + rhs)
case (.none, .none):
return .none
}
}
public static var zero: Optional<Wrapped> {
.some(Wrapped.zero)
}
}
extension Optional: Numeric where Wrapped: Numeric {
public static func *= (lhs: inout Optional<Wrapped>, rhs: Optional<Wrapped>) {
switch (lhs, rhs) {
case let (.some(lhsValue), .some(rhsValue)):
lhs = lhsValue * rhsValue
case (.some, .none),
(.none, .some),
(.none, .none):
lhs = .none
}
}
public init?<T>(exactly source: T) where T : BinaryInteger {
if let wrappedValue = Wrapped(exactly: source) {
self = .some(wrappedValue)
} else {
self = .none
}
}
public var magnitude: Wrapped.Magnitude {
switch self {
case .none:
return Self.zero!.magnitude
case let .some(value):
return value.magnitude
}
}
public static func * (lhs: Optional<Wrapped>, rhs: Optional<Wrapped>) -> Optional<Wrapped> {
switch (lhs, rhs) {
case let (.some(lhs), .some(rhs)):
return .some(lhs * rhs)
case (.some, .none),
(.none, .some),
(.none, .none):
return .none
}
}
public typealias Magnitude = Wrapped.Magnitude
}
extension Optional: Comparable where Wrapped: Comparable {
public static func < (lhs: Optional<Wrapped>, rhs: Optional<Wrapped>) -> Bool {
switch (lhs, rhs) {
case let (.some(lhs), .some(rhs)):
return lhs < rhs
default:
return false
}
}
}

View File

@@ -0,0 +1,93 @@
import Foundation
public struct Percentage: Equatable, RawRepresentable {
public var rawValue: Double
public init(rawValue: Double) {
self.rawValue = rawValue
}
public init(fraction: Double) {
self.rawValue = fraction * 100
}
public var fraction: Double {
self.rawValue / 100
}
}
extension Percentage: ExpressibleByFloatLiteral {
public typealias FloatLiteralType = Double
public init(floatLiteral value: Double) {
self.init(rawValue: value)
}
}
extension Percentage: ExpressibleByIntegerLiteral {
public typealias IntegerLiteralType = Int
public init(integerLiteral value: Int) {
self.init(rawValue: Double(value))
}
}
extension Percentage: Numeric {
public typealias Magnitude = Double.Magnitude
public init?<T>(exactly source: T) where T : BinaryInteger {
self.init(rawValue: Double(source))
}
public var magnitude: Double.Magnitude {
rawValue.magnitude
}
public static func * (lhs: Percentage, rhs: Percentage) -> Percentage {
.init(rawValue: lhs.rawValue * rhs.rawValue)
}
public static func + (lhs: Percentage, rhs: Percentage) -> Percentage {
.init(rawValue: lhs.rawValue + rhs.rawValue)
}
public static func *= (lhs: inout Percentage, rhs: Percentage) {
lhs.rawValue *= rhs.rawValue
}
public static func - (lhs: Percentage, rhs: Percentage) -> Percentage {
.init(rawValue: lhs.rawValue - rhs.rawValue)
}
}
extension Percentage: Comparable {
public static func < (lhs: Percentage, rhs: Percentage) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
extension Percentage: CustomStringConvertible {
static var formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .percent
return formatter
}()
public var description: String {
Self.formatter.string(for: fraction) ?? "\(String(format: "%g", rawValue))%"
}
}
postfix operator %
public postfix func %(value: Double) -> Percentage {
.init(rawValue: value)
}
public postfix func %(value: Int) -> Percentage {
.init(rawValue: Double(value))
}

View File

@@ -0,0 +1,87 @@
import Foundation
@propertyWrapper
public struct Positive<Value> where Value: Numeric, Value: Comparable {
public var wrappedValue: Value
public init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
public var positiveValue: Value {
guard self.wrappedValue > .zero else {
return wrappedValue * -1
}
return wrappedValue
}
public var projectedValue: Self {
get { self }
set { self = newValue }
}
}
extension Positive: Equatable where Value: Equatable { }
extension Positive: Comparable where Value: Comparable {
public static func < (lhs: Positive<Value>, rhs: Positive<Value>) -> Bool {
lhs.wrappedValue < rhs.wrappedValue
}
}
extension Positive: ExpressibleByIntegerLiteral where Value: ExpressibleByIntegerLiteral {
public typealias IntegerLiteralType = Value.IntegerLiteralType
public init(integerLiteral value: Value.IntegerLiteralType) {
self.init(wrappedValue: .init(integerLiteral: value))
}
}
extension Positive: ExpressibleByFloatLiteral where Value: ExpressibleByFloatLiteral {
public typealias FloatLiteralType = Value.FloatLiteralType
public init(floatLiteral value: Value.FloatLiteralType) {
self.init(wrappedValue: .init(floatLiteral: value))
}
}
extension Positive: AdditiveArithmetic where Value: AdditiveArithmetic {
public static func - (lhs: Positive<Value>, rhs: Positive<Value>) -> Positive<Value> {
.init(wrappedValue: lhs.wrappedValue - rhs.wrappedValue)
}
public static func + (lhs: Positive<Value>, rhs: Positive<Value>) -> Positive<Value> {
.init(wrappedValue: lhs.wrappedValue + rhs.wrappedValue)
}
public static var zero: Positive<Value> {
.init(wrappedValue: Value.zero)
}
}
extension Positive: Numeric where Value: Numeric {
public static func *= (lhs: inout Positive<Value>, rhs: Positive<Value>) {
lhs.wrappedValue *= rhs.wrappedValue
}
public init?<T>(exactly source: T) where T : BinaryInteger {
guard let value = Value(exactly: source) else {
return nil
}
self = .init(wrappedValue: value)
}
public var magnitude: Value.Magnitude {
wrappedValue.magnitude
}
public static func * (lhs: Positive<Value>, rhs: Positive<Value>) -> Positive<Value> {
.init(wrappedValue: lhs.wrappedValue * rhs.wrappedValue)
}
public typealias Magnitude = Value.Magnitude
}

View File

@@ -0,0 +1,33 @@
import Foundation
public enum PlenumDimension: Equatable {
case rectangular(width: Double, height: Double)
case round(Double)
public var areaInSquareFeet: Double {
switch self {
case .rectangular(width: let width, height: let height):
return (width * height) / 144
case let .round(dimension):
return pow((dimension / 12) / 2, 2) * Double.pi
}
}
public enum Key: String, Equatable, CaseIterable, CustomStringConvertible {
case rectangular, round
public var description: String { rawValue.capitalized }
}
public var key: Key {
switch self {
case .rectangular:
return .rectangular
case .round:
return .round
}
}
}

View File

@@ -0,0 +1,50 @@
import Foundation
public struct RatedEnvelope<Rating>: Equatable {
public var maximum: Double
public var minimum: Double
public var rated: Double
public init(
maximum: Double,
minimum: Double,
rated: Double
) {
self.maximum = maximum
self.minimum = minimum
self.rated = rated
}
}
// MARK: - Namespaces
public enum StaticPressure { }
public enum AirflowPerTon { }
public enum AirflowLimits { }
// MARK: - Static Pressures
public typealias RatedStaticPressures = RatedEnvelope<StaticPressure>
extension RatedStaticPressures {
public init() {
self.init(maximum: 0.8, minimum: 0.3, rated: 0.5)
}
}
// MARK: - Airflow Per Ton
public typealias RatedAirflowPerTon = RatedEnvelope<AirflowPerTon>
extension RatedAirflowPerTon {
public init() {
self.init(maximum: 500, minimum: 350, rated: 400)
}
}
// MARK: - Airflow Limits
public typealias RatedAirflowLimits = RatedEnvelope<AirflowLimits>
extension RatedAirflowLimits {
public init(tons: CoolingCapacity, using airflowPerTon: RatedAirflowPerTon = .init()) {
self.init(
maximum: airflowPerTon.maximum * tons.rawValue,
minimum: airflowPerTon.minimum * tons.rawValue,
rated: airflowPerTon.rated * tons.rawValue
)
}
}

View File

@@ -0,0 +1,18 @@
import Foundation
public struct Velocity: Equatable {
public var airflow: Double
public var dimension: PlenumDimension
public var value: Double {
airflow / dimension.areaInSquareFeet
}
public var flagged: Flagged { .velocity(self) }
}
extension Flagged {
public static func velocity(_ velocity: Velocity) -> Self {
.init(wrappedValue: velocity.value, .velocity())
}
}