bestpedro commited on
Commit
c74c3b3
·
verified ·
1 Parent(s): e4b374c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +155 -28
app.py CHANGED
@@ -14,20 +14,16 @@ import os
14
  from openai import AzureOpenAI
15
  import base64
16
 
17
-
18
- # st.page_link("report.py", label="Home", icon="🏠")
19
- # st.page_link("pages/page_1.py", label="Page 1", icon="1️⃣")
20
- # st.page_link("pages/page_2.py", label="Page 2", icon="2️⃣", disabled=True)
21
-
22
  ACCOUNT_ID = "act_416207949073936"
23
  PAGE_ID = "63257509478"
24
  OPENAI_API = os.getenv("OPENAI_API")
25
  ACCESS_TOKEN = os.getenv("ACCESS_TOKEN")
26
  BIG_DATASET = None
27
 
28
- print(ACCESS_TOKEN)
29
  ANALYSIS_TYPE = {
30
  "OUTCOME_SALES": "ROAS",
 
 
31
  }
32
 
33
  API_BASE = 'https://bestever-vision.openai.azure.com/'
@@ -112,7 +108,7 @@ def get_ads(adset_id):
112
  url = f"{adset_id}/insights"
113
  params = {
114
  "date_preset": "last_90d",
115
- "fields": "ad_name,ad_id,impressions,spend,video_play_actions,video_p25_watched_actions,video_p50_watched_actions,video_p75_watched_actions,video_p100_watched_actions,video_play_curve_actions,purchase_roas",
116
  "breakdowns": "age,gender",
117
  "limit": 1000,
118
  "level": "ad",
@@ -130,6 +126,48 @@ def save_image_from_url(url, filename):
130
  return True
131
  return False
132
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  def get_creative_assets(ad_id):
134
  # checking if the asset already exists
135
  if os.path.exists(f'assets/{ad_id}.png') or os.path.exists(f'assets/{ad_id}.mp4') or os.path.exists(f'assets/{ad_id}.jpg'):
@@ -141,7 +179,6 @@ def get_creative_assets(ad_id):
141
  }
142
  creative = call_graph_api(url, params)["creative"]
143
  saved = False
144
- print("-" * 10)
145
  if "video_id" in creative:
146
  # download video
147
  video_id = creative["video_id"]
@@ -155,6 +192,7 @@ def get_creative_assets(ad_id):
155
  if len(ext) > 4:
156
  ext = "mp4"
157
  saved = save_image_from_url(video_source, os.path.join("assets", f'{ad_id}.{ext}'))
 
158
 
159
  elif "image_url" in creative:
160
  image_url = creative["image_url"]
@@ -178,6 +216,7 @@ def get_creative_assets(ad_id):
178
  if len(ext) > 4:
179
  ext = "png"
180
  saved = save_image_from_url(video_url, os.path.join("assets", f'{ad_id}.{ext}'))
 
181
  elif "image" in media:
182
  image_url = media["image"]["src"]
183
  ext = image_url.split("?")[0].split(".")[-1]
@@ -212,22 +251,34 @@ def top_n_ads(df, n=5):
212
  if os.path.exists(f'assets/{ad_id}.png'):
213
  image_paths.append(f'assets/{ad_id}.png')
214
  elif os.path.exists(f'assets/{ad_id}.mp4'):
215
- image_paths.append(f'assets/{ad_id}.mp4')
216
  elif os.path.exists(f'assets/{ad_id}.jpg'):
217
  image_paths.append(f'assets/{ad_id}.jpg')
218
  return image_paths
219
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
 
221
- def perform_analysis(df, objective):
222
  # - TS to CTR ratio analysis
223
- # - ROAS analysis (I will see the better metric here to use)
224
  # - Video drop off analysis
225
  if ANALYSIS_TYPE[objective] == "ROAS":
226
- # 3 analysis:
227
- # general
228
- # male
229
- # female
230
-
231
  df_general = df.groupby(["ad_id"]).sum()
232
  df_general = df_general.reset_index()
