github.com/prebid/prebid-server/v2@v2.18.0/floors/enforce.go (about)

     1  package floors
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math/rand"
     7  
     8  	"github.com/prebid/openrtb/v20/openrtb2"
     9  	"github.com/prebid/prebid-server/v2/config"
    10  	"github.com/prebid/prebid-server/v2/currency"
    11  	"github.com/prebid/prebid-server/v2/exchange/entities"
    12  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    13  )
    14  
    15  // Enforce does floors enforcement for bids from all bidders based on floors provided in request, account level floors config
    16  func Enforce(bidRequestWrapper *openrtb_ext.RequestWrapper, seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, account config.Account, conversions currency.Conversions) (map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, []error, []*entities.PbsOrtbSeatBid) {
    17  	rejectionErrs := []error{}
    18  
    19  	rejectedBids := []*entities.PbsOrtbSeatBid{}
    20  
    21  	requestExt, err := bidRequestWrapper.GetRequestExt()
    22  	if err != nil {
    23  		return seatBids, []error{errors.New("Error in getting request extension")}, rejectedBids
    24  	}
    25  
    26  	if !isPriceFloorsEnabled(account, bidRequestWrapper) {
    27  		return seatBids, nil, rejectedBids
    28  	}
    29  
    30  	if isSignalingSkipped(requestExt) || !isValidImpBidFloorPresent(bidRequestWrapper.BidRequest.Imp) {
    31  		return seatBids, nil, rejectedBids
    32  	}
    33  
    34  	enforceFloors := isSatisfiedByEnforceRate(requestExt, account.PriceFloors.EnforceFloorsRate, rand.Intn)
    35  	if updateEnforcePBS(enforceFloors, requestExt) {
    36  		err := bidRequestWrapper.RebuildRequest()
    37  		if err != nil {
    38  			return seatBids, []error{err}, rejectedBids
    39  		}
    40  	}
    41  	updateBidExt(bidRequestWrapper, seatBids)
    42  	if enforceFloors {
    43  		enforceDealFloors := account.PriceFloors.EnforceDealFloors && getEnforceDealsFlag(requestExt)
    44  		seatBids, rejectionErrs, rejectedBids = enforceFloorToBids(bidRequestWrapper, seatBids, conversions, enforceDealFloors)
    45  	}
    46  	return seatBids, rejectionErrs, rejectedBids
    47  }
    48  
    49  // updateEnforcePBS updates prebid extension in request if enforcePBS needs to be updated
    50  func updateEnforcePBS(enforceFloors bool, requestExt *openrtb_ext.RequestExt) bool {
    51  	updateReqExt := false
    52  
    53  	prebidExt := requestExt.GetPrebid()
    54  	if prebidExt == nil {
    55  		prebidExt = new(openrtb_ext.ExtRequestPrebid)
    56  	}
    57  
    58  	if prebidExt.Floors == nil {
    59  		prebidExt.Floors = new(openrtb_ext.PriceFloorRules)
    60  	}
    61  	floorExt := prebidExt.Floors
    62  
    63  	if floorExt.Enforcement == nil {
    64  		floorExt.Enforcement = new(openrtb_ext.PriceFloorEnforcement)
    65  	}
    66  
    67  	if floorExt.Enforcement.EnforcePBS == nil {
    68  		updateReqExt = true
    69  		floorExt.Enforcement.EnforcePBS = new(bool)
    70  		*floorExt.Enforcement.EnforcePBS = enforceFloors
    71  	} else {
    72  		oldEnforcePBS := *floorExt.Enforcement.EnforcePBS
    73  		*floorExt.Enforcement.EnforcePBS = enforceFloors && *floorExt.Enforcement.EnforcePBS
    74  		updateReqExt = oldEnforcePBS != *floorExt.Enforcement.EnforcePBS
    75  	}
    76  
    77  	if updateReqExt {
    78  		requestExt.SetPrebid(prebidExt)
    79  	}
    80  
    81  	return updateReqExt
    82  }
    83  
    84  // updateBidExt updates bid extension for floors related details
    85  func updateBidExt(bidRequestWrapper *openrtb_ext.RequestWrapper, seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) {
    86  	impMap := make(map[string]*openrtb_ext.ImpWrapper, bidRequestWrapper.LenImp())
    87  	for _, imp := range bidRequestWrapper.GetImp() {
    88  		impMap[imp.ID] = imp
    89  	}
    90  
    91  	for _, seatBid := range seatBids {
    92  		for _, bid := range seatBid.Bids {
    93  			reqImp, ok := impMap[bid.Bid.ImpID]
    94  			if ok {
    95  				updateBidExtWithFloors(reqImp, bid, reqImp.BidFloorCur)
    96  			}
    97  		}
    98  	}
    99  }
   100  
   101  // enforceFloorToBids function does floors enforcement for each bid,
   102  // The bids returned by each partner below bid floor price are rejected and remaining eligible bids are considered for further processing
   103  func enforceFloorToBids(bidRequestWrapper *openrtb_ext.RequestWrapper, seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, conversions currency.Conversions, enforceDealFloors bool) (map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, []error, []*entities.PbsOrtbSeatBid) {
   104  	errs := []error{}
   105  	rejectedBids := []*entities.PbsOrtbSeatBid{}
   106  	impMap := make(map[string]*openrtb_ext.ImpWrapper, bidRequestWrapper.LenImp())
   107  
   108  	for _, v := range bidRequestWrapper.GetImp() {
   109  		impMap[v.ID] = v
   110  	}
   111  
   112  	for bidderName, seatBid := range seatBids {
   113  		eligibleBids := make([]*entities.PbsOrtbBid, 0, len(seatBid.Bids))
   114  		for _, bid := range seatBid.Bids {
   115  
   116  			reqImp, ok := impMap[bid.Bid.ImpID]
   117  			if !ok {
   118  				continue
   119  			}
   120  
   121  			requestExt, err := bidRequestWrapper.GetRequestExt()
   122  			if err != nil {
   123  				errs = append(errs, fmt.Errorf("error in getting req extension = %v", err.Error()))
   124  				continue
   125  			}
   126  
   127  			if isEnforcementEnabled(requestExt) {
   128  				if hasDealID(bid) && !enforceDealFloors {
   129  					eligibleBids = append(eligibleBids, bid)
   130  					continue
   131  				}
   132  
   133  				rate, err := getCurrencyConversionRate(seatBid.Currency, reqImp.BidFloorCur, conversions)
   134  				if err != nil {
   135  					errs = append(errs, fmt.Errorf("error in rate conversion from = %s to %s with bidder %s for impression id %s and bid id %s error = %v", seatBid.Currency, reqImp.BidFloorCur, bidderName, bid.Bid.ImpID, bid.Bid.ID, err.Error()))
   136  					continue
   137  				}
   138  
   139  				bidPrice := rate * bid.Bid.Price
   140  				if (bidPrice + floorPrecision) < reqImp.BidFloor {
   141  					rejectedBid := &entities.PbsOrtbSeatBid{
   142  						Currency: seatBid.Currency,
   143  						Seat:     seatBid.Seat,
   144  						Bids:     []*entities.PbsOrtbBid{bid},
   145  					}
   146  					rejectedBids = append(rejectedBids, rejectedBid)
   147  					continue
   148  				}
   149  			}
   150  			eligibleBids = append(eligibleBids, bid)
   151  		}
   152  		seatBids[bidderName].Bids = eligibleBids
   153  	}
   154  	return seatBids, errs, rejectedBids
   155  }
   156  
   157  // isEnforcementEnabled check for floors enforcement enabled in request
   158  func isEnforcementEnabled(requestExt *openrtb_ext.RequestExt) bool {
   159  	if floorsExt := getFloorsExt(requestExt); floorsExt != nil {
   160  		return floorsExt.GetEnforcePBS()
   161  	}
   162  	return true
   163  }
   164  
   165  // isSignalingSkipped check for floors signalling is skipped due to skip rate
   166  func isSignalingSkipped(requestExt *openrtb_ext.RequestExt) bool {
   167  	if floorsExt := getFloorsExt(requestExt); floorsExt != nil {
   168  		return floorsExt.GetFloorsSkippedFlag()
   169  	}
   170  	return false
   171  }
   172  
   173  // getEnforceRateRequest returns enforceRate provided in request
   174  func getEnforceRateRequest(requestExt *openrtb_ext.RequestExt) int {
   175  	if floorsExt := getFloorsExt(requestExt); floorsExt != nil {
   176  		return floorsExt.GetEnforceRate()
   177  	}
   178  	return 0
   179  }
   180  
   181  // getEnforceDealsFlag returns FloorDeals flag from req.ext.prebid.floors.enforcement
   182  func getEnforceDealsFlag(requestExt *openrtb_ext.RequestExt) bool {
   183  	if floorsExt := getFloorsExt(requestExt); floorsExt != nil {
   184  		return floorsExt.GetEnforceDealsFlag()
   185  	}
   186  	return false
   187  }
   188  
   189  // getFloorsExt returns req.ext.prebid.floors
   190  func getFloorsExt(requestExt *openrtb_ext.RequestExt) *openrtb_ext.PriceFloorRules {
   191  	if requestExt != nil {
   192  		if prebidExt := requestExt.GetPrebid(); prebidExt != nil && prebidExt.Floors != nil {
   193  			return prebidExt.Floors
   194  		}
   195  	}
   196  	return nil
   197  }
   198  
   199  // isValidImpBidFloorPresent checks if non zero imp.bidfloor is present in request
   200  func isValidImpBidFloorPresent(imp []openrtb2.Imp) bool {
   201  	for i := range imp {
   202  		if imp[i].BidFloor > 0 {
   203  			return true
   204  		}
   205  	}
   206  	return false
   207  }
   208  
   209  // isSatisfiedByEnforceRate check enforcements should be done or not based on enforceRate in config and in request
   210  func isSatisfiedByEnforceRate(requestExt *openrtb_ext.RequestExt, configEnforceRate int, f func(int) int) bool {
   211  	requestEnforceRate := getEnforceRateRequest(requestExt)
   212  	enforceRate := f(enforceRateMax)
   213  	satisfiedByRequest := requestEnforceRate == 0 || enforceRate < requestEnforceRate
   214  	satisfiedByAccount := configEnforceRate == 0 || enforceRate < configEnforceRate
   215  	shouldEnforce := satisfiedByRequest && satisfiedByAccount
   216  
   217  	return shouldEnforce
   218  }
   219  
   220  // hasDealID checks for dealID presence in bid
   221  func hasDealID(bid *entities.PbsOrtbBid) bool {
   222  	if bid != nil && bid.Bid != nil && bid.Bid.DealID != "" {
   223  		return true
   224  	}
   225  	return false
   226  }
   227  
   228  // getCurrencyConversionRate gets conversion rate in case floor currency and seatBid currency are not same
   229  func getCurrencyConversionRate(seatBidCur, reqImpCur string, conversions currency.Conversions) (float64, error) {
   230  	rate := 1.0
   231  	if seatBidCur != reqImpCur {
   232  		return conversions.GetRate(seatBidCur, reqImpCur)
   233  	} else {
   234  		return rate, nil
   235  	}
   236  }
   237  
   238  // updateBidExtWithFloors updates floors related details in bid extension
   239  func updateBidExtWithFloors(reqImp *openrtb_ext.ImpWrapper, bid *entities.PbsOrtbBid, floorCurrency string) {
   240  	impExt, err := reqImp.GetImpExt()
   241  	if err != nil {
   242  		return
   243  	}
   244  
   245  	var bidExtFloors openrtb_ext.ExtBidPrebidFloors
   246  	prebidExt := impExt.GetPrebid()
   247  	if prebidExt == nil || prebidExt.Floors == nil {
   248  		if reqImp.BidFloor > 0 {
   249  			bidExtFloors.FloorValue = reqImp.BidFloor
   250  			bidExtFloors.FloorCurrency = reqImp.BidFloorCur
   251  			bid.BidFloors = &bidExtFloors
   252  		}
   253  	} else {
   254  		bidExtFloors.FloorRule = prebidExt.Floors.FloorRule
   255  		bidExtFloors.FloorRuleValue = prebidExt.Floors.FloorRuleValue
   256  		bidExtFloors.FloorValue = prebidExt.Floors.FloorValue
   257  		bidExtFloors.FloorCurrency = floorCurrency
   258  		bid.BidFloors = &bidExtFloors
   259  	}
   260  }