56 lines
1.8 KiB
Python
56 lines
1.8 KiB
Python
"""
|
|
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"
|