Skip to content

Commit 27b7427

Browse files
committed
Introduce flight priority again
1 parent 2cb74a1 commit 27b7427

File tree

2 files changed

+115
-13
lines changed

2 files changed

+115
-13
lines changed

adserver/decisionengine/backends.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from ..constants import HOUSE_CAMPAIGN
1515
from ..constants import PAID_CAMPAIGN
1616
from ..constants import PUBLISHER_HOUSE_CAMPAIGN
17+
from ..models import Advertisement
1718
from ..models import Flight
1819
from ..models import Region
1920
from ..models import Topic
@@ -269,7 +270,7 @@ def get_candidate_flights(self):
269270
).filter(num_ads__gt=0)
270271

271272
# Ensure we prefetch necessary data so it doesn't result in N queries for each flight
272-
return flights.select_related("campaign")
273+
return flights.select_related()
273274

274275
def filter_flight(self, flight, regions=None, topics=None):
275276
"""
@@ -373,6 +374,16 @@ def select_flight(self):
373374
* Prioritize the flight that needs the most impressions
374375
"""
375376
flights = self.get_candidate_flights()
377+
flights = flights.prefetch_related(
378+
models.Prefetch(
379+
"advertisements",
380+
queryset=Advertisement.objects.filter(
381+
live=True, ad_types__slug__in=self.ad_types
382+
),
383+
to_attr="matching_ads",
384+
),
385+
"matching_ads__ad_types",
386+
)
376387

377388
paid_flights = []
378389
affiliate_flights = []
@@ -468,6 +479,15 @@ def select_flight(self):
468479
)
469480
)
470481

482+
# Boost the weight of this flight if it matches a high priority placement
483+
priority = 1
484+
for ad in flight.matching_ads:
485+
placement = self.get_placement(ad)
486+
if placement:
487+
priority = max(priority, placement.get("priority", 1))
488+
489+
weighted_clicks_needed_this_interval *= priority
490+
471491
flight_range.append(
472492
[
473493
total_clicks_needed,
@@ -563,14 +583,18 @@ def select_ad_for_flight(self, flight):
563583
if self.ad_slug:
564584
# Ignore live and adtype checks when forcing a specific ad
565585
candidate_ads = flight.advertisements.filter(slug=self.ad_slug)
586+
candidate_ads = candidate_ads.select_related("flight").prefetch_related(
587+
"ad_types"
588+
)
589+
elif hasattr(flight, "matching_ads"):
590+
candidate_ads = flight.matching_ads
566591
else:
567592
candidate_ads = flight.advertisements.filter(
568593
live=True, ad_types__slug__in=self.ad_types
569594
)
570-
571-
candidate_ads = candidate_ads.select_related("flight").prefetch_related(
572-
"ad_types"
573-
)
595+
candidate_ads = candidate_ads.select_related("flight").prefetch_related(
596+
"ad_types"
597+
)
574598

575599
# Get similarity scores for candidate ads if embedding support is available
576600
# Reuse the publisher_embedding and domain_embedding fetched earlier to avoid duplicate queries

adserver/tests/test_decision_engine.py

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -518,21 +518,20 @@ def test_database_queries_made(self):
518518
flights = list(self.probabilistic_backend.get_candidate_flights())
519519
self.assertEqual(len(flights), 3)
520520

521-
with self.assertNumQueries(1):
522-
# This should just be the same query from `get_candidate_flights` above
521+
with self.assertNumQueries(3):
522+
# One for flights, one for ads, one for ad types (due to prefetch)
523523
flight = self.probabilistic_backend.select_flight()
524524

525-
with self.assertNumQueries(2):
526-
# One query to get the specific ad for the chosen flight
527-
# One to prefetch all the ad types
525+
with self.assertNumQueries(0):
526+
# No queries because we used the prefetched ads
528527
ad = self.probabilistic_backend.select_ad_for_flight(flight)
529528
self.assertTrue(ad in self.possible_ads, ad)
530529

531530
with self.assertNumQueries(3):
532531
# Three total queries to get an ad placement
533-
# 1. Get all the candidate flights
534-
# 2. Choose the specific ad for the chosen flight
535-
# 3. Prefetch the ad types for all the ads in the chosen flight
532+
# 1. Get all the candidate flights (and prefetch ads/adtypes)
533+
# 2. Choose the specific ad for the chosen flight (0 queries)
534+
# 3. get_placement (0 queries as it uses prefetch)
536535
ad, _ = self.probabilistic_backend.get_ad_and_placement()
537536
self.assertTrue(ad in self.possible_ads, ad)
538537

@@ -620,6 +619,85 @@ def test_click_probability(self):
620619
ad, _ = self.probabilistic_backend.get_ad_and_placement()
621620
self.assertEqual(ad, None)
622621

622+
def test_flight_matching_priority(self):
623+
# Remove existing flights
624+
for flight in Flight.objects.all():
625+
flight.live = False
626+
flight.save()
627+
628+
# Priority 1
629+
ad_type_a = get(AdType, has_image=False, slug="a")
630+
# Priority 10
631+
ad_type_b = get(AdType, has_image=False, slug="b")
632+
633+
placements = [
634+
{"div_id": "a", "ad_type": "a", "priority": 1},
635+
{"div_id": "b", "ad_type": "b", "priority": 10},
636+
]
637+
638+
# Flight 1: Matches placement A (priority 1)
639+
flight1 = get(
640+
Flight,
641+
live=True,
642+
campaign=self.campaign,
643+
sold_clicks=1000,
644+
start_date=get_ad_day().date(),
645+
end_date=get_ad_day().date() + datetime.timedelta(days=30),
646+
pacing_interval=24 * 60 * 60,
647+
)
648+
ad1 = get(
649+
Advertisement,
650+
slug="ad1",
651+
live=True,
652+
flight=flight1,
653+
)
654+
ad1.ad_types.add(ad_type_a)
655+
656+
# Flight 2: Matches placement B (priority 10)
657+
flight2 = get(
658+
Flight,
659+
live=True,
660+
campaign=self.campaign,
661+
sold_clicks=1000,
662+
start_date=get_ad_day().date(),
663+
end_date=get_ad_day().date() + datetime.timedelta(days=30),
664+
pacing_interval=24 * 60 * 60,
665+
)
666+
ad2 = get(
667+
Advertisement,
668+
slug="ad2",
669+
live=True,
670+
flight=flight2,
671+
)
672+
ad2.ad_types.add(ad_type_b)
673+
674+
backend = ProbabilisticFlightBackend(
675+
request=self.request, placements=placements, publisher=self.publisher
676+
)
677+
678+
flight1_count = 0
679+
flight2_count = 0
680+
iterations = 1000
681+
682+
# Ensure base weights are equal
683+
self.assertEqual(
684+
flight1.weighted_clicks_needed_this_interval(self.publisher),
685+
flight2.weighted_clicks_needed_this_interval(self.publisher),
686+
)
687+
688+
for _ in range(iterations):
689+
selected = backend.select_flight()
690+
if selected == flight1:
691+
flight1_count += 1
692+
elif selected == flight2:
693+
flight2_count += 1
694+
695+
# Avoid div by 0 and handle potential 0 counts
696+
ratio = flight2_count / (flight1_count + 1)
697+
698+
# Used to be ~1, now should be ~10
699+
self.assertGreater(ratio, 5.0)
700+
623701
def test_publisher_campaign_type_restrictions(self):
624702
self.campaign.campaign_type = PAID_CAMPAIGN
625703
self.campaign.save()

0 commit comments

Comments
 (0)