Basic Ad Server#
This notebook implements a simple programmatic ad server using Flask. It demonstrates ad targeting, bidding, and serving logic.
# Import necessary libraries
from flask import Flask, jsonify, request, render_template
from datetime import datetime
import json
from typing import Dict
import uuid
# Initialize Flask application
app = Flask(__name__)
# Sample ad inventory data structure
# Each ad contains metadata, targeting criteria, and performance statistics
ad_inventory = [
{
"id": 1,
"name": "Summer Sale Banner",
"image_url": "https://via.placeholder.com/300x250?text=Summer+Sale",
"target_url": "https://example.com/summer-sale",
"size": "300x250",
"advertiser": "Fashion Store",
"active": True,
"targeting": {
"countries": ["US", "CA", "UK"], # Countries where ad can be shown
"languages": ["en"], # Supported languages
"devices": ["desktop", "mobile"], # Target devices
"age_range": {"min": 18, "max": 35}, # Target age range
"interests": ["fashion", "shopping"], # Target user interests
"daily_budget": 1000.00, # Maximum daily spend in USD
"cpm": 2.50 # Cost per thousand impressions
},
"statistics": {
"impressions": 0, # Number of times ad was shown
"clicks": 0, # Number of clicks received
"spent": 0.00 # Total amount spent
}
},
# Second ad entry with similar structure but different targeting
{
"id": 2,
"name": "Tech Gadget Promotion",
"image_url": "https://via.placeholder.com/728x90?text=Tech+Gadget",
"target_url": "https://example.com/gadget-promo",
"size": "728x90",
"advertiser": "Tech Shop",
"active": True,
"targeting": {
"countries": ["US", "DE", "FR"],
"languages": ["en", "de", "fr"],
"devices": ["desktop"],
"age_range": {"min": 24, "max": 55},
"interests": ["technology", "gadgets"],
"daily_budget": 1500.00,
"cpm": 3.00
},
"statistics": {
"impressions": 0,
"clicks": 0,
"spent": 0.00
}
}
]
class AdTargeting:
"""Class handling ad targeting logic and bid calculations"""
@staticmethod
def match_targeting_criteria(ad: Dict, request_data: Dict) -> bool:
"""
Check if an ad matches the targeting criteria based on request data
Args:
ad: Dictionary containing ad information and targeting rules
request_data: Dictionary containing request parameters
Returns:
bool: True if ad matches all targeting criteria, False otherwise
"""
targeting = ad['targeting']
# Verify country match
if request_data.get('country') not in targeting['countries']:
return False
# Verify language match
if request_data.get('language') not in targeting['languages']:
return False
# Verify device type match
if request_data.get('device') not in targeting['devices']:
return False
# Verify age requirements if provided
user_age = request_data.get('age')
if user_age and user_age.isdigit():
user_age = int(user_age)
if not (targeting['age_range']['min'] <= user_age <= targeting['age_range']['max']):
return False
# Check if daily budget has been exceeded
if ad['statistics']['spent'] >= targeting['daily_budget']:
return False
return True
@staticmethod
def calculate_bid_value(ad: Dict, request_data: Dict) -> float:
"""
Calculate the bid value for an ad based on targeting match quality
Args:
ad: Dictionary containing ad information
request_data: Dictionary containing request parameters
Returns:
float: Calculated bid value
"""
base_bid = ad['targeting']['cpm']
# Apply premium inventory multiplier
if request_data.get('premium'):
base_bid *= 1.2
# Calculate interest match multiplier
user_interests = request_data.get('interests', [])
if isinstance(user_interests, str):
user_interests = [i.strip() for i in user_interests.split(',') if i.strip()]
interest_match = len(set(user_interests) & set(ad['targeting']['interests']))
interest_multiplier = 1 + (0.1 * interest_match)
return base_bid * interest_multiplier
def track_impression(ad: Dict, bid_value: float):
"""
Track ad impression and update statistics
Args:
ad: Dictionary containing ad information
bid_value: Winning bid value for the impression
Returns:
str: Unique impression ID
"""
impression_id = str(uuid.uuid4())
impression = {
"id": impression_id,
"ad_id": ad['id'],
"timestamp": datetime.utcnow().isoformat(),
"ip": request.remote_addr,
"bid_value": bid_value,
"user_agent": request.headers.get('User-Agent')
}
# Update impression count and spent amount
ad['statistics']['impressions'] += 1
ad['statistics']['spent'] += bid_value / 1000 # Convert CPM to actual cost
return impression_id
@app.route('/serve-ad', methods=['GET'])
def serve_ad():
"""
API endpoint to serve an ad based on targeting criteria
Query parameters:
size: Ad size (e.g., '300x250')
country: Target country code
language: Target language code
device: Device type
age: User age
interests: Comma-separated list of interests
premium: Boolean indicating premium inventory
Returns:
JSON response with matched ad or error message
"""
# Parse and normalize request parameters
request_data = {
'size': request.args.get('size', '300x250'),
'country': request.args.get('country', 'US'),
'language': request.args.get('language', 'en'),
'device': request.args.get('device', 'desktop'),
'age': int(request.args.get('age', 0)) if request.args.get('age') else None,
'interests': request.args.get('interests', '').split(',') if request.args.get('interests') else [],
'premium': request.args.get('premium', '').lower() == 'true'
}
# Find matching ads and calculate their bid values
matching_ads = []
for ad in ad_inventory:
if (ad['size'] == request_data['size'] and
ad['active'] and
AdTargeting.match_targeting_criteria(ad, request_data)):
bid_value = AdTargeting.calculate_bid_value(ad, request_data)
matching_ads.append((ad, bid_value))
# Return error if no matching ads found
if not matching_ads:
return jsonify({
"error": "No matching ads found",
"requested_criteria": request_data
}), 404
# Select highest bidding ad
matching_ads.sort(key=lambda x: x[1], reverse=True)
selected_ad, winning_bid = matching_ads[0]
# Record impression and update statistics
track_impression(selected_ad, winning_bid)
# Remove sensitive targeting and statistics data
ad_response = selected_ad.copy()
ad_response.pop('targeting')
ad_response.pop('statistics')
return jsonify(ad_response)
@app.route('/ad-click/<int:ad_id>', methods=['GET'])
def track_click(ad_id):
"""
Track ad clicks for a specific ad
Args:
ad_id: ID of the ad that was clicked
Returns:
JSON response indicating success or failure
"""
ad = next((ad for ad in ad_inventory if ad['id'] == ad_id), None)
if ad:
ad['statistics']['clicks'] += 1
return jsonify({"status": "success", "message": "Click tracked"})
return jsonify({"status": "error", "message": "Ad not found"}), 404
@app.route('/ad-stats', methods=['GET'])
def get_statistics():
"""
Get performance statistics for all ads
Returns:
JSON response with ad statistics including impressions, clicks, CTR, and spend
"""
stats = [{
"id": ad['id'],
"name": ad['name'],
"impressions": ad['statistics']['impressions'],
"clicks": ad['statistics']['clicks'],
"ctr": (ad['statistics']['clicks'] / ad['statistics']['impressions'] * 100)
if ad['statistics']['impressions'] > 0 else 0,
"spent": ad['statistics']['spent']
} for ad in ad_inventory]
return jsonify(stats)
@app.route('/')
@app.route('/test')
def test_page():
"""Render the test page template"""
# In a notebook, rendering templates from a folder might require configuration
# or creating the file locally. For simplicity, we can return a string or simple HTML here if needed.
try:
return render_template('test.html')
except:
return "Test page. Ensure 'templates/test.html' exists."
# Run the Flask application
if __name__ == '__main__':
# use_reloader=False is required for running Flask in Jupyter notebooks
app.run(debug=True, use_reloader=False)
* Serving Flask app '__main__'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
127.0.0.1 - - [26/Nov/2025 19:17:42] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [26/Nov/2025 19:17:42] "GET /favicon.ico HTTP/1.1" 404 -