Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -5,102 +5,80 @@ from oauth2client.service_account import ServiceAccountCredentials
|
|
5 |
from datetime import datetime, timedelta
|
6 |
|
7 |
# -------------------- AUTH --------------------
|
8 |
-
scope = [
|
9 |
-
|
10 |
-
"https://www.googleapis.com/auth/drive"
|
11 |
-
]
|
12 |
-
creds = ServiceAccountCredentials.from_json_keyfile_name(
|
13 |
-
"deep-mile-461309-t8-0e90103411e0.json",
|
14 |
-
scope
|
15 |
-
)
|
16 |
client = gspread.authorize(creds)
|
17 |
sheet_url = "https://docs.google.com/spreadsheets/d/1if4KoVQvw5ZbhknfdZbzMkcTiPfsD6bz9V3a1th-bwQ"
|
18 |
|
19 |
# -------------------- UTILS --------------------
|
20 |
-
def
|
21 |
-
|
22 |
-
return [h.strip().title() for h in raw_header]
|
23 |
-
|
24 |
-
def load_sheet(sheet_name: str) -> pd.DataFrame:
|
25 |
-
ws = client.open_by_url(sheet_url).worksheet(sheet_name)
|
26 |
-
all_vals = ws.get_all_values()
|
27 |
-
if not all_vals or len(all_vals) < 2:
|
28 |
-
return pd.DataFrame()
|
29 |
-
header = normalize_header(all_vals[0])
|
30 |
-
rows = all_vals[1:]
|
31 |
-
df = pd.DataFrame(rows, columns=header)
|
32 |
return df
|
33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
def get_current_week_range():
|
35 |
today = datetime.now()
|
36 |
start = today - timedelta(days=today.weekday())
|
37 |
end = start + timedelta(days=6)
|
38 |
return start.date(), end.date()
|
39 |
|
40 |
-
|
41 |
-
|
42 |
-
df = load_sheet("Calls")
|
43 |
-
if "Call Date" not in df:
|
44 |
-
return pd.DataFrame([{"Error": "Missing 'Call Date' column"}])
|
45 |
-
df["Call Date"] = pd.to_datetime(df["Call Date"], errors="coerce").dt.date
|
46 |
start, end = get_current_week_range()
|
47 |
-
filtered = df[(df[
|
48 |
if rep:
|
49 |
-
filtered = filtered[filtered[
|
50 |
return filtered
|
51 |
|
52 |
-
def
|
53 |
-
df = load_sheet("Calls")
|
54 |
-
if "Call Date" not in df:
|
55 |
-
return pd.DataFrame([{"Error": "Missing 'Call Date' column"}])
|
56 |
try:
|
57 |
target = datetime(int(y), int(m), int(d)).date()
|
58 |
except:
|
59 |
return pd.DataFrame([{"Error": "Invalid date input"}])
|
60 |
-
df[
|
61 |
-
filtered = df[df[
|
62 |
if rep:
|
63 |
-
filtered = filtered[filtered[
|
64 |
return filtered
|
65 |
|
66 |
-
# --------------------
|
67 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
df = load_sheet("Appointments")
|
69 |
-
if "Appointment Date" not in df:
|
70 |
return pd.DataFrame([{"Error": "Missing 'Appointment Date' column"}])
|
71 |
-
|
72 |
-
start, end = get_current_week_range()
|
73 |
-
filtered = df[(df["Appointment Date"] >= start) & (df["Appointment Date"] <= end)]
|
74 |
-
if rep:
|
75 |
-
filtered = filtered[filtered["Rep"] == rep]
|
76 |
-
return filtered
|
77 |
|
78 |
-
def
|
79 |
-
|
80 |
-
if "
|
81 |
-
return
|
82 |
-
return
|
83 |
-
.size() \
|
84 |
-
.reset_index(name="Appointment Count")
|
85 |
|
86 |
def search_appointments_by_date(y, m, d, rep):
|
87 |
df = load_sheet("Appointments")
|
88 |
-
if "Appointment Date" not in df:
|
89 |
return pd.DataFrame([{"Error": "Missing 'Appointment Date' column"}])
|
90 |
-
|
91 |
-
target = datetime(int(y), int(m), int(d)).date()
|
92 |
-
except:
|
93 |
-
return pd.DataFrame([{"Error": "Invalid date input"}])
|
94 |
-
df["Appointment Date"] = pd.to_datetime(df["Appointment Date"], errors="coerce").dt.date
|
95 |
-
filtered = df[df["Appointment Date"] == target]
|
96 |
-
if rep:
|
97 |
-
filtered = filtered[filtered["Rep"] == rep]
|
98 |
-
return filtered
|
99 |
|
100 |
-
# -------------------- LEADS --------------------
|
101 |
def get_leads_detail():
|
102 |
df = load_sheet("AllocatedLeads")
|
103 |
-
|
|
|
|
|
104 |
return pd.DataFrame([{"Error": "Missing 'Assigned Rep' or 'Company Name' column"}])
|
105 |
return df
|
106 |
|
@@ -108,99 +86,87 @@ def get_leads_summary():
|
|
108 |
df = get_leads_detail()
|
109 |
if "Error" in df.columns:
|
110 |
return df
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
|
115 |
-
# -------------------- INSIGHTS --------------------
|
116 |
def compute_insights():
|
117 |
calls = get_calls()
|
118 |
-
|
119 |
leads = get_leads_detail()
|
120 |
|
121 |
-
|
122 |
-
|
|
|
123 |
|
124 |
-
|
125 |
-
{"Metric": "Most Calls This Week", "Rep":
|
126 |
-
{"Metric": "Most Appointments This Week", "Rep":
|
127 |
-
{"Metric": "Most Leads Allocated", "Rep":
|
128 |
])
|
|
|
129 |
|
130 |
# -------------------- DROPDOWN OPTIONS --------------------
|
131 |
def rep_options(sheet_name, rep_col):
|
132 |
df = load_sheet(sheet_name)
|
133 |
-
|
|
|
|
|
134 |
|
135 |
# -------------------- UI LAYOUT --------------------
|
136 |
with gr.Blocks(title="Graffiti Admin Dashboard") as app:
|
137 |
gr.Markdown("# π Graffiti Admin Dashboard")
|
138 |
|
139 |
-
# Calls Report
|
140 |
with gr.Tab("Calls Report"):
|
141 |
-
rep_calls = gr.Dropdown("Optional Rep Filter",
|
142 |
-
|
143 |
-
allow_custom_value=True)
|
144 |
-
calls_btn = gr.Button("Load Current Week Calls")
|
145 |
calls_table = gr.Dataframe()
|
146 |
calls_btn.click(fn=get_calls, inputs=rep_calls, outputs=calls_table)
|
147 |
|
148 |
gr.Markdown("### π Search Calls by Specific Date")
|
149 |
-
y1, m1, d1 = gr.Textbox("Year"), gr.Textbox("Month"), gr.Textbox("Day")
|
150 |
-
rep1 = gr.Dropdown("Optional Rep Filter",
|
151 |
-
|
152 |
-
allow_custom_value=True)
|
153 |
-
calls_date_btn = gr.Button("Search Calls by Date")
|
154 |
calls_date_table = gr.Dataframe()
|
155 |
-
calls_date_btn.click(fn=search_calls_by_date,
|
156 |
-
inputs=[y1, m1, d1, rep1],
|
157 |
-
outputs=calls_date_table)
|
158 |
|
159 |
-
# Appointments Report
|
160 |
with gr.Tab("Appointments Report"):
|
161 |
-
rep_appt = gr.Dropdown("Optional Rep Filter",
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
fn=lambda rep: (appointments_summary(rep), appointments_detail(rep)),
|
169 |
inputs=rep_appt,
|
170 |
-
outputs=[
|
171 |
)
|
172 |
|
173 |
gr.Markdown("### π Search Appointments by Specific Date")
|
174 |
-
y2, m2, d2 = gr.Textbox("Year"), gr.Textbox("Month"), gr.Textbox("Day")
|
175 |
-
rep2 = gr.Dropdown("Optional Rep Filter",
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
)
|
189 |
-
date_btn.click(fn=by_date,
|
190 |
-
inputs=[y2, m2, d2, rep2],
|
191 |
-
outputs=[date_sum, date_det])
|
192 |
-
|
193 |
-
# Appointed Leads
|
194 |
with gr.Tab("Appointed Leads"):
|
195 |
-
leads_btn
|
196 |
-
|
197 |
-
|
198 |
leads_btn.click(
|
199 |
fn=lambda: (get_leads_summary(), get_leads_detail()),
|
200 |
-
outputs=[
|
201 |
)
|
202 |
|
203 |
-
# Insights
|
204 |
with gr.Tab("Insights"):
|
205 |
insights_btn = gr.Button("Generate Insights")
|
206 |
insights_tbl = gr.Dataframe()
|
|
|
5 |
from datetime import datetime, timedelta
|
6 |
|
7 |
# -------------------- AUTH --------------------
|
8 |
+
scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
|
9 |
+
creds = ServiceAccountCredentials.from_json_keyfile_name("deep-mile-461309-t8-0e90103411e0.json", scope)
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
client = gspread.authorize(creds)
|
11 |
sheet_url = "https://docs.google.com/spreadsheets/d/1if4KoVQvw5ZbhknfdZbzMkcTiPfsD6bz9V3a1th-bwQ"
|
12 |
|
13 |
# -------------------- UTILS --------------------
|
14 |
+
def normalize_columns(df):
|
15 |
+
df.columns = df.columns.str.strip().str.title() # e.g. βappointment date β β βAppointment Dateβ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
return df
|
17 |
|
18 |
+
def load_sheet(sheet_name):
|
19 |
+
try:
|
20 |
+
sheet = client.open_by_url(sheet_url).worksheet(sheet_name)
|
21 |
+
df = pd.DataFrame(sheet.get_all_records())
|
22 |
+
df = normalize_columns(df)
|
23 |
+
return df
|
24 |
+
except Exception as e:
|
25 |
+
return pd.DataFrame([{"Error": str(e)}])
|
26 |
+
|
27 |
def get_current_week_range():
|
28 |
today = datetime.now()
|
29 |
start = today - timedelta(days=today.weekday())
|
30 |
end = start + timedelta(days=6)
|
31 |
return start.date(), end.date()
|
32 |
|
33 |
+
def filter_week(df, date_column, rep_column=None, rep=None):
|
34 |
+
df[date_column] = pd.to_datetime(df[date_column], errors='coerce').dt.date
|
|
|
|
|
|
|
|
|
35 |
start, end = get_current_week_range()
|
36 |
+
filtered = df[(df[date_column] >= start) & (df[date_column] <= end)]
|
37 |
if rep:
|
38 |
+
filtered = filtered[filtered[rep_column] == rep]
|
39 |
return filtered
|
40 |
|
41 |
+
def filter_date(df, date_column, rep_column, y, m, d, rep):
|
|
|
|
|
|
|
42 |
try:
|
43 |
target = datetime(int(y), int(m), int(d)).date()
|
44 |
except:
|
45 |
return pd.DataFrame([{"Error": "Invalid date input"}])
|
46 |
+
df[date_column] = pd.to_datetime(df[date_column], errors='coerce').dt.date
|
47 |
+
filtered = df[df[date_column] == target]
|
48 |
if rep:
|
49 |
+
filtered = filtered[filtered[rep_column] == rep]
|
50 |
return filtered
|
51 |
|
52 |
+
# -------------------- REPORT FUNCTIONS --------------------
|
53 |
+
def get_calls(rep=None):
|
54 |
+
df = load_sheet("Calls")
|
55 |
+
if "Call Date" not in df.columns:
|
56 |
+
return pd.DataFrame([{"Error": "Missing 'Call Date' column"}])
|
57 |
+
return filter_week(df, "Call Date", "Rep", rep)
|
58 |
+
|
59 |
+
def get_appointments(rep=None):
|
60 |
df = load_sheet("Appointments")
|
61 |
+
if "Appointment Date" not in df.columns:
|
62 |
return pd.DataFrame([{"Error": "Missing 'Appointment Date' column"}])
|
63 |
+
return filter_week(df, "Appointment Date", "Rep", rep)
|
|
|
|
|
|
|
|
|
|
|
64 |
|
65 |
+
def search_calls_by_date(y, m, d, rep):
|
66 |
+
df = load_sheet("Calls")
|
67 |
+
if "Call Date" not in df.columns:
|
68 |
+
return pd.DataFrame([{"Error": "Missing 'Call Date' column"}])
|
69 |
+
return filter_date(df, "Call Date", "Rep", y, m, d, rep)
|
|
|
|
|
70 |
|
71 |
def search_appointments_by_date(y, m, d, rep):
|
72 |
df = load_sheet("Appointments")
|
73 |
+
if "Appointment Date" not in df.columns:
|
74 |
return pd.DataFrame([{"Error": "Missing 'Appointment Date' column"}])
|
75 |
+
return filter_date(df, "Appointment Date", "Rep", y, m, d, rep)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
76 |
|
|
|
77 |
def get_leads_detail():
|
78 |
df = load_sheet("AllocatedLeads")
|
79 |
+
# normalize expected names if necessary:
|
80 |
+
df = df.rename(columns={"Assigned Rep": "Assigned Rep", "Company Name": "Company Name"})
|
81 |
+
if "Assigned Rep" not in df.columns or "Company Name" not in df.columns:
|
82 |
return pd.DataFrame([{"Error": "Missing 'Assigned Rep' or 'Company Name' column"}])
|
83 |
return df
|
84 |
|
|
|
86 |
df = get_leads_detail()
|
87 |
if "Error" in df.columns:
|
88 |
return df
|
89 |
+
# count number of leads per rep
|
90 |
+
summary = df.groupby("Assigned Rep").size().reset_index(name="Leads Count")
|
91 |
+
return summary
|
92 |
|
93 |
+
# -------------------- INSIGHTS (Top Performers) --------------------
|
94 |
def compute_insights():
|
95 |
calls = get_calls()
|
96 |
+
appts = get_appointments()
|
97 |
leads = get_leads_detail()
|
98 |
|
99 |
+
top_calls = calls.groupby("Rep").size().idxmax() if not calls.empty else "N/A"
|
100 |
+
top_appts = appts.groupby("Rep").size().idxmax() if not appts.empty else "N/A"
|
101 |
+
top_leads = leads.groupby("Assigned Rep").size().idxmax() if "Assigned Rep" in leads.columns else "N/A"
|
102 |
|
103 |
+
insights = pd.DataFrame([
|
104 |
+
{"Metric": "Most Calls This Week", "Rep": top_calls},
|
105 |
+
{"Metric": "Most Appointments This Week", "Rep": top_appts},
|
106 |
+
{"Metric": "Most Leads Allocated", "Rep": top_leads},
|
107 |
])
|
108 |
+
return insights
|
109 |
|
110 |
# -------------------- DROPDOWN OPTIONS --------------------
|
111 |
def rep_options(sheet_name, rep_col):
|
112 |
df = load_sheet(sheet_name)
|
113 |
+
if rep_col in df.columns:
|
114 |
+
return sorted(df[rep_col].dropna().unique().tolist())
|
115 |
+
return []
|
116 |
|
117 |
# -------------------- UI LAYOUT --------------------
|
118 |
with gr.Blocks(title="Graffiti Admin Dashboard") as app:
|
119 |
gr.Markdown("# π Graffiti Admin Dashboard")
|
120 |
|
|
|
121 |
with gr.Tab("Calls Report"):
|
122 |
+
rep_calls = gr.Dropdown(label="Optional Rep Filter", choices=rep_options("Calls", "Rep"), allow_custom_value=True)
|
123 |
+
calls_btn = gr.Button("Load Current Week Calls")
|
|
|
|
|
124 |
calls_table = gr.Dataframe()
|
125 |
calls_btn.click(fn=get_calls, inputs=rep_calls, outputs=calls_table)
|
126 |
|
127 |
gr.Markdown("### π Search Calls by Specific Date")
|
128 |
+
y1, m1, d1 = gr.Textbox(label="Year"), gr.Textbox(label="Month"), gr.Textbox(label="Day")
|
129 |
+
rep1 = gr.Dropdown(label="Optional Rep Filter", choices=rep_options("Calls", "Rep"), allow_custom_value=True)
|
130 |
+
calls_date_btn = gr.Button("Search Calls by Date")
|
|
|
|
|
131 |
calls_date_table = gr.Dataframe()
|
132 |
+
calls_date_btn.click(fn=search_calls_by_date, inputs=[y1, m1, d1, rep1], outputs=calls_date_table)
|
|
|
|
|
133 |
|
|
|
134 |
with gr.Tab("Appointments Report"):
|
135 |
+
rep_appt = gr.Dropdown(label="Optional Rep Filter", choices=rep_options("Appointments", "Rep"), allow_custom_value=True)
|
136 |
+
appt_btn = gr.Button("Load Current Week Appointments")
|
137 |
+
appt_summary = gr.Dataframe(label="π Weekly Appointments Summary by Rep")
|
138 |
+
appt_table = gr.Dataframe()
|
139 |
+
appt_btn.click(
|
140 |
+
fn=lambda rep: (get_appointments(rep).groupby("Rep").size().reset_index(name="Count"),
|
141 |
+
get_appointments(rep)),
|
|
|
142 |
inputs=rep_appt,
|
143 |
+
outputs=[appt_summary, appt_table]
|
144 |
)
|
145 |
|
146 |
gr.Markdown("### π Search Appointments by Specific Date")
|
147 |
+
y2, m2, d2 = gr.Textbox(label="Year"), gr.Textbox(label="Month"), gr.Textbox(label="Day")
|
148 |
+
rep2 = gr.Dropdown(label="Optional Rep Filter", choices=rep_options("Appointments", "Rep"), allow_custom_value=True)
|
149 |
+
appt_date_btn = gr.Button("Search Appointments by Date")
|
150 |
+
appt_date_summary = gr.Dataframe(label="π Appointments Summary for Date by Rep")
|
151 |
+
appt_date_table = gr.Dataframe()
|
152 |
+
appt_date_btn.click(
|
153 |
+
fn=lambda y,m,d,rep: (
|
154 |
+
search_appointments_by_date(y,m,d,rep).groupby("Rep").size().reset_index(name="Count"),
|
155 |
+
search_appointments_by_date(y,m,d,rep)
|
156 |
+
),
|
157 |
+
inputs=[y2, m2, d2, rep2],
|
158 |
+
outputs=[appt_date_summary, appt_date_table]
|
159 |
+
)
|
160 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
161 |
with gr.Tab("Appointed Leads"):
|
162 |
+
leads_btn = gr.Button("View Appointed Leads")
|
163 |
+
leads_summary= gr.Dataframe(label="π Leads Count by Rep")
|
164 |
+
leads_detail = gr.Dataframe(label="π Detailed Leads")
|
165 |
leads_btn.click(
|
166 |
fn=lambda: (get_leads_summary(), get_leads_detail()),
|
167 |
+
outputs=[leads_summary, leads_detail]
|
168 |
)
|
169 |
|
|
|
170 |
with gr.Tab("Insights"):
|
171 |
insights_btn = gr.Button("Generate Insights")
|
172 |
insights_tbl = gr.Dataframe()
|