feat: Finishes economic balance point.

This commit is contained in:
2025-03-04 17:17:55 -05:00
parent 6c31a9db09
commit b58d053ba1
8 changed files with 226 additions and 15 deletions

View File

@@ -1,6 +1,7 @@
import CoreModels
import Foundation
import Logging
import OpcostAnalysis
import Routes
public extension HeatingBalancePoint.Request {
@@ -10,6 +11,42 @@ public extension HeatingBalancePoint.Request {
case let .thermal(request):
logger.debug("Calculating thermal balance point: \(request)")
return try await request.respond(logger: logger)
case let .economic(request):
logger.debug("Calculating economic balance point: \(request)")
return try await request.respond(logger: logger)
}
}
}
private extension HeatingBalancePoint.Request.Economic {
func respond(logger: Logger) async throws -> HeatingBalancePoint.Response {
try validate()
let fuelCostPerMMBTU = fuelType.costPerMMBTU(afue: fuelAFUE, costPerUnit: fuelCostPerUnit)
logger.debug("Fuel cost per mmBTU: \(fuelCostPerMMBTU)")
let electricCostPerMMBTU = 1_000_000 / 3412 * costPerKW
let electricFuelRatio = electricCostPerMMBTU / fuelCostPerMMBTU
let balancePointTemperature = (electricFuelRatio - 1.8273) / 0.0413
let copAtBalancePoint = 0.0413 * balancePointTemperature + 1.8273
return .economic(.init(
balancePointTemperature: balancePointTemperature,
fuelCostPerMMBTU: fuelCostPerMMBTU,
electricCostPerMMBTU: electricCostPerMMBTU,
copAtBalancePoint: copAtBalancePoint,
electricFuelRatio: electricFuelRatio
))
}
func validate() throws {
guard fuelCostPerUnit > 0 else {
throw ValidationError(message: "Fuel cost should be greater than 0.")
}
guard fuelAFUE > 0, fuelAFUE < 100 else {
throw ValidationError(message: "AFUE is outside of range 0-100%.")
}
guard costPerKW > 0 else {
throw ValidationError(message: "Electric cost per KW should be greater than 0.")
}
}
}
@@ -144,6 +181,35 @@ extension ClimateZone {
case .seven: return -15
}
}
var averageHeatingLoadHours: Double {
switch self {
case .one: return 1000
case .two: return 2250
case .three: return 3750
case .four: return 5250
case .five: return 6750
case .six: return 8250
case .seven: return 10000
}
}
}
private extension HeatingBalancePoint.FuelType {
var btuPerUnit: Double {
switch self {
case .naturalGas: return 100_000
case .propane: return 91333
case .oil: return 138_690
}
}
func costPerMMBTU(afue: Double, costPerUnit: Double) -> Double {
if self == .naturalGas {
return costPerUnit * 10 / afue * 100
}
return (1_000_000 / btuPerUnit) * costPerUnit / afue * 100
}
}
private func thermalBalancePoint(