feat: WIP
This commit is contained in:
@@ -9,7 +9,6 @@ let package = Package(
|
|||||||
products: [
|
products: [
|
||||||
.executable(name: "App", targets: ["App"]),
|
.executable(name: "App", targets: ["App"]),
|
||||||
.library(name: "ApiController", targets: ["ApiController"]),
|
.library(name: "ApiController", targets: ["ApiController"]),
|
||||||
.library(name: "ClimateZoneClient", targets: ["ClimateZoneClient"]),
|
|
||||||
.library(name: "CoreModels", targets: ["CoreModels"]),
|
.library(name: "CoreModels", targets: ["CoreModels"]),
|
||||||
.library(name: "LocationClient", targets: ["LocationClient"]),
|
.library(name: "LocationClient", targets: ["LocationClient"]),
|
||||||
.library(name: "Routes", targets: ["Routes"]),
|
.library(name: "Routes", targets: ["Routes"]),
|
||||||
@@ -62,22 +61,6 @@ let package = Package(
|
|||||||
],
|
],
|
||||||
swiftSettings: swiftSettings
|
swiftSettings: swiftSettings
|
||||||
),
|
),
|
||||||
.target(
|
|
||||||
name: "LocationClient",
|
|
||||||
dependencies: [
|
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
|
||||||
.product(name: "DependenciesMacros", package: "swift-dependencies")
|
|
||||||
],
|
|
||||||
swiftSettings: swiftSettings
|
|
||||||
),
|
|
||||||
.target(
|
|
||||||
name: "ClimateZoneClient",
|
|
||||||
dependencies: [
|
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
|
||||||
.product(name: "DependenciesMacros", package: "swift-dependencies")
|
|
||||||
],
|
|
||||||
swiftSettings: swiftSettings
|
|
||||||
),
|
|
||||||
.target(
|
.target(
|
||||||
name: "CoreModels",
|
name: "CoreModels",
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
@@ -94,6 +77,7 @@ let package = Package(
|
|||||||
.target(
|
.target(
|
||||||
name: "LocationClient",
|
name: "LocationClient",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
"CoreModels",
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
.product(name: "DependenciesMacros", package: "swift-dependencies")
|
.product(name: "DependenciesMacros", package: "swift-dependencies")
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public enum ClimateZone {
|
public enum ClimateZone: String, CaseIterable, Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
case one = "CZ1"
|
||||||
|
case two = "CZ2"
|
||||||
|
case three = "CZ3"
|
||||||
|
case four = "CZ4"
|
||||||
|
case five = "CZ5"
|
||||||
|
case six = "CZ6"
|
||||||
|
|
||||||
|
public var label: String { rawValue }
|
||||||
|
|
||||||
public enum ZoneType: String, CaseIterable, Codable, Equatable, Sendable {
|
public enum ZoneType: String, CaseIterable, Codable, Equatable, Sendable {
|
||||||
// NOTE: Keep in this order.
|
// NOTE: Keep in this order.
|
||||||
@@ -35,33 +44,33 @@ public enum ClimateZone {
|
|||||||
public var label: String {
|
public var label: String {
|
||||||
return "\(self == .hotHumid ? "Hot Humid" : rawValue.capitalized) (\(zoneIdentifiers.joined(separator: ", ")))"
|
return "\(self == .hotHumid ? "Hot Humid" : rawValue.capitalized) (\(zoneIdentifiers.joined(separator: ", ")))"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents climate zone identifiers.
|
/// Represents climate zone identifiers.
|
||||||
public enum ZoneIdentifier: String, CaseIterable, Codable, Equatable, Sendable {
|
public enum ZoneIdentifier: String, CaseIterable, Codable, Equatable, Sendable {
|
||||||
// A zones (hotHumid)
|
// A zones (hotHumid)
|
||||||
case oneA = "1A"
|
case oneA = "1A"
|
||||||
case twoA = "2A"
|
case twoA = "2A"
|
||||||
// A zones (moist)
|
// A zones (moist)
|
||||||
case threeA = "3A"
|
case threeA = "3A"
|
||||||
case fourA = "4A"
|
case fourA = "4A"
|
||||||
case fiveA = "5A"
|
case fiveA = "5A"
|
||||||
case sixA = "6A"
|
case sixA = "6A"
|
||||||
case sevenA = "7A"
|
case sevenA = "7A"
|
||||||
|
|
||||||
// B zones (dry)
|
// B zones (dry)
|
||||||
case twoB = "2B"
|
case twoB = "2B"
|
||||||
case threeB = "3B"
|
case threeB = "3B"
|
||||||
case fourB = "4B"
|
case fourB = "4B"
|
||||||
case fiveB = "5B"
|
case fiveB = "5B"
|
||||||
case sixB = "6B"
|
case sixB = "6B"
|
||||||
case sevenB = "7B"
|
case sevenB = "7B"
|
||||||
|
|
||||||
// C zones (marine)
|
// C zones (marine)
|
||||||
case threeC = "3C"
|
case threeC = "3C"
|
||||||
case fourC = "4C"
|
case fourC = "4C"
|
||||||
|
|
||||||
public var label: String { rawValue }
|
public var label: String { rawValue }
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
@_exported import CoreModels
|
||||||
import Dependencies
|
import Dependencies
|
||||||
import DependenciesMacros
|
import DependenciesMacros
|
||||||
import Foundation
|
import Foundation
|
||||||
@@ -11,56 +12,132 @@ public extension DependencyValues {
|
|||||||
|
|
||||||
@DependencyClient
|
@DependencyClient
|
||||||
public struct LocationClient: Sendable {
|
public struct LocationClient: Sendable {
|
||||||
public var search: @Sendable (Int) async throws -> [Response]
|
|
||||||
|
|
||||||
// TODO: Add ClimateZone.ZoneIdentifier??
|
/// Estimate the climate zone by the heating design temperature and
|
||||||
public struct Response: Codable, Equatable, Sendable {
|
/// the state code (ex. "OH").
|
||||||
|
public var estimatedClimateZone:
|
||||||
|
@Sendable (_ heatingTemperature: Double, _ stateCode: String) async throws -> ClimateZone
|
||||||
|
|
||||||
public let city: String
|
/// Estimate the design temperatures based on location's coordinates.
|
||||||
public let latitude: String
|
public var estimatedDesignTemperatures: @Sendable (Coordinates) async throws -> DesignTemperatures
|
||||||
public let longitude: String
|
|
||||||
public let zipCode: String
|
|
||||||
public let state: String
|
|
||||||
public let stateCode: String
|
|
||||||
public let county: String
|
|
||||||
|
|
||||||
public init(
|
/// Get location details from a zip code.
|
||||||
city: String,
|
public var search: @Sendable (_ zipCode: Int) async throws -> [Location]
|
||||||
latitude: String,
|
|
||||||
longitude: String,
|
|
||||||
zipCode: String,
|
|
||||||
state: String,
|
|
||||||
stateCode: String,
|
|
||||||
county: String
|
|
||||||
) {
|
|
||||||
self.city = city
|
|
||||||
self.latitude = latitude
|
|
||||||
self.longitude = longitude
|
|
||||||
self.zipCode = zipCode
|
|
||||||
self.state = state
|
|
||||||
self.stateCode = stateCode
|
|
||||||
self.county = county
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
public func estimateDesignTemperaturesAndClimateZone(
|
||||||
case city
|
coordinates: Coordinates,
|
||||||
case latitude
|
stateCode: String
|
||||||
case longitude
|
) async throws -> (DesignTemperatures, ClimateZone) {
|
||||||
case zipCode = "postal_code"
|
let designTemperatures = try await estimatedDesignTemperatures(coordinates)
|
||||||
case state
|
let climateZone = try await estimatedClimateZone(
|
||||||
case stateCode = "state_code"
|
heatingTemperature: designTemperatures.heating,
|
||||||
case county = "province"
|
stateCode: stateCode
|
||||||
}
|
)
|
||||||
|
return (designTemperatures, climateZone)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct DecodingError: Error {}
|
||||||
|
|
||||||
extension LocationClient: TestDependencyKey {
|
extension LocationClient: TestDependencyKey {
|
||||||
public static let testValue: LocationClient = Self()
|
public static let testValue: LocationClient = Self()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Intermediate response returned from 'https://app.zipcodebase.com'
|
||||||
|
private struct IntermediateResponse: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
let city: String
|
||||||
|
let latitude: String
|
||||||
|
let longitude: String
|
||||||
|
let zipCode: String
|
||||||
|
let state: String
|
||||||
|
let stateCode: String
|
||||||
|
let county: String
|
||||||
|
|
||||||
|
func toLocation() throws -> Location {
|
||||||
|
guard let longitude = Double(longitude), let latitude = Double(latitude) else {
|
||||||
|
throw DecodingError()
|
||||||
|
}
|
||||||
|
return .init(
|
||||||
|
city: city,
|
||||||
|
state: state,
|
||||||
|
stateCode: stateCode,
|
||||||
|
zipCode: zipCode,
|
||||||
|
county: county,
|
||||||
|
coordinates: .init(latitude: latitude, longitude: longitude)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case city
|
||||||
|
case latitude
|
||||||
|
case longitude
|
||||||
|
case zipCode = "postal_code"
|
||||||
|
case state
|
||||||
|
case stateCode = "state_code"
|
||||||
|
case county = "province"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func estimatedHeatingTemperature(_ coordinates: Coordinates) -> Double {
|
||||||
|
let latitude = coordinates.latitude
|
||||||
|
let longitude = coordinates.longitude
|
||||||
|
|
||||||
|
// Gulf coast region
|
||||||
|
if latitude < 31, longitude > -95 && longitude < -89 {
|
||||||
|
return 35
|
||||||
|
}
|
||||||
|
|
||||||
|
// Florida
|
||||||
|
if latitude < 31, longitude > -87.5 && longitude < -80 {
|
||||||
|
return 40
|
||||||
|
}
|
||||||
|
|
||||||
|
let absLat = abs(latitude)
|
||||||
|
if absLat < 30 { return 35 }
|
||||||
|
if absLat < 33 { return 30 }
|
||||||
|
if absLat < 36 { return 25 }
|
||||||
|
if absLat < 40 { return 15 }
|
||||||
|
if absLat < 45 { return 5 }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private func estimatedCoolingTemperature(_ coordinates: Coordinates) -> Double {
|
||||||
|
let latitude = coordinates.latitude
|
||||||
|
let longitude = coordinates.longitude
|
||||||
|
|
||||||
|
// Gulf coast and Florida
|
||||||
|
if latitude < 31, longitude > -95 && longitude < -80 {
|
||||||
|
return 95
|
||||||
|
}
|
||||||
|
|
||||||
|
let absLat = abs(latitude)
|
||||||
|
if absLat < 30 { return 95 }
|
||||||
|
if absLat < 33 { return 92 }
|
||||||
|
if absLat < 36 { return 90 }
|
||||||
|
if absLat < 40 { return 90 }
|
||||||
|
if absLat < 45 { return 88 }
|
||||||
|
return 85
|
||||||
|
}
|
||||||
|
|
||||||
|
private func determineClimateZone(heatingTemperature: Double, stateCode: String) -> ClimateZone {
|
||||||
|
let hotHumidStates = ["FL", "LA", "TX", "MS", "AL", "GA", "SC"]
|
||||||
|
if hotHumidStates.contains(stateCode.uppercased()) {
|
||||||
|
return .two
|
||||||
|
}
|
||||||
|
|
||||||
|
if heatingTemperature >= 45 { return .one }
|
||||||
|
if heatingTemperature >= 35 { return .two }
|
||||||
|
if heatingTemperature >= 25 { return .three }
|
||||||
|
if heatingTemperature >= 15 { return .four }
|
||||||
|
if heatingTemperature >= 5 { return .five }
|
||||||
|
return .six
|
||||||
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|
||||||
public extension LocationClient.Response {
|
extension IntermediateResponse {
|
||||||
static let mock = Self(
|
static let mock = Self(
|
||||||
city: "Monroe",
|
city: "Monroe",
|
||||||
latitude: "39.4413000",
|
latitude: "39.4413000",
|
||||||
|
|||||||
50
Sources/Routes/Models/HeatingBalancePoint.swift
Normal file
50
Sources/Routes/Models/HeatingBalancePoint.swift
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
public enum HeatingBalancePoint {
|
||||||
|
|
||||||
|
public enum Mode: String, CaseIterable, Codable, Equatable, Sendable {
|
||||||
|
case thermal
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Request: Codable, Equatable, Sendable {
|
||||||
|
case thermal(Thermal)
|
||||||
|
|
||||||
|
public struct Thermal: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
public let systemSize: Double
|
||||||
|
public let capacityAt47: Double?
|
||||||
|
public let capacityAt17: Double?
|
||||||
|
public let heatingDesignTemperature: Double
|
||||||
|
public let buildingHeatLoss: Double
|
||||||
|
|
||||||
|
public init(
|
||||||
|
systemSize: Double,
|
||||||
|
capacityAt47: Double? = nil,
|
||||||
|
capacityAt17: Double? = nil,
|
||||||
|
heatingDesignTemperature: Double,
|
||||||
|
buildingHeatLoss: Double
|
||||||
|
) {
|
||||||
|
self.systemSize = systemSize
|
||||||
|
self.capacityAt47 = capacityAt47
|
||||||
|
self.capacityAt17 = capacityAt17
|
||||||
|
self.heatingDesignTemperature = heatingDesignTemperature
|
||||||
|
self.buildingHeatLoss = buildingHeatLoss
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Response: Codable, Equatable, Sendable {
|
||||||
|
case thermal(Thermal)
|
||||||
|
|
||||||
|
public struct Thermal: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
public let capacityAt47: Double
|
||||||
|
public let capacityAt17: Double
|
||||||
|
public let balancePointTemperature: Double
|
||||||
|
|
||||||
|
public init(capacityAt47: Double, capacityAt17: Double, balancePointTemperature: Double) {
|
||||||
|
self.capacityAt47 = capacityAt47
|
||||||
|
self.capacityAt17 = capacityAt17
|
||||||
|
self.balancePointTemperature = balancePointTemperature
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import Foundation
|
|||||||
import PsychrometricClient
|
import PsychrometricClient
|
||||||
@preconcurrency import URLRouting
|
@preconcurrency import URLRouting
|
||||||
|
|
||||||
|
// swiftlint:disable type_body_length
|
||||||
public enum SiteRoute: Equatable, Sendable {
|
public enum SiteRoute: Equatable, Sendable {
|
||||||
|
|
||||||
case api(Api)
|
case api(Api)
|
||||||
@@ -392,3 +393,5 @@ public extension SiteRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// swiftlint:enable type_body_length
|
||||||
|
|||||||
Reference in New Issue
Block a user