152 lines
4.0 KiB
Swift
152 lines
4.0 KiB
Swift
@_exported import CoreModels
|
|
import Dependencies
|
|
import DependenciesMacros
|
|
import Foundation
|
|
|
|
public extension DependencyValues {
|
|
var locationClient: LocationClient {
|
|
get { self[LocationClient.self] }
|
|
set { self[LocationClient.self] = newValue }
|
|
}
|
|
}
|
|
|
|
@DependencyClient
|
|
public struct LocationClient: Sendable {
|
|
|
|
/// Estimate the climate zone by the heating design temperature and
|
|
/// the state code (ex. "OH").
|
|
public var estimatedClimateZone:
|
|
@Sendable (_ heatingTemperature: Double, _ stateCode: String) async throws -> ClimateZone
|
|
|
|
/// Estimate the design temperatures based on location's coordinates.
|
|
public var estimatedDesignTemperatures: @Sendable (Coordinates) async throws -> DesignTemperatures
|
|
|
|
/// Get location details from a zip code.
|
|
public var search: @Sendable (_ zipCode: Int) async throws -> [Location]
|
|
|
|
public func estimateDesignTemperaturesAndClimateZone(
|
|
coordinates: Coordinates,
|
|
stateCode: String
|
|
) async throws -> (DesignTemperatures, ClimateZone) {
|
|
let designTemperatures = try await estimatedDesignTemperatures(coordinates)
|
|
let climateZone = try await estimatedClimateZone(
|
|
heatingTemperature: designTemperatures.heating,
|
|
stateCode: stateCode
|
|
)
|
|
return (designTemperatures, climateZone)
|
|
}
|
|
|
|
}
|
|
|
|
struct DecodingError: Error {}
|
|
|
|
extension LocationClient: TestDependencyKey {
|
|
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
|
|
|
|
extension IntermediateResponse {
|
|
static let mock = Self(
|
|
city: "Monroe",
|
|
latitude: "39.4413000",
|
|
longitude: "-84.3652000",
|
|
zipCode: "45050",
|
|
state: "Ohio",
|
|
stateCode: "OH",
|
|
county: "Butler"
|
|
)
|
|
}
|
|
#endif
|