feat: Reorganizes / creates duct sizes container, uses it in views and projectClient.

This commit is contained in:
2026-01-16 11:40:21 -05:00
parent 146baa7815
commit 13c4bb33b5
11 changed files with 90 additions and 91 deletions

View File

@@ -1,7 +1,21 @@
import Dependencies import Dependencies
import Foundation import Foundation
public enum DuctSizing { public struct DuctSizes: Codable, Equatable, Sendable {
public let rooms: [RoomContainer]
public let trunks: [TrunkContainer]
public init(
rooms: [DuctSizes.RoomContainer],
trunks: [DuctSizes.TrunkContainer]
) {
self.rooms = rooms
self.trunks = trunks
}
}
extension DuctSizes {
public struct SizeContainer: Codable, Equatable, Sendable { public struct SizeContainer: Codable, Equatable, Sendable {
@@ -16,7 +30,7 @@ public enum DuctSizing {
public init( public init(
rectangularID: Room.RectangularSize.ID? = nil, rectangularID: Room.RectangularSize.ID? = nil,
designCFM: DuctSizing.DesignCFM, designCFM: DuctSizes.DesignCFM,
roundSize: Double, roundSize: Double,
finalSize: Int, finalSize: Int,
velocity: Int, velocity: Int,
@@ -35,8 +49,6 @@ public enum DuctSizing {
} }
} }
// TODO: Uses SizeContainer
@dynamicMemberLookup @dynamicMemberLookup
public struct RoomContainer: Codable, Equatable, Sendable { public struct RoomContainer: Codable, Equatable, Sendable {
@@ -69,7 +81,7 @@ public enum DuctSizing {
self.ductSize = ductSize self.ductSize = ductSize
} }
public subscript<T>(dynamicMember keyPath: KeyPath<DuctSizing.SizeContainer, T>) -> T { public subscript<T>(dynamicMember keyPath: KeyPath<DuctSizes.SizeContainer, T>) -> T {
ductSize[keyPath: keyPath] ductSize[keyPath: keyPath]
} }
} }
@@ -95,7 +107,7 @@ public enum DuctSizing {
} }
} }
extension DuctSizing { extension DuctSizes {
// Represents the database model that the duct sizes have been calculated // Represents the database model that the duct sizes have been calculated
// for. // for.
@@ -118,7 +130,7 @@ extension DuctSizing {
trunk[keyPath: keyPath] trunk[keyPath: keyPath]
} }
public subscript<T>(dynamicMember keyPath: KeyPath<DuctSizing.SizeContainer, T>) -> T { public subscript<T>(dynamicMember keyPath: KeyPath<DuctSizes.SizeContainer, T>) -> T {
ductSize[keyPath: keyPath] ductSize[keyPath: keyPath]
} }
} }

View File

