feat: Moves TrunkSize to be it's own namespace rather than being under DuctSizing, as it's got it's own database model, etc.

This commit is contained in:
2026-01-16 10:48:07 -05:00
parent b5436c2073
commit 146baa7815
9 changed files with 159 additions and 135 deletions

View File

@@ -74,7 +74,7 @@ extension DatabaseClient.Migrations: DependencyKey {
EquipmentInfo.Migrate(), EquipmentInfo.Migrate(),
Room.Migrate(), Room.Migrate(),
EffectiveLength.Migrate(), EffectiveLength.Migrate(),
DuctSizing.TrunkSize.Migrate(), TrunkSize.Migrate(),
] ]
} }
) )

View File

@@ -7,13 +7,13 @@ import ManualDCore
extension DatabaseClient { extension DatabaseClient {
@DependencyClient @DependencyClient
public struct TrunkSizes: Sendable { public struct TrunkSizes: Sendable {
public var create: @Sendable (DuctSizing.TrunkSize.Create) async throws -> DuctSizing.TrunkSize public var create: @Sendable (TrunkSize.Create) async throws -> TrunkSize
public var delete: @Sendable (DuctSizing.TrunkSize.ID) async throws -> Void public var delete: @Sendable (TrunkSize.ID) async throws -> Void
public var fetch: @Sendable (Project.ID) async throws -> [DuctSizing.TrunkSize] public var fetch: @Sendable (Project.ID) async throws -> [TrunkSize]
public var get: @Sendable (DuctSizing.TrunkSize.ID) async throws -> DuctSizing.TrunkSize? public var get: @Sendable (TrunkSize.ID) async throws -> TrunkSize?
public var update: public var update:
@Sendable (DuctSizing.TrunkSize.ID, DuctSizing.TrunkSize.Update) async throws -> @Sendable (TrunkSize.ID, TrunkSize.Update) async throws ->
DuctSizing.TrunkSize TrunkSize
} }
} }
@@ -26,7 +26,7 @@ extension DatabaseClient.TrunkSizes: TestDependencyKey {
try request.validate() try request.validate()
let trunk = request.toModel() let trunk = request.toModel()
var roomProxies = [DuctSizing.TrunkSize.RoomProxy]() var roomProxies = [TrunkSize.RoomProxy]()
try await trunk.save(on: database) try await trunk.save(on: database)
@@ -90,7 +90,7 @@ extension DatabaseClient.TrunkSizes: TestDependencyKey {
} }
} }
extension DuctSizing.TrunkSize.Create { extension TrunkSize.Create {
func validate() throws(ValidationError) { func validate() throws(ValidationError) {
guard rooms.count > 0 else { guard rooms.count > 0 else {
@@ -113,7 +113,7 @@ extension DuctSizing.TrunkSize.Create {
} }
} }
extension DuctSizing.TrunkSize.Update { extension TrunkSize.Update {
func validate() throws(ValidationError) { func validate() throws(ValidationError) {
if let rooms { if let rooms {
guard rooms.count > 0 else { guard rooms.count > 0 else {
@@ -128,7 +128,7 @@ extension DuctSizing.TrunkSize.Update {
} }
} }
extension DuctSizing.TrunkSize { extension TrunkSize {
struct Migrate: AsyncMigration { struct Migrate: AsyncMigration {
let name = "CreateTrunkSize" let name = "CreateTrunkSize"
@@ -192,7 +192,7 @@ final class TrunkRoomModel: Model, @unchecked Sendable {
trunkID: TrunkModel.IDValue, trunkID: TrunkModel.IDValue,
roomID: RoomModel.IDValue, roomID: RoomModel.IDValue,
registers: [Int], registers: [Int],
type: DuctSizing.TrunkSize.TrunkType type: TrunkSize.TrunkType
) { ) {
self.id = id self.id = id
$trunk.id = trunkID $trunk.id = trunkID
@@ -201,7 +201,7 @@ final class TrunkRoomModel: Model, @unchecked Sendable {
self.type = type.rawValue self.type = type.rawValue
} }
func toDTO(on database: any Database) async throws -> DuctSizing.TrunkSize.RoomProxy { func toDTO(on database: any Database) async throws -> TrunkSize.RoomProxy {
guard let room = try await RoomModel.find($room.id, on: database) else { guard let room = try await RoomModel.find($room.id, on: database) else {
throw NotFoundError() throw NotFoundError()
} }
@@ -240,7 +240,7 @@ final class TrunkModel: Model, @unchecked Sendable {
init( init(
id: UUID? = nil, id: UUID? = nil,
projectID: Project.ID, projectID: Project.ID,
type: DuctSizing.TrunkSize.TrunkType, type: TrunkSize.TrunkType,
height: Int? = nil, height: Int? = nil,
name: String? = nil name: String? = nil
) { ) {
@@ -251,15 +251,15 @@ final class TrunkModel: Model, @unchecked Sendable {
self.name = name self.name = name
} }
func toDTO(on database: any Database) async throws -> DuctSizing.TrunkSize { func toDTO(on database: any Database) async throws -> TrunkSize {
let rooms = try await withThrowingTaskGroup(of: DuctSizing.TrunkSize.RoomProxy.self) { group in let rooms = try await withThrowingTaskGroup(of: TrunkSize.RoomProxy.self) { group in
for room in self.rooms { for room in self.rooms {
group.addTask { group.addTask {
try await room.toDTO(on: database) try await room.toDTO(on: database)
} }
} }
return try await group.reduce(into: [DuctSizing.TrunkSize.RoomProxy]()) { return try await group.reduce(into: [TrunkSize.RoomProxy]()) {
$0.append($1) $0.append($1)
} }
@@ -277,7 +277,7 @@ final class TrunkModel: Model, @unchecked Sendable {
} }
func applyUpdates( func applyUpdates(
_ updates: DuctSizing.TrunkSize.Update, _ updates: TrunkSize.Update,
on database: any Database on database: any Database
) async throws { ) async throws {
if let type = updates.type, type.rawValue != self.type { if let type = updates.type, type.rawValue != self.type {
@@ -340,15 +340,15 @@ final class TrunkModel: Model, @unchecked Sendable {
extension Array where Element == TrunkModel { extension Array where Element == TrunkModel {
func toDTO(on database: any Database) async throws -> [DuctSizing.TrunkSize] { func toDTO(on database: any Database) async throws -> [TrunkSize] {
try await withThrowingTaskGroup(of: DuctSizing.TrunkSize.self) { group in try await withThrowingTaskGroup(of: TrunkSize.self) { group in
for model in self { for model in self {
group.addTask { group.addTask {
try await model.toDTO(on: database) try await model.toDTO(on: database)
} }
} }
return try await group.reduce(into: [DuctSizing.TrunkSize]()) { return try await group.reduce(into: [TrunkSize]()) {
$0.append($1) $0.append($1)
} }
} }

View File

@@ -14,7 +14,7 @@ extension Room {
} }
} }
extension DuctSizing.TrunkSize.RoomProxy { extension TrunkSize.RoomProxy {
// We need to make sure if registers got removed after a trunk // We need to make sure if registers got removed after a trunk
// was already made / saved that we do not include registers that // was already made / saved that we do not include registers that
@@ -35,7 +35,7 @@ extension DuctSizing.TrunkSize.RoomProxy {
} }
} }
extension DuctSizing.TrunkSize { extension TrunkSize {
var totalHeatingLoad: Double { var totalHeatingLoad: Double {
rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad } rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad }

View File

@@ -114,7 +114,7 @@ extension DuctSizing {
self.ductSize = ductSize self.ductSize = ductSize
} }
public subscript<T>(dynamicMember keyPath: KeyPath<DuctSizing.TrunkSize, T>) -> T { public subscript<T>(dynamicMember keyPath: KeyPath<TrunkSize, T>) -> T {
trunk[keyPath: keyPath] trunk[keyPath: keyPath]
} }
@@ -122,99 +122,4 @@ extension DuctSizing {
ductSize[keyPath: keyPath] ductSize[keyPath: keyPath]
} }
} }
// TODO: Add an optional label that the user can set.
// Represents the database model.
public struct TrunkSize: Codable, Equatable, Identifiable, Sendable {
public let id: UUID
public let projectID: Project.ID
public let type: TrunkType
public let rooms: [RoomProxy]
public let height: Int?
public let name: String?
public init(
id: UUID,
projectID: Project.ID,
type: DuctSizing.TrunkSize.TrunkType,
rooms: [DuctSizing.TrunkSize.RoomProxy],
height: Int? = nil,
name: String? = nil
) {
self.id = id
self.projectID = projectID
self.type = type
self.rooms = rooms
self.height = height
self.name = name
}
}
}
extension DuctSizing.TrunkSize {
public struct Create: Codable, Equatable, Sendable {
public let projectID: Project.ID
public let type: TrunkType
public let rooms: [Room.ID: [Int]]
public let height: Int?
public let name: String?
public init(
projectID: Project.ID,
type: DuctSizing.TrunkSize.TrunkType,
rooms: [Room.ID: [Int]],
height: Int? = nil,
name: String? = nil
) {
self.projectID = projectID
self.type = type
self.rooms = rooms
self.height = height
self.name = name
}
}
public struct Update: Codable, Equatable, Sendable {
public let type: TrunkType?
public let rooms: [Room.ID: [Int]]?
public let height: Int?
public let name: String?
public init(
type: DuctSizing.TrunkSize.TrunkType? = nil,
rooms: [Room.ID: [Int]]? = nil,
height: Int? = nil,
name: String? = nil
) {
self.type = type
self.rooms = rooms
self.height = height
self.name = name
}
}
// TODO: Make registers non-optional
public struct RoomProxy: Codable, Equatable, Identifiable, Sendable {
public var id: Room.ID { room.id }
public let room: Room
public let registers: [Int]
public init(room: Room, registers: [Int]) {
self.room = room
self.registers = registers
}
}
public enum TrunkType: String, CaseIterable, Codable, Equatable, Sendable {
case `return`
case supply
public static let allCases = [Self.supply, .return]
}
} }

View File

@@ -668,9 +668,9 @@ extension SiteRoute.View.ProjectRoute {
} }
public enum TrunkRoute: Equatable, Sendable { public enum TrunkRoute: Equatable, Sendable {
case delete(DuctSizing.TrunkSize.ID) case delete(TrunkSize.ID)
case submit(TrunkSizeForm) case submit(TrunkSizeForm)
case update(DuctSizing.TrunkSize.ID, TrunkSizeForm) case update(TrunkSize.ID, TrunkSizeForm)
public static let rootPath = "trunk" public static let rootPath = "trunk"
@@ -678,7 +678,7 @@ extension SiteRoute.View.ProjectRoute {
Route(.case(Self.delete)) { Route(.case(Self.delete)) {
Path { Path {
rootPath rootPath
DuctSizing.TrunkSize.ID.parser() TrunkSize.ID.parser()
} }
Method.delete Method.delete
} }
@@ -690,7 +690,7 @@ extension SiteRoute.View.ProjectRoute {
Body { Body {
FormData { FormData {
Field("projectID") { Project.ID.parser() } Field("projectID") { Project.ID.parser() }
Field("type") { DuctSizing.TrunkSize.TrunkType.parser() } Field("type") { TrunkSize.TrunkType.parser() }
Optionally { Optionally {
Field("height") { Int.parser() } Field("height") { Int.parser() }
@@ -708,13 +708,13 @@ extension SiteRoute.View.ProjectRoute {
Route(.case(Self.update)) { Route(.case(Self.update)) {
Path { Path {
rootPath rootPath
DuctSizing.TrunkSize.ID.parser() TrunkSize.ID.parser()
} }
Method.patch Method.patch
Body { Body {
FormData { FormData {
Field("projectID") { Project.ID.parser() } Field("projectID") { Project.ID.parser() }
Field("type") { DuctSizing.TrunkSize.TrunkType.parser() } Field("type") { TrunkSize.TrunkType.parser() }
Optionally { Optionally {
Field("height") { Int.parser() } Field("height") { Int.parser() }
} }
@@ -732,17 +732,43 @@ extension SiteRoute.View.ProjectRoute {
} }
public struct RoomRectangularForm: Equatable, Sendable { public struct RoomRectangularForm: Equatable, Sendable {
public let id: Room.RectangularSize.ID? public let id: Room.RectangularSize.ID?
public let register: Int public let register: Int
public let height: Int public let height: Int
public init(
id: Room.RectangularSize.ID? = nil,
register: Int,
height: Int
) {
self.id = id
self.register = register
self.height = height
}
} }
public struct TrunkSizeForm: Equatable, Sendable { public struct TrunkSizeForm: Equatable, Sendable {
public let projectID: Project.ID public let projectID: Project.ID
public let type: DuctSizing.TrunkSize.TrunkType public let type: TrunkSize.TrunkType
public let height: Int? public let height: Int?
public let name: String? public let name: String?
public let rooms: [String] public let rooms: [String]
public init(
projectID: Project.ID,
type: TrunkSize.TrunkType,
height: Int? = nil,
name: String? = nil,
rooms: [String]
) {
self.projectID = projectID
self.type = type
self.height = height
self.name = name
self.rooms = rooms
}
} }
} }
} }

View File

@@ -0,0 +1,93 @@
import Foundation
// Represents the database model.
public struct TrunkSize: Codable, Equatable, Identifiable, Sendable {
public let id: UUID
public let projectID: Project.ID
public let type: TrunkType
public let rooms: [RoomProxy]
public let height: Int?
public let name: String?
public init(
id: UUID,
projectID: Project.ID,
type: TrunkType,
rooms: [RoomProxy],
height: Int? = nil,
name: String? = nil
) {
self.id = id
self.projectID = projectID
self.type = type
self.rooms = rooms
self.height = height
self.name = name
}
}
extension TrunkSize {
public struct Create: Codable, Equatable, Sendable {
public let projectID: Project.ID
public let type: TrunkType
public let rooms: [Room.ID: [Int]]
public let height: Int?
public let name: String?
public init(
projectID: Project.ID,
type: TrunkType,
rooms: [Room.ID: [Int]],
height: Int? = nil,
name: String? = nil
) {
self.projectID = projectID
self.type = type
self.rooms = rooms
self.height = height
self.name = name
}
}
public struct Update: Codable, Equatable, Sendable {
public let type: TrunkType?
public let rooms: [Room.ID: [Int]]?
public let height: Int?
public let name: String?
public init(
type: TrunkType? = nil,
rooms: [Room.ID: [Int]]? = nil,
height: Int? = nil,
name: String? = nil
) {
self.type = type
self.rooms = rooms
self.height = height
self.name = name
}
}
// TODO: Make registers non-optional
public struct RoomProxy: Codable, Equatable, Identifiable, Sendable {
public var id: Room.ID { room.id }
public let room: Room
public let registers: [Int]
public init(room: Room, registers: [Int]) {
self.room = room
self.registers = registers
}
}
public enum TrunkType: String, CaseIterable, Codable, Equatable, Sendable {
case `return`
case supply
public static let allCases = [Self.supply, .return]
}
}

View File

@@ -16,7 +16,7 @@ extension ManualDClient {
func calculateDuctSizes( func calculateDuctSizes(
rooms: [Room], rooms: [Room],
trunks: [DuctSizing.TrunkSize], trunks: [TrunkSize],
sharedRequest: DuctSizeSharedRequest, sharedRequest: DuctSizeSharedRequest,
logger: Logger? = nil logger: Logger? = nil
) async throws -> ProjectClient.DuctSizeResponse { ) async throws -> ProjectClient.DuctSizeResponse {
@@ -93,7 +93,7 @@ extension ManualDClient {
func calculateTrunkSizes( func calculateTrunkSizes(
rooms: [Room], rooms: [Room],
trunks: [DuctSizing.TrunkSize], trunks: [TrunkSize],
sharedRequest: DuctSizeSharedRequest, sharedRequest: DuctSizeSharedRequest,
logger: Logger? = nil logger: Logger? = nil
) async throws -> [DuctSizing.TrunkContainer] { ) async throws -> [DuctSizing.TrunkContainer] {
@@ -190,7 +190,7 @@ extension Room {
} }
} }
extension DuctSizing.TrunkSize.RoomProxy { extension TrunkSize.RoomProxy {
// We need to make sure if registers got removed after a trunk // We need to make sure if registers got removed after a trunk
// was already made / saved that we do not include registers that // was already made / saved that we do not include registers that
@@ -211,7 +211,7 @@ extension DuctSizing.TrunkSize.RoomProxy {
} }
} }
extension DuctSizing.TrunkSize { extension TrunkSize {
var totalHeatingLoad: Double { var totalHeatingLoad: Double {
rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad } rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad }

View File

@@ -4,7 +4,7 @@ import ManualDCore
extension SiteRoute.View.ProjectRoute.DuctSizingRoute.TrunkSizeForm { extension SiteRoute.View.ProjectRoute.DuctSizingRoute.TrunkSizeForm {
func toCreate(logger: Logger? = nil) throws -> DuctSizing.TrunkSize.Create { func toCreate(logger: Logger? = nil) throws -> TrunkSize.Create {
try .init( try .init(
projectID: projectID, projectID: projectID,
type: type, type: type,
@@ -14,7 +14,7 @@ extension SiteRoute.View.ProjectRoute.DuctSizingRoute.TrunkSizeForm {
) )
} }
func toUpdate(logger: Logger? = nil) throws -> DuctSizing.TrunkSize.Update { func toUpdate(logger: Logger? = nil) throws -> TrunkSize.Update {
try .init( try .init(
type: type, type: type,
rooms: makeRooms(logger: logger), rooms: makeRooms(logger: logger),

View File

@@ -17,7 +17,7 @@ struct TrunkSizeForm: HTML, Sendable {
let rooms: [DuctSizing.RoomContainer] let rooms: [DuctSizing.RoomContainer]
let dismiss: Bool let dismiss: Bool
var trunk: DuctSizing.TrunkSize? { var trunk: TrunkSize? {
container?.trunk container?.trunk
} }
@@ -56,7 +56,7 @@ struct TrunkSizeForm: HTML, Sendable {
label(.class("select w-full")) { label(.class("select w-full")) {
span(.class("label")) { "Type" } span(.class("label")) { "Type" }
select(.name("type")) { select(.name("type")) {
for type in DuctSizing.TrunkSize.TrunkType.allCases { for type in TrunkSize.TrunkType.allCases {
option(.value(type.rawValue)) { type.rawValue.capitalized } option(.value(type.rawValue)) { type.rawValue.capitalized }
.attributes(.selected, when: trunk?.type == type) .attributes(.selected, when: trunk?.type == type)
} }
@@ -121,7 +121,7 @@ struct TrunkSizeForm: HTML, Sendable {
} }
extension Array where Element == DuctSizing.TrunkSize.RoomProxy { extension Array where Element == TrunkSize.RoomProxy {
func hasRoom(_ room: DuctSizing.RoomContainer) -> Bool { func hasRoom(_ room: DuctSizing.RoomContainer) -> Bool {
first { first {
$0.id == room.roomID $0.id == room.roomID