feat: Update room cooling load to accept either total or sensible loads, instead of requiring a total load.
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
import Foundation
|
||||
// import Foundation
|
||||
|
||||
public struct CoolingLoad: Codable, Equatable, Sendable {
|
||||
public let total: Double
|
||||
public let sensible: Double
|
||||
public var latent: Double { total - sensible }
|
||||
public var shr: Double { sensible / total }
|
||||
|
||||
public init(total: Double, sensible: Double) {
|
||||
self.total = total
|
||||
self.sensible = sensible
|
||||
}
|
||||
}
|
||||
//
|
||||
// public struct CoolingLoad: Codable, Equatable, Sendable {
|
||||
// public let total: Double
|
||||
// public let sensible: Double
|
||||
// public var latent: Double { total - sensible }
|
||||
// public var shr: Double { sensible / total }
|
||||
//
|
||||
// public init(total: Double, sensible: Double) {
|
||||
// self.total = total
|
||||
// self.sensible = sensible
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -152,11 +152,12 @@ extension DuctSizes {
|
||||
public static func mock(
|
||||
equipmentInfo: EquipmentInfo,
|
||||
rooms: [Room],
|
||||
trunks: [TrunkSize]
|
||||
trunks: [TrunkSize],
|
||||
shr: Double
|
||||
) -> Self {
|
||||
|
||||
let totalHeatingLoad = rooms.totalHeatingLoad
|
||||
let totalCoolingLoad = rooms.totalCoolingLoad
|
||||
let totalCoolingLoad = try! rooms.totalCoolingLoad(shr: shr)
|
||||
|
||||
let roomContainers = rooms.reduce(into: [RoomContainer]()) { array, room in
|
||||
array += RoomContainer.mock(
|
||||
@@ -164,7 +165,8 @@ extension DuctSizes {
|
||||
totalHeatingLoad: totalHeatingLoad,
|
||||
totalCoolingLoad: totalCoolingLoad,
|
||||
totalHeatingCFM: Double(equipmentInfo.heatingCFM),
|
||||
totalCoolingCFM: Double(equipmentInfo.coolingCFM)
|
||||
totalCoolingCFM: Double(equipmentInfo.coolingCFM),
|
||||
shr: shr
|
||||
)
|
||||
}
|
||||
|
||||
@@ -187,14 +189,15 @@ extension DuctSizes {
|
||||
totalHeatingLoad: Double,
|
||||
totalCoolingLoad: Double,
|
||||
totalHeatingCFM: Double,
|
||||
totalCoolingCFM: Double
|
||||
totalCoolingCFM: Double,
|
||||
shr: Double
|
||||
) -> [Self] {
|
||||
var retval = [DuctSizes.RoomContainer]()
|
||||
let heatingLoad = room.heatingLoad / Double(room.registerCount)
|
||||
let heatingFraction = heatingLoad / totalHeatingLoad
|
||||
let heatingCFM = totalHeatingCFM * heatingFraction
|
||||
// Not really accurate, but works for mocks.
|
||||
let coolingLoad = room.coolingTotal / Double(room.registerCount)
|
||||
let coolingLoad = (try! room.coolingLoad.ensured(shr: shr).total) / Double(room.registerCount)
|
||||
let coolingFraction = coolingLoad / totalCoolingLoad
|
||||
let coolingCFM = totalCoolingCFM * coolingFraction
|
||||
|
||||
|
||||
@@ -9,19 +9,19 @@ import Foundation
|
||||
public struct Room: Codable, Equatable, Identifiable, Sendable {
|
||||
/// The unique id of the room.
|
||||
public let id: UUID
|
||||
|
||||
/// The project this room is associated with.
|
||||
public let projectID: Project.ID
|
||||
|
||||
/// A unique name for the room in the project.
|
||||
public let name: String
|
||||
|
||||
/// The heating load required for the room (from Manual-J).
|
||||
public let heatingLoad: Double
|
||||
/// The total cooling load required for the room (from Manual-J).
|
||||
public let coolingTotal: Double
|
||||
/// An optional sensible cooling load for the room.
|
||||
///
|
||||
/// **NOTE:** This is generally not set, but calculated from the project wide
|
||||
/// sensible heat ratio.
|
||||
public let coolingSensible: Double?
|
||||
|
||||
/// The cooling load required for the room (from Manual-J).
|
||||
public let coolingLoad: CoolingLoad
|
||||
|
||||
/// The number of registers for the room.
|
||||
public let registerCount: Int
|
||||
/// The rectangular duct size calculations for a room.
|
||||
@@ -29,8 +29,10 @@ public struct Room: Codable, Equatable, Identifiable, Sendable {
|
||||
/// **NOTE:** These are optionally set after the round sizes have been calculate
|
||||
/// for a room.
|
||||
public let rectangularSizes: [RectangularSize]?
|
||||
|
||||
/// When the room was created in the database.
|
||||
public let createdAt: Date
|
||||
|
||||
/// When the room was updated in the database.
|
||||
public let updatedAt: Date
|
||||
|
||||
@@ -39,8 +41,7 @@ public struct Room: Codable, Equatable, Identifiable, Sendable {
|
||||
projectID: Project.ID,
|
||||
name: String,
|
||||
heatingLoad: Double,
|
||||
coolingTotal: Double,
|
||||
coolingSensible: Double? = nil,
|
||||
coolingLoad: CoolingLoad,
|
||||
registerCount: Int = 1,
|
||||
rectangularSizes: [RectangularSize]? = nil,
|
||||
createdAt: Date,
|
||||
@@ -50,13 +51,46 @@ public struct Room: Codable, Equatable, Identifiable, Sendable {
|
||||
self.projectID = projectID
|
||||
self.name = name
|
||||
self.heatingLoad = heatingLoad
|
||||
self.coolingTotal = coolingTotal
|
||||
self.coolingSensible = coolingSensible
|
||||
self.coolingLoad = coolingLoad
|
||||
self.registerCount = registerCount
|
||||
self.rectangularSizes = rectangularSizes
|
||||
self.createdAt = createdAt
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
|
||||
/// Represents the cooling load of a room.
|
||||
///
|
||||
/// Generally only one of the values is provided by a Manual-J room x room
|
||||
/// calculation.
|
||||
///
|
||||
public struct CoolingLoad: Codable, Equatable, Sendable {
|
||||
|
||||
public let total: Double?
|
||||
public let sensible: Double?
|
||||
|
||||
public init(total: Double? = nil, sensible: Double? = nil) {
|
||||
self.total = total
|
||||
self.sensible = sensible
|
||||
}
|
||||
|
||||
/// Calculates the cooling load based on the shr.
|
||||
///
|
||||
/// Generally Manual-J room x room loads provide either the total load or the
|
||||
/// sensible load, so this allows us to calculate whichever is not provided.
|
||||
public func ensured(shr: Double) throws -> (total: Double, sensible: Double) {
|
||||
switch (total, sensible) {
|
||||
case (.none, .none):
|
||||
throw CoolingLoadError("Both the total and sensible loads are nil.")
|
||||
case (.some(let total), .some(let sensible)):
|
||||
return (total, sensible)
|
||||
case (.some(let total), .none):
|
||||
return (total, total * shr)
|
||||
case (.none, .some(let sensible)):
|
||||
return (sensible / shr, sensible)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Room {
|
||||
@@ -69,17 +103,21 @@ extension Room {
|
||||
/// The heating load required for the room (from Manual-J).
|
||||
public let heatingLoad: Double
|
||||
/// The total cooling load required for the room (from Manual-J).
|
||||
public let coolingTotal: Double
|
||||
public let coolingTotal: Double?
|
||||
/// An optional sensible cooling load for the room.
|
||||
public let coolingSensible: Double?
|
||||
/// The number of registers for the room.
|
||||
public let registerCount: Int
|
||||
|
||||
public var coolingLoad: Room.CoolingLoad {
|
||||
.init(total: coolingTotal, sensible: coolingSensible)
|
||||
}
|
||||
|
||||
public init(
|
||||
projectID: Project.ID,
|
||||
name: String,
|
||||
heatingLoad: Double,
|
||||
coolingTotal: Double,
|
||||
coolingTotal: Double? = nil,
|
||||
coolingSensible: Double? = nil,
|
||||
registerCount: Int = 1
|
||||
) {
|
||||
@@ -118,7 +156,7 @@ extension Room {
|
||||
|
||||
/// Represents field that can be updated on a room after it's been created in the database.
|
||||
///
|
||||
/// Only fields that are supplied get updated.
|
||||
/// Onlly fields that are supplied get updated.
|
||||
public struct Update: Codable, Equatable, Sendable {
|
||||
/// A unique name for the room in the project.
|
||||
public let name: String?
|
||||
@@ -133,6 +171,13 @@ extension Room {
|
||||
/// The rectangular duct size calculations for a room.
|
||||
public let rectangularSizes: [RectangularSize]?
|
||||
|
||||
public var coolingLoad: CoolingLoad? {
|
||||
guard coolingTotal != nil || coolingSensible != nil else {
|
||||
return nil
|
||||
}
|
||||
return .init(total: coolingTotal, sensible: coolingSensible)
|
||||
}
|
||||
|
||||
public init(
|
||||
name: String? = nil,
|
||||
heatingLoad: Double? = nil,
|
||||
@@ -169,22 +214,30 @@ extension Array where Element == Room {
|
||||
}
|
||||
|
||||
/// The sum of total cooling loads for an array of rooms.
|
||||
public var totalCoolingLoad: Double {
|
||||
reduce(into: 0) { $0 += $1.coolingTotal }
|
||||
public func totalCoolingLoad(shr: Double) throws -> Double {
|
||||
try reduce(into: 0) { $0 += try $1.coolingLoad.ensured(shr: shr).total }
|
||||
}
|
||||
|
||||
/// The sum of sensible cooling loads for an array of rooms.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - shr: The project wide sensible heat ratio.
|
||||
public func totalCoolingSensible(shr: Double) -> Double {
|
||||
reduce(into: 0) {
|
||||
let sensible = $1.coolingSensible ?? ($1.coolingTotal * shr)
|
||||
$0 += sensible
|
||||
public func totalCoolingSensible(shr: Double) throws -> Double {
|
||||
try reduce(into: 0) {
|
||||
// let sensible = $1.coolingSensible ?? ($1.coolingTotal * shr)
|
||||
$0 += try $1.coolingLoad.ensured(shr: shr).sensible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct CoolingLoadError: Error, Equatable, Sendable {
|
||||
public let reason: String
|
||||
|
||||
public init(_ reason: String) {
|
||||
self.reason = reason
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
extension Room {
|
||||
@@ -199,8 +252,8 @@ extension Array where Element == Room {
|
||||
projectID: projectID,
|
||||
name: "Bed-1",
|
||||
heatingLoad: 3913,
|
||||
coolingTotal: 2472,
|
||||
coolingSensible: nil,
|
||||
coolingLoad: .init(total: 2472),
|
||||
// coolingSensible: nil,
|
||||
registerCount: 1,
|
||||
rectangularSizes: nil,
|
||||
createdAt: now,
|
||||
@@ -211,8 +264,8 @@ extension Array where Element == Room {
|
||||
projectID: projectID,
|
||||
name: "Entry",
|
||||
heatingLoad: 8284,
|
||||
coolingTotal: 2916,
|
||||
coolingSensible: nil,
|
||||
coolingLoad: .init(total: 2916),
|
||||
// coolingSensible: nil,
|
||||
registerCount: 2,
|
||||
rectangularSizes: nil,
|
||||
createdAt: now,
|
||||
@@ -223,8 +276,8 @@ extension Array where Element == Room {
|
||||
projectID: projectID,
|
||||
name: "Family Room",
|
||||
heatingLoad: 9785,
|
||||
coolingTotal: 7446,
|
||||
coolingSensible: nil,
|
||||
coolingLoad: .init(total: 7446),
|
||||
// coolingSensible: nil,
|
||||
registerCount: 3,
|
||||
rectangularSizes: nil,
|
||||
createdAt: now,
|
||||
@@ -235,8 +288,8 @@ extension Array where Element == Room {
|
||||
projectID: projectID,
|
||||
name: "Kitchen",
|
||||
heatingLoad: 4518,
|
||||
coolingTotal: 5096,
|
||||
coolingSensible: nil,
|
||||
coolingLoad: .init(total: 5096),
|
||||
// coolingSensible: nil,
|
||||
registerCount: 2,
|
||||
rectangularSizes: nil,
|
||||
createdAt: now,
|
||||
@@ -247,8 +300,8 @@ extension Array where Element == Room {
|
||||
projectID: projectID,
|
||||
name: "Living Room",
|
||||
heatingLoad: 7553,
|
||||
coolingTotal: 6829,
|
||||
coolingSensible: nil,
|
||||
coolingLoad: .init(total: 6829),
|
||||
// coolingSensible: nil,
|
||||
registerCount: 2,
|
||||
rectangularSizes: nil,
|
||||
createdAt: now,
|
||||
@@ -259,8 +312,8 @@ extension Array where Element == Room {
|
||||
projectID: projectID,
|
||||
name: "Master",
|
||||
heatingLoad: 8202,
|
||||
coolingTotal: 2076,
|
||||
coolingSensible: nil,
|
||||
coolingLoad: .init(total: 2076),
|
||||
// coolingSensible: nil,
|
||||
registerCount: 2,
|
||||
rectangularSizes: nil,
|
||||
createdAt: now,
|
||||
|
||||
@@ -218,9 +218,11 @@ extension SiteRoute.View.ProjectRoute {
|
||||
Field("projectID") { Project.ID.parser() }
|
||||
Field("name", .string)
|
||||
Field("heatingLoad") { Double.parser() }
|
||||
Field("coolingTotal") { Double.parser() }
|
||||
Optionally {
|
||||
Field("coolingSensible", default: nil) { Double.parser() }
|
||||
Field("coolingTotal") { Double.parser() }
|
||||
}
|
||||
Optionally {
|
||||
Field("coolingSensible") { Double.parser() }
|
||||
}
|
||||
Field("registerCount") { Digits() }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user