mktgtech commited on
Commit
6ef337b
·
verified ·
1 Parent(s): 1c8a2f6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +132 -97
app.py CHANGED
@@ -12,7 +12,7 @@ API_TOKEN = os.getenv("LEADFEEDER_API_TOKEN")
12
  APP_PASSWORD = os.getenv("APP_PASSWORD")
13
 
14
  if not API_TOKEN or not APP_PASSWORD:
15
- raise RuntimeError("Missing required secrets")
16
 
17
  ACCOUNT_ID = "255333" # PREMIUM account
18
  BASE_URL = "https://api.leadfeeder.com"
@@ -64,81 +64,96 @@ def authenticate(pwd):
64
  return True
65
 
66
  # =====================================================
67
- # DATA FETCH (WORKING)
68
  # =====================================================
69
  def fetch_company_data(start_date, end_date):
70
- page, rows = 1, []
71
-
 
72
  while True:
73
- r = requests.get(
74
- f"{BASE_URL}/accounts/{ACCOUNT_ID}/leads",
75
- headers=HEADERS,
76
- params={
77
- "start_date": start_date,
78
- "end_date": end_date,
79
- "page[number]": page,
80
- "page[size]": PAGE_SIZE
81
- },
82
- timeout=30
83
- )
84
- r.raise_for_status()
85
- js = r.json()
86
-
87
- data = js.get("data", [])
88
- if not data:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  break
90
 
91
- loc_map = {
92
- i["id"]: i["attributes"]
93
- for i in js.get("included", [])
94
- if i["type"] == "locations"
95
- }
96
-
97
- for lead in data:
98
- a = lead["attributes"]
99
- loc_id = lead.get("relationships", {}).get("location", {}).get("data", {}).get("id")
100
- loc = loc_map.get(loc_id, {})
101
-
102
- rows.append({
103
- "lead_id": lead.get("id"),
104
- "company_name": a.get("name"),
105
- "website_url": a.get("website_url"),
106
- "phone": a.get("phone"),
107
- "business_id": a.get("business_id"),
108
- "primary_industry": a.get("industry"),
109
- "all_industries": ", ".join(
110
- [i.get("name") for i in a.get("industries", [])]
111
- ) if a.get("industries") else None,
112
- "first_visit_date": a.get("first_visit_date"),
113
- "last_visit_date": a.get("last_visit_date"),
114
- "total_visits": a.get("visits"),
115
- "lead_quality_score": a.get("quality"),
116
- "employee_count": a.get("employee_count"),
117
- "employees_min": a.get("employees_range", {}).get("min")
118
- if a.get("employees_range") else None,
119
- "employees_max": a.get("employees_range", {}).get("max")
120
- if a.get("employees_range") else None,
121
- "revenue": a.get("revenue"),
122
- "assignee": a.get("assignee"),
123
- "emailed_to": a.get("emailed_to"),
124
- "crm_lead_id": a.get("crm_lead_id"),
125
- "crm_organization_id": a.get("crm_organization_id"),
126
- "tags": ", ".join(a.get("tags", [])) if a.get("tags") else None,
127
- "linkedin_url": a.get("linkedin_url"),
128
- "twitter_handle": a.get("twitter_handle"),
129
- "facebook_url": a.get("facebook_url"),
130
- "landing_page_path": landing,
131
- "country": loc.get("country"),
132
- "exit_page_path": exit_page,
133
- "region": loc.get("region"),
134
- "city": loc.get("city"),
135
- "leadfeeder_url": a.get("view_in_leadfeeder"),
136
- })
137
-
138
- page += 1
139
-
140
  df = pd.DataFrame(rows)
141
- df["last_visit_date"] = pd.to_datetime(df["last_visit_date"], errors="coerce")
 
142
  return df
143
 
144
  # =====================================================
@@ -146,6 +161,9 @@ def fetch_company_data(start_date, end_date):
146
  # =====================================================
147
  def load_data_with_preview(start_date, end_date):
148
  df = fetch_company_data(start_date, end_date)
 
 
 
149
  preview = df.head(500)
150
  status = f"✅ Loaded **{len(df):,} companies** (showing first 500 rows)"
151
  return df, preview, status
@@ -154,6 +172,8 @@ def load_data_with_preview(start_date, end_date):
154
  # EXCEL EXPORT
155
  # =====================================================
156
  def export_excel(df):
 
 
157
  path = "/tmp/leadfeeder_company_data.xlsx"
158
  df.to_excel(path, index=False)
159
  return path
@@ -162,6 +182,9 @@ def export_excel(df):
162
  # KPI CARDS
163
  # =====================================================
164
  def build_kpis(df):
 
 
 
