MBG0903 commited on
Commit
0ad8926
·
verified ·
1 Parent(s): 0c68dba

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +302 -115
app.py CHANGED
@@ -2,174 +2,361 @@ import gradio as gr
2
  import pandas as pd
3
  import numpy as np
4
  import requests
 
 
5
  from geopy.geocoders import Nominatim
6
  from geopy.extra.rate_limiter import RateLimiter
7
  from ortools.constraint_solver import pywrapcp, routing_enums_pb2
8
- import folium
9
- from folium.plugins import AntPath
10
-
11
- # -------------------------------------------
12
- # 1. Geocoding Function (Address -> Lat/Lon)
13
- # -------------------------------------------
14
-
15
- geolocator = Nominatim(user_agent="procelevate_route_planner")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1)
17
 
18
  def get_coordinates(address):
19
- loc = geocode(address)
20
- if loc:
21
- return (loc.latitude, loc.longitude)
 
 
 
22
  return None
23
 
 
 
 
24
 
25
- # -------------------------------------------
26
- # 2. OSRM Distance API
27
- # -------------------------------------------
28
-
29
- def osrm_distance(coord1, coord2):
30
  url = f"http://router.project-osrm.org/route/v1/driving/{coord1[1]},{coord1[0]};{coord2[1]},{coord2[0]}?overview=false"
31
- res = requests.get(url).json()
32
- if "routes" in res:
33
- d = res["routes"][0]["distance"] # meters
34
- t = res["routes"][0]["duration"] # seconds
35
- return d, t
36
  return None, None
37
 
38
-
39
- # -------------------------------------------
40
- # 3. Build Distance & Time Matrix
41
- # -------------------------------------------
42
-
43
  def build_matrices(coords):
44
  n = len(coords)
45
- dist_mat = np.zeros((n, n))
46
- time_mat = np.zeros((n, n))
47
 
48
  for i in range(n):
49
  for j in range(n):
50
  if i == j:
51
  continue
52
- d, t = osrm_distance(coords[i], coords[j])
53
- if d is None:
54
  return None, None
55
- dist_mat[i][j] = int(d)
56
- time_mat[i][j] = int(t)
57
-
58
- return dist_mat, time_mat
59
 
 
60
 
61
- # -------------------------------------------
62
- # 4. OR-Tools Route Optimization
63
- # -------------------------------------------
64
 
65
- def optimize_route(dist_matrix):
66
- n = len(dist_matrix)
67
  manager = pywrapcp.RoutingIndexManager(n, 1, 0)
68
  routing = pywrapcp.RoutingModel(manager)
69
 
70
- def callback(from_index, to_index):
71
- return int(dist_matrix[manager.IndexToNode(from_index)][manager.IndexToNode(to_index)])
72
 
73
- transit_idx = routing.RegisterTransitCallback(callback)
74
  routing.SetArcCostEvaluatorOfAllVehicles(transit_idx)
75
 
76
- search = pywrapcp.DefaultRoutingSearchParameters()
77
- search.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC
78
 
79
- sol = routing.SolveWithParameters(search)
80
  if not sol:
81
  return None
82
 
83
  index = routing.Start(0)
84
- route = []
 
85
  while not routing.IsEnd(index):
86
- route.append(manager.IndexToNode(index))
87
  index = sol.Value(routing.NextVar(index))
88
 
89
- return route
90
 
 
 
 
91
 
92
- # -------------------------------------------
93
- # 5. Build Folium Map
94
- # -------------------------------------------
 
 
95
 
96
- def build_map(coords, addresses, route_order):
97
- start = coords[route_order[0]]
98
- m = folium.Map(location=start, zoom_start=12)
99
 
100
- # Add markers
101
- for i, idx in enumerate(route_order):
102
- folium.Marker(coords[idx], tooltip=f"{i+1}. {addresses[idx]}").add_to(m)
103
 
104
- # Draw lines between points
105
- path = [coords[idx] for idx in route_order]
106
- AntPath(path, color="blue", weight=4).add_to(m)
 
 
 
 
107
 
 
108
  return m._repr_html_()
109
 
 
 
 
110
 
111
- # -------------------------------------------
112
- # MAIN FUNCTION
113
- # -------------------------------------------
114
 
