Skip to content

Commit

Permalink
Bid Category Mapping (prebid#818)
Browse files Browse the repository at this point in the history
* Initial implementation for categories mapping files loading from filesystem

* Unit tests fix, minor refactoring

* File fetcher: changed filesystem traversal algorithm to recursive

* Fix for categories unmarshalling: skip invalid category mapping files instead of throwing error

* - Categories minor refactoring
- Categories unit test added

* -Removed categories as input parameter from AMP endpoint
-Added unit test for FetchCategories
-Code clean up

* -Categories unit test clean up
-File fetcher minor refactoring

* Configuration fix

* Code refactoring:
-Changed FetchCategories signature, inluding all implementations and unit tests
-Moved categories unmarshalling to FetchCategories

* Code refactoring

* Changed category mapping file for Freewheel

* Code refactoring:
-Stored requests directory is now configurable
-Category mapping directory is now configurable
-Changed FetchCategories signature
-Unit tests refactoring

* Initial implementation for categories mapping files loading from filesystem

* Unit tests fix, minor refactoring

* File fetcher: changed filesystem traversal algorithm to recursive

* Fix for categories unmarshalling: skip invalid category mapping files instead of throwing error

* - Categories minor refactoring
- Categories unit test added

* -Removed categories as input parameter from AMP endpoint
-Added unit test for FetchCategories
-Code clean up

* -Categories unit test clean up
-File fetcher minor refactoring

* Configuration fix

* Code refactoring:
-Changed FetchCategories signature, inluding all implementations and unit tests
-Moved categories unmarshalling to FetchCategories

* Code refactoring

* Changed category mapping file for Freewheel

* Code refactoring:
-Stored requests directory is now configurable
-Category mapping directory is now configurable
-Changed FetchCategories signature
-Unit tests refactoring

* Make a separate interface for CategoryFetcher (the Fetcher interface deals with stored requests and impressions).

* Categories fetching code improvement

* Fetch categories: added error handling for missing categories file and unit test for it

* Initial version of appnexus adapter mapping an AppNexus brand to an IAB category (if no IAB category was returned in the bid response).

* Store category mapping in a field in the adapter object so it gets loaded when the adapter is created, this way we know it won't try to be used before it's done loading.

* Adding test cases to appnexus adapter for video bids with and without valid "brand_id" fields in the response to make sure mapping to an IAB category works as expected in both cases.

* Storing custom options for adapters in static/adapter-options/ folder and moved category mapping for appnexus adapter into there. Config package attempts to read custom options for each adapter so this could be used for other adapter's specific options as well (although code needs to be change to pass them into the adapter constructor functions if so).

* Move loading of adapter options file back into the adapter itself, but the file is in the static/adapter/appnexus folder instead of being alongside the code this time.

* IAB to adserver category translation

* Code refactoring:
-Reused existing structures to unmarshal BidExtension
-Removed check id category exist in BidExt.Prebid.Targeting.PrimaryCategory
-Removed check if duration exist in BidExt.Prebid.Duration

* Improve clarity of bad primary ad server setting error message.

* Change MultiFetcher.FetchCategories() to loop through fetchers and return response when one is found or a NotFoundError otherwise.

* Added missed parameter

* Fixed unit test

* Categories lazy unmarshalling

* Categories lazy unmarshalling - added removing of unmarshalled file

* Implements an AllFetcher interface to handle both types of fetchers

* Minor code review fixes

* Minor code review fixes
  • Loading branch information
jmaynardxandr authored and hhhjort committed Mar 15, 2019
1 parent 99af2f9 commit 18bb081
Show file tree
Hide file tree
Showing 37 changed files with 1,326 additions and 107 deletions.
81 changes: 61 additions & 20 deletions adapters/appnexus/appnexus.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"

"github.com/buger/jsonparser"
Expand All @@ -21,8 +22,9 @@ import (
)

type AppNexusAdapter struct {
http *adapters.HTTPAdapter
URI string
http *adapters.HTTPAdapter
URI string
iabCategoryMap map[string]string
}

// used for cookies and such
Expand All @@ -39,6 +41,10 @@ type KeyVal struct {
Values []string `json:"value,omitempty"`
}

type appnexusAdapterOptions struct {
IabCategories map[string]string `json:"iab_categories"`
}

type appnexusParams struct {
LegacyPlacementId int `json:"placementId"`
LegacyInvCode string `json:"invCode"`
Expand Down Expand Up @@ -68,6 +74,7 @@ type appnexusBidExt struct {

type appnexusBidExtAppnexus struct {
BidType int `json:"bid_ad_type"`
BrandId int `json:"brand_id"`
}

type appnexusImpExt struct {
Expand Down Expand Up @@ -235,9 +242,12 @@ func (a *AppNexusAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder
NURL: bid.NURL,
}

if mediaType, err := getMediaTypeForBid(&bid); err == nil {
pbid.CreativeMediaType = string(mediaType)
bids = append(bids, &pbid)
var impExt appnexusBidExt
if err := json.Unmarshal(bid.Ext, &impExt); err == nil {
if mediaType, err := getMediaTypeForBid(&impExt); err == nil {
pbid.CreativeMediaType = string(mediaType)
bids = append(bids, &pbid)
}
}
}
}
Expand Down Expand Up @@ -433,26 +443,33 @@ func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb.BidRequest, external
for _, sb := range bidResp.SeatBid {
for i := 0; i < len(sb.Bid); i++ {
bid := sb.Bid[i]
if bidType, err := getMediaTypeForBid(&bid); err == nil {
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &bid,
BidType: bidType,
})
} else {
var impExt appnexusBidExt
if err := json.Unmarshal(bid.Ext, &impExt); err != nil {
errs = append(errs, err)
} else {
if bidType, err := getMediaTypeForBid(&impExt); err == nil {
if len(bid.Cat) == 0 {
if iabCategory, err := a.getIabCategoryForBid(&impExt); err == nil {
bid.Cat = []string{iabCategory}
}
}

bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &bid,
BidType: bidType,
})
} else {
errs = append(errs, err)
}
}
}
}
return bidResponse, errs
}

