shuv25 commited on
Commit
3adc503
·
verified ·
1 Parent(s): 33ce87f

Update tools.py

Browse files
Files changed (1) hide show
  1. tools.py +266 -218
tools.py CHANGED
@@ -1,18 +1,19 @@
1
  from langchain_core.tools import StructuredTool
2
- from schema import RouteInput,CostInput,TrafficInput
3
- from typing import Tuple,Dict
4
  from datetime import datetime
5
  import time
6
  import folium
7
  import os
8
 
9
- from api import geocode_address_nominatim,get_route_osrm,get_alternative_routes_osrm, get_detailed_route_with_instructions,get_realtime_traffic
 
 
 
 
10
 
11
  def estimate_traffic_from_time() -> float:
12
- """
13
- Estimate traffic based on time of day
14
- Uses typical traffic patterns
15
- """
16
  current_hour = datetime.now().hour
17
 
18
  if 7 <= current_hour <= 10:
@@ -27,130 +28,51 @@ def estimate_traffic_from_time() -> float:
27
  return 1.0
28
 
29
  REAL_VEHICLES = [
30
- {
31
- "id": "BIKE-001",
32
- "type": "Motorcycle",
33
- "capacity_kg": 30,
34
- "fuel_cost_per_km": 2.5,
35
- "speed_kmph": 40,
36
- "available": True
37
- },
38
- {
39
- "id": "VAN-001",
40
- "type": "Small Van",
41
- "capacity_kg": 500,
42
- "fuel_cost_per_km": 8.0,
43
- "speed_kmph": 50,
44
- "available": True
45
- },
46
- {
47
- "id": "TRUCK-001",
48
- "type": "Light Truck",
49
- "capacity_kg": 2000,
50
- "fuel_cost_per_km": 15.0,
51
- "speed_kmph": 45,
52
- "available": True
53
- },
54
- {
55
- "id": "TRUCK-002",
56
- "type": "Heavy Truck",
57
- "capacity_kg": 5000,
58
- "fuel_cost_per_km": 25.0,
59
- "speed_kmph": 40,
60
- "available": True
61
- },
62
  ]
63
 
64
  def calculate_traffic(origin_coords: Tuple[float, float], dest_coords: Tuple[float, float]):
65
- """Analyze the traffic using the realtime data"""
66
  traffic_data = get_realtime_traffic(origin_coords, dest_coords)
67
-
68
  if not traffic_data:
69
- print("sing fallback: estimate_traffic_from_time()")
70
  return estimate_traffic_from_time()
71
-
72
  try:
73
  base_time = traffic_data["noTrafficTravelTimeInSeconds"]
74
  current_time = traffic_data["travelTimeInSeconds"]
75
- delay = traffic_data["trafficDelayInSeconds"]
76
-
77
  if base_time == 0:
78
  return 1.0
79
-
80
  traffic_factor = current_time / base_time
81
-
82
  traffic_factor = max(0.8, min(traffic_factor, 2.0))
83
  return round(traffic_factor, 2)
84
-
85
  except KeyError:
86
- print("Missing fields in traffic data, fallback to estimate_traffic_from_time()")
87
  return estimate_traffic_from_time()
88
 
89
-
90
- def format_route_instructions(route_data: Dict) -> str:
91
- """
92
- Format route instructions in readable format
93
- """
94
- if not route_data:
95
- return "No route instructions available"
96
-
97
- output = f"\nRoute {route_data['route_number']}:\n"
98
- output += f"Total Distance: {route_data['distance_km']:.1f} km\n"
99
- output += f"Total Duration: {route_data['duration_min']:.0f} minutes\n"
100
- output += "\nTurn-by-Turn Directions:\n"
101
- output += "-" * 50 + "\n"
102
-
103
- cumulative_distance = 0
104
- for idx, step in enumerate(route_data['instructions'], 1):
105
- cumulative_distance += step['distance_km']
106
-
107
- instruction_type = step['instruction']
108
- direction = step['direction']
109
-
110
- if instruction_type == "depart":
111
- action = "Start"
112
- elif instruction_type == "arrive":
113
- action = "Arrive at destination"
114
- elif instruction_type == "turn":
115
- action = f"Turn {direction}" if direction else "Turn"
116
- elif instruction_type == "new name":
117
- action = "Continue onto"
118
- elif instruction_type == "roundabout":
119
- action = "Take roundabout"
120
- elif instruction_type == "merge":
121
- action = f"Merge {direction}" if direction else "Merge"
122
- else:
123
- action = instruction_type.replace("_", " ").capitalize()
124
-
125
- road_name = step['road_name']
126
- distance = step['distance_km']
127
-
128
- if distance > 0.1:
129
- output += f"{idx}. {action}"
130
- if road_name and road_name != "Unnamed road":
131
- output += f" on {road_name}"
132
- output += f" for {distance:.1f} km"
133
- output += f" (Total: {cumulative_distance:.1f} km)\n"
134
-
135
- return output
136
-
137
-
138
  def find_optimal_route(origin_coords: Tuple[float, float],
139
  dest_coords: Tuple[float, float]) -> Dict:
