412 lines
12 KiB
Swift
412 lines
12 KiB
Swift
import Foundation
|
|
|
|
public enum EquipmentMeasurement: Equatable, Sendable {
|
|
|
|
case airHandler(AirHandler)
|
|
case furnaceAndCoil(FurnaceAndCoil)
|
|
|
|
public var equipmentType: EquipmentType {
|
|
switch self {
|
|
case .airHandler:
|
|
return .airHandler
|
|
case .furnaceAndCoil:
|
|
return .furnaceAndCoil
|
|
}
|
|
}
|
|
|
|
public var externalStaticPressure: Positive<Double> {
|
|
switch self {
|
|
case let .airHandler(airHandler):
|
|
return airHandler.externalStaticPressure
|
|
case let .furnaceAndCoil(furnaceAndCoil):
|
|
return furnaceAndCoil.externalStaticPressure
|
|
}
|
|
}
|
|
|
|
public var manufacturersIncludedFilterPressureDrop: Positive<Double> {
|
|
switch self {
|
|
case let .airHandler(airHandler):
|
|
return airHandler.$manufacturersIncludedFilterPressureDrop
|
|
case let .furnaceAndCoil(furnaceAndCoil):
|
|
return furnaceAndCoil.$manufacturersIncludedFilterPressureDrop
|
|
}
|
|
}
|
|
|
|
public struct AirHandler: Equatable, Sendable {
|
|
|
|
@Positive
|
|
public var airflow: Double
|
|
|
|
@Positive
|
|
public var manufacturersIncludedFilterPressureDrop: 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,
|
|
manufacturersIncludedFilterPressureDrop: Double,
|
|
returnPlenumPressure: Double,
|
|
postFilterPressure: Double,
|
|
postCoilPressure: Double,
|
|
supplyPlenumPressure: Double
|
|
) {
|
|
self.airflow = airflow
|
|
self.manufacturersIncludedFilterPressureDrop = manufacturersIncludedFilterPressureDrop
|
|
self.returnPlenumPressure = returnPlenumPressure
|
|
self.postFilterPressure = postFilterPressure
|
|
self.postCoilPressure = postCoilPressure
|
|
self.supplyPlenumPressure = supplyPlenumPressure
|
|
}
|
|
|
|
public init(
|
|
airflow: Positive<Double>,
|
|
manufacturersIncludedFilterPressureDrop: Positive<Double>,
|
|
returnPlenumPressure: Positive<Double>,
|
|
postFilterPressure: Positive<Double>,
|
|
postCoilPressure: Positive<Double>,
|
|
supplyPlenumPressure: Positive<Double>
|
|
) {
|
|
self._airflow = airflow
|
|
self._manufacturersIncludedFilterPressureDrop = manufacturersIncludedFilterPressureDrop
|
|
self._returnPlenumPressure = returnPlenumPressure
|
|
self._postFilterPressure = postFilterPressure
|
|
self._postCoilPressure = postCoilPressure
|
|
self._supplyPlenumPressure = supplyPlenumPressure
|
|
}
|
|
|
|
public var externalStaticPressure: Positive<Double> {
|
|
guard $manufacturersIncludedFilterPressureDrop == .zero else {
|
|
return Positive(
|
|
($postFilterPressure.positiveValue - $manufacturersIncludedFilterPressureDrop.positiveValue)
|
|
+ $supplyPlenumPressure.positiveValue
|
|
)
|
|
}
|
|
return Positive(
|
|
$returnPlenumPressure.positiveValue + $supplyPlenumPressure.positiveValue
|
|
)
|
|
}
|
|
}
|
|
|
|
public enum EquipmentType: Equatable, CaseIterable, CustomStringConvertible, Identifiable, Sendable {
|
|
case airHandler
|
|
case furnaceAndCoil
|
|
|
|
public var id: Self { self }
|
|
|
|
public var description: String {
|
|
switch self {
|
|
case .airHandler:
|
|
return "Air Handler"
|
|
|
|
case .furnaceAndCoil:
|
|
return "Furnace & Coil"
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct FlaggedMeasurement: Equatable, Sendable {
|
|
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: EquipmentMetadata.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: EquipmentMetadata.CoolingCapacity?
|
|
) -> Self {
|
|
.init(
|
|
airflow: checkAirflow(value: measurement.airflow, tons: tons),
|
|
coilPressureDrop: .init(
|
|
wrappedValue: (measurement.$postCoilPressure.positiveValue - measurement.$postFilterPressure.positiveValue),
|
|
.result(.good())
|
|
),
|
|
externalStaticPressure: checkExternalStaticPressure(
|
|
value: measurement.externalStaticPressure,
|
|
ratedPressures: ratedPressures
|
|
),
|
|
filterPressureDrop: calculateFilterPressureDrop(
|
|
returnPlenumPressure: measurement.$returnPlenumPressure,
|
|
postFilterPressure: measurement.$postFilterPressure,
|
|
filterBudget: budgets.filterBudget,
|
|
ratedPressures: ratedPressures
|
|
),
|
|
returnPlenumPressure: .init(
|
|
value: measurement.$returnPlenumPressure.positiveValue,
|
|
budget: budgets.returnPlenumBudget,
|
|
ratedPressures: ratedPressures
|
|
),
|
|
supplyPlenumPressure: .init(
|
|
value: measurement.$supplyPlenumPressure.positiveValue,
|
|
budget: budgets.supplyPlenumBudget,
|
|
ratedPressures: ratedPressures
|
|
)
|
|
)
|
|
}
|
|
|
|
public static func furnaceAndCoil(
|
|
budgets: BudgetedPercentEnvelope,
|
|
measurement: EquipmentMeasurement.FurnaceAndCoil,
|
|
ratedPressures: RatedStaticPressures,
|
|
tons: EquipmentMetadata.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: calculateFilterPressureDrop(
|
|
returnPlenumPressure: measurement.$returnPlenumPressure,
|
|
postFilterPressure: measurement.$postFilterPressure,
|
|
filterBudget: budgets.filterBudget,
|
|
ratedPressures: ratedPressures
|
|
),
|
|
returnPlenumPressure: .init(
|
|
value: measurement.$returnPlenumPressure.positiveValue,
|
|
budget: budgets.returnPlenumBudget,
|
|
ratedPressures: ratedPressures
|
|
),
|
|
supplyPlenumPressure: .init(
|
|
value: measurement.$supplyPlenumPressure.positiveValue,
|
|
budget: budgets.supplyPlenumBudget,
|
|
ratedPressures: ratedPressures
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
|
|
public struct FurnaceAndCoil: Equatable, Sendable {
|
|
|
|
@Positive
|
|
public var airflow: Double
|
|
|
|
@Positive
|
|
public var manufacturersIncludedFilterPressureDrop: 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,
|
|
manufacturersIncludedFilterPressureDrop: Double,
|
|
returnPlenumPressure: Double,
|
|
postFilterPressure: Double,
|
|
preCoilPressure: Double,
|
|
supplyPlenumPressure: Double
|
|
) {
|
|
self.airflow = airflow
|
|
self.manufacturersIncludedFilterPressureDrop = manufacturersIncludedFilterPressureDrop
|
|
self.returnPlenumPressure = returnPlenumPressure
|
|
self.postFilterPressure = postFilterPressure
|
|
self.preCoilPressure = preCoilPressure
|
|
self.supplyPlenumPressure = supplyPlenumPressure
|
|
}
|
|
|
|
public init(
|
|
airflow: Positive<Double>,
|
|
manufacturersIncludedFilterPressureDrop: Positive<Double>,
|
|
returnPlenumPressure: Positive<Double>,
|
|
postFilterPressure: Positive<Double>,
|
|
preCoilPressure: Positive<Double>,
|
|
supplyPlenumPressure: Positive<Double>
|
|
) {
|
|
self._airflow = airflow
|
|
self._manufacturersIncludedFilterPressureDrop = manufacturersIncludedFilterPressureDrop
|
|
self._returnPlenumPressure = returnPlenumPressure
|
|
self._postFilterPressure = postFilterPressure
|
|
self._preCoilPressure = preCoilPressure
|
|
self._supplyPlenumPressure = supplyPlenumPressure
|
|
}
|
|
|
|
public var externalStaticPressure: Positive<Double> {
|
|
let double = ($postFilterPressure.positiveValue - $manufacturersIncludedFilterPressureDrop.positiveValue)
|
|
+ $preCoilPressure.positiveValue
|
|
|
|
// ($postFilterPressure - $manufacturersIncludedFilterPressureDrop) + $preCoilPressure
|
|
return Positive(double)
|
|
}
|
|
}
|
|
}
|
|
|
|
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 calculateFilterPressureDrop(
|
|
returnPlenumPressure: Positive<Double>,
|
|
postFilterPressure: Positive<Double>,
|
|
filterBudget: Percentage,
|
|
ratedPressures: RatedStaticPressures
|
|
) -> Flagged {
|
|
guard postFilterPressure > 0 else {
|
|
return .init(wrappedValue: 0, .result(.good()))
|
|
}
|
|
return .init(
|
|
value: postFilterPressure.positiveValue - returnPlenumPressure.positiveValue,
|
|
budget: filterBudget,
|
|
ratedPressures: ratedPressures,
|
|
ignoreMinimum: true
|
|
)
|
|
}
|
|
|
|
fileprivate func checkExternalStaticPressure(
|
|
value: Positive<Double>,
|
|
ratedPressures: RatedStaticPressures
|
|
) -> Flagged {
|
|
.init(
|
|
wrappedValue: value.positiveValue,
|
|
.rated(ratedPressures)
|
|
)
|
|
}
|
|
|
|
fileprivate func checkAirflow(
|
|
value: Double?,
|
|
tons: EquipmentMetadata.CoolingCapacity?
|
|
) -> Flagged {
|
|
guard let value, let tons else {
|
|
return .init(wrappedValue: value ?? 0, .result(.good()))
|
|
}
|
|
return .init(wrappedValue: value, .airflow(tons: tons))
|
|
}
|
|
|
|
|
|
#if DEBUG
|
|
extension EquipmentMeasurement {
|
|
public static func mock(type equipmentType: EquipmentType) -> Self {
|
|
switch equipmentType {
|
|
case .airHandler:
|
|
return .airHandler(.init(
|
|
airflow: 1200,
|
|
manufacturersIncludedFilterPressureDrop: 0.1,
|
|
returnPlenumPressure: 0.3,
|
|
postFilterPressure: 0.6,
|
|
postCoilPressure: 0.9,
|
|
supplyPlenumPressure: 0.2
|
|
))
|
|
|
|
case .furnaceAndCoil:
|
|
return .furnaceAndCoil(.init(
|
|
airflow: 1200,
|
|
manufacturersIncludedFilterPressureDrop: 0.0,
|
|
returnPlenumPressure: 0.3,
|
|
postFilterPressure: 0.6,
|
|
preCoilPressure: 0.4,
|
|
supplyPlenumPressure: 0.1
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
extension EquipmentMeasurement.FlaggedMeasurement {
|
|
public static func mock(type equipmentType: EquipmentMeasurement.EquipmentType) -> Self {
|
|
.init(
|
|
budgets: .init(equipmentType: equipmentType, fanType: .variableSpeed),
|
|
measurement: .mock(type: equipmentType),
|
|
ratedPressures: .init(),
|
|
tons: .default
|
|
)
|
|
}
|
|
}
|
|
#endif
|