File size: 7,971 Bytes
b42db06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# train_model.py (UPDATED TO USE USER DATA)

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
import xgboost as xgb
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
import joblib
import sys
import sqlite3
import json
import os
from datetime import datetime

print("--- Starting model training process with user data ---")

# --- Step 1: Load User Data from Database ---
try:
    os.makedirs('db', exist_ok=True)
    conn = sqlite3.connect('db/training_data.db')
    cursor = conn.cursor()
    
    # Get user training data
    cursor.execute('''
        SELECT route_id, addresses, coordinates, predicted_eta_minutes, 
               actual_eta_minutes, start_time, sensor_data, route_metadata
        FROM training_data 
        WHERE actual_eta_minutes IS NOT NULL
    ''')
    
    user_data = cursor.fetchall()
    conn.close()
    
    print(f"βœ… Step 1a: Found {len(user_data)} user training records")
    
    if len(user_data) < 10:
        print("⚠️  Warning: Less than 10 user records found. Using fallback dataset.")
        # Fallback to original dataset
        try:
            dataset_path = os.path.join(os.path.dirname(__file__), 'dataset', 'delhivery_data.csv')
            df = pd.read_csv(dataset_path)
            print(f"βœ… Step 1b: Successfully loaded fallback 'delhivery_data.csv'. Shape: {df.shape}")
        except FileNotFoundError:
            print("❌ FATAL ERROR: No user data and fallback dataset not found.")
            sys.exit()
    else:
        # Convert user data to DataFrame
        user_records = []
        for record in user_data:
            route_id, addresses, coordinates, predicted_eta, actual_eta, start_time, sensor_data, route_metadata = record
            
            try:
                # Parse JSON data
                addresses_list = json.loads(addresses)
                coordinates_list = json.loads(coordinates)
                route_meta = json.loads(route_metadata)
                
                # Calculate features
                num_stops = len(addresses_list)
                total_distance = route_meta.get('total_distance_km', 0)
                ors_duration = route_meta.get('ors_duration_minutes', predicted_eta)
                
                user_records.append({
                    'actual_duration_minutes': actual_eta,
                    'ors_duration_minutes': ors_duration,
                    'total_distance_km': total_distance,
                    'num_stops': num_stops,
                    'start_time': start_time,
                    'route_id': route_id
                })
            except (json.JSONDecodeError, KeyError) as e:
                print(f"⚠️  Skipping invalid record {route_id}: {e}")
                continue
        
        df = pd.DataFrame(user_records)
        print(f"βœ… Step 1b: Created user data DataFrame. Shape: {df.shape}")
        
        # If we have user data, also try to load fallback for more training data
        try:
            fallback_df = pd.read_csv('/home/tejas/Projects/Delivery_route_optimize/delivery-route-app/backend/dataset/delhivery_data.csv')
            print(f"βœ… Step 1c: Also loaded fallback dataset. Shape: {fallback_df.shape}")
            # Combine datasets
            df = pd.concat([df, fallback_df], ignore_index=True)
            print(f"βœ… Step 1d: Combined datasets. Final shape: {df.shape}")
        except FileNotFoundError:
            print("ℹ️  No fallback dataset found, using only user data")
            
except Exception as e:
    print(f"❌ Error loading user data: {e}")
    print("Falling back to original dataset...")
    try:
        df = pd.read_csv('/home/tejas/Projects/Delivery_route_optimize/delivery-route-app/backend/dataset/delhivery_data.csv')
        print(f"βœ… Step 1a: Successfully loaded fallback 'delhivery_data.csv'. Shape: {df.shape}")
    except FileNotFoundError:
        print("❌ FATAL ERROR: No data available for training.")
        sys.exit()

# --- Step 2: Clean and Map Columns ---
# Check if we have user data format or fallback format
if 'actual_duration_minutes' in df.columns:
    # User data format
    required_columns = ['actual_duration_minutes', 'ors_duration_minutes', 'total_distance_km', 'num_stops', 'start_time']
    print("βœ… Step 2a: Using user data format")