140
- """
141
- Compare all available routes and find optimal with detailed instructions
142
- """
143
-
 
144
  detailed_routes = get_detailed_route_with_instructions(origin_coords, dest_coords)
145
 
146
  if not detailed_routes:
147
  return None
148
 
149
- traffic_factor = calculate_traffic(origin_coords,dest_coords)
 
 
150
 
151
  scored_routes = []
152
  for route_data in detailed_routes:
153
- adjusted_time = route_data['duration_min'] * traffic_factor
154
 
155
  fuel_cost_per_km = 8.0
156
  estimated_cost = route_data['distance_km'] * fuel_cost_per_km
@@ -164,19 +86,21 @@ def find_optimal_route(origin_coords: Tuple[float, float],
164
  'adjusted_duration': adjusted_time,
165
  'estimated_cost': estimated_cost,
166
  'score': score,
167
- 'instructions': route_data['instructions']
 
168
  })
169
 
170
  scored_routes.sort(key=lambda x: x['score'])
171
 
172
  return {
173
  'optimal': scored_routes[0],
174
- 'all_routes': scored_routes
 
 
175
  }
176
 
177
- def create_route_map_html(origin_coords, dest_coords, route_data, filename="static/route_map.html"):
178
- """Generate interactive map with YOUR exact turn-by-turn directions"""
179
-
180
  os.makedirs("static", exist_ok=True)
181
 
