feat: Cleaning up shared models.

This commit is contained in:
2024-06-10 09:03:54 -04:00
parent 68da203164
commit 51f7a30701
19 changed files with 383 additions and 339 deletions

View File

@@ -5,9 +5,9 @@ public struct FlaggedEquipmentMeasurementView: View {
@Environment(\.flaggedEquipmentMeasurementStyle) private var style @Environment(\.flaggedEquipmentMeasurementStyle) private var style
let measurement: FlaggedEquipmentMeasurement let measurement: EquipmentMeasurement.FlaggedMeasurement
public init(_ measurement: FlaggedEquipmentMeasurement) { public init(_ measurement: EquipmentMeasurement.FlaggedMeasurement) {
self.measurement = measurement self.measurement = measurement
} }
@@ -29,7 +29,7 @@ public protocol FlaggedEquipmentMeasurementStyle {
} }
public struct FlaggedEquipmentMeasurementStyleConfiguration { public struct FlaggedEquipmentMeasurementStyleConfiguration {
public let measurement: FlaggedEquipmentMeasurement public let measurement: EquipmentMeasurement.FlaggedMeasurement
} }
public struct AnyFlaggedEquipmentMeasurementStyle: FlaggedEquipmentMeasurementStyle { public struct AnyFlaggedEquipmentMeasurementStyle: FlaggedEquipmentMeasurementStyle {
@@ -54,7 +54,7 @@ public struct GridFlaggedEquipmentMeasurementStyle: FlaggedEquipmentMeasurementS
public func makeBody(configuration: Configuration) -> some View { public func makeBody(configuration: Configuration) -> some View {
Grid(alignment: .leading, verticalSpacing: 20) { Grid(alignment: .leading, verticalSpacing: 20) {
ForEach(FlaggedEquipmentMeasurement.Key.allCases) { field in ForEach(EquipmentMeasurement.FlaggedMeasurement.Key.allCases) { field in
FlaggedView( FlaggedView(
field.title, field.title,
flagged: configuration.measurement[keyPath: field.flaggedKeyPath] flagged: configuration.measurement[keyPath: field.flaggedKeyPath]
@@ -93,7 +93,7 @@ extension View {
} }
// MARK: - Key // MARK: - Key
fileprivate extension FlaggedEquipmentMeasurement { fileprivate extension EquipmentMeasurement.FlaggedMeasurement {
// NOTE: These need to be kept in display order. // NOTE: These need to be kept in display order.
enum Key: Hashable, CaseIterable, Identifiable { enum Key: Hashable, CaseIterable, Identifiable {
case returnPlenum case returnPlenum
@@ -122,7 +122,7 @@ fileprivate extension FlaggedEquipmentMeasurement {
} }
} }
var flaggedKeyPath: KeyPath<FlaggedEquipmentMeasurement, Flagged> { var flaggedKeyPath: KeyPath<EquipmentMeasurement.FlaggedMeasurement, Flagged> {
switch self { switch self {
case .returnPlenum: case .returnPlenum:
return \.returnPlenumPressure return \.returnPlenumPressure

View File

@@ -18,14 +18,14 @@ public struct EquipmentMeasurementForm {
public struct State: Equatable { public struct State: Equatable {
@Presents public var destination: Destination.State? @Presents public var destination: Destination.State?
public var allowEquipmentTypeSelection: Bool public var allowEquipmentTypeSelection: Bool
public var equipmentType: EquipmentType public var equipmentType: EquipmentMeasurement.EquipmentType
public var focusedField: Field? public var focusedField: Field?
public var measurements: Measurements public var measurements: Measurements
public init( public init(
allowEquipmentTypeSelection: Bool = true, allowEquipmentTypeSelection: Bool = true,
destination: Destination.State? = nil, destination: Destination.State? = nil,
equipmentType: EquipmentType = .airHandler, equipmentType: EquipmentMeasurement.EquipmentType = .airHandler,
focusedField: Field? = nil, focusedField: Field? = nil,
measurements: Measurements = .init() measurements: Measurements = .init()
) { ) {
@@ -92,7 +92,9 @@ public struct EquipmentMeasurementForm {
} }
} }
public func equipmentMeasurement(type: EquipmentType) -> EquipmentMeasurement { public func equipmentMeasurement(
type: EquipmentMeasurement.EquipmentType
) -> EquipmentMeasurement {
switch type { switch type {
case .airHandler: case .airHandler:
@@ -229,14 +231,9 @@ public struct EquipmentMeasurementFormView: View {
} header: { } header: {
Text("Equipment Type") Text("Equipment Type")
} footer: { } footer: {
Picker("Equipment Type", selection: $store.equipmentType) { EquipmentTypePicker(selection: $store.equipmentType)
ForEach(EquipmentType.allCases) { .pickerStyle(.segmented)
Text($0.description) .labelsHidden()
.tag($0)
}
}
.pickerStyle(.segmented)
.labelsHidden()
} }
} }
Section { Section {

View File

@@ -23,18 +23,18 @@ public struct EquipmentSettingsForm {
public struct State: Equatable { public struct State: Equatable {
@Presents public var destination: Destination.State? @Presents public var destination: Destination.State?
public var coolingCapacity: CoolingCapacity public var coolingCapacity: EquipmentMetadata.CoolingCapacity
public var equipmentType: EquipmentType public var equipmentType: EquipmentMeasurement.EquipmentType
public var fanType: FanType public var fanType: EquipmentMetadata.FanType
public var focusedField: Field? = nil public var focusedField: Field? = nil
public var heatingCapacity: Double? public var heatingCapacity: Double?
public var ratedStaticPressures: RatedStaticPressures public var ratedStaticPressures: RatedStaticPressures
public init( public init(
coolingCapacity: CoolingCapacity = .default, coolingCapacity: EquipmentMetadata.CoolingCapacity = .default,
destination: Destination.State? = nil, destination: Destination.State? = nil,
equipmentType: EquipmentType = .airHandler, equipmentType: EquipmentMeasurement.EquipmentType = .airHandler,
fanType: FanType = .constantSpeed, fanType: EquipmentMetadata.FanType = .constantSpeed,
heatingCapacity: Double? = nil, heatingCapacity: Double? = nil,
ratedStaticPressures: RatedStaticPressures = .init() ratedStaticPressures: RatedStaticPressures = .init()
) { ) {
@@ -154,12 +154,13 @@ public struct EquipmentSettingsFormView: View {
: "Capacity" : "Capacity"
) )
Spacer() Spacer()
Picker("Cooling Capcity", selection: $store.coolingCapacity) { CoolingCapacityPicker(selection: $store.coolingCapacity)
ForEach(CoolingCapacity.allCases) { // Picker("Cooling Capcity", selection: $store.coolingCapacity) {
Text($0.description) // ForEach(CoolingCapacity.allCases) {
.tag($0) // Text($0.description)
} // .tag($0)
} // }
// }
} }
if store.equipmentType == .furnaceAndCoil { if store.equipmentType == .furnaceAndCoil {
GridRow { GridRow {

View File

@@ -10,13 +10,13 @@ public struct EstimationForm {
@ObservableState @ObservableState
public struct State: Equatable { public struct State: Equatable {
public var cfmPerTon: Int public var cfmPerTon: Int
public var coolingCapacity: CoolingCapacity public var coolingCapacity: EquipmentMetadata.CoolingCapacity
public var filterPressureDrop: Double? public var filterPressureDrop: Double?
public var name: String public var name: String
public init( public init(
cfmPerTon: Int = 350, cfmPerTon: Int = 350,
coolingCapacity: CoolingCapacity = .default, coolingCapacity: EquipmentMetadata.CoolingCapacity = .default,
filterPressureDrop: Double? = nil, filterPressureDrop: Double? = nil,
name: String = "" name: String = ""
) { ) {

View File

@@ -35,13 +35,13 @@ public struct FlaggedMeasurementsList {
public struct FlaggedMeasurementContainer: Equatable, Identifiable { public struct FlaggedMeasurementContainer: Equatable, Identifiable {
public let id: UUID public let id: UUID
public var flaggedMeasurement: FlaggedEquipmentMeasurement public var flaggedMeasurement: EquipmentMeasurement.FlaggedMeasurement
public var name: String public var name: String
public init( public init(
id: UUID, id: UUID,
name: String, name: String,
flaggedMeasurement: FlaggedEquipmentMeasurement flaggedMeasurement: EquipmentMeasurement.FlaggedMeasurement
) { ) {
self.id = id self.id = id
self.name = name self.name = name
@@ -58,8 +58,8 @@ public struct FlaggedMeasurementsList {
@CasePathable @CasePathable
public enum ReceiveAction { public enum ReceiveAction {
case existingFlaggedMeasurement(FlaggedEquipmentMeasurement) case existingFlaggedMeasurement(EquipmentMeasurement.FlaggedMeasurement)
case estimatedFlaggedMeasurement(name: String, measurement: FlaggedEquipmentMeasurement) case estimatedFlaggedMeasurement(name: String, measurement: EquipmentMeasurement.FlaggedMeasurement)
} }
@CasePathable @CasePathable
@@ -190,7 +190,7 @@ public struct FlaggedMeasurementsList {
filterPressureDrop: filterPressureDrop filterPressureDrop: filterPressureDrop
) )
let flaggedMeasurement = FlaggedEquipmentMeasurement( let flaggedMeasurement = EquipmentMeasurement.FlaggedMeasurement(
budgets: budgets, budgets: budgets,
measurement: measurement, measurement: measurement,
ratedPressures: ratedStaticPressures, ratedPressures: ratedStaticPressures,

View File

@@ -4,19 +4,19 @@ import SharedModels
struct SharedSettings: Equatable { struct SharedSettings: Equatable {
var budgets: BudgetedPercentEnvelope? var budgets: BudgetedPercentEnvelope?
var coolingCapacity: CoolingCapacity var coolingCapacity: EquipmentMetadata.CoolingCapacity
var equipmentMeasurement: EquipmentMeasurement? var equipmentMeasurement: EquipmentMeasurement?
var fanType: FanType var fanType: EquipmentMetadata.FanType
var flaggedEquipmentMeasurement: FlaggedEquipmentMeasurement? var flaggedEquipmentMeasurement: EquipmentMeasurement.FlaggedMeasurement?
var heatingCapacity: Double? var heatingCapacity: Double?
var ratedStaticPressures: RatedStaticPressures var ratedStaticPressures: RatedStaticPressures
init( init(
budgets: BudgetedPercentEnvelope? = nil, budgets: BudgetedPercentEnvelope? = nil,
coolingCapacity: CoolingCapacity = .default, coolingCapacity: EquipmentMetadata.CoolingCapacity = .default,
equipmentMeasurement: EquipmentMeasurement? = nil, equipmentMeasurement: EquipmentMeasurement? = nil,
fanType: FanType = .constantSpeed, fanType: EquipmentMetadata.FanType = .constantSpeed,
flaggedEquipmentMeasurement: FlaggedEquipmentMeasurement? = nil, flaggedEquipmentMeasurement: EquipmentMeasurement.FlaggedMeasurement? = nil,
heatingCapacity: Double? = nil, heatingCapacity: Double? = nil,
ratedStaticPressures: RatedStaticPressures = .init() ratedStaticPressures: RatedStaticPressures = .init()
) { ) {

View File

@@ -27,7 +27,10 @@ public struct BudgetedPercentEnvelope: Equatable {
coilBudget + filterBudget + supplyPlenumBudget + returnPlenumBudget coilBudget + filterBudget + supplyPlenumBudget + returnPlenumBudget
} }
public init(equipmentType: EquipmentType, fanType: FanType) { public init(
equipmentType: EquipmentMeasurement.EquipmentType,
fanType: EquipmentMetadata.FanType
) {
switch equipmentType { switch equipmentType {
case .furnaceAndCoil: case .furnaceAndCoil:
switch fanType { switch fanType {

View File

@@ -1,43 +1,2 @@
import Foundation import Foundation
public enum CoolingCapacity: Double, Hashable, CaseIterable, Identifiable, CustomStringConvertible {
case half = 0.5
case threeQuarter = 0.75
case one = 1
case oneAndAHalf = 1.5
case two = 2
case twoAndAHalf = 2.5
case three = 3
case threeAndAHalf = 3.5
case four = 4
case five = 5
public var id: Self { self }
public static var `default`: Self { .three }
public var description: String {
switch self {
case .half:
return "1/2 Ton"
case .threeQuarter:
return "3/4 Ton"
case .one:
return "1 Ton"
case .oneAndAHalf:
return "1.5 Tons"
case .two:
return "2 Tons"
case .twoAndAHalf:
return "2.5 Tons"
case .three:
return "3 Tons"
case .threeAndAHalf:
return "3.5 Tons"
case .four:
return "4 Tons"
case .five:
return "5 Tons"
}
}
}

View File

@@ -7,7 +7,7 @@ public enum EquipmentMeasurement: Equatable {
case airHandler(AirHandler) case airHandler(AirHandler)
case furnaceAndCoil(FurnaceAndCoil) case furnaceAndCoil(FurnaceAndCoil)
public var equipmentType: EquipmentType { public var equipmentType: EquipmentType {
switch self { switch self {
case .airHandler: case .airHandler:
@@ -25,7 +25,7 @@ public enum EquipmentMeasurement: Equatable {
return furnaceAndCoil.externalStaticPressure return furnaceAndCoil.externalStaticPressure
} }
} }
public struct AirHandler: Equatable { public struct AirHandler: Equatable {
@Positive @Positive
@@ -70,6 +70,144 @@ public enum EquipmentMeasurement: Equatable {
} }
} }
public enum EquipmentType: Equatable, CaseIterable, CustomStringConvertible, Identifiable {
case airHandler
case furnaceAndCoil
public var id: Self { self }
public var description: String {
switch self {
case .airHandler:
return "Air Handler"
case .furnaceAndCoil:
return "Furnace & Coil"
}
}
}
// TODO: Needs updated for when forms are using `minmal` values.
public struct FlaggedMeasurement: Equatable {
public var airflow: Flagged
public var coilPressureDrop: Flagged
public var externalStaticPressure: Flagged
public var filterPressureDrop: Flagged
public var returnPlenumPressure: Flagged
public var supplyPlenumPressure: Flagged
public init(
airflow: Flagged,
coilPressureDrop: Flagged,
externalStaticPressure: Flagged,
filterPressureDrop: Flagged,
returnPlenumPressure: Flagged,
supplyPlenumPressure: Flagged
) {
self.airflow = airflow
self.coilPressureDrop = coilPressureDrop
self.externalStaticPressure = externalStaticPressure
self.filterPressureDrop = filterPressureDrop
self.returnPlenumPressure = returnPlenumPressure
self.supplyPlenumPressure = supplyPlenumPressure
}
public init(
budgets: BudgetedPercentEnvelope,
measurement: EquipmentMeasurement,
ratedPressures: RatedStaticPressures,
tons: EquipmentMetadata.CoolingCapacity?
) {
switch measurement {
case let .airHandler(airHandler):
self = .airHandler(
budgets: budgets,
measurement: airHandler,
ratedPressures: ratedPressures,
tons: tons
)
case let .furnaceAndCoil(furnaceAndCoil):
self = .furnaceAndCoil(
budgets: budgets,
measurement: furnaceAndCoil,
ratedPressures: ratedPressures,
tons: tons
)
}
}
public static func airHandler(
budgets: BudgetedPercentEnvelope,
measurement: EquipmentMeasurement.AirHandler,
ratedPressures: RatedStaticPressures,
tons: EquipmentMetadata.CoolingCapacity?
) -> Self {
.init(
airflow: checkAirflow(value: measurement.airflow, tons: tons),
coilPressureDrop: .init(
wrappedValue: (measurement.$postCoilPressure.positiveValue - measurement.$postFilterPressure.positiveValue) ?? 0,
.result(.good())
),
externalStaticPressure: checkExternalStaticPressure(
value: measurement.externalStaticPressure,
ratedPressures: ratedPressures
),
filterPressureDrop: .init(
value: measurement.$postFilterPressure.positiveValue - measurement.$returnPlenumPressure.positiveValue,
budget: budgets.filterBudget,
ratedPressures: ratedPressures
),
returnPlenumPressure: .init(
value: measurement.$returnPlenumPressure.positiveValue,
budget: budgets.returnPlenumBudget,
ratedPressures: ratedPressures
),
supplyPlenumPressure: .init(
value: measurement.$supplyPlenumPressure.positiveValue ?? 0,
budget: budgets.supplyPlenumBudget,
ratedPressures: ratedPressures
)
)
}
public static func furnaceAndCoil(
budgets: BudgetedPercentEnvelope,
measurement: EquipmentMeasurement.FurnaceAndCoil,
ratedPressures: RatedStaticPressures,
tons: EquipmentMetadata.CoolingCapacity?
) -> Self {
.init(
airflow: checkAirflow(value: measurement.airflow, tons: tons),
coilPressureDrop: .init(
value: measurement.$preCoilPressure.positiveValue - measurement.$supplyPlenumPressure.positiveValue,
budget: budgets.coilBudget,
ratedPressures: ratedPressures
),
externalStaticPressure: checkExternalStaticPressure(
value: measurement.externalStaticPressure,
ratedPressures: ratedPressures
),
filterPressureDrop: .init(
value: measurement.$postFilterPressure.positiveValue - measurement.$returnPlenumPressure.positiveValue,
budget: budgets.filterBudget,
ratedPressures: ratedPressures,
ignoreMinimum: true
),
returnPlenumPressure: .init(
value: measurement.$returnPlenumPressure.positiveValue,
budget: budgets.returnPlenumBudget,
ratedPressures: ratedPressures
),
supplyPlenumPressure: .init(
value: measurement.$supplyPlenumPressure.positiveValue,
budget: budgets.supplyPlenumBudget,
ratedPressures: ratedPressures
)
)
}
}
public struct FurnaceAndCoil: Equatable { public struct FurnaceAndCoil: Equatable {
@Positive @Positive
@@ -128,6 +266,62 @@ public enum EquipmentMeasurement: Equatable {
// case airflow // case airflow
// } // }
//} //}
fileprivate extension Flagged {
init(
value: Double,
budget: Percentage,
ratedPressures: RatedStaticPressures,
ignoreMinimum: Bool = false
) {
let minimum = ignoreMinimum ? 0 : ratedPressures.minimum * budget.fraction
let maximum = ratedPressures.maximum * budget.fraction
let rated = ratedPressures.rated * budget.fraction
self.init(
wrappedValue: value,
.using(maximum: maximum, minimum: minimum, rated: rated)
)
}
init(
value: Double?,
budget: Percentage,
ratedPressures: RatedStaticPressures,
ignoreMinimum: Bool = false
) {
guard let value else {
self = .error(message: "Value is not set.")
return
}
self.init(
value: value,
budget: budget,
ratedPressures: ratedPressures,
ignoreMinimum: ignoreMinimum
)
}
}
fileprivate func checkExternalStaticPressure(
value: Double,
ratedPressures: RatedStaticPressures
) -> Flagged {
.init(
wrappedValue: value,
.rated(ratedPressures)
)
}
fileprivate func checkAirflow(
value: Double?,
tons: EquipmentMetadata.CoolingCapacity?
) -> Flagged {
guard let value, let tons else {
return .init(wrappedValue: value ?? 0, .result(.good()))
}
return .init(wrappedValue: value, .airflow(tons: tons))
}
#if DEBUG #if DEBUG
extension EquipmentMeasurement { extension EquipmentMeasurement {
@@ -153,4 +347,15 @@ extension EquipmentMeasurement {
} }
} }
} }
extension EquipmentMeasurement.FlaggedMeasurement {
public static func mock(type equipmentType: EquipmentMeasurement.EquipmentType) -> Self {
.init(
budgets: .init(equipmentType: equipmentType, fanType: .variableSpeed),
measurement: .mock(type: equipmentType),
ratedPressures: .init(),
tons: .default
)
}
}
#endif #endif

View File

@@ -0,0 +1,76 @@
import Foundation
public struct EquipmentMetadata: Equatable {
public var coolingCapacity: CoolingCapacity
public var fanType: FanType
public var ratedStaticPressure: RatedStaticPressures
public init(
coolingCapacity: CoolingCapacity = .three,
fanType: FanType = .constantSpeed,
ratedStaticPressure: RatedStaticPressures = .init()
) {
self.coolingCapacity = coolingCapacity
self.fanType = fanType
self.ratedStaticPressure = ratedStaticPressure
}
public enum CoolingCapacity: Double, Equatable, CaseIterable, Identifiable, CustomStringConvertible {
case half = 0.5
case threeQuarter = 0.75
case one = 1
case oneAndAHalf = 1.5
case two = 2
case twoAndAHalf = 2.5
case three = 3
case threeAndAHalf = 3.5
case four = 4
case five = 5
public var id: Self { self }
public static var `default`: Self { .three }
public var description: String {
switch self {
case .half:
return "1/2 Ton"
case .threeQuarter:
return "3/4 Ton"
case .one:
return "1 Ton"
case .oneAndAHalf:
return "1.5 Tons"
case .two:
return "2 Tons"
case .twoAndAHalf:
return "2.5 Tons"
case .three:
return "3 Tons"
case .threeAndAHalf:
return "3.5 Tons"
case .four:
return "4 Tons"
case .five:
return "5 Tons"
}
}
}
public enum FanType: Hashable, Equatable, CaseIterable, CustomStringConvertible, Identifiable {
case constantSpeed
case variableSpeed
public var id: Self { self }
public static var `default`: Self { .constantSpeed }
public var description: String {
switch self {
case .constantSpeed:
return "Constant Speed"
case .variableSpeed:
return "Variable Speed"
}
}
}
}

View File

@@ -1,18 +0,0 @@
import Foundation
public enum EquipmentType: Equatable, CaseIterable, CustomStringConvertible, Identifiable {
case airHandler
case furnaceAndCoil
public var id: Self { self }
public var description: String {
switch self {
case .airHandler:
return "Air Handler"
case .furnaceAndCoil:
return "Furnace & Coil"
}
}
}

View File

@@ -1,17 +0,0 @@
import Foundation
public enum FanType: Hashable, Equatable, CaseIterable, CustomStringConvertible, Identifiable {
case constantSpeed
case variableSpeed
public var id: Self { self }
public var description: String {
switch self {
case .constantSpeed:
return "Constant Speed"
case .variableSpeed:
return "Variable Speed"
}
}
}

View File

@@ -151,11 +151,11 @@ extension Flagged.CheckHandler {
} }
public static func airflow( public static func airflow(
tons: CoolingCapacity, tons: EquipmentMetadata.CoolingCapacity,
ratings: RatedAirflowPerTon = .init(), ratings: RatedAirflowPerTon = .init(),
goodMessage: Flagged.GoodMessageHandler? = nil goodMessage: Flagged.GoodMessageHandler? = nil
) -> Self { ) -> Self {
.rated(RatedAirflowLimits(tons: tons, using: ratings), goodMessage: goodMessage) .rated(RatedAirflowLimits(tons: tons, airflowPerTon: ratings), goodMessage: goodMessage)
} }
public static func percent( public static func percent(

View File

@@ -1,190 +0,0 @@
import Foundation
// TODO: Needs updated for when forms are using `minmal` values.
public struct FlaggedEquipmentMeasurement: Equatable {
public var airflow: Flagged
public var coilPressureDrop: Flagged
public var externalStaticPressure: Flagged
public var filterPressureDrop: Flagged
public var returnPlenumPressure: Flagged
public var supplyPlenumPressure: Flagged
public init(
airflow: Flagged,
coilPressureDrop: Flagged,
externalStaticPressure: Flagged,
filterPressureDrop: Flagged,
returnPlenumPressure: Flagged,
supplyPlenumPressure: Flagged
) {
self.airflow = airflow
self.coilPressureDrop = coilPressureDrop
self.externalStaticPressure = externalStaticPressure
self.filterPressureDrop = filterPressureDrop
self.returnPlenumPressure = returnPlenumPressure
self.supplyPlenumPressure = supplyPlenumPressure
}
public init(
budgets: BudgetedPercentEnvelope,
measurement: EquipmentMeasurement,
ratedPressures: RatedStaticPressures,
tons: CoolingCapacity?
) {
switch measurement {
case let .airHandler(airHandler):
self = .airHandler(
budgets: budgets,
measurement: airHandler,
ratedPressures: ratedPressures,
tons: tons
)
case let .furnaceAndCoil(furnaceAndCoil):
self = .furnaceAndCoil(
budgets: budgets,
measurement: furnaceAndCoil,
ratedPressures: ratedPressures,
tons: tons
)
}
}
public static func airHandler(
budgets: BudgetedPercentEnvelope,
measurement: EquipmentMeasurement.AirHandler,
ratedPressures: RatedStaticPressures,
tons: CoolingCapacity?
) -> Self {
.init(
airflow: checkAirflow(value: measurement.airflow, tons: tons),
coilPressureDrop: .init(
wrappedValue: (measurement.$postCoilPressure.positiveValue - measurement.$postFilterPressure.positiveValue) ?? 0,
.result(.good())
),
externalStaticPressure: checkExternalStaticPressure(
value: measurement.externalStaticPressure,
ratedPressures: ratedPressures
),
filterPressureDrop: .init(
value: measurement.$postFilterPressure.positiveValue - measurement.$returnPlenumPressure.positiveValue,
budget: budgets.filterBudget,
ratedPressures: ratedPressures
),
returnPlenumPressure: .init(
value: measurement.$returnPlenumPressure.positiveValue,
budget: budgets.returnPlenumBudget,
ratedPressures: ratedPressures
),
supplyPlenumPressure: .init(
value: measurement.$supplyPlenumPressure.positiveValue ?? 0,
budget: budgets.supplyPlenumBudget,
ratedPressures: ratedPressures
)
)
}
public static func furnaceAndCoil(
budgets: BudgetedPercentEnvelope,
measurement: EquipmentMeasurement.FurnaceAndCoil,
ratedPressures: RatedStaticPressures,
tons: CoolingCapacity?
) -> Self {
.init(
airflow: checkAirflow(value: measurement.airflow, tons: tons),
coilPressureDrop: .init(
value: measurement.$preCoilPressure.positiveValue - measurement.$supplyPlenumPressure.positiveValue,
budget: budgets.coilBudget,
ratedPressures: ratedPressures
),
externalStaticPressure: checkExternalStaticPressure(
value: measurement.externalStaticPressure,
ratedPressures: ratedPressures
),
filterPressureDrop: .init(
value: measurement.$postFilterPressure.positiveValue - measurement.$returnPlenumPressure.positiveValue,
budget: budgets.filterBudget,
ratedPressures: ratedPressures,
ignoreMinimum: true
),
returnPlenumPressure: .init(
value: measurement.$returnPlenumPressure.positiveValue,
budget: budgets.returnPlenumBudget,
ratedPressures: ratedPressures
),
supplyPlenumPressure: .init(
value: measurement.$supplyPlenumPressure.positiveValue,
budget: budgets.supplyPlenumBudget,
ratedPressures: ratedPressures
)
)
}
}
// MARK: - Helpers
#if DEBUG
extension FlaggedEquipmentMeasurement {
public static func mock(type equipmentType: EquipmentType) -> Self {
.init(
budgets: .init(equipmentType: equipmentType, fanType: .variableSpeed),
measurement: .mock(type: equipmentType),
ratedPressures: .init(),
tons: .default
)
}
}
#endif
fileprivate extension Flagged {
init(
value: Double,
budget: Percentage,
ratedPressures: RatedStaticPressures,
ignoreMinimum: Bool = false
) {
let minimum = ignoreMinimum ? 0 : ratedPressures.minimum * budget.fraction
let maximum = ratedPressures.maximum * budget.fraction
let rated = ratedPressures.rated * budget.fraction
self.init(
wrappedValue: value,
.using(maximum: maximum, minimum: minimum, rated: rated)
)
}
init(
value: Double?,
budget: Percentage,
ratedPressures: RatedStaticPressures,
ignoreMinimum: Bool = false
) {
guard let value else {
self = .error(message: "Value is not set.")
return
}
self.init(
value: value,
budget: budget,
ratedPressures: ratedPressures,
ignoreMinimum: ignoreMinimum
)
}
}
fileprivate func checkExternalStaticPressure(
value: Double,
ratedPressures: RatedStaticPressures
) -> Flagged {
.init(
wrappedValue: value,
.rated(ratedPressures)
)
}
fileprivate func checkAirflow(
value: Double?,
tons: CoolingCapacity?
) -> Flagged {
guard let value, let tons else {
return .init(wrappedValue: value ?? 0, .result(.good()))
}
return .init(wrappedValue: value, .airflow(tons: tons))
}

View File

@@ -40,7 +40,10 @@ extension RatedAirflowPerTon {
// MARK: - Airflow Limits // MARK: - Airflow Limits
public typealias RatedAirflowLimits = RatedEnvelope<AirflowLimits> public typealias RatedAirflowLimits = RatedEnvelope<AirflowLimits>
extension RatedAirflowLimits { extension RatedAirflowLimits {
public init(tons: CoolingCapacity, using airflowPerTon: RatedAirflowPerTon = .init()) { public init(
tons: EquipmentMetadata.CoolingCapacity,
airflowPerTon: RatedAirflowPerTon = .init()
) {
self.init( self.init(
maximum: airflowPerTon.maximum * tons.rawValue, maximum: airflowPerTon.maximum * tons.rawValue,
minimum: airflowPerTon.minimum * tons.rawValue, minimum: airflowPerTon.minimum * tons.rawValue,

View File

@@ -0,0 +1,40 @@
import SwiftUI
public struct CaseIterablePicker<Label, Content>: View
where Content: CaseIterable,
Content: CustomStringConvertible,
Content: Hashable,
Content: Identifiable,
Content.AllCases: RandomAccessCollection,
Label: View
{
let label: () -> Label
@Binding<Content> var selection: Content
public init(
selection: Binding<Content>,
@ViewBuilder label: @escaping () -> Label
) {
self.label = label
self._selection = selection
}
public var body: some View {
Picker(selection: $selection, label: label()) {
ForEach(Content.allCases) {
Text($0.description)
.tag($0)
}
}
}
}
extension CaseIterablePicker where Label == Text {
public init<S: StringProtocol>(_ title: S, selection: Binding<Content>) {
self.init(selection: selection) {
Text(title)
}
}
}

View File

@@ -2,18 +2,13 @@ import SharedModels
import SwiftUI import SwiftUI
public struct CoolingCapacityPicker: View { public struct CoolingCapacityPicker: View {
@Binding var selection: CoolingCapacity @Binding var selection: EquipmentMetadata.CoolingCapacity
public init(selection: Binding<CoolingCapacity>) { public init(selection: Binding<EquipmentMetadata.CoolingCapacity>) {
self._selection = selection self._selection = selection
} }
public var body: some View { public var body: some View {
Picker("Cooling Capacity", selection: $selection) { CaseIterablePicker("Cooling Capacity", selection: $selection)
ForEach(CoolingCapacity.allCases) {
Text($0.description)
.tag($0)
}
}
} }
} }

View File

@@ -2,18 +2,13 @@ import SharedModels
import SwiftUI import SwiftUI
public struct EquipmentTypePicker: View { public struct EquipmentTypePicker: View {
@Binding var selection: EquipmentType @Binding var selection: EquipmentMeasurement.EquipmentType
public init(selection: Binding<EquipmentType>) { public init(selection: Binding<EquipmentMeasurement.EquipmentType>) {
self._selection = selection self._selection = selection
} }
public var body: some View { public var body: some View {
Picker("Equipment Type", selection: $selection) { CaseIterablePicker("Equipment Type", selection: $selection)
ForEach(EquipmentType.allCases) {
Text($0.description)
.tag($0)
}
}
} }
} }

View File

@@ -3,19 +3,14 @@ import SwiftUI
public struct FanTypePicker: View { public struct FanTypePicker: View {
@Binding var selection: FanType @Binding var selection: EquipmentMetadata.FanType
public init(selection: Binding<FanType>) { public init(selection: Binding<EquipmentMetadata.FanType>) {
self._selection = selection self._selection = selection
} }
public var body: some View { public var body: some View {
Picker("Fan Type", selection: $selection) { CaseIterablePicker("Fan Type", selection: $selection)
ForEach(FanType.allCases) {
Text($0.description)
.tag($0)
}
}
} }
} }