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.") } } struct ValidationError: Error { let message: String } }