WIP: Attempt at breaking out some logic / middleware between database and view layer, to remove some code from the view controller. Not complete, maybe revert.
This commit is contained in:
@@ -8,6 +8,7 @@ let package = Package(
|
||||
.executable(name: "App", targets: ["App"]),
|
||||
.library(name: "ApiController", targets: ["ApiController"]),
|
||||
.library(name: "DatabaseClient", targets: ["DatabaseClient"]),
|
||||
.library(name: "ProjectClient", targets: ["ProjectClient"]),
|
||||
.library(name: "ManualDCore", targets: ["ManualDCore"]),
|
||||
.library(name: "ManualDClient", targets: ["ManualDClient"]),
|
||||
.library(name: "Styleguide", targets: ["Styleguide"]),
|
||||
@@ -63,6 +64,13 @@ let package = Package(
|
||||
.product(name: "Vapor", package: "vapor"),
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "ProjectClient",
|
||||
dependencies: [
|
||||
.target(name: "DatabaseClient"),
|
||||
.target(name: "ManualDClient"),
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "ManualDCore",
|
||||
dependencies: [
|
||||
@@ -105,6 +113,7 @@ let package = Package(
|
||||
name: "ViewController",
|
||||
dependencies: [
|
||||
.target(name: "DatabaseClient"),
|
||||
.target(name: "ProjectClient"),
|
||||
.target(name: "ManualDClient"),
|
||||
.target(name: "ManualDCore"),
|
||||
.target(name: "Styleguide"),
|
||||
|
||||
@@ -73,10 +73,16 @@ extension DatabaseClient.Projects: TestDependencyKey {
|
||||
)
|
||||
},
|
||||
getSensibleHeatRatio: { id in
|
||||
guard let model = try await ProjectModel.find(id, on: database) else {
|
||||
guard
|
||||
let shr = try await ProjectModel.query(on: database)
|
||||
.field(\.$id)
|
||||
.field(\.$sensibleHeatRatio)
|
||||
.filter(\.$id == id)
|
||||
.first()
|
||||
else {
|
||||
throw NotFoundError()
|
||||
}
|
||||
return model.sensibleHeatRatio
|
||||
return shr.sensibleHeatRatio
|
||||
},
|
||||
fetch: { userID, request in
|
||||
try await ProjectModel.query(on: database)
|
||||
|
||||
@@ -3,6 +3,13 @@ import DependenciesMacros
|
||||
import Logging
|
||||
import ManualDCore
|
||||
|
||||
extension DependencyValues {
|
||||
public var manualD: ManualDClient {
|
||||
get { self[ManualDClient.self] }
|
||||
set { self[ManualDClient.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
@DependencyClient
|
||||
public struct ManualDClient: Sendable {
|
||||
public var ductSize: @Sendable (DuctSizeRequest) async throws -> DuctSizeResponse
|
||||
@@ -11,144 +18,155 @@ public struct ManualDClient: Sendable {
|
||||
public var equivalentRectangularDuct:
|
||||
@Sendable (EquivalentRectangularDuctRequest) async throws -> EquivalentRectangularDuctResponse
|
||||
|
||||
public func calculateSizes(
|
||||
rooms: [Room],
|
||||
trunks: [DuctSizing.TrunkSize],
|
||||
equipmentInfo: EquipmentInfo,
|
||||
maxSupplyLength: EffectiveLength,
|
||||
maxReturnLength: EffectiveLength,
|
||||
designFrictionRate: Double,
|
||||
projectSHR: Double,
|
||||
logger: Logger? = nil
|
||||
) async throws -> (rooms: [DuctSizing.RoomContainer], trunks: [DuctSizing.TrunkContainer]) {
|
||||
try await (
|
||||
calculateSizes(
|
||||
rooms: rooms, equipmentInfo: equipmentInfo,
|
||||
maxSupplyLength: maxSupplyLength, maxReturnLength: maxReturnLength,
|
||||
designFrictionRate: designFrictionRate, projectSHR: projectSHR
|
||||
),
|
||||
calculateSizes(
|
||||
rooms: rooms, trunks: trunks, equipmentInfo: equipmentInfo,
|
||||
maxSupplyLength: maxSupplyLength, maxReturnLength: maxReturnLength,
|
||||
designFrictionRate: designFrictionRate, projectSHR: projectSHR)
|
||||
)
|
||||
}
|
||||
// TODO: add (Project.ID) async throws -> ProjectResponse
|
||||
|
||||
func calculateSizes(
|
||||
rooms: [Room],
|
||||
equipmentInfo: EquipmentInfo,
|
||||
maxSupplyLength: EffectiveLength,
|
||||
maxReturnLength: EffectiveLength,
|
||||
designFrictionRate: Double,
|
||||
projectSHR: Double,
|
||||
logger: Logger? = nil
|
||||
) async throws -> [DuctSizing.RoomContainer] {
|
||||
|
||||
var retval: [DuctSizing.RoomContainer] = []
|
||||
let totalHeatingLoad = rooms.totalHeatingLoad
|
||||
let totalCoolingSensible = rooms.totalCoolingSensible(shr: projectSHR)
|
||||
|
||||
for room in rooms {
|
||||
let heatingLoad = room.heatingLoadPerRegister
|
||||
let coolingLoad = room.coolingSensiblePerRegister(projectSHR: projectSHR)
|
||||
let heatingPercent = heatingLoad / totalHeatingLoad
|
||||
let coolingPercent = coolingLoad / totalCoolingSensible
|
||||
let heatingCFM = heatingPercent * Double(equipmentInfo.heatingCFM)
|
||||
let coolingCFM = coolingPercent * Double(equipmentInfo.coolingCFM)
|
||||
let designCFM = DuctSizing.DesignCFM(heating: heatingCFM, cooling: coolingCFM)
|
||||
let sizes = try await self.ductSize(
|
||||
.init(designCFM: Int(designCFM.value), frictionRate: designFrictionRate)
|
||||
)
|
||||
|
||||
for n in 1...room.registerCount {
|
||||
|
||||
var rectangularWidth: Int? = nil
|
||||
let rectangularSize = room.rectangularSizes?
|
||||
.first(where: { $0.register == nil || $0.register == n })
|
||||
|
||||
if let rectangularSize {
|
||||
let response = try await self.equivalentRectangularDuct(
|
||||
.init(round: sizes.finalSize, height: rectangularSize.height)
|
||||
)
|
||||
rectangularWidth = response.width
|
||||
}
|
||||
|
||||
retval.append(
|
||||
.init(
|
||||
roomID: room.id,
|
||||
roomName: "\(room.name)-\(n)",
|
||||
roomRegister: n,
|
||||
heatingLoad: heatingLoad,
|
||||
coolingLoad: coolingLoad,
|
||||
heatingCFM: heatingCFM,
|
||||
coolingCFM: coolingCFM,
|
||||
designCFM: designCFM,
|
||||
roundSize: sizes.ductulatorSize,
|
||||
finalSize: sizes.finalSize,
|
||||
velocity: sizes.velocity,
|
||||
flexSize: sizes.flexSize,
|
||||
rectangularSize: rectangularSize,
|
||||
rectangularWidth: rectangularWidth
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
func calculateSizes(
|
||||
rooms: [Room],
|
||||
trunks: [DuctSizing.TrunkSize],
|
||||
equipmentInfo: EquipmentInfo,
|
||||
maxSupplyLength: EffectiveLength,
|
||||
maxReturnLength: EffectiveLength,
|
||||
designFrictionRate: Double,
|
||||
projectSHR: Double,
|
||||
logger: Logger? = nil
|
||||
) async throws -> [DuctSizing.TrunkContainer] {
|
||||
|
||||
var retval = [DuctSizing.TrunkContainer]()
|
||||
let totalHeatingLoad = rooms.totalHeatingLoad
|
||||
let totalCoolingSensible = rooms.totalCoolingSensible(shr: projectSHR)
|
||||
|
||||
for trunk in trunks {
|
||||
let heatingLoad = trunk.totalHeatingLoad
|
||||
let coolingLoad = trunk.totalCoolingSensible(projectSHR: projectSHR)
|
||||
let heatingPercent = heatingLoad / totalHeatingLoad
|
||||
let coolingPercent = coolingLoad / totalCoolingSensible
|
||||
let heatingCFM = heatingPercent * Double(equipmentInfo.heatingCFM)
|
||||
let coolingCFM = coolingPercent * Double(equipmentInfo.coolingCFM)
|
||||
let designCFM = DuctSizing.DesignCFM(heating: heatingCFM, cooling: coolingCFM)
|
||||
let sizes = try await self.ductSize(
|
||||
.init(designCFM: Int(designCFM.value), frictionRate: designFrictionRate)
|
||||
)
|
||||
var width: Int? = nil
|
||||
if let height = trunk.height {
|
||||
let rectangularSize = try await self.equivalentRectangularDuct(
|
||||
.init(round: sizes.finalSize, height: height)
|
||||
)
|
||||
width = rectangularSize.width
|
||||
}
|
||||
|
||||
retval.append(
|
||||
.init(
|
||||
trunk: trunk,
|
||||
ductSize: .init(
|
||||
designCFM: designCFM,
|
||||
roundSize: sizes.ductulatorSize,
|
||||
finalSize: sizes.finalSize,
|
||||
velocity: sizes.velocity,
|
||||
flexSize: sizes.flexSize,
|
||||
height: trunk.height,
|
||||
width: width
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return retval
|
||||
}
|
||||
// TODO: Move to helpers.
|
||||
// public func calculateSizes(
|
||||
// rooms: [Room],
|
||||
// trunks: [DuctSizing.TrunkSize],
|
||||
// equipmentInfo: EquipmentInfo,
|
||||
// maxSupplyLength: EffectiveLength,
|
||||
// maxReturnLength: EffectiveLength,
|
||||
// designFrictionRate: Double,
|
||||
// projectSHR: Double,
|
||||
// logger: Logger? = nil
|
||||
// ) async throws -> ProjectResponse {
|
||||
// try await .init(
|
||||
// rooms: calculateRoomSizes(
|
||||
// rooms: rooms,
|
||||
// equipmentInfo: equipmentInfo,
|
||||
// maxSupplyLength: maxSupplyLength,
|
||||
// maxReturnLength: maxReturnLength,
|
||||
// designFrictionRate: designFrictionRate,
|
||||
// projectSHR: projectSHR
|
||||
// ),
|
||||
// trunks: calculateTrunkSizes(
|
||||
// rooms: rooms,
|
||||
// trunks: trunks,
|
||||
// equipmentInfo: equipmentInfo,
|
||||
// maxSupplyLength: maxSupplyLength,
|
||||
// maxReturnLength: maxReturnLength,
|
||||
// designFrictionRate: designFrictionRate,
|
||||
// projectSHR: projectSHR
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func calculateRoomSizes(
|
||||
// rooms: [Room],
|
||||
// equipmentInfo: EquipmentInfo,
|
||||
// maxSupplyLength: EffectiveLength,
|
||||
// maxReturnLength: EffectiveLength,
|
||||
// designFrictionRate: Double,
|
||||
// projectSHR: Double,
|
||||
// logger: Logger? = nil
|
||||
// ) async throws -> [DuctSizing.RoomContainer] {
|
||||
//
|
||||
// var retval: [DuctSizing.RoomContainer] = []
|
||||
// let totalHeatingLoad = rooms.totalHeatingLoad
|
||||
// let totalCoolingSensible = rooms.totalCoolingSensible(shr: projectSHR)
|
||||
//
|
||||
// for room in rooms {
|
||||
// let heatingLoad = room.heatingLoadPerRegister
|
||||
// let coolingLoad = room.coolingSensiblePerRegister(projectSHR: projectSHR)
|
||||
// let heatingPercent = heatingLoad / totalHeatingLoad
|
||||
// let coolingPercent = coolingLoad / totalCoolingSensible
|
||||
// let heatingCFM = heatingPercent * Double(equipmentInfo.heatingCFM)
|
||||
// let coolingCFM = coolingPercent * Double(equipmentInfo.coolingCFM)
|
||||
// let designCFM = DuctSizing.DesignCFM(heating: heatingCFM, cooling: coolingCFM)
|
||||
// let sizes = try await self.ductSize(
|
||||
// .init(designCFM: Int(designCFM.value), frictionRate: designFrictionRate)
|
||||
// )
|
||||
//
|
||||
// for n in 1...room.registerCount {
|
||||
//
|
||||
// var rectangularWidth: Int? = nil
|
||||
// let rectangularSize = room.rectangularSizes?
|
||||
// .first(where: { $0.register == nil || $0.register == n })
|
||||
//
|
||||
// if let rectangularSize {
|
||||
// let response = try await self.equivalentRectangularDuct(
|
||||
// .init(round: sizes.finalSize, height: rectangularSize.height)
|
||||
// )
|
||||
// rectangularWidth = response.width
|
||||
// }
|
||||
//
|
||||
// retval.append(
|
||||
// .init(
|
||||
// roomID: room.id,
|
||||
// roomName: "\(room.name)-\(n)",
|
||||
// roomRegister: n,
|
||||
// heatingLoad: heatingLoad,
|
||||
// coolingLoad: coolingLoad,
|
||||
// heatingCFM: heatingCFM,
|
||||
// coolingCFM: coolingCFM,
|
||||
// designCFM: designCFM,
|
||||
// roundSize: sizes.ductulatorSize,
|
||||
// finalSize: sizes.finalSize,
|
||||
// velocity: sizes.velocity,
|
||||
// flexSize: sizes.flexSize,
|
||||
// rectangularSize: rectangularSize,
|
||||
// rectangularWidth: rectangularWidth
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return retval
|
||||
// }
|
||||
//
|
||||
// func calculateTrunkSizes(
|
||||
// rooms: [Room],
|
||||
// trunks: [DuctSizing.TrunkSize],
|
||||
// equipmentInfo: EquipmentInfo,
|
||||
// maxSupplyLength: EffectiveLength,
|
||||
// maxReturnLength: EffectiveLength,
|
||||
// designFrictionRate: Double,
|
||||
// projectSHR: Double,
|
||||
// logger: Logger? = nil
|
||||
// ) async throws -> [DuctSizing.TrunkContainer] {
|
||||
//
|
||||
// var retval = [DuctSizing.TrunkContainer]()
|
||||
// let totalHeatingLoad = rooms.totalHeatingLoad
|
||||
// let totalCoolingSensible = rooms.totalCoolingSensible(shr: projectSHR)
|
||||
//
|
||||
// for trunk in trunks {
|
||||
// let heatingLoad = trunk.totalHeatingLoad
|
||||
// let coolingLoad = trunk.totalCoolingSensible(projectSHR: projectSHR)
|
||||
// let heatingPercent = heatingLoad / totalHeatingLoad
|
||||
// let coolingPercent = coolingLoad / totalCoolingSensible
|
||||
// let heatingCFM = heatingPercent * Double(equipmentInfo.heatingCFM)
|
||||
// let coolingCFM = coolingPercent * Double(equipmentInfo.coolingCFM)
|
||||
// let designCFM = DuctSizing.DesignCFM(heating: heatingCFM, cooling: coolingCFM)
|
||||
// let sizes = try await self.ductSize(
|
||||
// .init(designCFM: Int(designCFM.value), frictionRate: designFrictionRate)
|
||||
// )
|
||||
// var width: Int? = nil
|
||||
// if let height = trunk.height {
|
||||
// let rectangularSize = try await self.equivalentRectangularDuct(
|
||||
// .init(round: sizes.finalSize, height: height)
|
||||
// )
|
||||
// width = rectangularSize.width
|
||||
// }
|
||||
//
|
||||
// retval.append(
|
||||
// .init(
|
||||
// trunk: trunk,
|
||||
// ductSize: .init(
|
||||
// designCFM: designCFM,
|
||||
// roundSize: sizes.ductulatorSize,
|
||||
// finalSize: sizes.finalSize,
|
||||
// velocity: sizes.velocity,
|
||||
// flexSize: sizes.flexSize,
|
||||
// height: trunk.height,
|
||||
// width: width
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// return retval
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@@ -156,15 +174,27 @@ extension ManualDClient: TestDependencyKey {
|
||||
public static let testValue = Self()
|
||||
}
|
||||
|
||||
extension DependencyValues {
|
||||
public var manualD: ManualDClient {
|
||||
get { self[ManualDClient.self] }
|
||||
set { self[ManualDClient.self] = newValue }
|
||||
}
|
||||
}
|
||||
// MARK: Project Response
|
||||
// extension ManualDClient {
|
||||
//
|
||||
// public struct ProjectResponse: Codable, Equatable, Sendable {
|
||||
// public let rooms: [DuctSizing.RoomContainer]
|
||||
// public let trunks: [DuctSizing.TrunkContainer]
|
||||
//
|
||||
// public init(
|
||||
// rooms: [DuctSizing.RoomContainer],
|
||||
// trunks: [DuctSizing.TrunkContainer]
|
||||
// ) {
|
||||
// self.rooms = rooms
|
||||
// self.trunks = trunks
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
// MARK: Duct Size
|
||||
extension ManualDClient {
|
||||
|
||||
public struct DuctSizeRequest: Codable, Equatable, Sendable {
|
||||
public let designCFM: Int
|
||||
public let frictionRate: Double
|
||||
|
||||
9
Sources/ProjectClient/DuctCalcClientError.swift
Normal file
9
Sources/ProjectClient/DuctCalcClientError.swift
Normal file
@@ -0,0 +1,9 @@
|
||||
import Foundation
|
||||
|
||||
public struct DuctCalcClientError: Error {
|
||||
public let reason: String
|
||||
|
||||
public init(_ reason: String) {
|
||||
self.reason = reason
|
||||
}
|
||||
}
|
||||
35
Sources/ProjectClient/Interface.swift
Normal file
35
Sources/ProjectClient/Interface.swift
Normal file
@@ -0,0 +1,35 @@
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import ManualDCore
|
||||
|
||||
extension DependencyValues {
|
||||
public var projectClient: ProjectClient {
|
||||
get { self[ProjectClient.self] }
|
||||
set { self[ProjectClient.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
@DependencyClient
|
||||
public struct ProjectClient: Sendable {
|
||||
public var calculateDuctSizes: @Sendable (Project.ID) async throws -> ProjectResponse
|
||||
}
|
||||
|
||||
extension ProjectClient: TestDependencyKey {
|
||||
public static let testValue = Self()
|
||||
}
|
||||
|
||||
extension ProjectClient {
|
||||
|
||||
public struct ProjectResponse: Codable, Equatable, Sendable {
|
||||
public let rooms: [DuctSizing.RoomContainer]
|
||||
public let trunks: [DuctSizing.TrunkContainer]
|
||||
|
||||
public init(
|
||||
rooms: [DuctSizing.RoomContainer],
|
||||
trunks: [DuctSizing.TrunkContainer]
|
||||
) {
|
||||
self.rooms = rooms
|
||||
self.trunks = trunks
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import DatabaseClient
|
||||
import Dependencies
|
||||
import ManualDClient
|
||||
import ManualDCore
|
||||
|
||||
extension DatabaseClient {
|
||||
|
||||
func calculateDuctSizes(
|
||||
projectID: Project.ID
|
||||
) async throws -> ProjectClient.ProjectResponse {
|
||||
@Dependency(\.manualD) var manualD
|
||||
|
||||
guard let dfrResponse = try await designFrictionRate(projectID: projectID) else {
|
||||
throw DuctCalcClientError("Project not complete.")
|
||||
}
|
||||
|
||||
let ensuredTEL = try dfrResponse.ensureMaxContainer()
|
||||
|
||||
return try await manualD.calculateDuctSizes(
|
||||
rooms: rooms.fetch(projectID),
|
||||
trunks: trunkSizes.fetch(projectID),
|
||||
equipmentInfo: dfrResponse.equipmentInfo,
|
||||
maxSupplyLength: ensuredTEL.supply,
|
||||
maxReturnLength: ensuredTEL.return,
|
||||
designFrictionRate: dfrResponse.designFrictionRate,
|
||||
projectSHR: ensuredSHR(projectID)
|
||||
)
|
||||
}
|
||||
|
||||
// Fetches the project sensible heat ratio or throws an error if it's nil.
|
||||
func ensuredSHR(_ projectID: Project.ID) async throws -> Double {
|
||||
guard let projectSHR = try await projects.getSensibleHeatRatio(projectID) else {
|
||||
throw DuctCalcClientError("Project sensible heat ratio not set.")
|
||||
}
|
||||
return projectSHR
|
||||
}
|
||||
|
||||
// Internal container.
|
||||
struct DesignFrictionRateResponse: Equatable, Sendable {
|
||||
|
||||
typealias EnsuredTEL = (supply: EffectiveLength, return: EffectiveLength)
|
||||
|
||||
let designFrictionRate: Double
|
||||
let equipmentInfo: EquipmentInfo
|
||||
let telMaxContainer: EffectiveLength.MaxContainer
|
||||
|
||||
func ensureMaxContainer() throws -> EnsuredTEL {
|
||||
|
||||
guard let maxSupplyLength = telMaxContainer.supply else {
|
||||
throw DuctCalcClientError("Max supply TEL not found")
|
||||
}
|
||||
guard let maxReturnLength = telMaxContainer.return else {
|
||||
throw DuctCalcClientError("Max supply TEL not found")
|
||||
}
|
||||
|
||||
return (maxSupplyLength, maxReturnLength)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func designFrictionRate(
|
||||
projectID: Project.ID
|
||||
) async throws -> DesignFrictionRateResponse? {
|
||||
guard let equipmentInfo = try await equipment.fetch(projectID) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let equivalentLengths = try await effectiveLength.fetchMax(projectID)
|
||||
guard let tel = equivalentLengths.total else { return nil }
|
||||
|
||||
let componentLosses = try await componentLoss.fetch(projectID)
|
||||
guard componentLosses.count > 0 else { return nil }
|
||||
|
||||
let availableStaticPressure =
|
||||
equipmentInfo.staticPressure - componentLosses.total
|
||||
|
||||
return .init(
|
||||
designFrictionRate: (availableStaticPressure * 100) / tel,
|
||||
equipmentInfo: equipmentInfo,
|
||||
telMaxContainer: equivalentLengths
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
import Logging
|
||||
import ManualDClient
|
||||
import ManualDCore
|
||||
|
||||
// TODO: Remove Logger and use depedency logger.
|
||||
|
||||
extension ManualDClient {
|
||||
|
||||
func calculateDuctSizes(
|
||||
rooms: [Room],
|
||||
trunks: [DuctSizing.TrunkSize],
|
||||
equipmentInfo: EquipmentInfo,
|
||||
maxSupplyLength: EffectiveLength,
|
||||
maxReturnLength: EffectiveLength,
|
||||
designFrictionRate: Double,
|
||||
projectSHR: Double,
|
||||
logger: Logger? = nil
|
||||
) async throws -> ProjectClient.ProjectResponse {
|
||||
try await .init(
|
||||
rooms: calculateRoomSizes(
|
||||
rooms: rooms,
|
||||
equipmentInfo: equipmentInfo,
|
||||
maxSupplyLength: maxSupplyLength,
|
||||
maxReturnLength: maxReturnLength,
|
||||
designFrictionRate: designFrictionRate,
|
||||
projectSHR: projectSHR
|
||||
),
|
||||
trunks: calculateTrunkSizes(
|
||||
rooms: rooms,
|
||||
trunks: trunks,
|
||||
equipmentInfo: equipmentInfo,
|
||||
maxSupplyLength: maxSupplyLength,
|
||||
maxReturnLength: maxReturnLength,
|
||||
designFrictionRate: designFrictionRate,
|
||||
projectSHR: projectSHR
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func calculateRoomSizes(
|
||||
rooms: [Room],
|
||||
equipmentInfo: EquipmentInfo,
|
||||
maxSupplyLength: EffectiveLength,
|
||||
maxReturnLength: EffectiveLength,
|
||||
designFrictionRate: Double,
|
||||
projectSHR: Double,
|
||||
logger: Logger? = nil
|
||||
) async throws -> [DuctSizing.RoomContainer] {
|
||||
|
||||
var retval: [DuctSizing.RoomContainer] = []
|
||||
let totalHeatingLoad = rooms.totalHeatingLoad
|
||||
let totalCoolingSensible = rooms.totalCoolingSensible(shr: projectSHR)
|
||||
|
||||
for room in rooms {
|
||||
let heatingLoad = room.heatingLoadPerRegister
|
||||
let coolingLoad = room.coolingSensiblePerRegister(projectSHR: projectSHR)
|
||||
let heatingPercent = heatingLoad / totalHeatingLoad
|
||||
let coolingPercent = coolingLoad / totalCoolingSensible
|
||||
let heatingCFM = heatingPercent * Double(equipmentInfo.heatingCFM)
|
||||
let coolingCFM = coolingPercent * Double(equipmentInfo.coolingCFM)
|
||||
let designCFM = DuctSizing.DesignCFM(heating: heatingCFM, cooling: coolingCFM)
|
||||
let sizes = try await self.ductSize(
|
||||
.init(designCFM: Int(designCFM.value), frictionRate: designFrictionRate)
|
||||
)
|
||||
|
||||
for n in 1...room.registerCount {
|
||||
|
||||
var rectangularWidth: Int? = nil
|
||||
let rectangularSize = room.rectangularSizes?
|
||||
.first(where: { $0.register == nil || $0.register == n })
|
||||
|
||||
if let rectangularSize {
|
||||
let response = try await self.equivalentRectangularDuct(
|
||||
.init(round: sizes.finalSize, height: rectangularSize.height)
|
||||
)
|
||||
rectangularWidth = response.width
|
||||
}
|
||||
|
||||
retval.append(
|
||||
.init(
|
||||
roomID: room.id,
|
||||
roomName: "\(room.name)-\(n)",
|
||||
roomRegister: n,
|
||||
heatingLoad: heatingLoad,
|
||||
coolingLoad: coolingLoad,
|
||||
heatingCFM: heatingCFM,
|
||||
coolingCFM: coolingCFM,
|
||||
designCFM: designCFM,
|
||||
roundSize: sizes.ductulatorSize,
|
||||
finalSize: sizes.finalSize,
|
||||
velocity: sizes.velocity,
|
||||
flexSize: sizes.flexSize,
|
||||
rectangularSize: rectangularSize,
|
||||
rectangularWidth: rectangularWidth
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
func calculateTrunkSizes(
|
||||
rooms: [Room],
|
||||
trunks: [DuctSizing.TrunkSize],
|
||||
equipmentInfo: EquipmentInfo,
|
||||
maxSupplyLength: EffectiveLength,
|
||||
maxReturnLength: EffectiveLength,
|
||||
designFrictionRate: Double,
|
||||
projectSHR: Double,
|
||||
logger: Logger? = nil
|
||||
) async throws -> [DuctSizing.TrunkContainer] {
|
||||
|
||||
var retval = [DuctSizing.TrunkContainer]()
|
||||
let totalHeatingLoad = rooms.totalHeatingLoad
|
||||
let totalCoolingSensible = rooms.totalCoolingSensible(shr: projectSHR)
|
||||
|
||||
for trunk in trunks {
|
||||
let heatingLoad = trunk.totalHeatingLoad
|
||||
let coolingLoad = trunk.totalCoolingSensible(projectSHR: projectSHR)
|
||||
let heatingPercent = heatingLoad / totalHeatingLoad
|
||||
let coolingPercent = coolingLoad / totalCoolingSensible
|
||||
let heatingCFM = heatingPercent * Double(equipmentInfo.heatingCFM)
|
||||
let coolingCFM = coolingPercent * Double(equipmentInfo.coolingCFM)
|
||||
let designCFM = DuctSizing.DesignCFM(heating: heatingCFM, cooling: coolingCFM)
|
||||
let sizes = try await self.ductSize(
|
||||
.init(designCFM: Int(designCFM.value), frictionRate: designFrictionRate)
|
||||
)
|
||||
var width: Int? = nil
|
||||
if let height = trunk.height {
|
||||
let rectangularSize = try await self.equivalentRectangularDuct(
|
||||
.init(round: sizes.finalSize, height: height)
|
||||
)
|
||||
width = rectangularSize.width
|
||||
}
|
||||
|
||||
retval.append(
|
||||
.init(
|
||||
trunk: trunk,
|
||||
ductSize: .init(
|
||||
designCFM: designCFM,
|
||||
roundSize: sizes.ductulatorSize,
|
||||
finalSize: sizes.finalSize,
|
||||
velocity: sizes.velocity,
|
||||
flexSize: sizes.flexSize,
|
||||
height: trunk.height,
|
||||
width: width
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Room {
|
||||
|
||||
var heatingLoadPerRegister: Double {
|
||||
|
||||
heatingLoad / Double(registerCount)
|
||||
}
|
||||
|
||||
func coolingSensiblePerRegister(projectSHR: Double) -> Double {
|
||||
let sensible = coolingSensible ?? (coolingTotal * projectSHR)
|
||||
return sensible / Double(registerCount)
|
||||
}
|
||||
}
|
||||
|
||||
extension DuctSizing.TrunkSize.RoomProxy {
|
||||
|
||||
// We need to make sure if registers got removed after a trunk
|
||||
// was already made / saved that we do not include registers that
|
||||
// no longer exist.
|
||||
private var actualRegisterCount: Int {
|
||||
guard registers.count <= room.registerCount else {
|
||||
return room.registerCount
|
||||
}
|
||||
return registers.count
|
||||
}
|
||||
|
||||
var totalHeatingLoad: Double {
|
||||
room.heatingLoadPerRegister * Double(actualRegisterCount)
|
||||
}
|
||||
|
||||
func totalCoolingSensible(projectSHR: Double) -> Double {
|
||||
room.coolingSensiblePerRegister(projectSHR: projectSHR) * Double(actualRegisterCount)
|
||||
}
|
||||
}
|
||||
|
||||
extension DuctSizing.TrunkSize {
|
||||
|
||||
var totalHeatingLoad: Double {
|
||||
rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad }
|
||||
}
|
||||
|
||||
func totalCoolingSensible(projectSHR: Double) -> Double {
|
||||
rooms.reduce(into: 0) { $0 += $1.totalCoolingSensible(projectSHR: projectSHR) }
|
||||
}
|
||||
}
|
||||
19
Sources/ProjectClient/Live.swift
Normal file
19
Sources/ProjectClient/Live.swift
Normal file
@@ -0,0 +1,19 @@
|
||||
import DatabaseClient
|
||||
import Dependencies
|
||||
import Logging
|
||||
import ManualDClient
|
||||
import ManualDCore
|
||||
|
||||
extension ProjectClient: DependencyKey {
|
||||
|
||||
public static var liveValue: Self {
|
||||
@Dependency(\.database) var database
|
||||
|
||||
return .init(
|
||||
calculateDuctSizes: { projectID in
|
||||
try await database.calculateDuctSizes(projectID: projectID)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,42 +24,42 @@ extension DatabaseClient.Projects {
|
||||
}
|
||||
}
|
||||
|
||||
extension DatabaseClient {
|
||||
|
||||
func calculateDuctSizes(
|
||||
projectID: Project.ID
|
||||
) async throws -> (rooms: [DuctSizing.RoomContainer], trunks: [DuctSizing.TrunkContainer]) {
|
||||
@Dependency(\.manualD) var manualD
|
||||
|
||||
return try await manualD.calculate(
|
||||
rooms: rooms.fetch(projectID),
|
||||
trunks: trunkSizes.fetch(projectID),
|
||||
designFrictionRateResult: designFrictionRate(projectID: projectID),
|
||||
projectSHR: projects.getSensibleHeatRatio(projectID)
|
||||
)
|
||||
}
|
||||
|
||||
func designFrictionRate(
|
||||
projectID: Project.ID
|
||||
) async throws -> (EquipmentInfo, EffectiveLength.MaxContainer, Double)? {
|
||||
guard let equipmentInfo = try await equipment.fetch(projectID) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let equivalentLengths = try await effectiveLength.fetchMax(projectID)
|
||||
guard let tel = equivalentLengths.total else { return nil }
|
||||
|
||||
let componentLosses = try await componentLoss.fetch(projectID)
|
||||
guard componentLosses.count > 0 else { return nil }
|
||||
|
||||
let availableStaticPressure =
|
||||
equipmentInfo.staticPressure - componentLosses.total
|
||||
|
||||
let designFrictionRate = (availableStaticPressure * 100) / tel
|
||||
|
||||
return (equipmentInfo, equivalentLengths, designFrictionRate)
|
||||
}
|
||||
}
|
||||
// extension DatabaseClient {
|
||||
//
|
||||
// func calculateDuctSizes(
|
||||
// projectID: Project.ID
|
||||
// ) async throws -> (rooms: [DuctSizing.RoomContainer], trunks: [DuctSizing.TrunkContainer]) {
|
||||
// @Dependency(\.manualD) var manualD
|
||||
//
|
||||
// return try await manualD.calculate(
|
||||
// rooms: rooms.fetch(projectID),
|
||||
// trunks: trunkSizes.fetch(projectID),
|
||||
// designFrictionRateResult: designFrictionRate(projectID: projectID),
|
||||
// projectSHR: projects.getSensibleHeatRatio(projectID)
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func designFrictionRate(
|
||||
// projectID: Project.ID
|
||||
// ) async throws -> (EquipmentInfo, EffectiveLength.MaxContainer, Double)? {
|
||||
// guard let equipmentInfo = try await equipment.fetch(projectID) else {
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// let equivalentLengths = try await effectiveLength.fetchMax(projectID)
|
||||
// guard let tel = equivalentLengths.total else { return nil }
|
||||
//
|
||||
// let componentLosses = try await componentLoss.fetch(projectID)
|
||||
// guard componentLosses.count > 0 else { return nil }
|
||||
//
|
||||
// let availableStaticPressure =
|
||||
// equipmentInfo.staticPressure - componentLosses.total
|
||||
//
|
||||
// let designFrictionRate = (availableStaticPressure * 100) / tel
|
||||
//
|
||||
// return (equipmentInfo, equivalentLengths, designFrictionRate)
|
||||
// }
|
||||
// }
|
||||
|
||||
extension DatabaseClient.ComponentLoss {
|
||||
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import Logging
|
||||
import ManualDClient
|
||||
import ManualDCore
|
||||
|
||||
extension ManualDClient {
|
||||
|
||||
func calculate(
|
||||
rooms: [Room],
|
||||
trunks: [DuctSizing.TrunkSize],
|
||||
designFrictionRateResult: (EquipmentInfo, EffectiveLength.MaxContainer, Double)?,
|
||||
projectSHR: Double?,
|
||||
logger: Logger? = nil
|
||||
) async throws -> (rooms: [DuctSizing.RoomContainer], trunks: [DuctSizing.TrunkContainer]) {
|
||||
guard let designFrictionRateResult else { return ([], []) }
|
||||
let equipmentInfo = designFrictionRateResult.0
|
||||
let effectiveLengths = designFrictionRateResult.1
|
||||
let designFrictionRate = designFrictionRateResult.2
|
||||
|
||||
guard let maxSupply = effectiveLengths.supply else { return ([], []) }
|
||||
guard let maxReturn = effectiveLengths.return else { return ([], []) }
|
||||
|
||||
let ductRooms = try await self.calculateSizes(
|
||||
rooms: rooms,
|
||||
trunks: trunks,
|
||||
equipmentInfo: equipmentInfo,
|
||||
maxSupplyLength: maxSupply,
|
||||
maxReturnLength: maxReturn,
|
||||
designFrictionRate: designFrictionRate,
|
||||
projectSHR: projectSHR ?? 1.0,
|
||||
logger: logger
|
||||
)
|
||||
|
||||
// logger?.debug("Rooms: \(ductRooms)")
|
||||
|
||||
return ductRooms
|
||||
|
||||
}
|
||||
}
|
||||
// import Logging
|
||||
// import ManualDClient
|
||||
// import ManualDCore
|
||||
//
|
||||
// extension ManualDClient {
|
||||
//
|
||||
// func calculate(
|
||||
// rooms: [Room],
|
||||
// trunks: [DuctSizing.TrunkSize],
|
||||
// designFrictionRateResult: (EquipmentInfo, EffectiveLength.MaxContainer, Double)?,
|
||||
// projectSHR: Double?,
|
||||
// logger: Logger? = nil
|
||||
// ) async throws -> (rooms: [DuctSizing.RoomContainer], trunks: [DuctSizing.TrunkContainer]) {
|
||||
// guard let designFrictionRateResult else { return ([], []) }
|
||||
// let equipmentInfo = designFrictionRateResult.0
|
||||
// let effectiveLengths = designFrictionRateResult.1
|
||||
// let designFrictionRate = designFrictionRateResult.2
|
||||
//
|
||||
// guard let maxSupply = effectiveLengths.supply else { return ([], []) }
|
||||
// guard let maxReturn = effectiveLengths.return else { return ([], []) }
|
||||
//
|
||||
// let ductRooms = try await self.calculateSizes(
|
||||
// rooms: rooms,
|
||||
// trunks: trunks,
|
||||
// equipmentInfo: equipmentInfo,
|
||||
// maxSupplyLength: maxSupply,
|
||||
// maxReturnLength: maxReturn,
|
||||
// designFrictionRate: designFrictionRate,
|
||||
// projectSHR: projectSHR ?? 1.0,
|
||||
// logger: logger
|
||||
// )
|
||||
//
|
||||
// // logger?.debug("Rooms: \(ductRooms)")
|
||||
//
|
||||
// return ductRooms
|
||||
//
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -3,6 +3,7 @@ import Dependencies
|
||||
import Elementary
|
||||
import Foundation
|
||||
import ManualDCore
|
||||
import ProjectClient
|
||||
import Styleguide
|
||||
|
||||
extension ViewController.Request {
|
||||
@@ -10,6 +11,7 @@ extension ViewController.Request {
|
||||
func render() async -> AnySendableHTML {
|
||||
|
||||
@Dependency(\.database) var database
|
||||
@Dependency(\.projectClient) var projectClient
|
||||
|
||||
switch route {
|
||||
case .test:
|
||||
@@ -18,7 +20,7 @@ extension ViewController.Request {
|
||||
await ResultView {
|
||||
return (
|
||||
try await database.projects.getCompletedSteps(projectID),
|
||||
try await database.calculateDuctSizes(projectID: projectID)
|
||||
try await projectClient.calculateDuctSizes(projectID)
|
||||
)
|
||||
} onSuccess: { (_, result) in
|
||||
TestPage(trunks: result.trunks, rooms: result.rooms)
|
||||
@@ -555,6 +557,7 @@ extension SiteRoute.View.ProjectRoute.DuctSizingRoute {
|
||||
) async -> AnySendableHTML {
|
||||
@Dependency(\.database) var database
|
||||
@Dependency(\.manualD) var manualD
|
||||
@Dependency(\.projectClient) var projectClient
|
||||
|
||||
switch self {
|
||||
case .index:
|
||||
@@ -563,7 +566,7 @@ extension SiteRoute.View.ProjectRoute.DuctSizingRoute {
|
||||
case .deleteRectangularSize(let roomID, let request):
|
||||
return await ResultView {
|
||||
let room = try await database.rooms.deleteRectangularSize(roomID, request.rectangularSizeID)
|
||||
return try await database.calculateDuctSizes(projectID: projectID)
|
||||
return try await projectClient.calculateDuctSizes(projectID)
|
||||
.rooms
|
||||
.filter({ $0.roomID == room.id && $0.roomRegister == request.register })
|
||||
.first!
|
||||
@@ -577,7 +580,7 @@ extension SiteRoute.View.ProjectRoute.DuctSizingRoute {
|
||||
roomID,
|
||||
.init(id: form.id ?? .init(), register: form.register, height: form.height)
|
||||
)
|
||||
return try await database.calculateDuctSizes(projectID: projectID)
|
||||
return try await projectClient.calculateDuctSizes(projectID)
|
||||
.rooms
|
||||
.filter({ $0.roomID == room.id && $0.roomRegister == form.register })
|
||||
.first!
|
||||
@@ -612,13 +615,14 @@ extension SiteRoute.View.ProjectRoute.DuctSizingRoute {
|
||||
catching: @escaping @Sendable () async throws -> Void = {}
|
||||
) async -> AnySendableHTML {
|
||||
@Dependency(\.database) var database
|
||||
@Dependency(\.projectClient) var project
|
||||
|
||||
return await request.view {
|
||||
await ResultView {
|
||||
try await catching()
|
||||
return (
|
||||
try await database.projects.getCompletedSteps(projectID),
|
||||
try await database.calculateDuctSizes(projectID: projectID)
|
||||
try await project.calculateDuctSizes(projectID)
|
||||
)
|
||||
} onSuccess: { (steps, ducts) in
|
||||
ProjectView(projectID: projectID, activeTab: .ductSizing, completedSteps: steps) {
|
||||
|
||||
Reference in New Issue
Block a user