182
  m = folium.Map(
@@ -184,68 +108,113 @@ def create_route_map_html(origin_coords, dest_coords, route_data, filename="stat
184
  zoom_start=8
185
  )
186
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  folium.Marker(
188
  origin_coords,
189
  popup="<b>START</b>",
190
- icon=folium.Icon(color='green', icon='play')
191
  ).add_to(m)
192
 
193
  folium.Marker(
194
  dest_coords,
195
  popup="<b>DESTINATION</b>",
196
- icon=folium.Icon(color='red', icon='stop')
197
  ).add_to(m)
198
 
 
199
  if 'geometry' in route_data and route_data['geometry']:
200
  coords = [[c[1], c[0]] for c in route_data['geometry']['coordinates']]
201
- folium.PolyLine(coords, color='blue', weight=5, opacity=0.7).add_to(m)
202
-
203
- directions_html = "<div style='position:fixed;top:10px;right:10px;width:300px;background:white;padding:10px;border:2px solid blue;max-height:80vh;overflow-y:auto;z-index:1000'>"
204
- directions_html += f"<h3>Route Directions</h3>"
205
- directions_html += f"<b>Distance:</b> {route_data.get('distance_km', 0):.1f} km<br>"
206
- directions_html += f"<b>Duration:</b> {route_data.get('duration_min', 0):.0f} min<br><hr>"
207
-
208
- for idx, step in enumerate(route_data.get('instructions', []), 1):
209
- if step['distance_km'] > 0.1:
210
- directions_html += f"<b>{idx}.</b> {step['instruction'].title()}"
211
- if step['road_name'] != "Unnamed road":
212
- directions_html += f" on <b>{step['road_name']}</b>"
213
- directions_html += f"<br><small>{step['distance_km']:.1f} km</small><hr>"
214
-
215
- directions_html += "</div>"
216
-
217
- m.get_root().html.add_child(folium.Element(directions_html))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
  m.save(filename)
220
  return filename
221
 
 
222
  def real_route_planner(origin: str, destination: str) -> str:
223
- """
224
- Plan route using FREE OSRM with real OpenStreetMap data
225
- """
226
  print(f"\n{'='*60}")
227
- print(f"Planning route: {origin} {destination}")
228
  print(f"{'='*60}")
229
 
230
  origin_coords = geocode_address_nominatim(origin)
231
  if not origin_coords:
232
- return f"Could not find location: {origin}. Try being more specific (e.g., 'Mumbai, Maharashtra, India')"
233
- print(f"✓ Origin found: {origin_coords}")
234
 
235
  dest_coords = geocode_address_nominatim(destination)
236
  if not dest_coords:
237
- return f"Could not find location: {destination}. Try being more specific."
238
- print(f"✓ Destination found: {dest_coords}")
239
 
240
  route = get_route_osrm(origin_coords, dest_coords)
241
  if not route:
242
- return f"No route found between {origin} and {destination}"
243
- print(f"✓ Route found: {route['distance_km']:.1f} km, {route['duration_min']:.0f} min")
244
 
245
  optimization = find_optimal_route(origin_coords, dest_coords)
246
  if not optimization:
247
  return f"Could not optimize route"
248
 
 
249
  safe_origin = origin.replace(' ', '_').replace(',', '')[:30]
250
  safe_dest = destination.replace(' ', '_').replace(',', '')[:30]
251
  map_filename = f"route_{safe_origin}_to_{safe_dest}.html"
@@ -253,121 +222,182 @@ def real_route_planner(origin: str, destination: str) -> str:
253
 
254
  optimal_route_data = {
255
  'distance_km': optimization['optimal']['distance_km'],
256
- 'duration_min': optimization['optimal']['duration_min'],
257
  'instructions': optimization['optimal']['instructions'],
258
  'geometry': route.get('geometry')
259
  }
260
 
 
 
261
  try:
262
- map_file = create_route_map_html(origin_coords, dest_coords, optimal_route_data, map_path)
263
- print("Map saved to", map_file)
264
  map_url = f"/view-map/{map_filename}"
265
  except Exception as e:
266
  print("Map creation failed:", e)
267
  map_url = None
268
-
269
-
270
- result = f"Route Analysis (OpenStreetMap Data):\n"
271
- result += f"From: {origin}\n"
272
- result += f"To: {destination}\n\n"
 
 
273
 
274
  if map_url:
275
- result += f"INTERACTIVE MAP: {map_url}\n"
276
- result += f"(Open this link in your browser to view the map)\n\n"
277
 
278
- result += f"PRIMARY ROUTE:\n"
279
- result += f"Distance: {route['distance_km']:.1f} km\n"
280
- result += f"Duration: {route['duration_min']:.0f} minutes\n"
281
-
282
- if optimization and len(optimization['all_routes']) >= 1:
283
- result += "\n\nROUTE COMPARISON:\n"
284
- for i, r in enumerate(optimization['all_routes']):
285
- marker = " [OPTIMAL]" if i == 0 else ""
286
- result += (f"\nRoute {r['route_num']}:{marker}\n"
287
- f" Distance: {r['distance_km']:.1f} km\n"
288
- f" Base Time: {r['duration_min']:.0f} min\n"
289
- f" With Traffic: {r['adjusted_duration']:.0f} min\n"
290
- f" Est. Cost: Rs {r['estimated_cost']:.2f}\n"
291
- f" Score: {r['score']:.1f}")
292
-
293
- if optimization['optimal']['route_num'] != 1:
294
- result += f"\n\nRECOMMENDATION: Route {optimization['optimal']['route_num']} is optimal based on time and cost factors"
295
-
296
- optimal_route = optimization['optimal']
297
- result += "\n\n" + "=" * 50
298
- result += format_route_instructions({
299
- 'route_number': optimal_route['route_num'],
300
- 'distance_km': optimal_route['distance_km'],
301
- 'duration_min': optimal_route['duration_min'],
302
- 'instructions': optimal_route['instructions']
303
- })
 
 
 
 
 
 
 
304
 
305
- return result
306
 
307
- def real_cost_optimizer(origin: str, destination: str,distance_km: float, weight_kg: float, duration_min: float) -> str:
308
- """
309
- Calculate real-world delivery costs
310
- """
311
- print(f"\nCalculating costs: {distance_km:.1f}km, {weight_kg}kg, {duration_min/60:.0f}hr")
312
 
 
 
313
  origin_coords = geocode_address_nominatim(origin)
314
  dest_coords = geocode_address_nominatim(destination)
315
 
 
 
 
 
316
  suitable = [v for v in REAL_VEHICLES if v["capacity_kg"] >= weight_kg and v["available"]]
317
 
318
  if not suitable:
319
- return f"No vehicle available for {weight_kg}kg (max capacity: {max(v['capacity_kg'] for v in REAL_VEHICLES)}kg)"
 
 
 
 
320
 
321
  options = []
322
  for vehicle in suitable:
323
-
324
  fuel_cost = distance_km * vehicle["fuel_cost_per_km"]
325
-
326
  driver_cost = (duration_min / 60) * 200
327
-
328
  base_fee = 150
329
 
330
- traffic_multiplier = calculate_traffic(origin_coords,dest_coords)
331
-
332
  capacity_usage = weight_kg / vehicle["capacity_kg"]
333
  capacity_multiplier = 1.15 if capacity_usage > 0.8 else 1.0
334
 
335
- total_cost = (base_fee + fuel_cost + driver_cost) * traffic_multiplier * capacity_multiplier
336
 
337
  options.append({
338
  "vehicle": vehicle,
339
  "total_cost": total_cost,
340
  "fuel_cost": fuel_cost,
341
  "driver_cost": driver_cost,
342
- "traffic_factor": traffic_multiplier
 
343
  })
344
 
345
  options.sort(key=lambda x: x["total_cost"])
346
  best = options[0]
347
 
348
- result = (
349
- f"Cost Estimate (Real-world pricing):\n"
350
- f"Recommended: {best['vehicle']['type']} ({best['vehicle']['id']})\n"
351
- f"Total Cost: ₹{best['total_cost']:.2f}\n"
352
- f"• Base Fee: 150\n"
353
- f"• Fuel: {best['fuel_cost']:.2f}\n"
354
- f"• Driver: {best['driver_cost']:.2f}\n"
355
- f"• Traffic Factor: {best['traffic_factor']:.2f}x\n"
356
- f"Capacity: {best['vehicle']['capacity_kg']}kg (using {(weight_kg/best['vehicle']['capacity_kg'])*100:.1f}%)"
357
- )
358
-
359
- if len(options) > 1:
360
- alt = options[1]
361
- result += f"\n Alternative: {alt['vehicle']['type']} at ₹{alt['total_cost']:.2f}"
362
 
363
  return result
364
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
 
366
  def real_traffic_analyzer(origin: str, destination: str) -> str:
367
- """
368
- Analyze traffic conditions using real data and patterns
369
- """
370
- print(f"\nAnalyzing traffic: {origin} → {destination}")
371
 
372
  origin_coords = geocode_address_nominatim(origin)
373
  dest_coords = geocode_address_nominatim(destination)
@@ -380,7 +410,7 @@ def real_traffic_analyzer(origin: str, destination: str) -> str:
380
  return "No route found for traffic analysis"
381
 
382
  current_hour = datetime.now().hour
383
- traffic_factor = calculate_traffic(origin_coords,dest_coords)
384
 
385
  if traffic_factor >= 1.5:
386
  traffic_level = "Heavy"
@@ -390,43 +420,61 @@ def real_traffic_analyzer(origin: str, destination: str) -> str:
390
  advice = "Expect minor delays, monitor conditions"
391
  else:
392
  traffic_level = "Light"
393
- advice = "Good time to depart!"
394
-
395
 
396
  base_duration = route["duration_min"]
397
  adjusted_duration = base_duration * traffic_factor
398
  total_delay = adjusted_duration - base_duration
399
 
400
  result = (
401
- f"Traffic Analysis (Real-time patterns):\n"
402
  f"Current Traffic: {traffic_level}\n"
403
  f"Time: {datetime.now().strftime('%H:%M')} (Factor: {traffic_factor:.2f}x)\n"
404
  f"Base ETA: {base_duration:.0f} min\n"
405
- f"Adjusted ETA: {adjusted_duration:.0f} min (+{total_delay:.0f} min delay)\n"
 
 
406
  )
407
-
408
- result += f"\nAdvice: {advice}"
409
-
410
- return result
 
411
 
 
 
 
 
 
 
 
 
 
412
 
413
  route_tool = StructuredTool.from_function(
414
  func=real_route_planner,
415
  name="real_route_planner",
416
- description="Find optimal route using FREE OpenStreetMap data. Provide city names or addresses and the detailed routes.",
417
  args_schema=RouteInput
418
  )
419
 
420
  cost_tool = StructuredTool.from_function(
421
  func=real_cost_optimizer,
422
  name="real_cost_optimizer",
423
- description="Calculate delivery costs with real-world pricing. Pass distance_km, weight_kg, duration_min as numbers.",
424
  args_schema=CostInput
425
  )
426
 
427
  traffic_tool = StructuredTool.from_function(
428
  func=real_traffic_analyzer,
429
  name="real_traffic_analyzer",
430
- description="Analyze traffic conditions using real patterns.",
431
  args_schema=TrafficInput
 
 
 
 
 
 
 
432
  )
 
1
  from langchain_core.tools import StructuredTool
2
+ from schema import RouteInput, CostInput, TrafficInput, WeatherInput
3
+ from typing import Tuple, Dict
4
  from datetime import datetime
5
  import time
6
  import folium
7
  import os
8
 
9
+ from api import (
10
+ geocode_address_nominatim, get_route_osrm, get_alternative_routes_osrm,
11
+ get_detailed_route_with_instructions, get_realtime_traffic, get_weather_along_route
12
+ )
13
+
14
 
15
  def estimate_traffic_from_time() -> float:
16
+ """Estimate traffic based on time of day"""
 
 
 
17
  current_hour = datetime.now().hour
18
 
19
  if 7 <= current_hour <= 10:
 
28
  return 1.0
29
 
30
  REAL_VEHICLES = [
31
+ {"id": "BIKE-001", "type": "Motorcycle", "capacity_kg": 30, "fuel_cost_per_km": 2.5, "speed_kmph": 40, "available": True},
32
+ {"id": "VAN-001", "type": "Small Van", "capacity_kg": 500, "fuel_cost_per_km": 8.0, "speed_kmph": 50, "available": True},
33
+ {"id": "TRUCK-001", "type": "Light Truck", "capacity_kg": 2000, "fuel_cost_per_km": 15.0, "speed_kmph": 45, "available": True},
34
+ {"id": "TRUCK-002", "type": "Heavy Truck", "capacity_kg": 5000, "fuel_cost_per_km": 25.0, "speed_kmph": 40, "available": True},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  ]
36
 
37
  def calculate_traffic(origin_coords: Tuple[float, float], dest_coords: Tuple[float, float]):
38
+ """Analyze traffic using realtime data"""
39
  traffic_data = get_realtime_traffic(origin_coords, dest_coords)
40
+
41
  if not traffic_data:
 
42
  return estimate_traffic_from_time()
43
+
44
  try:
45
  base_time = traffic_data["noTrafficTravelTimeInSeconds"]
46
  current_time = traffic_data["travelTimeInSeconds"]
47
+
 
48
  if base_time == 0:
49
  return 1.0
50
+
51
  traffic_factor = current_time / base_time
 
52
  traffic_factor = max(0.8, min(traffic_factor, 2.0))
53
  return round(traffic_factor, 2)
 
54
  except KeyError:
 
55
  return estimate_traffic_from_time()
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  def find_optimal_route(origin_coords: Tuple[float, float],
58
  dest_coords: Tuple[float, float]) -> Dict:
59
+ """Compare all available routes and find optimal with detailed instructions"""
60
+ print(f"\n{'='*60}")
61
+ print("Checking for better route")
62
+ print(f"\n{'='*60}")
63
+
64
  detailed_routes = get_detailed_route_with_instructions(origin_coords, dest_coords)
65
 
66
  if not detailed_routes:
67
  return None
68
 
69
+ traffic_factor = calculate_traffic(origin_coords, dest_coords)
70
+ weather_data = get_weather_along_route(origin_coords, dest_coords)
71
+ weather_factor = weather_data.get("weather_factor", 1.0)
72
 
73
  scored_routes = []
74
  for route_data in detailed_routes:
75
+ adjusted_time = route_data['duration_min'] * traffic_factor * weather_factor
76
 
77
  fuel_cost_per_km = 8.0
78
  estimated_cost = route_data['distance_km'] * fuel_cost_per_km
 
86
  'adjusted_duration': adjusted_time,
87
  'estimated_cost': estimated_cost,
88
  'score': score,
89
+ 'instructions': route_data['instructions'],
90
+ 'weather_warnings': weather_data.get('warnings', [])
91
  })
