import Dependencies import DependenciesMacros import Foundation @_exported import SharedModels extension DependencyValues { public var estimatedPressuresClient: EstimatedPressureDependency { get { self[EstimatedPressureDependency.self] } set { self[EstimatedPressureDependency.self] = newValue } } } @DependencyClient public struct EstimatedPressureDependency { public var estimatedAirflow: (EstimatedAirflowRequest) async throws -> Positive public var estimatedPressure: (EstimatedPressureRequest) async throws -> Positive public struct EstimatedPressureRequest: Equatable { public let existingPressure: Positive public let existingAirflow: Positive public let targetAirflow: Positive public init( existingPressure: Double, existingAirflow: Double, targetAirflow: Double ) { self.existingPressure = .init(wrappedValue: existingPressure) self.existingAirflow = .init(wrappedValue: existingAirflow) self.targetAirflow = .init(wrappedValue: targetAirflow) } } public struct EstimatedAirflowRequest: Equatable { public let existingAirflow: Positive public let existingPressure: Positive public let targetPressure: Positive public init( existingAirflow: Double, existingPressure: Double, targetPressure: Double ) { self.existingPressure = .init(wrappedValue: existingPressure) self.existingAirflow = .init(wrappedValue: existingAirflow) self.targetPressure = .init(wrappedValue: targetPressure) } } public func estimatedPressure( existingPressure: Double, existingAirflow: Double, targetAirflow: Double ) async throws -> Positive { try await self.estimatedPressure( .init( existingPressure: existingPressure, existingAirflow: existingAirflow, targetAirflow: targetAirflow ) ) } } extension EstimatedPressureDependency { private func estimatedPressure( existingPressure: Positive, existingAirflow: Double, targetAirflow: Double ) async throws -> Positive { try await self.estimatedPressure( .init( existingPressure: existingPressure.positiveValue ?? 0, existingAirflow: existingAirflow, targetAirflow: targetAirflow ) ) } public func estimatedPressure( equipmentMeasurement: EquipmentMeasurement, airflow updatedAirflow: Double ) async throws -> EquipmentMeasurement { switch equipmentMeasurement { case let .airHandler(airHandler): guard let existingAirflow = airHandler.airflow else { throw InvalidAirflow() } return try await .airHandler( .init( airflow: updatedAirflow, returnPlenumPressure: self.estimatedPressure( existingPressure: airHandler.$returnPlenumPressure, existingAirflow: existingAirflow, targetAirflow: updatedAirflow ), postFilterPressure: self.estimatedPressure( existingPressure: airHandler.$postFilterPressure, existingAirflow: existingAirflow, targetAirflow: updatedAirflow ), postCoilPressure: self.estimatedPressure( existingPressure: airHandler.$postCoilPressure, existingAirflow: existingAirflow, targetAirflow: updatedAirflow ), supplyPlenumPressure: self.estimatedPressure( existingPressure: airHandler.$supplyPlenumPressure, existingAirflow: existingAirflow, targetAirflow: updatedAirflow ) ) ) case let .furnaceAndCoil(furnaceAndCoil): guard let existingAirflow = furnaceAndCoil.airflow else { throw InvalidAirflow() } return try await .furnaceAndCoil( .init( airflow: updatedAirflow, returnPlenumPressure: self.estimatedPressure( existingPressure: furnaceAndCoil.$returnPlenumPressure, existingAirflow: existingAirflow, targetAirflow: updatedAirflow ), postFilterPressure: self.estimatedPressure( existingPressure: furnaceAndCoil.$postFilterPressure, existingAirflow: existingAirflow, targetAirflow: updatedAirflow ), preCoilPressure: self.estimatedPressure( existingPressure: furnaceAndCoil.$preCoilPressure, existingAirflow: existingAirflow, targetAirflow: updatedAirflow ), supplyPlenumPressure: self.estimatedPressure( existingPressure: furnaceAndCoil.$supplyPlenumPressure, existingAirflow: existingAirflow, targetAirflow: updatedAirflow ) ) ) } } public func estimatedPressure( equipmentMeasurement: EquipmentMeasurement, airflow updatedAirflow: Double, filterPressureDrop: Positive ) async throws -> EquipmentMeasurement { let estimate = try await estimatedPressure( equipmentMeasurement: equipmentMeasurement, airflow: updatedAirflow ) switch estimate { case var .airHandler(airHandler): airHandler.postFilterPressure = airHandler.returnPlenumPressure + filterPressureDrop.positiveValue return .airHandler(airHandler) case var .furnaceAndCoil(furnaceAndCoil): furnaceAndCoil.postFilterPressure = furnaceAndCoil.returnPlenumPressure + filterPressureDrop.positiveValue return .furnaceAndCoil(furnaceAndCoil) } } public func estimatedPressure( equipmentMeasurement: EquipmentMeasurement, airflow updatedAirflow: Double, filterPressureDrop: Positive? ) async throws -> EquipmentMeasurement { guard let filterPressureDrop else { return try await estimatedPressure( equipmentMeasurement: equipmentMeasurement, airflow: updatedAirflow ) } return try await estimatedPressure( equipmentMeasurement: equipmentMeasurement, airflow: updatedAirflow, filterPressureDrop: filterPressureDrop ) } } extension EstimatedPressureDependency: DependencyKey { public static var testValue: EstimatedPressureDependency { Self() } public static var liveValue: EstimatedPressureDependency { .init( estimatedAirflow: { request in guard request.existingPressure.positiveValue > 0 else { throw LessThanZeroError.existingPressure } let value = request.existingAirflow.positiveValue * sqrt( request.targetPressure.positiveValue / request.existingPressure.positiveValue ) return .init(wrappedValue: value) }, estimatedPressure: { request in guard request.existingAirflow.positiveValue > 0 else { throw LessThanZeroError.existingAirflow } let value = pow( request.targetAirflow.positiveValue / request.existingAirflow.positiveValue, 2 ) * request.existingPressure.positiveValue return .init(wrappedValue: value) } ) } } fileprivate extension EquipmentMeasurement.AirHandler { init( airflow: Double? = nil, returnPlenumPressure: Positive, postFilterPressure: Positive, postCoilPressure: Positive, supplyPlenumPressure: Positive ) { self.init( airflow: airflow, returnPlenumPressure: returnPlenumPressure.positiveValue, postFilterPressure: postFilterPressure.positiveValue, postCoilPressure: postCoilPressure.positiveValue, supplyPlenumPressure: supplyPlenumPressure.positiveValue ) } } fileprivate extension EquipmentMeasurement.FurnaceAndCoil { init( airflow: Double? = nil, returnPlenumPressure: Positive, postFilterPressure: Positive, preCoilPressure: Positive, supplyPlenumPressure: Positive ) { self.init( airflow: airflow, returnPlenumPressure: returnPlenumPressure.positiveValue, postFilterPressure: postFilterPressure.positiveValue, preCoilPressure: preCoilPressure.positiveValue, supplyPlenumPressure: supplyPlenumPressure.positiveValue ) } } struct InvalidAirflow: Error { } enum LessThanZeroError: Error { case existingAirflow case existingPressure }