@@ -16,11 +16,11 @@ extension DependencyValues {
/// for the view controller client to render views. /// for the view controller client to render views.
@DependencyClient @DependencyClient
public struct ProjectClient: Sendable { public struct ProjectClient: Sendable {
public var calculateDuctSizes: @Sendable (Project.ID) async throws -> DuctSizeResponse public var calculateDuctSizes: @Sendable (Project.ID) async throws -> DuctSizes
public var calculateRoomDuctSizes: public var calculateRoomDuctSizes:
@Sendable (Project.ID) async throws -> [DuctSizing.RoomContainer] @Sendable (Project.ID) async throws -> [DuctSizes.RoomContainer]
public var calculateTrunkDuctSizes: public var calculateTrunkDuctSizes:
@Sendable (Project.ID) async throws -> [DuctSizing.TrunkContainer] @Sendable (Project.ID) async throws -> [DuctSizes.TrunkContainer]
public var createProject: public var createProject:
@Sendable (User.ID, Project.Create) async throws -> CreateProjectResponse @Sendable (User.ID, Project.Create) async throws -> CreateProjectResponse
@@ -54,19 +54,6 @@ extension ProjectClient {
} }
} }
public struct DuctSizeResponse: 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
}
}
public struct FrictionRateResponse: Codable, Equatable, Sendable { public struct FrictionRateResponse: Codable, Equatable, Sendable {
public let componentLosses: [ComponentPressureLoss] public let componentLosses: [ComponentPressureLoss]

View File

@@ -7,7 +7,7 @@ extension DatabaseClient {
func calculateDuctSizes( func calculateDuctSizes(
projectID: Project.ID projectID: Project.ID
) async throws -> ProjectClient.DuctSizeResponse { ) async throws -> DuctSizes {
@Dependency(\.manualD) var manualD @Dependency(\.manualD) var manualD
return try await manualD.calculateDuctSizes( return try await manualD.calculateDuctSizes(
@@ -19,7 +19,7 @@ extension DatabaseClient {
func calculateRoomDuctSizes( func calculateRoomDuctSizes(
projectID: Project.ID projectID: Project.ID
) async throws -> [DuctSizing.RoomContainer] { ) async throws -> [DuctSizes.RoomContainer] {
@Dependency(\.manualD) var manualD @Dependency(\.manualD) var manualD
return try await manualD.calculateRoomSizes( return try await manualD.calculateRoomSizes(
@@ -30,7 +30,7 @@ extension DatabaseClient {
func calculateTrunkDuctSizes( func calculateTrunkDuctSizes(
projectID: Project.ID projectID: Project.ID
) async throws -> [DuctSizing.TrunkContainer] { ) async throws -> [DuctSizes.TrunkContainer] {
@Dependency(\.manualD) var manualD @Dependency(\.manualD) var manualD
return try await manualD.calculateTrunkSizes( return try await manualD.calculateTrunkSizes(

View File

@@ -19,7 +19,7 @@ extension ManualDClient {
trunks: [TrunkSize], trunks: [TrunkSize],
sharedRequest: DuctSizeSharedRequest, sharedRequest: DuctSizeSharedRequest,
logger: Logger? = nil logger: Logger? = nil
) async throws -> ProjectClient.DuctSizeResponse { ) async throws -> DuctSizes {
try await .init( try await .init(
rooms: calculateRoomSizes( rooms: calculateRoomSizes(
rooms: rooms, rooms: rooms,
@@ -37,9 +37,9 @@ extension ManualDClient {
rooms: [Room], rooms: [Room],
sharedRequest: DuctSizeSharedRequest, sharedRequest: DuctSizeSharedRequest,
logger: Logger? = nil logger: Logger? = nil
) async throws -> [DuctSizing.RoomContainer] { ) async throws -> [DuctSizes.RoomContainer] {
var retval: [DuctSizing.RoomContainer] = [] var retval: [DuctSizes.RoomContainer] = []
let totalHeatingLoad = rooms.totalHeatingLoad let totalHeatingLoad = rooms.totalHeatingLoad
let totalCoolingSensible = rooms.totalCoolingSensible(shr: sharedRequest.projectSHR) let totalCoolingSensible = rooms.totalCoolingSensible(shr: sharedRequest.projectSHR)
@@ -50,7 +50,7 @@ extension ManualDClient {
let coolingPercent = coolingLoad / totalCoolingSensible let coolingPercent = coolingLoad / totalCoolingSensible
let heatingCFM = heatingPercent * Double(sharedRequest.equipmentInfo.heatingCFM) let heatingCFM = heatingPercent * Double(sharedRequest.equipmentInfo.heatingCFM)
let coolingCFM = coolingPercent * Double(sharedRequest.equipmentInfo.coolingCFM) let coolingCFM = coolingPercent * Double(sharedRequest.equipmentInfo.coolingCFM)
let designCFM = DuctSizing.DesignCFM(heating: heatingCFM, cooling: coolingCFM) let designCFM = DuctSizes.DesignCFM(heating: heatingCFM, cooling: coolingCFM)
let sizes = try await self.ductSize( let sizes = try await self.ductSize(
.init(designCFM: Int(designCFM.value), frictionRate: sharedRequest.designFrictionRate) .init(designCFM: Int(designCFM.value), frictionRate: sharedRequest.designFrictionRate)
) )
@@ -96,9 +96,9 @@ extension ManualDClient {
trunks: [TrunkSize], trunks: [TrunkSize],
sharedRequest: DuctSizeSharedRequest, sharedRequest: DuctSizeSharedRequest,
logger: Logger? = nil logger: Logger? = nil
) async throws -> [DuctSizing.TrunkContainer] { ) async throws -> [DuctSizes.TrunkContainer] {
var retval = [DuctSizing.TrunkContainer]() var retval = [DuctSizes.TrunkContainer]()
let totalHeatingLoad = rooms.totalHeatingLoad let totalHeatingLoad = rooms.totalHeatingLoad
let totalCoolingSensible = rooms.totalCoolingSensible(shr: sharedRequest.projectSHR) let totalCoolingSensible = rooms.totalCoolingSensible(shr: sharedRequest.projectSHR)
@@ -109,7 +109,7 @@ extension ManualDClient {
let coolingPercent = coolingLoad / totalCoolingSensible let coolingPercent = coolingLoad / totalCoolingSensible
let heatingCFM = heatingPercent * Double(sharedRequest.equipmentInfo.heatingCFM) let heatingCFM = heatingPercent * Double(sharedRequest.equipmentInfo.heatingCFM)
let coolingCFM = coolingPercent * Double(sharedRequest.equipmentInfo.coolingCFM) let coolingCFM = coolingPercent * Double(sharedRequest.equipmentInfo.coolingCFM)
let designCFM = DuctSizing.DesignCFM(heating: heatingCFM, cooling: coolingCFM) let designCFM = DuctSizes.DesignCFM(heating: heatingCFM, cooling: coolingCFM)
let sizes = try await self.ductSize( let sizes = try await self.ductSize(
.init(designCFM: Int(designCFM.value), frictionRate: sharedRequest.designFrictionRate) .init(designCFM: Int(designCFM.value), frictionRate: sharedRequest.designFrictionRate)
) )
@@ -139,9 +139,9 @@ extension ManualDClient {
} }
extension DuctSizing.SizeContainer { extension DuctSizes.SizeContainer {
init( init(
designCFM: DuctSizing.DesignCFM, designCFM: DuctSizes.DesignCFM,
sizes: ManualDClient.DuctSizeResponse, sizes: ManualDClient.DuctSizeResponse,
height: Int?, height: Int?,
width: Int? width: Int?
@@ -159,7 +159,7 @@ extension DuctSizing.SizeContainer {
} }
init( init(
designCFM: DuctSizing.DesignCFM, designCFM: DuctSizes.DesignCFM,
sizes: ManualDClient.DuctSizeResponse, sizes: ManualDClient.DuctSizeResponse,
rectangularSize: Room.RectangularSize?, rectangularSize: Room.RectangularSize?,
width: Int? width: Int?

View File

@@ -15,15 +15,17 @@ extension ViewController.Request {
switch route { switch route {
case .test: case .test:
let projectID = UUID(uuidString: "A9C20153-E2E5-4C65-B33F-4D8A29C63A7A")! // let projectID = UUID(uuidString: "A9C20153-E2E5-4C65-B33F-4D8A29C63A7A")!
return await view { return await view {
await ResultView { await ResultView {
return (
try await database.projects.getCompletedSteps(projectID), // return (
try await projectClient.calculateDuctSizes(projectID) // try await database.projects.getCompletedSteps(projectID),
) // try await projectClient.calculateDuctSizes(projectID)
} onSuccess: { (_, result) in // )
TestPage(trunks: result.trunks, rooms: result.rooms) } onSuccess: {
TestPage()
// TestPage(trunks: result.trunks, rooms: result.rooms)
} }
} }
case .login(let route): case .login(let route):
@@ -609,7 +611,7 @@ extension SiteRoute.View.ProjectRoute.DuctSizingRoute {
) )
} onSuccess: { (steps, ducts) in } onSuccess: { (steps, ducts) in
ProjectView(projectID: projectID, activeTab: .ductSizing, completedSteps: steps) { ProjectView(projectID: projectID, activeTab: .ductSizing, completedSteps: steps) {
DuctSizingView(rooms: ducts.rooms, trunks: ducts.trunks) DuctSizingView(ductSizes: ducts)
} }
} }
} }

View File

@@ -7,8 +7,7 @@ struct DuctSizingView: HTML, Sendable {
@Environment(ProjectViewValue.$projectID) var projectID @Environment(ProjectViewValue.$projectID) var projectID
let rooms: [DuctSizing.RoomContainer] let ductSizes: DuctSizes
let trunks: [DuctSizing.TrunkContainer]
var body: some HTML { var body: some HTML {
div(.class("space-y-4")) { div(.class("space-y-4")) {
@@ -21,13 +20,13 @@ struct DuctSizingView: HTML, Sendable {
Must complete all the previous sections to display duct sizing calculations. Must complete all the previous sections to display duct sizing calculations.
""" """
) )
.hidden(when: rooms.count > 0) .hidden(when: ductSizes.rooms.count > 0)
.attributes(.class("text-error font-bold italic mt-4")) .attributes(.class("text-error font-bold italic mt-4"))
} }
} }
if rooms.count != 0 { if ductSizes.rooms.count != 0 {
RoomsTable(rooms: rooms) RoomsTable(rooms: ductSizes.rooms)
PageTitleRow { PageTitleRow {
PageTitle { PageTitle {
@@ -42,13 +41,13 @@ struct DuctSizingView: HTML, Sendable {
.tooltip("Add trunk / runout") .tooltip("Add trunk / runout")
} }
if trunks.count > 0 { if ductSizes.trunks.count > 0 {
TrunkTable(trunks: trunks, rooms: rooms) TrunkTable(ductSizes: ductSizes)
} }
} }
TrunkSizeForm(rooms: rooms, dismiss: true) TrunkSizeForm(rooms: ductSizes.rooms, dismiss: true)
} }
} }

View File

@@ -5,7 +5,7 @@ import Styleguide
struct RectangularSizeForm: HTML, Sendable { struct RectangularSizeForm: HTML, Sendable {
static func id(_ room: DuctSizing.RoomContainer) -> String { static func id(_ room: DuctSizes.RoomContainer) -> String {
let base = "rectangularSize" let base = "rectangularSize"
return "\(base)_\(room.roomName.idString)" return "\(base)_\(room.roomName.idString)"
} }
@@ -13,12 +13,12 @@ struct RectangularSizeForm: HTML, Sendable {
@Environment(ProjectViewValue.$projectID) var projectID @Environment(ProjectViewValue.$projectID) var projectID
let id: String let id: String
let room: DuctSizing.RoomContainer let room: DuctSizes.RoomContainer
let dismiss: Bool let dismiss: Bool
init( init(
id: String? = nil, id: String? = nil,
room: DuctSizing.RoomContainer, room: DuctSizes.RoomContainer,
dismiss: Bool = true dismiss: Bool = true
) { ) {
self.id = Self.id(room) self.id = Self.id(room)

View File

@@ -9,7 +9,7 @@ extension DuctSizingView {
struct RoomsTable: HTML, Sendable { struct RoomsTable: HTML, Sendable {
@Environment(ProjectViewValue.$projectID) var projectID @Environment(ProjectViewValue.$projectID) var projectID
let rooms: [DuctSizing.RoomContainer] let rooms: [DuctSizes.RoomContainer]
var body: some HTML<HTMLTag.table> { var body: some HTML<HTMLTag.table> {
@@ -34,13 +34,13 @@ extension DuctSizingView {
struct RoomRow: HTML, Sendable { struct RoomRow: HTML, Sendable {
static func id(_ room: DuctSizing.RoomContainer) -> String { static func id(_ room: DuctSizes.RoomContainer) -> String {
"roomRow_\(room.roomName.idString)" "roomRow_\(room.roomName.idString)"
} }
@Environment(ProjectViewValue.$projectID) var projectID @Environment(ProjectViewValue.$projectID) var projectID
let room: DuctSizing.RoomContainer let room: DuctSizes.RoomContainer
let formID = UUID().idString let formID = UUID().idString
var deleteRoute: String { var deleteRoute: String {

View File

@@ -5,7 +5,7 @@ import Styleguide
struct TrunkSizeForm: HTML, Sendable { struct TrunkSizeForm: HTML, Sendable {
static func id(_ trunk: DuctSizing.TrunkContainer? = nil) -> String { static func id(_ trunk: DuctSizes.TrunkContainer? = nil) -> String {
let base = "trunkSizeForm" let base = "trunkSizeForm"
guard let trunk else { return base } guard let trunk else { return base }
return "\(base)_\(trunk.id.idString)" return "\(base)_\(trunk.id.idString)"
@@ -13,8 +13,8 @@ struct TrunkSizeForm: HTML, Sendable {
@Environment(ProjectViewValue.$projectID) var projectID @Environment(ProjectViewValue.$projectID) var projectID
let container: DuctSizing.TrunkContainer? let container: DuctSizes.TrunkContainer?
let rooms: [DuctSizing.RoomContainer] let rooms: [DuctSizes.RoomContainer]
let dismiss: Bool let dismiss: Bool
var trunk: TrunkSize? { var trunk: TrunkSize? {
@@ -22,8 +22,8 @@ struct TrunkSizeForm: HTML, Sendable {
} }
init( init(
trunk: DuctSizing.TrunkContainer? = nil, trunk: DuctSizes.TrunkContainer? = nil,
rooms: [DuctSizing.RoomContainer], rooms: [DuctSizes.RoomContainer],
dismiss: Bool = true dismiss: Bool = true
) { ) {
self.container = trunk self.container = trunk
@@ -122,7 +122,7 @@ struct TrunkSizeForm: HTML, Sendable {
} }
extension Array where Element == TrunkSize.RoomProxy { extension Array where Element == TrunkSize.RoomProxy {
func hasRoom(_ room: DuctSizing.RoomContainer) -> Bool { func hasRoom(_ room: DuctSizes.RoomContainer) -> Bool {
first { first {
$0.id == room.roomID $0.id == room.roomID
&& $0.registers.contains(room.roomRegister) && $0.registers.contains(room.roomRegister)

View File

@@ -7,11 +7,10 @@ extension DuctSizingView {
struct TrunkTable: HTML, Sendable { struct TrunkTable: HTML, Sendable {
let trunks: [DuctSizing.TrunkContainer] let ductSizes: DuctSizes
let rooms: [DuctSizing.RoomContainer]
private var sortedTrunks: [DuctSizing.TrunkContainer] { private var sortedTrunks: [DuctSizes.TrunkContainer] {
trunks ductSizes.trunks
.sorted(by: { $0.designCFM.value > $1.designCFM.value }) .sorted(by: { $0.designCFM.value > $1.designCFM.value })
.sorted(by: { $0.type.rawValue > $1.type.rawValue }) .sorted(by: { $0.type.rawValue > $1.type.rawValue })
} }
@@ -29,7 +28,7 @@ extension DuctSizingView {
} }
tbody { tbody {
for trunk in sortedTrunks { for trunk in sortedTrunks {
TrunkRow(trunk: trunk, rooms: rooms) TrunkRow(trunk: trunk, rooms: ductSizes.rooms)
} }
} }
} }
@@ -41,8 +40,8 @@ extension DuctSizingView {
@Environment(ProjectViewValue.$projectID) var projectID @Environment(ProjectViewValue.$projectID) var projectID
let trunk: DuctSizing.TrunkContainer let trunk: DuctSizes.TrunkContainer
let rooms: [DuctSizing.RoomContainer] let rooms: [DuctSizes.RoomContainer]
var body: some HTML<HTMLTag.tr> { var body: some HTML<HTMLTag.tr> {
tr { tr {

View File

@@ -5,27 +5,27 @@ import ManualDCore
import Styleguide import Styleguide
struct TestPage: HTML, Sendable { struct TestPage: HTML, Sendable {
let trunks: [DuctSizing.TrunkContainer] // let ductSizes: DuctSizes
let rooms: [DuctSizing.RoomContainer]
var body: some HTML { var body: some HTML {
div(.class("overflow-auto")) { div {}
DuctSizingView.TrunkTable(trunks: trunks, rooms: rooms) // div(.class("overflow-auto")) {
// DuctSizingView.TrunkTable(ductSizes: ductSizes)
Row { //
h2(.class("text-2xl font-bold")) { "Trunk Sizes" } // Row {
// h2(.class("text-2xl font-bold")) { "Trunk Sizes" }
PlusButton() //
.attributes( // PlusButton()
.class("me-6"), // .attributes(
.showModal(id: TrunkSizeForm.id()) // .class("me-6"),
) // .showModal(id: TrunkSizeForm.id())
} // )
.attributes(.class("mt-6")) // }
// .attributes(.class("mt-6"))
div(.class("divider -mt-2")) {} //
// div(.class("divider -mt-2")) {}
DuctSizingView.TrunkTable(trunks: trunks, rooms: rooms) //
} // DuctSizingView.TrunkTable(ductSizes: ductSizes)
// }
} }
} }