92
 
93
  scored_routes.sort(key=lambda x: x['score'])
94
 
95
  return {
96
  'optimal': scored_routes[0],
97
+ 'all_routes': scored_routes,
98
+ 'weather_data': weather_data,
99
+ 'traffic_factor': traffic_factor
100
  }
101
 
102
+ def create_enhanced_map(origin_coords, dest_coords, route_data, weather_data, filename="static/route_map.html", map_type="route"):
103
+ """Generate interactive map with route visualization (supports route/traffic/weather modes)"""
 
104
  os.makedirs("static", exist_ok=True)
105
 
106
  m = folium.Map(
 
108
  zoom_start=8
109
  )
110
 
111
+ if map_type == "traffic":
112
+ origin_icon = folium.Icon(color='orange', icon='exclamation-triangle', prefix='fa')
113
+ dest_icon = folium.Icon(color='red', icon='flag-checkered', prefix='fa')
114
+ route_color = '#ff6b6b'
115
+ elif map_type == "weather":
116
+ origin_icon = folium.Icon(color='blue', icon='cloud', prefix='fa')
117
+ dest_icon = folium.Icon(color='lightblue', icon='cloud-sun', prefix='fa')
118
+ route_color = '#4dabf7'
119
+ else:
120
+ origin_icon = folium.Icon(color='green', icon='play')
121
+ dest_icon = folium.Icon(color='red', icon='stop')
122
+ route_color = '#4C763B'
123
+
124
  folium.Marker(
125
  origin_coords,
126
  popup="<b>START</b>",
127
+ icon=origin_icon
128
  ).add_to(m)
