Skip to content

Commit

Permalink
Core & priceFloors: pass bid request to bidCpmAdjustment; warn abou…
Browse files Browse the repository at this point in the history
…t invalid `adUnit.floors` definitions (prebid#9441)

* Core & priceFloors: pass `bidRequest` as third arg to `bidCpmAdjustment`

* Floors: warn when adUnit.floors is not valid
  • Loading branch information
dgirardi authored and jorgeluisrocha committed May 18, 2023
1 parent e16602a commit 2b35182
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 19 deletions.
15 changes: 7 additions & 8 deletions modules/priceFloors.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {auctionManager} from '../src/auctionManager.js';
import {IMP, PBS, registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js';
import {timedAuctionHook, timedBidResponseHook} from '../src/utils/perfMetrics.js';
import {beConvertCurrency} from '../src/utils/currency.js';
import {adjustCpm} from '../src/utils/cpm.js';

/**
* @summary This Module is intended to provide users with the ability to dynamically set and enforce price floors on a per auction basis.
Expand Down Expand Up @@ -179,12 +180,8 @@ function generatePossibleEnumerations(arrayOfFields, delimiter) {
/**
* @summary If a the input bidder has a registered cpmadjustment it returns the input CPM after being adjusted
*/
export function getBiddersCpmAdjustment(bidderName, inputCpm, bid, bidRequest) {
const adjustmentFunction = bidderSettings.get(bidderName, 'bidCpmAdjustment');
if (adjustmentFunction) {
return parseFloat(adjustmentFunction(inputCpm, { ...bid, cpm: inputCpm }, bidRequest));
}
return parseFloat(inputCpm);
export function getBiddersCpmAdjustment(inputCpm, bid, bidRequest) {
return parseFloat(adjustCpm(inputCpm, {...bid, cpm: inputCpm}, bidRequest));
}

/**
Expand Down Expand Up @@ -254,7 +251,7 @@ export function getFloor(requestParams = {currency: 'USD', mediaType: '*', size:
if (inverseFunction) {
floorInfo.matchingFloor = inverseFunction(floorInfo.matchingFloor, bidRequest);
} else {
let cpmAdjustment = getBiddersCpmAdjustment(bidRequest.bidder, floorInfo.matchingFloor, {}, bidRequest);
let cpmAdjustment = getBiddersCpmAdjustment(floorInfo.matchingFloor, null, bidRequest);
floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor;
}
}
Expand Down Expand Up @@ -313,6 +310,8 @@ export function getFloorDataFromAdUnits(adUnits) {
// copy over the new rules into our values object
Object.assign(accum.values, newRules);
}
} else if (adUnit.floors != null) {
logWarn(`adUnit '${adUnit.code}' provides an invalid \`floor\` definition, it will be ignored for floor calculations`, adUnit);
}
return accum;
}, {});
Expand Down Expand Up @@ -737,7 +736,7 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a
}

// ok we got the bid response cpm in our desired currency. Now we need to run the bidders CPMAdjustment function if it exists
adjustedCpm = getBiddersCpmAdjustment(bid.bidderCode, adjustedCpm, bid, matchingBidRequest);
adjustedCpm = getBiddersCpmAdjustment(adjustedCpm, bid, matchingBidRequest);

// add necessary data information for analytics adapters / floor providers would possibly need
addFloorDataToBid(floorData, floorInfo, bid, adjustedCpm);
Expand Down
13 changes: 2 additions & 11 deletions src/auction.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ import CONSTANTS from './constants.json';
import {GreedyPromise} from './utils/promise.js';
import {useMetrics} from './utils/perfMetrics.js';
import {createBid} from './bidfactory.js';
import {adjustCpm} from './utils/cpm.js';

const { syncUsers } = userSync;

Expand Down Expand Up @@ -971,17 +972,7 @@ function setKeys(keyValues, bidderSettings, custBidObj, bidReq) {
}

export function adjustBids(bid) {
let code = bid.bidderCode;
let bidPriceAdjusted = bid.cpm;
const bidCpmAdjustment = bidderSettings.get(code || null, 'bidCpmAdjustment');

if (bidCpmAdjustment && typeof bidCpmAdjustment === 'function') {
try {
bidPriceAdjusted = bidCpmAdjustment(bid.cpm, Object.assign({}, bid));
} catch (e) {
logError('Error during bid adjustment', 'bidmanager.js', e);
}
}
let bidPriceAdjusted = adjustCpm(bid.cpm, bid);

if (bidPriceAdjusted >= 0) {
bid.cpm = bidPriceAdjusted;
Expand Down
17 changes: 17 additions & 0 deletions src/utils/cpm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {auctionManager} from '../auctionManager.js';
import {bidderSettings} from '../bidderSettings.js';
import {logError} from '../utils.js';

export function adjustCpm(cpm, bidResponse, bidRequest, {index = auctionManager.index, bs = bidderSettings} = {}) {
bidRequest = bidRequest || index.getBidRequest(bidResponse);
const bidCpmAdjustment = bs.get(bidResponse?.bidderCode || bidRequest?.bidder, 'bidCpmAdjustment');

if (bidCpmAdjustment && typeof bidCpmAdjustment === 'function') {
try {
return bidCpmAdjustment(cpm, Object.assign({}, bidResponse), bidRequest);
} catch (e) {
logError('Error during bid adjustment', e);
}
}
return cpm;
}
64 changes: 64 additions & 0 deletions test/spec/unit/utils/cpm_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {adjustCpm} from '../../../../src/utils/cpm.js';

describe('adjustCpm', () => {
const bidderCode = 'mockBidder';
let adjustmentFn, bs, index;
beforeEach(() => {
bs = {
get: sinon.stub()
}
index = {
getBidRequest: sinon.stub()
}
adjustmentFn = sinon.stub().callsFake((cpm) => cpm * 2);
})

it('throws when neither bidRequest nor bidResponse are provided', () => {
expect(() => adjustCpm(1)).to.throw();
})

it('always provides an object as bidResponse for the adjustment fn', () => {
bs.get.callsFake(() => adjustmentFn);
adjustCpm(1, null, {bidder: bidderCode}, {index, bs});
sinon.assert.calledWith(adjustmentFn, 1, {});
});

describe('when no bidRequest is provided', () => {
Object.entries({
'unavailable': undefined,
'found': {foo: 'bar'}
}).forEach(([t, req]) => {
describe(`and it is ${t} in the index`, () => {
beforeEach(() => {
bs.get.callsFake(() => adjustmentFn);
index.getBidRequest.callsFake(() => req)
});

it('provides it to the adjustment fn', () => {
const bidResponse = {bidderCode};
adjustCpm(1, bidResponse, undefined, {index, bs});
sinon.assert.calledWith(index.getBidRequest, bidResponse);
sinon.assert.calledWith(adjustmentFn, 1, bidResponse, req);
})
})
})
});

Object.entries({
'bidResponse': [{bidderCode}],
'bidRequest': [null, {bidder: bidderCode}],
}).forEach(([t, [bidResp, bidReq]]) => {
describe(`when passed ${t}`, () => {
beforeEach(() => {
bs.get.callsFake((bidder) => { if (bidder === bidderCode) return adjustmentFn });
});
it('retrieves the correct bidder code', () => {
expect(adjustCpm(1, bidResp, bidReq, {bs, index})).to.eql(2);
});
it('passes them to the adjustment fn', () => {
adjustCpm(1, bidResp, bidReq, {bs, index});
sinon.assert.calledWith(adjustmentFn, 1, bidResp == null ? sinon.match.any : bidResp, bidReq);
});
});
})
});

0 comments on commit 2b35182

Please sign in to comment.