Compare commits
4 Commits
5f03056534
...
57766c990e
| Author | SHA1 | Date | |
|---|---|---|---|
|
57766c990e
|
|||
|
b2b5e32535
|
|||
|
881737978d
|
|||
|
6a764ade2b
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@ tailwindcss
|
|||||||
.env
|
.env
|
||||||
.env*
|
.env*
|
||||||
default.profraw
|
default.profraw
|
||||||
|
rooms.csv
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ let package = Package(
|
|||||||
products: [
|
products: [
|
||||||
.executable(name: "App", targets: ["App"]),
|
.executable(name: "App", targets: ["App"]),
|
||||||
.library(name: "AuthClient", targets: ["AuthClient"]),
|
.library(name: "AuthClient", targets: ["AuthClient"]),
|
||||||
|
.library(name: "CSVParser", targets: ["CSVParser"]),
|
||||||
.library(name: "DatabaseClient", targets: ["DatabaseClient"]),
|
.library(name: "DatabaseClient", targets: ["DatabaseClient"]),
|
||||||
.library(name: "EnvVars", targets: ["EnvVars"]),
|
.library(name: "EnvVars", targets: ["EnvVars"]),
|
||||||
.library(name: "FileClient", targets: ["FileClient"]),
|
.library(name: "FileClient", targets: ["FileClient"]),
|
||||||
@@ -62,6 +63,20 @@ let package = Package(
|
|||||||
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
.target(
|
||||||
|
name: "CSVParser",
|
||||||
|
dependencies: [
|
||||||
|
.target(name: "ManualDCore"),
|
||||||
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "CSVParsingTests",
|
||||||
|
dependencies: [
|
||||||
|
.target(name: "CSVParser")
|
||||||
|
]
|
||||||
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "DatabaseClient",
|
name: "DatabaseClient",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -172,6 +187,7 @@ let package = Package(
|
|||||||
name: "ViewController",
|
name: "ViewController",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.target(name: "AuthClient"),
|
.target(name: "AuthClient"),
|
||||||
|
.target(name: "CSVParser"),
|
||||||
.target(name: "DatabaseClient"),
|
.target(name: "DatabaseClient"),
|
||||||
.target(name: "PdfClient"),
|
.target(name: "PdfClient"),
|
||||||
.target(name: "ProjectClient"),
|
.target(name: "ProjectClient"),
|
||||||
|
|||||||
43
Sources/CSVParser/Interface.swift
Normal file
43
Sources/CSVParser/Interface.swift
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import Dependencies
|
||||||
|
import DependenciesMacros
|
||||||
|
import ManualDCore
|
||||||
|
import Parsing
|
||||||
|
|
||||||
|
extension DependencyValues {
|
||||||
|
public var csvParser: CSVParser {
|
||||||
|
get { self[CSVParser.self] }
|
||||||
|
set { self[CSVParser.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
public struct CSVParser: Sendable {
|
||||||
|
public var parseRooms: @Sendable (Room.CSV) async throws -> [Room.Create]
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CSVParser: DependencyKey {
|
||||||
|
public static let testValue = Self()
|
||||||
|
|
||||||
|
public static let liveValue = Self(
|
||||||
|
parseRooms: { csv in
|
||||||
|
guard let string = String(data: csv.file, encoding: .utf8) else {
|
||||||
|
throw CSVParsingError("Unreadable file data")
|
||||||
|
}
|
||||||
|
let rows = try RoomCSVParser().parse(string[...].utf8)
|
||||||
|
let rooms = rows.reduce(into: [Room.Create]()) {
|
||||||
|
if case .room(let room) = $1 {
|
||||||
|
$0.append(room)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rooms
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct CSVParsingError: Error {
|
||||||
|
let reason: String
|
||||||
|
|
||||||
|
public init(_ reason: String) {
|
||||||
|
self.reason = reason
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Sources/CSVParser/Internal/Room+parsing.swift
Normal file
51
Sources/CSVParser/Internal/Room+parsing.swift
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import ManualDCore
|
||||||
|
import Parsing
|
||||||
|
|
||||||
|
struct RoomCSVParser: Parser {
|
||||||
|
var body: some Parser<Substring.UTF8View, [RoomRowType]> {
|
||||||
|
Many {
|
||||||
|
RoomRowParser()
|
||||||
|
} separator: {
|
||||||
|
"\n".utf8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RoomRowParser: Parser {
|
||||||
|
|
||||||
|
var body: some Parser<Substring.UTF8View, RoomRowType> {
|
||||||
|
OneOf {
|
||||||
|
RoomCreateParser().map { RoomRowType.room($0) }
|
||||||
|
Prefix { $0 != UInt8(ascii: "\n") }
|
||||||
|
.map(.string)
|
||||||
|
.map { RoomRowType.header($0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RoomRowType {
|
||||||
|
case header(String)
|
||||||
|
case room(Room.Create)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RoomCreateParser: ParserPrinter {
|
||||||
|
|
||||||
|
var body: some ParserPrinter<Substring.UTF8View, Room.Create> {
|
||||||
|
ParsePrint {
|
||||||
|
Prefix { $0 != UInt8(ascii: ",") }.map(.string)
|
||||||
|
",".utf8
|
||||||
|
Double.parser()
|
||||||
|
",".utf8
|
||||||
|
Optionally {
|
||||||
|
Double.parser()
|
||||||
|
}
|
||||||
|
",".utf8
|
||||||
|
Optionally {
|
||||||
|
Double.parser()
|
||||||
|
}
|
||||||
|
",".utf8
|
||||||
|
Int.parser()
|
||||||
|
}
|
||||||
|
.map(.memberwise(Room.Create.init))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,8 +89,8 @@ public struct DatabaseClient: Sendable {
|
|||||||
|
|
||||||
@DependencyClient
|
@DependencyClient
|
||||||
public struct Rooms: Sendable {
|
public struct Rooms: Sendable {
|
||||||
public var create: @Sendable (Room.Create) async throws -> Room
|
public var create: @Sendable (Project.ID, Room.Create) async throws -> Room
|
||||||
public var createMany: @Sendable ([Room.Create]) async throws -> [Room]
|
public var createMany: @Sendable (Project.ID, [Room.Create]) async throws -> [Room]
|
||||||
public var delete: @Sendable (Room.ID) async throws -> Void
|
public var delete: @Sendable (Room.ID) async throws -> Void
|
||||||
public var deleteRectangularSize:
|
public var deleteRectangularSize:
|
||||||
@Sendable (Room.ID, Room.RectangularSize.ID) async throws -> Room
|
@Sendable (Room.ID, Room.RectangularSize.ID) async throws -> Room
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ extension DatabaseClient.Rooms: TestDependencyKey {
|
|||||||
|
|
||||||
public static func live(database: any Database) -> Self {
|
public static func live(database: any Database) -> Self {
|
||||||
.init(
|
.init(
|
||||||
create: { request in
|
create: { projectID, request in
|
||||||
let model = try request.toModel()
|
let model = try request.toModel(projectID: projectID)
|
||||||
try await model.validateAndSave(on: database)
|
try await model.validateAndSave(on: database)
|
||||||
return try model.toDTO()
|
return try model.toDTO()
|
||||||
},
|
},
|
||||||
createMany: { rooms in
|
createMany: { projectID, rooms in
|
||||||
try await rooms.asyncMap { request in
|
try await rooms.asyncMap { request in
|
||||||
let model = try request.toModel()
|
let model = try request.toModel(projectID: projectID)
|
||||||
try await model.validateAndSave(on: database)
|
try await model.validateAndSave(on: database)
|
||||||
return try model.toDTO()
|
return try model.toDTO()
|
||||||
}
|
}
|
||||||
@@ -83,12 +83,11 @@ extension DatabaseClient.Rooms: TestDependencyKey {
|
|||||||
|
|
||||||
extension Room.Create {
|
extension Room.Create {
|
||||||
|
|
||||||
func toModel() throws -> RoomModel {
|
func toModel(projectID: Project.ID) throws -> RoomModel {
|
||||||
return .init(
|
return .init(
|
||||||
name: name,
|
name: name,
|
||||||
heatingLoad: heatingLoad,
|
heatingLoad: heatingLoad,
|
||||||
coolingTotal: coolingTotal,
|
coolingLoad: coolingLoad,
|
||||||
coolingSensible: coolingSensible,
|
|
||||||
registerCount: registerCount,
|
registerCount: registerCount,
|
||||||
projectID: projectID
|
projectID: projectID
|
||||||
)
|
)
|
||||||
@@ -104,8 +103,7 @@ extension Room {
|
|||||||
.id()
|
.id()
|
||||||
.field("name", .string, .required)
|
.field("name", .string, .required)
|
||||||
.field("heatingLoad", .double, .required)
|
.field("heatingLoad", .double, .required)
|
||||||
.field("coolingTotal", .double, .required)
|
.field("coolingLoad", .dictionary, .required)
|
||||||
.field("coolingSensible", .double)
|
|
||||||
.field("registerCount", .int8, .required)
|
.field("registerCount", .int8, .required)
|
||||||
.field("rectangularSizes", .array)
|
.field("rectangularSizes", .array)
|
||||||
.field("createdAt", .datetime)
|
.field("createdAt", .datetime)
|
||||||
@@ -136,11 +134,8 @@ final class RoomModel: Model, @unchecked Sendable, Validatable {
|
|||||||
@Field(key: "heatingLoad")
|
@Field(key: "heatingLoad")
|
||||||
var heatingLoad: Double
|
var heatingLoad: Double
|
||||||
|
|
||||||
@Field(key: "coolingTotal")
|
@Field(key: "coolingLoad")
|
||||||
var coolingTotal: Double
|
var coolingLoad: Room.CoolingLoad
|
||||||
|
|
||||||
@Field(key: "coolingSensible")
|
|
||||||
var coolingSensible: Double?
|
|
||||||
|
|
||||||
@Field(key: "registerCount")
|
@Field(key: "registerCount")
|
||||||
var registerCount: Int
|
var registerCount: Int
|
||||||
@@ -163,8 +158,7 @@ final class RoomModel: Model, @unchecked Sendable, Validatable {
|
|||||||
id: UUID? = nil,
|
id: UUID? = nil,
|
||||||
name: String,
|
name: String,
|
||||||
heatingLoad: Double,
|
heatingLoad: Double,
|
||||||
coolingTotal: Double,
|
coolingLoad: Room.CoolingLoad,
|
||||||
coolingSensible: Double? = nil,
|
|
||||||
registerCount: Int,
|
registerCount: Int,
|
||||||
rectangularSizes: [Room.RectangularSize]? = nil,
|
rectangularSizes: [Room.RectangularSize]? = nil,
|
||||||
createdAt: Date? = nil,
|
createdAt: Date? = nil,
|
||||||
@@ -174,8 +168,7 @@ final class RoomModel: Model, @unchecked Sendable, Validatable {
|
|||||||
self.id = id
|
self.id = id
|
||||||
self.name = name
|
self.name = name
|
||||||
self.heatingLoad = heatingLoad
|
self.heatingLoad = heatingLoad
|
||||||
self.coolingTotal = coolingTotal
|
self.coolingLoad = coolingLoad
|
||||||
self.coolingSensible = coolingSensible
|
|
||||||
self.registerCount = registerCount
|
self.registerCount = registerCount
|
||||||
self.rectangularSizes = rectangularSizes
|
self.rectangularSizes = rectangularSizes
|
||||||
self.createdAt = createdAt
|
self.createdAt = createdAt
|
||||||
@@ -189,8 +182,7 @@ final class RoomModel: Model, @unchecked Sendable, Validatable {
|
|||||||
projectID: $project.id,
|
projectID: $project.id,
|
||||||
name: name,
|
name: name,
|
||||||
heatingLoad: heatingLoad,
|
heatingLoad: heatingLoad,
|
||||||
coolingTotal: coolingTotal,
|
coolingLoad: coolingLoad,
|
||||||
coolingSensible: coolingSensible,
|
|
||||||
registerCount: registerCount,
|
registerCount: registerCount,
|
||||||
rectangularSizes: rectangularSizes,
|
rectangularSizes: rectangularSizes,
|
||||||
createdAt: createdAt!,
|
createdAt: createdAt!,
|
||||||
@@ -206,11 +198,8 @@ final class RoomModel: Model, @unchecked Sendable, Validatable {
|
|||||||
if let heatingLoad = updates.heatingLoad, heatingLoad != self.heatingLoad {
|
if let heatingLoad = updates.heatingLoad, heatingLoad != self.heatingLoad {
|
||||||
self.heatingLoad = heatingLoad
|
self.heatingLoad = heatingLoad
|
||||||
}
|
}
|
||||||
if let coolingTotal = updates.coolingTotal, coolingTotal != self.coolingTotal {
|
if let coolingLoad = updates.coolingLoad, coolingLoad != self.coolingLoad {
|
||||||
self.coolingTotal = coolingTotal
|
self.coolingLoad = coolingLoad
|
||||||
}
|
|
||||||
if let coolingSensible = updates.coolingSensible, coolingSensible != self.coolingSensible {
|
|
||||||
self.coolingSensible = coolingSensible
|
|
||||||
}
|
}
|
||||||
if let registerCount = updates.registerCount, registerCount != self.registerCount {
|
if let registerCount = updates.registerCount, registerCount != self.registerCount {
|
||||||
self.registerCount = registerCount
|
self.registerCount = registerCount
|
||||||
@@ -229,11 +218,8 @@ final class RoomModel: Model, @unchecked Sendable, Validatable {
|
|||||||
Validator.validate(\.heatingLoad, with: .greaterThanOrEquals(0))
|
Validator.validate(\.heatingLoad, with: .greaterThanOrEquals(0))
|
||||||
.errorLabel("Heating Load", inline: true)
|
.errorLabel("Heating Load", inline: true)
|
||||||
|
|
||||||
Validator.validate(\.coolingTotal, with: .greaterThanOrEquals(0))
|
Validator.validate(\.coolingLoad)
|
||||||
.errorLabel("Cooling Total", inline: true)
|
.errorLabel("Cooling Load", inline: true)
|
||||||
|
|
||||||
Validator.validate(\.coolingSensible, with: Double.greaterThanOrEquals(0).optional())
|
|
||||||
.errorLabel("Cooling Sensible", inline: true)
|
|
||||||
|
|
||||||
Validator.validate(\.registerCount, with: .greaterThanOrEquals(1))
|
Validator.validate(\.registerCount, with: .greaterThanOrEquals(1))
|
||||||
.errorLabel("Register Count", inline: true)
|
.errorLabel("Register Count", inline: true)
|
||||||
@@ -244,6 +230,25 @@ final class RoomModel: Model, @unchecked Sendable, Validatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Room.CoolingLoad: Validatable {
|
||||||
|
|
||||||
|
public var body: some Validation<Self> {
|
||||||
|
Validator.accumulating {
|
||||||
|
// Ensure that at least one of the values is not nil.
|
||||||
|
Validator.oneOf {
|
||||||
|
Validator.validate(\.total, with: .notNil())
|
||||||
|
.errorLabel("Total or Sensible", inline: true)
|
||||||
|
Validator.validate(\.sensible, with: .notNil())
|
||||||
|
.errorLabel("Total or Sensible", inline: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
Validator.validate(\.total, with: Double.greaterThan(0).optional())
|
||||||
|
Validator.validate(\.sensible, with: Double.greaterThan(0).optional())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
extension Room.RectangularSize: Validatable {
|
extension Room.RectangularSize: Validatable {
|
||||||
|
|
||||||
public var body: some Validation<Self> {
|
public var body: some Validation<Self> {
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ import ManualDCore
|
|||||||
|
|
||||||
extension Room {
|
extension Room {
|
||||||
|
|
||||||
var heatingLoadPerRegister: Double {
|
public var heatingLoadPerRegister: Double {
|
||||||
|
|
||||||
heatingLoad / Double(registerCount)
|
heatingLoad / Double(registerCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func coolingSensiblePerRegister(projectSHR: Double) -> Double {
|
public func coolingSensiblePerRegister(projectSHR: Double) throws -> Double {
|
||||||
let sensible = coolingSensible ?? (coolingTotal * projectSHR)
|
let sensible = try coolingLoad.ensured(shr: projectSHR).sensible
|
||||||
return sensible / Double(registerCount)
|
return sensible / Double(registerCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,8 +29,8 @@ extension TrunkSize.RoomProxy {
|
|||||||
room.heatingLoadPerRegister * Double(actualRegisterCount)
|
room.heatingLoadPerRegister * Double(actualRegisterCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func totalCoolingSensible(projectSHR: Double) -> Double {
|
func totalCoolingSensible(projectSHR: Double) throws -> Double {
|
||||||
room.coolingSensiblePerRegister(projectSHR: projectSHR) * Double(actualRegisterCount)
|
try room.coolingSensiblePerRegister(projectSHR: projectSHR) * Double(actualRegisterCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,8 +40,8 @@ extension TrunkSize {
|
|||||||
rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad }
|
rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad }
|
||||||
}
|
}
|
||||||
|
|
||||||
func totalCoolingSensible(projectSHR: Double) -> Double {
|
func totalCoolingSensible(projectSHR: Double) throws -> Double {
|
||||||
rooms.reduce(into: 0) { $0 += $1.totalCoolingSensible(projectSHR: projectSHR) }
|
try rooms.reduce(into: 0) { $0 += try $1.totalCoolingSensible(projectSHR: projectSHR) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import Foundation
|
// import Foundation
|
||||||
|
|
||||||
public struct CoolingLoad: Codable, Equatable, Sendable {
|
//
|
||||||
public let total: Double
|
// public struct CoolingLoad: Codable, Equatable, Sendable {
|
||||||
public let sensible: Double
|
// public let total: Double
|
||||||
public var latent: Double { total - sensible }
|
// public let sensible: Double
|
||||||
public var shr: Double { sensible / total }
|
// public var latent: Double { total - sensible }
|
||||||
|
// public var shr: Double { sensible / total }
|
||||||
public init(total: Double, sensible: Double) {
|
//
|
||||||
self.total = total
|
// public init(total: Double, sensible: Double) {
|
||||||
self.sensible = sensible
|
// self.total = total
|
||||||
}
|
// self.sensible = sensible
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|||||||
@@ -152,11 +152,12 @@ extension DuctSizes {
|
|||||||
public static func mock(
|
public static func mock(
|
||||||
equipmentInfo: EquipmentInfo,
|
equipmentInfo: EquipmentInfo,
|
||||||
rooms: [Room],
|
rooms: [Room],
|
||||||
trunks: [TrunkSize]
|
trunks: [TrunkSize],
|
||||||
|
shr: Double
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
||||||
let totalHeatingLoad = rooms.totalHeatingLoad
|
let totalHeatingLoad = rooms.totalHeatingLoad
|
||||||
let totalCoolingLoad = rooms.totalCoolingLoad
|
let totalCoolingLoad = try! rooms.totalCoolingLoad(shr: shr)
|
||||||
|
|
||||||
let roomContainers = rooms.reduce(into: [RoomContainer]()) { array, room in
|
let roomContainers = rooms.reduce(into: [RoomContainer]()) { array, room in
|
||||||
array += RoomContainer.mock(
|
array += RoomContainer.mock(
|
||||||
@@ -164,7 +165,8 @@ extension DuctSizes {
|
|||||||
totalHeatingLoad: totalHeatingLoad,
|
totalHeatingLoad: totalHeatingLoad,
|
||||||
totalCoolingLoad: totalCoolingLoad,
|
totalCoolingLoad: totalCoolingLoad,
|
||||||
totalHeatingCFM: Double(equipmentInfo.heatingCFM),
|
totalHeatingCFM: Double(equipmentInfo.heatingCFM),
|
||||||
totalCoolingCFM: Double(equipmentInfo.coolingCFM)
|
totalCoolingCFM: Double(equipmentInfo.coolingCFM),
|
||||||
|
shr: shr
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,14 +189,15 @@ extension DuctSizes {
|
|||||||
totalHeatingLoad: Double,
|
totalHeatingLoad: Double,
|
||||||
totalCoolingLoad: Double,
|
totalCoolingLoad: Double,
|
||||||
totalHeatingCFM: Double,
|
totalHeatingCFM: Double,
|
||||||
totalCoolingCFM: Double
|
totalCoolingCFM: Double,
|
||||||
|
shr: Double
|
||||||
) -> [Self] {
|
) -> [Self] {
|
||||||
var retval = [DuctSizes.RoomContainer]()
|
var retval = [DuctSizes.RoomContainer]()
|
||||||
let heatingLoad = room.heatingLoad / Double(room.registerCount)
|
let heatingLoad = room.heatingLoad / Double(room.registerCount)
|
||||||
let heatingFraction = heatingLoad / totalHeatingLoad
|
let heatingFraction = heatingLoad / totalHeatingLoad
|
||||||
let heatingCFM = totalHeatingCFM * heatingFraction
|
let heatingCFM = totalHeatingCFM * heatingFraction
|
||||||
// Not really accurate, but works for mocks.
|
// 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 coolingFraction = coolingLoad / totalCoolingLoad
|
||||||
let coolingCFM = totalCoolingCFM * coolingFraction
|
let coolingCFM = totalCoolingCFM * coolingFraction
|
||||||
|
|
||||||
|
|||||||
@@ -9,19 +9,19 @@ import Foundation
|
|||||||
public struct Room: Codable, Equatable, Identifiable, Sendable {
|
public struct Room: Codable, Equatable, Identifiable, Sendable {
|
||||||
/// The unique id of the room.
|
/// The unique id of the room.
|
||||||
public let id: UUID
|
public let id: UUID
|
||||||
|
|
||||||
/// The project this room is associated with.
|
/// The project this room is associated with.
|
||||||
public let projectID: Project.ID
|
public let projectID: Project.ID
|
||||||
|
|
||||||
/// A unique name for the room in the project.
|
/// A unique name for the room in the project.
|
||||||
public let name: String
|
public let name: String
|
||||||
|
|
||||||
/// The heating load required for the room (from Manual-J).
|
/// The heating load required for the room (from Manual-J).
|
||||||
public let heatingLoad: Double
|
public let heatingLoad: Double
|
||||||
/// The total cooling load required for the room (from Manual-J).
|
|
||||||
public let coolingTotal: Double
|
/// The cooling load required for the room (from Manual-J).
|
||||||
/// An optional sensible cooling load for the room.
|
public let coolingLoad: CoolingLoad
|
||||||
///
|
|
||||||
/// **NOTE:** This is generally not set, but calculated from the project wide
|
|
||||||
/// sensible heat ratio.
|
|
||||||
public let coolingSensible: Double?
|
|
||||||
/// The number of registers for the room.
|
/// The number of registers for the room.
|
||||||
public let registerCount: Int
|
public let registerCount: Int
|
||||||
/// The rectangular duct size calculations for a room.
|
/// 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
|
/// **NOTE:** These are optionally set after the round sizes have been calculate
|
||||||
/// for a room.
|
/// for a room.
|
||||||
public let rectangularSizes: [RectangularSize]?
|
public let rectangularSizes: [RectangularSize]?
|
||||||
|
|
||||||
/// When the room was created in the database.
|
/// When the room was created in the database.
|
||||||
public let createdAt: Date
|
public let createdAt: Date
|
||||||
|
|
||||||
/// When the room was updated in the database.
|
/// When the room was updated in the database.
|
||||||
public let updatedAt: Date
|
public let updatedAt: Date
|
||||||
|
|
||||||
@@ -39,8 +41,7 @@ public struct Room: Codable, Equatable, Identifiable, Sendable {
|
|||||||
projectID: Project.ID,
|
projectID: Project.ID,
|
||||||
name: String,
|
name: String,
|
||||||
heatingLoad: Double,
|
heatingLoad: Double,
|
||||||
coolingTotal: Double,
|
coolingLoad: CoolingLoad,
|
||||||
coolingSensible: Double? = nil,
|
|
||||||
registerCount: Int = 1,
|
registerCount: Int = 1,
|
||||||
rectangularSizes: [RectangularSize]? = nil,
|
rectangularSizes: [RectangularSize]? = nil,
|
||||||
createdAt: Date,
|
createdAt: Date,
|
||||||
@@ -50,40 +51,73 @@ public struct Room: Codable, Equatable, Identifiable, Sendable {
|
|||||||
self.projectID = projectID
|
self.projectID = projectID
|
||||||
self.name = name
|
self.name = name
|
||||||
self.heatingLoad = heatingLoad
|
self.heatingLoad = heatingLoad
|
||||||
self.coolingTotal = coolingTotal
|
self.coolingLoad = coolingLoad
|
||||||
self.coolingSensible = coolingSensible
|
|
||||||
self.registerCount = registerCount
|
self.registerCount = registerCount
|
||||||
self.rectangularSizes = rectangularSizes
|
self.rectangularSizes = rectangularSizes
|
||||||
self.createdAt = createdAt
|
self.createdAt = createdAt
|
||||||
self.updatedAt = updatedAt
|
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 {
|
extension Room {
|
||||||
/// Represents the data required to create a new room for a project.
|
/// Represents the data required to create a new room for a project.
|
||||||
public struct Create: Codable, Equatable, Sendable {
|
public struct Create: Codable, Equatable, Sendable {
|
||||||
/// The project this room is associated with.
|
|
||||||
public let projectID: Project.ID
|
|
||||||
/// A unique name for the room in the project.
|
/// A unique name for the room in the project.
|
||||||
public let name: String
|
public let name: String
|
||||||
/// The heating load required for the room (from Manual-J).
|
/// The heating load required for the room (from Manual-J).
|
||||||
public let heatingLoad: Double
|
public let heatingLoad: Double
|
||||||
/// The total cooling load required for the room (from Manual-J).
|
/// 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.
|
/// An optional sensible cooling load for the room.
|
||||||
public let coolingSensible: Double?
|
public let coolingSensible: Double?
|
||||||
/// The number of registers for the room.
|
/// The number of registers for the room.
|
||||||
public let registerCount: Int
|
public let registerCount: Int
|
||||||
|
|
||||||
|
public var coolingLoad: Room.CoolingLoad {
|
||||||
|
.init(total: coolingTotal, sensible: coolingSensible)
|
||||||
|
}
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
projectID: Project.ID,
|
|
||||||
name: String,
|
name: String,
|
||||||
heatingLoad: Double,
|
heatingLoad: Double,
|
||||||
coolingTotal: Double,
|
coolingTotal: Double? = nil,
|
||||||
coolingSensible: Double? = nil,
|
coolingSensible: Double? = nil,
|
||||||
registerCount: Int = 1
|
registerCount: Int = 1
|
||||||
) {
|
) {
|
||||||
self.projectID = projectID
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.heatingLoad = heatingLoad
|
self.heatingLoad = heatingLoad
|
||||||
self.coolingTotal = coolingTotal
|
self.coolingTotal = coolingTotal
|
||||||
@@ -92,6 +126,14 @@ extension Room {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct CSV: Equatable, Sendable {
|
||||||
|
public let file: Data
|
||||||
|
|
||||||
|
public init(file: Data) {
|
||||||
|
self.file = file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a rectangular size calculation that is stored in the
|
/// Represents a rectangular size calculation that is stored in the
|
||||||
/// database for a given room.
|
/// database for a given room.
|
||||||
///
|
///
|
||||||
@@ -118,7 +160,7 @@ extension Room {
|
|||||||
|
|
||||||
/// Represents field that can be updated on a room after it's been created in the database.
|
/// 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 {
|
public struct Update: Codable, Equatable, Sendable {
|
||||||
/// A unique name for the room in the project.
|
/// A unique name for the room in the project.
|
||||||
public let name: String?
|
public let name: String?
|
||||||
@@ -133,6 +175,13 @@ extension Room {
|
|||||||
/// The rectangular duct size calculations for a room.
|
/// The rectangular duct size calculations for a room.
|
||||||
public let rectangularSizes: [RectangularSize]?
|
public let rectangularSizes: [RectangularSize]?
|
||||||
|
|
||||||
|
public var coolingLoad: CoolingLoad? {
|
||||||
|
guard coolingTotal != nil || coolingSensible != nil else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .init(total: coolingTotal, sensible: coolingSensible)
|
||||||
|
}
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
name: String? = nil,
|
name: String? = nil,
|
||||||
heatingLoad: Double? = nil,
|
heatingLoad: Double? = nil,
|
||||||
@@ -169,22 +218,30 @@ extension Array where Element == Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The sum of total cooling loads for an array of rooms.
|
/// The sum of total cooling loads for an array of rooms.
|
||||||
public var totalCoolingLoad: Double {
|
public func totalCoolingLoad(shr: Double) throws -> Double {
|
||||||
reduce(into: 0) { $0 += $1.coolingTotal }
|
try reduce(into: 0) { $0 += try $1.coolingLoad.ensured(shr: shr).total }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The sum of sensible cooling loads for an array of rooms.
|
/// The sum of sensible cooling loads for an array of rooms.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - shr: The project wide sensible heat ratio.
|
/// - shr: The project wide sensible heat ratio.
|
||||||
public func totalCoolingSensible(shr: Double) -> Double {
|
public func totalCoolingSensible(shr: Double) throws -> Double {
|
||||||
reduce(into: 0) {
|
try reduce(into: 0) {
|
||||||
let sensible = $1.coolingSensible ?? ($1.coolingTotal * shr)
|
// let sensible = $1.coolingSensible ?? ($1.coolingTotal * shr)
|
||||||
$0 += sensible
|
$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
|
#if DEBUG
|
||||||
|
|
||||||
extension Room {
|
extension Room {
|
||||||
@@ -199,8 +256,8 @@ extension Array where Element == Room {
|
|||||||
projectID: projectID,
|
projectID: projectID,
|
||||||
name: "Bed-1",
|
name: "Bed-1",
|
||||||
heatingLoad: 3913,
|
heatingLoad: 3913,
|
||||||
coolingTotal: 2472,
|
coolingLoad: .init(total: 2472),
|
||||||
coolingSensible: nil,
|
// coolingSensible: nil,
|
||||||
registerCount: 1,
|
registerCount: 1,
|
||||||
rectangularSizes: nil,
|
rectangularSizes: nil,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
@@ -211,8 +268,8 @@ extension Array where Element == Room {
|
|||||||
projectID: projectID,
|
projectID: projectID,
|
||||||
name: "Entry",
|
name: "Entry",
|
||||||
heatingLoad: 8284,
|
heatingLoad: 8284,
|
||||||
coolingTotal: 2916,
|
coolingLoad: .init(total: 2916),
|
||||||
coolingSensible: nil,
|
// coolingSensible: nil,
|
||||||
registerCount: 2,
|
registerCount: 2,
|
||||||
rectangularSizes: nil,
|
rectangularSizes: nil,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
@@ -223,8 +280,8 @@ extension Array where Element == Room {
|
|||||||
projectID: projectID,
|
projectID: projectID,
|
||||||
name: "Family Room",
|
name: "Family Room",
|
||||||
heatingLoad: 9785,
|
heatingLoad: 9785,
|
||||||
coolingTotal: 7446,
|
coolingLoad: .init(total: 7446),
|
||||||
coolingSensible: nil,
|
// coolingSensible: nil,
|
||||||
registerCount: 3,
|
registerCount: 3,
|
||||||
rectangularSizes: nil,
|
rectangularSizes: nil,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
@@ -235,8 +292,8 @@ extension Array where Element == Room {
|
|||||||
projectID: projectID,
|
projectID: projectID,
|
||||||
name: "Kitchen",
|
name: "Kitchen",
|
||||||
heatingLoad: 4518,
|
heatingLoad: 4518,
|
||||||
coolingTotal: 5096,
|
coolingLoad: .init(total: 5096),
|
||||||
coolingSensible: nil,
|
// coolingSensible: nil,
|
||||||
registerCount: 2,
|
registerCount: 2,
|
||||||
rectangularSizes: nil,
|
rectangularSizes: nil,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
@@ -247,8 +304,8 @@ extension Array where Element == Room {
|
|||||||
projectID: projectID,
|
projectID: projectID,
|
||||||
name: "Living Room",
|
name: "Living Room",
|
||||||
heatingLoad: 7553,
|
heatingLoad: 7553,
|
||||||
coolingTotal: 6829,
|
coolingLoad: .init(total: 6829),
|
||||||
coolingSensible: nil,
|
// coolingSensible: nil,
|
||||||
registerCount: 2,
|
registerCount: 2,
|
||||||
rectangularSizes: nil,
|
rectangularSizes: nil,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
@@ -259,8 +316,8 @@ extension Array where Element == Room {
|
|||||||
projectID: projectID,
|
projectID: projectID,
|
||||||
name: "Master",
|
name: "Master",
|
||||||
heatingLoad: 8202,
|
heatingLoad: 8202,
|
||||||
coolingTotal: 2076,
|
coolingLoad: .init(total: 2076),
|
||||||
coolingSensible: nil,
|
// coolingSensible: nil,
|
||||||
registerCount: 2,
|
registerCount: 2,
|
||||||
rectangularSizes: nil,
|
rectangularSizes: nil,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum RoomRoute: Equatable, Sendable {
|
public enum RoomRoute: Equatable, Sendable {
|
||||||
|
case csv(Room.CSV)
|
||||||
case delete(id: Room.ID)
|
case delete(id: Room.ID)
|
||||||
case index
|
case index
|
||||||
case submit(Room.Create)
|
case submit(Room.Create)
|
||||||
@@ -197,6 +198,23 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
static let rootPath = "rooms"
|
static let rootPath = "rooms"
|
||||||
|
|
||||||
public static let router = OneOf {
|
public static let router = OneOf {
|
||||||
|
Route(.case(Self.csv)) {
|
||||||
|
Path {
|
||||||
|
rootPath
|
||||||
|
"csv"
|
||||||
|
}
|
||||||
|
Headers {
|
||||||
|
Field("Content-Type") { "multipart/form-data" }
|
||||||
|
}
|
||||||
|
Method.post
|
||||||
|
Body().map(.memberwise(Room.CSV.init))
|
||||||
|
// Body {
|
||||||
|
// FormData {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// .map(.memberwise(Room.CSV.init))
|
||||||
|
// }
|
||||||
|
}
|
||||||
Route(.case(Self.delete)) {
|
Route(.case(Self.delete)) {
|
||||||
Path {
|
Path {
|
||||||
rootPath
|
rootPath
|
||||||
@@ -215,12 +233,14 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
Method.post
|
Method.post
|
||||||
Body {
|
Body {
|
||||||
FormData {
|
FormData {
|
||||||
Field("projectID") { Project.ID.parser() }
|
// Field("projectID") { Project.ID.parser() }
|
||||||
Field("name", .string)
|
Field("name", .string)
|
||||||
Field("heatingLoad") { Double.parser() }
|
Field("heatingLoad") { Double.parser() }
|
||||||
Field("coolingTotal") { Double.parser() }
|
|
||||||
Optionally {
|
Optionally {
|
||||||
Field("coolingSensible", default: nil) { Double.parser() }
|
Field("coolingTotal") { Double.parser() }
|
||||||
|
}
|
||||||
|
Optionally {
|
||||||
|
Field("coolingSensible") { Double.parser() }
|
||||||
}
|
}
|
||||||
Field("registerCount") { Digits() }
|
Field("registerCount") { Digits() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,12 @@ extension PdfClient {
|
|||||||
project: project,
|
project: project,
|
||||||
rooms: rooms,
|
rooms: rooms,
|
||||||
componentLosses: ComponentPressureLoss.mock(projectID: project.id),
|
componentLosses: ComponentPressureLoss.mock(projectID: project.id),
|
||||||
ductSizes: .mock(equipmentInfo: equipmentInfo, rooms: rooms, trunks: trunks),
|
ductSizes: .mock(
|
||||||
|
equipmentInfo: equipmentInfo,
|
||||||
|
rooms: rooms,
|
||||||
|
trunks: trunks,
|
||||||
|
shr: project.sensibleHeatRatio ?? 0.83
|
||||||
|
),
|
||||||
equipmentInfo: equipmentInfo,
|
equipmentInfo: equipmentInfo,
|
||||||
maxSupplyTEL: equivalentLengths.first { $0.type == .supply }!,
|
maxSupplyTEL: equivalentLengths.first { $0.type == .supply }!,
|
||||||
maxReturnTEL: equivalentLengths.first { $0.type == .return }!,
|
maxReturnTEL: equivalentLengths.first { $0.type == .return }!,
|
||||||
|
|||||||
@@ -21,10 +21,9 @@ struct RoomsTable: HTML, Sendable {
|
|||||||
tr {
|
tr {
|
||||||
td { room.name }
|
td { room.name }
|
||||||
td { room.heatingLoad.string(digits: 0) }
|
td { room.heatingLoad.string(digits: 0) }
|
||||||
td { room.coolingTotal.string(digits: 0) }
|
td { try! room.coolingLoad.ensured(shr: projectSHR).total.string(digits: 0) }
|
||||||
td {
|
td {
|
||||||
(room.coolingSensible
|
try! room.coolingLoad.ensured(shr: projectSHR).sensible.string(digits: 0)
|
||||||
?? (room.coolingTotal * projectSHR)).string(digits: 0)
|
|
||||||
}
|
}
|
||||||
td { room.registerCount.string() }
|
td { room.registerCount.string() }
|
||||||
}
|
}
|
||||||
@@ -37,10 +36,10 @@ struct RoomsTable: HTML, Sendable {
|
|||||||
rooms.totalHeatingLoad.string(digits: 0)
|
rooms.totalHeatingLoad.string(digits: 0)
|
||||||
}
|
}
|
||||||
td(.class("coolingTotal label")) {
|
td(.class("coolingTotal label")) {
|
||||||
rooms.totalCoolingLoad.string(digits: 0)
|
try! rooms.totalCoolingLoad(shr: projectSHR).string(digits: 0)
|
||||||
}
|
}
|
||||||
td(.class("coolingSensible label")) {
|
td(.class("coolingSensible label")) {
|
||||||
rooms.totalCoolingSensible(shr: projectSHR).string(digits: 0)
|
try! rooms.totalCoolingSensible(shr: projectSHR).string(digits: 0)
|
||||||
}
|
}
|
||||||
td {}
|
td {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,11 +41,11 @@ extension ManualDClient {
|
|||||||
|
|
||||||
var retval: [DuctSizes.RoomContainer] = []
|
var retval: [DuctSizes.RoomContainer] = []
|
||||||
let totalHeatingLoad = rooms.totalHeatingLoad
|
let totalHeatingLoad = rooms.totalHeatingLoad
|
||||||
let totalCoolingSensible = rooms.totalCoolingSensible(shr: sharedRequest.projectSHR)
|
let totalCoolingSensible = try rooms.totalCoolingSensible(shr: sharedRequest.projectSHR)
|
||||||
|
|
||||||
for room in rooms {
|
for room in rooms {
|
||||||
let heatingLoad = room.heatingLoadPerRegister
|
let heatingLoad = room.heatingLoadPerRegister
|
||||||
let coolingLoad = room.coolingSensiblePerRegister(projectSHR: sharedRequest.projectSHR)
|
let coolingLoad = try room.coolingSensiblePerRegister(projectSHR: sharedRequest.projectSHR)
|
||||||
let heatingPercent = heatingLoad / totalHeatingLoad
|
let heatingPercent = heatingLoad / totalHeatingLoad
|
||||||
let coolingPercent = coolingLoad / totalCoolingSensible
|
let coolingPercent = coolingLoad / totalCoolingSensible
|
||||||
let heatingCFM = heatingPercent * Double(sharedRequest.equipmentInfo.heatingCFM)
|
let heatingCFM = heatingPercent * Double(sharedRequest.equipmentInfo.heatingCFM)
|
||||||
@@ -102,11 +102,11 @@ extension ManualDClient {
|
|||||||
|
|
||||||
var retval = [DuctSizes.TrunkContainer]()
|
var retval = [DuctSizes.TrunkContainer]()
|
||||||
let totalHeatingLoad = rooms.totalHeatingLoad
|
let totalHeatingLoad = rooms.totalHeatingLoad
|
||||||
let totalCoolingSensible = rooms.totalCoolingSensible(shr: sharedRequest.projectSHR)
|
let totalCoolingSensible = try rooms.totalCoolingSensible(shr: sharedRequest.projectSHR)
|
||||||
|
|
||||||
for trunk in trunks {
|
for trunk in trunks {
|
||||||
let heatingLoad = trunk.totalHeatingLoad
|
let heatingLoad = trunk.totalHeatingLoad
|
||||||
let coolingLoad = trunk.totalCoolingSensible(projectSHR: sharedRequest.projectSHR)
|
let coolingLoad = try trunk.totalCoolingSensible(projectSHR: sharedRequest.projectSHR)
|
||||||
let heatingPercent = heatingLoad / totalHeatingLoad
|
let heatingPercent = heatingLoad / totalHeatingLoad
|
||||||
let coolingPercent = coolingLoad / totalCoolingSensible
|
let coolingPercent = coolingLoad / totalCoolingSensible
|
||||||
let heatingCFM = heatingPercent * Double(sharedRequest.equipmentInfo.heatingCFM)
|
let heatingCFM = heatingPercent * Double(sharedRequest.equipmentInfo.heatingCFM)
|
||||||
@@ -181,18 +181,18 @@ extension DuctSizes.SizeContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Room {
|
// extension Room {
|
||||||
|
//
|
||||||
var heatingLoadPerRegister: Double {
|
// var heatingLoadPerRegister: Double {
|
||||||
|
//
|
||||||
heatingLoad / Double(registerCount)
|
// heatingLoad / Double(registerCount)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func coolingSensiblePerRegister(projectSHR: Double) -> Double {
|
// func coolingSensiblePerRegister(projectSHR: Double) -> Double {
|
||||||
let sensible = coolingSensible ?? (coolingTotal * projectSHR)
|
// let sensible = coolingSensible ?? (coolingTotal * projectSHR)
|
||||||
return sensible / Double(registerCount)
|
// return sensible / Double(registerCount)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
extension TrunkSize.RoomProxy {
|
extension TrunkSize.RoomProxy {
|
||||||
|
|
||||||
@@ -210,8 +210,8 @@ extension TrunkSize.RoomProxy {
|
|||||||
room.heatingLoadPerRegister * Double(actualRegisterCount)
|
room.heatingLoadPerRegister * Double(actualRegisterCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func totalCoolingSensible(projectSHR: Double) -> Double {
|
func totalCoolingSensible(projectSHR: Double) throws -> Double {
|
||||||
room.coolingSensiblePerRegister(projectSHR: projectSHR) * Double(actualRegisterCount)
|
try room.coolingSensiblePerRegister(projectSHR: projectSHR) * Double(actualRegisterCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ extension TrunkSize {
|
|||||||
rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad }
|
rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad }
|
||||||
}
|
}
|
||||||
|
|
||||||
func totalCoolingSensible(projectSHR: Double) -> Double {
|
func totalCoolingSensible(projectSHR: Double) throws -> Double {
|
||||||
rooms.reduce(into: 0) { $0 += $1.totalCoolingSensible(projectSHR: projectSHR) }
|
try rooms.reduce(into: 0) { $0 += try $1.totalCoolingSensible(projectSHR: projectSHR) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import CSVParser
|
||||||
import DatabaseClient
|
import DatabaseClient
|
||||||
import Dependencies
|
import Dependencies
|
||||||
import Elementary
|
import Elementary
|
||||||
@@ -284,10 +285,18 @@ extension SiteRoute.View.ProjectRoute.RoomRoute {
|
|||||||
on request: ViewController.Request,
|
on request: ViewController.Request,
|
||||||
projectID: Project.ID
|
projectID: Project.ID
|
||||||
) async -> AnySendableHTML {
|
) async -> AnySendableHTML {
|
||||||
|
@Dependency(\.csvParser) var csvParser
|
||||||
@Dependency(\.database) var database
|
@Dependency(\.database) var database
|
||||||
|
|
||||||
switch self {
|
switch self {
|
||||||
|
|
||||||
|
case .csv(let csv):
|
||||||
|
return await roomsView(on: request, projectID: projectID) {
|
||||||
|
let rooms = try await csvParser.parseRooms(csv)
|
||||||
|
_ = try await database.rooms.createMany(projectID, rooms)
|
||||||
|
}
|
||||||
|
// return EmptyHTML()
|
||||||
|
|
||||||
case .delete(let id):
|
case .delete(let id):
|
||||||
return await ResultView {
|
return await ResultView {
|
||||||
try await database.rooms.delete(id)
|
try await database.rooms.delete(id)
|
||||||
@@ -298,7 +307,7 @@ extension SiteRoute.View.ProjectRoute.RoomRoute {
|
|||||||
|
|
||||||
case .submit(let form):
|
case .submit(let form):
|
||||||
return await roomsView(on: request, projectID: projectID) {
|
return await roomsView(on: request, projectID: projectID) {
|
||||||
_ = try await database.rooms.create(form)
|
_ = try await database.rooms.create(projectID, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
case .update(let id, let form):
|
case .update(let id, let form):
|
||||||
|
|||||||
@@ -47,8 +47,6 @@ struct RoomForm: HTML, Sendable {
|
|||||||
.hx.swap(.outerHTML)
|
.hx.swap(.outerHTML)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
input(.class("hidden"), .name("projectID"), .value("\(projectID)"))
|
|
||||||
|
|
||||||
if let id = room?.id {
|
if let id = room?.id {
|
||||||
input(.class("hidden"), .name("id"), .value("\(id)"))
|
input(.class("hidden"), .name("id"), .value("\(id)"))
|
||||||
}
|
}
|
||||||
@@ -73,14 +71,15 @@ struct RoomForm: HTML, Sendable {
|
|||||||
.value(room?.heatingLoad)
|
.value(room?.heatingLoad)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: Add description that only one is required (cooling total or sensible)
|
||||||
|
|
||||||
LabeledInput(
|
LabeledInput(
|
||||||
"Cooling Total",
|
"Cooling Total",
|
||||||
.name("coolingTotal"),
|
.name("coolingTotal"),
|
||||||
.type(.number),
|
.type(.number),
|
||||||
.placeholder("1234"),
|
.placeholder("1234 (Optional)"),
|
||||||
.required,
|
|
||||||
.min("0"),
|
.min("0"),
|
||||||
.value(room?.coolingTotal)
|
.value(room?.coolingLoad.total)
|
||||||
)
|
)
|
||||||
|
|
||||||
LabeledInput(
|
LabeledInput(
|
||||||
@@ -89,7 +88,7 @@ struct RoomForm: HTML, Sendable {
|
|||||||
.type(.number),
|
.type(.number),
|
||||||
.placeholder("1234 (Optional)"),
|
.placeholder("1234 (Optional)"),
|
||||||
.min("0"),
|
.min("0"),
|
||||||
.value(room?.coolingSensible)
|
.value(room?.coolingLoad.sensible)
|
||||||
)
|
)
|
||||||
|
|
||||||
LabeledInput(
|
LabeledInput(
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ struct RoomsView: HTML, Sendable {
|
|||||||
let rooms: [Room]
|
let rooms: [Room]
|
||||||
let sensibleHeatRatio: Double?
|
let sensibleHeatRatio: Double?
|
||||||
|
|
||||||
|
private var csvRoute: String {
|
||||||
|
SiteRoute.router.path(for: .view(.project(.detail(projectID, .rooms(.index)))))
|
||||||
|
.appendingPath("csv")
|
||||||
|
}
|
||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
div(.class("flex w-full flex-col")) {
|
div(.class("flex w-full flex-col")) {
|
||||||
PageTitleRow {
|
PageTitleRow {
|
||||||
@@ -44,6 +49,18 @@ struct RoomsView: HTML, Sendable {
|
|||||||
.attributes(.class("border border-error"), when: sensibleHeatRatio == nil)
|
.attributes(.class("border border-error"), when: sensibleHeatRatio == nil)
|
||||||
}
|
}
|
||||||
.attributes(.class("tooltip-open"), when: sensibleHeatRatio == nil)
|
.attributes(.class("tooltip-open"), when: sensibleHeatRatio == nil)
|
||||||
|
|
||||||
|
Tooltip("Upload csv file", position: .left) {
|
||||||
|
form(
|
||||||
|
.hx.post(csvRoute),
|
||||||
|
.hx.target("body"),
|
||||||
|
.hx.swap(.outerHTML),
|
||||||
|
.custom(name: "enctype", value: "multipart/form-data")
|
||||||
|
) {
|
||||||
|
input(.type(.file), .name("file"), .accept(".csv"))
|
||||||
|
SubmitButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div(.class("flex items-end space-x-4 font-bold")) {
|
div(.class("flex items-end space-x-4 font-bold")) {
|
||||||
@@ -54,13 +71,15 @@ struct RoomsView: HTML, Sendable {
|
|||||||
|
|
||||||
div(.class("flex justify-center items-end space-x-4 my-auto font-bold")) {
|
div(.class("flex justify-center items-end space-x-4 my-auto font-bold")) {
|
||||||
span(.class("text-lg")) { "Cooling Total" }
|
span(.class("text-lg")) { "Cooling Total" }
|
||||||
Badge(number: rooms.totalCoolingLoad, digits: 0)
|
// TODO: ResultView ??
|
||||||
|
Badge(number: try! rooms.totalCoolingLoad(shr: sensibleHeatRatio ?? 1.0), digits: 0)
|
||||||
.attributes(.class("badge-success"))
|
.attributes(.class("badge-success"))
|
||||||
}
|
}
|
||||||
|
|
||||||
div(.class("flex grow justify-end items-end space-x-4 me-4 my-auto font-bold")) {
|
div(.class("flex grow justify-end items-end space-x-4 me-4 my-auto font-bold")) {
|
||||||
span(.class("text-lg")) { "Cooling Sensible" }
|
span(.class("text-lg")) { "Cooling Sensible" }
|
||||||
Badge(number: rooms.totalCoolingSensible(shr: sensibleHeatRatio ?? 1.0), digits: 0)
|
// TODO: ResultView ??
|
||||||
|
Badge(number: try! rooms.totalCoolingSensible(shr: sensibleHeatRatio ?? 1.0), digits: 0)
|
||||||
.attributes(.class("badge-info"))
|
.attributes(.class("badge-info"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,10 +144,11 @@ struct RoomsView: HTML, Sendable {
|
|||||||
let shr: Double
|
let shr: Double
|
||||||
|
|
||||||
var coolingSensible: Double {
|
var coolingSensible: Double {
|
||||||
guard let value = room.coolingSensible else {
|
try! room.coolingLoad.ensured(shr: shr).sensible
|
||||||
return room.coolingTotal * shr
|
// guard let value = room.coolingSensible else {
|
||||||
}
|
// return room.coolingTotal * shr
|
||||||
return value
|
// }
|
||||||
|
// return value
|
||||||
}
|
}
|
||||||
|
|
||||||
init(room: Room, shr: Double?) {
|
init(room: Room, shr: Double?) {
|
||||||
@@ -147,7 +167,7 @@ struct RoomsView: HTML, Sendable {
|
|||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
div(.class("flex justify-center")) {
|
div(.class("flex justify-center")) {
|
||||||
Number(room.coolingTotal, digits: 0)
|
Number(try! room.coolingLoad.ensured(shr: shr).total, digits: 0)
|
||||||
// .attributes(.class("text-success"))
|
// .attributes(.class("text-success"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
Tests/CSVParsingTests/CSVParsingTests.swift
Normal file
22
Tests/CSVParsingTests/CSVParsingTests.swift
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import CSVParser
|
||||||
|
import Foundation
|
||||||
|
import Testing
|
||||||
|
|
||||||
|
@Suite
|
||||||
|
struct CSVParsingTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func roomParsing() async throws {
|
||||||
|
|
||||||
|
let parser = CSVParser.liveValue
|
||||||
|
|
||||||
|
let input = """
|
||||||
|
Name,Heating Load,Cooling Total,Cooling Sensible,Register Count
|
||||||
|
Bed-1,12345,12345,,2
|
||||||
|
Bed-2,1223,,1123,1
|
||||||
|
"""
|
||||||
|
let rooms = try await parser.parseRooms(.init(file: Data(input.utf8)))
|
||||||
|
|
||||||
|
#expect(rooms.count == 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,7 +85,8 @@ struct ProjectTests {
|
|||||||
#expect(completed.frictionRate == true)
|
#expect(completed.frictionRate == true)
|
||||||
|
|
||||||
_ = try await database.rooms.create(
|
_ = try await database.rooms.create(
|
||||||
.init(projectID: project.id, name: "Test", heatingLoad: 12345, coolingTotal: 12345)
|
project.id,
|
||||||
|
.init(name: "Test", heatingLoad: 12345, coolingTotal: 12345)
|
||||||
)
|
)
|
||||||
completed = try await database.projects.getCompletedSteps(project.id)
|
completed = try await database.projects.getCompletedSteps(project.id)
|
||||||
#expect(completed.rooms == true)
|
#expect(completed.rooms == true)
|
||||||
@@ -130,7 +131,8 @@ struct ProjectTests {
|
|||||||
.init(projectID: project.id, name: "Test", value: 0.2)
|
.init(projectID: project.id, name: "Test", value: 0.2)
|
||||||
)
|
)
|
||||||
let room = try await database.rooms.create(
|
let room = try await database.rooms.create(
|
||||||
.init(projectID: project.id, name: "Test", heatingLoad: 12345, coolingTotal: 12345)
|
project.id,
|
||||||
|
.init(name: "Test", heatingLoad: 12345, coolingTotal: 12345)
|
||||||
)
|
)
|
||||||
let supplyLength = try await database.equivalentLengths.create(
|
let supplyLength = try await database.equivalentLengths.create(
|
||||||
.init(
|
.init(
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import Foundation
|
import Foundation
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
import Parsing
|
||||||
import Testing
|
import Testing
|
||||||
import Validations
|
|
||||||
|
|
||||||
@testable import DatabaseClient
|
@testable import DatabaseClient
|
||||||
|
|
||||||
@@ -15,7 +15,8 @@ struct RoomTests {
|
|||||||
@Dependency(\.database.rooms) var rooms
|
@Dependency(\.database.rooms) var rooms
|
||||||
|
|
||||||
let room = try await rooms.create(
|
let room = try await rooms.create(
|
||||||
.init(projectID: project.id, name: "Test", heatingLoad: 1234, coolingTotal: 1234)
|
project.id,
|
||||||
|
.init(name: "Test", heatingLoad: 1234, coolingTotal: 1234)
|
||||||
)
|
)
|
||||||
|
|
||||||
let fetched = try await rooms.fetch(project.id)
|
let fetched = try await rooms.fetch(project.id)
|
||||||
@@ -48,10 +49,13 @@ struct RoomTests {
|
|||||||
try await withTestUserAndProject { _, project in
|
try await withTestUserAndProject { _, project in
|
||||||
@Dependency(\.database.rooms) var rooms
|
@Dependency(\.database.rooms) var rooms
|
||||||
|
|
||||||
let created = try await rooms.createMany([
|
let created = try await rooms.createMany(
|
||||||
.init(projectID: project.id, name: "Test 1", heatingLoad: 1234, coolingTotal: 1234),
|
project.id,
|
||||||
.init(projectID: project.id, name: "Test 2", heatingLoad: 1234, coolingTotal: 1234),
|
[
|
||||||
])
|
.init(name: "Test 1", heatingLoad: 1234, coolingTotal: 1234),
|
||||||
|
.init(name: "Test 2", heatingLoad: 1234, coolingTotal: 1234),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
#expect(created.count == 2)
|
#expect(created.count == 2)
|
||||||
#expect(created[0].name == "Test 1")
|
#expect(created[0].name == "Test 1")
|
||||||
@@ -85,7 +89,7 @@ struct RoomTests {
|
|||||||
@Test(
|
@Test(
|
||||||
arguments: [
|
arguments: [
|
||||||
Room.Create(
|
Room.Create(
|
||||||
projectID: UUID(0),
|
// projectID: UUID(0),
|
||||||
name: "",
|
name: "",
|
||||||
heatingLoad: 12345,
|
heatingLoad: 12345,
|
||||||
coolingTotal: 12344,
|
coolingTotal: 12344,
|
||||||
@@ -93,7 +97,7 @@ struct RoomTests {
|
|||||||
registerCount: 1
|
registerCount: 1
|
||||||
),
|
),
|
||||||
Room.Create(
|
Room.Create(
|
||||||
projectID: UUID(0),
|
// projectID: UUID(0),
|
||||||
name: "Test",
|
name: "Test",
|
||||||
heatingLoad: -12345,
|
heatingLoad: -12345,
|
||||||
coolingTotal: 12344,
|
coolingTotal: 12344,
|
||||||
@@ -101,7 +105,7 @@ struct RoomTests {
|
|||||||
registerCount: 1
|
registerCount: 1
|
||||||
),
|
),
|
||||||
Room.Create(
|
Room.Create(
|
||||||
projectID: UUID(0),
|
// projectID: UUID(0),
|
||||||
name: "Test",
|
name: "Test",
|
||||||
heatingLoad: 12345,
|
heatingLoad: 12345,
|
||||||
coolingTotal: -12344,
|
coolingTotal: -12344,
|
||||||
@@ -109,7 +113,7 @@ struct RoomTests {
|
|||||||
registerCount: 1
|
registerCount: 1
|
||||||
),
|
),
|
||||||
Room.Create(
|
Room.Create(
|
||||||
projectID: UUID(0),
|
// projectID: UUID(0),
|
||||||
name: "Test",
|
name: "Test",
|
||||||
heatingLoad: 12345,
|
heatingLoad: 12345,
|
||||||
coolingTotal: 12344,
|
coolingTotal: 12344,
|
||||||
@@ -117,7 +121,7 @@ struct RoomTests {
|
|||||||
registerCount: 1
|
registerCount: 1
|
||||||
),
|
),
|
||||||
Room.Create(
|
Room.Create(
|
||||||
projectID: UUID(0),
|
// projectID: UUID(0),
|
||||||
name: "Test",
|
name: "Test",
|
||||||
heatingLoad: 12345,
|
heatingLoad: 12345,
|
||||||
coolingTotal: 12344,
|
coolingTotal: 12344,
|
||||||
@@ -125,19 +129,27 @@ struct RoomTests {
|
|||||||
registerCount: -1
|
registerCount: -1
|
||||||
),
|
),
|
||||||
Room.Create(
|
Room.Create(
|
||||||
projectID: UUID(0),
|
// projectID: UUID(0),
|
||||||
name: "",
|
name: "",
|
||||||
heatingLoad: -12345,
|
heatingLoad: -12345,
|
||||||
coolingTotal: -12344,
|
coolingTotal: -12344,
|
||||||
coolingSensible: -1,
|
coolingSensible: -1,
|
||||||
registerCount: -1
|
registerCount: -1
|
||||||
),
|
),
|
||||||
|
Room.Create(
|
||||||
|
// projectID: UUID(0),
|
||||||
|
name: "Test",
|
||||||
|
heatingLoad: 12345,
|
||||||
|
coolingTotal: nil,
|
||||||
|
coolingSensible: nil,
|
||||||
|
registerCount: 1
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
func validations(room: Room.Create) throws {
|
func validations(room: Room.Create) throws {
|
||||||
#expect(throws: (any Error).self) {
|
#expect(throws: (any Error).self) {
|
||||||
// do {
|
// do {
|
||||||
try room.toModel().validate()
|
try room.toModel(projectID: UUID(0)).validate()
|
||||||
// } catch {
|
// } catch {
|
||||||
// print("\(error)")
|
// print("\(error)")
|
||||||
// throw error
|
// throw error
|
||||||
@@ -145,3 +157,4 @@ struct RoomTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ struct TrunkSizeTests {
|
|||||||
@Dependency(\.database) var database
|
@Dependency(\.database) var database
|
||||||
|
|
||||||
let room = try await database.rooms.create(
|
let room = try await database.rooms.create(
|
||||||
|
project.id,
|
||||||
.init(
|
.init(
|
||||||
projectID: project.id, name: "Test", heatingLoad: 12345, coolingTotal: 12345,
|
name: "Test", heatingLoad: 12345, coolingTotal: 12345,
|
||||||
coolingSensible: nil, registerCount: 5)
|
coolingSensible: nil, registerCount: 5
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
let trunk = try await database.trunkSizes.create(
|
let trunk = try await database.trunkSizes.create(
|
||||||
|
|||||||
@@ -103,7 +103,8 @@ struct ViewControllerTests {
|
|||||||
let mockDuctSizes = DuctSizes.mock(
|
let mockDuctSizes = DuctSizes.mock(
|
||||||
equipmentInfo: equipment,
|
equipmentInfo: equipment,
|
||||||
rooms: rooms,
|
rooms: rooms,
|
||||||
trunks: trunks
|
trunks: trunks,
|
||||||
|
shr: project.sensibleHeatRatio ?? 0.83
|
||||||
)
|
)
|
||||||
|
|
||||||
try await withDefaultDependencies {
|
try await withDefaultDependencies {
|
||||||
|
|||||||
@@ -63,6 +63,12 @@ p-6 w-full">
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tooltip tooltip-left" data-tip="Upload csv file">
|
||||||
|
<form hx-post="/projects/00000000-0000-0000-0000-000000000000/rooms/csv" hx-target="body" hx-swap="outerHTML" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="file" accept=".csv">
|
||||||
|
<button class="btn btn-secondary" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-end space-x-4 font-bold">
|
<div class="flex items-end space-x-4 font-bold">
|
||||||
<span class="text-lg">Heating Total</span>
|
<span class="text-lg">Heating Total</span>
|
||||||
@@ -151,14 +157,13 @@ p-6 w-full">
|
|||||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000001.close()"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000001.close()"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
||||||
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
||||||
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000001" hx-target="body" hx-swap="outerHTML">
|
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000001" hx-target="body" hx-swap="outerHTML">
|
||||||
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
|
||||||
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000001">
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000001">
|
||||||
Name<label class="input w-full"><span class="label"></span>
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
<input name="name" type="text" placeholder="Name" required autofocus value="Bed-1">
|
<input name="name" type="text" placeholder="Name" required autofocus value="Bed-1">
|
||||||
Heating Load</label><label class="input w-full"><span class="label"></span>
|
Heating Load</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="3913.0">
|
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="3913.0">
|
||||||
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="coolingTotal" type="number" placeholder="1234" required min="0" value="2472.0">
|
<input name="coolingTotal" type="number" placeholder="1234 (Optional)" min="0" value="2472.0">
|
||||||
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
|
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
|
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
|
||||||
Registers</label><label class="input w-full"><span class="label"></span>
|
Registers</label><label class="input w-full"><span class="label"></span>
|
||||||
@@ -204,14 +209,13 @@ p-6 w-full">
|
|||||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000002.close()"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000002.close()"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
||||||
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
||||||
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000002" hx-target="body" hx-swap="outerHTML">
|
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000002" hx-target="body" hx-swap="outerHTML">
|
||||||
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
|
||||||
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000002">
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000002">
|
||||||
Name<label class="input w-full"><span class="label"></span>
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
<input name="name" type="text" placeholder="Name" required autofocus value="Entry">
|
<input name="name" type="text" placeholder="Name" required autofocus value="Entry">
|
||||||
Heating Load</label><label class="input w-full"><span class="label"></span>
|
Heating Load</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="8284.0">
|
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="8284.0">
|
||||||
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="coolingTotal" type="number" placeholder="1234" required min="0" value="2916.0">
|
<input name="coolingTotal" type="number" placeholder="1234 (Optional)" min="0" value="2916.0">
|
||||||
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
|
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
|
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
|
||||||
Registers</label><label class="input w-full"><span class="label"></span>
|
Registers</label><label class="input w-full"><span class="label"></span>
|
||||||
@@ -257,14 +261,13 @@ p-6 w-full">
|
|||||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000003.close()"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000003.close()"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
||||||
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
||||||
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000003" hx-target="body" hx-swap="outerHTML">
|
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000003" hx-target="body" hx-swap="outerHTML">
|
||||||
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
|
||||||
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000003">
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000003">
|
||||||
Name<label class="input w-full"><span class="label"></span>
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
<input name="name" type="text" placeholder="Name" required autofocus value="Family Room">
|
<input name="name" type="text" placeholder="Name" required autofocus value="Family Room">
|
||||||
Heating Load</label><label class="input w-full"><span class="label"></span>
|
Heating Load</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="9785.0">
|
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="9785.0">
|
||||||
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="coolingTotal" type="number" placeholder="1234" required min="0" value="7446.0">
|
<input name="coolingTotal" type="number" placeholder="1234 (Optional)" min="0" value="7446.0">
|
||||||
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
|
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
|
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
|
||||||
Registers</label><label class="input w-full"><span class="label"></span>
|
Registers</label><label class="input w-full"><span class="label"></span>
|
||||||
@@ -310,14 +313,13 @@ p-6 w-full">
|
|||||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000004.close()"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000004.close()"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
||||||
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
||||||
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000004" hx-target="body" hx-swap="outerHTML">
|
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000004" hx-target="body" hx-swap="outerHTML">
|
||||||
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
|
||||||
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000004">
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000004">
|
||||||
Name<label class="input w-full"><span class="label"></span>
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
<input name="name" type="text" placeholder="Name" required autofocus value="Kitchen">
|
<input name="name" type="text" placeholder="Name" required autofocus value="Kitchen">
|
||||||
Heating Load</label><label class="input w-full"><span class="label"></span>
|
Heating Load</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="4518.0">
|
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="4518.0">
|
||||||
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="coolingTotal" type="number" placeholder="1234" required min="0" value="5096.0">
|
<input name="coolingTotal" type="number" placeholder="1234 (Optional)" min="0" value="5096.0">
|
||||||
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
|
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
|
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
|
||||||
Registers</label><label class="input w-full"><span class="label"></span>
|
Registers</label><label class="input w-full"><span class="label"></span>
|
||||||
@@ -363,14 +365,13 @@ p-6 w-full">
|
|||||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000005.close()"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000005.close()"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
||||||
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
||||||
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000005" hx-target="body" hx-swap="outerHTML">
|
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000005" hx-target="body" hx-swap="outerHTML">
|
||||||
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
|
||||||
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000005">
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000005">
|
||||||
Name<label class="input w-full"><span class="label"></span>
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
<input name="name" type="text" placeholder="Name" required autofocus value="Living Room">
|
<input name="name" type="text" placeholder="Name" required autofocus value="Living Room">
|
||||||
Heating Load</label><label class="input w-full"><span class="label"></span>
|
Heating Load</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="7553.0">
|
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="7553.0">
|
||||||
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="coolingTotal" type="number" placeholder="1234" required min="0" value="6829.0">
|
<input name="coolingTotal" type="number" placeholder="1234 (Optional)" min="0" value="6829.0">
|
||||||
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
|
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
|
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
|
||||||
Registers</label><label class="input w-full"><span class="label"></span>
|
Registers</label><label class="input w-full"><span class="label"></span>
|
||||||
@@ -416,14 +417,13 @@ p-6 w-full">
|
|||||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000006.close()"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000006.close()"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
||||||
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
||||||
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000006" hx-target="body" hx-swap="outerHTML">
|
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000006" hx-target="body" hx-swap="outerHTML">
|
||||||
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
|
||||||
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000006">
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000006">
|
||||||
Name<label class="input w-full"><span class="label"></span>
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
<input name="name" type="text" placeholder="Name" required autofocus value="Master">
|
<input name="name" type="text" placeholder="Name" required autofocus value="Master">
|
||||||
Heating Load</label><label class="input w-full"><span class="label"></span>
|
Heating Load</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="8202.0">
|
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="8202.0">
|
||||||
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="coolingTotal" type="number" placeholder="1234" required min="0" value="2076.0">
|
<input name="coolingTotal" type="number" placeholder="1234 (Optional)" min="0" value="2076.0">
|
||||||
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
|
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
|
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
|
||||||
Registers</label><label class="input w-full"><span class="label"></span>
|
Registers</label><label class="input w-full"><span class="label"></span>
|
||||||
@@ -441,13 +441,12 @@ p-6 w-full">
|
|||||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm.close()"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm.close()"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
||||||
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
||||||
<form class="grid grid-cols-1 gap-4" hx-post="/projects/00000000-0000-0000-0000-000000000000/rooms" hx-target="body" hx-swap="outerHTML">
|
<form class="grid grid-cols-1 gap-4" hx-post="/projects/00000000-0000-0000-0000-000000000000/rooms" hx-target="body" hx-swap="outerHTML">
|
||||||
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
<label class="input w-full"><span class="label">Name</span>
|
||||||
Name<label class="input w-full"><span class="label"></span>
|
|
||||||
<input name="name" type="text" placeholder="Name" required autofocus value="">
|
<input name="name" type="text" placeholder="Name" required autofocus value="">
|
||||||
Heating Load</label><label class="input w-full"><span class="label"></span>
|
Heating Load</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="">
|
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="">
|
||||||
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="coolingTotal" type="number" placeholder="1234" required min="0" value="">
|
<input name="coolingTotal" type="number" placeholder="1234 (Optional)" min="0" value="">
|
||||||
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
|
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
|
||||||
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
|
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
|
||||||
Registers</label><label class="input w-full"><span class="label"></span>
|
Registers</label><label class="input w-full"><span class="label"></span>
|
||||||
|
|||||||
Reference in New Issue
Block a user