165
  return (
166
  len(df),
167
  df["total_visits"].gt(0).sum(),
@@ -175,12 +198,16 @@ def build_kpis(df):
175
  # DASHBOARD ENGINE
176
  # =====================================================
177
  def build_dashboard(df, preset, top_n):
 
 
 
178
  dim, metric, agg = DASHBOARD_PRESETS[preset]
 
179
  if agg == "count":
180
  grouped = df.groupby(dim, dropna=False).size().reset_index(name="value")
181
  else:
182
  grouped = df.groupby(dim, dropna=False)[metric].agg(agg).reset_index(name="value")
183
-
184
  grouped = grouped.sort_values("value", ascending=False).head(top_n)
185
  return px.bar(grouped, x=dim, y="value", template="plotly_white", title=preset)
186
 
@@ -188,26 +215,29 @@ def build_dashboard(df, preset, top_n):
188
  # TREND ENGINE
189
  # =====================================================
190
  def build_trend(df, preset, grain):
 
 
 
191
  metric, agg, segment = TREND_PRESETS[preset]
192
  df = df.dropna(subset=["last_visit_date"]).copy()
193
-
194
  if grain == "Weekly":
195
  df["period"] = df["last_visit_date"].dt.to_period("W").astype(str)
196
  elif grain == "Monthly":
197
  df["period"] = df["last_visit_date"].dt.to_period("M").astype(str)
198
  else:
199
  df["period"] = df["last_visit_date"].dt.date
200
-
201
  if agg == "count":
202
  df["_v"] = df[metric].notna().astype(int)
203
  val = "_v"
204
  else:
205
  val = metric
206
-
207
  if segment:
208
  ts = df.groupby(["period", segment])[val].agg(agg).reset_index()
209
  return px.line(ts, x="period", y=val, color=segment, template="plotly_white", title=preset)
210
-
211
  ts = df.groupby("period")[val].agg(agg).reset_index()
212
  return px.line(ts, x="period", y=val, markers=True, template="plotly_white", title=preset)
213
 
@@ -216,30 +246,32 @@ def build_trend(df, preset, grain):
216
  # =====================================================
217
  with gr.Blocks(title="Leadfeeder Company-Level Analytics") as demo:
218
  gr.Markdown("## 🔐 Login")
219
- pwd = gr.Textbox(type="password")
220
  gr.Button("Login").click(authenticate, pwd, None)
221
 
222
  df_state = gr.State()
223
  status = gr.Markdown()
224
 
225
  with gr.Tabs():
226
-
227
  # ================= DATA =================
228
  with gr.Tab("📋 Data"):
229
- start = gr.Textbox(value=(date.today() - timedelta(days=30)).isoformat())
230
- end = gr.Textbox(value=date.today().isoformat())
231
-
232
- load_btn = gr.Button("Load Data")
233
- preview_table = gr.Dataframe(interactive=True)
234
- download_btn = gr.Button("⬇️ Download Excel")
235
- download_file = gr.File()
236
-
 
 
 
 
237
  load_btn.click(
238
  load_data_with_preview,
239
  [start, end],
240
  [df_state, preview_table, status]
241
  )
242
-
243
  download_btn.click(export_excel, df_state, download_file)
244
 
245
  # ================= DASHBOARD =================
@@ -251,21 +283,24 @@ with gr.Blocks(title="Leadfeeder Company-Level Analytics") as demo:
251
  k4 = gr.Number(label="Avg Quality")
252
  k5 = gr.Number(label="% CRM Coverage")
253
  k6 = gr.Number(label="% LinkedIn Coverage")
254
-
255
  gr.Button("Refresh KPIs").click(build_kpis, df_state, [k1,k2,k3,k4,k5,k6])
256
-
257
- preset = gr.Dropdown(choices=list(DASHBOARD_PRESETS.keys()))
258
- top_n = gr.Slider(5, 50, value=10, step=5)
 
 
259
  chart = gr.Plot()
260
-
261
- gr.Button("Build View").click(build_dashboard, [df_state, preset, top_n], chart)
262
 
263
  # ================= TRENDS =================
264
  with gr.Tab("📈 Trends"):
265
- trend_view = gr.Dropdown(choices=list(TREND_PRESETS.keys()))
266
- grain = gr.Radio(["Daily", "Weekly", "Monthly"], value="Daily")
 
 
267
  plot = gr.Plot()
 
268
 
269
- gr.Button("Show Trend").click(build_trend, [df_state, trend_view, grain], plot)
270
-
271
- demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)
 
12
  APP_PASSWORD = os.getenv("APP_PASSWORD")
13
 
14
  if not API_TOKEN or not APP_PASSWORD:
15
+ raise RuntimeError("Missing required secrets: LEADFEEDER_API_TOKEN or APP_PASSWORD")
16
 