129
 
130
  folium.Marker(
131
  dest_coords,
132
  popup="<b>DESTINATION</b>",
133
+ icon=dest_icon
134
  ).add_to(m)
135
 
136
+
137
  if 'geometry' in route_data and route_data['geometry']:
138
  coords = [[c[1], c[0]] for c in route_data['geometry']['coordinates']]
139
+ folium.PolyLine(
140
+ coords,
141
+ color=route_color,
142
+ weight=5,
143
+ opacity=0.7,
144
+ tooltip=f"{map_type.title()} Route"
145
+ ).add_to(m)
146
+
147
+ if map_type == "weather" and weather_data:
148
+ if weather_data.get('origin'):
149
+ origin_w = weather_data['origin']
150
+ folium.CircleMarker(
151
+ origin_coords,
152
+ radius=15,
153
+ popup=f"<b>Origin Weather</b><br>{origin_w.get('temperature')}°C<br>{origin_w.get('description', 'N/A')}",
154
+ color='blue',
155
+ fill=True,
156
+ fillColor='lightblue',
157
+ fillOpacity=0.3
158
+ ).add_to(m)
159
+
160
+ if weather_data.get('destination'):
161
+ dest_w = weather_data['destination']
162
+ folium.CircleMarker(
163
+ dest_coords,
164
+ radius=15,
165
+ popup=f"<b>Destination Weather</b><br>{dest_w.get('temperature')}°C<br>{dest_w.get('description', 'N/A')}",
166
+ color='blue',
167
+ fill=True,
168
+ fillColor='lightblue',
169
+ fillOpacity=0.3
170
+ ).add_to(m)
171
+
172
+ if weather_data.get('warnings'):
173
+ mid_lat = (origin_coords[0] + dest_coords[0]) / 2
174
+ mid_lon = (origin_coords[1] + dest_coords[1]) / 2
175
+ folium.Marker(
176
+ [mid_lat, mid_lon],
177
+ popup=f"<b>⚠️ Weather Alert</b><br>{'<br>'.join(weather_data['warnings'])}",
178
+ icon=folium.Icon(color='orange', icon='exclamation-triangle', prefix='fa')
179
+ ).add_to(m)
180
+
181
+ if map_type == "traffic":
182
+ mid_lat = (origin_coords[0] + dest_coords[0]) / 2
183
+ mid_lon = (origin_coords[1] + dest_coords[1]) / 2
184
+ folium.Marker(
185
+ [mid_lat, mid_lon],
186
+ popup=f"<b>🚦 Traffic Analysis</b><br>Check route details for conditions",
187
+ icon=folium.Icon(color='orange', icon='car', prefix='fa')
188
+ ).add_to(m)
189
 
