IAMTFRMZA commited on
Commit
38203f1
·
verified ·
1 Parent(s): 17df432

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +124 -162
app.py CHANGED
@@ -1,8 +1,8 @@
1
  import pandas as pd
2
  import gspread
3
  import gradio as gr
4
- import plotly.express as px
5
  from oauth2client.service_account import ServiceAccountCredentials
 
6
 
7
  # ------------------ AUTH ------------------
8
  VALID_USERS = {
@@ -13,146 +13,83 @@ VALID_USERS = {
13
 
14
  # ------------------ GOOGLE SHEET SETUP ------------------
15
  scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
16
- creds = ServiceAccountCredentials.from_json_keyfile_name("tough-star.json", scope)
17
  client = gspread.authorize(creds)
18
- sheet_url = "https://docs.google.com/spreadsheets/d/1bpeFS6yihb6niCavpwjWmVEypaSkGxONGg2jZfKX_sA"
19
-
20
- # ------------------ DATA REFRESH FUNCTION ------------------
21
- def refresh_data():
22
- sheet = client.open_by_url(sheet_url).worksheet("Calls")
23
- data = sheet.get_all_records()
24
- df = pd.DataFrame(data)
25
-
26
- # Timestamp parsing
27
- df['Timestamp'] = pd.to_datetime(df['Timestamp'], dayfirst=True, errors='coerce')
28
- df['Date'] = df['Timestamp'].dt.date.astype(str)
29
- df['Time'] = df['Timestamp'].dt.time
30
-
31
- # Location parsing
32
- location_split = df['Location'].str.split(',', expand=True)
33
- df['Latitude'] = pd.to_numeric(location_split[0], errors='coerce')
34
- df['Longitude'] = pd.to_numeric(location_split[1], errors='coerce')
35
-
36
- # Data cleaning
37
- df = df.dropna(subset=['Date', 'Rep Name', 'Latitude', 'Longitude'])
38
- df = df[(df['Latitude'] != 0) & (df['Longitude'] != 0)]
39
- df = df.sort_values(by=['Rep Name', 'Timestamp'])
40
- df['Time Diff (min)'] = df.groupby(['Rep Name', 'Date'])['Timestamp'].diff().dt.total_seconds().div(60).fillna(0)
41
- df['Visit Order'] = df.groupby(['Rep Name', 'Date']).cumcount() + 1
42
-
43
  return df
44
 
45
- # ------------------ DEALER ESCALATIONS DATA FUNCTION ------------------
46
- def get_dealer_escalations():
47
- dealers_sheet = client.open_by_url(sheet_url).worksheet("Dealers")
48
- dealers_data = dealers_sheet.get_all_records()
49
- dealers_df = pd.DataFrame(dealers_data)
50
-
51
- # Standardize column names (in case of different casing/spacing)
52
- dealers_df.columns = [c.strip() for c in dealers_df.columns]
53
-
54
- # Filter for rows where Escalate Dealer == 'yes' (case-insensitive)
55
- mask = dealers_df['Escalate Dealer'].str.strip() == 'Yes'
56
- filtered_df = dealers_df.loc[mask, [
57
- 'Dealership Name',
58
- 'Rep Name',
59
- 'Escalate Dealer',
60
- 'Escalation Comment'
61
- ]]
62
-
63
- # Optional: Sort by Rep Name and Dealership Name
64
- filtered_df = filtered_df.sort_values(by=['Rep Name', 'Dealership Name'])
65
-
66
- # If there are no escalations, show a friendly empty DataFrame
67
- if filtered_df.empty:
68
- filtered_df = pd.DataFrame(
69
- [["No dealer escalations found.", "", "", ""]],
70
- columns=['Dealership Name', 'Rep Name', 'Escalate Dealer', 'Escalation Comment']
71
- )
72
-
73
- return filtered_df
74
-
75
- # ------------------ DASHBOARD FUNCTIONS ------------------
76
  def generate_summary(date_str):
77
- df = refresh_data()
78
- all_reps = sorted(df['Rep Name'].dropna().unique())
79
- day_df = df[df['Date'] == date_str]
80
- active = day_df.groupby('Rep Name').size().reset_index(name='Total Visits')
81
- active_list = active['Rep Name'].tolist()
 
 
 
 
 
 
 
 
82
  inactive_list = [rep for rep in all_reps if rep not in active_list]
83
  inactive_df = pd.DataFrame({'Inactive Reps': inactive_list})
84
- return active, inactive_df
85
-
86
- def get_reps(date_str):
87
- df = refresh_data()
88
- reps = df[df['Date'] == date_str]['Rep Name'].dropna().unique()
89
- return gr.update(choices=sorted(reps))
90
-
91
- def show_map(date_str, rep):
92
- df = refresh_data()
93
- subset = df[(df['Date'] == date_str) & (df['Rep Name'] == rep)]
94
- if subset.empty:
95
- return "No valid data", None
96
-
97
- subset = subset.sort_values(by='Timestamp').copy()
98
- subset['Visit Order'] = range(1, len(subset) + 1)
99
- center_lat = subset['Latitude'].mean()
100
- center_lon = subset['Longitude'].mean()
101
-
102
- fig = px.line_mapbox(
103
- subset,
104
- lat="Latitude", lon="Longitude",
105
- hover_name="Dealership Name",
106
- hover_data={"Time": True, "Time Diff (min)": True, "Visit Order": True},
107
- height=700,
108
- zoom=13,
109
- center={"lat": center_lat, "lon": center_lon}
110
- )
111
-
112
- scatter = px.scatter_mapbox(
113
- subset,
114
- lat="Latitude", lon="Longitude",
115
- color="Visit Order",
116
- hover_name="Dealership Name",
117
- hover_data=["Time", "Time Diff (min)"],
118
- color_continuous_scale="Bluered"
119
- )
120
- for trace in scatter.data:
121
- fig.add_trace(trace)
122
-
123
- fig.add_trace(px.scatter_mapbox(
124
- pd.DataFrame([subset.iloc[0]]),
125
- lat="Latitude", lon="Longitude",
126
- text=["Start"], color_discrete_sequence=["green"]).data[0])
127
- fig.add_trace(px.scatter_mapbox(
128
- pd.DataFrame([subset.iloc[-1]]),
129
- lat="Latitude", lon="Longitude",
130
- text=["End"], color_discrete_sequence=["red"]).data[0])
131
-
132
- fig.update_layout(mapbox_style="open-street-map", title=f"📍 {rep}'s Route on {date_str}")
133
-
134
- table = subset[[
135
- 'Visit Order', 'Dealership Name', 'Time', 'Time Diff (min)',
136
- 'Type of call', 'Sales or service'
137
- ]].rename(columns={
138
- 'Dealership Name': '🧭 Dealer',
139
- 'Time': '🕒 Time',
140
- 'Time Diff (min)': '⏱️ Time Spent',
141
- 'Type of call': '📞 Call Type',
142
- 'Sales or service': '💼 Category'
143
  })
144
-
145
- total_time = round(table['⏱️ Time Spent'].sum(), 2)
146
- summary_row = pd.DataFrame([{
147
- 'Visit Order': '',
148
- '🧭 Dealer': f"🧮 Total Time: {total_time} min",
149
- '🕒 Time': '',
150
- '⏱️ Time Spent': '',
151
- '📞 Call Type': '',
152
- '💼 Category': ''
153
- }])
154
- table = pd.concat([table, summary_row], ignore_index=True)
155
- return table, fig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
  # ------------------ GRADIO APP ------------------
158
  with gr.Blocks() as app:
@@ -165,42 +102,67 @@ with gr.Blocks() as app:
165
  login_msg = gr.Markdown("")
166
 
167
  with gr.Column(visible=False) as main_ui:
168
- gr.Markdown("## 🗂️ Carfind Rep Tracker")
169
- df_initial = refresh_data()
170
- unique_dates = sorted(df_initial['Date'].unique(), reverse=True)
 
 
171
 
 
172
  with gr.Tab("📊 Summary"):
173
  date_summary = gr.Dropdown(label="Select Date", choices=unique_dates)
174
- active_table = gr.Dataframe(label="✅ Active Reps (with total visits)")
175
- inactive_table = gr.Dataframe(label="⚠️ Inactive Reps")
176
- date_summary.change(fn=generate_summary, inputs=date_summary, outputs=[active_table, inactive_table])
177
-
178
- with gr.Tab("👤 KAM's"):
179
- with gr.Row():
180
- with gr.Column(scale=1):
181
- date_picker = gr.Dropdown(label="Select Date", choices=unique_dates)
182
- rep_picker = gr.Dropdown(label="Select Rep")
183
- btn = gr.Button("Show Route")
184
- with gr.Column(scale=2):
185
- table = gr.Dataframe(label="Call Table")
186
-
187
- map_plot = gr.Plot(label="Map")
188
- date_picker.change(fn=get_reps, inputs=date_picker, outputs=rep_picker)
189
- btn.click(fn=show_map, inputs=[date_picker, rep_picker], outputs=[table, map_plot])
190
-
191
- with gr.Tab("🚨 Dealer Escalations"):
192
- gr.Markdown("### 🚨 Dealer Escalations (Only showing escalated dealers)")
193
- escalations_df = gr.Dataframe(value=get_dealer_escalations, label="Escalated Dealers", interactive=False)
194
- refresh_btn = gr.Button("🔄 Refresh Escalations")
195
-
196
- # Refreshes the dataframe on button click
197
- refresh_btn.click(fn=get_dealer_escalations, outputs=escalations_df)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
 
199
  def do_login(user, pw):
200
  if VALID_USERS.get(user) == pw:
201
  return gr.update(visible=False), gr.update(visible=True), ""
202
  else:
203
- return gr.update(visible=True), gr.update(visible=False), "❌ Invalid email or password."
204
 
205
  login_btn.click(fn=do_login, inputs=[email, password], outputs=[login_ui, main_ui, login_msg])
206
 
 
1
  import pandas as pd
2
  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 = {
 
13
 
14
  # ------------------ GOOGLE SHEET SETUP ------------------
15
  scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
16
+ creds = ServiceAccountCredentials.from_json_keyfile_name("credentials.json", scope)
17
  client = gspread.authorize(creds)
18
+ sheet_file = client.open("userAccess")
19
+
20
+ # ------------------ HELPERS ------------------
21
+ def load_tab(sheet_name):
22
+ return pd.DataFrame(sheet_file.worksheet(sheet_name).get_all_records())
23
+
24
+ # ------------------ FIELD SALES ------------------
25
+ def load_field_sales():
26
+ df = load_tab("Field Sales")
27
+ df['Date'] = pd.to_datetime(df.get("Date", datetime.today()), errors='coerce')
28
+ df = df.dropna(subset=["Date"])
29
+ df['DateStr'] = df['Date'].dt.date.astype(str)
30
+ df['Order Value'] = pd.to_numeric(df.get("Order Value", 0), errors='coerce').fillna(0)
 
 
 
 
 
 
 
 
 
 
 
 
31
  return df
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  def generate_summary(date_str):
34
+ df = load_field_sales()
35
+ all_reps = sorted(df['Rep'].dropna().unique())
36
+ day_df = df[df['DateStr'] == date_str]
37
+
38
+ total_visits = day_df.groupby("Rep").size().reset_index(name="Total Visits")
39
+ current = day_df[day_df["Current/Prospect Custor"] == "Current"]
40
+ prospect = day_df[day_df["Current/Prospect Custor"] == "Prospect"]
41
+ breakdown = pd.DataFrame({
42
+ "Rep": all_reps,
43
+ "Current": [len(current[current["Rep"] == rep]) for rep in all_reps],
44
+ "Prospect": [len(prospect[prospect["Rep"] == rep]) for rep in all_reps]
45
+ })
46
+ active_list = total_visits['Rep'].tolist()
47
  inactive_list = [rep for rep in all_reps if rep not in active_list]
48
  inactive_df = pd.DataFrame({'Inactive Reps': inactive_list})
49
+ return total_visits, breakdown, inactive_df
50
+
51
+ def get_order_summary(date_str):
52
+ df = load_field_sales()
53
+ day_df = df[df['DateStr'] == date_str]
54
+ rep_group = day_df.groupby("Rep").agg({
55
+ "Order Received": lambda x: (x == "Yes").sum(),
56
+ "Order Value": "sum"
57
+ }).reset_index().rename(columns={
58
+ "Order Received": "Orders Received",
59
+ "Order Value": "Total Order Value"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  })
61
+ return rep_group.sort_values(by="Total Order Value", ascending=False)
62
+
63
+ def get_escalations():
64
+ df = load_field_sales()
65
+ flagged = df[df["Customer Type"].str.contains("Second", na=False)]
66
+ return flagged if not flagged.empty else pd.DataFrame([["No second-hand dealerships flagged."]], columns=["Status"])
67
+
68
+ # ------------------ TELESALeS ------------------
69
+ def get_telesales_summary():
70
+ df = load_tab("TeleSales")
71
+ df["Date"] = pd.to_datetime(df.get("Date", datetime.today()), errors='coerce')
72
+ df["DateStr"] = df["Date"].dt.date.astype(str)
73
+ return df.groupby(["Rep Email", "DateStr"]).size().reset_index(name="Calls Made")
74
+
75
+ # ------------------ OEM VISITS ------------------
76
+ def get_oem_summary():
77
+ df = load_tab("OEM Visit")
78
+ df["Date"] = pd.to_datetime(df.get("Date", datetime.today()), errors='coerce')
79
+ df["DateStr"] = df["Date"].dt.date.astype(str)
80
+ return df.groupby(["Rep", "DateStr"]).size().reset_index(name="OEM Visits")
81
+
82
+ # ------------------ CUSTOMER REQUESTS ------------------
83
+ def get_requests():
84
+ return load_tab("Customer Requests")
85
+
86
+ # ------------------ CUSTOMER LISTINGS ------------------
87
+ def get_listings():
88
+ return load_tab("CustomerListings")
89
+
90
+ # ------------------ USERS ------------------
91
+ def get_users():
92
+ return load_tab("Users")
93
 
94
  # ------------------ GRADIO APP ------------------
95
  with gr.Blocks() as app:
 
102
  login_msg = gr.Markdown("")
103
 
104
  with gr.Column(visible=False) as main_ui:
105
+ gr.Markdown("## 🗂️ CarMat Dashboard")
106
+
107
+ # preload dates for field sales
108
+ df_initial = load_field_sales()
109
+ unique_dates = sorted(df_initial["DateStr"].unique(), reverse=True)
110
 
111
+ # --- Summary Tab ---
112
  with gr.Tab("📊 Summary"):
113
  date_summary = gr.Dropdown(label="Select Date", choices=unique_dates)
114
+ visits = gr.Dataframe(label="✅ Total Visits")
115
+ breakdown = gr.Dataframe(label="🏷️ Current vs. Prospect")
116
+ inactive = gr.Dataframe(label="⚠️ Inactive Reps")
117
+ date_summary.change(fn=generate_summary, inputs=date_summary, outputs=[visits, breakdown, inactive])
118
+
119
+ # --- Orders Tab ---
120
+ with gr.Tab("📦 Orders"):
121
+ order_date = gr.Dropdown(label="Select Date", choices=unique_dates)
122
+ order_table = gr.Dataframe(label="💰 Orders Summary")
123
+ order_date.change(fn=get_order_summary, inputs=order_date, outputs=order_table)
124
+
125
+ # --- Escalations ---
126
+ with gr.Tab("🚨 Escalations"):
127
+ esc_table = gr.Dataframe(value=get_escalations, label="Second-hand Dealerships")
128
+ esc_btn = gr.Button("🔄 Refresh")
129
+ esc_btn.click(fn=get_escalations, outputs=esc_table)
130
+
131
+ # --- TeleSales ---
132
+ with gr.Tab("📞 TeleSales"):
133
+ ts_table = gr.Dataframe(value=get_telesales_summary, label="TeleSales Summary")
134
+ ts_refresh = gr.Button("🔄 Refresh TeleSales")
135
+ ts_refresh.click(fn=get_telesales_summary, outputs=ts_table)
136
+
137
+ # --- OEM Visits ---
138
+ with gr.Tab("🏭 OEM Visits"):
139
+ oem_table = gr.Dataframe(value=get_oem_summary, label="OEM Visit Summary")
140
+ oem_refresh = gr.Button("🔄 Refresh OEM")
141
+ oem_refresh.click(fn=get_oem_summary, outputs=oem_table)
142
+
143
+ # --- Requests ---
144
+ with gr.Tab("📬 Customer Requests"):
145
+ req_table = gr.Dataframe(value=get_requests, label="Customer Requests", interactive=False)
146
+ req_refresh = gr.Button("🔄 Refresh Requests")
147
+ req_refresh.click(fn=get_requests, outputs=req_table)
148
+
149
+ # --- Dealerships ---
150
+ with gr.Tab("📋 Dealership Directory"):
151
+ listings_table = gr.Dataframe(value=get_listings, label="Customer Listings")
152
+ listings_refresh = gr.Button("🔄 Refresh Listings")
153
+ listings_refresh.click(fn=get_listings, outputs=listings_table)
154
+
155
+ # --- Users ---
156
+ with gr.Tab("👤 Users"):
157
+ users_table = gr.Dataframe(value=get_users, label="Users")
158
+ users_refresh = gr.Button("🔄 Refresh Users")
159
+ users_refresh.click(fn=get_users, outputs=users_table)
160
 
161
  def do_login(user, pw):
162
  if VALID_USERS.get(user) == pw:
163
  return gr.update(visible=False), gr.update(visible=True), ""
164
  else:
165
+ return gr.update(visible=True), gr.update(visible=False), "❌ Invalid login."
166
 
167
  login_btn.click(fn=do_login, inputs=[email, password], outputs=[login_ui, main_ui, login_msg])
168