# My Notes:
- Make sure to ask if the timezone matters, e.g. do we need to send a batch of notifications
at 12 PM Eastern Time and another batch at 12 PM Pacific Time?
- Ask clarifying questions, like what if there is a tie where two events happen at the same time.
What should you do in that scenario? Just take the first event.
"""
The solution you are writing is part of a marketing engine for our StubHub website.
You will be implementing the setup and business logic for different marketing campaigns that find “relevant” events for a user and notifies them accordingly.
We are first looking for the correctness and performance of your solution, but also the extensibility and long-term maintainability.
Q1
This code is missing some key elements. Define the MarketingEngine class and implement a send_customer_notifications method to notify the customer of all the events happening in the same city as the customer.
While in the real world we would notify the customer of these events via email or text, for the purposes of this exercise, we want you to output the text content we intend to send to the customer and print it to the console.
Please note that this method will be invoked multiple times for multiple different customers.
Q2
Extend the solution to add a new campaign which sends a notification to the customer with the event closest to their next or upcoming birthday.
Please note that this campaign will be run multiple times for multiple different customers.
# Just pick the first event
# 100_000 customers
Q3
We now need to implement a new campaign that notifies the customer of the 5 closest events to the customer based on the distance between the customer and the event.
You should assume that we have about 10 million events and we need to call this per user.
Q4
In the stubhub backend we have a simple restful api that you can call that will return prices for events:
For all event prices: https://sh-mockapi.azurewebsites.net/api/ticketprice
For a price per event: https://sh-mockapi.azurewebsites.net/api/ticketprice?eventId={EventId}
i.e https://sh-mockapi.azurewebsites.net/api/ticketprice?eventId=1
Can you call this service from your solution to send a notification of the 5 cheapest tickets within a Y mile radius of the customer? We can test with various radiuses.
"""
from bisect import bisect_left
from collections import defaultdict
from dataclasses import dataclass
from datetime import datetime, timedelta
from dateutil import parser
from math import sqrt
import requests
from typing import List
from requests.models import Response
class City:
name: str
x_cor: int
y_cor: int
'''
/*-------------------------------------
Coordinates are roughly to scale with miles in the USA
2000 +----------------------+
| |
| |
Y | |
| |
| |
| |
| |
0 +----------------------+
0 X 4000
---------------------------------------*/
'''
# Assume a static number of cities
CITY_MAP : dict[str, City]= {
'New York': City('New York', 3572, 1455),
'Los Angeles': City('Los Angeles', 462, 975),
'Boston': City('Boston', 3778, 1566),
'Chicago': City('Chicago', 2608, 1525),
'San Francisco': City('San Francisco', 183, 1233),
'Washington': City('Washington', 3358, 1320)
}
def distance(city1: City, city2: City) -> float:
return sqrt(
(city1.x_cor - city2.x_cor) * (city1.x_cor - city2.x_cor) +
(city1.y_cor - city2.y_cor) * (city1.y_cor - city2.y_cor)
)
class Event:
id: int
name: str
city: str
event_date: datetime
class Customer:
id: int
name: str
city: str
birth_date: datetime
class MarketingEngine:
def __init__(self, events: List[Event]):
now: datetime = datetime.now()
self.events: List[Event] = [
event for event in events if event.event_date >= now
]
self.city_to_events: dict[str, List[Event]] = defaultdict(list)
for event in self.events:
self.city_to_events[event.city].append(event)
event_date_to_event: dict[datetime, Event] = {event.event_date: event for event in self.events}
self.events_sorted_by_date = sorted(
event_date_to_event.values(), key=lambda event: event.event_date
)
self.closest_threshold: int = 5
self.prices_ttl: timedelta = timedelta(days=1)
self.last_price_cache: datetime = datetime.now()
price_data = requests.get("https://sh-mockapi.azurewebsites.net/api/ticketprice").json()
self.id_to_event = {int(event["Id"]): event for event in price_data}
def send_customer_notifications(self, customer: Customer) -> None:
# "Sends" notifications via the print method
print(f"Calling send_customer_notifications for customer {customer.name}")
result = self.city_to_events.get(customer.city, [])
print(result)
def send_customer_notifications_by_birthday(self, customer: Customer) -> None:
# log(|EVENTS|)
if not self.events_sorted_by_date:
print(f"No events for {customer}")
base_birthdate = customer.birth_date
now: datetime = datetime.now()
next_birthday = datetime(now.year, base_birthdate.month, base_birthdate.day)
if now > next_birthday:
next_birthday = datetime(now.year + 1, next_birthday.month, next_birthday.day)
print(f"{next_birthday=}")
idx = bisect_left(self.events_sorted_by_date, next_birthday, key=lambda event: event.event_date)
# If you're sandwiched between two events, which is closer?
if idx > 0:
time_diff_right = abs(self.events_sorted_by_date[idx].event_date - next_birthday)
time_diff_left = abs(self.events_sorted_by_date[idx - 1].event_date - next_birthday)
if time_diff_left <= time_diff_right:
print(self.events_sorted_by_date[idx - 1])
return
print(self.events_sorted_by_date[idx])
def send_customer_notifications_by_proximity(self, customer: Customer) -> None:
customer_city = CITY_MAP.get(customer.city)
if not customer_city:
print(f"No city found for {customer}")
result = []
nearest_cities = sorted(CITY_MAP.values(), key=lambda city: distance(customer_city, city))
for city in nearest_cities:
events = self.city_to_events[city.name]
for event in events:
result.append(event)
if len(result) == self.closest_threshold:
break
if len(result) == self.closest_threshold:
break
print("Closest events: ")
print(result)
def part_4(self, customer: Customer, radius: int) -> None:
customer_city = CITY_MAP.get(customer.city)
if not customer_city:
print(f"No city found for {customer}")
return
now = datetime.now()
if now - self.last_price_cache < self.prices_ttl:
price_data = requests.get("https://sh-mockapi.azurewebsites.net/api/ticketprice").json()
# print(price_data)
self.id_to_event = {int(event["Id"]): event for event in price_data}
self.last_price_cache = now
# 5 cheapest tickets in a y-mile radius
cities_within_radius = [
city for city in CITY_MAP.values() if distance(city, customer_city) <= radius
]
event_ids_in_the_radius: List[int] = []
for city in cities_within_radius:
event_ids_in_the_radius.extend(event.id for event in self.city_to_events.get(city.name, []))
print(f"{event_ids_in_the_radius=}")
# List[(price, event)]
prices_and_events = []
for event_id in event_ids_in_the_radius:
price = self.id_to_event[event_id]["Price"]
prices_and_events.append((float(price), self.id_to_event[event_id]))
prices_and_events.sort()
if len(prices_and_events) < 5:
prices_and_events = prices_and_events[:5]
print([event for _, event in prices_and_events])
def main():
events: list[Event] = [
Event(1, "Phantom of the Opera", "New York", parser.parse("2023-12-23")), # X
Event(2, "Metallica", "Los Angeles", parser.parse("2024-12-02")), # OK
Event(3, "Metallica", "New York", parser.parse("2024-12-06")), # OK
Event(4, "Metallica", "Boston", parser.parse("2024-10-23")), # OK
Event(5, "LadyGaGa", "New York", parser.parse("2023-09-20")),
Event(6, "LadyGaGa", "Boston", parser.parse("2024-08-01")), # OK
Event(7, "LadyGaGa", "Chicago", parser.parse("2024-07-04")), # OK
Event(8, "LadyGaGa", "San Francisco", parser.parse("2024-07-07")), #OK
Event(9, "LadyGaGa", "Washington", parser.parse("2023-05-22")),
Event(10, "Metallica", "Chicago", parser.parse("2024-01-01")),
Event(11, "Phantom of the Opera", "San Francisco", parser.parse("2024-07-04")),
Event(12, "Phantom of the Opera", "Chicago", parser.parse("2025-05-15")),
]
customer: Customer = Customer(1, "Amos", "New York", parser.parse("1995-05-11"))
engine: MarketingEngine = MarketingEngine(events)
# engine.send_customer_notifications(customer)
# engine.send_customer_notifications_by_birthday(customer)
# print()
# print("By proximity: ")
# engine.send_customer_notifications_by_proximity(customer)
engine.part_4(customer, 2000)