Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| import requests | |
| import folium | |
| from folium.plugins import AntPath | |
| from geopy.geocoders import Nominatim | |
| from geopy.extra.rate_limiter import RateLimiter | |
| from ortools.constraint_solver import pywrapcp, routing_enums_pb2 | |
| import os | |
| from openai import OpenAI | |
| from datetime import datetime | |
| import base64 | |
| # --------------------------------------- | |
| # CONSTANTS & BRANDING | |
| # --------------------------------------- | |
| PRIMARY_COLOR = "#0F2C59" # Procelevate Blue | |
| VEHICLE_MILEAGE = { | |
| "Truck (32 ft)": 2.5, | |
| "Mini-Truck / Tata Ace": 12, | |
| "Bike Delivery": 45, | |
| "Car / SUV": 12 | |
| } | |
| # GPT Client | |
| OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") | |
| client = OpenAI(api_key=OPENAI_API_KEY) | |
| # --------------------------------------- | |
| # GEOCODING | |
| # --------------------------------------- | |
| geolocator = Nominatim(user_agent="procelevate_route_app") | |
| geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1) | |
| def get_coordinates(address): | |
| try: | |
| loc = geocode(address) | |
| if loc: | |
| return (loc.latitude, loc.longitude) | |
| except: | |
| return None | |
| return None | |
| # --------------------------------------- | |
| # OSRM Distance + Time Fetcher | |
| # --------------------------------------- | |
| def osrm_query(coord1, coord2): | |
| url = f"http://router.project-osrm.org/route/v1/driving/{coord1[1]},{coord1[0]};{coord2[1]},{coord2[0]}?overview=false" | |
| r = requests.get(url).json() | |
| if "routes" in r: | |
| return r["routes"][0]["distance"], r["routes"][0]["duration"] | |
| return None, None | |
| def build_matrices(coords): | |
| n = len(coords) | |
| dist = np.zeros((n,n)) | |
| time = np.zeros((n,n)) | |
| for i in range(n): | |
| for j in range(n): | |
| if i == j: | |
| continue | |
| d, t = osrm_query(coords[i], coords[j]) | |
| if not d: | |
| return None, None | |
| dist[i][j] = d | |
| time[i][j] = t | |
| return dist, time | |
| # --------------------------------------- | |
| # OR-TOOLS Optimization | |
| # --------------------------------------- | |
| def optimize_route(distance_matrix): | |
| n = len(distance_matrix) | |
| manager = pywrapcp.RoutingIndexManager(n, 1, 0) | |
| routing = pywrapcp.RoutingModel(manager) | |
| def distance_callback(from_i, to_i): | |
| return int(distance_matrix[manager.IndexToNode(from_i)][manager.IndexToNode(to_i)]) | |
| transit_idx = routing.RegisterTransitCallback(distance_callback) | |
| routing.SetArcCostEvaluatorOfAllVehicles(transit_idx) | |
| params = pywrapcp.DefaultRoutingSearchParameters() | |
| params.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC | |
| sol = routing.SolveWithParameters(params) | |
| if not sol: | |
| return None | |
| index = routing.Start(0) | |
| order = [] | |
| while not routing.IsEnd(index): | |
| order.append(manager.IndexToNode(index)) | |
| index = sol.Value(routing.NextVar(index)) | |
| return order | |
| # --------------------------------------- | |
| # Fuel, Cost, Toll Calculations | |
| # --------------------------------------- | |
| def calculate_kpis(distance_km, mileage, fuel_price, toll_estimate=0): | |
| fuel_needed = distance_km / mileage | |
| fuel_cost = fuel_needed * fuel_price | |
| total_cost = fuel_cost + toll_estimate | |
| return fuel_needed, fuel_cost, total_cost | |
| # --------------------------------------- | |
| # Folium Map | |
| # --------------------------------------- | |
| def make_map(coords, addresses, order): | |
| m = folium.Map(location=coords[order[0]], zoom_start=12) | |
| path = [] | |
| for i, idx in enumerate(order): | |
| folium.Marker( | |
| coords[idx], | |
| tooltip=f"{i+1}. {addresses[idx]}", | |
| icon=folium.Icon(color="blue") | |
| ).add_to(m) | |
| path.append(coords[idx]) | |
| AntPath(path, color="blue", weight=4).add_to(m) | |
| return m._repr_html_() | |
| # --------------------------------------- | |
| # AI Explanation via GPT | |
| # --------------------------------------- | |
| def generate_ai_explanation(opt_km, naive_km, fuel_saved, cost_saved, time_saved): | |
| prompt = f""" | |
| You are an AI logistics expert. Explain clearly why the optimized route is better. | |
| Optimized distance: {opt_km:.2f} km | |
| Naive distance: {naive_km:.2f} km | |
| Fuel saved: {fuel_saved:.2f} litres | |
| Cost saved: ₹{cost_saved:.2f} | |
| Time saved: {time_saved:.2f} minutes | |
| Write a short, professional explanation suitable for a supply-chain manager at CEVA Logistics. | |
| """ | |
| response = client.chat.completions.create( | |
| model="gpt-4o-mini", | |
| messages=[{"role": "user", "content": prompt}] | |
| ) | |
| return response.choices[0].message.content | |
| # --------------------------------------- | |
| # HTML REPORT GENERATOR | |
| # --------------------------------------- | |
| def generate_report_html(summary_text, ai_text, map_html): | |
| html = f""" | |
| <html> | |
| <head> | |
| <style> | |
| body {{ font-family: Arial; }} | |
| h2 {{ color: {PRIMARY_COLOR}; }} | |
| </style> | |
| </head> | |
| <body> | |
| <h2>Procelevate AI Route Optimization Report</h2> | |
| <p><b>Generated:</b> {datetime.now().strftime('%Y-%m-%d %H:%M')}</p> | |
| <h3>Summary</h3> | |
| <p>{summary_text}</p> | |
| <h3>AI Explanation</h3> | |
| <p>{ai_text}</p> | |
| <h3>Route Map</h3> | |
| {map_html} | |
| </body> | |
| </html> | |
| """ | |
| return html | |
| # --------------------------------------- | |
| # PART 3 — FULL ENTERPRISE DASHBOARD UI | |
| # --------------------------------------- | |
| def run_route_engine(start, end, stops_text, vehicle_type, fuel_price): | |
| stops = [s.strip() for s in stops_text.split("\n") if s.strip()] | |
| # Build address list | |
| addresses = [start] + stops + [end] | |
| addresses = [a for a in addresses if a.strip()] | |
| if len(addresses) < 2: | |
| return ["❌ Please enter valid addresses."] + [None]*5 | |
| # Geocode all addresses | |
| coords = [] | |
| for a in addresses: | |
| c = get_coordinates(a) | |
| if not c: | |
| return [f"❌ Failed to locate address: {a}"] + [None]*5 | |
| coords.append(c) | |
| # Build OSRM matrices | |
| dist_m, time_m = build_matrices(coords) | |
| if dist_m is None: | |
| return ["❌ OSRM routing failed. Try different locations."] + [None]*5 | |
| # Convert meters → km, seconds → minutes | |
| dist_km = dist_m / 1000 | |
| time_min = time_m / 60 | |
| # Optimized route | |
| opt_order = optimize_route(dist_m) | |
| if opt_order is None: | |
| return ["❌ Optimization failed."] + [None]*5 | |
| # Naive + reverse orders | |
| naive_order = list(range(len(addresses))) | |
| rev_order = list(reversed(naive_order)) | |
| def compute_totals(order): | |
| total_km = 0 | |
| total_min = 0 | |
| for i in range(len(order)-1): | |
| total_km += dist_km[order[i]][order[i+1]] | |
| total_min += time_min[order[i]][order[i+1]] | |
| return total_km, total_min | |
| opt_km, opt_min = compute_totals(opt_order) | |
| naive_km, naive_min = compute_totals(naive_order) | |
| rev_km, rev_min = compute_totals(rev_order) | |
| mileage = VEHICLE_MILEAGE[vehicle_type] | |
| # KPI calculations | |
| opt_fuel, opt_fuel_cost, opt_total_cost = calculate_kpis(opt_km, mileage, fuel_price) | |
| naive_fuel, naive_fuel_cost, naive_total_cost = calculate_kpis(naive_km, mileage, fuel_price) | |
| rev_fuel, rev_fuel_cost, rev_total_cost = calculate_kpis(rev_km, mileage, fuel_price) | |
| # Savings | |
| fuel_saved = naive_fuel - opt_fuel | |
| cost_saved = naive_total_cost - opt_total_cost | |
| time_saved = naive_min - opt_min | |
| # AI Explanation | |
| ai_text = generate_ai_explanation(opt_km, naive_km, fuel_saved, cost_saved, time_saved) | |
| # Optimized route map | |
| map_html = make_map(coords, addresses, opt_order) | |
| # Summary panel text | |
| summary_text = f""" | |
| <b>Optimized Distance:</b> {opt_km:.2f} km<br> | |
| <b>Optimized Time:</b> {opt_min:.2f} minutes<br> | |
| <b>Fuel Needed:</b> {opt_fuel:.2f} L<br> | |
| <b>Total Cost:</b> ₹{opt_total_cost:.2f}<br> | |
| <b>Efficiency Gain vs Naive:</b> {((naive_km-opt_km)/naive_km)*100:.2f}%<br> | |
| """ | |
| # Comparison table | |
| comp_df = pd.DataFrame({ | |
| "Route Type": ["Optimized", "Naive", "Reverse"], | |
| "Distance (km)": [opt_km, naive_km, rev_km], | |
| "Time (min)": [opt_min, naive_min, rev_min], | |
| "Fuel (L)": [opt_fuel, naive_fuel, rev_fuel], | |
| "Cost (₹)": [opt_total_cost, naive_total_cost, rev_total_cost] | |
| }) | |
| # Data tables | |
| dist_df = pd.DataFrame(dist_km, columns=addresses, index=addresses) | |
| time_df = pd.DataFrame(time_min, columns=addresses, index=addresses) | |
| # Downloadable report | |
| report_html = generate_report_html(summary_text, ai_text, map_html) | |
| b64 = base64.b64encode(report_html.encode()).decode() | |
| download_link = f'<a href="data:text/html;base64,{b64}" download="route_report.html">Download Report</a>' | |
| return ( | |
| summary_text, # TAB 1 | |
| map_html, # TAB 2 | |
| comp_df, # TAB 3 | |
| ai_text, # TAB 4 | |
| dist_df, # TAB 5A | |
| time_df, # TAB 5B | |
| download_link # TAB 6 | |
| ) | |
| # --------------------------------------- | |
| # GRADIO UI LAYOUT — TABS + BRANDING | |
| # --------------------------------------- | |
| with gr.Blocks() as demo: | |
| gr.HTML(f""" | |
| <style> | |
| :root {{ | |
| --primary-color: {PRIMARY_COLOR}; | |
| }} | |
| h1 {{ | |
| color: {PRIMARY_COLOR} !important; | |
| }} | |
| .gradio-container {{ | |
| --primary-hue: 220; | |
| }} | |
| </style> | |
| """) | |
| gr.Markdown(f"<h1 style='color:{PRIMARY_COLOR}'>Procelevate AI Route Optimization Suite</h1>") | |
| # ------------------------- | |
| # Address Inputs | |
| # ------------------------- | |
| with gr.Row(): | |
| start = gr.Textbox(label="From", placeholder="Starting point (e.g., Bangalore Airport)") | |
| end = gr.Textbox(label="To", placeholder="Final destination (e.g., Whitefield)") | |
| stops_text = gr.Textbox( | |
| label="Stops (one per line, optional)", | |
| lines=4, | |
| placeholder="Example:\nMG Road\nBTM Layout\nElectronic City" | |
| ) | |
| # ------------------------- | |
| # Vehicle + Fuel Inputs | |
| # ------------------------- | |
| vehicle_type = gr.Dropdown( | |
| list(VEHICLE_MILEAGE.keys()), | |
| label="Vehicle Type", | |
| value="Truck (32 ft)" | |
| ) | |
| fuel_price = gr.Number( | |
| label="Fuel Price (₹ per litre)", | |
| value=91 | |
| ) | |
| # ------------------------- | |
| # Submit Button | |
| # ------------------------- | |
| submit_btn = gr.Button("Optimize Route", variant="primary") | |
| # TAB STRUCTURE | |
| with gr.Tabs(): | |
| with gr.Tab("Overview"): | |
| overview_out = gr.HTML() | |
| with gr.Tab("Optimized Route"): | |
| map_out = gr.HTML() | |
| with gr.Tab("Route Comparison"): | |
| comp_out = gr.Dataframe() | |
| with gr.Tab("AI Explanation"): | |
| ai_out = gr.Markdown() | |
| with gr.Tab("Data Tables"): | |
| dist_out = gr.Dataframe(label="Distance (km)") | |
| time_out = gr.Dataframe(label="Time (min)") | |
| with gr.Tab("Download Report"): | |
| report_out = gr.HTML() | |
| # CONNECT BUTTON | |
| submit_btn.click( | |
| fn=run_route_engine, | |
| inputs=[start, end, stops_text, vehicle_type, fuel_price], | |
| outputs=[overview_out, map_out, comp_out, ai_out, dist_out, time_out, report_out] | |
| ) | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| ssr_mode=False | |
| ) | |