""" Feature band encoders ====================== Discrete bucketers for assignment-context features (distance, time-of-day, load, order density). These are small pure helpers used by the Thompson-sampling strategy bandit (`strategy_bandit.py`) and the /riderassign bandit context. NOTE: The ID3 SUCCESS/RISK decision tree that used to live here has been retired — it only ever produced response metadata and never affected any assignment. Only the generic feature-band encoders remain. """ from datetime import datetime def distance_band(km: float) -> str: """Total route distance -> discrete band.""" if km <= 5.0: return "SHORT" if km <= 15.0: return "MID" if km <= 30.0: return "LONG" return "VERY_LONG" def time_band(ts_str: str) -> str: """ISO timestamp -> time-of-day band.""" try: hour = datetime.fromisoformat(ts_str).hour if 6 <= hour < 10: return "MORNING_RUSH" if 10 <= hour < 12: return "LATE_MORNING" if 12 <= hour < 14: return "LUNCH_RUSH" if 14 <= hour < 17: return "AFTERNOON" if 17 <= hour < 20: return "EVENING_RUSH" if 20 <= hour < 23: return "NIGHT" return "LATE_NIGHT" except Exception: return "UNKNOWN" def load_band(avg_load: float) -> str: """Average orders-per-rider -> load band.""" if avg_load <= 2.0: return "LIGHT" if avg_load <= 5.0: return "MODERATE" if avg_load <= 8.0: return "HEAVY" return "OVERLOADED" def order_density_band(num_orders: int, num_riders: int) -> str: """Orders per available rider -> density band.""" if num_riders == 0: return "NO_RIDERS" ratio = num_orders / num_riders if ratio <= 2.0: return "SPARSE" if ratio <= 5.0: return "NORMAL" if ratio <= 9.0: return "DENSE" return "OVERLOADED"