WIP: Begins work on pdf client.

This commit is contained in:
2026-01-17 12:59:46 -05:00
parent 3ec1ee2814
commit 0fe80d05c6
13 changed files with 501 additions and 24 deletions

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@ DerivedData/
.swift-version
node_modules/
tailwindcss
.envrc

View File

@@ -9,6 +9,7 @@ let package = Package(
.library(name: "ApiController", targets: ["ApiController"]),
.library(name: "AuthClient", targets: ["AuthClient"]),
.library(name: "DatabaseClient", targets: ["DatabaseClient"]),
.library(name: "PdfClient", targets: ["PdfClient"]),
.library(name: "ProjectClient", targets: ["ProjectClient"]),
.library(name: "ManualDCore", targets: ["ManualDCore"]),
.library(name: "ManualDClient", targets: ["ManualDClient"]),
@@ -75,6 +76,14 @@ let package = Package(
.product(name: "Vapor", package: "vapor"),
]
),
.target(
name: "PdfClient",
dependencies: [
.target(name: "ManualDCore"),
.product(name: "Dependencies", package: "swift-dependencies"),
.product(name: "DependenciesMacros", package: "swift-dependencies"),
]
),
.target(
name: "ProjectClient",
dependencies: [

View File

@@ -5,6 +5,7 @@ import Fluent
import FluentSQLiteDriver
import ManualDCore
import NIOSSL
import ProjectClient
import Vapor
import VaporElementary
@preconcurrency import VaporRouting
@@ -111,6 +112,8 @@ extension SiteRoute {
}
}
extension DuctSizes: Content {}
@Sendable
private func siteHandler(
request: Request,
@@ -118,6 +121,7 @@ private func siteHandler(
) async throws -> any AsyncResponseEncodable {
@Dependency(\.apiController) var apiController
@Dependency(\.viewController) var viewController
@Dependency(\.projectClient) var projectClient
switch route {
case .api(let route):
@@ -125,6 +129,11 @@ private func siteHandler(
case .health:
return HTTPStatus.ok
case .view(let route):
// FIX: Remove.
if route == .test {
let projectID = UUID(uuidString: "E796C96C-F527-4753-A00A-EBCF25630663")!
return try await projectClient.calculateDuctSizes(projectID)
}
return try await viewController.respond(route: route, request: request)
}
}

View File

@@ -16,7 +16,7 @@ extension DependencyValues {
@DependencyClient
public struct ManualDClient: Sendable {
public var ductSize: @Sendable (DuctSizeRequest) async throws -> DuctSizeResponse
public var frictionRate: @Sendable (FrictionRateRequest) async throws -> FrictionRateResponse
public var frictionRate: @Sendable (FrictionRateRequest) async throws -> FrictionRate
public var totalEquivalentLength: @Sendable (TotalEquivalentLengthRequest) async throws -> Int
public var rectangularSize:
@Sendable (RectangularSizeRequest) async throws -> RectangularSizeResponse

View File

@@ -28,7 +28,7 @@ extension ManualDClient: DependencyKey {
let totalComponentLosses = request.componentPressureLosses.total
let availableStaticPressure = request.externalStaticPressure - totalComponentLosses
let frictionRate = availableStaticPressure * 100.0 / Double(request.totalEffectiveLength)
return .init(availableStaticPressure: availableStaticPressure, frictionRate: frictionRate)
return .init(availableStaticPressure: availableStaticPressure, value: frictionRate)
},
totalEquivalentLength: { request in
let trunkLengths = request.trunkLengths.reduce(0) { $0 + $1 }

View File

@@ -0,0 +1,14 @@
/// Holds onto values returned when calculating the design
/// friction rate for a project.
public struct FrictionRate: Codable, Equatable, Sendable {
public let availableStaticPressure: Double
public let value: Double
public init(
availableStaticPressure: Double,
value: Double
) {
self.availableStaticPressure = availableStaticPressure
self.value = value
}
}

View File

@@ -0,0 +1,47 @@
import Dependencies
import DependenciesMacros
import ManualDCore
@DependencyClient
public struct PdfClient: Sendable {
public var markdown: @Sendable (Request) async throws -> String
}
extension PdfClient: TestDependencyKey {
public static let testValue = Self()
}
extension PdfClient {
public struct Request: Codable, Equatable, Sendable {
public let project: Project
public let componentLosses: [ComponentPressureLoss]
public let ductSizes: DuctSizes
public let equipmentInfo: EquipmentInfo
public let maxSupplyTEL: EffectiveLength
public let maxReturnTEL: EffectiveLength
public let designFrictionRate: FrictionRate
public let projectSHR: Double
public init(
project: Project,
componentLosses: [ComponentPressureLoss],
ductSizes: DuctSizes,
equipmentInfo: EquipmentInfo,
maxSupplyTEL: EffectiveLength,
maxReturnTEL: EffectiveLength,
designFrictionRate: FrictionRate,
projectSHR: Double
) {
self.project = project
self.componentLosses = componentLosses
self.ductSizes = ductSizes
self.equipmentInfo = equipmentInfo
self.maxSupplyTEL = maxSupplyTEL
self.maxReturnTEL = maxReturnTEL
self.designFrictionRate = designFrictionRate
self.projectSHR = projectSHR
}
}
}

View File

@@ -0,0 +1,42 @@
import ManualDCore
extension PdfClient.Request {
func toMarkdown() -> String {
var retval = """
# Duct Calc
**Name:** \(project.name)
**Address:** \(project.streetAddress)
\(project.city), \(project.state) \(project.zipCode)
## Equipment
| | Value |
|-----------------|---------------------------------|
| Static Pressure | \(equipmentInfo.staticPressure) |
| Heating CFM | \(equipmentInfo.heatingCFM) |
| Cooling CFM | \(equipmentInfo.coolingCFM) |
## Friction Rate
| | Value |
|-----------------|---------------------------------|
"""
for row in componentLosses {
retval = """
\(retval)
\(componentLossRow(row))
"""
}
return retval
}
func componentLossRow(_ row: ComponentPressureLoss) -> String {
return """
| \(row.name) | \(row.value) |
"""
}
}

View File

@@ -58,12 +58,12 @@ extension ProjectClient {
public let componentLosses: [ComponentPressureLoss]
public let equivalentLengths: EffectiveLength.MaxContainer
public let frictionRate: ManualDClient.FrictionRateResponse?
public let frictionRate: FrictionRate?
public init(
componentLosses: [ComponentPressureLoss],
equivalentLengths: EffectiveLength.MaxContainer,
frictionRate: ManualDClient.FrictionRateResponse? = nil
frictionRate: FrictionRate? = nil
) {
self.componentLosses = componentLosses
self.equivalentLengths = equivalentLengths

View File

@@ -15,7 +15,7 @@ extension ViewController.Request {
switch route {
case .test:
// let projectID = UUID(uuidString: "A9C20153-E2E5-4C65-B33F-4D8A29C63A7A")!
// let projectID = UUID(uuidString: "E796C96C-F527-4753-A00A-EBCF25630663")!
return await view {
await ResultView {
@@ -372,7 +372,7 @@ extension SiteRoute.View.ProjectRoute.FrictionRateRoute {
FrictionRateView(
componentLosses: losses,
equivalentLengths: lengths,
frictionRateResponse: frictionRate
frictionRate: frictionRate
)
}
@@ -430,7 +430,7 @@ extension SiteRoute.View.ProjectRoute.ComponentLossRoute {
FrictionRateView(
componentLosses: response.componentLosses,
equivalentLengths: response.equivalentLengths,
frictionRateResponse: response.frictionRate
frictionRate: response.frictionRate
)
}

View File

@@ -9,37 +9,33 @@ struct FrictionRateView: HTML, Sendable {
let componentLosses: [ComponentPressureLoss]
let equivalentLengths: EffectiveLength.MaxContainer
let frictionRateResponse: ManualDClient.FrictionRateResponse?
let frictionRate: FrictionRate?
private var availableStaticPressure: Double? {
frictionRateResponse?.availableStaticPressure
}
private var frictionRateDesignValue: Double? {
frictionRateResponse?.frictionRate
frictionRate?.availableStaticPressure
}
private var shouldShowBadges: Bool {
frictionRateDesignValue != nil || availableStaticPressure != nil
frictionRate != nil
}
private var badgeColor: String {
let base = "badge-info"
guard let frictionRateDesignValue else { return base }
if frictionRateDesignValue >= 0.18 || frictionRateDesignValue <= 0.02 {
guard let frictionRate = frictionRate?.value else { return base }
if frictionRate >= 0.18 || frictionRate <= 0.02 {
return "badge-error"
}
return base
}
private var showHighErrors: Bool {
guard let frictionRateDesignValue else { return false }
return frictionRateDesignValue >= 0.18
guard let frictionRate = frictionRate?.value else { return false }
return frictionRate >= 0.18
}
private var showLowErrors: Bool {
guard let frictionRateDesignValue else { return false }
return frictionRateDesignValue <= 0.02
guard let frictionRate = frictionRate?.value else { return false }
return frictionRate <= 0.02
}
private var showNoComponentLossesError: Bool {
@@ -47,7 +43,7 @@ struct FrictionRateView: HTML, Sendable {
}
private var showIncompleteSectionsError: Bool {
availableStaticPressure == nil || frictionRateDesignValue == nil
availableStaticPressure == nil || frictionRate?.value == nil
}
private var hasAlerts: Bool {
@@ -68,11 +64,11 @@ struct FrictionRateView: HTML, Sendable {
div(.class("space-y-2 justify-end font-bold text-lg")) {
if shouldShowBadges {
if let frictionRateDesignValue {
if let frictionRate = frictionRate?.value {
LabeledContent {
span { "Friction Rate Design Value" }
} content: {
Badge(number: frictionRateDesignValue, digits: 2)
Badge(number: frictionRate, digits: 2)
.attributes(.class("\(badgeColor) badge-lg"))
.bold()
}

View File

@@ -234,7 +234,7 @@ extension ManualDClient {
equipmentInfo: EquipmentInfo?,
componentLosses: [ComponentPressureLoss],
effectiveLength: EffectiveLength.MaxContainer
) async throws -> FrictionRateResponse? {
) async throws -> FrictionRate? {
guard let staticPressure = equipmentInfo?.staticPressure else {
return nil
}

359
ductsizes.json Normal file
View File

@@ -0,0 +1,359 @@
{
"trunks": [
{
"trunk": {
"projectID": "E796C96C-F527-4753-A00A-EBCF25630663",
"type": "supply",
"name": "West",
"id": "AFBC5264-8129-4383-97D8-F44CF5258A53",
"rooms": [
{
"room": {
"coolingTotal": 4567,
"id": "FA2F107F-5DF0-4D69-B231-DE3C6EF64134",
"registerCount": 3,
"heatingLoad": 9876,
"updatedAt": "2026-01-17T16:51:33Z",
"createdAt": "2026-01-17T16:51:33Z",
"projectID": "E796C96C-F527-4753-A00A-EBCF25630663",
"name": "Kitchen"
},
"registers": [1, 2, 3]
},
{
"room": {
"coolingTotal": 4567,
"id": "9A01CEE8-6A4B-4299-9353-58AFAD042903",
"registerCount": 3,
"heatingLoad": 9876,
"updatedAt": "2026-01-17T16:51:21Z",
"createdAt": "2026-01-17T16:51:21Z",
"name": "Master",
"projectID": "E796C96C-F527-4753-A00A-EBCF25630663"
},
"registers": [1, 2, 3]
}
],
"height": 8
},
"ductSize": {
"height": 8,
"roundSize": 12.3660960368007,
"width": 19,
"velocity": 737,
"finalSize": 14,
"flexSize": 14,
"designCFM": {
"cooling": {
"_0": 787.278055507671
}
}
}
},
{
"trunk": {
"projectID": "E796C96C-F527-4753-A00A-EBCF25630663",
"name": "East",
"id": "C6A21594-0A28-4A20-BC15-628502010201",
"type": "supply",
"height": 8,
"rooms": [
{
"room": {
"coolingTotal": 1234,
"id": "2420F9C7-0FCA-4E92-BAC9-8A054CB3201B",
"registerCount": 1,
"heatingLoad": 4567,
"updatedAt": "2026-01-17T16:51:10Z",
"createdAt": "2026-01-17T16:51:10Z",
"name": "Entry",
"projectID": "E796C96C-F527-4753-A00A-EBCF25630663"
},
"registers": [1]
},
{
"room": {
"coolingTotal": 1234,
"id": "449FE324-2ACF-4C12-83A3-EB86FD45A991",
"registerCount": 2,
"heatingLoad": 4567,
"updatedAt": "2026-01-17T16:51:02Z",
"createdAt": "2026-01-17T16:51:02Z",
"projectID": "E796C96C-F527-4753-A00A-EBCF25630663",
"name": "Bed-1"
},
"registers": [1, 2]
}
]
},
"ductSize": {
"height": 8,
"roundSize": 8.39504804369037,
"width": 8,
"velocity": 643,
"flexSize": 10,
"finalSize": 9,
"designCFM": {
"heating": {
"_0": 284.587689538185
}
}
}
},
{
"trunk": {
"name": "Main",
"projectID": "E796C96C-F527-4753-A00A-EBCF25630663",
"id": "C65863A0-D2EE-4D90-8C71-B1FD2454A8DF",
"type": "return",
"rooms": [
{
"registers": [1, 2, 3],
"room": {
"coolingTotal": 4567,
"id": "FA2F107F-5DF0-4D69-B231-DE3C6EF64134",
"registerCount": 3,
"heatingLoad": 9876,
"updatedAt": "2026-01-17T16:51:33Z",
"createdAt": "2026-01-17T16:51:33Z",
"name": "Kitchen",
"projectID": "E796C96C-F527-4753-A00A-EBCF25630663"
}
},
{
"registers": [1, 2, 3],
"room": {
"coolingTotal": 4567,
"id": "9A01CEE8-6A4B-4299-9353-58AFAD042903",
"registerCount": 3,
"heatingLoad": 9876,
"updatedAt": "2026-01-17T16:51:21Z",
"createdAt": "2026-01-17T16:51:21Z",
"name": "Master",
"projectID": "E796C96C-F527-4753-A00A-EBCF25630663"
}
},
{
"registers": [1],
"room": {
"coolingTotal": 1234,
"id": "2420F9C7-0FCA-4E92-BAC9-8A054CB3201B",
"registerCount": 1,
"heatingLoad": 4567,
"updatedAt": "2026-01-17T16:51:10Z",
"createdAt": "2026-01-17T16:51:10Z",
"name": "Entry",
"projectID": "E796C96C-F527-4753-A00A-EBCF25630663"
}
},
{
"registers": [1, 2],
"room": {
"coolingTotal": 1234,
"id": "449FE324-2ACF-4C12-83A3-EB86FD45A991",
"registerCount": 2,
"heatingLoad": 4567,
"updatedAt": "2026-01-17T16:51:02Z",
"createdAt": "2026-01-17T16:51:02Z",
"name": "Bed-1",
"projectID": "E796C96C-F527-4753-A00A-EBCF25630663"
}
}
]
},
"ductSize": {
"designCFM": {
"cooling": {
"_0": 1000
}
},
"roundSize": 13.539327773393,
"velocity": 935,
"finalSize": 14,
"flexSize": 16
}
}
],
"rooms": [
{
"ductSize": {
"velocity": 521,
"flexSize": 6,
"designCFM": {
"heating": {
"_0": 71.1469223845461
}
},
"roundSize": 4.95724506597333,
"finalSize": 5
},
"heatingCFM": 71.1469223845461,
"roomRegister": 1,
"heatingLoad": 2283.5,
"roomName": "Bed-1-1",
"roomID": "449FE324-2ACF-4C12-83A3-EB86FD45A991",
"coolingLoad": 512.11,
"coolingCFM": 53.1804861230822
},
{
"ductSize": {
"velocity": 521,
"flexSize": 6,
"designCFM": {
"heating": {
"_0": 71.1469223845461
}
},
"roundSize": 4.95724506597333,
"finalSize": 5
},
"heatingCFM": 71.1469223845461,
"roomRegister": 2,
"heatingLoad": 2283.5,
"roomName": "Bed-1-2",
"roomID": "449FE324-2ACF-4C12-83A3-EB86FD45A991",
"coolingLoad": 512.11,
"coolingCFM": 53.1804861230822
},
{
"ductSize": {
"velocity": 532,
"flexSize": 7,
"designCFM": {
"heating": {
"_0": 142.293844769092
}
},
"roundSize": 6.4510704920341,
"finalSize": 7
},
"heatingCFM": 142.293844769092,
"roomRegister": 1,
"heatingLoad": 4567,
"roomName": "Entry-1",
"roomID": "2420F9C7-0FCA-4E92-BAC9-8A054CB3201B",
"coolingLoad": 1024.22,
"coolingCFM": 106.360972246164
},
{
"ductSize": {
"velocity": 490,
"flexSize": 7,
"designCFM": {
"cooling": {
"_0": 131.213009251279
}
},
"roundSize": 6.25641154872314,
"finalSize": 7
},
"heatingCFM": 102.568718410303,
"roomRegister": 1,
"heatingLoad": 3292,
"roomName": "Kitchen-1",
"roomID": "FA2F107F-5DF0-4D69-B231-DE3C6EF64134",
"coolingLoad": 1263.53666666667,
"coolingCFM": 131.213009251279
},
{
"ductSize": {
"velocity": 490,
"flexSize": 7,
"designCFM": {
"cooling": {
"_0": 131.213009251279
}
},
"roundSize": 6.25641154872314,
"finalSize": 7
},
"heatingCFM": 102.568718410303,
"roomRegister": 2,
"heatingLoad": 3292,
"roomName": "Kitchen-2",
"roomID": "FA2F107F-5DF0-4D69-B231-DE3C6EF64134",
"coolingLoad": 1263.53666666667,
"coolingCFM": 131.213009251279
},
{
"ductSize": {
"velocity": 490,
"flexSize": 7,
"designCFM": {
"cooling": {
"_0": 131.213009251279
}
},
"roundSize": 6.25641154872314,
"finalSize": 7
},
"heatingCFM": 102.568718410303,
"roomRegister": 3,
"heatingLoad": 3292,
"roomName": "Kitchen-3",
"roomID": "FA2F107F-5DF0-4D69-B231-DE3C6EF64134",
"coolingLoad": 1263.53666666667,
"coolingCFM": 131.213009251279
},
{
"ductSize": {
"velocity": 490,
"flexSize": 7,
"designCFM": {
"cooling": {
"_0": 131.213009251279
}
},
"roundSize": 6.25641154872314,
"finalSize": 7
},
"heatingCFM": 102.568718410303,
"roomRegister": 1,
"heatingLoad": 3292,
"roomName": "Master-1",
"roomID": "9A01CEE8-6A4B-4299-9353-58AFAD042903",
"coolingLoad": 1263.53666666667,
"coolingCFM": 131.213009251279
},
{
"ductSize": {
"velocity": 490,
"flexSize": 7,
"designCFM": {
"cooling": {
"_0": 131.213009251279
}
},
"roundSize": 6.25641154872314,
"finalSize": 7
},
"heatingCFM": 102.568718410303,
"roomRegister": 2,
"heatingLoad": 3292,
"roomName": "Master-2",
"roomID": "9A01CEE8-6A4B-4299-9353-58AFAD042903",
"coolingLoad": 1263.53666666667,
"coolingCFM": 131.213009251279
},
{
"ductSize": {
"velocity": 490,
"flexSize": 7,
"designCFM": {
"cooling": {
"_0": 131.213009251279
}
},
"roundSize": 6.25641154872314,
"finalSize": 7
},
"heatingCFM": 102.568718410303,
"roomRegister": 3,
"heatingLoad": 3292,
"roomName": "Master-3",
"roomID": "9A01CEE8-6A4B-4299-9353-58AFAD042903",
"coolingLoad": 1263.53666666667,
"coolingCFM": 131.213009251279
}
}
]