115
- def run_planner(address_text):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
- addresses = [a.strip() for a in address_text.split("\n") if a.strip()]
118
  if len(addresses) < 2:
119
- return "❌ Enter at least two locations.", None, None, None
120
 
121
- # 1. Geocode
122
  coords = []
123
  for a in addresses:
124
  c = get_coordinates(a)
125
  if not c:
126
- return f"❌ Unable to geocode: {a}", None, None, None
127
  coords.append(c)
128
 
129
- # 2. Build matrices
130
- dist_mat, time_mat = build_matrices(coords)
131
- if dist_mat is None:
132
- return "❌ Failed to fetch OSRM distances.", None, None, None
133
-
134
- # 3. Optimize route
135
- route_order = optimize_route(dist_mat)
136
- if route_order is None:
137
- return "❌ Route optimization failed.", None, None, None
138
-
139
- # 4. Format output text
140
- route_text = "🚚 **Optimized Real Route (OSRM):**\n\n"
141
- for i, idx in enumerate(route_order):
142
- route_text += f"**{i+1}. {addresses[idx]}**\n"
143
-
144
- # 5. Build map
145
- map_html = build_map(coords, addresses, route_order)
146
-
147
- # 6. Convert matrices to DataFrames
148
- df_dist = pd.DataFrame(dist_mat, columns=addresses, index=addresses)
149
- df_time = pd.DataFrame(time_mat, columns=addresses, index=addresses)
150
-
151
- return route_text, df_dist, df_time, map_html
152
-
153
-
154
- # -------------------------------------------
155
- # GRADIO UI
156
- # -------------------------------------------
157
-
158
- interface = gr.Interface(
159
- fn=run_planner,
160
- inputs=gr.Textbox(
161
- lines=8,
162
- label="Enter addresses (one per line):",
163
- placeholder="Example:\nBangalore Airport\nMG Road\nElectronic City\nWhitefield",
164
- ),
165
- outputs=[
166
- gr.Markdown(label="Optimized Route"),
167
- gr.Dataframe(label="Distance (meters)"),
168
- gr.Dataframe(label="Time (seconds)"),
169
- gr.HTML(label="Route Map"),
170
- ],
171
- title="Procelevate AI Route Planner",
172
- description="Real logistics routing using OSRM, OR-Tools, and AI-driven insights.",
173
- )
174
-
175
- interface.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import pandas as pd
3
  import numpy as np
4
  import requests
5
+ import folium
6
+ from folium.plugins import AntPath
7
  from geopy.geocoders import Nominatim
8
  from geopy.extra.rate_limiter import RateLimiter
9
  from ortools.constraint_solver import pywrapcp, routing_enums_pb2
10
+ import os
11
+ from openai import OpenAI
12
+ from datetime import datetime
13
+ import base64
14
+
15
+ # ---------------------------------------
16
+ # CONSTANTS & BRANDING
17
+ # ---------------------------------------
18
+
19
+ PRIMARY_COLOR = "#0F2C59" # Procelevate Blue
20
+ VEHICLE_MILEAGE = {
21
+ "Truck (32 ft)": 2.5,
22
+ "Mini-Truck / Tata Ace": 12,
23
+ "Bike Delivery": 45,
24
+ "Car / SUV": 12
25
+ }
26
+
27
+ # GPT Client
28
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
29
+ client = OpenAI(api_key=OPENAI_API_KEY)
30
+
31
+ # ---------------------------------------
32
+ # GEOCODING
33
+ # ---------------------------------------
34
+
35
+ geolocator = Nominatim(user_agent="procelevate_route_app")
36
  geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1)
37
 
38
  def get_coordinates(address):
39
+ try:
40
+ loc = geocode(address)
41
+ if loc:
42
+ return (loc.latitude, loc.longitude)
43
+ except:
44
+ return None
45
  return None
46
 
47
+ # ---------------------------------------
48
+ # OSRM Distance + Time Fetcher
49
+ # ---------------------------------------
50
 
51
+ def osrm_query(coord1, coord2):
 
 
 
 
52
  url = f"http://router.project-osrm.org/route/v1/driving/{coord1[1]},{coord1[0]};{coord2[1]},{coord2[0]}?overview=false"