190
  m.save(filename)
191
  return filename
192
 
193
+
194
  def real_route_planner(origin: str, destination: str) -> str:
195
+ """Plan route with weather and memory integration"""
196
+
 
197
  print(f"\n{'='*60}")
198
+ print(f"Planning route: {origin} -> {destination}")
199
  print(f"{'='*60}")
200
 
201
  origin_coords = geocode_address_nominatim(origin)
202
  if not origin_coords:
203
+ return f"Could not find location: {origin}"
 
204
 
205
  dest_coords = geocode_address_nominatim(destination)
206
  if not dest_coords:
207
+ return f"Could not find location: {destination}"
 
208
 
209
  route = get_route_osrm(origin_coords, dest_coords)
210
  if not route:
211
+ return f"No route found"
 
212
 
213
  optimization = find_optimal_route(origin_coords, dest_coords)
214
  if not optimization:
215
  return f"Could not optimize route"
216
 
217
+ # Create map filename
218
  safe_origin = origin.replace(' ', '_').replace(',', '')[:30]
219
  safe_dest = destination.replace(' ', '_').replace(',', '')[:30]
220
  map_filename = f"route_{safe_origin}_to_{safe_dest}.html"
 
222
 
223
  optimal_route_data = {
224
  'distance_km': optimization['optimal']['distance_km'],
225
+ 'duration_min': optimization['optimal']['adjusted_duration'],
226
  'instructions': optimization['optimal']['instructions'],
227
  'geometry': route.get('geometry')
228
  }
229
 
230
+ weather_data = optimization.get('weather_data', {})
231
+
232
  try:
233
+ create_enhanced_map(origin_coords, dest_coords, optimal_route_data, weather_data, map_path,map_type="route")
 
234
  map_url = f"/view-map/{map_filename}"
235
  except Exception as e:
236
  print("Map creation failed:", e)
237
  map_url = None
238
+
239
+ result = f"ROUTE SUMMARY\n"
240
+ result += f"Origin: {origin}\n"
241
+ result += f"Destination: {destination}\n"
242
+ result += f"Distance: {route['distance_km']:.1f} km\n"
243
+ result += f"Base Duration: {route['duration_min']:.0f} min\n"
244
+ result += f"Adjusted ETA: {optimization['optimal']['adjusted_duration']:.0f} min\n\n"
245
 
246
  if map_url:
247
+ result += f"INTERACTIVE MAP: {map_url}\n\n"
 
248
 
249
+ result += f"TRAFFIC ANALYSIS\n"
250
+ traffic_factor = optimization.get('traffic_factor', 1.0)
251
+ if traffic_factor >= 1.5:
252
+ traffic_level = "Heavy"
253
+ advice = "Consider delaying by 1-2 hours or use alternative route"
254
+ elif traffic_factor >= 1.2:
255
+ traffic_level = "Moderate"
256
+ advice = "Expect minor delays, monitor conditions"
257
+ else:
258
+ traffic_level = "Light"
259
+ advice = "Good time to depart"
260
+
261
+ result += f"Current Traffic: {traffic_level}\n"
262
+ result += f"Traffic Factor: {traffic_factor:.2f}x\n"
263
+
264
+ base_duration = route['duration_min']
265
+ adjusted = optimization['optimal']['adjusted_duration']
266
+ delay = adjusted - base_duration
267
+ result += f"Expected Delay: {delay:.0f} min\n"
268
+ result += f"Advice: {advice}\n\n"
269
+
270
+ result += f"WEATHER CONDITIONS\n"
271
+ origin_w = weather_data.get('origin', {})
272
+ dest_w = weather_data.get('destination', {})
273
+ if origin_w:
274
+ result += f"Origin: {origin_w.get('temperature', 'N/A')}°C, {origin_w.get('condition', 'N/A')}\n"
275
+ if dest_w:
276
+ result += f"Destination: {dest_w.get('temperature', 'N/A')}°C, {dest_w.get('condition', 'N/A')}\n"
277
+
278
+ if optimization['optimal'].get('weather_warnings'):
279
+ result += f"\nWeather Alerts:\n"
280
+ for warning in optimization['optimal']['weather_warnings']:
281
+ result += f" • {warning}\n"
282
 
