import Foundation import Logging import OrderedCollections import Routes public extension AtticVentilation.Request { private static let stackEffectCoefficient = 0.0188 private static let windEffectCoefficient = 0.0299 private static let minimumVentRatio = 1.0 / 300.0 private static let recommendedVentRatio = 1.0 / 150 private static let intakeToExhaustRatio = 1.5 // swiftlint:disable function_body_length func respond(logger: Logger) async throws -> AtticVentilation.Response { try validate() var recommendations = [String]() var warnings = [String]() var ventilationStatus: AtticVentilation.VentilationStatus = .adequate let temperatureDifference = checkTemperatureDifferential( ventilationStatus: &ventilationStatus, warnings: &warnings ) let dewpointDifference = atticDewpoint - outdoorDewpoint let stackEffect = Self.stackEffectCoefficient * atticFloorArea * sqrt(abs(temperatureDifference)) * (temperatureDifference < 0 ? -1 : 1) let minimumVentArea = atticFloorArea * Self.minimumVentRatio let recommendVentArea = atticFloorArea * Self.recommendedVentRatio let requiredIntake = (recommendVentArea * Self.intakeToExhaustRatio) / (1 + Self.intakeToExhaustRatio) let requiredExhaust = recommendVentArea - requiredIntake if abs(pressureDifferential) > 2 { warnings.append( "High pressure differential - may indicate blocked or inadequate ventilation" ) if ventilationStatus != .critical { ventilationStatus = .inadequate } } if abs(dewpointDifference) > 5 { warnings.append( "High moisture levels in the attic - increased ventilation is recommended" ) if ventilationStatus != .critical { ventilationStatus = .inadequate } } // compare existing ventilation to requirements. checkExistingAreas( minimumVentArea: minimumVentArea, recommendVentArea: recommendVentArea, requiredIntake: requiredIntake, requiredExhaust: requiredExhaust, recommendations: &recommendations, ventilationStatus: &ventilationStatus, warnings: &warnings ) // Pressure recommendations addPressureRecommendations(&recommendations) return .init( pressureDifferential: pressureDifferential, temperatureDifferential: temperatureDifference, dewpointDifferential: dewpointDifference, stackEffect: stackEffect, requiredVentilationArea: .init(intake: requiredIntake, exhaust: requiredExhaust), recommendations: recommendations, warnings: warnings, ventilationStatus: ventilationStatus ) } // swiftlint:enable function_body_length private func checkTemperatureDifferential( ventilationStatus: inout AtticVentilation.VentilationStatus, warnings: inout [String] ) -> Double { let temperatureDifference = atticTemperature - outdoorTemperature let absTempDiff = abs(temperatureDifference) if absTempDiff > 20 { warnings.append( "High temperature differential - indicates insufficient ventilation." ) ventilationStatus = .inadequate } if absTempDiff > 30 { warnings.append( "Critical temperature differential - immediate action recommended" ) ventilationStatus = .critical } return temperatureDifference } // swiftlint:disable function_parameter_count private func checkExistingAreas( minimumVentArea: Double, recommendVentArea: Double, requiredIntake: Double, requiredExhaust: Double, recommendations: inout [String], ventilationStatus: inout AtticVentilation.VentilationStatus, warnings: inout [String] ) { if let existingIntakeArea, let existingExhaustArea { let totalExisting = existingIntakeArea + existingExhaustArea if totalExisting < minimumVentArea { warnings.append("Existing ventilation area below minimum requirement.") if ventilationStatus != .critical { ventilationStatus = .inadequate } } else if totalExisting < recommendVentArea { recommendations.append("Consider increasing ventilation area to meet recommended guidelines.") } if existingIntakeArea < requiredIntake { recommendations.append( "Add \(double: requiredIntake - existingIntakeArea, fractionDigits: 1) ft² of intake ventilation." ) } if existingExhaustArea < requiredExhaust { recommendations.append( "Add \(double: requiredExhaust - existingExhaustArea, fractionDigits: 1) ft² of exhaust ventilation." ) } } else { recommendations.append( "Add \(double: requiredIntake, fractionDigits: 1) ft² of intake ventilation." ) recommendations.append( "Add \(double: requiredExhaust, fractionDigits: 1) ft² of exhaust ventilation." ) } } // swiftlint:enable function_parameter_count private func addPressureRecommendations(_ recommendations: inout [String]) { if pressureDifferential > 0 { recommendations.append("Consider adding more exhaust ventilation to balance pressure.") } else if pressureDifferential < 0 { recommendations.append("Consider adding more intake ventilation to balance pressure.") } } private func validate() throws { guard atticFloorArea > 0 else { throw ValidationError(message: "Attic floor area should be greater than 0.") } if let existingIntakeArea, existingIntakeArea < 0 { throw ValidationError(message: "Existing intake area should be greater than 0.") } if let existingExhaustArea, existingExhaustArea < 0 { throw ValidationError(message: "Existing exhaust area should be greater than 0.") } } }