53
+ r = requests.get(url).json()
54
+ if "routes" in r:
55
+ return r["routes"][0]["distance"], r["routes"][0]["duration"]
 
 
56
  return None, None
57
 
 
 
 
 
 
58
  def build_matrices(coords):
59
  n = len(coords)
60
+ dist = np.zeros((n,n))
61
+ time = np.zeros((n,n))
62
 
63
  for i in range(n):
64
  for j in range(n):
65
  if i == j:
66
  continue
67
+ d, t = osrm_query(coords[i], coords[j])
68
+ if not d:
69
  return None, None
70
+ dist[i][j] = d
71
+ time[i][j] = t
 
 
72
 
73
+ return dist, time
74
 
75
+ # ---------------------------------------
76
+ # OR-TOOLS Optimization
77
+ # ---------------------------------------
78
 
79
+ def optimize_route(distance_matrix):
80
+ n = len(distance_matrix)
81
  manager = pywrapcp.RoutingIndexManager(n, 1, 0)
82
  routing = pywrapcp.RoutingModel(manager)
83
 
84
+ def distance_callback(from_i, to_i):
85
+ return int(distance_matrix[manager.IndexToNode(from_i)][manager.IndexToNode(to_i)])
86
 
87
+ transit_idx = routing.RegisterTransitCallback(distance_callback)
88
  routing.SetArcCostEvaluatorOfAllVehicles(transit_idx)
89
 
90
+ params = pywrapcp.DefaultRoutingSearchParameters()
91
+ params.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC
92
 
93
+ sol = routing.SolveWithParameters(params)
94
  if not sol:
95
  return None
96
 
97
  index = routing.Start(0)
98
+ order = []
99
+
100
  while not routing.IsEnd(index):
101
+ order.append(manager.IndexToNode(index))
102
  index = sol.Value(routing.NextVar(index))
103
 
104
+ return order
105
 
106
+ # ---------------------------------------
107
+ # Fuel, Cost, Toll Calculations
108
+ # ---------------------------------------
109
 
110
+ def calculate_kpis(distance_km, mileage, fuel_price, toll_estimate=0):
111
+ fuel_needed = distance_km / mileage
112
+ fuel_cost = fuel_needed * fuel_price
113
+ total_cost = fuel_cost + toll_estimate
114
+ return fuel_needed, fuel_cost, total_cost
115
 
116
+ # ---------------------------------------
117
+ # Folium Map
118
+ # ---------------------------------------
119
 
120
+ def make_map(coords, addresses, order):
121
+ m = folium.Map(location=coords[order[0]], zoom_start=12)
122
+ path = []
123
 
124
+ for i, idx in enumerate(order):
125
+ folium.Marker(
126
+ coords[idx],
127
+ tooltip=f"{i+1}. {addresses[idx]}",
128
+ icon=folium.Icon(color="blue")
129
+ ).add_to(m)
130
+ path.append(coords[idx])
131
 
132
+ AntPath(path, color="blue", weight=4).add_to(m)
133
  return m._repr_html_()
134
 
135
+ # ---------------------------------------
136
+ # AI Explanation via GPT
137
+ # ---------------------------------------
138
 
