feat: Adds attic ventilation calculation.
This commit is contained in:
@@ -28,6 +28,10 @@ extension ApiController: DependencyKey {
|
||||
logger.debug("API Route: \(route)")
|
||||
|
||||
switch route {
|
||||
case let .calculateAtticVentilation(request):
|
||||
logger.debug("Calculating attic ventilation: \(request)")
|
||||
return try await request.respond(logger: logger)
|
||||
|
||||
case let .calculateCapacitor(request):
|
||||
logger.debug("Calculating capacitor: \(request)")
|
||||
return try await request.respond(logger: logger)
|
||||
|
||||
165
Sources/ApiController/Extensions/AtticVentilation.swift
Normal file
165
Sources/ApiController/Extensions/AtticVentilation.swift
Normal file
@@ -0,0 +1,165 @@
|
||||
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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user