17
  ACCOUNT_ID = "255333" # PREMIUM account
18
  BASE_URL = "https://api.leadfeeder.com"
 
64
  return True
65
 
66
  # =====================================================
67
+ # DATA FETCH (FIXED)
68
  # =====================================================
69
  def fetch_company_data(start_date, end_date):
70
+ page = 1
71
+ rows = []
72
+
73
  while True:
74
+ try:
75
+ r = requests.get(
76
+ f"{BASE_URL}/accounts/{ACCOUNT_ID}/leads",
77
+ headers=HEADERS,
78
+ params={
79
+ "start_date": start_date,
80
+ "end_date": end_date,
81
+ "page[number]": page,
82
+ "page[size]": PAGE_SIZE
83
+ },
84
+ timeout=30
85
+ )
86
+ r.raise_for_status()
87
+ js = r.json()
88
+ data = js.get("data", [])
89
+
90
+ if not data:
91
+ break
92
+
93
+ # Create a lookup map for included location objects
94
+ loc_map = {
95
+ i["id"]: i["attributes"]
96
+ for i in js.get("included", [])
97
+ if i["type"] == "locations"
98
+ }
99
+
100
+ for lead in data:
101
+ a = lead["attributes"]
102
+
103
+ # Get location ID safely
104
+ loc_id = lead.get("relationships", {}).get("location", {}).get("data", {}).get("id")
105
+ loc = loc_map.get(loc_id, {})
106
+
107
+ rows.append({
108
+ "lead_id": lead.get("id"),
109
+ "company_name": a.get("name"),
110
+ "website_url": a.get("website_url"),
111
+ "phone": a.get("phone"),
112
+ "business_id": a.get("business_id"),
113
+ "primary_industry": a.get("industry"),
114
+ "all_industries": ", ".join(
115
+ [i.get("name") for i in a.get("industries", [])]
116
+ ) if a.get("industries") else None,
117
+ "first_visit_date": a.get("first_visit_date"),
118
+ "last_visit_date": a.get("last_visit_date"),
119
+ "total_visits": a.get("visits"),
120
+ "lead_quality_score": a.get("quality"),
121
+ "employee_count": a.get("employee_count"),
122
+ "employees_min": a.get("employees_range", {}).get("min")
123
+ if a.get("employees_range") else None,
124
+ "employees_max": a.get("employees_range", {}).get("max")
125
+ if a.get("employees_range") else None,
126
+ "revenue": a.get("revenue"),
127
+ "assignee": a.get("assignee"),
128
+ "emailed_to": a.get("emailed_to"),
129
+ "crm_lead_id": a.get("crm_lead_id"),
130
+ "crm_organization_id": a.get("crm_organization_id"),
131
+ "tags": ", ".join(a.get("tags", [])) if a.get("tags") else None,
132
+ "linkedin_url": a.get("linkedin_url"),
133
+ "twitter_handle": a.get("twitter_handle"),
134
+ "facebook_url": a.get("facebook_url"),
135
+
136
+ # --- FIXED SECTION START ---
137
+ # Using .get() to avoid NameError.
138
+ "landing_page_path": a.get("landing_page_path"),
139
+ "exit_page_path": a.get("exit_page_path"),
140
+ # --- FIXED SECTION END ---
141
+
142
+ "country": loc.get("country"),
143
+ "region": loc.get("region"),
144
+ "city": loc.get("city"),
145
+ "leadfeeder_url": a.get("view_in_leadfeeder"),
146
+ })
147
+
148
+ page += 1
149
+
150
+ except requests.exceptions.RequestException as e:
151
+ print(f"Error fetching page {page}: {e}")
152
  break
153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  df = pd.DataFrame(rows)
155
+ if not df.empty:
156
+ df["last_visit_date"] = pd.to_datetime(df["last_visit_date"], errors="coerce")
157
  return df
158
 
159
  # =====================================================
 
161
  # =====================================================
162
  def load_data_with_preview(start_date, end_date):
163
  df = fetch_company_data(start_date, end_date)
164
+ if df.empty:
165
+ return df, pd.DataFrame(), "⚠️ No data found for this date range."
166
+
167
  preview = df.head(500)
168
  status = f"✅ Loaded **{len(df):,} companies** (showing first 500 rows)"
169
  return df, preview, status
 
172
  # EXCEL EXPORT
173
  # =====================================================
174
  def export_excel(df):
175
+ if df is None or df.empty:
176
+ return None
177
  path = "/tmp/leadfeeder_company_data.xlsx"
178
  df.to_excel(path, index=False)
179
  return path
 
182
  # KPI CARDS
183
  # =====================================================
184
  def build_kpis(df):