139
+ def generate_ai_explanation(opt_km, naive_km, fuel_saved, cost_saved, time_saved):
140
+ prompt = f"""
141
+ You are an AI logistics expert. Explain clearly why the optimized route is better.
142
 
143
+ Optimized distance: {opt_km:.2f} km
144
+ Naive distance: {naive_km:.2f} km
145
+ Fuel saved: {fuel_saved:.2f} litres
146
+ Cost saved: ₹{cost_saved:.2f}
147
+ Time saved: {time_saved:.2f} minutes
148
+
149
+ Write a short, professional explanation suitable for a supply-chain manager at CEVA Logistics.
150
+ """
151
+
152
+ response = client.chat.completions.create(
153
+ model="gpt-4o-mini",
154
+ messages=[{"role": "user", "content": prompt}]
155
+ )
156
+
157
+ return response.choices[0].message.content
158
+
159
+ # ---------------------------------------
160
+ # HTML REPORT GENERATOR
161
+ # ---------------------------------------
162
+
163
+ def generate_report_html(summary_text, ai_text, map_html):
164
+ html = f"""
165
+ <html>
166
+ <head>
167
+ <style>
168
+ body {{ font-family: Arial; }}
169
+ h2 {{ color: {PRIMARY_COLOR}; }}
170
+ </style>
171
+ </head>
172
+ <body>
173
+ <h2>Procelevate AI Route Optimization Report</h2>
174
+ <p><b>Generated:</b> {datetime.now().strftime('%Y-%m-%d %H:%M')}</p>
175
+
176
+ <h3>Summary</h3>
177
+ <p>{summary_text}</p>
178
+
179
+ <h3>AI Explanation</h3>
180
+ <p>{ai_text}</p>
181
+
182
+ <h3>Route Map</h3>
183
+ {map_html}
184
+
185
+ </body>
186
+ </html>
187
+ """
188
+ return html
189
+
190
+ # ---------------------------------------
191
+ # PART 3 — FULL ENTERPRISE DASHBOARD UI
192
+ # ---------------------------------------
193
+
194
+ def run_route_engine(start, end, stops, vehicle_type, fuel_price):
195
+
196
+ # Build address list
197
+ addresses = [start] + stops + [end]
198
+ addresses = [a for a in addresses if a.strip()]
199
 
 
200
  if len(addresses) < 2:
201
+ return ["❌ Please enter valid addresses."] + [None]*5
202
 
203
+ # Geocode all addresses
204
  coords = []
205
  for a in addresses:
206
  c = get_coordinates(a)
207
  if not c:
208
+ return [f"❌ Failed to locate address: {a}"] + [None]*5
209
  coords.append(c)
210
 
