◎
Select an advertiser
Choose an account from the sidebar to view campaign analysis and recommendations.
Campaign Configurations
Advertiser ↕ Campaign ↕ Daily Budget ↕ % Used L7D ↕ CPC ↕ Ads ↕ Targeting ↕ Neg KW ↕ Pos KW ↕ Platforms Categories ↕ Geo Budget Pacing ↕ History
📄 Portfolio Demo

Disclaimer

This is a portfolio demo using fully fabricated data. All advertiser names, campaign metrics, and recommendations are fictional and for illustration purposes only.

What is this?

The CVR Optimizer is a tool I built to automate daily campaign performance analysis for a marketplace's Platinum advertisers, generating actionable recommendations for the Customer Success team. It is a working prototype built and maintained by Waldo Vanderhaeghen (Product Management).

This public version uses two fictional advertisers (TechRebuild GmbH and Gartenparadies Online GmbH) with generated data to demonstrate the dashboard's capabilities.

How it works

🗄️
Data warehouse
perf · config · audit log
→
📐
Feature extraction
CTR · pacing · search terms
→
🔍
Evaluation layer
deterministic waste flags
→
🧠
Claude (Bedrock)
diagnosis + prescription
→
📊
Dashboard
GitHub Pages · nightly
100%
campaigns covered, every night
15
E2E tests gate every deploy
0
manual steps in production
🎯
Maximise campaign spend efficiency for Platinum advertisers — help Customer Success act on the right lever for each campaign daily, without manual analysis.
📈
Grow CTR & CVR by surfacing underperformance signals early and recommending targeted, evidence-backed actions (keywords, CPC, platform, category).
🕐
Run daily, stay current. Pipeline triggered nightly; dashboard published to GitHub Pages after each run with fresh data.
1
Fetch — data warehouse pulls active campaign config, L7D CTR/spend CSVs, budget utilisation curves, and the audit log (config change history).
↓
2
Aggregate — compute_adv_aggregates.py pre-computes advertiser-level CVR/CTR averages and derived metrics into pipeline_state.json.
↓
3
Classify — verdict.py runs deterministic Python rule trees: two-axis verdict (performance × budget), underspend root cause, tried-and-failed history, confidence score. No LLM involved here.
↓
4
Analyse — analyst.py passes the structured CampaignVerdict JSON to Claude (Amazon Bedrock). Claude writes the readable recommendation card. Python enforces hard guardrails in the system prompt.
↓
5
Build & Deploy — build_dashboard.py injects all data into this HTML file. deploy_dashboard.sh pushes to GitHub Pages after explicit human confirmation.
🚫
Never recommend reducing budget — market ceiling → raise CPC or expand keywords instead.
🚫
Never recommend enabling pacing — pacing OFF is the strategic default; enabling it is not surfaced as an option.
✅
Human validation required — all recommendations must be reviewed before implementation.
✅
QA gates before deploy — sanity checks, dashboard checks, and E2E tests must all pass; deploy requires explicit confirmation.

Recommendation logic

Performance signal
CVR used when ≥20 L14D conversions; fallback to CTR
Benchmarked against advertiser’s own trailing average
CTR floor: 0.19% → advertiser avg once mature (≥14d, ≥50k imp L28D)
improving >+10% WoW
stable ±10%
declining <−10% WoW
no data <7 days & <10k impressions
Budget signal
cap ≥95% utilisation — hitting daily limit
healthy 50–94% utilisation
underspend <50% utilisation
exhaust early budget gone before 18:00 on ≥2 days (pacing OFF only)
Utilisation excludes paused days; recalculated from post-config-change days when a change occurred in L14D.
Underspend root cause
market_size — addressable spend <70% of budget at 100% win rate → raise CPC
cpc — winning ≥50% auctions but still underspending → raise CPC
reach — low impression share → expand keyword list; covers_budget = false is the trigger
Positive keyword selection
CTR gate — every candidate must clear its impression tier:
<5 impressions → excluded
5–49 imp → CTR ≥5% (hard floor)
≥50 imp → CTR ≥ campaign impression-weighted avg CTR
Topic filter — candidates must contain a stem from the campaign name + top-80 ad-title tokens
Additional: intent blocklist; min length ≥3 chars; dedup against existing positive list
Confidence scoring
High≥14 days & ≥1k imp, no change in L7D
Med7–13 days or recent change
Low<7 days or <100 imp
Tried-and-failed detection
Audit log (L30D) scanned for previously applied recommendations
Before/after CTR or CVR delta shown for any repeated suggestion
Recommendation not suppressed — history surfaced for AM to judge
≥3 different levers tried without improvement → escalation flag
Targeting flags
homepage excluded — cat-0 add-back recommended first when underspending
platform coverage — flagged when selected platforms cover <80% of market traffic
pacing enabled — flagged; pacing OFF is the strategic default

How targeting recommendations work

Every recommendation starts by identifying the binding constraint — the one thing that is actually limiting the campaign right now. The optimizer never recommends expanding reach when the budget is the problem, and never recommends raising the budget when inventory is the ceiling.

C
Budget = cap (or exhaust early) — too much inventory relative to budget
  • Campaign is spending at its daily limit. The fix is spending on better impressions, not more impressions.
  • If targeting type is auto: tighten targeting — add positive keyword targeting with high-intent terms, or exclude diluting categories.
  • If targeting type is not auto: narrow the category list, trim low-CTR keywords from the positive list, or add negative keywords to filter irrelevant queries.
  • exhaust_early + positive keywords: trim the keyword list — do not raise CPC, which accelerates early exhaustion.

U
Budget = underspend — not enough reach to fill the budget
  • Campaign cannot find enough matching impressions. Fix: expand targeting progressively until the addressable market covers the daily budget. Never reduce the budget.
  • CPC raise rule: only recommend raising CPC when the campaign’s current CPC is below the market average for the keywords/categories shown.
  • If targeting type is auto: apply positive keyword targeting to reach more high-intent users and broaden the effective market.
  • If targeting type is not auto: expand category list → add cat-0 (homepage) if needed → expand keyword list → open to broader inventory as last resort.
  • Never remove negative keywords to fix underspend.
Example 1 — Cap, keyword campaign
Budget: cap (97% used), has positive keywords
→ Add negative keywords to filter irrelevant queries. Expanding reach would not help — the budget is already exhausted.
Example 2 — Underspend, list too narrow
Budget: underspend (31%), keyword list covers only €25/day of €80/day budget
→ Expand the positive keyword list to close the reach gap. Root cause = reach. Raising CPC would not help.
Example 3 — Underspend, high impression share
Budget: underspend (40%), 65% impression share already
→ Raise CPC — winning most available auctions, more reach is not available at current bid. Root cause = cpc. Keyword expansion would not help.
Example 4 — Underspend, homepage excluded
Budget: underspend (22%), category targeting only, homepage excluded
→ Remove category targeting to unlock homepage inventory. Root cause = reach → homepage_excluded = true.

Tech stack

Date Field Old Value New Value By