IAMTFRMZA commited on
Commit
36d76ed
ยท
verified ยท
1 Parent(s): 38203f1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +163 -125
app.py CHANGED
@@ -1,8 +1,8 @@
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,83 +13,146 @@ 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,68 +165,43 @@ 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
 
169
- app.launch()
 
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
 
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
  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
 
207
+ app.launch()