     return Classification(rawValue: token)
   }
   
-  private var weighted: (min: Float?, max: Float?)?
-  private var prices: (min: Float?, max: Float?)?
-  private var hassles: (min: Float, max: Float)?
-  private var durations: (min: Float, max: Float)?
-  private var calories: (min: Float, max: Float)?
-  private var carbons: (min: Float, max: Float)?
+  private struct MetricRange {
+    var best: Trip?
+    var min: Float? // min => best trip
+    var max: Float? // max => worst trip
+  }
+  
+  private var ranges: [Classification: MetricRange] = [:]
+  
+  @discardableResult
+  private func register(_ trip: Trip, classification: Classification) -> Bool {
+    guard let value = trip.value(for: classification) else { return false }
+    
+    var range = ranges[classification] ?? .init()
+    if value < range.min ?? .infinity {
+      range.min = value
+      range.best = trip
+    }
+    if value > range.max ?? .leastNormalMagnitude {
+      range.max = value
+    }
+    ranges[classification] = range
+    return true
+  }
 }
 
 extension TKMetricClassifier: TKTripClassifier {

    let trips = tripGroups.compactMap(\.representativeTrip)
    var anyHaveUnknownCost = false
    for trip in trips {
      if let price = trip.totalPrice?.floatValue {
        prices = (min(prices?.min ?? .infinity, price), max(prices?.max ?? .leastNormalMagnitude, price))
      } else {
        anyHaveUnknownCost = true
      }
      
      weighted = (min(weighted?.min ?? .infinity, trip.totalScore),
                  max(weighted?.max ?? .leastNormalMagnitude, trip.totalScore))
      hassles = (min(hassles?.min ?? .infinity, trip.totalHassle),
                 max(hassles?.max ?? .leastNormalMagnitude, trip.totalHassle))
      durations = (min(durations?.min ?? .infinity, trip.calculateDuration().floatValue),
                   max(durations?.max ?? .leastNormalMagnitude, trip.calculateDuration().floatValue))
      
      // inverted!
      calories = (min(calories?.min ?? .infinity, trip.totalCalories * -1),
                  max(calories?.max ?? .leastNormalMagnitude, trip.totalCalories * -1))
      
      carbons = (min(carbons?.min ?? .infinity, trip.totalCarbon),
                 max(carbons?.max ?? .leastNormalMagnitude, trip.totalCarbon))
    }
    if anyHaveUnknownCost {
      prices = nil
    }
  }
  

    
    guard let trip = tripGroup.representativeTrip else { return nil }
    
    if matches(trip, classification: .recommended) {
      return TKMetricClassifier.Classification.recommended.rawValue
    }
    if matches(trip, classification: .fastest) {
      return TKMetricClassifier.Classification.fastest.rawValue
    }
    if matches(trip, classification: .cheapest) {
      return TKMetricClassifier.Classification.cheapest.rawValue
    }
    if matches(trip, classification: .healthiest) {
      return TKMetricClassifier.Classification.healthiest.rawValue
    }
    if matches(trip, classification: .easiest) {
      return TKMetricClassifier.Classification.easiest.rawValue
    }
    if matches(trip, classification: .greenest) {
      return TKMetricClassifier.Classification.greenest.rawValue
    }
    return nil
  }

  private func matches(_ trip: Trip, classification: Classification) -> Bool {
    guard
      let min = ranges[classification]?.min, let max = ranges[classification]?.max,
      let value = trip.value(for: classification)
    else { return false }
    
    guard min == value else { return false }
    
    // max has to be more than 25% of min, i.e., don't give the label
    // if everything is so clsoe
    return max > min * 1.25
  }

  
  private func matches(min: Float, max: Float, value: Float?) -> Bool {
    guard let value = value else { return false }
    guard min == value else { return false }
    
    // max has to be more than 25% of min, i.e., don't give the label
    // if everything is so clsoe
    return max > min * 1.25
  }
  
}

fileprivate extension Trip {
  func value(for classification: TKMetricClassifier.Classification) -> Float? {
    switch classification {
    case .cheapest: return totalPrice?.floatValue
    case .recommended: return totalScore
    case .fastest: return calculateDuration().floatValue
    case .easiest: return totalHassle
    case .healthiest: return totalCalories * -1 // inverted!
    case .greenest: return totalCarbon
    }
  }
}

fileprivate extension TripGroup {