211
+ # Build OSRM matrices
212
+ dist_m, time_m = build_matrices(coords)
213
+ if dist_m is None:
214
+ return ["❌ OSRM routing failed. Try different locations."] + [None]*5
215
+
216
+ # Convert meters → km, seconds → minutes
217
+ dist_km = dist_m / 1000
218
+ time_min = time_m / 60
219
+
220
+ # Optimized route
221
+ opt_order = optimize_route(dist_m)
222
+ if opt_order is None:
223
+ return ["❌ Optimization failed."] + [None]*5
224
+
225
+ # Naive + reverse orders
226
+ naive_order = list(range(len(addresses)))
227
+ rev_order = list(reversed(naive_order))
228
+
229
+ def compute_totals(order):
230
+ total_km = 0
231
+ total_min = 0
232
+ for i in range(len(order)-1):
233
+ total_km += dist_km[order[i]][order[i+1]]
234
+ total_min += time_min[order[i]][order[i+1]]
235
+ return total_km, total_min
236
+
237
+ opt_km, opt_min = compute_totals(opt_order)
238
+ naive_km, naive_min = compute_totals(naive_order)
239
+ rev_km, rev_min = compute_totals(rev_order)
240
+
241
+ mileage = VEHICLE_MILEAGE[vehicle_type]
242
+
243
+ # KPI calculations
244
+ opt_fuel, opt_fuel_cost, opt_total_cost = calculate_kpis(opt_km, mileage, fuel_price)
245
+ naive_fuel, naive_fuel_cost, naive_total_cost = calculate_kpis(naive_km, mileage, fuel_price)
246
+ rev_fuel, rev_fuel_cost, rev_total_cost = calculate_kpis(rev_km, mileage, fuel_price)
247
+
248
+ # Savings
249
+ fuel_saved = naive_fuel - opt_fuel
250
+ cost_saved = naive_total_cost - opt_total_cost
251
+ time_saved = naive_min - opt_min
252
+
253
+ # AI Explanation
254
+ ai_text = generate_ai_explanation(opt_km, naive_km, fuel_saved, cost_saved, time_saved)
255
+
256
+ # Optimized route map
257
+ map_html = make_map(coords, addresses, opt_order)
258
+
259
+ # Summary panel text
260
+ summary_text = f"""
261
+ <b>Optimized Distance:</b> {opt_km:.2f} km<br>
262
+ <b>Optimized Time:</b> {opt_min:.2f} minutes<br>
263
+ <b>Fuel Needed:</b> {opt_fuel:.2f} L<br>
264
+ <b>Total Cost:</b> ₹{opt_total_cost:.2f}<br>
265
+ <b>Efficiency Gain vs Naive:</b> {((naive_km-opt_km)/naive_km)*100:.2f}%<br>
266
+ """
267
+
268
+ # Comparison table
269
+ comp_df = pd.DataFrame({
270
+ "Route Type": ["Optimized", "Naive", "Reverse"],
271
+ "Distance (km)": [opt_km, naive_km, rev_km],
272
+ "Time (min)": [opt_min, naive_min, rev_min],
273
+ "Fuel (L)": [opt_fuel, naive_fuel, rev_fuel],
274
+ "Cost (₹)": [opt_total_cost, naive_total_cost, rev_total_cost]
275
+ })
276
+
277
+ # Data tables
278
+ dist_df = pd.DataFrame(dist_km, columns=addresses, index=addresses)
279
+ time_df = pd.DataFrame(time_min, columns=addresses, index=addresses)
280
+
281
+ # Downloadable report
282
+ report_html = generate_report_html(summary_text, ai_text, map_html)
283
+ b64 = base64.b64encode(report_html.encode()).decode()
284
+ download_link = f'<a href="data:text/html;base64,{b64}" download="route_report.html">Download Report</a>'
285
+
286
+ return (
287
+ summary_text, # TAB 1
288
+ map_html, # TAB 2
289
+ comp_df, # TAB 3
290
+ ai_text, # TAB 4
291
+ dist_df, # TAB 5A
292
+ time_df, # TAB 5B
293
+ download_link # TAB 6
294
+ )
295
+
296
+
297
+ # ---------------------------------------
298
+ # GRADIO UI LAYOUT — TABS + BRANDING
299
+ # ---------------------------------------
300
+
301
+ with gr.Blocks(theme=gr.themes.Base(primary_hue="blue")) as demo:
302
+
303
+ gr.Markdown(f"<h1 style='color:{PRIMARY_COLOR}'>Procelevate AI Route Optimization Suite</h1>")
304
+
305
+ with gr.Row():
306
+ start = gr.Textbox(label="From")
307
+ end = gr.Textbox(label="To")
308
+
309
+ # Dynamic stops
310
+ stops_box = gr.Group()
311
+ stop1 = gr.Textbox(label="Stop 1 (optional)")
312
+ stop2 = gr.Textbox(label="Stop 2 (optional)")
313
+
314
+ stops = [stop1, stop2]
315
+
316
+ with gr.Row():
317
+ add_stop_btn = gr.Button("➕ Add Stop")
318
+ def add_more_stop():
319
+ new = gr.Textbox(label=f"Stop {len(stops)+1} (optional)")
320
+ stops.append(new)
321
+ return stops_box.update(components=stops)
322
+
323
+ add_stop_btn.click(add_more_stop, None, stops_box)
324
+
325
+ stops_box.render()
326
+
327
+ vehicle_type = gr.Dropdown(list(VEHICLE_MILEAGE.keys()), label="Vehicle Type", value="Truck (32 ft)")
328
+ fuel_price = gr.Number(label="Fuel Price (₹/L)", value=91)
329
+
330
+ submit_btn = gr.Button("Optimize Route", variant="primary")
331
+
332
+ # TAB STRUCTURE
333
+ with gr.Tabs():
334
+ with gr.Tab("Overview"):
335
+ overview_out = gr.HTML()
336
+
337
+ with gr.Tab("Optimized Route"):
338
+ map_out = gr.HTML()
339
+
340
+ with gr.Tab("Route Comparison"):
341
+ comp_out = gr.Dataframe()
342
+
343
+ with gr.Tab("AI Explanation"):
344
+ ai_out = gr.Markdown()
345
+
346
+ with gr.Tab("Data Tables"):
347
+ dist_out = gr.Dataframe(label="Distance (km)")
348
+ time_out = gr.Dataframe(label="Time (min)")
349
+
350
+ with gr.Tab("Download Report"):
351
+ report_out = gr.HTML()
352
+
353
+ # CONNECT BUTTON
354
+ submit_btn.click(
355
+ fn=run_route_engine,
356
+ inputs=[start, end, stop1, stop2, vehicle_type, fuel_price],
357
+ outputs=[overview_out, map_out, comp_out, ai_out, dist_out, time_out, report_out]
358
+ )
359
+
360
+
361
+ demo.launch()
362
+