Files
swift-estimated-pressures-core/Sources/SharedModels/Flagged.swift

209 lines
5.0 KiB
Swift

import Foundation
@dynamicMemberLookup
public struct Flagged: Equatable {
public var checkValue: CheckHandler
public var wrappedValue: Double
public init(
wrappedValue: Double,
_ handler: CheckHandler
) {
self.checkValue = handler
self.wrappedValue = wrappedValue
}
public init(
wrappedValue: Double,
_ checkValue: @escaping (Double) -> CheckResult
) {
self.checkValue = .init(checkValue)
self.wrappedValue = wrappedValue
}
public static func error(_ wrappedValue: Double? = nil, message: String) -> Self {
self.init(wrappedValue: wrappedValue ?? 0) { _ in
return .error(message)
}
}
public var projectedValue: CheckResult {
checkValue(wrappedValue)
}
public struct GoodMessageHandler {
let message: (Double) -> String?
public init(message: @escaping (Double) -> String?) {
self.message = message
}
public func callAsFunction(_ double: Double) -> String? {
self.message(double)
}
public static var none: Self {
.init { _ in nil }
}
}
public struct CheckHandler {
let checkValue: (Double) -> CheckResult
public init(_ checkValue: @escaping (Double) -> CheckResult) {
self.checkValue = checkValue
}
public func callAsFunction(_ value: Double) -> CheckResult {
self.checkValue(value)
}
}
public enum CheckResult: Equatable {
case aboveMaximum(Double)
case belowMinimum(Double)
case betweenRange(minimum: Double, maximum: Double)
case betweenRatedAndMaximum(rated: Double, maximum: Double)
case good(String? = nil)
case error(String)
public var key: Key {
switch self {
case .aboveMaximum(_):
return .error
case .belowMinimum(_):
return .error
case .betweenRange:
return .warning
case .betweenRatedAndMaximum:
return .warning
case .good(_):
return .good
case .error(_):
return .error
}
}
public var message: String? {
switch self {
case let .aboveMaximum(max):
return "Above maximum: \(doubleString(max))"
case let .belowMinimum(min):
return "Below minimum: \(doubleString(min))"
case .betweenRange(minimum: let minimum, maximum: let maximum):
return "Between: \(minimum) and \(maximum)"
case .betweenRatedAndMaximum(rated: let rated, maximum: let maximum):
return "Between rated: \(doubleString(rated)) and maximum: \(doubleString(maximum))"
case let .good(goodMessage):
return goodMessage
case let .error(errorMessage):
return errorMessage
}
}
public enum Key: String, Equatable, CaseIterable {
case good, warning, error
public var title: String {
self.rawValue.capitalized
}
}
}
public static func == (lhs: Flagged, rhs: Flagged) -> Bool {
lhs.wrappedValue == rhs.wrappedValue
}
public subscript<T>(dynamicMember keyPath: KeyPath<CheckResult, T>) -> T {
self.projectedValue[keyPath: keyPath]
}
}
// MARK: - Check Handlers
extension Flagged.CheckHandler {
public static func using(
maximum: Double,
minimum: Double,
rated: Double,
goodMessage: Flagged.GoodMessageHandler = .none
) -> Self {
.init { value in
if value < minimum {
return .belowMinimum(minimum)
} else if value > maximum {
return .aboveMaximum(maximum)
} else if value > rated {
return .betweenRatedAndMaximum(rated: rated, maximum: maximum)
} else {
return .good(goodMessage(value))
}
}
}
public static func airflow(
tons: CoolingCapacity,
ratings: RatedAirflowPerTon = .init(),
goodMessage: Flagged.GoodMessageHandler? = nil
) -> Self {
.rated(RatedAirflowLimits(tons: tons, using: ratings), goodMessage: goodMessage)
}
public static func percent(
goodMessage: Flagged.GoodMessageHandler = .none
) -> Self {
.using(maximum: 100, minimum: 100, rated: 100)
}
public static func rated<T>(
_ ratings: RatedEnvelope<T>,
goodMessage: Flagged.GoodMessageHandler? = nil
) -> Self {
.using(
maximum: ratings.maximum,
minimum: ratings.minimum,
rated: ratings.rated,
goodMessage: goodMessage ?? .none
)
}
public static func result(_ result: Flagged.CheckResult) -> Self {
self.init { _ in result }
}
public static func velocity(
goodMessage: Flagged.GoodMessageHandler = .none
) -> Self {
.init { value in
if value <= 700 {
return .good(goodMessage(value))
} else if value < 900 {
return .betweenRange(minimum: 700, maximum: 900)
} else {
return .error("Velocity greater than 900 FPM")
}
}
}
}
// MARK: - Helpers
fileprivate let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
return formatter
}()
fileprivate func doubleString(_ double: Double) -> String {
return formatter.string(for: double) ?? "\(double)"
}