Assesment
1. Data Sync Design
Using CampaignData_1 and CampaignData_2, design a data sync logic:
- Merge data by io_id
- Track status and spend changes
- Highlight discrepancies in budget vs. actual spend
- Output should include: io_id, campaign_name, status_old, status_new, budget, actual_spend, and change_flag
2. Root Cause Analysis (RCA)
Using the merged data, identify:
- Which campaign overspent?
- What was the potential cause?
- What steps would you take to prevent this?
3. Change Tracker
Implement logic to flag campaigns whose:
- Delivery status changed
- Actual spend exceeded budget by over 10%
4. Script Optimization
Describe how you would optimize a sync process that runs for over 50 minutes.
- Script limitations
- Optimization strategies (e.g., batch operations)
- How you'd test and measure improvements
5. Dashboard Planning
Using any of the sheets:
- Propose key KPIs for tracking campaign performance and team compliance
- Suggest layout and tools (e.g., Google Sheets, Looker)
6. Multi-Campaign Reporting
Using Lifecycle_Data, organize a reporting view that:
- Separates PG, Direct IO, and PMP campaigns
- Tracks current vs. past campaigns
7. Audit-Ready Systems
List practices you follow to ensure:
- Someone else can pick up your data/process work
- Scripts and trackers are understandable
Deliverable
Deliverable: A working spreadsheet with script and a short write-
up explaining the logic.
Deliverable: RCA document (max 1 page).
Deliverable: Annotated Google Sheet with highlighted flags.
Deliverable: A short write-up covering all the 3 points
Deliverable: Mock dashboard sketch or description.
Deliverable: Cleaned and sorted sheet with filters or pivot.
Deliverable: Bullet list with 5-7 points.
function syncCampaignData() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet1 = ss.getSheetByName("CampaignData_1");
const sheet2 = ss.getSheetByName("CampaignData_2");
const outputSheet = ss.getSheetByName("SyncedOutput") || ss.insertSheet("SyncedOutput");
// Clear previous output
outputSheet.clearContents();
// Read headers and data
const data1 = sheet1.getDataRange().getValues();
const data2 = sheet2.getDataRange().getValues();
const headers1 = data1[0];
const headers2 = data2[0];
const idx = (arr, key) => arr.indexOf(key);
const ioIndex1 = idx(headers1, "io_id");
const ioIndex2 = idx(headers2, "io_id");
// Convert sheet2 to a map for quick lookup
const map2 = {};
for (let i = 1; i < data2.length; i++) {
const row = data2[i];
map2[row[ioIndex2]] = row;
}
// Output headers
const output = [
["io_id", "campaign_name", "status_old", "status_new", "budget", "actual_spend", "change_flag"]
];
for (let i = 1; i < data1.length; i++) {
const row1 = data1[i];
const io_id = row1[ioIndex1];
const row2 = map2[io_id];
if (!row2) continue; // skip if no match
// Indexes for needed fields
const campaign_name = row1[idx(headers1, "campaign_name")];
const status_old = row1[idx(headers1, "status")];
const status_new = row2[idx(headers2, "status")];
const budget = row2[idx(headers2, "budget")];
const actual_spend = row2[idx(headers2, "spend")];
let change_flag = "";
if (status_old !== status_new) change_flag += "Status Changed; ";
if (row1[idx(headers1, "spend")] !== actual_spend) change_flag += "Spend Changed; ";
if (budget != actual_spend) change_flag += "Budget ≠ Spend;";
output.push([io_id, campaign_name, status_old, status_new, budget, actual_spend, change_flag.trim()]);
}
// Paste output
outputSheet.getRange(1, 1, output.length, output[0].length).setValues(output);
}
Status Spend
Campai delivery actual_s
io_id status Changed Budget Differen
gn _status pend
? ce
Launch Deliverin
IO001 Booked ✅ Yes 10000 9500 500
A g
Deliverin Deliverin
IO002 Promo B ❌ No 20000 21000 -1000
g g
Deliverin
IO003 Test C Booked ✅ Yes 15000 14000 1000
g
Delivere Delivere
IO004 Flight D ❌ No 18000 18500 -500
d d
function trackCampaignChanges() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet1 = ss.getSheetByName("CampaignData_1");
const sheet2 = ss.getSheetByName("CampaignData_2");
const outputSheet = ss.getSheetByName("ChangeTrackerOutput") || ss.insertSheet("ChangeTrackerOutput");
outputSheet.clearContents();
const data1 = sheet1.getDataRange().getValues();
const data2 = sheet2.getDataRange().getValues();
const headers1 = data1[0];
const headers2 = data2[0];
const idx = (arr, key) => arr.indexOf(key);
const ioIdx1 = idx(headers1, "io_id");
const ioIdx2 = idx(headers2, "io_id");
const statusIdx1 = idx(headers1, "status");
const statusIdx2 = idx(headers2, "status");
const spendIdx1 = idx(headers1, "spend");
const spendIdx2 = idx(headers2, "spend");
const budgetIdx2 = idx(headers2, "budget");
const nameIdx1 = idx(headers1, "campaign_name");
// Build a map from sheet2 (latest data)
const latestMap = {};
for (let i = 1; i < data2.length; i++) {
latestMap[data2[i][ioIdx2]] = data2[i];
}
const output = [
["io_id", "campaign_name", "status_old", "status_new", "budget", "actual_spend", "change_flag"]
];
const highlightRows = [];
for (let i = 1; i < data1.length; i++) {
const row1 = data1[i];
const io_id = row1[ioIdx1];
const row2 = latestMap[io_id];
if (!row2) continue;
const campaignName = row1[nameIdx1];
const statusOld = row1[statusIdx1];
const statusNew = row2[statusIdx2];
const budget = row2[budgetIdx2];
const actualSpend = row2[spendIdx2];
let flag = "";
if (statusOld !== statusNew) flag += "Status Changed; ";
if (budget !== "" && actualSpend !== "" && Number(actualSpend) > Number(budget) * 1.10) {
flag += "Spend > Budget (10%+);";
}
output.push([io_id, campaignName, statusOld, statusNew, budget, actualSpend, flag.trim()]);
if (flag !== "") {
highlightRows.push(output.length); // row number in output array
}
}
// Write to output sheet
outputSheet.getRange(1, 1, output.length, output[0].length).setValues(output);
// Highlight flagged rows
for (let i = 0; i < highlightRows.length; i++) {
const row = highlightRows[i];
outputSheet.getRange(row, 1, 1, output[0].length).setBackground("#fff8b3"); // light yellow
}
}
ackerOutput");
const data = sheet.getRange(2, 1, numRows, numCols).getValues(); // Read in one go
function generateKPIDashboard() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const dataSheet = ss.getSheetByName("CampaignData_2");
const dashboard = ss.getSheetByName("Campaign Dashboard") || ss.insertSheet("Campaign Dashboard");
const data = dataSheet.getDataRange().getValues();
const headers = data[0];
const idx = (key) => headers.indexOf(key);
const budgetIdx = idx("budget");
const spendIdx = idx("spend");
const statusIdx = idx("status");
let totalCampaigns = 0;
let totalBudget = 0;
let totalSpend = 0;
let active = 0, paused = 0, completed = 0;
let overspent = 0;
for (let i = 1; i < data.length; i++) {
totalCampaigns++;
const budget = Number(data[i][budgetIdx]);
const spend = Number(data[i][spendIdx]);
const status = data[i][statusIdx];
totalBudget += budget;
totalSpend += spend;
if (status === "Active") active++;
if (status === "Paused") paused++;
if (status === "Completed") completed++;
if (budget > 0 && spend > budget * 1.10) overspent++;
}
dashboard.clear();
dashboard.getRange("A1:B8").setValues([
["KPI", "Value"],
["Total Campaigns", totalCampaigns],
["Active Campaigns", active],
["Paused Campaigns", paused],
["Completed Campaigns", completed],
["Total Budget", totalBudget],
["Total Spend", totalSpend],
["Overspent Campaigns (10%+)", overspent],
]);
}
ashboard");
KPI Value Chart Type
Total Campaigns 120 Scorecard
Active Campaigns 80 Pie Chart
% Budget Utilization 85% Gauge / Bar Chart
Overspent Campaigns (10%+) 12 Bar Chart
Status Change Count 25 Line/Bar
channel_type - all -
Count of campaign_id status
Months start_date end_date Booked Delivered Delivering Total Result
Jun Jun 25 2025-07-08 00:00:00 1 1
Jun 25 Result 1 1
Jun 27 2025-07-04 00:00:00 1 1
Jun 27 Result 1 1
Jun 28 2025-07-07 00:00:00 1 1
Jun 28 Result 1 1
Jun Result 1 2 3
Jul Jul 08 2025-07-16 00:00:00 1 1
Jul 08 Result 1 1
Jul Result 1 1
Total Result 1 1 2 4
Audit-Ready Systems
List practices I follow to ensure:
•
•
•
•
•
- Someone else can pick up your data/process work
Adopted a data governance strategy
limited access to data according to there roles & responsbilities
only collecting necessary data due to threat of data breaches
Encrypt data and implement password protections to make sure all the data is safe
making sure all the software are updated
- Scripts and trackers are understandable
Modular and Reusable Code breaking down scripts into smaller, independent modules to improve maintainability, reduce dup
Use consistent and descriptive names for scripts, variables
Identify and resolve issues efficiently through proper error handling and logging to improve code reliability and stability
Enable effective teamwork and code management by tracking changes, facilitating code reviews
ve maintainability, reduce duplication, and save time
e reliability and stability
io_id campaign_name budget status last_updated
IO001 Launch A 10000 Booked 2025-07-01 00:00:00
IO002 Promo B 20000 Delivering 2025-07-02 00:00:00
IO003 Test C 15000 Delivering 2025-07-01 00:00:00
IO004 Flight D 18000 Delivered 2025-06-30 00:00:00
io_id campaign_name actual_spend delivery_status last_updated
IO001 Launch A 9500 Delivering 2025-07-03 00:00:00
IO002 Promo B 21000 Delivering 2025-07-03 00:00:00
IO003 Test C 14000 Booked 2025-07-02 00:00:00
IO004 Flight D 18500 Delivered 2025-07-01 00:00:00
campaign_idchannel_type start_date end_date status
CAMP001 PG 2025-06-25 00:00:00 2025-07-08 00:00:00 Delivering
CAMP002 Direct IO 2025-07-08 00:00:00 2025-07-16 00:00:00 Booked
CAMP003 PMP 2025-06-27 00:00:00 2025-07-04 00:00:00 Delivering
CAMP004 PG 2025-06-28 00:00:00 2025-07-07 00:00:00 Delivered