Files
swift-hvac-toolbox/Sources/ApiController/Extensions/HVACSystemPerformance.swift

89 lines
3.2 KiB
Swift

import Dependencies
import Foundation
import Logging
import PsychrometricClient
import Routes
public extension HVACSystemPerformance.Request {
// TODO: Check if we should use psychrometrics for the request conditions instead
private static let airDensity = 0.075 // lb/ft^3 at standard conditions.
private static let specificHeat = 0.24 // BTU/lb at standard conditions (imperial).
func respond(logger: Logger) async throws -> HVACSystemPerformance.Response {
@Dependency(\.psychrometricClient) var psychrometricClient
try validate()
let altitude = parseAltitude()
let temperatureSplit = returnAirTemperature - supplyAirTemperature
let returnAirProperties = try await psychrometricClient.psychrometricProperties(.dryBulb(
.init(.init(returnAirTemperature)),
relativeHumidity: returnAirHumidity%,
altitude: altitude
))
let supplyAirProperties = try await psychrometricClient.psychrometricProperties(.dryBulb(
.init(.init(supplyAirTemperature)),
relativeHumidity: supplyAirHumidity%,
altitude: altitude
))
let airMassFlow = airflow * 60 * Self.airDensity
logger.debug("Air mass flow: \(airMassFlow)")
let condensationRate = airMassFlow * (
returnAirProperties.humidityRatio.value - supplyAirProperties.humidityRatio.value
) // lb/hr
let sensibleCapacity = airMassFlow * Self.specificHeat * temperatureSplit
logger.debug("Return enthalpy: \(returnAirProperties.enthalpy.value)")
logger.debug("Supply enthalpy: \(supplyAirProperties.enthalpy.value)")
let deltaEnthalpy = returnAirProperties.enthalpy.value - supplyAirProperties.enthalpy.value
logger.debug("Delta enthalpy: \(deltaEnthalpy)")
let totalCapacity = airMassFlow * deltaEnthalpy
let capacity = HVACSystemPerformance.Capacity(
total: totalCapacity,
sensible: sensibleCapacity,
latent: totalCapacity - sensibleCapacity
)
let systemMetrics = HVACSystemPerformance.SystemMetrics(
cfmPerTon: airflow / systemSize,
targetTemperatureSplit: (systemSize * 12000 * 0.75) / (1.08 * airflow),
actualTemperatureSplit: temperatureSplit,
condensationRatePoundsPerHour: condensationRate
)
return .init(
returnAirProperties: returnAirProperties,
supplyAirProperties: supplyAirProperties,
capacity: capacity,
systemMetrics: systemMetrics
)
}
private func parseAltitude() -> Length {
guard let altitude, altitude > 0 else { return .seaLevel }
return .init(altitude)
}
private func validate() throws {
guard returnAirTemperature > 0 else {
throw ValidationError(message: "Return air temperature should be greater than 0.")
}
guard returnAirHumidity > 0 else {
throw ValidationError(message: "Return air humidity should be greater than 0.")
}
guard supplyAirTemperature > 0 else {
throw ValidationError(message: "Supply air temperature should be greater than 0.")
}
guard supplyAirHumidity > 0 else {
throw ValidationError(message: "Supply air humidity should be greater than 0.")
}
guard systemSize > 0 else {
throw ValidationError(message: "System size should be greater than 0.")
}
}
}