Script 1313: Intraday Budget Cap Based on Strategy Target
Purpose
The Python script manages campaign budgets by pausing campaigns when the month-to-date (MTD) spend reaches the monthly budget specified in the strategy.
To Elaborate
The script is designed to automate the management of advertising campaigns by monitoring their spending against predefined monthly budgets. It calculates the month-to-date (MTD) spend for each campaign strategy and compares it to the allocated monthly budget. If the MTD spend exceeds the budget, adjusted for a safety margin, the script recommends pausing the campaign to prevent overspending. Additionally, the script can reactivate campaigns at the start of a new month if they were paused due to budget constraints in the previous month and are now under budget. This ensures that campaigns are only active when they have sufficient budget, optimizing the allocation of advertising resources.
Walking Through the Code
- Local Mode Configuration
- The script begins with a configuration section for running locally, including setting a flag and specifying a path to a pickle file containing data.
- It checks if the script is running on a server or locally, loading data from a pickle file if running locally.
- Data Preparation and Initialization
- The script imports necessary libraries and sets constants, including a safety margin for budget calculations.
- It loads data into a DataFrame and processes columns to ensure they are in the correct format, such as converting budget values to numeric and handling missing values.
- Budget and Spend Calculations
- The script calculates the MTD spend for each strategy by summing the publication costs.
- It identifies campaigns that have a monthly budget and checks if their MTD spend exceeds the budget, considering the safety margin.
- Campaign Status Updates
- Campaigns that exceed their budget are marked for pausing, while those under budget and previously paused are marked for reactivation.
- At the start of a new month, the script reactivates campaigns that are under budget and clears their pause dates.
- Final Output Preparation
- The script updates the campaign status and pause dates based on the calculated conditions.
- It prepares the final output DataFrame with relevant columns for further processing or export.
Vitals
- Script ID : 1313
- Client ID / Customer ID: 1306927943 / 60270393
- Action Type: Bulk Upload
- Item Changed: Campaign
- Output Columns: Account, Campaign, Status, Auto Pause Date, Auto Pause Rec. Campaign Status
- Linked Datasource: M1 Report
- Reference Datasource: None
- Owner: dwaidhas@marinsoftware.com (dwaidhas@marinsoftware.com)
- Created by dwaidhas@marinsoftware.com on 2024-08-06 21:24
- Last Updated by dwaidhas@marinsoftware.com on 2024-11-05 20:55
> See it in Action
Python Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
## name: Intraday Budget Cap via Strategy
## description:
## Pause campaigns when MTD spend reaches Monthly Budget (stored in Strategy)
##
## author: Dana Waidhas
## created: 2024-08-06
##############################
########### START - Local Mode Config ###########
# Step 1: Uncomment download_preview_input flag and run Preview successfully with the Datasources you want
download_preview_input=False
# Step 2: In MarinOne, go to Scripts -> Preview -> Logs, download 'dataSourceDict' pickle file, and update pickle_path below
pickle_path = '/Users/mhuang/Downloads/pickle/generic_budget_cap_intraday_datasource_dict.pkl'
# Step 3: Copy this script into local IDE with Python virtual env loaded with pandas and numpy.
# Step 4: Run locally with below code to init dataSourceDict
# determine if code is running on server or locally
def is_executing_on_server():
try:
# Attempt to access a known restricted builtin
dict_items = dataSourceDict.items()
return True
except NameError:
# NameError: dataSourceDict object is missing (indicating not on server)
return False
local_dev = False
if is_executing_on_server():
print("Code is executing on server. Skip init.")
elif len(pickle_path) > 3:
print("Code is NOT executing on server. Doing init.")
local_dev = True
# load dataSourceDict via pickled file
import pickle
dataSourceDict = pickle.load(open(pickle_path, 'rb'))
# print shape and first 5 rows for each entry in dataSourceDict
for key, value in dataSourceDict.items():
print(f"Shape of dataSourceDict[{key}]: {value.shape}")
# print(f"First 5 rows of dataSourceDict[{key}]:\n{value.head(5)}")
# set outputDf same as inputDf
inputDf = dataSourceDict["1"]
outputDf = inputDf.copy()
# setup timezone
import datetime
# Chicago Timezone is GMT-5. Adjust as needed.
CLIENT_TIMEZONE = datetime.timezone(datetime.timedelta(hours=-5))
# import pandas
import pandas as pd
import numpy as np
# Printing out the version of Python, Pandas and Numpy
# import sys
# python_version = sys.version
# pandas_version = pd.__version__
# numpy_version = np.__version__
# print(f"python version: {python_version}")
# print(f"pandas version: {pandas_version}")
# print(f"numpy version: {numpy_version}")
# other imports
import re
import urllib
# import Marin util functions
from marin_scripts_utils import tableize, select_changed
else:
print("Running locally but no pickle path defined. dataSourceDict not loaded.")
exit(1)
########### END - Local Mode Setup ###########
CLIENT_TIMEZONE = datetime.timezone(datetime.timedelta(hours=-5))
today = datetime.datetime.now(CLIENT_TIMEZONE).date()
# Constants and Parameters
BUDGET_CAP_SAFETY_MARGIN = 0.02 # 2% safety margin for budget cap
# Define expected columns in report
RPT_COL_ACCOUNT = 'Account'
RPT_COL_CAMPAIGN = 'Campaign'
RPT_COL_CAMPAIGN_STATUS = 'Campaign Status'
RPT_COL_PUB_COST = 'Pub. Cost $'
RPT_COL_AUTO_PAUSE_STATUS = 'Auto Pause Status'
RPT_COL_STRATEGY = 'Strategy'
RPT_COL_AUTO_PAUSE_DATE = 'Auto Pause Date'
RPT_COL_AUTO_PAUSE_REC_STATUS = 'Auto Pause Rec. Campaign Status'
RPT_COL_MONTHLY_BUDGET = 'Strategy Target'
# Output Columns
BULK_COL_ACCOUNT = 'Account'
BULK_COL_CAMPAIGN = 'Campaign'
BULK_COL_STATUS = 'Status'
BULK_COL_AUTO_PAUSE_DATE = 'Auto Pause Date'
BULK_COL_AUTO_PAUSE_REC_STATUS = 'Auto Pause Rec. Campaign Status'
# Other Settings
VAL_STATUS_ACTIVE = 'Active'
VAL_STATUS_PAUSED = 'Paused'
VAL_BLANK = ''
# Get today's date and reset trigger date for month start
today = datetime.datetime.now().date()
is_month_start = today.day == 1
# Load data
inputDf = dataSourceDict["1"]
originalDf = inputDf.copy()
# Process Monthly Budget as numeric
inputDf[RPT_COL_MONTHLY_BUDGET] = pd.to_numeric(inputDf[RPT_COL_MONTHLY_BUDGET], errors='coerce')
inputDf[RPT_COL_MONTHLY_BUDGET].fillna(0.0, inplace=True)
inputDf[RPT_COL_AUTO_PAUSE_DATE] = pd.to_datetime(inputDf[RPT_COL_AUTO_PAUSE_DATE], errors='coerce').dt.date
# Set any NaN fields to blank
inputDf.fillna(VAL_BLANK, inplace=True)
# Calculate MTD Spend per Strategy
inputDf['MTD Spend'] = inputDf.groupby(RPT_COL_STRATEGY)[RPT_COL_PUB_COST].transform('sum')
# Condition for campaigns to pause due to budget cap
has_monthly_budget = inputDf[RPT_COL_MONTHLY_BUDGET] > 0.0
over_budget = inputDf['MTD Spend'] >= inputDf[RPT_COL_MONTHLY_BUDGET] * (1 - BUDGET_CAP_SAFETY_MARGIN)
pause_conditions = has_monthly_budget & over_budget
# Apply recommendations
inputDf.loc[pause_conditions, RPT_COL_AUTO_PAUSE_REC_STATUS] = VAL_STATUS_PAUSED
# Reactivate campaigns under budget that are paused from previous month
under_budget = inputDf['MTD Spend'] < inputDf[RPT_COL_MONTHLY_BUDGET] * (1 - BUDGET_CAP_SAFETY_MARGIN)
paused_campaigns = inputDf[RPT_COL_AUTO_PAUSE_DATE].notna()
resume_conditions = under_budget & paused_campaigns
# Month start reactivation if needed
if is_month_start:
inputDf.loc[resume_conditions, RPT_COL_AUTO_PAUSE_REC_STATUS] = VAL_STATUS_ACTIVE
inputDf.loc[resume_conditions, RPT_COL_AUTO_PAUSE_DATE] = VAL_BLANK
# Only apply to campaigns with "traffic" in Auto Pause Status
should_traffic_pause = (inputDf[RPT_COL_AUTO_PAUSE_STATUS].str.lower() == 'traffic') & pause_conditions
should_traffic_resume = (inputDf[RPT_COL_AUTO_PAUSE_STATUS].str.lower() == 'traffic') & resume_conditions
# Update final statuses and dates in output
inputDf.loc[should_traffic_pause, RPT_COL_CAMPAIGN_STATUS] = VAL_STATUS_PAUSED
inputDf.loc[should_traffic_pause, RPT_COL_AUTO_PAUSE_DATE] = today
inputDf.loc[should_traffic_resume, RPT_COL_CAMPAIGN_STATUS] = VAL_STATUS_ACTIVE
inputDf.loc[should_traffic_resume, RPT_COL_AUTO_PAUSE_DATE] = VAL_BLANK
# Prepare output DataFrame with necessary columns
outputDf = inputDf[[RPT_COL_ACCOUNT, RPT_COL_CAMPAIGN, RPT_COL_CAMPAIGN_STATUS, RPT_COL_AUTO_PAUSE_DATE, RPT_COL_AUTO_PAUSE_REC_STATUS]]
outputDf.columns = [BULK_COL_ACCOUNT, BULK_COL_CAMPAIGN, BULK_COL_STATUS, BULK_COL_AUTO_PAUSE_DATE, BULK_COL_AUTO_PAUSE_REC_STATUS]
# Local debug
if local_dev:
outputDf.to_csv('outputDf.csv', index=False)
print(f"Output written to outputDf.csv")
Post generated on 2024-11-27 06:58:46 GMT