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: Positive { switch self { case let .airHandler(airHandler): return airHandler.externalStaticPressure case let .furnaceAndCoil(furnaceAndCoil): return furnaceAndCoil.externalStaticPressure } } public var manufacturersIncludedFilterPressureDrop: Positive { switch self { case let .airHandler(airHandler): return airHandler.$manufacturersIncludedFilterPressureDrop case let .furnaceAndCoil(furnaceAndCoil): return furnaceAndCoil.$manufacturersIncludedFilterPressureDrop } } public struct AirHandler: Equatable { @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, manufacturersIncludedFilterPressureDrop: Positive, returnPlenumPressure: Positive, postFilterPressure: Positive, postCoilPressure: Positive, supplyPlenumPressure: Positive ) { self._airflow = airflow self._manufacturersIncludedFilterPressureDrop = manufacturersIncludedFilterPressureDrop self._returnPlenumPressure = returnPlenumPressure self._postFilterPressure = postFilterPressure self._postCoilPressure = postCoilPressure self._supplyPlenumPressure = supplyPlenumPressure } public var externalStaticPressure: Positive { var postFilterAdder = Positive.zero if $postFilterPressure > $manufacturersIncludedFilterPressureDrop { postFilterAdder = $postFilterPressure - $manufacturersIncludedFilterPressureDrop } return $returnPlenumPressure + postFilterAdder + $supplyPlenumPressure } } public enum EquipmentType: Equatable, CaseIterable, CustomStringConvertible, Identifiable { 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" } } } // TODO: Needs updated for when forms are using `minmal` values. public struct FlaggedMeasurement: 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: 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 { @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, manufacturersIncludedFilterPressureDrop: Positive, returnPlenumPressure: Positive, postFilterPressure: Positive, preCoilPressure: Positive, supplyPlenumPressure: Positive ) { self._airflow = airflow self._manufacturersIncludedFilterPressureDrop = manufacturersIncludedFilterPressureDrop self._returnPlenumPressure = returnPlenumPressure self._postFilterPressure = postFilterPressure self._preCoilPressure = preCoilPressure self._supplyPlenumPressure = supplyPlenumPressure } public var externalStaticPressure: Positive { ($postFilterPressure - $manufacturersIncludedFilterPressureDrop) + $preCoilPressure } } } 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, postFilterPressure: Positive, 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, 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