WIP: Html view that prints to pdf ok.
This commit is contained in:
@@ -82,6 +82,7 @@ let package = Package(
|
|||||||
.target(name: "ManualDCore"),
|
.target(name: "ManualDCore"),
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
|
.product(name: "Elementary", package: "elementary"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
@@ -89,6 +90,7 @@ let package = Package(
|
|||||||
dependencies: [
|
dependencies: [
|
||||||
.target(name: "DatabaseClient"),
|
.target(name: "DatabaseClient"),
|
||||||
.target(name: "ManualDClient"),
|
.target(name: "ManualDClient"),
|
||||||
|
.target(name: "PdfClient"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import AuthClient
|
|||||||
import DatabaseClient
|
import DatabaseClient
|
||||||
import Dependencies
|
import Dependencies
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
import PdfClient
|
||||||
import Vapor
|
import Vapor
|
||||||
import ViewController
|
import ViewController
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ struct DependenciesMiddleware: AsyncMiddleware {
|
|||||||
$0.database = database
|
$0.database = database
|
||||||
// $0.dateFormatter = .liveValue
|
// $0.dateFormatter = .liveValue
|
||||||
$0.viewController = viewController
|
$0.viewController = viewController
|
||||||
|
$0.pdfClient = .liveValue
|
||||||
} operation: {
|
} operation: {
|
||||||
try await next.respond(to: request)
|
try await next.respond(to: request)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,8 @@ private func siteHandler(
|
|||||||
// FIX: Remove.
|
// FIX: Remove.
|
||||||
if route == .test {
|
if route == .test {
|
||||||
let projectID = UUID(uuidString: "E796C96C-F527-4753-A00A-EBCF25630663")!
|
let projectID = UUID(uuidString: "E796C96C-F527-4753-A00A-EBCF25630663")!
|
||||||
return try await projectClient.calculateDuctSizes(projectID)
|
// return try await projectClient.toMarkdown(projectID)
|
||||||
|
return try await AnyHTMLResponse(value: projectClient.toHTML(projectID))
|
||||||
}
|
}
|
||||||
return try await viewController.respond(route: route, request: request)
|
return try await viewController.respond(route: route, request: request)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,5 +130,19 @@ extension DuctSizes {
|
|||||||
public subscript<T>(dynamicMember keyPath: KeyPath<DuctSizes.SizeContainer, T>) -> T {
|
public subscript<T>(dynamicMember keyPath: KeyPath<DuctSizes.SizeContainer, T>) -> T {
|
||||||
ductSize[keyPath: keyPath]
|
ductSize[keyPath: keyPath]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func registerIDS(rooms: [RoomContainer]) -> [String] {
|
||||||
|
trunk.rooms.reduce(into: []) { array, room in
|
||||||
|
array = room.registers.reduce(into: array) { array, register in
|
||||||
|
if let room =
|
||||||
|
rooms
|
||||||
|
.first(where: { $0.roomID == room.id && $0.roomRegister == register })
|
||||||
|
{
|
||||||
|
array.append(room.roomName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sorted()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
public struct FrictionRate: Codable, Equatable, Sendable {
|
public struct FrictionRate: Codable, Equatable, Sendable {
|
||||||
public let availableStaticPressure: Double
|
public let availableStaticPressure: Double
|
||||||
public let value: Double
|
public let value: Double
|
||||||
|
public var hasErrors: Bool { error != nil }
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
availableStaticPressure: Double,
|
availableStaticPressure: Double,
|
||||||
@@ -11,4 +12,40 @@ public struct FrictionRate: Codable, Equatable, Sendable {
|
|||||||
self.availableStaticPressure = availableStaticPressure
|
self.availableStaticPressure = availableStaticPressure
|
||||||
self.value = value
|
self.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var error: FrictionRateError? {
|
||||||
|
if value >= 0.18 {
|
||||||
|
return .init(
|
||||||
|
"Friction rate should be lower than 0.18",
|
||||||
|
resolutions: [
|
||||||
|
"Decrease the blower speed",
|
||||||
|
"Decrease the blower size",
|
||||||
|
"Increase the Total Equivalent Length",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
} else if value <= 0.02 {
|
||||||
|
return .init(
|
||||||
|
"Friction rate should be higher than 0.02",
|
||||||
|
resolutions: [
|
||||||
|
"Increase the blower speed",
|
||||||
|
"Increase the blower size",
|
||||||
|
"Decrease the Total Equivalent Length",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct FrictionRateError: Error, Equatable, Sendable {
|
||||||
|
public let reason: String
|
||||||
|
public let resolutions: [String]
|
||||||
|
|
||||||
|
public init(
|
||||||
|
_ reason: String,
|
||||||
|
resolutions: [String]
|
||||||
|
) {
|
||||||
|
self.reason = reason
|
||||||
|
self.resolutions = resolutions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
Sources/ManualDCore/Numbers+string.swift
Normal file
24
Sources/ManualDCore/Numbers+string.swift
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Double {
|
||||||
|
|
||||||
|
public func string(digits: Int = 2) -> String {
|
||||||
|
numberString(self, digits: digits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Int {
|
||||||
|
|
||||||
|
public func string() -> String {
|
||||||
|
numberString(Double(self), digits: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func numberString(_ value: Double, digits: Int = 2) -> String {
|
||||||
|
let formatter = NumberFormatter()
|
||||||
|
formatter.maximumFractionDigits = digits
|
||||||
|
formatter.groupingSize = 3
|
||||||
|
formatter.groupingSeparator = ","
|
||||||
|
formatter.numberStyle = .decimal
|
||||||
|
return formatter.string(for: value)!
|
||||||
|
}
|
||||||
@@ -1,14 +1,33 @@
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import DependenciesMacros
|
import DependenciesMacros
|
||||||
|
import Elementary
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
|
extension DependencyValues {
|
||||||
|
|
||||||
|
public var pdfClient: PdfClient {
|
||||||
|
get { self[PdfClient.self] }
|
||||||
|
set { self[PdfClient.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@DependencyClient
|
@DependencyClient
|
||||||
public struct PdfClient: Sendable {
|
public struct PdfClient: Sendable {
|
||||||
|
public var html: @Sendable (Request) async throws -> (any HTML & Sendable)
|
||||||
public var markdown: @Sendable (Request) async throws -> String
|
public var markdown: @Sendable (Request) async throws -> String
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PdfClient: TestDependencyKey {
|
extension PdfClient: DependencyKey {
|
||||||
public static let testValue = Self()
|
public static let testValue = Self()
|
||||||
|
|
||||||
|
public static let liveValue = Self(
|
||||||
|
html: { request in
|
||||||
|
request.toHTML()
|
||||||
|
},
|
||||||
|
markdown: { request in
|
||||||
|
request.toMarkdown()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PdfClient {
|
extension PdfClient {
|
||||||
@@ -16,31 +35,34 @@ extension PdfClient {
|
|||||||
public struct Request: Codable, Equatable, Sendable {
|
public struct Request: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
public let project: Project
|
public let project: Project
|
||||||
|
public let rooms: [Room]
|
||||||
public let componentLosses: [ComponentPressureLoss]
|
public let componentLosses: [ComponentPressureLoss]
|
||||||
public let ductSizes: DuctSizes
|
public let ductSizes: DuctSizes
|
||||||
public let equipmentInfo: EquipmentInfo
|
public let equipmentInfo: EquipmentInfo
|
||||||
public let maxSupplyTEL: EffectiveLength
|
public let maxSupplyTEL: EffectiveLength
|
||||||
public let maxReturnTEL: EffectiveLength
|
public let maxReturnTEL: EffectiveLength
|
||||||
public let designFrictionRate: FrictionRate
|
public let frictionRate: FrictionRate
|
||||||
public let projectSHR: Double
|
public let projectSHR: Double
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
project: Project,
|
project: Project,
|
||||||
|
rooms: [Room],
|
||||||
componentLosses: [ComponentPressureLoss],
|
componentLosses: [ComponentPressureLoss],
|
||||||
ductSizes: DuctSizes,
|
ductSizes: DuctSizes,
|
||||||
equipmentInfo: EquipmentInfo,
|
equipmentInfo: EquipmentInfo,
|
||||||
maxSupplyTEL: EffectiveLength,
|
maxSupplyTEL: EffectiveLength,
|
||||||
maxReturnTEL: EffectiveLength,
|
maxReturnTEL: EffectiveLength,
|
||||||
designFrictionRate: FrictionRate,
|
frictionRate: FrictionRate,
|
||||||
projectSHR: Double
|
projectSHR: Double
|
||||||
) {
|
) {
|
||||||
self.project = project
|
self.project = project
|
||||||
|
self.rooms = rooms
|
||||||
self.componentLosses = componentLosses
|
self.componentLosses = componentLosses
|
||||||
self.ductSizes = ductSizes
|
self.ductSizes = ductSizes
|
||||||
self.equipmentInfo = equipmentInfo
|
self.equipmentInfo = equipmentInfo
|
||||||
self.maxSupplyTEL = maxSupplyTEL
|
self.maxSupplyTEL = maxSupplyTEL
|
||||||
self.maxReturnTEL = maxReturnTEL
|
self.maxReturnTEL = maxReturnTEL
|
||||||
self.designFrictionRate = designFrictionRate
|
self.frictionRate = frictionRate
|
||||||
self.projectSHR = projectSHR
|
self.projectSHR = projectSHR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
531
Sources/PdfClient/Request+html.swift
Normal file
531
Sources/PdfClient/Request+html.swift
Normal file
@@ -0,0 +1,531 @@
|
|||||||
|
import Elementary
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
extension PdfClient.Request {
|
||||||
|
|
||||||
|
func toHTML() -> (some HTML & Sendable) {
|
||||||
|
PdfDocument(request: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PdfDocument: HTMLDocument {
|
||||||
|
|
||||||
|
let title = "Duct Calc"
|
||||||
|
let lang = "en"
|
||||||
|
let request: PdfClient.Request
|
||||||
|
|
||||||
|
var head: some HTML {
|
||||||
|
style {
|
||||||
|
"""
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
table td, table th {
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
max-width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 10px auto;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.w-half {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
.table-footer {
|
||||||
|
background-color: #75af4c;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.bg-green {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.heating {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.coolingTotal {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
.coolingSensible {
|
||||||
|
color: cyan;
|
||||||
|
}
|
||||||
|
.justify-end {
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.flex table {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
width: 50%;
|
||||||
|
margin: 0;
|
||||||
|
flex: 1 1 calc(50% - 10px);
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.table-container {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.table-container table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
.customerTable {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.effectiveLengthGroupTable, .effectiveLengthGroupHeader {
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.headline {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some HTML {
|
||||||
|
div {
|
||||||
|
h1(.class("headline")) { "Duct Calc" }
|
||||||
|
|
||||||
|
h2 { "Project" }
|
||||||
|
|
||||||
|
div(.class("flex")) {
|
||||||
|
table(.class("table customer-table")) {
|
||||||
|
tbody {
|
||||||
|
tr {
|
||||||
|
td { "Name" }
|
||||||
|
td { request.project.name }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Street Address" }
|
||||||
|
td {
|
||||||
|
p {
|
||||||
|
request.project.streetAddress
|
||||||
|
br()
|
||||||
|
request.project.cityStateZipString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// HACK:
|
||||||
|
table {}
|
||||||
|
}
|
||||||
|
|
||||||
|
div(.class("section")) {
|
||||||
|
div(.class("flex")) {
|
||||||
|
h2 { "Equipment" }
|
||||||
|
h2 { "Friction Rate" }
|
||||||
|
}
|
||||||
|
div(.class("flex")) {
|
||||||
|
div(.class("container")) {
|
||||||
|
div(.class("table-container")) {
|
||||||
|
EquipmentTable(title: "Equipment", equipmentInfo: request.equipmentInfo)
|
||||||
|
}
|
||||||
|
// .attributes(.style("height: 140px;"))
|
||||||
|
div(.class("table-container")) {
|
||||||
|
FrictionRateTable(
|
||||||
|
title: "Friction Rate",
|
||||||
|
componentLosses: request.componentLosses,
|
||||||
|
frictionRate: request.frictionRate,
|
||||||
|
totalEquivalentLength: request.totalEquivalentLength,
|
||||||
|
displayTotals: false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let error = request.frictionRate.error {
|
||||||
|
div(.class("section")) {
|
||||||
|
p(.class("error")) {
|
||||||
|
error.reason
|
||||||
|
for resolution in error.resolutions {
|
||||||
|
br()
|
||||||
|
" * \(resolution)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div(.class("section")) {
|
||||||
|
h2 { "Duct Sizes" }
|
||||||
|
DuctSizesTable(rooms: request.ductSizes.rooms)
|
||||||
|
.attributes(.class("w-full"))
|
||||||
|
}
|
||||||
|
|
||||||
|
div(.class("section")) {
|
||||||
|
h2 { "Supply Trunk / Run Outs" }
|
||||||
|
TrunkTable(sizes: request.ductSizes, type: .supply)
|
||||||
|
.attributes(.class("w-full"))
|
||||||
|
}
|
||||||
|
|
||||||
|
div(.class("section")) {
|
||||||
|
h2 { "Return Trunk / Run Outs" }
|
||||||
|
TrunkTable(sizes: request.ductSizes, type: .return)
|
||||||
|
.attributes(.class("w-full"))
|
||||||
|
}
|
||||||
|
|
||||||
|
div(.class("section")) {
|
||||||
|
h2 { "Total Equivalent Lengths" }
|
||||||
|
EffectiveLengthsTable(effectiveLengths: [
|
||||||
|
request.maxSupplyTEL, request.maxReturnTEL,
|
||||||
|
])
|
||||||
|
.attributes(.class("w-full"))
|
||||||
|
}
|
||||||
|
|
||||||
|
div(.class("section")) {
|
||||||
|
h2 { "Register Detail" }
|
||||||
|
RegisterDetailTable(rooms: request.ductSizes.rooms)
|
||||||
|
.attributes(.class("w-full"))
|
||||||
|
}
|
||||||
|
|
||||||
|
div(.class("section")) {
|
||||||
|
h2 { "Room Detail" }
|
||||||
|
RoomsTable(rooms: request.rooms, projectSHR: request.projectSHR)
|
||||||
|
.attributes(.class("w-full"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EffectiveLengthsTable: HTML, Sendable {
|
||||||
|
let effectiveLengths: [EffectiveLength]
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
table {
|
||||||
|
thead {
|
||||||
|
tr(.class("bg-green")) {
|
||||||
|
th { "Name" }
|
||||||
|
th { "Type" }
|
||||||
|
th { "Straight Lengths" }
|
||||||
|
th { "Groups" }
|
||||||
|
th { "Total" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
for row in effectiveLengths {
|
||||||
|
tr {
|
||||||
|
td { row.name }
|
||||||
|
td { row.type.rawValue }
|
||||||
|
td {
|
||||||
|
ul {
|
||||||
|
for length in row.straightLengths {
|
||||||
|
li { length.string() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
EffectiveLengthGroupTable(groups: row.groups)
|
||||||
|
.attributes(.class("w-full"))
|
||||||
|
}
|
||||||
|
td { row.totalEquivalentLength.string(digits: 0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EffectiveLengthGroupTable: HTML, Sendable {
|
||||||
|
let groups: [EffectiveLength.Group]
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
table {
|
||||||
|
thead {
|
||||||
|
tr(.class("effectiveLengthGroupHeader")) {
|
||||||
|
th { "Name" }
|
||||||
|
th { "Length" }
|
||||||
|
th { "Quantity" }
|
||||||
|
th { "Total" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
for row in groups {
|
||||||
|
tr {
|
||||||
|
td { "\(row.group)-\(row.letter)" }
|
||||||
|
td { row.value.string(digits: 0) }
|
||||||
|
td { row.quantity.string() }
|
||||||
|
td { (row.value * Double(row.quantity)).string(digits: 0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RoomsTable: HTML, Sendable {
|
||||||
|
let rooms: [Room]
|
||||||
|
let projectSHR: Double
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
table {
|
||||||
|
thead {
|
||||||
|
tr(.class("bg-green")) {
|
||||||
|
th { "Name" }
|
||||||
|
th { "Heating BTU" }
|
||||||
|
th { "Cooling Total BTU" }
|
||||||
|
th { "Cooling Sensible BTU" }
|
||||||
|
th { "Register Count" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
for room in rooms {
|
||||||
|
tr {
|
||||||
|
td { room.name }
|
||||||
|
td { room.heatingLoad.string(digits: 0) }
|
||||||
|
td { room.coolingTotal.string(digits: 0) }
|
||||||
|
td {
|
||||||
|
(room.coolingSensible
|
||||||
|
?? (room.coolingTotal * projectSHR)).string(digits: 0)
|
||||||
|
}
|
||||||
|
td { room.registerCount.string() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Totals
|
||||||
|
// tr(.class("table-footer")) {
|
||||||
|
tr {
|
||||||
|
td(.class("label")) { "Totals" }
|
||||||
|
td(.class("heating label")) {
|
||||||
|
rooms.totalHeatingLoad.string(digits: 0)
|
||||||
|
}
|
||||||
|
td(.class("coolingTotal label")) {
|
||||||
|
rooms.totalCoolingLoad.string(digits: 0)
|
||||||
|
}
|
||||||
|
td(.class("coolingSensible label")) {
|
||||||
|
rooms.totalCoolingSensible(shr: projectSHR).string(digits: 0)
|
||||||
|
}
|
||||||
|
td {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RegisterDetailTable: HTML, Sendable {
|
||||||
|
let rooms: [DuctSizes.RoomContainer]
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
table {
|
||||||
|
thead {
|
||||||
|
tr(.class("bg-green")) {
|
||||||
|
th { "Name" }
|
||||||
|
th { "Heating BTU" }
|
||||||
|
th { "Cooling BTU" }
|
||||||
|
th { "Heating CFM" }
|
||||||
|
th { "Cooling CFM" }
|
||||||
|
th { "Design CFM" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
for row in rooms {
|
||||||
|
tr {
|
||||||
|
td { row.roomName }
|
||||||
|
td { row.heatingLoad.string(digits: 0) }
|
||||||
|
td { row.coolingLoad.string(digits: 0) }
|
||||||
|
td { row.heatingCFM.string(digits: 0) }
|
||||||
|
td { row.coolingCFM.string(digits: 0) }
|
||||||
|
td { row.designCFM.value.string(digits: 0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TrunkTable: HTML, Sendable {
|
||||||
|
public let sizes: DuctSizes
|
||||||
|
public let type: TrunkSize.TrunkType
|
||||||
|
|
||||||
|
var trunks: [DuctSizes.TrunkContainer] {
|
||||||
|
sizes.trunks.filter { $0.type == type }
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
table {
|
||||||
|
thead(.class("bg-green")) {
|
||||||
|
tr {
|
||||||
|
th { "Name" }
|
||||||
|
th { "Dsn CFM" }
|
||||||
|
th { "Round Size" }
|
||||||
|
th { "Velocity" }
|
||||||
|
th { "Final Size" }
|
||||||
|
th { "Flex Size" }
|
||||||
|
th { "Height" }
|
||||||
|
th { "Width" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
for row in trunks {
|
||||||
|
tr {
|
||||||
|
td { row.name ?? "" }
|
||||||
|
td { row.designCFM.value.string(digits: 0) }
|
||||||
|
td { row.ductSize.roundSize.string() }
|
||||||
|
td { row.velocity.string() }
|
||||||
|
td { row.finalSize.string() }
|
||||||
|
td { row.flexSize.string() }
|
||||||
|
td { row.ductSize.height?.string() ?? "" }
|
||||||
|
td { row.width?.string() ?? "" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DuctSizesTable: HTML, Sendable {
|
||||||
|
let rooms: [DuctSizes.RoomContainer]
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
table {
|
||||||
|
thead {
|
||||||
|
tr(.class("bg-green")) {
|
||||||
|
th { "Name" }
|
||||||
|
th { "Dsn CFM" }
|
||||||
|
th { "Round Size" }
|
||||||
|
th { "Velocity" }
|
||||||
|
th { "Final Size" }
|
||||||
|
th { "Flex Size" }
|
||||||
|
th { "Height" }
|
||||||
|
th { "Width" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
for row in rooms {
|
||||||
|
tr {
|
||||||
|
td { row.roomName }
|
||||||
|
td { row.designCFM.value.string(digits: 0) }
|
||||||
|
td { row.roundSize.string() }
|
||||||
|
td { row.velocity.string() }
|
||||||
|
td { row.flexSize.string() }
|
||||||
|
td { row.finalSize.string() }
|
||||||
|
td { row.ductSize.height?.string() ?? "" }
|
||||||
|
td { row.width?.string() ?? "" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EquipmentTable: HTML, Sendable {
|
||||||
|
let title: String?
|
||||||
|
let equipmentInfo: EquipmentInfo
|
||||||
|
|
||||||
|
init(title: String? = nil, equipmentInfo: EquipmentInfo) {
|
||||||
|
self.title = title
|
||||||
|
self.equipmentInfo = equipmentInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
|
||||||
|
table {
|
||||||
|
thead {
|
||||||
|
tr(.class("bg-green")) {
|
||||||
|
th { title ?? "" }
|
||||||
|
th(.class("justify-end")) { "Value" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
tr {
|
||||||
|
td { "Static Pressure" }
|
||||||
|
td(.class("justify-end")) { equipmentInfo.staticPressure.string() }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Heating CFM" }
|
||||||
|
td(.class("justify-end")) { equipmentInfo.heatingCFM.string() }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Cooling CFM" }
|
||||||
|
td(.class("justify-end")) { equipmentInfo.coolingCFM.string() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FrictionRateTable: HTML, Sendable {
|
||||||
|
let title: String?
|
||||||
|
let componentLosses: [ComponentPressureLoss]
|
||||||
|
let frictionRate: FrictionRate
|
||||||
|
let totalEquivalentLength: Double
|
||||||
|
let displayTotals: Bool
|
||||||
|
|
||||||
|
var sortedLosses: [ComponentPressureLoss] {
|
||||||
|
componentLosses.sorted { $0.value > $1.value }
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
table {
|
||||||
|
thead {
|
||||||
|
tr(.class("bg-green")) {
|
||||||
|
th { title ?? "" }
|
||||||
|
th(.class("justify-end")) { "Value" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
for row in sortedLosses {
|
||||||
|
tr {
|
||||||
|
td { row.name }
|
||||||
|
td(.class("justify-end")) { row.value.string() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if displayTotals {
|
||||||
|
tr {
|
||||||
|
td(.class("label justify-end")) { "Available Static Pressure" }
|
||||||
|
td(.class("justify-end")) { frictionRate.availableStaticPressure.string() }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td(.class("label justify-end")) { "Total Equivalent Length" }
|
||||||
|
td(.class("justify-end")) { totalEquivalentLength.string() }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td(.class("label justify-end")) { "Friction Rate Design Value" }
|
||||||
|
td(.class("justify-end")) { frictionRate.value.string() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Project {
|
||||||
|
var cityStateZipString: String {
|
||||||
|
return "\(city), \(state) \(zipCode)"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import Foundation
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
extension PdfClient.Request {
|
extension PdfClient.Request {
|
||||||
@@ -13,30 +14,100 @@ extension PdfClient.Request {
|
|||||||
## Equipment
|
## Equipment
|
||||||
|
|
||||||
| | Value |
|
| | Value |
|
||||||
|-----------------|---------------------------------|
|
|:----------------|:--------------------------------|
|
||||||
| Static Pressure | \(equipmentInfo.staticPressure) |
|
| Static Pressure | \(equipmentInfo.staticPressure.string()) |
|
||||||
| Heating CFM | \(equipmentInfo.heatingCFM) |
|
| Heating CFM | \(equipmentInfo.heatingCFM.string()) |
|
||||||
| Cooling CFM | \(equipmentInfo.coolingCFM) |
|
| Cooling CFM | \(equipmentInfo.coolingCFM.string()) |
|
||||||
|
|
||||||
## Friction Rate
|
## Friction Rate
|
||||||
|
|
||||||
| | Value |
|
| Component Loss | Value |
|
||||||
|-----------------|---------------------------------|
|
|:----------------|:--------------------------------|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for row in componentLosses {
|
for row in componentLosses {
|
||||||
retval = """
|
retval += "\(componentLossRow(row))\n"
|
||||||
\(retval)
|
}
|
||||||
\(componentLossRow(row))
|
|
||||||
|
retval += """
|
||||||
|
|
||||||
|
|
||||||
|
| Results | Value |
|
||||||
|
|:-----------------|:---------------------------------|
|
||||||
|
| Available Static Pressure | \(frictionRate.availableStaticPressure.string()) |
|
||||||
|
| Total Equivalent Length | \(totalEquivalentLength.string()) |
|
||||||
|
| Friction Rate Design Value | \(frictionRate.value.string()) |
|
||||||
|
|
||||||
|
## Duct Sizes
|
||||||
|
|
||||||
|
| Register | Dsn CFM | Round Size | Velocity | Final Size | Flex Size | Height | Width |
|
||||||
|
|:---------|:--------|:----------------|:---------|:-----------|:----------|:-------|:------|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
for row in ductSizes.rooms {
|
||||||
|
retval += "\(registerRow(row))\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
retval += """
|
||||||
|
|
||||||
|
## Trunk Sizes
|
||||||
|
|
||||||
|
### Supply Trunks
|
||||||
|
|
||||||
|
| Name | Associated Supplies | Dsn CFM | Velocity | Final Size | Flex Size | Height | Width |
|
||||||
|
|:---------|:--------------------|:--------|:---------|:-----------|:----------|:-------|:------|
|
||||||
|
|
||||||
|
"""
|
||||||
|
for row in ductSizes.trunks.filter({ $0.type == .supply }) {
|
||||||
|
retval += "\(trunkRow(row))\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
retval += """
|
||||||
|
|
||||||
|
### Return Trunks / Run Outs
|
||||||
|
|
||||||
|
| Name | Associated Supplies | Dsn CFM | Velocity | Final Size | Flex Size | Height | Width |
|
||||||
|
|:---------|:--------------------|:--------|:---------|:-----------|:----------|:-------|:------|
|
||||||
|
|
||||||
|
"""
|
||||||
|
for row in ductSizes.trunks.filter({ $0.type == .return }) {
|
||||||
|
retval += "\(trunkRow(row))\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
return retval
|
return retval
|
||||||
}
|
}
|
||||||
|
|
||||||
func componentLossRow(_ row: ComponentPressureLoss) -> String {
|
func registerRow(_ row: DuctSizes.RoomContainer) -> String {
|
||||||
return """
|
return """
|
||||||
| \(row.name) | \(row.value) |
|
| \(row.roomName) | \(row.designCFM.value.string(digits: 0)) | \(row.roundSize.string()) | \(row.velocity.string()) | \(row.finalSize.string()) | \(row.flexSize.string()) | \(row.height?.string() ?? "") | \(row.width?.string() ?? "") |
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func trunkRow(_ row: DuctSizes.TrunkContainer) -> String {
|
||||||
|
return """
|
||||||
|
| \(row.name ?? "") | \(associatedSupplyString(row)) | \(row.designCFM.value.string(digits: 0)) | \(row.roundSize.string()) | \(row.velocity.string()) | \(row.finalSize.string()) | \(row.flexSize.string()) | \(row.ductSize.height?.string() ?? "") | \(row.width?.string() ?? "") |
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
func componentLossRow(_ row: ComponentPressureLoss) -> String {
|
||||||
|
return """
|
||||||
|
| \(row.name) | \(row.value.string()) |
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalEquivalentLength: Double {
|
||||||
|
maxSupplyTEL.totalEquivalentLength + maxReturnTEL.totalEquivalentLength
|
||||||
|
}
|
||||||
|
|
||||||
|
func associatedSupplyString(_ row: DuctSizes.TrunkContainer) -> String {
|
||||||
|
row.associatedSupplyString(rooms: ductSizes.rooms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DuctSizes.TrunkContainer {
|
||||||
|
|
||||||
|
func associatedSupplyString(rooms: [DuctSizes.RoomContainer]) -> String {
|
||||||
|
self.registerIDS(rooms: rooms)
|
||||||
|
.joined(separator: ", ")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import DependenciesMacros
|
import DependenciesMacros
|
||||||
|
import Elementary
|
||||||
import ManualDClient
|
import ManualDClient
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
@@ -26,6 +27,10 @@ public struct ProjectClient: Sendable {
|
|||||||
@Sendable (User.ID, Project.Create) async throws -> CreateProjectResponse
|
@Sendable (User.ID, Project.Create) async throws -> CreateProjectResponse
|
||||||
|
|
||||||
public var frictionRate: @Sendable (Project.ID) async throws -> FrictionRateResponse
|
public var frictionRate: @Sendable (Project.ID) async throws -> FrictionRateResponse
|
||||||
|
|
||||||
|
// FIX: Name to something to do with generating a pdf, just experimenting now.
|
||||||
|
public var toMarkdown: @Sendable (Project.ID) async throws -> String
|
||||||
|
public var toHTML: @Sendable (Project.ID) async throws -> (any HTML & Sendable)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProjectClient: TestDependencyKey {
|
extension ProjectClient: TestDependencyKey {
|
||||||
|
|||||||
@@ -7,36 +7,53 @@ extension DatabaseClient {
|
|||||||
|
|
||||||
func calculateDuctSizes(
|
func calculateDuctSizes(
|
||||||
projectID: Project.ID
|
projectID: Project.ID
|
||||||
) async throws -> DuctSizes {
|
) async throws -> (DuctSizes, DuctSizeSharedRequest, [Room]) {
|
||||||
@Dependency(\.manualD) var manualD
|
@Dependency(\.manualD) var manualD
|
||||||
|
|
||||||
return try await manualD.calculateDuctSizes(
|
let shared = try await sharedDuctRequest(projectID)
|
||||||
rooms: rooms.fetch(projectID),
|
let rooms = try await rooms.fetch(projectID)
|
||||||
|
|
||||||
|
return try await (
|
||||||
|
manualD.calculateDuctSizes(
|
||||||
|
rooms: rooms,
|
||||||
trunks: trunkSizes.fetch(projectID),
|
trunks: trunkSizes.fetch(projectID),
|
||||||
sharedRequest: sharedDuctRequest(projectID)
|
sharedRequest: shared
|
||||||
|
),
|
||||||
|
shared,
|
||||||
|
rooms
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateRoomDuctSizes(
|
func calculateRoomDuctSizes(
|
||||||
projectID: Project.ID
|
projectID: Project.ID
|
||||||
) async throws -> [DuctSizes.RoomContainer] {
|
) async throws -> ([DuctSizes.RoomContainer], DuctSizeSharedRequest) {
|
||||||
@Dependency(\.manualD) var manualD
|
@Dependency(\.manualD) var manualD
|
||||||
|
|
||||||
return try await manualD.calculateRoomSizes(
|
let shared = try await sharedDuctRequest(projectID)
|
||||||
|
|
||||||
|
return try await (
|
||||||
|
manualD.calculateRoomSizes(
|
||||||
rooms: rooms.fetch(projectID),
|
rooms: rooms.fetch(projectID),
|
||||||
sharedRequest: sharedDuctRequest(projectID)
|
sharedRequest: shared
|
||||||
|
),
|
||||||
|
shared
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateTrunkDuctSizes(
|
func calculateTrunkDuctSizes(
|
||||||
projectID: Project.ID
|
projectID: Project.ID
|
||||||
) async throws -> [DuctSizes.TrunkContainer] {
|
) async throws -> ([DuctSizes.TrunkContainer], DuctSizeSharedRequest) {
|
||||||
@Dependency(\.manualD) var manualD
|
@Dependency(\.manualD) var manualD
|
||||||
|
|
||||||
return try await manualD.calculateTrunkSizes(
|
let shared = try await sharedDuctRequest(projectID)
|
||||||
|
|
||||||
|
return try await (
|
||||||
|
manualD.calculateTrunkSizes(
|
||||||
rooms: rooms.fetch(projectID),
|
rooms: rooms.fetch(projectID),
|
||||||
trunks: trunkSizes.fetch(projectID),
|
trunks: trunkSizes.fetch(projectID),
|
||||||
sharedRequest: sharedDuctRequest(projectID)
|
sharedRequest: shared
|
||||||
|
),
|
||||||
|
shared
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import DatabaseClient
|
||||||
|
import Dependencies
|
||||||
|
import ManualDClient
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
extension ManualDClient {
|
||||||
|
|
||||||
|
func frictionRate(projectID: Project.ID) async throws -> ProjectClient.FrictionRateResponse {
|
||||||
|
@Dependency(\.database) var database
|
||||||
|
|
||||||
|
let componentLosses = try await database.componentLoss.fetch(projectID)
|
||||||
|
let lengths = try await database.effectiveLength.fetchMax(projectID)
|
||||||
|
|
||||||
|
let equipmentInfo = try await database.equipment.fetch(projectID)
|
||||||
|
guard let staticPressure = equipmentInfo?.staticPressure else {
|
||||||
|
return .init(componentLosses: componentLosses, equivalentLengths: lengths)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let totalEquivalentLength = lengths.total else {
|
||||||
|
return .init(componentLosses: componentLosses, equivalentLengths: lengths)
|
||||||
|
}
|
||||||
|
|
||||||
|
return try await .init(
|
||||||
|
componentLosses: componentLosses,
|
||||||
|
equivalentLengths: lengths,
|
||||||
|
frictionRate: frictionRate(
|
||||||
|
.init(
|
||||||
|
externalStaticPressure: staticPressure,
|
||||||
|
componentPressureLosses: database.componentLoss.fetch(projectID),
|
||||||
|
totalEffectiveLength: Int(totalEquivalentLength)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,22 +3,24 @@ import Dependencies
|
|||||||
import Logging
|
import Logging
|
||||||
import ManualDClient
|
import ManualDClient
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
import PdfClient
|
||||||
|
|
||||||
extension ProjectClient: DependencyKey {
|
extension ProjectClient: DependencyKey {
|
||||||
|
|
||||||
public static var liveValue: Self {
|
public static var liveValue: Self {
|
||||||
@Dependency(\.database) var database
|
@Dependency(\.database) var database
|
||||||
@Dependency(\.manualD) var manualD
|
@Dependency(\.manualD) var manualD
|
||||||
|
@Dependency(\.pdfClient) var pdfClient
|
||||||
|
|
||||||
return .init(
|
return .init(
|
||||||
calculateDuctSizes: { projectID in
|
calculateDuctSizes: { projectID in
|
||||||
try await database.calculateDuctSizes(projectID: projectID)
|
try await database.calculateDuctSizes(projectID: projectID).0
|
||||||
},
|
},
|
||||||
calculateRoomDuctSizes: { projectID in
|
calculateRoomDuctSizes: { projectID in
|
||||||
try await database.calculateRoomDuctSizes(projectID: projectID)
|
try await database.calculateRoomDuctSizes(projectID: projectID).0
|
||||||
},
|
},
|
||||||
calculateTrunkDuctSizes: { projectID in
|
calculateTrunkDuctSizes: { projectID in
|
||||||
try await database.calculateTrunkDuctSizes(projectID: projectID)
|
try await database.calculateTrunkDuctSizes(projectID: projectID).0
|
||||||
},
|
},
|
||||||
createProject: { userID, request in
|
createProject: { userID, request in
|
||||||
let project = try await database.projects.create(userID, request)
|
let project = try await database.projects.create(userID, request)
|
||||||
@@ -31,32 +33,44 @@ extension ProjectClient: DependencyKey {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
frictionRate: { projectID in
|
frictionRate: { projectID in
|
||||||
|
try await manualD.frictionRate(projectID: projectID)
|
||||||
let componentLosses = try await database.componentLoss.fetch(projectID)
|
},
|
||||||
let lengths = try await database.effectiveLength.fetchMax(projectID)
|
toMarkdown: { projectID in
|
||||||
|
try await pdfClient.markdown(database.makePdfRequest(projectID))
|
||||||
let equipmentInfo = try await database.equipment.fetch(projectID)
|
},
|
||||||
guard let staticPressure = equipmentInfo?.staticPressure else {
|
toHTML: { projectID in
|
||||||
return .init(componentLosses: componentLosses, equivalentLengths: lengths)
|
try await pdfClient.html(database.makePdfRequest(projectID))
|
||||||
}
|
|
||||||
|
|
||||||
guard let totalEquivalentLength = lengths.total else {
|
|
||||||
return .init(componentLosses: componentLosses, equivalentLengths: lengths)
|
|
||||||
}
|
|
||||||
|
|
||||||
return try await .init(
|
|
||||||
componentLosses: componentLosses,
|
|
||||||
equivalentLengths: lengths,
|
|
||||||
frictionRate: manualD.frictionRate(
|
|
||||||
.init(
|
|
||||||
externalStaticPressure: staticPressure,
|
|
||||||
componentPressureLosses: database.componentLoss.fetch(projectID),
|
|
||||||
totalEffectiveLength: Int(totalEquivalentLength)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension DatabaseClient {
|
||||||
|
|
||||||
|
fileprivate func makePdfRequest(_ projectID: Project.ID) async throws -> PdfClient.Request {
|
||||||
|
@Dependency(\.manualD) var manualD
|
||||||
|
|
||||||
|
guard let project = try await projects.get(projectID) else {
|
||||||
|
throw ProjectClientError("Project not found. id: \(projectID)")
|
||||||
|
}
|
||||||
|
let frictionRateResponse = try await manualD.frictionRate(projectID: projectID)
|
||||||
|
guard let frictionRate = frictionRateResponse.frictionRate else {
|
||||||
|
throw ProjectClientError("Friction rate not found. id: \(projectID)")
|
||||||
|
}
|
||||||
|
let (ductSizes, sharedInfo, rooms) = try await calculateDuctSizes(projectID: projectID)
|
||||||
|
|
||||||
|
return .init(
|
||||||
|
project: project,
|
||||||
|
rooms: rooms,
|
||||||
|
componentLosses: frictionRateResponse.componentLosses,
|
||||||
|
ductSizes: ductSizes,
|
||||||
|
equipmentInfo: sharedInfo.equipmentInfo,
|
||||||
|
maxSupplyTEL: sharedInfo.maxSupplyLength,
|
||||||
|
maxReturnTEL: sharedInfo.maxReturnLenght,
|
||||||
|
frictionRate: frictionRate,
|
||||||
|
projectSHR: sharedInfo.projectSHR
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
import Elementary
|
import Elementary
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
public struct Number: HTML, Sendable {
|
public struct Number: HTML, Sendable {
|
||||||
let fractionDigits: Int
|
let fractionDigits: Int
|
||||||
let value: Double
|
let value: Double
|
||||||
|
|
||||||
private var formatter: NumberFormatter {
|
// private var formatter: NumberFormatter {
|
||||||
let formatter = NumberFormatter()
|
// let formatter = NumberFormatter()
|
||||||
formatter.maximumFractionDigits = fractionDigits
|
// formatter.maximumFractionDigits = fractionDigits
|
||||||
formatter.numberStyle = .decimal
|
// formatter.numberStyle = .decimal
|
||||||
return formatter
|
// formatter.groupingSize = 3
|
||||||
}
|
// formatter.groupingSeparator = ","
|
||||||
|
// return formatter
|
||||||
|
// }
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
_ value: Double,
|
_ value: Double,
|
||||||
@@ -27,6 +30,6 @@ public struct Number: HTML, Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var body: some HTML<HTMLTag.span> {
|
public var body: some HTML<HTMLTag.span> {
|
||||||
span { formatter.string(for: value) ?? "N/A" }
|
span { value.string(digits: fractionDigits) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,17 +134,18 @@ extension DuctSizingView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var registerIDS: [String] {
|
private var registerIDS: [String] {
|
||||||
trunk.rooms.reduce(into: []) { array, room in
|
trunk.registerIDS(rooms: rooms)
|
||||||
array = room.registers.reduce(into: array) { array, register in
|
// trunk.rooms.reduce(into: []) { array, room in
|
||||||
if let room =
|
// array = room.registers.reduce(into: array) { array, register in
|
||||||
rooms
|
// if let room =
|
||||||
.first(where: { $0.roomID == room.id && $0.roomRegister == register })
|
// rooms
|
||||||
{
|
// .first(where: { $0.roomID == room.id && $0.roomRegister == register })
|
||||||
array.append(room.roomName)
|
// {
|
||||||
}
|
// array.append(room.roomName)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
.sorted()
|
// }
|
||||||
|
// .sorted()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,19 +48,19 @@ struct RoomsView: HTML, Sendable {
|
|||||||
|
|
||||||
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(.class("text-lg")) { "Heating Total" }
|
||||||
Badge(number: rooms.heatingTotal, digits: 0)
|
Badge(number: rooms.totalHeatingLoad, digits: 0)
|
||||||
.attributes(.class("badge-error"))
|
.attributes(.class("badge-error"))
|
||||||
}
|
}
|
||||||
|
|
||||||
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.coolingTotal, digits: 0)
|
Badge(number: rooms.totalCoolingLoad, 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.coolingSensible(shr: sensibleHeatRatio), digits: 0)
|
Badge(number: rooms.totalCoolingSensible(shr: sensibleHeatRatio ?? 1.0), digits: 0)
|
||||||
.attributes(.class("badge-info"))
|
.attributes(.class("badge-info"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,22 +238,3 @@ struct RoomsView: HTML, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Array where Element == Room {
|
|
||||||
var heatingTotal: Double {
|
|
||||||
reduce(into: 0) { $0 += $1.heatingLoad }
|
|
||||||
}
|
|
||||||
|
|
||||||
var coolingTotal: Double {
|
|
||||||
reduce(into: 0) { $0 += $1.coolingTotal }
|
|
||||||
}
|
|
||||||
|
|
||||||
func coolingSensible(shr: Double?) -> Double {
|
|
||||||
let shr = shr ?? 1.0
|
|
||||||
|
|
||||||
return reduce(into: 0) {
|
|
||||||
let sensible = $1.coolingSensible ?? ($1.coolingTotal * shr)
|
|
||||||
$0 += sensible
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user