233
  df_general["relative_roas"] = df_general["purchase_roas"] / df_general["spend"]
@@ -277,6 +328,53 @@ def perform_analysis(df, objective):
277
  "Female": female_output,
278
  }
279
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  def format_adsets(campaign_id):
281
  st_campaigns.empty()
282
  adsets = get_adsets(campaign_id)
@@ -308,23 +406,40 @@ def format_ads(adset_id):
308
  for col in video_cols:
309
  if col in df_ads.columns:
310
  df_ads[col] = df_ads[col].apply(lambda x: float(x[0].get("value", 0)) if isinstance(x, list) else 0)
 
 
 
 
 
 
 
 
 
 
 
311
 
312
  if "purchase_roas" in df_ads.columns:
313
  df_ads["purchase_roas"] = df_ads["purchase_roas"].apply(lambda x: float(x[0].get("value", 0)) if isinstance(x, list) else 0)
 
 
314
 
315
  if BIG_DATASET is None:
316
  BIG_DATASET = df_ads
317
  else:
318
  BIG_DATASET = pd.concat([BIG_DATASET, df_ads])
319
- BIG_DATASET.to_csv("big_dataset.csv")
320
  with st_ads.container():
 
321
  with st.expander("See analysis", expanded=False):
322
  analysis = st.empty()
323
 
324
  for i, ad in enumerate(df_ads["ad_id"].unique()):
325
  get_creative_assets(ad)
326
  ad_name = df_ads[df_ads["ad_id"] == ad]["ad_name"].values[0]
327
- with st.popover(ad_name):
 
 
 
 
328
  tab1, tab2, tab3 = st.tabs(["Creative", "Analytics", "Video Analysis"])
329
  df_tmp = df_ads[df_ads["ad_id"] == ad]
330
  with tab2:
@@ -363,7 +478,6 @@ def format_ads(adset_id):
363
 
364
  if "purchase_roas" in df_tmp.columns:
365
  df_roas = df_tmp.groupby(options)[["spend","purchase_roas"]].sum().reset_index().sort_values("purchase_roas", ascending=False)
366
- print(df_roas)
367
  values = [str(v) for v in df_tmp[options].values]