185
+ if df is None or df.empty:
186
+ return 0, 0, 0, 0, 0, 0
187
+
188
  return (
189
  len(df),
190
  df["total_visits"].gt(0).sum(),
 
198
  # DASHBOARD ENGINE
199
  # =====================================================
200
  def build_dashboard(df, preset, top_n):
201
+ if df is None or df.empty:
202
+ return px.bar(title="No Data Loaded")
203
+
204
  dim, metric, agg = DASHBOARD_PRESETS[preset]
205
+
206
  if agg == "count":
207
  grouped = df.groupby(dim, dropna=False).size().reset_index(name="value")
208
  else:
209
  grouped = df.groupby(dim, dropna=False)[metric].agg(agg).reset_index(name="value")
210
+
211
  grouped = grouped.sort_values("value", ascending=False).head(top_n)
212
  return px.bar(grouped, x=dim, y="value", template="plotly_white", title=preset)
213
 
 
215
  # TREND ENGINE
216
  # =====================================================
217
  def build_trend(df, preset, grain):
218
+ if df is None or df.empty:
219
+ return px.line(title="No Data Loaded")
220
+
221
  metric, agg, segment = TREND_PRESETS[preset]
222
  df = df.dropna(subset=["last_visit_date"]).copy()
223
+
224
  if grain == "Weekly":
225
  df["period"] = df["last_visit_date"].dt.to_period("W").astype(str)
226
  elif grain == "Monthly":
227
  df["period"] = df["last_visit_date"].dt.to_period("M").astype(str)
228
  else:
229
  df["period"] = df["last_visit_date"].dt.date
230
+
231
  if agg == "count":
232
  df["_v"] = df[metric].notna().astype(int)
233
  val = "_v"
234
  else:
235
  val = metric
236
+
237
  if segment:
238
  ts = df.groupby(["period", segment])[val].agg(agg).reset_index()
239
  return px.line(ts, x="period", y=val, color=segment, template="plotly_white", title=preset)
240
+
241
  ts = df.groupby("period")[val].agg(agg).reset_index()
242
  return px.line(ts, x="period", y=val, markers=True, template="plotly_white", title=preset)
243
 
 
246
  # =====================================================
247
  with gr.Blocks(title="Leadfeeder Company-Level Analytics") as demo:
248
  gr.Markdown("## 🔐 Login")
249
+ pwd = gr.Textbox(type="password", label="Enter App Password")
250
  gr.Button("Login").click(authenticate, pwd, None)
251
 
252
  df_state = gr.State()
253
  status = gr.Markdown()
254
 
255
  with gr.Tabs():
 
256
  # ================= DATA =================
257
  with gr.Tab("📋 Data"):
258
+ with gr.Row():
259
+ start = gr.Textbox(label="Start Date", value=(date.today() - timedelta(days=30)).isoformat())
260
+ end = gr.Textbox(label="End Date", value=date.today().isoformat())
261
+
262
+ load_btn = gr.Button("Load Data", variant="primary")
263
+
264
+ with gr.Row():
265
+ download_btn = gr.Button("⬇️ Download Excel")
266
+ download_file = gr.File(label="Download")
267
+
268
+ preview_table = gr.Dataframe(interactive=True, label="Data Preview")
269
+
270
  load_btn.click(
271
  load_data_with_preview,
272
  [start, end],
273
  [df_state, preview_table, status]
274
  )
 
275
  download_btn.click(export_excel, df_state, download_file)
276
 
277
  # ================= DASHBOARD =================
 
283
  k4 = gr.Number(label="Avg Quality")
284
  k5 = gr.Number(label="% CRM Coverage")
285
  k6 = gr.Number(label="% LinkedIn Coverage")
286
+
287
  gr.Button("Refresh KPIs").click(build_kpis, df_state, [k1,k2,k3,k4,k5,k6])
288
+
289
+ with gr.Row():
290
+ preset = gr.Dropdown(choices=list(DASHBOARD_PRESETS.keys()), label="Select Chart")
291
+ top_n = gr.Slider(5, 50, value=10, step=5, label="Top N")
292
+
293
  chart = gr.Plot()
294
+ gr.Button("Build View", variant="primary").click(build_dashboard, [df_state, preset, top_n], chart)
 
295
 
296
  # ================= TRENDS =================
297
  with gr.Tab("📈 Trends"):
298
+ with gr.Row():
299
+ trend_view = gr.Dropdown(choices=list(TREND_PRESETS.keys()), label="Select Trend")
300
+ grain = gr.Radio(["Daily", "Weekly", "Monthly"], value="Daily", label="Granularity")
301
+
302
  plot = gr.Plot()
303
+ gr.Button("Show Trend", variant="primary").click(build_trend, [df_state, trend_view, grain], plot)
304
 
305
+ if __name__ == "__main__":
306
+ demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)