283
+ return result
284
 
 
 
 
 
 
285
 
286
+ def real_cost_optimizer(origin: str, destination: str, distance_km: float, weight_kg: float, duration_min: float) -> str:
287
+ """Calculate costs with weather impact"""
288
  origin_coords = geocode_address_nominatim(origin)
289
  dest_coords = geocode_address_nominatim(destination)
290
 
291
+ print(f"\n{'='*60}")
292
+ print("Inside the cost optimizer")
293
+ print(f"\n{'='*60}")
294
+
295
  suitable = [v for v in REAL_VEHICLES if v["capacity_kg"] >= weight_kg and v["available"]]
296
 
297
  if not suitable:
298
+ return f"No vehicle available for {weight_kg}kg"
299
+
300
+ traffic_multiplier = calculate_traffic(origin_coords, dest_coords)
301
+ weather_data = get_weather_along_route(origin_coords, dest_coords)
302
+ weather_multiplier = weather_data.get("weather_factor", 1.0)
303
 
304
  options = []
305
  for vehicle in suitable:
 
306
  fuel_cost = distance_km * vehicle["fuel_cost_per_km"]
 
307
  driver_cost = (duration_min / 60) * 200
 
308
  base_fee = 150
309
 
 
 
310
  capacity_usage = weight_kg / vehicle["capacity_kg"]
311
  capacity_multiplier = 1.15 if capacity_usage > 0.8 else 1.0
312
 
313
+ total_cost = (base_fee + fuel_cost + driver_cost) * traffic_multiplier * weather_multiplier * capacity_multiplier
314
 
315
  options.append({
316
  "vehicle": vehicle,
317
  "total_cost": total_cost,
318
  "fuel_cost": fuel_cost,
319
  "driver_cost": driver_cost,
320
+ "traffic_factor": traffic_multiplier,
321
+ "weather_factor": weather_multiplier
322
  })
323
 
324
  options.sort(key=lambda x: x["total_cost"])
325
  best = options[0]
326
 
327
+ result = f"\nCOST ESTIMATE\n"
328
+ result += f"Recommended Vehicle: {best['vehicle']['type']} ({best['vehicle']['id']})\n"
329
+ result += f"Total Cost: Rs {best['total_cost']:.2f}\n\n"
330
+ result += f"Cost Breakdown:\n"
331
+ result += f" • Base Fee: Rs 150\n"
332
+ result += f" • Fuel Cost: Rs {best['fuel_cost']:.2f}\n"
333
+ result += f" • Driver Cost: Rs {best['driver_cost']:.2f}\n"
334
+ result += f" • Traffic Multiplier: {best['traffic_factor']:.2f}x\n"
335
+ result += f" Weather Multiplier: {best['weather_factor']:.2f}x\n"
 
 
 
 
 
336
 
337
  return result
338
 