// getMediaTypeForBid determines which type of bid.
func getMediaTypeForBid(bid *openrtb.Bid) (openrtb_ext.BidType, error) {
var impExt appnexusBidExt
if err := json.Unmarshal(bid.Ext, &impExt); err != nil {
return "", err
}
switch impExt.Appnexus.BidType {
func getMediaTypeForBid(bid *appnexusBidExt) (openrtb_ext.BidType, error) {
switch bid.Appnexus.BidType {
case 0:
return openrtb_ext.BidTypeBanner, nil
case 1:
Expand All @@ -462,7 +479,18 @@ func getMediaTypeForBid(bid *openrtb.Bid) (openrtb_ext.BidType, error) {
case 3:
return openrtb_ext.BidTypeNative, nil
default:
return "", fmt.Errorf("Unrecognized bid_ad_type in response from appnexus: %d", impExt.Appnexus.BidType)
return "", fmt.Errorf("Unrecognized bid_ad_type in response from appnexus: %d", bid.Appnexus.BidType)
}
}

// getIabCategoryForBid maps an appnexus brand id to an IAB category.
func (a *AppNexusAdapter) getIabCategoryForBid(bid *appnexusBidExt) (string, error) {
// TODO: Change from BrandId to a CategoryId once that is returned from impbus
brandIDString := strconv.Itoa(bid.Appnexus.BrandId)
if iabCategory, ok := a.iabCategoryMap[brandIDString]; ok {
return iabCategory, nil
} else {
return "", fmt.Errorf("category not in map: %s", brandIDString)
}
}

Expand All @@ -480,8 +508,21 @@ func NewAppNexusAdapter(config *adapters.HTTPAdapterConfig, endpoint string) *Ap

func NewAppNexusBidder(client *http.Client, endpoint string) *AppNexusAdapter {
a := &adapters.HTTPAdapter{Client: client}

// Load custom options for our adapter (currently just a lookup table to convert appnexus => iab categories)
var catmap map[string]string
opts, err := ioutil.ReadFile("./static/adapter/appnexus/opts.json")
if err == nil {
var adapterOptions appnexusAdapterOptions

if err := json.Unmarshal(opts, &adapterOptions); err == nil {
catmap = adapterOptions.IabCategories
}
}

return &AppNexusAdapter{
http: a,
URI: endpoint,
http: a,
URI: endpoint,
iabCategoryMap: catmap,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"crid": "29681110",
"w": 300,
"h": 250,
"cat": ["IAB20-3"],
"ext": {
"appnexus": {
"brand_id": 1,
Expand Down
122 changes: 122 additions & 0 deletions adapters/appnexus/appnexustest/exemplary/simple-video.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
{
"mockBidRequest": {
"id": "test-request-id",
"imp": [
{
"id": "test-imp-id",
"video": {
"mimes": ["video/mp4"],
"minduration": 15,
"maxduration": 30,
"protocols": [2, 3, 5, 6, 7, 8],
"w": 940,
"h": 560
},
"ext": {
"bidder": {
"placement_id": 10433394
}
}
}
]
},

"httpCalls": [
{
"expectedRequest": {
"uri": "http://ib.adnxs.com/openrtb2",
"body": {
"id": "test-request-id",
"imp": [
{
"id": "test-imp-id",
"video": {
"mimes": ["video/mp4"],
"minduration": 15,
"maxduration": 30,
"protocols": [2, 3, 5, 6, 7, 8],
"w": 940,
"h": 560
},
"ext": {
"appnexus": {
"placement_id": 10433394
}
}
}
]
}
},
"mockResponse": {
"status": 200,
"body": {
"id": "test-request-id",
"seatbid": [
{
"seat": "958",
"bid": [{
"id": "7706636740145184841",
"impid": "test-imp-id",
"price": 0.500000,
"adid": "29681110",
"adm": "some-test-ad",
"adomain": ["appnexus.com"],
"iurl": "http://nym1-ib.adnxs.com/cr?id=29681110",
"cid": "958",
"crid": "29681110",
"h": 250,
"w": 300,
"cat": ["IAB9-1"],
"ext": {
"appnexus": {
"brand_id": 9,
"auction_id": 8189378542222915032,
"bid_ad_type": 1,
"bidder_id": 2,
"ranking_price": 0.000000
}
}
}]
}
],
"bidid": "5778926625248726496",
"cur": "USD"
}
}
}
],

"expectedBidResponses": [
{
"currency": "USD",
"bids": [
{
"bid": {
"id": "7706636740145184841",
"impid": "test-imp-id",
"price": 0.5,
"adm": "some-test-ad",
"adid": "29681110",
"adomain": ["appnexus.com"],
"iurl": "http://nym1-ib.adnxs.com/cr?id=29681110",
"cid": "958",
"crid": "29681110",
"w": 300,
"h": 250,
"cat": ["IAB9-1"],
"ext": {
"appnexus": {
"brand_id": 9,
"auction_id": 8189378542222915032,
"bid_ad_type": 1,
"bidder_id": 2,
"ranking_price": 0.000000
}
}
},
"type": "video"
}
]
}
]
}
120 changes: 120 additions & 0 deletions adapters/appnexus/appnexustest/exemplary/video-invalid-category.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
{
"mockBidRequest": {
"id": "test-request-id",
"imp": [
{
"id": "test-imp-id",
"video": {
"mimes": ["video/mp4"],
"minduration": 15,
"maxduration": 30,
"protocols": [2, 3, 5, 6, 7, 8],
"w": 940,
"h": 560
},
"ext": {
"bidder": {
"placement_id": 10433394
}
}
}
]
},

"httpCalls": [
{
"expectedRequest": {
"uri": "http://ib.adnxs.com/openrtb2",
"body": {
"id": "test-request-id",
"imp": [
{
"id": "test-imp-id",
"video": {
"mimes": ["video/mp4"],
"minduration": 15,
"maxduration": 30,
"protocols": [2, 3, 5, 6, 7, 8],
"w": 940,
"h": 560
},
"ext": {
"appnexus": {
"placement_id": 10433394
}
}
}
]
}
},
"mockResponse": {
"status": 200,
"body": {
"id": "test-request-id",
"seatbid": [
{
"seat": "958",
"bid": [{
"id": "7706636740145184841",
"impid": "test-imp-id",
"price": 0.500000,
"adid": "29681110",
"adm": "some-test-ad",
"adomain": ["appnexus.com"],
"iurl": "http://nym1-ib.adnxs.com/cr?id=29681110",
"cid": "958",
"crid": "29681110",
"h": 250,
"w": 300,
"ext": {
"appnexus": {
"brand_id": 99,
"auction_id": 8189378542222915032,
"bid_ad_type": 1,
"bidder_id": 2,
"ranking_price": 0.000000
}
}
}]
}
],
"bidid": "5778926625248726496",
"cur": "USD"
}
}
}
],

"expectedBidResponses": [
{
"currency": "USD",
"bids": [
{
"bid": {
"id": "7706636740145184841",
"impid": "test-imp-id",
"price": 0.5,
"adm": "some-test-ad",
"adid": "29681110",
"adomain": ["appnexus.com"],
"iurl": "http://nym1-ib.adnxs.com/cr?id=29681110",
"cid": "958",
"crid": "29681110",
"w": 300,
"h": 250,
"ext": {
"appnexus": {
"brand_id": 99,
"auction_id": 8189378542222915032,
"bid_ad_type": 1,
"bidder_id": 2,
"ranking_price": 0.000000
}
}
},
"type": "video"
}
]
}
]
}
Loading

0 comments on commit 18bb081

Please sign in to comment.