368
  fig = go.Figure(data=[
369
  go.Bar(name='ROAS', x=values, y=df_roas["purchase_roas"]),
@@ -377,7 +491,6 @@ def format_ads(adset_id):
377
  if "video_play_actions" in df_tmp.columns:
378
  values = df_ads[["ad_id","video_play_actions","video_p25_watched_actions","video_p50_watched_actions","video_p75_watched_actions","video_p100_watched_actions"]].groupby("ad_id").get_group(ad).sum().values[1:]
379
  labels = ["Total video plays","Video plays until 25%","Video plays until 50%","Video plays until 75%","Video plays until 100%"]
380
- print(values)
381
  if values[0] > 0:
382
  st.plotly_chart(create_video_plays_funnel(values, labels), use_container_width=True)
383
  with tab1:
@@ -389,13 +502,27 @@ def format_ads(adset_id):
389
  st.image(f'assets/{ad}.jpg', caption='Creative', use_column_width=True)
390
 
391
  with analysis.container():
392
- report = perform_analysis(df_tmp, "OUTCOME_SALES")
393
- tabs = st.tabs(report.keys())
394
- tabs_names = list(report.keys())
395
- for i, tab in enumerate(tabs):
396
- with tab:
397
- st.multiselect("", report[tabs_names[i]]["keywords"], report[tabs_names[i]]["keywords"], key=f"{ad}_{i}")
398
- st.write(report[tabs_names[i]]["insights"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
 
400
  def create_video_plays_funnel(funnel_data, funnel_title):
401
  fig = go.Figure(go.Funnel(
@@ -413,7 +540,6 @@ if not st.session_state["initiated"]:
413
  st.session_state["initiated"] = True
414
  with st_campaigns.container():
415
  st.title("Campaigns")
416
- print(get_campaigns(ACCOUNT_ID))
417
  for c in (get_campaigns(ACCOUNT_ID))["data"]:
418
  with st.popover(c["campaign_name"]):
419
  st.markdown("**Impressions**: " + str(c["impressions"]))
@@ -425,3 +551,4 @@ if not st.session_state["initiated"]:
425
  on_click=format_adsets,
426
  kwargs={"campaign_id": c["campaign_id"]},
427
  )
 
 
14
  from openai import AzureOpenAI
15
  import base64
16
 
 
 
 
 
 
17
  ACCOUNT_ID = "act_416207949073936"
18
  PAGE_ID = "63257509478"
19
  OPENAI_API = os.getenv("OPENAI_API")
20
  ACCESS_TOKEN = os.getenv("ACCESS_TOKEN")
21
  BIG_DATASET = None
22
 
 
23
  ANALYSIS_TYPE = {
24
  "OUTCOME_SALES": "ROAS",
25
+ "OUTCOME_AWARENESS": "ENGAGEMENT",
26
+ "OUTCOME_LEADS": "ENGAGEMENT"
27
  }
28
 
29
  API_BASE = 'https://bestever-vision.openai.azure.com/'
 
108
  url = f"{adset_id}/insights"
109
  params = {
110
  "date_preset": "last_90d",
111
+ "fields": "ad_name,ad_id,impressions,spend,video_play_actions,video_p25_watched_actions,video_p50_watched_actions,video_p75_watched_actions,video_p100_watched_actions,video_play_curve_actions,purchase_roas,cost_per_action_type,objective",
112
  "breakdowns": "age,gender",
113
  "limit": 1000,
114
  "level": "ad",
 
126
  return True
127
  return False
128
 
129
+
130
+ def extract_specific_frame(video_path, frame_position, output_image):
131
+ # Open the video file
132
+ cap = cv2.VideoCapture(video_path)
133
+
134
+ if not cap.isOpened():
135
+ print("Error opening video file")
136
+ return
137
+
138
+ # Get the total number of frames
139
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
140
+
141
+ # Calculate the frame index based on the position
142
+ if frame_position == 'middle':
143
+ frame_index = total_frames // 2
144
+ elif frame_position == 'last':
145
+ frame_index = total_frames - 1
146
+ else: # 'first' or any other input defaults to the first frame
147
+ frame_index = 0
148
+
149
+ # Set the current frame position
150
+ cap.set(cv2.CAP_PROP_POS_FRAMES, frame_index)
151
+
152
+ # Read the frame
153
+ ret, frame = cap.read()
154
+ if ret:
155
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
156
+ frame = Image.fromarray(frame)
157
+ frame.save(output_image, "JPEG")
158
+ else:
159
+ print(f"Error reading the {frame_position} frame")
160
+
161
+ # Release the video capture object
162
+ cap.release()
163
+
164
+ def split_video_in_frames(video_path):
165
+ output_path = video_path.split(".")[0]
166
+ extract_specific_frame(video_path, 'first', output_path + "_first.jpg")
167
+ extract_specific_frame(video_path, 'middle', output_path + "_middle.jpg")
168
+ extract_specific_frame(video_path, 'last', output_path + "_last.jpg")
169
+
170
+
171
  def get_creative_assets(ad_id):
172
  # checking if the asset already exists
173
  if os.path.exists(f'assets/{ad_id}.png') or os.path.exists(f'assets/{ad_id}.mp4') or os.path.exists(f'assets/{ad_id}.jpg'):
 
179
  }
180
  creative = call_graph_api(url, params)["creative"]
181
  saved = False
 
182
  if "video_id" in creative:
183
  # download video
184
  video_id = creative["video_id"]
 
192
  if len(ext) > 4:
193
  ext = "mp4"
194
  saved = save_image_from_url(video_source, os.path.join("assets", f'{ad_id}.{ext}'))
195
+ split_video_in_frames(os.path.join("assets", f'{ad_id}.{ext}'))
196
 
197
  elif "image_url" in creative:
198
  image_url = creative["image_url"]
 
216
  if len(ext) > 4:
217
  ext = "png"
218
  saved = save_image_from_url(video_url, os.path.join("assets", f'{ad_id}.{ext}'))
219
+ split_video_in_frames(os.path.join("assets", f'{ad_id}.{ext}'))
220
  elif "image" in media:
221
  image_url = media["image"]["src"]
222
  ext = image_url.split("?")[0].split(".")[-1]
 
251
  if os.path.exists(f'assets/{ad_id}.png'):
252
  image_paths.append(f'assets/{ad_id}.png')
253
  elif os.path.exists(f'assets/{ad_id}.mp4'):
254
+ image_paths.append(f'assets/{ad_id}_first.jpg')
255
  elif os.path.exists(f'assets/{ad_id}.jpg'):
256
  image_paths.append(f'assets/{ad_id}.jpg')
257
  return image_paths
258
 
259
+ def video_dropoff_analysis(df):
260
+ if "video_play_actions" not in df.columns:
261
+ return "There is not enough data to generate insights about video dropoff."
262
+
263
+ df_general = df.groupby(["ad_id"]).sum()
264
+ df_general = df_general.reset_index()
265
+ df_general = df_general[df_general["video_play_actions"] > 0]
266
+
267
+ if df_general.shape[0] < 2:
268
+ return "There is not enough data to generate insights about video dropoff."
269
+
270
+ df_general["p100"] = df_general["video_p100_watched_actions"] / df_general["video_play_actions"]
271
+ df_general = df_general.sort_values("p100", ascending=False)
272
+ image_paths = top_n_ads(df_general)
273
+ image_paths = [path for path in image_paths if path.endswith(".mp4")]
274
+
275
+ response = call_gpt_vision(client, image_paths, f"You are given a set of the most performative videos. Your task is to evaluate and anylise these videos, getting features like type of shoot, lightinig, colors, motion, etc, and generate a paragraph explaning what makes a good video. I will also provide a list of video plays in different stages of the video. The main idea is to understand what makes people spend more time on the video. Please, try to be technical and generate insights that can be use to future videos. Dropoff stages: 25%, 50%, 75%, 100%. Dataset: {df.head(5)}")
276
+ return response.choices[0].message.content
277
 
278
+ def performance_analysis(df, objective):
279
  # - TS to CTR ratio analysis
 
280
  # - Video drop off analysis
281
  if ANALYSIS_TYPE[objective] == "ROAS":
 
 
 
 
 
282
  df_general = df.groupby(["ad_id"]).sum()
283
  df_general = df_general.reset_index()
284
  df_general["relative_roas"] = df_general["purchase_roas"] / df_general["spend"]
 
328
  "Female": female_output,
329
  }
330
 
331
+ elif ANALYSIS_TYPE[objective] == "ENGAGEMENT":
332
+ df_general = df.groupby(["ad_id"]).sum()
333
+ df_general = df_general.reset_index()
334
+ df_general = df_general.sort_values("cost_per_engagement", ascending=True)
335
+
336
+ image_paths = top_n_ads(df_general)
337
+ response = call_gpt_vision(client, image_paths, "You are a marketing analyst and your task is to find common features between the ads that presented more engagement. You are given the top 5 most perfomative ads, and we expect you to return 5 keywords and its explanation that defines what makes a good ad that show an excellent engagement. Return it as a list of 5 concepts and its explanation, using the provided ads as example. Try to use nice categories to describe the features (you can use some names like `minimalist design`, `Clear message`, etc). Also, pay attention if the ads are mostly images or videos, this is important to say. The output MUST contain one concept per line. For each like, follow the structure: <concept>:<explanation>.")
338
+ image_winner_concepts = parse_tags_from_content(response)
339
+
340
+ response = call_gpt_vision(client, [], f"Following, you have the key features that makes an ad a performative ad. Your task is to group this information and summarize in a nice paragraph that will be presented to the marketing team. Be concise. Features:\n{image_winner_concepts}")
341
+ insights = response.choices[0].message.content
342
+
343
+ general_output = {"keywords": [concept["name"] for concept in image_winner_concepts], "insights": insights}
344
+
345
+ # Groupby ad_id and gender
346
+ df_male = df[df["gender"] == "male"].groupby(["ad_id"]).sum()
347
+ df_male = df_male.reset_index()
348
+ df_male = df_male.sort_values("cost_per_engagement", ascending=True)
349
+
350
+ image_paths = top_n_ads(df_male)
351
+ response = call_gpt_vision(client, image_paths, "You are a marketing analyst and your task is to find common features between the ads that presented more engagement from men. You are given the top 5 most perfomative ads, and we expect you to return 5 keywords and its explanation that defines what makes a good ad that show an excellent engagement. Return it as a list of 5 concepts and its explanation, using the provided ads as example. Try to use nice categories to describe the features (you can use some names like `minimalist design`, `Clear message`, etc). Also, pay attention if the ads are mostly images or videos, this is important to say. The output MUST contain one concept per line. For each like, follow the structure: <concept>:<explanation>.")
352
+ image_winner_concepts = parse_tags_from_content(response)
353
+
354
+ response = call_gpt_vision(client, [], f"Following, you have the key features that makes an ad a performative ad. Your task is to group this information and summarize in a nice paragraph that will be presented to the marketing team. Be concise. Features:\n{image_winner_concepts}")
355
+ insights = response.choices[0].message.content
356
+
357
+ male_output = {"keywords": [concept["name"] for concept in image_winner_concepts], "insights": insights}
358
+
359
+
360
+ df_female = df[df["gender"] == "female"].groupby(["ad_id"]).sum()
361
+ df_female = df_female.reset_index()
362
+ df_female = df_female.sort_values("cost_per_engagement", ascending=True)
363
+
364
+ image_paths = top_n_ads(df_female)
365
+ response = call_gpt_vision(client, image_paths, "You are a marketing analyst and your task is to find common features between the ads that presented more engagement from women. You are given the top 5 most perfomative ads, and we expect you to return 5 keywords and its explanation that defines what makes a good ad that show an excellent engagement. Return it as a list of 5 concepts and its explanation, using the provided ads as example. Try to use nice categories to describe the features (you can use some names like `minimalist design`, `Clear message`, etc). Also, pay attention if the ads are mostly images or videos, this is important to say. The output MUST contain one concept per line. For each like, follow the structure: <concept>:<explanation>.")
366
+ image_winner_concepts = parse_tags_from_content(response)
367
+
368
+ response = call_gpt_vision(client, [], f"Following, you have the key features that makes an ad a performative ad. Your task is to group this information and summarize in a nice paragraph that will be presented to the marketing team. Be concise. Features:\n{image_winner_concepts}")
369
+ insights = response.choices[0].message.content
370
+ female_output = {"keywords": [concept["name"] for concept in image_winner_concepts], "insights": insights}
371
+
372
+ return {
373
+ "General": general_output,
374
+ "Male": male_output,
375
+ "Female": female_output,
376
+ }
377
+
378
  def format_adsets(campaign_id):
379
  st_campaigns.empty()
380
  adsets = get_adsets(campaign_id)
 
406
  for col in video_cols:
407
  if col in df_ads.columns:
408
  df_ads[col] = df_ads[col].apply(lambda x: float(x[0].get("value", 0)) if isinstance(x, list) else 0)
409
+
410
+ objective = df_ads["objective"].values[0]
411
+ def get_engagement(row):
412
+ if isinstance(row, list):
413
+ for ac in row:
414
+ if ac["action_type"] == "post_engagement":
415
+ return float(ac["value"])
416
+ return 0
417
+ if "cost_per_action_type" in df_ads.columns:
418
+ df_ads["cost_per_engagement"] = df_ads["cost_per_action_type"].apply(get_engagement)
419
+ df_ads = df_ads.sort_values("cost_per_engagement", ascending=True)
420
 
421
  if "purchase_roas" in df_ads.columns:
422
  df_ads["purchase_roas"] = df_ads["purchase_roas"].apply(lambda x: float(x[0].get("value", 0)) if isinstance(x, list) else 0)
423
+ df_ads["r_purchase_roas"] = df_ads["purchase_roas"] / df_ads["spend"]
424
+ df_ads = df_ads.sort_values("r_purchase_roas", ascending=False)
425
 
426
  if BIG_DATASET is None:
427
  BIG_DATASET = df_ads
428
  else:
429
  BIG_DATASET = pd.concat([BIG_DATASET, df_ads])
 
430
  with st_ads.container():
431
+ st.title("Ads")
432
  with st.expander("See analysis", expanded=False):
433
  analysis = st.empty()
434
 
435
  for i, ad in enumerate(df_ads["ad_id"].unique()):
436
  get_creative_assets(ad)
437
  ad_name = df_ads[df_ads["ad_id"] == ad]["ad_name"].values[0]
438
+ if i < 3:
439
+ addon = "🏆"
440
+ else:
441
+ addon = ""
442
+ with st.popover(f"{addon} {ad_name}"):
443
  tab1, tab2, tab3 = st.tabs(["Creative", "Analytics", "Video Analysis"])
444
  df_tmp = df_ads[df_ads["ad_id"] == ad]
445
  with tab2:
 
478
 
479
  if "purchase_roas" in df_tmp.columns:
480
  df_roas = df_tmp.groupby(options)[["spend","purchase_roas"]].sum().reset_index().sort_values("purchase_roas", ascending=False)
 
481
  values = [str(v) for v in df_tmp[options].values]
482
  fig = go.Figure(data=[
483
  go.Bar(name='ROAS', x=values, y=df_roas["purchase_roas"]),
 
491
  if "video_play_actions" in df_tmp.columns:
492
  values = df_ads[["ad_id","video_play_actions","video_p25_watched_actions","video_p50_watched_actions","video_p75_watched_actions","video_p100_watched_actions"]].groupby("ad_id").get_group(ad).sum().values[1:]
493
  labels = ["Total video plays","Video plays until 25%","Video plays until 50%","Video plays until 75%","Video plays until 100%"]
 
494
  if values[0] > 0:
495
  st.plotly_chart(create_video_plays_funnel(values, labels), use_container_width=True)
496
  with tab1:
 
502
  st.image(f'assets/{ad}.jpg', caption='Creative', use_column_width=True)
503
 
504
  with analysis.container():
505
+ v_d, p_a = st.tabs(["Video Dropoff", "Performance Analysis"])
506
+ with p_a:
507
+ if not os.path.exists(f"{adset_id}_performance.json"):
508
+ report = performance_analysis(df_ads, objective)
509
+ json.dump(report, open(f"{adset_id}_performance.json", "w"))
510
+ else:
511
+ report = json.load(open(f"{adset_id}_performance.json", "r"))
512
+ tabs = st.tabs(report.keys())
513
+ tabs_names = list(report.keys())
514
+ for i, tab in enumerate(tabs):
515
+ with tab:
516
+ st.multiselect("", report[tabs_names[i]]["keywords"], report[tabs_names[i]]["keywords"], key=f"{ad}_{i}")
517
+ st.write(report[tabs_names[i]]["insights"])
518
+
519
+ with v_d:
520
+ if not os.path.exists(f"{adset_id}_video_dropoff.json"):
521
+ report = video_dropoff_analysis(df_ads)
522
+ json.dump(report, open(f"{adset_id}_video_dropoff.json", "w"))
523
+ else:
524
+ report = json.load(open(f"{adset_id}_video_dropoff.json", "r"))
525
+ st.write(report)
526
 
527
  def create_video_plays_funnel(funnel_data, funnel_title):
528
  fig = go.Figure(go.Funnel(
 
540
  st.session_state["initiated"] = True
541
  with st_campaigns.container():
542
  st.title("Campaigns")
 
543
  for c in (get_campaigns(ACCOUNT_ID))["data"]:
544
  with st.popover(c["campaign_name"]):
545
  st.markdown("**Impressions**: " + str(c["impressions"]))
 
551
  on_click=format_adsets,
552
  kwargs={"campaign_id": c["campaign_id"]},
553
  )
554
+