diff --git a/Public/css/output.css b/Public/css/output.css index 8ec475b..2c733bb 100644 --- a/Public/css/output.css +++ b/Public/css/output.css @@ -9,6 +9,7 @@ monospace; --color-amber-300: oklch(87.9% 0.169 91.605); --color-green-400: oklch(79.2% 0.209 151.711); + --color-sky-300: oklch(82.8% 0.111 230.318); --color-sky-600: oklch(58.8% 0.158 241.966); --color-violet-600: oklch(54.1% 0.281 293.009); --color-gray-200: oklch(92.8% 0.006 264.531); @@ -34,8 +35,6 @@ --text-4xl--line-height: calc(2.5 / 2.25); --text-5xl: 3rem; --text-5xl--line-height: 1; - --text-6xl: 3.75rem; - --text-6xl--line-height: 1; --text-8xl: 6rem; --text-8xl--line-height: 1; --font-weight-bold: 700; @@ -4233,9 +4232,18 @@ --toast-y: 0; } } + .top-0 { + top: calc(var(--spacing) * 0); + } .top-2 { top: calc(var(--spacing) * 2); } + .top-10 { + top: calc(var(--spacing) * 10); + } + .top-20 { + top: calc(var(--spacing) * 20); + } .right-2 { right: calc(var(--spacing) * 2); } @@ -4299,9 +4307,21 @@ .bottom-0 { bottom: calc(var(--spacing) * 0); } + .-left-10 { + left: calc(var(--spacing) * -10); + } + .-left-15 { + left: calc(var(--spacing) * -15); + } + .-left-20 { + left: calc(var(--spacing) * -20); + } .left-0 { left: calc(var(--spacing) * 0); } + .left-10 { + left: calc(var(--spacing) * 10); + } .join { display: inline-flex; align-items: stretch; @@ -5243,6 +5263,9 @@ .m-1 { margin: calc(var(--spacing) * 1); } + .m-4 { + margin: calc(var(--spacing) * 4); + } .m-6 { margin: calc(var(--spacing) * 6); } @@ -5291,6 +5314,12 @@ } } } + .mx-10 { + margin-inline: calc(var(--spacing) * 10); + } + .mx-20 { + margin-inline: calc(var(--spacing) * 20); + } .mx-auto { margin-inline: auto; } @@ -5380,18 +5409,12 @@ } } } - .-my-2 { - margin-block: calc(var(--spacing) * -2); - } .my-1 { margin-block: calc(var(--spacing) * 1); } .my-1\.5 { margin-block: calc(var(--spacing) * 1.5); } - .my-2 { - margin-block: calc(var(--spacing) * 2); - } .my-6 { margin-block: calc(var(--spacing) * 6); } @@ -5601,18 +5624,12 @@ border-width: var(--border, 1px) 0 var(--border, 1px) var(--border, 1px); } } - .-ms-2 { - margin-inline-start: calc(var(--spacing) * -2); - } - .ms-2 { - margin-inline-start: calc(var(--spacing) * 2); + .ms-10 { + margin-inline-start: calc(var(--spacing) * 10); } .me-2 { margin-inline-end: calc(var(--spacing) * 2); } - .me-3 { - margin-inline-end: calc(var(--spacing) * 3); - } .me-4 { margin-inline-end: calc(var(--spacing) * 4); } @@ -5664,15 +5681,12 @@ .-mt-2 { margin-top: calc(var(--spacing) * -2); } + .mt-1 { + margin-top: calc(var(--spacing) * 1); + } .mt-2 { margin-top: calc(var(--spacing) * 2); } - .mt-3 { - margin-top: calc(var(--spacing) * 3); - } - .mt-3\.5 { - margin-top: calc(var(--spacing) * 3.5); - } .mt-4 { margin-top: calc(var(--spacing) * 4); } @@ -5682,9 +5696,15 @@ .mt-8 { margin-top: calc(var(--spacing) * 8); } + .mt-10 { + margin-top: calc(var(--spacing) * 10); + } .mt-20 { margin-top: calc(var(--spacing) * 20); } + .mt-60 { + margin-top: calc(var(--spacing) * 60); + } .breadcrumbs { @layer daisyui.l1.l2.l3 { max-width: 100%; @@ -5761,12 +5781,6 @@ font-weight: 600; } } - .mb-1 { - margin-bottom: calc(var(--spacing) * 1); - } - .mb-2 { - margin-bottom: calc(var(--spacing) * 2); - } .mb-4 { margin-bottom: calc(var(--spacing) * 4); } @@ -6634,6 +6648,12 @@ width: calc(var(--size-selector, 0.25rem) * 4); } } + .w-\[200px\] { + width: 200px; + } + .w-\[250px\] { + width: 250px; + } .w-\[330px\] { width: 330px; } @@ -6643,12 +6663,6 @@ .w-full { width: 100%; } - .max-w-lg { - max-width: var(--container-lg); - } - .max-w-md { - max-width: var(--container-md); - } .min-w-\[80\%\] { min-width: 80%; } @@ -6695,8 +6709,8 @@ .-rotate-45 { rotate: calc(45deg * -1); } - .rotate-90 { - rotate: 90deg; + .rotate-45 { + rotate: 45deg; } .rotate-180 { rotate: 180deg; @@ -6794,6 +6808,9 @@ } } } + .list-disc { + list-style-type: disc; + } .alert-horizontal { @layer daisyui.l1.l2 { justify-content: start; @@ -6875,6 +6892,9 @@ .flex-wrap { flex-wrap: wrap; } + .items-baseline { + align-items: baseline; + } .items-center { align-items: center; } @@ -6967,6 +6987,9 @@ .overflow-auto { overflow: auto; } + .overflow-hidden { + overflow: hidden; + } .timeline-box { @layer daisyui.l1.l2.l3 { border: var(--border) solid; @@ -7079,9 +7102,6 @@ .rounded-sm { border-radius: var(--radius-sm); } - .rounded-xl { - border-radius: var(--radius-xl); - } .rounded-t-box { border-top-left-radius: var(--radius-box); border-top-right-radius: var(--radius-box); @@ -7258,10 +7278,18 @@ border-style: var(--tw-border-style); border-width: 2px; } + .border-3 { + border-style: var(--tw-border-style); + border-width: 3px; + } .border-b-1 { border-bottom-style: var(--tw-border-style); border-bottom-width: 1px; } + .border-b-6 { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 6px; + } .border-b-8 { border-bottom-style: var(--tw-border-style); border-bottom-width: 8px; @@ -7375,8 +7403,8 @@ border-color: currentColor; } } - .border-amber-300 { - border-color: var(--color-amber-300); + .border-accent { + border-color: var(--color-accent); } .border-error { border-color: var(--color-error); @@ -7387,6 +7415,9 @@ .border-primary { border-color: var(--color-primary); } + .border-secondary { + border-color: var(--color-secondary); + } .border-sky-600 { border-color: var(--color-sky-600); } @@ -7550,6 +7581,9 @@ .bg-error { background-color: var(--color-error); } + .bg-primary { + background-color: var(--color-primary); + } .bg-secondary { background-color: var(--color-secondary); } @@ -7765,6 +7799,15 @@ } } } + .mask-contain { + mask-size: contain; + } + .mask-clip-border { + mask-clip: border-box; + } + .mask-clip-content { + mask-clip: content-box; + } .mask-repeat { mask-repeat: repeat; } @@ -7967,6 +8010,9 @@ .px-4 { padding-inline: calc(var(--spacing) * 4); } + .px-6 { + padding-inline: calc(var(--spacing) * 6); + } .py-2 { padding-block: calc(var(--spacing) * 2); } @@ -8629,6 +8675,9 @@ color: var(--color-warning); } } + .text-accent { + color: var(--color-accent); + } .text-base-content { color: var(--color-base-content); } @@ -8644,12 +8693,15 @@ .text-info { color: var(--color-info); } + .text-primary { + color: var(--color-primary); + } + .text-secondary { + color: var(--color-secondary); + } .text-success { color: var(--color-success); } - .text-violet-600 { - color: var(--color-violet-600); - } .lowercase { text-transform: lowercase; } @@ -8726,10 +8778,6 @@ --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } - .shadow-xl { - --tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); - box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); - } .outline { outline-style: var(--tw-outline-style); outline-width: 1px; diff --git a/Public/files/ManD.Groups.pdf_original b/Public/files/ManD.Groups.pdf_original deleted file mode 100755 index 994d241..0000000 Binary files a/Public/files/ManD.Groups.pdf_original and /dev/null differ diff --git a/Sources/ManualDClient/Helpers.swift b/Sources/ManualDClient/Helpers.swift index fc5b2aa..155fe81 100644 --- a/Sources/ManualDClient/Helpers.swift +++ b/Sources/ManualDClient/Helpers.swift @@ -1,50 +1,6 @@ import Foundation import ManualDCore -extension Room { - - public var heatingLoadPerRegister: Double { - heatingLoad / Double(registerCount) - } - - public func coolingSensiblePerRegister(projectSHR: Double) throws -> Double { - let sensible = try coolingLoad.ensured(shr: projectSHR).sensible - return sensible / Double(registerCount) - } -} - -extension TrunkSize.RoomProxy { - - // We need to make sure if registers got removed after a trunk - // was already made / saved that we do not include registers that - // no longer exist. - private var actualRegisterCount: Int { - guard registers.count <= room.registerCount else { - return room.registerCount - } - return registers.count - } - - var totalHeatingLoad: Double { - room.heatingLoadPerRegister * Double(actualRegisterCount) - } - - func totalCoolingSensible(projectSHR: Double) throws -> Double { - try room.coolingSensiblePerRegister(projectSHR: projectSHR) * Double(actualRegisterCount) - } -} - -extension TrunkSize { - - var totalHeatingLoad: Double { - rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad } - } - - func totalCoolingSensible(projectSHR: Double) throws -> Double { - try rooms.reduce(into: 0) { $0 += try $1.totalCoolingSensible(projectSHR: projectSHR) } - } -} - extension Array where Element == EffectiveLengthGroup { var totalEffectiveLength: Int { reduce(0) { $0 + $1.effectiveLength } diff --git a/Sources/ProjectClient/Internal/ManualDClient+calculateDuctSizes.swift b/Sources/ProjectClient/Internal/ManualDClient+calculateDuctSizes.swift index 6fd3e72..859ced8 100644 --- a/Sources/ProjectClient/Internal/ManualDClient+calculateDuctSizes.swift +++ b/Sources/ProjectClient/Internal/ManualDClient+calculateDuctSizes.swift @@ -33,6 +33,7 @@ extension ManualDClient { ) } + // FIX: Need to add the loads for rooms that get delegated to other rooms here. func calculateRoomSizes( rooms: [Room], sharedRequest: DuctSizeSharedRequest, @@ -42,10 +43,15 @@ extension ManualDClient { var retval: [DuctSizes.RoomContainer] = [] let totalHeatingLoad = rooms.totalHeatingLoad let totalCoolingSensible = try rooms.totalCoolingSensible(shr: sharedRequest.projectSHR) + let nonDelegatedRooms = rooms.filter { $0.delegatedTo == nil } - for room in rooms { - let heatingLoad = room.heatingLoadPerRegister + for room in nonDelegatedRooms { + // Get all the rooms that delegate their loads to this room. + let delegatedRooms = rooms.filter { $0.delegatedTo == room.id } + + let heatingLoad = room.heatingLoadPerRegister(delegatedRooms: delegatedRooms) let coolingLoad = try room.coolingSensiblePerRegister(projectSHR: sharedRequest.projectSHR) + let heatingPercent = heatingLoad / totalHeatingLoad let coolingPercent = coolingLoad / totalCoolingSensible let heatingCFM = heatingPercent * Double(sharedRequest.equipmentInfo.heatingCFM) @@ -181,47 +187,34 @@ extension DuctSizes.SizeContainer { } } -// extension Room { +// extension TrunkSize.RoomProxy { // -// var heatingLoadPerRegister: Double { -// -// heatingLoad / Double(registerCount) +// // We need to make sure if registers got removed after a trunk +// // was already made / saved that we do not include registers that +// // no longer exist. +// private var actualRegisterCount: Int { +// guard registers.count <= room.registerCount else { +// return room.registerCount +// } +// return registers.count // } // -// func coolingSensiblePerRegister(projectSHR: Double) -> Double { -// let sensible = coolingSensible ?? (coolingTotal * projectSHR) -// return sensible / Double(registerCount) +// var totalHeatingLoad: Double { +// room.heatingLoadPerRegister() * Double(actualRegisterCount) +// } +// +// func totalCoolingSensible(projectSHR: Double) throws -> Double { +// try room.coolingSensiblePerRegister(projectSHR: projectSHR) * Double(actualRegisterCount) // } // } -extension TrunkSize.RoomProxy { - - // We need to make sure if registers got removed after a trunk - // was already made / saved that we do not include registers that - // no longer exist. - private var actualRegisterCount: Int { - guard registers.count <= room.registerCount else { - return room.registerCount - } - return registers.count - } - - var totalHeatingLoad: Double { - room.heatingLoadPerRegister * Double(actualRegisterCount) - } - - func totalCoolingSensible(projectSHR: Double) throws -> Double { - try room.coolingSensiblePerRegister(projectSHR: projectSHR) * Double(actualRegisterCount) - } -} - -extension TrunkSize { - - var totalHeatingLoad: Double { - rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad } - } - - func totalCoolingSensible(projectSHR: Double) throws -> Double { - try rooms.reduce(into: 0) { $0 += try $1.totalCoolingSensible(projectSHR: projectSHR) } - } -} +// extension TrunkSize { +// +// var totalHeatingLoad: Double { +// rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad } +// } +// +// func totalCoolingSensible(projectSHR: Double) throws -> Double { +// try rooms.reduce(into: 0) { $0 += try $1.totalCoolingSensible(projectSHR: projectSHR) } +// } +// } diff --git a/Sources/ProjectClient/Internal/Room+loadPerRegister.swift b/Sources/ProjectClient/Internal/Room+loadPerRegister.swift new file mode 100644 index 0000000..9346dbe --- /dev/null +++ b/Sources/ProjectClient/Internal/Room+loadPerRegister.swift @@ -0,0 +1,20 @@ +import Foundation +import ManualDCore + +extension Room { + + public func heatingLoadPerRegister(delegatedRooms: [Room]? = nil) -> Double { + (heatingLoad + (delegatedRooms?.totalHeatingLoad ?? 0)) / Double(registerCount) + } + + public func coolingSensiblePerRegister( + projectSHR: Double, + delegatedRooms: [Room]? = nil + ) throws -> Double { + let sensible = + try coolingLoad.ensured(shr: projectSHR).sensible + + (delegatedRooms?.totalCoolingSensible(shr: projectSHR) ?? 0) + + return sensible / Double(registerCount) + } +} diff --git a/Sources/ProjectClient/Internal/TrunkSize+loads.swift b/Sources/ProjectClient/Internal/TrunkSize+loads.swift new file mode 100644 index 0000000..2ab2bce --- /dev/null +++ b/Sources/ProjectClient/Internal/TrunkSize+loads.swift @@ -0,0 +1,34 @@ +import Foundation +import ManualDCore + +extension TrunkSize.RoomProxy { + + // We need to make sure if registers got removed after a trunk + // was already made / saved that we do not include registers that + // no longer exist. + private var actualRegisterCount: Int { + guard registers.count <= room.registerCount else { + return room.registerCount + } + return registers.count + } + + public var totalHeatingLoad: Double { + room.heatingLoadPerRegister() * Double(actualRegisterCount) + } + + public func totalCoolingSensible(projectSHR: Double) throws -> Double { + try room.coolingSensiblePerRegister(projectSHR: projectSHR) * Double(actualRegisterCount) + } +} + +extension TrunkSize { + + public var totalHeatingLoad: Double { + rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad } + } + + public func totalCoolingSensible(projectSHR: Double) throws -> Double { + try rooms.reduce(into: 0) { $0 += try $1.totalCoolingSensible(projectSHR: projectSHR) } + } +} diff --git a/Sources/ViewController/Views/Home.swift b/Sources/ViewController/Views/Home.swift index 7acd898..77fd88c 100644 --- a/Sources/ViewController/Views/Home.swift +++ b/Sources/ViewController/Views/Home.swift @@ -4,53 +4,113 @@ import ElementaryHTMX struct HomeView: HTML, Sendable { var body: some HTML { - div(.class("flex justify-end me-4")) { - button( - .class("btn btn-ghost btn-secondary text-lg"), - .hx.get(route: .login(.index())), - .hx.target("body"), - .hx.swap(.outerHTML) - ) { - "Login" + div( // Uncomment to test different theme's. + // .data("theme", value: "cyberpunk") + // NOTE: Footer background color will follow system theme, it will actually be the + // same as the `hero` background in reality. + ) { + div(.class("flex justify-end m-4")) { + button( + .class("btn btn-ghost btn-secondary text-lg"), + .hx.get(route: .login(.index())), + .hx.target("body"), + .hx.swap(.outerHTML) + ) { + "Login" + } } - } - div(.class("hero min-h-screen")) { - div( - .class( - """ - hero-content text-center bg-base-200 dark:bg-base-300 - min-w-[80%] min-h-[400px] rounded-3xl shadow-3xl - """ - ) - ) { - div { - header - a( - .class("btn btn-ghost text-md italic"), - .href("https://git.housh.dev/michael/swift-manual-d"), - .target(.blank) + div(.class("hero")) { + div( + .class( + """ + relative hero-content text-center bg-base-300 + w-full min-h-[400px] rounded-3xl shadow-3xl overflow-hidden + """ + ) + ) { + div( + .class( + """ + bg-secondary text-xl font-bold + absolute top-10 -left-15 + px-6 py-2 w-[250px] -rotate-45 + """ + ) ) { - "Open source residential duct design program" + "BETA" } - p(.class("text-xl py-6")) { - """ - Manual-D™ speed sheet, but on the web! - """ - } - button( - .class("btn btn-xl bg-violet-600 mt-6"), - .hx.get(route: .signup(.index)), - .hx.target("body"), - .hx.swap(.outerHTML) - ) { - "Get Started" - } - p(.class("text-xs italic mt-8")) { - """ - Manual-D™ is a trademark of Air Conditioning Contractors of America (ACCA). + div { + header + a( + .class("btn btn-ghost text-md text-primary font-bold italic"), + .href("https://git.housh.dev/michael/swift-manual-d"), + .target(.blank) + ) { + "Open source residential duct design program" + } + p(.class("text-3xl py-6")) { + """ + Manual-D™ speed sheet, but on the web! + """ + } + button( + .class("btn btn-xl btn-primary mt-6"), + .hx.get(route: .signup(.index)), + .hx.target("body"), + .hx.swap(.outerHTML) + ) { + "Get Started" + } + p(.class("text-xs italic mt-8")) { + """ + Manual-D™ is a trademark of Air Conditioning Contractors of America (ACCA). - This site is not designed by or affiliated with ACCA. - """ + This site is not designed by or affiliated with ACCA. + """ + } + } + } + } + + div(.class("grid grid-cols-1 md:grid-cols-2 gap-4 mx-20 my-6")) { + div(.class("border-3 border-accent rounded-lg shadow-lg p-4")) { + div(.class("flex items-center space-x-4")) { + div(.class("text-5xl text-primary font-bold")) { + "Features" + } + } + div(.class("text-xl ms-10 mt-10")) { + ul(.class("list-disc")) { + li { + div( + .class("font-bold italic bg-secondary rounded-lg shadow-lg px-4 w-fit") + ) { + "Built by humans" + } + } + li { "Fully open source." } + li { "Great replacement for speed sheet users." } + li { "Great for classrooms." } + li { "Store your projects in one place." } + li { "Export final project to pdf." } + li { "Import room loads via CSV file." } + li { "Web based." } + li { "Self host (run on your own infrastructure)." } + } + } + } + + div(.class("border-3 border-accent rounded-lg shadow-lg p-4")) { + div(.class("text-5xl text-primary font-bold")) { + "Coming Soon" + } + div(.class("text-xl ms-10 mt-10")) { + ul(.class("list-disc")) { + li { "API integration." } + li { "Command line interface." } + li { "Fitting selection tool." } + li { "Room load import from PDF." } + } } } } @@ -62,7 +122,7 @@ struct HomeView: HTML, Sendable { div( .class( """ - flex border-b-8 border-sky-600 + flex border-b-6 border-accent text-8xl font-bold my-auto space-2 """ ) @@ -72,7 +132,7 @@ struct HomeView: HTML, Sendable { span( .class( """ - bg-violet-600 rounded-md + bg-secondary rounded-md text-5xl rotate-180 p-2 """ ), diff --git a/Tests/DatabaseClientTests/Resources/rooms.csv b/Tests/DatabaseClientTests/Resources/rooms.csv index cf9b958..9b12eaf 100644 --- a/Tests/DatabaseClientTests/Resources/rooms.csv +++ b/Tests/DatabaseClientTests/Resources/rooms.csv @@ -1,5 +1,5 @@ Name,Heating Load,Cooling Total,Cooling Sensible,Register Count,Delegated To -Bed-1,12345,1234,1321,1, +Bed-1,2345,1234,1321,1, Entry,3456,2345,1234,1, Kitchen,7654,3456,2453,2, -Bath-1,890,345,,1,Kitchen +Bath-1,890,345,,0,Kitchen diff --git a/Tests/DatabaseClientTests/RoomTests.swift b/Tests/DatabaseClientTests/RoomTests.swift index 03816f4..0859872 100644 --- a/Tests/DatabaseClientTests/RoomTests.swift +++ b/Tests/DatabaseClientTests/RoomTests.swift @@ -77,16 +77,13 @@ struct RoomTests { let csvPath = Bundle.module.path(forResource: "rooms", ofType: "csv") let csvFile = Room.CSV(file: try Data(contentsOf: URL(filePath: csvPath!))) let rows = try await csvParser.parseRooms(csvFile) - print() - print("ROWS: \(rows)") - print() - let created = try await database.rooms.createFromCSV(project.id, rows) - - print() - print("CREATED: \(created)") - print() #expect(created.count == rows.count) + + // Check that delegating to another room works properly. + let bath = created.first(where: { $0.name == "Bath-1" })! + let kitchen = created.first(where: { $0.name == "Kitchen" })! + #expect(bath.delegatedTo == kitchen.id) } } diff --git a/Tests/ViewControllerTests/__Snapshots__/ViewControllerTests/home.1.html b/Tests/ViewControllerTests/__Snapshots__/ViewControllerTests/home.1.html index 28155e6..ea624a0 100644 --- a/Tests/ViewControllerTests/__Snapshots__/ViewControllerTests/home.1.html +++ b/Tests/ViewControllerTests/__Snapshots__/ViewControllerTests/home.1.html @@ -30,31 +30,69 @@
-
- -
-
-
-
-
-
+ +
+
+
+
BETA
+
+
+
-

Duct Calc

-
- Pro +

Duct Calc

+
+ Pro +
+ Open source residential duct design program +

Manual-D™ speed sheet, but on the web!

+ +

+ Manual-D™ is a trademark of Air Conditioning Contractors of America (ACCA). + + This site is not designed by or affiliated with ACCA. +

+
+
+
+
+
+
+
Features
+
+
+
    +
  • +
    Built by humans
    +
  • +
  • Fully open source.
  • +
  • Great replacement for speed sheet users.
  • +
  • Great for classrooms.
  • +
  • Store your projects in one place.
  • +
  • Export final project to pdf.
  • +
  • Import room loads via CSV file.
  • +
  • Web based.
  • +
  • Self host (run on your own infrastructure).
  • +
+
+
+
+
Coming Soon
+
+
    +
  • API integration.
  • +
  • Command line interface.
  • +
  • Fitting selection tool.
  • +
  • Room load import from PDF.
  • +
- Open source residential duct design program -

Manual-D™ speed sheet, but on the web!

- -

- Manual-D™ is a trademark of Air Conditioning Contractors of America (ACCA). - - This site is not designed by or affiliated with ACCA. -