Repository navigation
Expand file tree
/
Copy pathmatching_engine.py
More file actions
125 lines (100 loc) · 4.21 KB
/
Copy pathmatching_engine.py
File metadata and controls
125 lines (100 loc) · 4.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
from math import sqrt
from models import DriverLocationEvent, MatchResultEvent, TripRequestEvent
from utils import get_logger, now_timestamp
class MatchingEngine:
"""
Core driver–rider matching algorithm.
This version uses:
- Euclidean distance heuristic
- Simple ETA estimation
- Surge-aware scoring
- Top-K candidate ranking
In real systems (Uber, Lyft), this would expand into:
- H3 geospatial indexing
- ML-based ETA prediction
- Supply/demand balancing
"""
def __init__(self, max_candidates: int = 25):
self.max_candidates = max_candidates
self.logger = get_logger("MatchingEngine")
# ------------------------------------------------------------
# Distance Heuristic
# ------------------------------------------------------------
def _distance(self, lat1: float, lon1: float, lat2: float, lon2: float) -> float:
"""
Simple Euclidean distance for demo purposes.
Replace with Haversine or H3 index for production.
"""
return sqrt((lat1 - lat2) ** 2 + (lon1 - lon2) ** 2)
# ------------------------------------------------------------
# ETA Estimation
# ------------------------------------------------------------
def _estimate_eta(self, distance: float) -> float:
"""
ETA calculation: distance * factor
In real systems: ML model or traffic-aware routing system.
"""
return round(distance * 4, 2) # 4 minutes per distance unit heuristic
# ------------------------------------------------------------
# Score Function
# ------------------------------------------------------------
def _score_driver(self, distance: float, surge_multiplier: float) -> float:
"""
Higher score = better match.
Lower distance = higher score.
Surge slightly boosts score to balance rider demand.
"""
return max(0.01, (1 / (distance + 0.01))) * surge_multiplier
# ------------------------------------------------------------
# Candidate Ranking
# ------------------------------------------------------------
def rank_drivers(
self,
drivers: list[DriverLocationEvent],
trip: TripRequestEvent,
surge_multiplier: float,
) -> list[DriverLocationEvent]:
"""
Sort drivers by score (descending).
"""
scored = []
for d in drivers:
dist = self._distance(d.lat, d.lon, trip.pickup_lat, trip.pickup_lon)
score = self._score_driver(dist, surge_multiplier)
scored.append((score, d))
ranked = sorted(scored, key=lambda x: x[0], reverse=True)
top_ranked = [d for _, d in ranked[: self.max_candidates]]
self.logger.info(f"Ranked {len(top_ranked)} drivers for trip {trip.rider_id}")
return top_ranked
# ------------------------------------------------------------
# Match Selection
# ------------------------------------------------------------
def select_best_match(
self,
drivers: list[DriverLocationEvent],
trip: TripRequestEvent,
surge_multiplier: float,
) -> MatchResultEvent | None:
"""
Selects the highest-ranked driver and returns a MatchResultEvent.
"""
if not drivers:
self.logger.warning("No available drivers for matching.")
return None
ranked = self.rank_drivers(drivers, trip, surge_multiplier)
best = ranked[0]
distance = self._distance(best.lat, best.lon, trip.pickup_lat, trip.pickup_lon)
eta = self._estimate_eta(distance)
match_event = MatchResultEvent(
trip_id=f"trip_{trip.rider_id}_{now_timestamp().timestamp()}",
driver_id=best.driver_id,
rider_id=trip.rider_id,
eta_minutes=eta,
surge_multiplier=surge_multiplier,
timestamp=now_timestamp(),
)
self.logger.info(
f"Selected driver {best.driver_id} for rider {trip.rider_id} "
f"(ETA: {eta} mins, Surge: {surge_multiplier})"
)
return match_event