IAMTFRMZA commited on
Commit
12477af
Β·
verified Β·
1 Parent(s): 6259813

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +79 -127
app.py CHANGED
@@ -3,6 +3,7 @@ import gspread
3
  import gradio as gr
4
  from oauth2client.service_account import ServiceAccountCredentials
5
  from datetime import datetime
 
6
 
7
  # ------------------ AUTH ------------------
8
  VALID_USERS = {
@@ -25,13 +26,22 @@ def load_tab(sheet_name):
25
  except:
26
  return pd.DataFrame([["⚠️ Could not load sheet."]], columns=["Error"])
27
 
 
 
 
 
 
 
 
 
 
28
  # ------------------ FIELD SALES ------------------
29
  def load_field_sales():
30
  df = load_tab("Field Sales")
31
- if df.empty:
32
- return pd.DataFrame(columns=["Date", "Rep", "Order Value", "Order Received", "Current/Prospect Customer", "Customer Type & Status", "DateStr"])
33
 
34
- df['Date'] = pd.to_datetime(df.get("Date", datetime.today()), errors='coerce')
35
  df = df.dropna(subset=["Date"])
36
  df['DateStr'] = df['Date'].dt.date.astype(str)
37
 
@@ -40,101 +50,86 @@ def load_field_sales():
40
  else:
41
  df["Order Value"] = pd.to_numeric(df["Order Value"], errors="coerce").fillna(0)
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  return df
44
 
 
45
  def generate_summary(date_str):
46
  df = load_field_sales()
47
  if df.empty:
48
- return pd.DataFrame([["No data"]], columns=["Message"]), pd.DataFrame(), pd.DataFrame()
49
 
 
50
  all_reps = sorted(df['Rep'].dropna().unique())
51
- date_str = date_str.strip()
52
- day_df = df[df['DateStr'] == date_str]
53
- total_visits = day_df.groupby("Rep").size().reset_index(name="Total Visits")
54
-
55
  col = "Current/Prospect Customer"
56
- if col not in df.columns:
57
- df[col] = ""
58
 
59
- current = day_df[day_df[col] == "Current"]
60
- prospect = day_df[day_df[col] == "Prospect"]
 
 
61
  breakdown = pd.DataFrame({
62
  "Rep": all_reps,
63
  "Current": [len(current[current["Rep"] == rep]) for rep in all_reps],
64
  "Prospect": [len(prospect[prospect["Rep"] == rep]) for rep in all_reps]
65
  })
 
66
 
67
- active_list = total_visits['Rep'].tolist()
68
- inactive_list = [rep for rep in all_reps if rep not in active_list]
69
- inactive_df = pd.DataFrame({'Inactive Reps': inactive_list})
70
- return total_visits, breakdown, inactive_df
71
-
72
- def get_order_summary(date_str):
73
- df = load_field_sales()
74
- if df.empty:
75
- return pd.DataFrame([["No data"]], columns=["Message"])
76
-
77
- date_str = date_str.strip()
78
- day_df = df[df['DateStr'] == date_str]
79
- if "Order Received" not in df.columns:
80
- df["Order Received"] = ""
81
-
82
- rep_group = day_df.groupby("Rep").agg({
83
  "Order Received": lambda x: (x == "Yes").sum(),
84
- "Order Value": "sum"
85
- }).reset_index().rename(columns={
 
 
86
  "Order Received": "Orders Received",
87
- "Order Value": "Total Order Value"
88
- })
89
- return rep_group.sort_values(by="Total Order Value", ascending=False)
90
-
91
- def get_escalations():
92
- df = load_field_sales()
93
- if df.empty:
94
- return pd.DataFrame([["No data in Field Sales"]], columns=["Message"])
95
-
96
- col = "Customer Type & Status"
97
- if col in df.columns:
98
- flagged = df[df[col].str.contains("Second", na=False)]
99
- return flagged if not flagged.empty else pd.DataFrame([["No second-hand dealerships flagged."]], columns=["Message"])
100
- else:
101
- return pd.DataFrame([["⚠️ Column 'Customer Type & Status' not found."]], columns=["Message"])
102
-
103
- # ------------------ TELESALeS ------------------
104
- def get_telesales_summary():
105
- df = load_tab("TeleSales")
106
- if df.empty or "Rep Email" not in df.columns:
107
- return pd.DataFrame([["No data available"]], columns=["Message"])
108
-
109
- df["Date"] = pd.to_datetime(df.get("Date", datetime.today()), errors='coerce')
110
- df["DateStr"] = df["Date"].dt.date.astype(str)
111
- grouped = df.groupby(["Rep Email", "DateStr"]).size().reset_index(name="Calls Made")
112
- return grouped.rename(columns={"Rep Email": "Rep"})
113
-
114
- # ------------------ OEM VISITS ------------------
115
- def get_oem_summary():
116
- df = load_tab("OEM Visit")
117
- if df.empty or "Rep" not in df.columns:
118
- return pd.DataFrame([["No data available"]], columns=["Message"])
119
 
120
- df["Date"] = pd.to_datetime(df.get("Date", datetime.today()), errors='coerce')
121
- df["DateStr"] = df["Date"].dt.date.astype(str)
122
- return df.groupby(["Rep", "DateStr"]).size().reset_index(name="OEM Visits")
 
123
 
124
- # ------------------ CUSTOMER REQUESTS ------------------
125
- def get_requests():
126
- df = load_tab("Customer Requests")
127
- return df if not df.empty else pd.DataFrame([["No requests yet."]], columns=["Message"])
128
 
129
- # ------------------ CUSTOMER LISTINGS ------------------
130
- def get_listings():
131
- df = load_tab("CustomerListings")
132
- return df if not df.empty else pd.DataFrame([["No listings found."]], columns=["Message"])
133
-
134
- # ------------------ USERS ------------------
135
- def get_users():
136
- df = load_tab("Users")
137
- return df if not df.empty else pd.DataFrame([["No users configured."]], columns=["Message"])
138
 
139
  # ------------------ GRADIO APP ------------------
140
  with gr.Blocks() as app:
@@ -158,55 +153,12 @@ with gr.Blocks() as app:
158
  visits = gr.Dataframe(label="βœ… Total Visits")
159
  breakdown = gr.Dataframe(label="🏷️ Current vs. Prospect")
160
  inactive = gr.Dataframe(label="⚠️ Inactive Reps")
161
- date_summary.change(fn=generate_summary, inputs=date_summary, outputs=[visits, breakdown, inactive])
162
-
163
- # --- Orders Tab ---
164
- with gr.Tab("πŸ“¦ Orders"):
165
- order_date = gr.Dropdown(label="Select Date", choices=unique_dates)
166
- order_table = gr.Dataframe(label="πŸ’° Orders Summary")
167
- order_date.change(fn=get_order_summary, inputs=order_date, outputs=order_table)
168
-
169
- # --- Escalations ---
170
- with gr.Tab("🚨 Escalations"):
171
- esc_table = gr.Dataframe(value=get_escalations, label="Second-hand Dealerships")
172
- esc_btn = gr.Button("πŸ”„ Refresh")
173
- esc_btn.click(fn=get_escalations, outputs=esc_table)
174
-
175
- # --- TeleSales ---
176
- with gr.Tab("πŸ“ž TeleSales"):
177
- ts_table = gr.Dataframe(value=get_telesales_summary, label="TeleSales Summary")
178
- ts_refresh = gr.Button("πŸ”„ Refresh TeleSales")
179
- ts_refresh.click(fn=get_telesales_summary, outputs=ts_table)
180
-
181
- # --- OEM Visits ---
182
- with gr.Tab("🏭 OEM Visits"):
183
- oem_table = gr.Dataframe(value=get_oem_summary, label="OEM Visit Summary")
184
- oem_refresh = gr.Button("πŸ”„ Refresh OEM")
185
- oem_refresh.click(fn=get_oem_summary, outputs=oem_table)
186
-
187
- # --- Requests ---
188
- with gr.Tab("πŸ“¬ Customer Requests"):
189
- req_table = gr.Dataframe(value=get_requests, label="Customer Requests", interactive=False)
190
- req_refresh = gr.Button("πŸ”„ Refresh Requests")
191
- req_refresh.click(fn=get_requests, outputs=req_table)
192
-
193
- # --- Dealerships ---
194
- with gr.Tab("πŸ“‹ Dealership Directory"):
195
- listings_table = gr.Dataframe(value=get_listings, label="Customer Listings")
196
- listings_refresh = gr.Button("πŸ”„ Refresh Listings")
197
- listings_refresh.click(fn=get_listings, outputs=listings_table)
198
-
199
- # --- Users ---
200
- with gr.Tab("πŸ‘€ Users"):
201
- users_table = gr.Dataframe(value=get_users, label="Users")
202
- users_refresh = gr.Button("πŸ”„ Refresh Users")
203
- users_refresh.click(fn=get_users, outputs=users_table)
204
-
205
- # --- Field Sales Raw Data ---
206
- with gr.Tab("🧾 Field Sales Raw Data"):
207
- field_df = gr.Dataframe(value=load_field_sales, label="Full Field Sales Data", interactive=False)
208
- field_btn = gr.Button("πŸ”„ Refresh Field Sales")
209
- field_btn.click(fn=load_field_sales, outputs=field_df)
210
 
211
  def do_login(user, pw):
212
  if VALID_USERS.get(user) == pw:
 
3
  import gradio as gr
4
  from oauth2client.service_account import ServiceAccountCredentials
5
  from datetime import datetime
6
+ from math import radians, cos, sin, asin, sqrt
7
 
8
  # ------------------ AUTH ------------------
9
  VALID_USERS = {
 
26
  except:
27
  return pd.DataFrame([["⚠️ Could not load sheet."]], columns=["Error"])
28
 
29
+ def haversine(coord1, coord2):
30
+ lon1, lat1 = map(radians, map(float, coord1.split(',')[::-1]))
31
+ lon2, lat2 = map(radians, map(float, coord2.split(',')[::-1]))
32
+ dlon = lon2 - lon1
33
+ dlat = lat2 - lat1
34
+ a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
35
+ c = 2 * asin(sqrt(a))
36
+ return 6371 * c
37
+
38
  # ------------------ FIELD SALES ------------------
39
  def load_field_sales():
40
  df = load_tab("Field Sales")
41
+ if df.empty or "Date" not in df.columns:
42
+ return pd.DataFrame()
43
 
44
+ df['Date'] = pd.to_datetime(df['Date'], errors='coerce')
45
  df = df.dropna(subset=["Date"])
46
  df['DateStr'] = df['Date'].dt.date.astype(str)
47
 
 
50
  else:
51
  df["Order Value"] = pd.to_numeric(df["Order Value"], errors="coerce").fillna(0)
52
 
53
+ # Distance calc
54
+ distances = [0]
55
+ for i in range(1, len(df)):
56
+ try:
57
+ prev = df.loc[i-1, 'Location']
58
+ curr = df.loc[i, 'Location']
59
+ if pd.notna(prev) and pd.notna(curr):
60
+ distances.append(round(haversine(prev, curr), 2))
61
+ else:
62
+ distances.append(0)
63
+ except:
64
+ distances.append(0)
65
+ df["Distance Travelled (km)"] = distances
66
+
67
+ return df
68
+
69
+ # ------------------ TELESALES ------------------
70
+ def load_telesales():
71
+ df = load_tab("TeleSales")
72
+ if df.empty or "Rep Email" not in df.columns:
73
+ return pd.DataFrame()
74
+
75
+ df["Date"] = pd.to_datetime(df.get("Date", datetime.today()), errors='coerce')
76
+ df["DateStr"] = df["Date"].dt.date.astype(str)
77
+ return df
78
+
79
+ # ------------------ OEM ------------------
80
+ def load_oem():
81
+ df = load_tab("OEM Visit")
82
+ if df.empty or "Rep" not in df.columns:
83
+ return pd.DataFrame()
84
+ df["Date"] = pd.to_datetime(df.get("Date", datetime.today()), errors='coerce')
85
+ df["DateStr"] = df["Date"].dt.date.astype(str)
86
  return df
87
 
88
+ # ------------------ SUMMARY ------------------
89
  def generate_summary(date_str):
90
  df = load_field_sales()
91
  if df.empty:
92
+ return pd.DataFrame([["No Field Sales data"]], columns=["Message"])*5
93
 
94
+ df_day = df[df['DateStr'] == date_str.strip()]
95
  all_reps = sorted(df['Rep'].dropna().unique())
 
 
 
 
96
  col = "Current/Prospect Customer"
 
 
97
 
98
+ # --- Visits Breakdown
99
+ total_visits = df_day.groupby("Rep").size().reset_index(name="Total Visits")
100
+ current = df_day[df_day[col] == "Current"]
101
+ prospect = df_day[df_day[col] == "Prospect"]
102
  breakdown = pd.DataFrame({
103
  "Rep": all_reps,
104
  "Current": [len(current[current["Rep"] == rep]) for rep in all_reps],
105
  "Prospect": [len(prospect[prospect["Rep"] == rep]) for rep in all_reps]
106
  })
107
+ inactive = pd.DataFrame({'Inactive Reps': [rep for rep in all_reps if rep not in total_visits["Rep"].tolist()]})
108
 
109
+ # --- Field Summary per Rep
110
+ rep_summary = df_day.groupby("Rep").agg({
111
+ "Order Value": "sum",
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  "Order Received": lambda x: (x == "Yes").sum(),
113
+ "Current/Prospect Customer": lambda x: (x == "Current").sum(),
114
+ "Distance Travelled (km)": "sum"
115
+ }).rename(columns={
116
+ "Order Value": "Total Order Value",
117
  "Order Received": "Orders Received",
118
+ "Current/Prospect Customer": "Current Customers",
119
+ "Distance Travelled (km)": "Total Distance (km)"
120
+ }).reset_index()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
+ # --- TeleSales Summary
123
+ df_ts = load_telesales()
124
+ df_ts_day = df_ts[df_ts['DateStr'] == date_str.strip()]
125
+ ts_summary = df_ts_day.groupby("Rep Email").size().reset_index(name="Total Calls Made") if not df_ts_day.empty else pd.DataFrame([["No Telesales"]], columns=["Info"])
126
 
127
+ # --- OEM Summary
128
+ df_oem = load_oem()
129
+ df_oem_day = df_oem[df_oem['DateStr'] == date_str.strip()]
130
+ oem_summary = df_oem_day.groupby("Rep").size().reset_index(name="Total OEM Visits") if not df_oem_day.empty else pd.DataFrame([["No OEM Visits"]], columns=["Info"])
131
 
132
+ return total_visits, breakdown, inactive, rep_summary, ts_summary, oem_summary
 
 
 
 
 
 
 
 
133
 
134
  # ------------------ GRADIO APP ------------------
135
  with gr.Blocks() as app:
 
153
  visits = gr.Dataframe(label="βœ… Total Visits")
154
  breakdown = gr.Dataframe(label="🏷️ Current vs. Prospect")
155
  inactive = gr.Dataframe(label="⚠️ Inactive Reps")
156
+ field_summary = gr.Dataframe(label="πŸš— Field Sales Summary")
157
+ ts_summary = gr.Dataframe(label="πŸ“ž Telesales Summary")
158
+ oem_summary = gr.Dataframe(label="🏭 OEM Visit Summary")
159
+
160
+ date_summary.change(fn=generate_summary, inputs=date_summary,
161
+ outputs=[visits, breakdown, inactive, field_summary, ts_summary, oem_summary])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
  def do_login(user, pw):
164
  if VALID_USERS.get(user) == pw: