Skip to content

Commit

Permalink
Release:10-Nov-2020 - OTT-9 : Support for Deal Prioritisation for CTV…
Browse files Browse the repository at this point in the history
… / Ad Pod (#86)

* UOE-5440: Changes for capturing Pod algorithm execution time using pbmetrics (#65)

* Added function getPrometheusRegistry()

* Exported function GetPrometheusRegistry

* UOE-5440: Capturing execution time in nanoseconds for algorithms

* UOE-5440: Changes for prometheus algorithem metrics for pod using pbsmetrics

* UOE-5440: Test cases for prometheus

* UOE-5440: Added test cases

* UOE-5440: Changing buckets

* UOE-5440: changes in pbsmetrics for newly added metrics

Co-authored-by: Sachin Survase <[email protected]>
Co-authored-by: PubMatic-OpenWrap <[email protected]>
Co-authored-by: Shriprasad <[email protected]>

* UOE-5440: Fixed the Unit test issues (#72)

Fixed unit test issues 
Co-authored-by: Sachin Survase <[email protected]>
Co-authored-by: PubMatic-OpenWrap <[email protected]>
Co-authored-by: Shriprasad <[email protected]>

* UOE-5511 Support for skadnetwork in pubmatic (#73)

Co-authored-by: Isha Bharti <[email protected]>

* Resolved merge issues

* BugID:OTT-17 First Commit

* OTT-18: moved VideoAuction to selector pattern. This  required for mocking PBS response (#76)

Co-authored-by: Shriprasad <[email protected]>

* OTT-24: Basic support for sorting the deal bids in forming the final ad pod response

* OTT-24: Added changes around prebid#1503 (Proposal for Prebid Server)

* OTT-27, OTT-32 Supporting Deal Prioritization for CTV

* UOE-5616: Support wiid in pubmatic (#77)

Co-authored-by: Isha Bharti <[email protected]>

* OTT-29 Fixing Skip Dedup Map Issue

* OTT-29 Fixing Skip Dedup Map Issue

* OTT-29 Adding Video Duration in hb_pb_cat_dur key

* OTT-29 Adding Video Duration in hb_pb_cat_dur key
OTT-29 Fixing Skip Dedup Map Issue

* UOE-5741: adding omitempty for ExtImpPrebid fields

* UOE-5741: adding omitempty for ExtImpPrebid fields

* OTT-9 Adding Duration in hb_pb_cat_dur field

* OTT-9 Adding Duration in hb_pb_cat_dur field

* OTT-45: Added logger and Prometheus metrics to capture bid.id collisions (#84)

* OTT-45: Added logger and Prometheus metrics to capture bid.id collisions
Co-authored-by: Shriprasad <[email protected]>

* OTT-9 Removed duplicate import

* OTT-45: corrected comment

* OTT-9: Reverted with master changes. This changes are not required for OTT-9

Co-authored-by: Sachin Survase <[email protected]>
Co-authored-by: PubMatic-OpenWrap <[email protected]>
Co-authored-by: Shriprasad <[email protected]>
Co-authored-by: Isha Bharti <[email protected]>
Co-authored-by: Viral Vala <[email protected]>
Co-authored-by: shalmali-patil <[email protected]>
  • Loading branch information
7 people authored Nov 4, 2020
1 parent 252bcfc commit 85c6ea7
Show file tree
Hide file tree
Showing 18 changed files with 478 additions and 100 deletions.
11 changes: 9 additions & 2 deletions endpoints/openrtb2/ctv/response/adpod_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type highestCombination struct {
filteredBids map[string]*filteredBid
timeTakenCompExcl time.Duration // time taken by comp excl
timeTakenCombGen time.Duration // time taken by combination generator
nDealBids int
}

//AdPodGenerator AdPodGenerator
Expand Down Expand Up @@ -179,7 +180,7 @@ func (o *AdPodGenerator) getMaxAdPodBid(results []*highestCombination) *types.Ad
}
}

if len(result.bidIDs) > 0 && (nil == maxResult || maxResult.price < result.price) {
if len(result.bidIDs) > 0 && (nil == maxResult || maxResult.nDealBids < result.nDealBids || maxResult.price < result.price) {
maxResult = result
}
}
Expand Down Expand Up @@ -261,7 +262,7 @@ func findUniqueCombinations(data [][]*types.Bid, combination []int, maxCategoryS

ehc, inext, jnext, rc = evaluate(data[:], indices[:], totalBids, maxCategoryScore, maxDomainScore)
if nil != ehc {
if nil == hc || hc.price < ehc.price {
if nil == hc || hc.nDealBids < ehc.nDealBids || hc.price < ehc.price {
hc = ehc
} else {
// if you see current combination price lower than the highest one then break the loop
Expand Down Expand Up @@ -338,6 +339,7 @@ func evaluate(bids [][]*types.Bid, indices [][]int, totalBids int, maxCategorySc
price: 0,
categoryScore: make(map[string]int),
domainScore: make(map[string]int),
nDealBids: 0,
}
pos := 0

Expand All @@ -349,6 +351,11 @@ func evaluate(bids [][]*types.Bid, indices [][]int, totalBids int, maxCategorySc
hbc.bidIDs[pos] = bid.ID
pos++

//nDealBids
if bid.DealTierSatisfied {
hbc.nDealBids++
}

//Price
hbc.price = hbc.price + bid.Price

Expand Down
5 changes: 3 additions & 2 deletions endpoints/openrtb2/ctv/types/adpod_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import (
//Bid openrtb bid object with extra parameters
type Bid struct {
*openrtb.Bid
Duration int
FilterReasonCode constant.FilterReasonCode
Duration int
FilterReasonCode constant.FilterReasonCode
DealTierSatisfied bool
}

//ExtCTVBidResponse object for ctv bid resposne object
Expand Down
18 changes: 17 additions & 1 deletion endpoints/openrtb2/ctv/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/constant"
"github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/types"
"github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
"github.com/golang/glog"
)

Expand All @@ -21,13 +22,28 @@ func GetDurationWiseBidsBucket(bids []*types.Bid) types.BidsBuckets {
}

for k, v := range result {
sort.Slice(v[:], func(i, j int) bool { return v[i].Price > v[j].Price })
//sort.Slice(v[:], func(i, j int) bool { return v[i].Price > v[j].Price })
sortBids(v[:])
result[k] = v
}

return result
}

func sortBids(bids []*types.Bid) {
sort.Slice(bids, func(i, j int) bool {
if bids[i].DealTierSatisfied == bids[j].DealTierSatisfied {
return bids[i].Price > bids[j].Price
}
return bids[i].DealTierSatisfied
})
}

// GetDealTierSatisfied ...
func GetDealTierSatisfied(ext *openrtb_ext.ExtBid) bool {
return ext != nil && ext.Prebid != nil && ext.Prebid.DealTierSatisfied
}

func DecodeImpressionID(id string) (string, int) {
index := strings.LastIndex(id, constant.CTVImpressionIDSeparator)
if index == -1 {
Expand Down
126 changes: 126 additions & 0 deletions endpoints/openrtb2/ctv/util/util_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package util

import (
"fmt"
"testing"

"github.com/PubMatic-OpenWrap/openrtb"

"github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/types"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -48,3 +52,125 @@ func TestDecodeImpressionID(t *testing.T) {
})
}
}

func TestSortByDealPriority(t *testing.T) {

type testbid struct {
id string
price float64
isDealBid bool
}

testcases := []struct {
scenario string
bids []testbid
expectedBidIDOrdering []string
}{
/* tests based on truth table */
{
scenario: "all_deal_bids_do_price_based_sort",
bids: []testbid{
{id: "DB_$5", price: 5.0, isDealBid: true}, // Deal bid with low price
{id: "DB_$10", price: 10.0, isDealBid: true}, // Deal bid with high price
},
expectedBidIDOrdering: []string{"DB_$10", "DB_$5"}, // sort by price among deal bids
},
{
scenario: "normal_and_deal_bid_mix_case_1",
bids: []testbid{
{id: "DB_$15", price: 15.0, isDealBid: true}, // Deal bid with low price
{id: "B_$30", price: 30.0, isDealBid: false}, // Normal bid with high price
},
expectedBidIDOrdering: []string{"DB_$15", "B_$30"}, // no sort expected. Deal bid is already 1st in order
},
{
scenario: "normal_and_deal_bid_mix_case_2", // deal bids are not at start position in order
bids: []testbid{
{id: "B_$30", price: 30.0, isDealBid: false}, // Normal bid with high price
{id: "DB_$15", price: 15.0, isDealBid: true}, // Deal bid with low price
},
expectedBidIDOrdering: []string{"DB_$15", "B_$30"}, // sort based on deal bid
},
{
scenario: "all_normal_bids_sort_by_price_case_1",
bids: []testbid{
{id: "B_$5", price: 5.0, isDealBid: false},
{id: "B_$10", price: 10.0, isDealBid: false},
},
expectedBidIDOrdering: []string{"B_$10", "B_$5"}, // sort by price
},
{
scenario: "all_normal_bids_sort_by_price_case_2", // already sorted by highest price
bids: []testbid{
{id: "B_$10", price: 10.0, isDealBid: false},
{id: "B_$5", price: 5.0, isDealBid: false},
},
expectedBidIDOrdering: []string{"B_$10", "B_$5"}, // no sort required as already sorted
},
/* use cases */
{
scenario: "deal_bids_with_same_price",
bids: []testbid{
{id: "DB2_$10", price: 10.0, isDealBid: true},
{id: "DB1_$10", price: 10.0, isDealBid: true},
},
expectedBidIDOrdering: []string{"DB2_$10", "DB1_$10"}, // no sort expected
},
/* more than 2 Bids testcases */
{
scenario: "4_bids_with_first_and_last_are_deal_bids",
bids: []testbid{
{id: "DB_$15", price: 15.0, isDealBid: true}, // deal bid with low CPM than another bid
{id: "B_$40", price: 40.0, isDealBid: false}, // normal bid with highest CPM
{id: "B_$3", price: 3.0, isDealBid: false},
{id: "DB_$20", price: 20.0, isDealBid: true}, // deal bid with high cpm than another deal bid
},
expectedBidIDOrdering: []string{"DB_$20", "DB_$15", "B_$40", "B_$3"},
},
{
scenario: "deal_bids_and_normal_bids_with_same_price",
bids: []testbid{
{id: "B1_$7", price: 7.0, isDealBid: false},
{id: "DB2_$7", price: 7.0, isDealBid: true},
{id: "B3_$7", price: 7.0, isDealBid: false},
{id: "DB1_$7", price: 7.0, isDealBid: true},
{id: "B2_$7", price: 7.0, isDealBid: false},
},
expectedBidIDOrdering: []string{"DB2_$7", "DB1_$7", "B1_$7", "B3_$7", "B2_$7"}, // no sort expected
},
}

newBid := func(bid testbid) *types.Bid {
return &types.Bid{
Bid: &openrtb.Bid{
ID: bid.id,
Price: bid.price,
//Ext: json.RawMessage(`{"prebid":{ "dealTierSatisfied" : ` + bid.isDealBid + ` }}`),
},
DealTierSatisfied: bid.isDealBid,
}
}

for _, test := range testcases {
// if test.scenario != "deal_bids_and_normal_bids_with_same_price" {
// continue
// }
fmt.Println("Scenario : ", test.scenario)
bids := []*types.Bid{}
for _, bid := range test.bids {
bids = append(bids, newBid(bid))
}
for _, bid := range bids {
fmt.Println(bid.ID, ",", bid.Price, ",", bid.DealTierSatisfied)
}
sortBids(bids[:])
fmt.Println("After sort")
actual := []string{}
for _, bid := range bids {
fmt.Println(bid.ID, ",", bid.Price, ", ", bid.DealTierSatisfied)
actual = append(actual, bid.ID)
}
assert.Equal(t, test.expectedBidIDOrdering, actual, test.scenario+" failed")
fmt.Println("")
}
}
16 changes: 11 additions & 5 deletions endpoints/openrtb2/ctv_auction.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,6 @@ func (deps *ctvEndpointDeps) CTVAuctionEndpoint(w http.ResponseWriter, r *http.R
}

response, err = deps.holdAuction(request, usersyncs)

ao.Request = request
ao.Response = response
if err != nil || nil == response {
Expand Down Expand Up @@ -546,6 +545,12 @@ func (deps *ctvEndpointDeps) getBids(resp *openrtb.BidResponse) {
}
vseat.Bid = append(vseat.Bid, *bid)
} else {
//reading extension, ingorning parsing error
ext := openrtb_ext.ExtBid{}
if nil != bid.Ext {
json.Unmarshal(bid.Ext, &ext)
}

//Adding adpod bids
impBids, ok := result[originalImpID]
if !ok {
Expand All @@ -560,9 +565,10 @@ func (deps *ctvEndpointDeps) getBids(resp *openrtb.BidResponse) {
bid.ID = util.GetUniqueBidID(bid.ID, len(impBids.Bids)+1)

impBids.Bids = append(impBids.Bids, &types.Bid{
Bid: bid,
FilterReasonCode: constant.CTVRCDidNotGetChance,
Duration: int(deps.impData[index].Config[sequenceNumber-1].MaxDuration),
Bid: bid,
FilterReasonCode: constant.CTVRCDidNotGetChance,
Duration: int(deps.impData[index].Config[sequenceNumber-1].MaxDuration),
DealTierSatisfied: util.GetDealTierSatisfied(&ext),
})
}
}
Expand Down Expand Up @@ -700,7 +706,7 @@ func (deps *ctvEndpointDeps) getBidResponseExt(resp *openrtb.BidResponse) (data
if nil != imp.Bid && len(imp.Bid.Bids) > 0 {
for _, bid := range imp.Bid.Bids {
//update adm
bid.AdM = constant.VASTDefaultTag
//bid.AdM = constant.VASTDefaultTag

//add duration value
raw, err := jsonparser.Set(bid.Ext, []byte(strconv.Itoa(int(bid.Duration))), "prebid", "video", "duration")
Expand Down
11 changes: 6 additions & 5 deletions exchange/bidder.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,12 @@ type adaptedBidder interface {
// pbsOrtbBid.bidVideo is optional but should be filled out by the Bidder if bidType is video.
// pbsOrtbBid.dealPriority will become "response.seatbid[i].bid.dealPriority" in the final OpenRTB response.
type pbsOrtbBid struct {
bid *openrtb.Bid
bidType openrtb_ext.BidType
bidTargets map[string]string
bidVideo *openrtb_ext.ExtBidPrebidVideo
dealPriority int
bid *openrtb.Bid
bidType openrtb_ext.BidType
bidTargets map[string]string
bidVideo *openrtb_ext.ExtBidPrebidVideo
dealPriority int
dealTierSatisfied bool
}

// pbsOrtbSeatBid is a SeatBid returned by an adaptedBidder.
Expand Down
Loading

0 comments on commit 85c6ea7

Please sign in to comment.