feat: WIP

This commit is contained in:
2025-03-04 07:26:51 -05:00
parent 9da4149391
commit 19a51598e4
8 changed files with 201 additions and 78 deletions

View File

@@ -1,3 +1,4 @@
@_exported import CoreModels
import Dependencies
import DependenciesMacros
import Foundation
@@ -11,56 +12,132 @@ public extension DependencyValues {
@DependencyClient
public struct LocationClient: Sendable {
public var search: @Sendable (Int) async throws -> [Response]
// TODO: Add ClimateZone.ZoneIdentifier??
public struct Response: Codable, Equatable, 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
public let city: String
public let latitude: String
public let longitude: String
public let zipCode: String
public let state: String
public let stateCode: String
public let county: String
/// Estimate the design temperatures based on location's coordinates.
public var estimatedDesignTemperatures: @Sendable (Coordinates) async throws -> DesignTemperatures
public init(
city: String,
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
}
/// Get location details from a zip code.
public var search: @Sendable (_ zipCode: Int) async throws -> [Location]
private enum CodingKeys: String, CodingKey {
case city
case latitude
case longitude
case zipCode = "postal_code"
case state
case stateCode = "state_code"
case county = "province"
}
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
public extension LocationClient.Response {
extension IntermediateResponse {
static let mock = Self(
city: "Monroe",
latitude: "39.4413000",