bestpedro commited on
Commit
98eb3dc
1 Parent(s): b7227b4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +423 -2
app.py CHANGED
@@ -1,4 +1,425 @@
1
  import streamlit as st
 
 
2
 
3
- x = st.slider('Select a value')
4
- st.write(x, 'squared is', x * x)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ import numpy as np
3
+ import plotly.figure_factory as ff
4
 
5
+ import plotly.graph_objects as go
6
+ import plotly.express as px
7
+
8
+
9
+ import requests
10
+ import json
11
+ import pandas as pd
12
+ import shutil
13
+ 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
+ ANALYSIS_TYPE = {
29
+ "OUTCOME_SALES": "ROAS",
30
+ }
31
+
32
+ API_BASE = 'https://bestever-vision.openai.azure.com/'
33
+ DEPLOYMENT_NAME = 'vision'
34
+ API_VERSION = '2023-12-01-preview' # this might change in the future
35
+ API_URL = f"{API_BASE}openai/deployments/{DEPLOYMENT_NAME}/extensions"
36
+
37
+ client = AzureOpenAI(
38
+ api_key=OPENAI_API,
39
+ api_version=API_VERSION,
40
+ base_url=API_URL,
41
+ )
42
+
43
+ def encode_image(image_path):
44
+ with open(image_path, "rb") as image_file:
45
+ return base64.b64encode(image_file.read()).decode('utf-8')
46
+
47
+
48
+ def call_gpt_vision(client, images_path, user_prompt):
49
+ """Call the GPT4 Vision API to generate tags."""
50
+ images_content = [
51
+ {
52
+ "type": "image_url",
53
+ "image_url": {
54
+ "url": f"data:image/jpeg;base64,{encode_image(image_path)}",
55
+ },
56
+ }
57
+ for image_path in images_path
58
+ ]
59
+ user_content = [
60
+ {"type": "text", "text": user_prompt},
61
+ ]
62
+ user_content += images_content
63
+ response = client.chat.completions.create(
64
+ model=DEPLOYMENT_NAME,
65
+ messages=[
66
+ {"role": "user", "content": user_content},
67
+ ],
68
+ max_tokens=2000,
69
+ )
70
+ return response
71
+
72
+
73
+ def parse_tags_from_content(response):
74
+ """Parse the tags from the response."""
75
+ tags = []
76
+ content = response.choices[0].message.content
77
+ for full_tag in content.split("\n"):
78
+ splitted_fields = full_tag.split(":")
79
+ if len(splitted_fields) < 2:
80
+ continue
81
+ tag_name = splitted_fields[0]
82
+ tag_details = ":".join(splitted_fields[1:])
83
+ tag_element = {"name": tag_name, "metadata": {"details": tag_details}}
84
+ tags.append(tag_element)
85
+ return tags
86
+
87
+
88
+ def get_campaigns(account_id):
89
+ url = f"{account_id}/insights"
90
+ params = {
91
+ "date_preset": "last_90d",
92
+ "fields": "campaign_id,campaign_name,impressions,spend,objective",
93
+ "level": "campaign",
94
+ "access_token": ACCESS_TOKEN,
95
+ }
96
+ return call_graph_api(url, params)
97
+
98
+
99
+ def get_adsets(campaign_id):
100
+ url = f"{campaign_id}/insights"
101
+ params = {
102
+ "date_preset": "last_90d",
103
+ "fields": "adset_id,adset_name,impressions,spend",
104
+ "level": "adset",
105
+ "access_token": ACCESS_TOKEN,
106
+ }
107
+ return call_graph_api(url, params)
108
+
109
+
110
+ def get_ads(adset_id):
111
+ url = f"{adset_id}/insights"
112
+ params = {
113
+ "date_preset": "last_90d",
114
+ "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",
115
+ "breakdowns": "age,gender",
116
+ "limit": 1000,
117
+ "level": "ad",
118
+ "access_token": ACCESS_TOKEN,
119
+ }
120
+ return call_graph_api(url, params)
121
+
122
+
123
+ def save_image_from_url(url, filename):
124
+ res = requests.get(url, stream = True)
125
+
126
+ if res.status_code == 200:
127
+ with open(filename,'wb') as f:
128
+ shutil.copyfileobj(res.raw, f)
129
+ return True
130
+ return False
131
+
132
+ def get_creative_assets(ad_id):
133
+ # checking if the asset already exists
134
+ 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'):
135
+ return
136
+ url = f"{ad_id}"
137
+ params = {
138
+ "fields": "creative{video_id,id,effective_object_story_id,image_url}",
139
+ "access_token": ACCESS_TOKEN,
140
+ }
141
+ creative = call_graph_api(url, params)["creative"]
142
+ saved = False
143
+ print("-" * 10)
144
+ if "video_id" in creative:
145
+ # download video
146
+ video_id = creative["video_id"]
147
+ video_url = f"{video_id}"
148
+ video_params = {
149
+ "fields": "source",
150
+ "access_token": ACCESS_TOKEN,
151
+ }
152
+ video_source = call_graph_api(video_url, video_params)["source"]
153
+ ext = video_source.split("?")[0].split(".")[-1]
154
+ if len(ext) > 4:
155
+ ext = "mp4"
156
+ saved = save_image_from_url(video_source, os.path.join("assets", f'{ad_id}.{ext}'))
157
+
158
+ elif "image_url" in creative:
159
+ image_url = creative["image_url"]
160
+ ext = image_url.split("?")[0].split(".")[-1]
161
+ if len(ext) > 4:
162
+ ext = "png"
163
+ saved = save_image_from_url(image_url, os.path.join("assets", f'{ad_id}.{ext}'))
164
+
165
+ elif "effective_object_story_id" in creative:
166
+ object_story_url = creative["effective_object_story_id"]
167
+ object_story_params = {
168
+ "fields": "attachments",
169
+ "access_token": ACCESS_TOKEN,
170
+ }
171
+ attachments = call_graph_api(object_story_url, object_story_params)["attachments"]
172
+ if "media" in attachments:
173
+ media = attachments["media"]
174
+ if "source" in media or "video" in media:
175
+ video_url = media["video"]["source"]
176
+ ext = video_url.split("?")[0].split(".")[-1]
177
+ if len(ext) > 4:
178
+ ext = "png"
179
+ saved = save_image_from_url(video_url, os.path.join("assets", f'{ad_id}.{ext}'))
180
+ elif "image" in media:
181
+ image_url = media["image"]["src"]
182
+ ext = image_url.split("?")[0].split(".")[-1]
183
+ if len(ext) > 4:
184
+ ext = "mp4"
185
+ saved = save_image_from_url(image_url, os.path.join("assets", f'{ad_id}.{ext}'))
186
+
187
+ if not saved:
188
+ creative_url = f'{creative["id"]}'
189
+ creative_params = {
190
+ "fields": "thumbnail_url",
191
+ "access_token": ACCESS_TOKEN,
192
+ "thumbnail_width": 512,
193
+ "thumbnail_height": 512,
194
+ }
195
+ thumbnail_url = call_graph_api(creative_url, creative_params)["thumbnail_url"]
196
+ ext = thumbnail_url.split("?")[0].split(".")[-1]
197
+ if len(ext) > 4:
198
+ ext = "jpg"
199
+ saved = save_image_from_url(thumbnail_url, os.path.join("assets", f'{ad_id}.{ext}'))
200
+
201
+ def call_graph_api(url, params):
202
+ base_url = "https://graph.facebook.com/v19.0/"
203
+ response = requests.get(base_url + url, params=params)
204
+ return json.loads(response.text)
205
+
206
+
207
+ def top_n_ads(df, n=5):
208
+ ad_ids = df.head(n)["ad_id"].values
209
+ image_paths = []
210
+ for ad_id in ad_ids:
211
+ if os.path.exists(f'assets/{ad_id}.png'):
212
+ image_paths.append(f'assets/{ad_id}.png')
213
+ elif os.path.exists(f'assets/{ad_id}.mp4'):
214
+ image_paths.append(f'assets/{ad_id}.mp4')
215
+ elif os.path.exists(f'assets/{ad_id}.jpg'):
216
+ image_paths.append(f'assets/{ad_id}.jpg')
217
+ return image_paths
218
+
219
+
220
+ def perform_analysis(df, objective):
221
+ # - TS to CTR ratio analysis
222
+ # - ROAS analysis (I will see the better metric here to use)
223
+ # - Video drop off analysis
224
+ if ANALYSIS_TYPE[objective] == "ROAS":
225
+ # 3 analysis:
226
+ # general
227
+ # male
228
+ # female
229
+
230
+ df_general = df.groupby(["ad_id"]).sum()
231
+ df_general = df_general.reset_index()
232
+ df_general["relative_roas"] = df_general["purchase_roas"] / df_general["spend"]
233
+ df_general = df_general.sort_values("relative_roas", ascending=False)
234
+
235
+ image_paths = top_n_ads(df_general)
236
+ response = call_gpt_vision(client, image_paths, "You are a marketing analyst and your task is to find common features between the most performatives ads of the company. 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 ROAS. 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>.")
237
+ image_winner_concepts = parse_tags_from_content(response)
238
+
239
+ 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}")
240
+ insights = response.choices[0].message.content
241
+
242
+ general_output = {"keywords": [concept["name"] for concept in image_winner_concepts], "insights": insights}
243
+
244
+ # Groupby ad_id and gender
245
+ df_male = df[df["gender"] == "male"].groupby(["ad_id"]).sum()
246
+ df_male = df_male.reset_index()
247
+ df_male["relative_roas"] = df_male["purchase_roas"] / df_male["spend"]
248
+ df_male = df_male.sort_values("relative_roas", ascending=False)
249
+
250
+ image_paths = top_n_ads(df_male)
251
+ response = call_gpt_vision(client, image_paths, "You are a marketing analyst and your task is to find common features between the most performatives ads published to 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 ROAS. 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>.")
252
+ image_winner_concepts = parse_tags_from_content(response)
253
+
254
+ 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}")
255
+ insights = response.choices[0].message.content
256
+
257
+ male_output = {"keywords": [concept["name"] for concept in image_winner_concepts], "insights": insights}
258
+
259
+
260
+ df_female = df[df["gender"] == "female"].groupby(["ad_id"]).sum()
261
+ df_female = df_female.reset_index()
262
+ df_female["relative_roas"] = df_female["purchase_roas"] / df_female["spend"]
263
+ df_female = df_female.sort_values("relative_roas", ascending=False)
264
+
265
+ image_paths = top_n_ads(df_female)
266
+ response = call_gpt_vision(client, image_paths, "You are a marketing analyst and your task is to find common features between the most performatives ads published to 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 ROAS. 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>.")
267
+ image_winner_concepts = parse_tags_from_content(response)
268
+
269
+ 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}")
270
+ insights = response.choices[0].message.content
271
+ female_output = {"keywords": [concept["name"] for concept in image_winner_concepts], "insights": insights}
272
+
273
+ return {
274
+ "General": general_output,
275
+ "Male": male_output,
276
+ "Female": female_output,
277
+ }
278
+
279
+ def format_adsets(campaign_id):
280
+ st_campaigns.empty()
281
+ adsets = get_adsets(campaign_id)
282
+ with st_adsets.container():
283
+ st.title("Adsets")
284
+ for adset in adsets["data"]:
285
+ with st.popover(adset["adset_name"]):
286
+ st.markdown("**Impressions**: " + str(adset["impressions"]))
287
+ st.markdown("**Total Spend**: US$" + str(adset["spend"]))
288
+ st.button(
289
+ "View Ads",
290
+ key=adset["adset_name"],
291
+ on_click=format_ads,
292
+ kwargs={"adset_id": adset["adset_id"]},
293
+ )
294
+
295
+
296
+ def format_ads(adset_id):
297
+ st_adsets.empty()
298
+ BIG_DATASET = None
299
+ ads = get_ads(adset_id)
300
+ df_ads = pd.DataFrame(ads["data"])
301
+ options = ["gender"] #st.multiselect(
302
+ # "Which breakdowns do you want to see?", ["gender", "age"], ["gender"]
303
+ # )
304
+ df_ads["spend"] = df_ads["spend"].astype(float)
305
+ df_ads["impressions"] = df_ads["impressions"].astype(float)
306
+ video_cols = ["video_play_actions","video_p25_watched_actions","video_p50_watched_actions","video_p75_watched_actions","video_p100_watched_actions"]
307
+ for col in video_cols:
308
+ if col in df_ads.columns:
309
+ df_ads[col] = df_ads[col].apply(lambda x: float(x[0].get("value", 0)) if isinstance(x, list) else 0)
310
+
311
+ if "purchase_roas" in df_ads.columns:
312
+ df_ads["purchase_roas"] = df_ads["purchase_roas"].apply(lambda x: float(x[0].get("value", 0)) if isinstance(x, list) else 0)
313
+
314
+ if BIG_DATASET is None:
315
+ BIG_DATASET = df_ads
316
+ else:
317
+ BIG_DATASET = pd.concat([BIG_DATASET, df_ads])
318
+ BIG_DATASET.to_csv("big_dataset.csv")
319
+ with st_ads.container():
320
+ with st.expander("See analysis", expanded=False):
321
+ analysis = st.empty()
322
+
323
+ for i, ad in enumerate(df_ads["ad_id"].unique()):
324
+ get_creative_assets(ad)
325
+ ad_name = df_ads[df_ads["ad_id"] == ad]["ad_name"].values[0]
326
+ with st.popover(ad_name):
327
+ tab1, tab2, tab3 = st.tabs(["Creative", "Analytics", "Video Analysis"])
328
+ df_tmp = df_ads[df_ads["ad_id"] == ad]
329
+ with tab2:
330
+ if len(options) >= 1:
331
+ label = ["Total impressions"]
332
+ source = []
333
+ target = []
334
+ value = []
335
+ for option in options:
336
+ df_g_tmp = df_tmp.groupby(option).sum()
337
+ df_g_tmp = df_g_tmp.reset_index()
338
+ for imp, v in df_g_tmp[["impressions", option]].values:
339
+ label.append(v)
340
+ source.append(0)
341
+ target.append(len(label) - 1)
342
+ value.append(imp)
343
+
344
+ fig = go.Figure(
345
+ data=[
346
+ go.Sankey(
347
+ node=dict(
348
+ pad=15,
349
+ thickness=20,
350
+ line=dict(color="black", width=0.5),
351
+ label=label,
352
+ color="blue",
353
+ ),
354
+ link=dict(
355
+ source=source, target=target, value=value
356
+ ),
357
+ )
358
+ ]
359
+ )
360
+ fig.update_layout(title_text="Basic Sankey Diagram", font_size=10)
361
+ st.plotly_chart(fig, use_container_width=True)
362
+
363
+ if "purchase_roas" in df_tmp.columns:
364
+ df_roas = df_tmp.groupby(options)[["spend","purchase_roas"]].sum().reset_index().sort_values("purchase_roas", ascending=False)
365
+ print(df_roas)
366
+ values = [str(v) for v in df_tmp[options].values]
367
+ fig = go.Figure(data=[
368
+ go.Bar(name='ROAS', x=values, y=df_roas["purchase_roas"]),
369
+ go.Bar(name='Spend', x=values, y=df_roas["spend"])
370
+ ])
371
+ # Change the bar mode
372
+ fig.update_layout(barmode='group')
373
+ st.plotly_chart(fig, use_container_width=True)
374
+
375
+ with tab3:
376
+ if "video_play_actions" in df_tmp.columns:
377
+ 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:]
378
+ labels = ["Total video plays","Video plays until 25%","Video plays until 50%","Video plays until 75%","Video plays until 100%"]
379
+ print(values)
380
+ if values[0] > 0:
381
+ st.plotly_chart(create_video_plays_funnel(values, labels), use_container_width=True)
382
+ with tab1:
383
+ if os.path.exists(f'assets/{ad}.png'):
384
+ st.image(f'assets/{ad}.png', caption='Creative', use_column_width=True)
385
+ elif os.path.exists(f'assets/{ad}.mp4'):
386
+ st.video(f'assets/{ad}.mp4')
387
+ elif os.path.exists(f'assets/{ad}.jpg'):
388
+ st.image(f'assets/{ad}.jpg', caption='Creative', use_column_width=True)
389
+
390
+ with analysis.container():
391
+ report = perform_analysis(df_tmp, "OUTCOME_SALES")
392
+ tabs = st.tabs(report.keys())
393
+ tabs_names = list(report.keys())
394
+ for i, tab in enumerate(tabs):
395
+ with tab:
396
+ st.multiselect("", report[tabs_names[i]]["keywords"], report[tabs_names[i]]["keywords"], key=f"{ad}_{i}")
397
+ st.write(report[tabs_names[i]]["insights"])
398
+
399
+ def create_video_plays_funnel(funnel_data, funnel_title):
400
+ fig = go.Figure(go.Funnel(
401
+ y = funnel_title,
402
+ x = funnel_data))
403
+ return fig
404
+
405
+ if "initiated" not in st.session_state:
406
+ st.session_state["initiated"] = False
407
+
408
+ if not st.session_state["initiated"]:
409
+ st_campaigns = st.empty()
410
+ st_adsets = st.empty()
411
+ st_ads = st.empty()
412
+ st.session_state["initiated"] = True
413
+ with st_campaigns.container():
414
+ st.title("Campaigns")
415
+ for c in (get_campaigns(ACCOUNT_ID))["data"]:
416
+ with st.popover(c["campaign_name"]):
417
+ st.markdown("**Impressions**: " + str(c["impressions"]))
418
+ st.markdown("**Total Spend**: US$" + str(c["spend"]))
419
+ st.markdown("**Objective**: " + str(c["objective"]))
420
+ st.button(
421
+ "View Adsets",
422
+ key=c["campaign_name"],
423
+ on_click=format_adsets,
424
+ kwargs={"campaign_id": c["campaign_id"]},
425
+ )