else:
    # Fallback format
    required_columns = ['actual_time', 'osrm_time', 'osrm_distance', 'od_start_time']
    print("βœ… Step 2a: Using fallback data format")

if not all(col in df.columns for col in required_columns):
    print(f"❌ FATAL ERROR: One or more required columns are missing. Found: {df.columns.tolist()}")
    sys.exit()

initial_rows = len(df)
df = df[required_columns].dropna()
print(f"βœ… Step 2b: Dropped rows with missing values. Rows before: {initial_rows}, Rows after: {len(df)}")
if len(df) == 0:
    print("❌ FATAL ERROR: No valid data remaining after removing missing values.")
    sys.exit()

# Rename columns if using fallback format
if 'actual_time' in df.columns:
    df = df.rename(columns={
        'actual_time': 'actual_duration_minutes',
        'osrm_time': 'ors_duration_minutes',
        'osrm_distance': 'total_distance_km',
        'od_start_time': 'start_time'
    })
    df['num_stops'] = np.random.randint(2, 8, df.shape[0])

initial_rows = len(df)
df = df[(df['actual_duration_minutes'] > 0) & (df['ors_duration_minutes'] > 0) & (df['total_distance_km'] > 0)]
print(f"βœ… Step 2c: Performed final data cleaning. Rows before: {initial_rows}, Rows after: {len(df)}")
if len(df) == 0:
    print("❌ FATAL ERROR: No valid data remaining after final cleaning.")
    sys.exit()

# --- Step 3: Feature Engineering ---
# --- THIS IS THE CRUCIAL FIX ---
# Add errors='coerce' to turn any bad dates into NaT (Not a Time)
initial_rows = len(df)
df['start_time'] = pd.to_datetime(df['start_time'], errors='coerce')
# Now, drop any rows that had a bad date
df = df.dropna(subset=['start_time'])
print(f"βœ… Step 3a: Converted dates, ignoring errors. Rows before: {initial_rows}, Rows after: {len(df)}")
# -----------------------------

df['hour'] = df['start_time'].dt.hour
def get_time_of_day(hour):
    if 6 <= hour < 11: return 'Morning_Rush'
    elif 11 <= hour < 17: return 'Midday'
    elif 17 <= hour < 21: return 'Evening_Rush'
    else: return 'Night'
df['time_of_day'] = df['hour'].apply(get_time_of_day)
df['day_of_week'] = df['start_time'].dt.dayofweek.apply(lambda x: 'Weekday' if x < 5 else 'Weekend')
print("βœ… Step 3b: Feature engineering complete.")

# --- Step 4: Prepare Data for XGBoost ---
features = ['ors_duration_minutes', 'total_distance_km', 'num_stops', 'time_of_day', 'day_of_week']
target = 'actual_duration_minutes'
X = df[features]
y = df[target]
X = pd.get_dummies(X, columns=['time_of_day', 'day_of_week'])
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"βœ… Step 4: Data split into training and testing sets. Training samples: {len(X_train)}")

# --- Step 5: Train the Model ---
model = xgb.XGBRegressor(objective='reg:squarederror', n_estimators=100, learning_rate=0.1, max_depth=5, random_state=42, n_jobs=-1)
print("\n⏳ Step 5: Training the XGBoost model...")
model.fit(X_train, y_train)
print("βœ… Model training complete!")

# --- Step 6: Evaluate and Save ---
predictions = model.predict(X_test)
mae = mean_absolute_error(y_test, predictions)
mse = mean_squared_error(y_test, predictions)
rmse = np.sqrt(mse)
r2 = r2_score(y_test, predictions)

print(f"\nπŸ“Š Step 6: Model Evaluation Metrics:")
print(f"   - Mean Absolute Error (MAE): {mae:.2f} minutes")
print(f"   - Root Mean Sq. Error (RMSE): {rmse:.2f} minutes")
print(f"   - R-Squared Score (RΒ²): {r2:.4f}")
joblib.dump(model, 'eta_prediction_model.pkl')
joblib.dump(list(X_train.columns), 'model_columns.pkl')
print("βœ… Model and columns saved successfully.")
print("\n--- Script finished successfully! ---")