339
+ def real_weather_analyzer(origin: str, destination: str) -> str:
340
+ """Analyze weather conditions along route"""
341
+
342
+ print(f"\n{'='*60}")
343
+ print("Inside the weather analyzer")
344
+ print(f"\n{'='*60}")
345
+
346
+ origin_coords = geocode_address_nominatim(origin)
347
+ dest_coords = geocode_address_nominatim(destination)
348
+
349
+ if not origin_coords or not dest_coords:
350
+ return "Could not analyze weather - invalid locations"
351
+
352
+ weather_data = get_weather_along_route(origin_coords, dest_coords)
353
+
354
+ result = f"WEATHER ANALYSIS\n\n"
355
+
356
+ if weather_data.get('origin'):
357
+ origin_weather = weather_data['origin']
358
+ result += f"Origin Weather:\n"
359
+ result += f" Temperature: {origin_weather.get('temperature', 'N/A')}°C (feels like {origin_weather.get('feels_like', 'N/A')}°C)\n"
360
+ result += f" Condition: {origin_weather.get('description', 'N/A').title()}\n"
361
+ result += f" Humidity: {origin_weather.get('humidity', 'N/A')}%\n"
362
+ result += f" Wind Speed: {origin_weather.get('wind_speed', 'N/A')} m/s\n"
363
+ if origin_weather.get('rain', 0) > 0:
364
+ result += f" Rain: {origin_weather['rain']:.1f} mm/h\n"
365
+ result += "\n"
366
+
367
+ if weather_data.get('destination'):
368
+ dest_weather = weather_data['destination']
369
+ result += f"Destination Weather:\n"
370
+ result += f" Temperature: {dest_weather.get('temperature', 'N/A')}°C\n"
371
+ result += f" Condition: {dest_weather.get('description', 'N/A').title()}\n\n"
372
+
373
+ if weather_data.get('warnings'):
374
+ result += f"Weather Alerts:\n"
375
+ for warning in weather_data['warnings']:
376
+ result += f" • {warning}\n"
377
+ result += f"\nWeather Impact Factor: {weather_data.get('weather_factor', 1.0):.2f}x\n"
378
+ result += f"Recommendation: Exercise caution. Delivery time may increase by {(weather_data.get('weather_factor', 1.0) - 1) * 100:.0f}%\n"
379
+ else:
380
+ result += f"Good weather conditions for delivery.\n"
381
+
382
+ safe_origin = origin.replace(' ', '_').replace(',', '')[:30]
383
+ safe_dest = destination.replace(' ', '_').replace(',', '')[:30]
384
+ map_filename = f"weather_{safe_origin}_to_{safe_dest}.html"
385
+ map_path = f"static/{map_filename}"
386
+
387
+ route = get_route_osrm(origin_coords, dest_coords)
388
+ if route:
389
+ route_data = {
390
+ 'geometry': route.get('geometry')
391
+ }
392
+ create_enhanced_map(origin_coords, dest_coords, route_data, weather_data, map_path, map_type="weather")
393
+
394
+ result += f"\nINTERACTIVE MAP: /view-map/{map_filename}\n"
395
+
396
+ return result
397
 
398
  def real_traffic_analyzer(origin: str, destination: str) -> str:
399
+ """Analyze traffic conditions using real data and patterns"""
400
+ print(f"\nAnalyzing traffic: {origin} -> {destination}")
 
 
401
 
402
  origin_coords = geocode_address_nominatim(origin)
403
  dest_coords = geocode_address_nominatim(destination)
 
410
  return "No route found for traffic analysis"
411
 
412
  current_hour = datetime.now().hour
413
+ traffic_factor = calculate_traffic(origin_coords, dest_coords)
414
 
415
  if traffic_factor >= 1.5:
416
  traffic_level = "Heavy"
 
420
  advice = "Expect minor delays, monitor conditions"
421
  else:
422
  traffic_level = "Light"
423
+ advice = "Good time to depart"
 
424
 
425
  base_duration = route["duration_min"]
426
  adjusted_duration = base_duration * traffic_factor
427
  total_delay = adjusted_duration - base_duration
428
 
429
  result = (
430
+ f"TRAFFIC ANALYSIS\n"
431
  f"Current Traffic: {traffic_level}\n"
432
  f"Time: {datetime.now().strftime('%H:%M')} (Factor: {traffic_factor:.2f}x)\n"
433
  f"Base ETA: {base_duration:.0f} min\n"
434
+ f"Adjusted ETA: {adjusted_duration:.0f} min\n"
435
+ f"Expected Delay: {total_delay:.0f} min\n\n"
436
+ f"Advice: {advice}\n"
437
  )
438
+
439
+ safe_origin = origin.replace(' ', '_').replace(',', '')[:30]
440
+ safe_dest = destination.replace(' ', '_').replace(',', '')[:30]
441
+ map_filename = f"traffic_{safe_origin}_to_{safe_dest}.html"
442
+ map_path = f"static/{map_filename}"
443
 
444
+ if route:
445
+ route_data = {
446
+ 'geometry': route.get('geometry')
447
+ }
448
+ create_enhanced_map(origin_coords, dest_coords, route_data, {}, map_path, map_type="traffic")
449
+
450
+ result += f"\nINTERACTIVE MAP: /view-map/{map_filename}\n"
451
+
452
+ return result
453
 
454
  route_tool = StructuredTool.from_function(
455
  func=real_route_planner,
456
  name="real_route_planner",
457
+ description="Plan route with weather and traffic integration. Returns distance and duration from API.",
458
  args_schema=RouteInput
459
  )
460
 
461
  cost_tool = StructuredTool.from_function(
462
  func=real_cost_optimizer,
463
  name="real_cost_optimizer",
464
+ description="Calculate delivery costs with weather and traffic factors. ONLY use when user provides weight.",
465
  args_schema=CostInput
466
  )
467
 
468
  traffic_tool = StructuredTool.from_function(
469
  func=real_traffic_analyzer,
470
  name="real_traffic_analyzer",
471
+ description="Analyze traffic conditions using real patterns",
472
  args_schema=TrafficInput
473
+ )
474
+
475
+ weather_tool = StructuredTool.from_function(
476
+ func=real_weather_analyzer,
477
+ name="real_weather_analyzer",
478
+ description="Analyze weather conditions along route",
479
+ args_schema=WeatherInput
480
  )