shuv25 commited on
Commit
be1ef0b
·
verified ·
1 Parent(s): a4f2c67

Upload 7 files

Browse files
Files changed (7) hide show
  1. api.py +154 -0
  2. app.py +243 -0
  3. deep_agent.py +39 -0
  4. requirements.txt +8 -0
  5. schema.py +16 -0
  6. sub_agents.py +64 -0
  7. tools.py +403 -0
api.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Tuple, Optional
2
+ import requests
3
+ import json
4
+ import time
5
+ def geocode_address_nominatim(address: str) -> Optional[Tuple[float, float]]:
6
+ """
7
+ Geocoding using Nominatim (OpenStreetMap)
8
+ """
9
+ url = "https://nominatim.openstreetmap.org/search"
10
+
11
+ params = {
12
+ "q": address,
13
+ "format": "json",
14
+ "limit": 1
15
+ }
16
+
17
+ headers = {
18
+ "User-Agent": "DeliveryOptimizationSystem/1.0"
19
+ }
20
+
21
+ try:
22
+ time.sleep(1)
23
+ response = requests.get(url, params=params, headers=headers, timeout=10)
24
+ response.raise_for_status()
25
+ data = response.json()
26
+
27
+ if data:
28
+ return (float(data[0]["lat"]), float(data[0]["lon"]))
29
+ return None
30
+ except Exception as e:
31
+ print(f"Geocoding error: {e}")
32
+ return None
33
+
34
+
35
+ def get_route_osrm(origin_coords: Tuple[float, float],
36
+ dest_coords: Tuple[float, float]) -> Optional[Dict]:
37
+ """
38
+ Routing using OSRM (OpenStreetMap Routing Machine
39
+ """
40
+ lat1, lon1 = origin_coords
41
+ lat2, lon2 = dest_coords
42
+
43
+ url = f"http://router.project-osrm.org/route/v1/driving/{lon1},{lat1};{lon2},{lat2}"
44
+
45
+ params = {
46
+ "overview": "full",
47
+ "geometries": "geojson",
48
+ "steps": "true",
49
+ "annotations": "true"
50
+ }
51
+
52
+ try:
53
+ response = requests.get(url, params=params, timeout=15)
54
+ response.raise_for_status()
55
+ data = response.json()
56
+
57
+ if data.get("code") == "Ok" and data.get("routes"):
58
+ route = data["routes"][0]
59
+ return {
60
+ "distance_km": route["distance"] / 1000,
61
+ "duration_min": route["duration"] / 60,
62
+ "geometry": route["geometry"],
63
+ "steps": route["legs"][0].get("steps", [])
64
+ }
65
+ return None
66
+ except Exception as e:
67
+ print(f"Routing error: {e}")
68
+ return None
69
+
70
+
71
+ def get_alternative_routes_osrm(origin_coords: Tuple[float, float],
72
+ dest_coords: Tuple[float, float]) -> list:
73
+ """
74
+ Get multiple route options
75
+ """
76
+ lat1, lon1 = origin_coords
77
+ lat2, lon2 = dest_coords
78
+
79
+ url = f"http://router.project-osrm.org/route/v1/driving/{lon1},{lat1};{lon2},{lat2}"
80
+
81
+ params = {
82
+ "alternatives": "true",
83
+ "steps": "true",
84
+ "overview": "full"
85
+ }
86
+
87
+ try:
88
+ response = requests.get(url, params=params, timeout=15)
89
+ data = response.json()
90
+
91
+ if data.get("code") == "Ok":
92
+ routes = []
93
+ for route in data.get("routes", []):
94
+ routes.append({
95
+ "distance_km": route["distance"] / 1000,
96
+ "duration_min": route["duration"] / 60
97
+ })
98
+ return routes
99
+ return []
100
+ except:
101
+ return []
102
+
103
+ def get_detailed_route_with_instructions(origin_coords: Tuple[float, float],
104
+ dest_coords: Tuple[float, float]) -> Optional[Dict]:
105
+ """
106
+ Get detailed route with turn-by-turn instructions and road names
107
+ """
108
+ lat1, lon1 = origin_coords
109
+ lat2, lon2 = dest_coords
110
+
111
+ url = f"http://router.project-osrm.org/route/v1/driving/{lon1},{lat1};{lon2},{lat2}"
112
+
113
+ params = {
114
+ "alternatives": "true",
115
+ "steps": "true",
116
+ "overview": "full",
117
+ "geometries": "geojson",
118
+ "annotations": "true"
119
+ }
120
+
121
+ try:
122
+ response = requests.get(url, params=params, timeout=15)
123
+ response.raise_for_status()
124
+ data = response.json()
125
+
126
+ if data.get("code") == "Ok" and data.get("routes"):
127
+ all_routes = []
128
+
129
+ for route_idx, route in enumerate(data.get("routes", [])):
130
+ route_info = {
131
+ "route_number": route_idx + 1,
132
+ "distance_km": route["distance"] / 1000,
133
+ "duration_min": route["duration"] / 60,
134
+ "instructions": []
135
+ }
136
+
137
+ for leg in route.get("legs", []):
138
+ for step in leg.get("steps", []):
139
+ instruction = {
140
+ "distance_km": step["distance"] / 1000,
141
+ "duration_min": step["duration"] / 60,
142
+ "instruction": step.get("maneuver", {}).get("type", "continue"),
143
+ "road_name": step.get("name", "Unnamed road"),
144
+ "direction": step.get("maneuver", {}).get("modifier", "")
145
+ }
146
+ route_info["instructions"].append(instruction)
147
+
148
+ all_routes.append(route_info)
149
+
150
+ return all_routes
151
+ return None
152
+ except Exception as e:
153
+ print(f"Detailed routing error: {e}")
154
+ return None
app.py ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from deep_agent import agent
3
+ import os
4
+ import re
5
+
6
+ os.makedirs("static", exist_ok=True)
7
+
8
+ def process_delivery_query(query):
9
+ """Process delivery queries and return text + map"""
10
+ if not query.strip():
11
+ return "<div style='color:#ff6b6b; padding:20px; background:#ffe0e0; border-radius:8px;'>Please enter a query</div>", None
12
+
13
+ try:
14
+ result = agent.invoke({
15
+ "messages": [{"role": "user", "content": query}]
16
+ })
17
+
18
+ response_text = ""
19
+ if isinstance(result, dict) and "messages" in result:
20
+ for message in reversed(result["messages"]):
21
+ if getattr(message, "type", None) == "ai" and getattr(message, "content", None):
22
+ response_text = message.content
23
+ break
24
+ else:
25
+ response_text = str(result)
26
+
27
+ map_file_path = None
28
+ map_url_pattern = r'/view-map/([^\s\)]+\.html)'
29
+ map_match = re.search(map_url_pattern, response_text)
30
+
31
+ if map_match:
32
+ map_filename = map_match.group(1)
33
+ map_file_path = f"static/{map_filename}"
34
+ elif os.path.exists("static"):
35
+ map_files = [f for f in os.listdir("static") if f.startswith("route_") and f.endswith(".html")]
36
+ if map_files:
37
+ map_files.sort(key=lambda x: os.path.getmtime(os.path.join("static", x)), reverse=True)
38
+ map_file_path = f"static/{map_files[0]}"
39
+
40
+ response_text = re.sub(map_url_pattern, '', response_text)
41
+ response_text = re.sub(r'INTERACTIVE MAP:.*?\n', '', response_text)
42
+
43
+ html_response = response_text
44
+ html_response = html_response.replace('PRIMARY ROUTE:', '<h3 style="color:#667eea; margin-top:25px; border-left:4px solid #667eea; padding-left:15px;">PRIMARY ROUTE</h3>')
45
+ html_response = html_response.replace('ROUTE COMPARISON:', '<h3 style="color:#667eea; margin-top:25px; border-left:4px solid #667eea; padding-left:15px;">ROUTE COMPARISON</h3>')
46
+ html_response = html_response.replace('Cost Estimate', '<h3 style="color:#764ba2; margin-top:25px; border-left:4px solid #764ba2; padding-left:15px;">Cost Estimate</h3>')
47
+ html_response = html_response.replace('Traffic Analysis', '<h3 style="color:#f093fb; margin-top:25px; border-left:4px solid #f093fb; padding-left:15px;">Traffic Analysis</h3>')
48
+ html_response = html_response.replace('Route Analysis', '<h3 style="color:#667eea; margin-top:25px; border-left:4px solid #667eea; padding-left:15px;">Route Analysis</h3>')
49
+
50
+ html_response = html_response.replace('\n\n', '<br><br>')
51
+ html_response = html_response.replace('\n', '<br>')
52
+ html_response = f"<div style='font-family: system-ui; line-height:1.8; color:#333; padding:20px;'>{html_response}</div>"
53
+
54
+ return html_response, map_file_path
55
+
56
+ except Exception as e:
57
+ error_html = f"""
58
+ <div style='padding:25px; background:#ffe0e0; border-left:5px solid #ff6b6b; border-radius:10px;'>
59
+ <h3 style='color:#ff6b6b; margin:0 0 15px 0;'>Error Occurred</h3>
60
+ <p style='margin:0 0 10px 0; color:#666;'><strong>Error:</strong> {str(e)}</p>
61
+ <div style='margin-top:15px; padding:15px; background:#fff; border-radius:8px;'>
62
+ <p style='margin:0; font-size:14px; color:#999;'>
63
+ <strong>Tips:</strong><br>
64
+ - Use full city names: "Mumbai, Maharashtra, India"<br>
65
+ - Include country for international routes<br>
66
+ - Check spelling of location names
67
+ </p>
68
+ </div>
69
+ </div>
70
+ """
71
+ return error_html, None
72
+
73
+ with gr.Blocks(
74
+ theme=gr.themes.Soft(
75
+ primary_hue="indigo",
76
+ secondary_hue="purple",
77
+ ),
78
+ title="Delivery Optimization Agent",
79
+ css="""
80
+ body, html {
81
+ margin: 0;
82
+ padding: 0;
83
+ width: 100%;
84
+ overflow-x: hidden;
85
+ }
86
+ .gradio-container {
87
+ max-width: 100% !important;
88
+ width: 100% !important;
89
+ padding: 0 !important;
90
+ margin: 0 auto !important;
91
+ }
92
+ .block {
93
+ width: 100% !important;
94
+ }
95
+ #root > div {
96
+ width: 100% !important;
97
+ }
98
+ """
99
+ ) as demo:
100
+
101
+ gr.HTML("""
102
+ <div style='
103
+ text-align: center;
104
+ padding: 30px;
105
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
106
+ border-radius: 15px;
107
+ color: white;
108
+ margin-bottom: 30px;
109
+ box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
110
+ '>
111
+ <h1 style='margin:0; font-size:2.8em; font-weight:700;'>
112
+ Delivery Optimization Agent
113
+ </h1>
114
+ <p style='margin:15px 0 0 0; font-size:1.3em; opacity:0.95; font-weight:300;'>
115
+ Real-time Route Planning | Cost Analysis | Traffic Intelligence
116
+ </p>
117
+ <p style='margin:10px 0 0 0; font-size:1em; opacity:0.85;'>
118
+ Powered by SparkBrains
119
+ </p>
120
+ </div>
121
+ """)
122
+
123
+ with gr.Row():
124
+ with gr.Column(scale=2):
125
+ gr.Markdown("### Enter Your Delivery Query")
126
+
127
+ query_input = gr.Textbox(
128
+ label="",
129
+ placeholder="Example: Plan a delivery route from Mumbai, Maharashtra to Pune, Maharashtra with 500kg cargo",
130
+ lines=6,
131
+ show_label=False
132
+ )
133
+
134
+ with gr.Row():
135
+ submit_btn = gr.Button(
136
+ "Optimize Delivery",
137
+ variant="primary",
138
+ size="lg",
139
+ scale=4
140
+ )
141
+ clear_btn = gr.ClearButton(
142
+ [query_input],
143
+ value="Clear",
144
+ size="lg",
145
+ scale=1
146
+ )
147
+
148
+ with gr.Row():
149
+ with gr.Column(scale=3):
150
+ gr.Markdown("### Route Analysis")
151
+ output = gr.HTML(label="", show_label=False)
152
+
153
+ with gr.Column(scale=2):
154
+ gr.Markdown("### Interactive Map")
155
+ map_output = gr.HTML(
156
+ label="",
157
+ show_label=False,
158
+ value="<div style='padding:40px; text-align:center; color:#999; background:#f8f9fa; border-radius:10px; min-height:400px; display:flex; align-items:center; justify-content:center;'><div><p>Map will appear here after route planning</p></div></div>"
159
+ )
160
+
161
+ def process_and_display(query):
162
+ """Process query and return both text and map"""
163
+ text_result, map_path = process_delivery_query(query)
164
+
165
+ if map_path and os.path.exists(map_path):
166
+ with open(map_path, 'r', encoding='utf-8') as f:
167
+ map_html = f.read()
168
+
169
+ map_display = f"""
170
+ <div style='width:100%; height:600px; border-radius:10px; overflow:hidden; box-shadow:0 4px 6px rgba(0,0,0,0.1);'>
171
+ <iframe srcdoc='{map_html.replace("'", "&apos;")}'
172
+ style='width:100%; height:100%; border:none;'>
173
+ </iframe>
174
+ </div>
175
+ """
176
+ return text_result, map_display
177
+ else:
178
+ no_map = """
179
+ <div style='padding:40px; text-align:center; color:#999; background:#f8f9fa; border-radius:10px; min-height:400px; display:flex; align-items:center; justify-content:center;'>
180
+ <div>
181
+ <p>No map available for this query</p>
182
+ </div>
183
+ </div>
184
+ """
185
+ return text_result, no_map
186
+
187
+ submit_btn.click(
188
+ fn=process_and_display,
189
+ inputs=query_input,
190
+ outputs=[output, map_output]
191
+ )
192
+
193
+ gr.Markdown("### Try These Example Queries")
194
+ gr.Examples(
195
+ examples=[
196
+ "Plan route from Delhi, India to Jaipur, Rajasthan, India",
197
+ "Calculate delivery cost for 200kg package, distance 150km, duration 180 minutes",
198
+ "Check traffic conditions between Bangalore, Karnataka and Chennai, Tamil Nadu",
199
+ "Find optimal route from Mumbai, Maharashtra to Pune, Maharashtra with 500kg cargo",
200
+ "What's the best route from Kolkata to Bhubaneswar?",
201
+ ],
202
+ inputs=query_input,
203
+ label=""
204
+ )
205
+
206
+ gr.HTML("""
207
+ <div style='margin-top:30px; padding:25px; background:#f8f9fa; border-radius:15px;'>
208
+ <h3 style='margin:0 0 20px 0; color:#333;'>Features</h3>
209
+ <div style='display:grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap:15px;'>
210
+ <div style='padding:15px; background:white; border-radius:10px; border-left:4px solid #667eea;'>
211
+ <strong>Interactive Maps</strong>
212
+ <p style='margin:5px 0 0 0; font-size:0.9em; color:#666;'>Visual turn-by-turn directions</p>
213
+ </div>
214
+ <div style='padding:15px; background:white; border-radius:10px; border-left:4px solid #764ba2;'>
215
+ <strong>Cost Optimization</strong>
216
+ <p style='margin:5px 0 0 0; font-size:0.9em; color:#666;'>Real-world pricing estimates</p>
217
+ </div>
218
+ <div style='padding:15px; background:white; border-radius:10px; border-left:4px solid #f093fb;'>
219
+ <strong>Traffic Analysis</strong>
220
+ <p style='margin:5px 0 0 0; font-size:0.9em; color:#666;'>Live traffic patterns</p>
221
+ </div>
222
+ <div style='padding:15px; background:white; border-radius:10px; border-left:4px solid #667eea;'>
223
+ <strong>Route Comparison</strong>
224
+ <p style='margin:5px 0 0 0; font-size:0.9em; color:#666;'>Multiple route options</p>
225
+ </div>
226
+ </div>
227
+ </div>
228
+ """)
229
+
230
+ gr.Markdown("---")
231
+ gr.Markdown(
232
+ "<div style='text-align:center; color:#999; font-size:0.9em;'>"
233
+ "Built with DeepAgents | LangChain | Groq | OpenStreetMap<br>"
234
+ "Ready for Multi-Agent Hub Integration"
235
+ "</div>"
236
+ )
237
+
238
+ if __name__ == "__main__":
239
+ demo.launch(
240
+ server_name="0.0.0.0",
241
+ server_port=7860,
242
+ share=False
243
+ )
deep_agent.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from deepagents import create_deep_agent
2
+ from langchain_groq import ChatGroq
3
+ from sub_agents import route_agent, cost_agent, traffic_agent, coordinator
4
+ from tools import route_tool, cost_tool, traffic_tool
5
+ from dotenv import load_dotenv
6
+ import os
7
+
8
+ load_dotenv()
9
+
10
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY")
11
+
12
+ llm = ChatGroq(
13
+ model="llama-3.3-70b-versatile",
14
+ api_key=GROQ_API_KEY,
15
+ temperature=0.1,
16
+ )
17
+
18
+ agent = create_deep_agent(
19
+ model=llm,
20
+ tools=[route_tool, cost_tool, traffic_tool],
21
+ subagents=[route_agent, cost_agent, traffic_agent, coordinator],
22
+ system_prompt="""
23
+ You are a real-world delivery optimization system:
24
+ - OpenStreetMap (OSRM) for routing
25
+ - Nominatim for geocoding
26
+ - Real traffic patterns
27
+
28
+ Process:
29
+ 1. If a map URL is provided (INTERACTIVE MAP), keep it prominently visible
30
+ near the top of your response for easy user access.
31
+ 2. Calculate real-world costs
32
+ 3. Analyze current traffic
33
+ 4. Provide actionable recommendation
34
+
35
+ CRITICAL: Always include the map URL.
36
+
37
+ All numeric parameters must be numbers (not strings).
38
+ """,
39
+ )
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ langchain-groq
3
+ langchain-core
4
+ deepagents
5
+ pydantic
6
+ requests
7
+ folium
8
+ python-dotenv
schema.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+
3
+ class RouteInput(BaseModel):
4
+ origin: str = Field(description="Starting location (address or city)")
5
+ destination: str = Field(description="Destination location (address or city)")
6
+
7
+
8
+ class CostInput(BaseModel):
9
+ distance_km: float = Field(description="Distance in kilometers")
10
+ weight_kg: float = Field(description="Package weight in kilograms")
11
+ duration_min: float = Field(description="Estimated duration in minutes")
12
+
13
+
14
+ class TrafficInput(BaseModel):
15
+ origin: str = Field(description="Origin location")
16
+ destination: str = Field(description="Destination location")
sub_agents.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from deepagents.middleware.subagents import SubAgent
2
+ from tools import route_tool, cost_tool, traffic_tool
3
+
4
+ route_agent = SubAgent(
5
+ name="RealRouteAgent",
6
+ description="Plans routes using real OpenStreetMap data",
7
+ system_prompt="""
8
+ You are a route planning expert using real-world OpenStreetMap data.
9
+ Use the 'real_route_planner' tool with city names or addresses and detailed routes with direction.
10
+ Examples: "Delhi, India", "Mumbai, Maharashtra", "123 Main St, Bangalore"
11
+ Provide clear routing information with distances and times.
12
+ """,
13
+ tools=[route_tool],
14
+ )
15
+
16
+ cost_agent = SubAgent(
17
+ name="RealCostAgent",
18
+ description="Calculates costs with real-world pricing",
19
+ system_prompt="""
20
+ You are a cost analysis expert. Use 'real_cost_optimizer' tool.
21
+ CRITICAL: Pass numbers without quotes:
22
+ - distance_km: 50.5 (NOT "50.5")
23
+ - weight_kg: 100.0 (NOT "100")
24
+ - duration_min: 120.0 (NOT "120")
25
+ Explain cost breakdown clearly.
26
+ """,
27
+ tools=[cost_tool],
28
+ )
29
+
30
+ traffic_agent = SubAgent(
31
+ name="RealTrafficAgent",
32
+ description="Analyzes traffic using real patterns",
33
+ system_prompt="""
34
+ You are a traffic analysis expert. Use 'real_traffic_analyzer' tool.
35
+ Provide insights on current traffic conditions and best departure times.
36
+ Consider weather impacts and time-of-day patterns.
37
+ """,
38
+ tools=[traffic_tool],
39
+ )
40
+
41
+ coordinator = SubAgent(
42
+ name="Coordinator",
43
+ description="Synthesizes all information",
44
+ system_prompt="""
45
+ You are the delivery coordinator. Combine route, cost, and traffic info.
46
+
47
+ IMPORTANT MAP LINK FORMATTING:
48
+ - When you receive a map URL like "/view-map/route_X_Y.html", format it as:
49
+ "VIEW INTERACTIVE MAP: [CLICK HERE](/view-map/route_X_Y.html)"
50
+ - Place this map link at the TOP of your response in a prominent way
51
+ - Make it clear and obvious that users can click on it
52
+
53
+ Your response structure should be:
54
+ 1. nteractive Map Link (first and prominent)
55
+ 2. Route Summary (distance, duration)
56
+ 3. Dont give any detailed directions.
57
+ 4. Cost Breakdown
58
+ 5. Traffic Conditions
59
+ 6. Final Recommendations
60
+
61
+ Present information clearly and keep the map link easily accessible.
62
+ Never hide the map link in the middle of text - make it stand out!
63
+ """,
64
+ )
tools.py ADDED
@@ -0,0 +1,403 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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:
19
+ return 1.5
20
+ elif 17 <= current_hour <= 19:
21
+ return 1.6
22
+ elif 12 <= current_hour <= 14:
23
+ return 1.2
24
+ elif 0 <= current_hour <= 5:
25
+ return 0.8
26
+ else:
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 format_route_instructions(route_data: Dict) -> str:
65
+ """
66
+ Format route instructions in readable format
67
+ """
68
+ if not route_data:
69
+ return "No route instructions available"
70
+
71
+ output = f"\nRoute {route_data['route_number']}:\n"
72
+ output += f"Total Distance: {route_data['distance_km']:.1f} km\n"
73
+ output += f"Total Duration: {route_data['duration_min']:.0f} minutes\n"
74
+ output += "\nTurn-by-Turn Directions:\n"
75
+ output += "-" * 50 + "\n"
76
+
77
+ cumulative_distance = 0
78
+ for idx, step in enumerate(route_data['instructions'], 1):
79
+ cumulative_distance += step['distance_km']
80
+
81
+ instruction_type = step['instruction']
82
+ direction = step['direction']
83
+
84
+ if instruction_type == "depart":
85
+ action = "Start"
86
+ elif instruction_type == "arrive":
87
+ action = "Arrive at destination"
88
+ elif instruction_type == "turn":
89
+ action = f"Turn {direction}" if direction else "Turn"
90
+ elif instruction_type == "new name":
91
+ action = "Continue onto"
92
+ elif instruction_type == "roundabout":
93
+ action = "Take roundabout"
94
+ elif instruction_type == "merge":
95
+ action = f"Merge {direction}" if direction else "Merge"
96
+ else:
97
+ action = instruction_type.replace("_", " ").capitalize()
98
+
99
+ road_name = step['road_name']
100
+ distance = step['distance_km']
101
+
102
+ if distance > 0.1:
103
+ output += f"{idx}. {action}"
104
+ if road_name and road_name != "Unnamed road":
105
+ output += f" on {road_name}"
106
+ output += f" for {distance:.1f} km"
107
+ output += f" (Total: {cumulative_distance:.1f} km)\n"
108
+
109
+ return output
110
+
111
+
112
+ def find_optimal_route(origin_coords: Tuple[float, float],
113
+ dest_coords: Tuple[float, float]) -> Dict:
114
+ """
115
+ Compare all available routes and find optimal with detailed instructions
116
+ """
117
+
118
+ detailed_routes = get_detailed_route_with_instructions(origin_coords, dest_coords)
119
+
120
+ if not detailed_routes:
121
+ return None
122
+
123
+ traffic_factor = estimate_traffic_from_time()
124
+
125
+ scored_routes = []
126
+ for route_data in detailed_routes:
127
+ adjusted_time = route_data['duration_min'] * traffic_factor
128
+
129
+ fuel_cost_per_km = 8.0
130
+ estimated_cost = route_data['distance_km'] * fuel_cost_per_km
131
+
132
+ score = (adjusted_time * 0.6) + (estimated_cost * 0.4)
133
+
134
+ scored_routes.append({
135
+ 'route_num': route_data['route_number'],
136
+ 'distance_km': route_data['distance_km'],
137
+ 'duration_min': route_data['duration_min'],
138
+ 'adjusted_duration': adjusted_time,
139
+ 'estimated_cost': estimated_cost,
140
+ 'score': score,
141
+ 'instructions': route_data['instructions']
142
+ })
143
+
144
+ scored_routes.sort(key=lambda x: x['score'])
145
+
146
+ return {
147
+ 'optimal': scored_routes[0],
148
+ 'all_routes': scored_routes
149
+ }
150
+
151
+ def create_route_map_html(origin_coords, dest_coords, route_data, filename="static/route_map.html"):
152
+ """Generate interactive map with YOUR exact turn-by-turn directions"""
153
+
154
+ os.makedirs("static", exist_ok=True)
155
+
156
+ m = folium.Map(
157
+ location=[(origin_coords[0]+dest_coords[0])/2, (origin_coords[1]+dest_coords[1])/2],
158
+ zoom_start=8
159
+ )
160
+
161
+ folium.Marker(
162
+ origin_coords,
163
+ popup="<b>START</b>",
164
+ icon=folium.Icon(color='green', icon='play')
165
+ ).add_to(m)
166
+
167
+ folium.Marker(
168
+ dest_coords,
169
+ popup="<b>DESTINATION</b>",
170
+ icon=folium.Icon(color='red', icon='stop')
171
+ ).add_to(m)
172
+
173
+ if 'geometry' in route_data and route_data['geometry']:
174
+ coords = [[c[1], c[0]] for c in route_data['geometry']['coordinates']]
175
+ folium.PolyLine(coords, color='blue', weight=5, opacity=0.7).add_to(m)
176
+
177
+ 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'>"
178
+ directions_html += f"<h3>Route Directions</h3>"
179
+ directions_html += f"<b>Distance:</b> {route_data.get('distance_km', 0):.1f} km<br>"
180
+ directions_html += f"<b>Duration:</b> {route_data.get('duration_min', 0):.0f} min<br><hr>"
181
+
182
+ for idx, step in enumerate(route_data.get('instructions', []), 1):
183
+ if step['distance_km'] > 0.1:
184
+ directions_html += f"<b>{idx}.</b> {step['instruction'].title()}"
185
+ if step['road_name'] != "Unnamed road":
186
+ directions_html += f" on <b>{step['road_name']}</b>"
187
+ directions_html += f"<br><small>{step['distance_km']:.1f} km</small><hr>"
188
+
189
+ directions_html += "</div>"
190
+
191
+ m.get_root().html.add_child(folium.Element(directions_html))
192
+
193
+ m.save(filename)
194
+ return filename
195
+
196
+ def real_route_planner(origin: str, destination: str) -> str:
197
+ """
198
+ Plan route using FREE OSRM with real OpenStreetMap data
199
+ """
200
+ print(f"\n{'='*60}")
201
+ print(f"Planning route: {origin} → {destination}")
202
+ print(f"{'='*60}")
203
+
204
+ origin_coords = geocode_address_nominatim(origin)
205
+ if not origin_coords:
206
+ return f"Could not find location: {origin}. Try being more specific (e.g., 'Mumbai, Maharashtra, India')"
207
+ print(f"✓ Origin found: {origin_coords}")
208
+
209
+ dest_coords = geocode_address_nominatim(destination)
210
+ if not dest_coords:
211
+ return f"Could not find location: {destination}. Try being more specific."
212
+ print(f"✓ Destination found: {dest_coords}")
213
+
214
+ route = get_route_osrm(origin_coords, dest_coords)
215
+ if not route:
216
+ return f"No route found between {origin} and {destination}"
217
+ print(f"✓ Route found: {route['distance_km']:.1f} km, {route['duration_min']:.0f} min")
218
+
219
+ optimization = find_optimal_route(origin_coords, dest_coords)
220
+ if not optimization:
221
+ return f"Could not optimize route"
222
+
223
+ safe_origin = origin.replace(' ', '_').replace(',', '')[:30]
224
+ safe_dest = destination.replace(' ', '_').replace(',', '')[:30]
225
+ map_filename = f"route_{safe_origin}_to_{safe_dest}.html"
226
+ map_path = f"static/{map_filename}"
227
+
228
+ optimal_route_data = {
229
+ 'distance_km': optimization['optimal']['distance_km'],
230
+ 'duration_min': optimization['optimal']['duration_min'],
231
+ 'instructions': optimization['optimal']['instructions'],
232
+ 'geometry': route.get('geometry')
233
+ }
234
+
235
+ try:
236
+ map_file = create_route_map_html(origin_coords, dest_coords, optimal_route_data, map_path)
237
+ print("Map saved to", map_file)
238
+ map_url = f"/view-map/{map_filename}"
239
+ except Exception as e:
240
+ print("Map creation failed:", e)
241
+ map_url = None
242
+
243
+
244
+ result = f"Route Analysis (OpenStreetMap Data):\n"
245
+ result += f"From: {origin}\n"
246
+ result += f"To: {destination}\n\n"
247
+
248
+ if map_url:
249
+ result += f"INTERACTIVE MAP: {map_url}\n"
250
+ result += f"(Open this link in your browser to view the map)\n\n"
251
+
252
+ result += f"PRIMARY ROUTE:\n"
253
+ result += f"Distance: {route['distance_km']:.1f} km\n"
254
+ result += f"Duration: {route['duration_min']:.0f} minutes\n"
255
+
256
+ if optimization and len(optimization['all_routes']) >= 1:
257
+ result += "\n\nROUTE COMPARISON:\n"
258
+ for i, r in enumerate(optimization['all_routes']):
259
+ marker = " [OPTIMAL]" if i == 0 else ""
260
+ result += (f"\nRoute {r['route_num']}:{marker}\n"
261
+ f" Distance: {r['distance_km']:.1f} km\n"
262
+ f" Base Time: {r['duration_min']:.0f} min\n"
263
+ f" With Traffic: {r['adjusted_duration']:.0f} min\n"
264
+ f" Est. Cost: Rs {r['estimated_cost']:.2f}\n"
265
+ f" Score: {r['score']:.1f}")
266
+
267
+ if optimization['optimal']['route_num'] != 1:
268
+ result += f"\n\nRECOMMENDATION: Route {optimization['optimal']['route_num']} is optimal based on time and cost factors"
269
+
270
+ optimal_route = optimization['optimal']
271
+ result += "\n\n" + "=" * 50
272
+ result += format_route_instructions({
273
+ 'route_number': optimal_route['route_num'],
274
+ 'distance_km': optimal_route['distance_km'],
275
+ 'duration_min': optimal_route['duration_min'],
276
+ 'instructions': optimal_route['instructions']
277
+ })
278
+
279
+ return result
280
+
281
+ def real_cost_optimizer(distance_km: float, weight_kg: float, duration_min: float) -> str:
282
+ """
283
+ Calculate real-world delivery costs
284
+ """
285
+ print(f"\nCalculating costs: {distance_km:.1f}km, {weight_kg}kg, {duration_min/60:.0f}hr")
286
+
287
+ suitable = [v for v in REAL_VEHICLES if v["capacity_kg"] >= weight_kg and v["available"]]
288
+
289
+ if not suitable:
290
+ return f"No vehicle available for {weight_kg}kg (max capacity: {max(v['capacity_kg'] for v in REAL_VEHICLES)}kg)"
291
+
292
+ options = []
293
+ for vehicle in suitable:
294
+
295
+ fuel_cost = distance_km * vehicle["fuel_cost_per_km"]
296
+
297
+ driver_cost = (duration_min / 60) * 200
298
+
299
+ base_fee = 150
300
+
301
+ traffic_multiplier = estimate_traffic_from_time()
302
+
303
+ capacity_usage = weight_kg / vehicle["capacity_kg"]
304
+ capacity_multiplier = 1.15 if capacity_usage > 0.8 else 1.0
305
+
306
+ total_cost = (base_fee + fuel_cost + driver_cost) * traffic_multiplier * capacity_multiplier
307
+
308
+ options.append({
309
+ "vehicle": vehicle,
310
+ "total_cost": total_cost,
311
+ "fuel_cost": fuel_cost,
312
+ "driver_cost": driver_cost,
313
+ "traffic_factor": traffic_multiplier
314
+ })
315
+
316
+ options.sort(key=lambda x: x["total_cost"])
317
+ best = options[0]
318
+
319
+ result = (
320
+ f"Cost Estimate (Real-world pricing):\n"
321
+ f"Recommended: {best['vehicle']['type']} ({best['vehicle']['id']})\n"
322
+ f"Total Cost: ₹{best['total_cost']:.2f}\n"
323
+ f"• Base Fee: ₹150\n"
324
+ f"• Fuel: ₹{best['fuel_cost']:.2f}\n"
325
+ f"• Driver: ₹{best['driver_cost']:.2f}\n"
326
+ f"• Traffic Factor: {best['traffic_factor']:.2f}x\n"
327
+ f"Capacity: {best['vehicle']['capacity_kg']}kg (using {(weight_kg/best['vehicle']['capacity_kg'])*100:.1f}%)"
328
+ )
329
+
330
+ if len(options) > 1:
331
+ alt = options[1]
332
+ result += f"\n Alternative: {alt['vehicle']['type']} at ₹{alt['total_cost']:.2f}"
333
+
334
+ return result
335
+
336
+
337
+ def real_traffic_analyzer(origin: str, destination: str) -> str:
338
+ """
339
+ Analyze traffic conditions using real data and patterns
340
+ """
341
+ print(f"\nAnalyzing traffic: {origin} → {destination}")
342
+
343
+ origin_coords = geocode_address_nominatim(origin)
344
+ dest_coords = geocode_address_nominatim(destination)
345
+
346
+ if not origin_coords or not dest_coords:
347
+ return "Could not analyze traffic - invalid locations"
348
+
349
+ route = get_route_osrm(origin_coords, dest_coords)
350
+ if not route:
351
+ return "No route found for traffic analysis"
352
+
353
+ current_hour = datetime.now().hour
354
+ traffic_factor = estimate_traffic_from_time()
355
+
356
+ if traffic_factor >= 1.5:
357
+ traffic_level = "Heavy"
358
+ advice = "Consider delaying by 1-2 hours or use alternative route"
359
+ elif traffic_factor >= 1.2:
360
+ traffic_level = "Moderate"
361
+ advice = "Expect minor delays, monitor conditions"
362
+ else:
363
+ traffic_level = "Light"
364
+ advice = "Good time to depart!"
365
+
366
+
367
+ base_duration = route["duration_min"]
368
+ adjusted_duration = base_duration * traffic_factor
369
+ total_delay = adjusted_duration - base_duration
370
+
371
+ result = (
372
+ f"Traffic Analysis (Real-time patterns):\n"
373
+ f"Current Traffic: {traffic_level}\n"
374
+ f"Time: {datetime.now().strftime('%H:%M')} (Factor: {traffic_factor:.2f}x)\n"
375
+ f"Base ETA: {base_duration:.0f} min\n"
376
+ f"Adjusted ETA: {adjusted_duration:.0f} min (+{total_delay:.0f} min delay)\n"
377
+ )
378
+
379
+ result += f"\nAdvice: {advice}"
380
+
381
+ return result
382
+
383
+
384
+ route_tool = StructuredTool.from_function(
385
+ func=real_route_planner,
386
+ name="real_route_planner",
387
+ description="Find optimal route using FREE OpenStreetMap data. Provide city names or addresses and the detailed routes.",
388
+ args_schema=RouteInput
389
+ )
390
+
391
+ cost_tool = StructuredTool.from_function(
392
+ func=real_cost_optimizer,
393
+ name="real_cost_optimizer",
394
+ description="Calculate delivery costs with real-world pricing. Pass distance_km, weight_kg, duration_min as numbers.",
395
+ args_schema=CostInput
396
+ )
397
+
398
+ traffic_tool = StructuredTool.from_function(
399
+ func=real_traffic_analyzer,
400
+ name="real_traffic_analyzer",
401
+ description="Analyze traffic conditions using real patterns.",
402
+ args_schema=TrafficInput
403
+ )