From b2927eba9723b28ab0e2a97f15c21aa1eebcc8cd Mon Sep 17 00:00:00 2001 From: Sigma Software Date: Wed, 24 Dec 2025 00:15:06 +0200 Subject: [PATCH] Upgrade bid adjustments matching strategy --- bidadjustment/apply.go | 68 ++- bidadjustment/apply_test.go | 692 ++++++++++++++++++++++++++++-- bidadjustment/build_rules.go | 5 +- bidadjustment/build_rules_test.go | 94 +++- 4 files changed, 802 insertions(+), 57 deletions(-) diff --git a/bidadjustment/apply.go b/bidadjustment/apply.go index 864d6bf6fb5..e34dc60301f 100644 --- a/bidadjustment/apply.go +++ b/bidadjustment/apply.go @@ -2,6 +2,7 @@ package bidadjustment import ( "math" + "strings" "github.com/prebid/prebid-server/v3/adapters" "github.com/prebid/prebid-server/v3/openrtb_ext" @@ -15,7 +16,7 @@ const ( Delimiter = "|" ) -const maxNumOfCombos = 8 +const maxNumOfCombos = 12 const pricePrecision float64 = 10000 // Rounds to 4 Decimal Places const minBid = 0.1 @@ -23,7 +24,7 @@ const minBid = 0.1 func Apply(rules map[string][]openrtb_ext.Adjustment, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo, bidType string) (float64, string) { var adjustments []openrtb_ext.Adjustment if len(rules) > 0 { - adjustments = get(rules, bidType, string(bidderName), bidInfo.Bid.DealID) + adjustments = get(rules, bidType, string(bidInfo.Seat), string(bidderName), bidInfo.Bid.DealID) } else { return bidInfo.Bid.Price, currency } @@ -67,27 +68,58 @@ func apply(adjustments []openrtb_ext.Adjustment, bidPrice float64, currency stri // get() should return the highest priority slice of adjustments from the map that we can match with the given bid info // given the bid info, we create the same format of combinations that's present in the key of the ruleToAdjustments map // the slice is ordered by priority from highest to lowest, as soon as we find a match, we return that slice -func get(rules map[string][]openrtb_ext.Adjustment, bidType, bidderName, dealID string) []openrtb_ext.Adjustment { +func get(rules map[string][]openrtb_ext.Adjustment, bidType, seat, bidderName, dealID string) []openrtb_ext.Adjustment { priorityRules := [maxNumOfCombos]string{} - if dealID != "" { - priorityRules[0] = bidType + Delimiter + bidderName + Delimiter + dealID - priorityRules[1] = bidType + Delimiter + bidderName + Delimiter + WildCard - priorityRules[2] = bidType + Delimiter + WildCard + Delimiter + dealID - priorityRules[3] = WildCard + Delimiter + bidderName + Delimiter + dealID - priorityRules[4] = bidType + Delimiter + WildCard + Delimiter + WildCard - priorityRules[5] = WildCard + Delimiter + bidderName + Delimiter + WildCard - priorityRules[6] = WildCard + Delimiter + WildCard + Delimiter + dealID - priorityRules[7] = WildCard + Delimiter + WildCard + Delimiter + WildCard + + // lowercase the parameter to make the rules it case insensitive + bidType = strings.ToLower(bidType) + bidderName = strings.ToLower(bidderName) + dealID = strings.ToLower(dealID) + seat = strings.ToLower(seat) + + if seat != "" { + if dealID != "" { + priorityRules[0] = bidType + Delimiter + seat + Delimiter + dealID // type|seat|dealID + priorityRules[1] = bidType + Delimiter + bidderName + Delimiter + dealID // type|bidder|dealID + priorityRules[2] = bidType + Delimiter + seat + Delimiter + WildCard // type|seat|* + priorityRules[3] = bidType + Delimiter + bidderName + Delimiter + WildCard // type|bidder|* + priorityRules[4] = bidType + Delimiter + WildCard + Delimiter + dealID // type|*|dealID + priorityRules[5] = WildCard + Delimiter + seat + Delimiter + dealID // *|seat|dealID + priorityRules[6] = WildCard + Delimiter + bidderName + Delimiter + dealID // *|bidder|dealID + priorityRules[7] = bidType + Delimiter + WildCard + Delimiter + WildCard // type|*|* + priorityRules[8] = WildCard + Delimiter + seat + Delimiter + WildCard // *|seat|* + priorityRules[9] = WildCard + Delimiter + bidderName + Delimiter + WildCard // *|bidder|* + priorityRules[10] = WildCard + Delimiter + WildCard + Delimiter + dealID // *|*|dealID + priorityRules[11] = WildCard + Delimiter + WildCard + Delimiter + WildCard // *|*|* + } else { + priorityRules[0] = bidType + Delimiter + seat + Delimiter + WildCard // type|seat|* + priorityRules[1] = bidType + Delimiter + bidderName + Delimiter + WildCard // type|bidder|* + priorityRules[2] = bidType + Delimiter + WildCard + Delimiter + WildCard // type|*|* + priorityRules[3] = WildCard + Delimiter + seat + Delimiter + WildCard // *|seat|* + priorityRules[4] = WildCard + Delimiter + bidderName + Delimiter + WildCard // *|bidder|* + priorityRules[5] = WildCard + Delimiter + WildCard + Delimiter + WildCard // *|*|* + } } else { - priorityRules[0] = bidType + Delimiter + bidderName + Delimiter + WildCard - priorityRules[1] = bidType + Delimiter + WildCard + Delimiter + WildCard - priorityRules[2] = WildCard + Delimiter + bidderName + Delimiter + WildCard - priorityRules[3] = WildCard + Delimiter + WildCard + Delimiter + WildCard + if dealID != "" { + priorityRules[0] = bidType + Delimiter + bidderName + Delimiter + dealID // type|bidder|dealID + priorityRules[1] = bidType + Delimiter + bidderName + Delimiter + WildCard // type|bidder|* + priorityRules[2] = bidType + Delimiter + WildCard + Delimiter + dealID // type|*|dealID + priorityRules[3] = WildCard + Delimiter + bidderName + Delimiter + dealID // *|bidder|dealID + priorityRules[4] = bidType + Delimiter + WildCard + Delimiter + WildCard // type|*|* + priorityRules[5] = WildCard + Delimiter + bidderName + Delimiter + WildCard // *|bidder|* + priorityRules[6] = WildCard + Delimiter + WildCard + Delimiter + dealID // *|*|dealID + priorityRules[7] = WildCard + Delimiter + WildCard + Delimiter + WildCard // *|*|* + } else { + priorityRules[0] = bidType + Delimiter + bidderName + Delimiter + WildCard // type|bidder|* + priorityRules[1] = bidType + Delimiter + WildCard + Delimiter + WildCard // type|*|* + priorityRules[2] = WildCard + Delimiter + bidderName + Delimiter + WildCard // *|bidder|* + priorityRules[3] = WildCard + Delimiter + WildCard + Delimiter + WildCard // *|*|* + } } for _, rule := range priorityRules { - if _, ok := rules[rule]; ok { - return rules[rule] + if matchingRule, ok := rules[rule]; ok { + return matchingRule } } return nil diff --git a/bidadjustment/apply_test.go b/bidadjustment/apply_test.go index 1a9bf75a20c..f765df85e05 100644 --- a/bidadjustment/apply_test.go +++ b/bidadjustment/apply_test.go @@ -27,7 +27,7 @@ func TestGetAndApply(t *testing.T) { expectedCurrency string }{ { - name: "CpmAdjustment", + name: "CpmAdjustment no seat", givenBidInfo: &adapters.TypedBid{ Bid: &openrtb2.Bid{ Price: 10.0, @@ -36,14 +36,44 @@ func TestGetAndApply(t *testing.T) { }, givenBidType: string(openrtb_ext.BidTypeBanner), givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "banner|bidderA|dealId": { + "banner|biddera|dealid": { + { + Type: AdjustmentTypeCPM, + Value: 1.0, + Currency: adjCur, + }, + }, + "banner|biddera|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 2.0, + }, + }, + }, + givenBidderName: "bidderA", + setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, + expectedBidPrice: 7.5, + expectedCurrency: bidCur, + }, + { + name: "CpmAdjustment with seat", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 10.0, + DealID: "dealId", + }, + Seat: "seatA", + }, + givenBidType: string(openrtb_ext.BidTypeBanner), + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|seata|dealid": { { Type: AdjustmentTypeCPM, Value: 1.0, Currency: adjCur, }, }, - "banner|bidderA|*": { + "banner|seata|*": { { Type: AdjustmentTypeMultiplier, Value: 2.0, @@ -56,23 +86,54 @@ func TestGetAndApply(t *testing.T) { expectedCurrency: bidCur, }, { - name: "StaticAdjustment", + name: "StaticAdjustment no seat", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 10.0, + DealID: "dealId", + }, + }, + givenBidType: string(openrtb_ext.BidTypeBanner), + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|biddera|dealid": { + { + Type: AdjustmentTypeCPM, + Value: 1.0, + Currency: adjCur, + }, + }, + "banner|biddera|*": { + { + Type: AdjustmentTypeStatic, + Value: 2.0, + Currency: adjCur, + }, + }, + }, + givenBidderName: "bidderA", + setMock: nil, + expectedBidPrice: 2.0, + expectedCurrency: adjCur, + }, + { + name: "StaticAdjustment with seat", givenBidInfo: &adapters.TypedBid{ Bid: &openrtb2.Bid{ Price: 10.0, DealID: "dealId", }, + Seat: "seatA", }, givenBidType: string(openrtb_ext.BidTypeBanner), givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "*|bidderA|dealId": { + "*|seata|dealid": { { Type: AdjustmentTypeCPM, Value: 1.0, Currency: adjCur, }, }, - "banner|bidderA|*": { + "banner|seata|*": { { Type: AdjustmentTypeStatic, Value: 2.0, @@ -86,16 +147,46 @@ func TestGetAndApply(t *testing.T) { expectedCurrency: adjCur, }, { - name: "MultiplierAdjustment", + name: "MultiplierAdjustment no seat", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 10.0, + DealID: "dealId", + }, + }, + givenBidType: VideoInstream, + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|*|dealid": { + { + Type: AdjustmentTypeCPM, + Value: 1.0, + Currency: adjCur, + }, + }, + "video-instream|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 2.0, + }, + }, + }, + givenBidderName: "bidderA", + setMock: nil, + expectedBidPrice: 20.0, + expectedCurrency: bidCur, + }, + { + name: "MultiplierAdjustment with seat", givenBidInfo: &adapters.TypedBid{ Bid: &openrtb2.Bid{ Price: 10.0, DealID: "dealId", }, + Seat: "seatA", }, givenBidType: VideoInstream, givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "*|*|dealId": { + "*|*|dealid": { { Type: AdjustmentTypeCPM, Value: 1.0, @@ -115,12 +206,40 @@ func TestGetAndApply(t *testing.T) { expectedCurrency: bidCur, }, { - name: "CpmAndMultiplierAdjustments", + name: "CpmAndMultiplierAdjustments no seat", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 10.0, + DealID: "dealId", + }, + }, + givenBidType: VideoInstream, + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "video-instream|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 2.0, + }, + { + Type: AdjustmentTypeCPM, + Value: 1.0, + Currency: adjCur, + }, + }, + }, + givenBidderName: "bidderA", + setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, + expectedBidPrice: 17.5, + expectedCurrency: bidCur, + }, + { + name: "CpmAndMultiplierAdjustments with seat", givenBidInfo: &adapters.TypedBid{ Bid: &openrtb2.Bid{ Price: 10.0, DealID: "dealId", }, + Seat: "seatA", }, givenBidType: VideoInstream, givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ @@ -142,16 +261,40 @@ func TestGetAndApply(t *testing.T) { expectedCurrency: bidCur, }, { - name: "DealIdPresentAndNegativeAdjustedPrice", + name: "DealIdPresentAndNegativeAdjustedPrice no seat", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 1.0, + DealID: "dealId", + }, + }, + givenBidType: VideoInstream, + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|*|dealid": { + { + Type: AdjustmentTypeCPM, + Value: 1.0, + Currency: adjCur, + }, + }, + }, + givenBidderName: "bidderA", + setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, + expectedBidPrice: 0.0, + expectedCurrency: bidCur, + }, + { + name: "DealIdPresentAndNegativeAdjustedPrice with seat", givenBidInfo: &adapters.TypedBid{ Bid: &openrtb2.Bid{ Price: 1.0, DealID: "dealId", }, + Seat: "seatA", }, givenBidType: VideoInstream, givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "*|*|dealId": { + "*|*|dealid": { { Type: AdjustmentTypeCPM, Value: 1.0, @@ -165,7 +308,7 @@ func TestGetAndApply(t *testing.T) { expectedCurrency: bidCur, }, { - name: "NoDealIdNegativeAdjustedPrice", + name: "NoDealIdNegativeAdjustedPrice no seat", givenBidInfo: &adapters.TypedBid{ Bid: &openrtb2.Bid{ Price: 1.0, @@ -187,6 +330,30 @@ func TestGetAndApply(t *testing.T) { expectedBidPrice: 0.1, expectedCurrency: bidCur, }, + { + name: "NoDealIdNegativeAdjustedPrice with seat", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 1.0, + DealID: "", + }, + Seat: "seatA", + }, + givenBidType: string(openrtb_ext.BidTypeAudio), + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|*|*": { + { + Type: AdjustmentTypeCPM, + Value: 1.0, + Currency: adjCur, + }, + }, + }, + givenBidderName: "bidderA", + setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, + expectedBidPrice: 0.1, + expectedCurrency: bidCur, + }, { name: "NilMap", givenBidInfo: &adapters.TypedBid{ @@ -297,25 +464,26 @@ func TestApply(t *testing.T) { } } -func TestGet(t *testing.T) { +func TestGetNoSeat(t *testing.T) { testCases := []struct { name string givenRuleToAdjustments map[string][]openrtb_ext.Adjustment givenBidType openrtb_ext.BidType givenBidderName openrtb_ext.BidderName givenDealId string + givenSeat string expected []openrtb_ext.Adjustment }{ { name: "Priority1", givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "banner|bidderA|dealId": { + "banner|biddera|dealid": { { Type: AdjustmentTypeMultiplier, Value: 1.1, }, }, - "banner|bidderA|*": { + "banner|biddera|*": { { Type: AdjustmentTypeMultiplier, Value: 2.0, @@ -330,13 +498,13 @@ func TestGet(t *testing.T) { { name: "Priority2", givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "banner|bidderA|*": { + "banner|biddera|*": { { Type: AdjustmentTypeStatic, Value: 5.0, }, }, - "banner|*|dealId": { + "banner|*|dealid": { { Type: AdjustmentTypeMultiplier, Value: 2.0, @@ -351,14 +519,14 @@ func TestGet(t *testing.T) { { name: "Priority3", givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "banner|*|dealId": { + "banner|*|dealid": { { Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD", }, }, - "*|bidderA|dealId": { + "*|biddera|dealid": { { Type: AdjustmentTypeMultiplier, Value: 1.1, @@ -373,7 +541,7 @@ func TestGet(t *testing.T) { { name: "Priority4", givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "*|bidderA|dealId": { + "*|biddera|dealid": { { Type: AdjustmentTypeCPM, Value: 3.0, @@ -402,7 +570,7 @@ func TestGet(t *testing.T) { Currency: "USD", }, }, - "*|bidderA|*": { + "*|biddera|*": { { Type: AdjustmentTypeMultiplier, Value: 1.1, @@ -417,14 +585,14 @@ func TestGet(t *testing.T) { { name: "Priority6", givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "*|bidderA|*": { + "*|biddera|*": { { Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD", }, }, - "*|*|dealId": { + "*|*|dealid": { { Type: AdjustmentTypeMultiplier, Value: 1.1, @@ -439,7 +607,7 @@ func TestGet(t *testing.T) { { name: "Priority7", givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "*|*|dealId": { + "*|*|dealid": { { Type: AdjustmentTypeCPM, Value: 3.0, @@ -463,8 +631,9 @@ func TestGet(t *testing.T) { givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ "*|*|*": { { - Type: AdjustmentTypeMultiplier, - Value: 1.1, + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", }, }, "banner|bidderA|dealId": { @@ -477,15 +646,16 @@ func TestGet(t *testing.T) { givenBidType: openrtb_ext.BidTypeBanner, givenBidderName: "bidderB", givenDealId: "dealId", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, }, { name: "NoDealID", givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "banner|bidderA|*": { + "banner|biddera|*": { { - Type: AdjustmentTypeMultiplier, - Value: 1.1, + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", }, }, "banner|*|*": { @@ -498,18 +668,471 @@ func TestGet(t *testing.T) { givenBidType: openrtb_ext.BidTypeBanner, givenBidderName: "bidderA", givenDealId: "", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, }, { name: "NoPriorityRulesMatch", givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "banner|bidderA|dealId": { + "banner|biddera|dealid": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + "banner|biddera|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 2.0, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeVideo, + givenBidderName: "bidderB", + givenDealId: "diffDealId", + expected: nil, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + adjArray := get(test.givenRuleToAdjustments, string(test.givenBidType), test.givenSeat, string(test.givenBidderName), test.givenDealId) + assert.Equal(t, test.expected, adjArray) + }) + } +} + +func TestGetWithSeat(t *testing.T) { + testCases := []struct { + name string + givenRuleToAdjustments map[string][]openrtb_ext.Adjustment + givenBidType openrtb_ext.BidType + givenBidderName openrtb_ext.BidderName + givenDealId string + givenSeat string + expected []openrtb_ext.Adjustment + }{ + { + name: "Priority1", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|seata|dealid": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + "banner|biddera|dealid": { + { + Type: AdjustmentTypeMultiplier, + Value: 2.0, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, + }, + { + name: "Priority2", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|biddera|dealid": { + { + Type: AdjustmentTypeStatic, + Value: 5.0, + }, + }, + "banner|seata|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 2.0, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 5.0}}, + }, + { + name: "Priority3", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|seata|*": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "banner|biddera|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority4", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|biddera|*": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "banner|*|dealid": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority5", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|*|dealid": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "*|seat|dealid": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority6", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|seata|dealid": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "*|biddera|dealid": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority7", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|biddera|dealid": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "banner|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority8", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|*|*": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "*|seata|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderB", + givenDealId: "dealId", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority9", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|seata|*": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "*|bidderb|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderB", + givenDealId: "dealId", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority10", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|bidderb|*": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "*|*|dealid": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderB", + givenDealId: "dealId", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority11", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|*|dealid": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "*|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderB", + givenDealId: "dealId", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority12", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|*|*": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "*|biddera|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderB", + givenDealId: "dealId", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "NoDealID Priority 1", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|seata|*": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "banner|biddera|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "NoDealID Priority 2", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|biddera|*": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "banner|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "NoDealID Priority 3", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|*|*": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "*|seata|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "NoDealID Priority 4", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|seata|*": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "*|biddera|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "NoDealID Priority 5", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|biddera|*": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "*|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "NoDealID Priority 6", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|*|*": { + { + Type: AdjustmentTypeCPM, + Value: 3.0, + Currency: "USD", + }, + }, + "banner|bidderb|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "", + givenSeat: "seatA", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, + }, + { + name: "NoPriorityRulesMatch", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|seata|dealid": { { Type: AdjustmentTypeMultiplier, Value: 1.1, }, }, - "banner|bidderA|*": { + "banner|seat2|*": { { Type: AdjustmentTypeMultiplier, Value: 2.0, @@ -519,13 +1142,14 @@ func TestGet(t *testing.T) { givenBidType: openrtb_ext.BidTypeVideo, givenBidderName: "bidderB", givenDealId: "diffDealId", + givenSeat: "seatA", expected: nil, }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - adjArray := get(test.givenRuleToAdjustments, string(test.givenBidType), string(test.givenBidderName), test.givenDealId) + adjArray := get(test.givenRuleToAdjustments, string(test.givenBidType), test.givenSeat, string(test.givenBidderName), test.givenDealId) assert.Equal(t, test.expected, adjArray) }) } diff --git a/bidadjustment/build_rules.go b/bidadjustment/build_rules.go index 2d0ac7c4cd8..faae7251a6b 100644 --- a/bidadjustment/build_rules.go +++ b/bidadjustment/build_rules.go @@ -1,6 +1,8 @@ package bidadjustment import ( + "strings" + "github.com/prebid/prebid-server/v3/errortypes" "github.com/prebid/prebid-server/v3/openrtb_ext" ) @@ -31,7 +33,8 @@ func BuildRules(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) map[ func buildRulesForMediaType(mediaType string, rulesByBidder map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID, rules map[string][]openrtb_ext.Adjustment) { for bidderName := range rulesByBidder { for dealID, adjustments := range rulesByBidder[bidderName] { - rule := mediaType + Delimiter + string(bidderName) + Delimiter + dealID + // lowercase the rule to make it case insensitive + rule := strings.ToLower(mediaType + Delimiter + string(bidderName) + Delimiter + dealID) rules[rule] = adjustments } } diff --git a/bidadjustment/build_rules_test.go b/bidadjustment/build_rules_test.go index 29864bcdbc2..63ffc1ee5ef 100644 --- a/bidadjustment/build_rules_test.go +++ b/bidadjustment/build_rules_test.go @@ -27,7 +27,27 @@ func TestBuildRules(t *testing.T) { }, }, expectedRules: map[string][]openrtb_ext.Adjustment{ - "banner|bidderA|dealId": { + "banner|biddera|dealid": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + }, + { + name: "OneAdjustmentWithSeat", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "seatA": { + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, + }, + }, + }, + }, + expectedRules: map[string][]openrtb_ext.Adjustment{ + "banner|seata|dealid": { { Type: AdjustmentTypeMultiplier, Value: 1.1, @@ -61,13 +81,79 @@ func TestBuildRules(t *testing.T) { }, }, expectedRules: map[string][]openrtb_ext.Adjustment{ - "banner|bidderA|dealId": { + "banner|biddera|dealid": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + "banner|*|diffdealid": { + { + Type: AdjustmentTypeCPM, + Value: 1.1, + Currency: "USD", + }, + }, + "banner|*|*": { + { + Type: AdjustmentTypeStatic, + Value: 5.0, + Currency: "USD", + }, + }, + "video-instream|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + { + Type: AdjustmentTypeCPM, + Value: 0.18, + Currency: "USD", + }, + }, + "video-outstream|bidderb|*": { + { + Type: AdjustmentTypeStatic, + Value: 0.25, + Currency: "USD", + }, + }, + }, + }, + { + name: "MultipleAdjustmentsWithSeat", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "seatA": { + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, + }, + "*": { + "diffDealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 1.1, Currency: "USD"}}, + "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 5.0, Currency: "USD"}}, + }, + }, + VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "*": { + "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}, {Type: AdjustmentTypeCPM, Value: 0.18, Currency: "USD"}}, + }, + }, + VideoOutstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "seatB": { + "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 0.25, Currency: "USD"}}, + }, + }, + }, + }, + expectedRules: map[string][]openrtb_ext.Adjustment{ + "banner|seata|dealid": { { Type: AdjustmentTypeMultiplier, Value: 1.1, }, }, - "banner|*|diffDealId": { + "banner|*|diffdealid": { { Type: AdjustmentTypeCPM, Value: 1.1, @@ -92,7 +178,7 @@ func TestBuildRules(t *testing.T) { Currency: "USD", }, }, - "video-outstream|bidderB|*": { + "video-outstream|seatb|*": { { Type: AdjustmentTypeStatic, Value: 0.25,