feat: Adds capacitor calculations.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -28,6 +28,10 @@ extension ApiController: DependencyKey {
|
||||
logger.debug("API Route: \(route)")
|
||||
|
||||
switch route {
|
||||
case let .calculateCapacitor(request):
|
||||
logger.debug("Calculating capacitor: \(request)")
|
||||
return try await request.respond(logger: logger)
|
||||
|
||||
case let .calculateDehumidifierSize(request):
|
||||
logger.debug("Calculating dehumidifier size: \(request)")
|
||||
return try await request.respond(logger)
|
||||
@@ -42,8 +46,7 @@ extension ApiController: DependencyKey {
|
||||
|
||||
case let .calculateRoomPressure(request):
|
||||
logger.debug("Calculating room pressure: \(request)")
|
||||
// FIX:
|
||||
fatalError()
|
||||
return try await request.respond(logger: logger)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
117
Sources/ApiController/Extensions/Capacitor.swift
Normal file
117
Sources/ApiController/Extensions/Capacitor.swift
Normal file
@@ -0,0 +1,117 @@
|
||||
import Foundation
|
||||
import Logging
|
||||
import OrderedCollections
|
||||
import Routes
|
||||
|
||||
public extension Capacitor.Request {
|
||||
|
||||
static let standardCapacitorSizes = OrderedSet([
|
||||
5, 7.5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80
|
||||
])
|
||||
|
||||
func respond(logger: Logger) async throws -> Capacitor.Response {
|
||||
switch self {
|
||||
case let .size(request):
|
||||
return try calculateSize(request, logger)
|
||||
case let .test(request):
|
||||
return try calculateCapcitance(request, logger)
|
||||
}
|
||||
}
|
||||
|
||||
private func calculateCapcitance(
|
||||
_ request: Capacitor.Request.TestRequest,
|
||||
_ logger: Logger
|
||||
) throws -> Capacitor.Response {
|
||||
try request.validate()
|
||||
|
||||
let capacitance = (2653 * request.startWindingAmps) / request.runToCommonVoltage
|
||||
logger.debug("Test Capacitor calculated capacitance: \(capacitance)")
|
||||
|
||||
var ratedComparison: Capacitor.RatedComparison?
|
||||
|
||||
if let rating = request.ratedCapacitorSize {
|
||||
let rating = Double(rating)
|
||||
let deviation = ((capacitance - rating) / rating) * 100.0
|
||||
logger.debug("Test Capacitor calculated deviation: \(deviation)")
|
||||
// let positiveDeviation = deviation.ensurePostive()
|
||||
|
||||
ratedComparison = .init(
|
||||
value: Int(rating),
|
||||
isInRange: abs(deviation) <= 6,
|
||||
percentDeviation: deviation
|
||||
)
|
||||
}
|
||||
|
||||
return .test(result: .init(
|
||||
capacitance: capacitance,
|
||||
tolerance: .init(capacitance: capacitance),
|
||||
ratedComparison: ratedComparison
|
||||
))
|
||||
}
|
||||
|
||||
private func calculateSize(
|
||||
_ request: Capacitor.Request.SizeRequest,
|
||||
_ logger: Logger
|
||||
) throws -> Capacitor.Response {
|
||||
try request.validate()
|
||||
|
||||
let frequency = 60.0
|
||||
let phaseAngle = acos(request.powerFactor)
|
||||
let reactiveComponent = request.runningAmps * sin(phaseAngle)
|
||||
let capacitance = (reactiveComponent * 1_000_000) / (2 * Double.pi * frequency * request.lineVoltage)
|
||||
|
||||
logger.debug("Calculate capacitor size capacitance: \(capacitance)")
|
||||
|
||||
let standardSize = Self.standardCapacitorSizes.first(where: { $0 >= capacitance })
|
||||
?? Self.standardCapacitorSizes.last!
|
||||
|
||||
logger.debug("Calculate capacitor standard size: \(standardSize)")
|
||||
|
||||
return .size(result: .init(
|
||||
capacitance: capacitance,
|
||||
standardSize: standardSize,
|
||||
tolerance: .init(capacitance: capacitance)
|
||||
))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension Capacitor.Tolerance {
|
||||
init(capacitance: Double) {
|
||||
// +- 6% tolerance
|
||||
self.init(
|
||||
minimum: capacitance * 0.96,
|
||||
maximum: capacitance * 1.06
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private extension Capacitor.Request.TestRequest {
|
||||
func validate() throws {
|
||||
guard startWindingAmps > 0 else {
|
||||
throw ValidationError(message: "Start winding amps should be greater than 0.")
|
||||
}
|
||||
guard runToCommonVoltage > 0 else {
|
||||
throw ValidationError(message: "Run to common voltage should be greater than 0.")
|
||||
}
|
||||
if let ratedCapacitorSize {
|
||||
guard ratedCapacitorSize > 0 else {
|
||||
throw ValidationError(message: "Run to common voltage should be greater than 0.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension Capacitor.Request.SizeRequest {
|
||||
func validate() throws {
|
||||
guard runningAmps > 0 else {
|
||||
throw ValidationError(message: "Running amps should be greater than 0.")
|
||||
}
|
||||
guard lineVoltage > 0 else {
|
||||
throw ValidationError(message: "Line voltage should be greater than 0.")
|
||||
}
|
||||
guard powerFactor > 0, powerFactor < 1.01 else {
|
||||
throw ValidationError(message: "powerFactor should be greater than 0 and at max 1.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import Foundation
|
||||
|
||||
extension Double {
|
||||
|
||||
func ensurePostive() -> Self {
|
||||
if self < 0 { return self * -1 }
|
||||
return self
|
||||
}
|
||||
}
|
||||
121
Sources/Routes/Models/Capacitor.swift
Normal file
121
Sources/Routes/Models/Capacitor.swift
Normal file
@@ -0,0 +1,121 @@
|
||||
public enum Capacitor {
|
||||
|
||||
public enum Mode: String, CaseIterable, Codable, Equatable, Sendable {
|
||||
case size
|
||||
case test
|
||||
}
|
||||
|
||||
public enum Request: Codable, Equatable, Sendable {
|
||||
|
||||
case size(SizeRequest)
|
||||
case test(TestRequest)
|
||||
|
||||
public struct SizeRequest: Codable, Equatable, Sendable {
|
||||
|
||||
public let runningAmps: Double
|
||||
public let lineVoltage: Double
|
||||
public let powerFactor: Double
|
||||
|
||||
public init(runningAmps: Double, lineVoltage: Double, powerFactor: Double) {
|
||||
self.runningAmps = runningAmps
|
||||
self.lineVoltage = lineVoltage
|
||||
self.powerFactor = powerFactor
|
||||
}
|
||||
}
|
||||
|
||||
public struct TestRequest: Codable, Equatable, Sendable {
|
||||
|
||||
public let startWindingAmps: Double
|
||||
public let runToCommonVoltage: Double
|
||||
public let ratedCapacitorSize: Int?
|
||||
|
||||
public init(startWindingAmps: Double, runToCommonVoltage: Double, ratedCapacitorSize: Int? = nil) {
|
||||
self.startWindingAmps = startWindingAmps
|
||||
self.runToCommonVoltage = runToCommonVoltage
|
||||
self.ratedCapacitorSize = ratedCapacitorSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum Response: Codable, Equatable, Sendable {
|
||||
|
||||
case size(result: SizeResponse)
|
||||
case test(result: TestResponse)
|
||||
|
||||
public struct SizeResponse: Codable, Equatable, Sendable {
|
||||
|
||||
public let capacitance: Double
|
||||
public let standardSize: Double
|
||||
public let tolerance: Tolerance
|
||||
|
||||
public init(capacitance: Double, standardSize: Double, tolerance: Capacitor.Tolerance) {
|
||||
self.capacitance = capacitance
|
||||
self.standardSize = standardSize
|
||||
self.tolerance = tolerance
|
||||
}
|
||||
}
|
||||
|
||||
public struct TestResponse: Codable, Equatable, Sendable {
|
||||
|
||||
public let capacitance: Double
|
||||
public let tolerance: Tolerance
|
||||
public let ratedComparison: RatedComparison?
|
||||
|
||||
public init(
|
||||
capacitance: Double,
|
||||
tolerance: Capacitor.Tolerance,
|
||||
ratedComparison: Capacitor.RatedComparison? = nil
|
||||
) {
|
||||
self.capacitance = capacitance
|
||||
self.tolerance = tolerance
|
||||
self.ratedComparison = ratedComparison
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct Tolerance: Codable, Equatable, Sendable {
|
||||
|
||||
public let minimum: Double
|
||||
public let maximum: Double
|
||||
|
||||
public init(minimum: Double, maximum: Double) {
|
||||
self.minimum = minimum
|
||||
self.maximum = maximum
|
||||
}
|
||||
}
|
||||
|
||||
public struct RatedComparison: Codable, Equatable, Sendable {
|
||||
|
||||
public let value: Int
|
||||
public let isInRange: Bool
|
||||
public let percentDeviation: Double
|
||||
|
||||
public init(value: Int, isInRange: Bool, percentDeviation: Double) {
|
||||
self.value = value
|
||||
self.isInRange = isInRange
|
||||
self.percentDeviation = percentDeviation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public extension Capacitor.Response {
|
||||
|
||||
static func mock(mode: Capacitor.Mode) -> Self {
|
||||
switch mode {
|
||||
case .size:
|
||||
return .size(result: .init(
|
||||
capacitance: 57.5,
|
||||
standardSize: 60,
|
||||
tolerance: .init(minimum: 54.1, maximum: 61)
|
||||
))
|
||||
case .test:
|
||||
return .test(result: .init(
|
||||
capacitance: 34.8,
|
||||
tolerance: .init(minimum: 32.7, maximum: 36.9),
|
||||
ratedComparison: .init(value: 35, isInRange: Bool.random(), percentDeviation: 0.6)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -23,6 +23,7 @@ public extension SiteRoute {
|
||||
|
||||
enum Api: Equatable, Sendable {
|
||||
|
||||
case calculateCapacitor(Capacitor.Request)
|
||||
case calculateDehumidifierSize(DehumidifierSize.Request)
|
||||
case calculateHVACSystemPerformance(HVACSystemPerformance.Request)
|
||||
case calculateMoldRisk(MoldRisk.Request)
|
||||
@@ -31,6 +32,16 @@ public extension SiteRoute {
|
||||
static let rootPath = Path { "api"; "v1" }
|
||||
|
||||
public static let router = OneOf {
|
||||
Route(.case(Self.calculateCapacitor)) {
|
||||
Path { "api"; "v1"; "calculateRoomPressure" }
|
||||
Method.post
|
||||
OneOf {
|
||||
Body(.json(Capacitor.Request.SizeRequest.self))
|
||||
.map(.case(Capacitor.Request.size))
|
||||
Body(.json(Capacitor.Request.TestRequest.self))
|
||||
.map(.case(Capacitor.Request.test))
|
||||
}
|
||||
}
|
||||
Route(.case(Self.calculateDehumidifierSize)) {
|
||||
Path { "api"; "v1"; "calculateDehumidifierSize" }
|
||||
Method.post
|
||||
@@ -64,6 +75,7 @@ public extension SiteRoute {
|
||||
enum View: Equatable, Sendable {
|
||||
|
||||
case index
|
||||
case capacitor(Capacitor)
|
||||
case dehumidifierSize(DehumidifierSize)
|
||||
case hvacSystemPerformance(HVACSystemPerformance)
|
||||
case moldRisk(MoldRisk)
|
||||
@@ -73,6 +85,9 @@ public extension SiteRoute {
|
||||
Route(.case(Self.index)) {
|
||||
Method.get
|
||||
}
|
||||
Route(.case(Self.capacitor)) {
|
||||
Capacitor.router
|
||||
}
|
||||
Route(.case(Self.dehumidifierSize)) {
|
||||
DehumidifierSize.router
|
||||
}
|
||||
@@ -87,6 +102,50 @@ public extension SiteRoute {
|
||||
}
|
||||
}
|
||||
|
||||
public enum Capacitor: Equatable, Sendable {
|
||||
case index(mode: Routes.Capacitor.Mode? = nil)
|
||||
case submit(Routes.Capacitor.Request)
|
||||
|
||||
public static var index: Self { .index() }
|
||||
|
||||
static let rootPath = "capacitor-calculator"
|
||||
|
||||
public static let router = OneOf {
|
||||
Route(.case(Self.index)) {
|
||||
Path { rootPath }
|
||||
Method.get
|
||||
Query {
|
||||
Optionally { Field("mode") { Routes.Capacitor.Mode.parser() } }
|
||||
}
|
||||
}
|
||||
Route(.case(Self.submit)) {
|
||||
Path { rootPath }
|
||||
Method.post
|
||||
Body {
|
||||
OneOf {
|
||||
FormData {
|
||||
Field("runningAmps") { Double.parser() }
|
||||
Field("lineVoltage") { Double.parser() }
|
||||
Field("powerFactor") { Double.parser() }
|
||||
}
|
||||
.map(.memberwise(Routes.Capacitor.Request.SizeRequest.init))
|
||||
.map(.case(Routes.Capacitor.Request.size))
|
||||
|
||||
FormData {
|
||||
Field("startWindingAmps") { Double.parser() }
|
||||
Field("runToCommonVoltage") { Double.parser() }
|
||||
Optionally {
|
||||
Field("ratedCapacitorSize") { Int.parser() }
|
||||
}
|
||||
}
|
||||
.map(.memberwise(Routes.Capacitor.Request.TestRequest.init))
|
||||
.map(.case(Routes.Capacitor.Request.test))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum DehumidifierSize: Equatable, Sendable {
|
||||
case index
|
||||
case submit(Routes.DehumidifierSize.Request)
|
||||
|
||||
@@ -44,6 +44,7 @@ public struct SVGSize: Sendable {
|
||||
|
||||
public enum SVGType: Sendable, CaseIterable {
|
||||
case calculator
|
||||
case checkCircle
|
||||
case droplets
|
||||
case exclamation
|
||||
case funnel
|
||||
@@ -60,6 +61,7 @@ public enum SVGType: Sendable, CaseIterable {
|
||||
public func html(_ size: SVGSize) -> some HTML {
|
||||
switch self {
|
||||
case .calculator: return calculatorSvg(size: size)
|
||||
case .checkCircle: return checkCircleSvg(size: size)
|
||||
case .droplets: return dropletsSvg(size: size)
|
||||
case .exclamation: return exclamationSvg(size: size)
|
||||
case .funnel: return funnelSvg(size: size)
|
||||
@@ -80,6 +82,14 @@ public enum SVGType: Sendable, CaseIterable {
|
||||
|
||||
// swiftlint:disable line_length
|
||||
|
||||
private func checkCircleSvg(size: SVGSize) -> HTMLRaw {
|
||||
HTMLRaw("""
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="\(size.width)" height="\(size.height)" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-check-big">
|
||||
<path d="M21.801 10A10 10 0 1 1 17 3.335"/><path d="m9 11 3 3L22 4"/>
|
||||
</svg>
|
||||
""")
|
||||
}
|
||||
|
||||
private func houseSvg(size: SVGSize) -> HTMLRaw {
|
||||
HTMLRaw("""
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="\(size.width)" height="\(size.height)" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-house">
|
||||
|
||||
@@ -76,6 +76,17 @@ extension ViewController: DependencyKey {
|
||||
}
|
||||
}
|
||||
|
||||
case let .capacitor(route):
|
||||
switch route {
|
||||
case let .index(mode: mode):
|
||||
logger.debug("Capacitor view: \(String(describing: mode))")
|
||||
return request.respond(CapacitorForm(mode: mode, response: nil))
|
||||
case let .submit(request):
|
||||
logger.debug("Capaitor view submit: \(request)")
|
||||
let response = try await request.respond(logger: logger)
|
||||
return CapacitorResponse(response: response)
|
||||
}
|
||||
|
||||
case let .dehumidifierSize(route):
|
||||
switch route {
|
||||
case .index:
|
||||
|
||||
@@ -66,6 +66,7 @@ private struct Header: HTML {
|
||||
navLink(label: "Dehumidifier-Sizing", route: .dehumidifierSize(.index))
|
||||
navLink(label: "HVAC-System-Performance", route: .hvacSystemPerformance(.index))
|
||||
navLink(label: "Room-Pressure", route: .roomPressure(.index))
|
||||
navLink(label: "Capcitor-Calculator", route: .capacitor(.index))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
239
Sources/ViewController/Views/Capacitor.swift
Normal file
239
Sources/ViewController/Views/Capacitor.swift
Normal file
@@ -0,0 +1,239 @@
|
||||
import Elementary
|
||||
import ElementaryHTMX
|
||||
import Routes
|
||||
import Styleguide
|
||||
|
||||
struct CapacitorForm: HTML, Sendable {
|
||||
let mode: Capacitor.Mode
|
||||
let response: Capacitor.Response?
|
||||
|
||||
init(mode: Capacitor.Mode?, response: Capacitor.Response? = nil) {
|
||||
self.mode = mode ?? .test
|
||||
self.response = response
|
||||
}
|
||||
|
||||
var content: some HTML {
|
||||
div(.class("relative")) {
|
||||
div(.class("flex flex-wrap justify-between")) {
|
||||
FormHeader(label: "Capacitor Calculator - \(mode.rawValue.capitalized) Capacitor", svg: .zap)
|
||||
// Mode toggle / buttons.
|
||||
div(.class("flex items-center gap-x-0")) {
|
||||
switch mode {
|
||||
case .test:
|
||||
SecondaryButton(label: "Test Capacitor")
|
||||
.attributes(.class("rounded-s-lg"))
|
||||
.attributes(.disabled, when: mode == .test)
|
||||
.attributes(
|
||||
.hx.get(route: .capacitor(.index(mode: .test))),
|
||||
.hx.target("#content"),
|
||||
when: mode == .size
|
||||
)
|
||||
PrimaryButton(label: "Size Capacitor")
|
||||
.attributes(.class("rounded-e-lg"))
|
||||
.attributes(
|
||||
.hx.get(route: .capacitor(.index(mode: .size))),
|
||||
.hx.target("#content"),
|
||||
when: mode == .test
|
||||
)
|
||||
case .size:
|
||||
PrimaryButton(label: "Test Capacitor")
|
||||
.attributes(.class("rounded-s-lg"))
|
||||
.attributes(
|
||||
.hx.get(route: .capacitor(.index(mode: .test))),
|
||||
.hx.target("#content"),
|
||||
when: mode == .size
|
||||
)
|
||||
SecondaryButton(label: "Size Capacitor")
|
||||
.attributes(.class("rounded-e-lg"))
|
||||
.attributes(.disabled, when: mode == .size)
|
||||
.attributes(
|
||||
.hx.get(route: .capacitor(.index(mode: .size))),
|
||||
.hx.target("#content"),
|
||||
when: mode == .test
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Form(mode: mode).attributes(.class("mt-6"))
|
||||
|
||||
div(.id("result")) {
|
||||
if let response {
|
||||
CapacitorResponse(response: response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct Form: HTML, Sendable {
|
||||
let mode: Capacitor.Mode
|
||||
|
||||
var content: some HTML<HTMLTag.form> {
|
||||
form(
|
||||
.hx.post(route: .capacitor(.index)),
|
||||
.hx.target("#result")
|
||||
) {
|
||||
div(.class("space-y-6")) {
|
||||
switch mode {
|
||||
case .size:
|
||||
LabeledContent(label: "Running Current: (amps)") {
|
||||
Input(id: "runningAmps", placeholder: "Current amps")
|
||||
.attributes(.type(.number), .step("0.1"), .min("0.1"), .autofocus, .required)
|
||||
}
|
||||
LabeledContent(label: "Line Voltage") {
|
||||
Input(id: "lineVoltage", placeholder: "Voltage")
|
||||
.attributes(.type(.number), .step("0.1"), .min("0.1"), .required)
|
||||
}
|
||||
LabeledContent(label: "Power Factor") {
|
||||
Input(id: "powerFactor", placeholder: "Power factor (0-1)")
|
||||
.attributes(.type(.number), .step("0.01"), .min("0.1"), .max("1.00"), .required)
|
||||
}
|
||||
case .test:
|
||||
LabeledContent(label: "Start Winding: (amps)") {
|
||||
Input(id: "startWindingAmps", placeholder: "Current amps")
|
||||
.attributes(.type(.number), .step("0.1"), .min("0.1"), .autofocus, .required)
|
||||
}
|
||||
LabeledContent(label: "Run to Common: (volts)") {
|
||||
Input(id: "runToCommonVoltage", placeholder: "Voltage")
|
||||
.attributes(.type(.number), .step("0.1"), .min("0.1"), .required)
|
||||
}
|
||||
LabeledContent(label: "Capacitor Rated Size: (µF)") {
|
||||
Input(id: "ratedCapacitorSize", placeholder: "Size (optional)")
|
||||
.attributes(.type(.number), .step("0.1"), .min("0.1"))
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
SubmitButton(label: "\(mode == .size ? "Calculate Size" : "Test Capacitor")")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CapacitorResponse: HTML, Sendable {
|
||||
let response: Capacitor.Response
|
||||
|
||||
var content: some HTML {
|
||||
ResultContainer(reset: .capacitor(.index(mode: response.mode))) {
|
||||
switch response {
|
||||
case let .size(result: result):
|
||||
sizeResponse(result)
|
||||
case let .test(result: result):
|
||||
testResponse(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func sizeResponse(_ response: Capacitor.Response.SizeResponse) -> some HTML {
|
||||
div {
|
||||
div(
|
||||
.class("""
|
||||
w-full rounded-lg shadow-lg p-4
|
||||
border-2 border-blue-600 bg-blue-100 text-blue-600
|
||||
""")
|
||||
) {
|
||||
div(.class("flex justify-center")) {
|
||||
span(.class("font-extrabold")) { "Required Capacitor Size" }
|
||||
}
|
||||
row("Recommended Standard Size", "\(response.standardSize) µF")
|
||||
row("Calculated Size:", "\(double: response.capacitance, fractionDigits: 1) µF")
|
||||
toleranceRow(response.tolerance)
|
||||
}
|
||||
WarningBox(
|
||||
"Always verify voltage rating matches the application.",
|
||||
"Use the next larger size if exact match is unavailable.",
|
||||
"Ensure capacitor is rated for continuous duty.",
|
||||
"Consider ambient temperature in final selection."
|
||||
) { _ in
|
||||
span(.class("font-extrabold")) { "Important Notes:" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func testResponse(_ response: Capacitor.Response.TestResponse) -> some HTML {
|
||||
div {
|
||||
if let comparison = response.ratedComparison {
|
||||
div(
|
||||
.class("""
|
||||
w-full rounded-lg shadow-lg p-4
|
||||
border-2 \(comparison.borderColor) \(comparison.bgColor) \(comparison.textColor)
|
||||
""")
|
||||
) {
|
||||
div(.class("flex font-bold gap-x-4")) {
|
||||
SVG(
|
||||
comparison.isInRange ? .checkCircle : .exclamation,
|
||||
color: comparison.textColor
|
||||
)
|
||||
span(.class("font-normal")) {
|
||||
"Capacitor is \(comparison.isInRange ? "within" : "outside of") acceptable range: (± 6% of rated value)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div(.class("grid grid-cols-1 \(response.ratedComparison != nil ? "lg:grid-cols-2" : "") gap-4 mt-8")) {
|
||||
div(.class("bg-blue-100 rounded-lg border-2 border-blue-600 text-blue-500 px-4 pb-4")) {
|
||||
div(.class("flex justify-center mb-6 mt-2")) {
|
||||
span(.class("text-2xl font-extrabold")) { "Measured" }
|
||||
}
|
||||
row("Capacitance", "\(double: response.capacitance) µF")
|
||||
toleranceRow(response.tolerance)
|
||||
}
|
||||
|
||||
if let comparison = response.ratedComparison {
|
||||
div(.class("bg-blue-100 rounded-lg border-2 border-blue-600 text-blue-500 px-4 pb-4")) {
|
||||
div(.class("flex justify-center mb-6 mt-2")) {
|
||||
span(.class("text-2xl font-extrabold")) { "Rated Comparison" }
|
||||
}
|
||||
row("Rated Value", "\(comparison.value) µF")
|
||||
row("Deviation", "\(double: comparison.percentDeviation, fractionDigits: 1)%")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func row(_ label: String, _ value: String) -> some HTML {
|
||||
div(.class("flex justify-between mb-2")) {
|
||||
span(.class("font-semibold")) { label }
|
||||
span { value }
|
||||
}
|
||||
}
|
||||
|
||||
private func toleranceRow(_ tolerance: Capacitor.Tolerance) -> some HTML {
|
||||
row(
|
||||
"Acceptable Range (±6%):",
|
||||
"\(double: tolerance.minimum, fractionDigits: 1) - \(double: tolerance.maximum, fractionDigits: 1) µF"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension Capacitor.RatedComparison {
|
||||
|
||||
var bgColor: String {
|
||||
guard isInRange else { return "bg-red-100" }
|
||||
return "bg-green-100"
|
||||
}
|
||||
|
||||
var borderColor: String {
|
||||
guard isInRange else { return "border-red-600" }
|
||||
return "border-green-600"
|
||||
}
|
||||
|
||||
var textColor: String {
|
||||
guard isInRange else { return "text-red-600" }
|
||||
return "text-green-600"
|
||||
}
|
||||
}
|
||||
|
||||
private extension Capacitor.Response {
|
||||
var mode: Capacitor.Mode {
|
||||
switch self {
|
||||
case .size: return .size
|
||||
case .test: return .test
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,11 @@ struct RoomPressureForm: HTML, Sendable {
|
||||
|
||||
var content: some HTML {
|
||||
div(.class("relative")) {
|
||||
FormHeader(label: "Room Pressure Calculator", svg: .leftRightArrow)
|
||||
div(.class("flex flex-wrap justify-between")) {
|
||||
FormHeader(label: "Room Pressure Calculator - \(mode.label)", svg: .leftRightArrow)
|
||||
|
||||
// Mode toggle / buttons.
|
||||
div(.class("absolute top-0 right-0 flex items-center gap-x-0")) {
|
||||
div(.class("flex items-center gap-x-0")) {
|
||||
switch mode {
|
||||
case .knownAirflow:
|
||||
SecondaryButton(label: "Known Airflow")
|
||||
@@ -53,8 +54,10 @@ struct RoomPressureForm: HTML, Sendable {
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Form(mode: mode)
|
||||
.attributes(.class("mt-6"))
|
||||
|
||||
div(.id("result")) {
|
||||
if let response {
|
||||
@@ -241,3 +244,13 @@ struct RoomPressureResult: HTML, Sendable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension RoomPressure.Mode {
|
||||
|
||||
var label: String {
|
||||
switch self {
|
||||
case let .knownAirflow: return "Known Airflow"
|
||||
case let .measuredPressure: return "Measured Pressure"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user