Skip to content

Commit 40ea367

Browse files
Support bid rounding options (#3957)
1 parent a877687 commit 40ea367

19 files changed

Lines changed: 478 additions & 109 deletions

docs/application-settings.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ There are two ways to configure application settings: database and file. This do
2525
- `auction.bidadjustments.mediatype.*.*.*[].value` - value of the bid adjustment
2626
- `auction.bidadjustments.mediatype.*.*.*[].currency` - currency of the bid adjustment
2727
- `auction.events.enabled` - enables events for account if true
28+
- `auction.bid-rounding` - bid rounding options are:
29+
- **down** - rounding down to the lower price bucket
30+
- **up** - rounding up to the higher price bucket
31+
- **timesplit** - 50% of the time rounding down to the lower PB and 50% of the time rounding up to the higher price bucket
32+
- **true** - if the price >= 50% of the range, rounding up to the higher price bucket, otherwise rounding down
2833
- `auction.price-floors.enabled` - enables price floors for account if true. Defaults to true.
2934
- `auction.price-floors.fetch.enabled`- enables data fetch for price floors for account if true. Defaults to false.
3035
- `auction.price-floors.fetch.url` - url to fetch price floors data from.

src/main/java/org/prebid/server/auction/BidResponseCreator.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,7 @@ private Future<CategoryMappingResult> createCategoryMapping(AuctionContext aucti
594594
return categoryMappingService.createCategoryMapping(
595595
bidderResponses,
596596
auctionContext.getBidRequest(),
597+
auctionContext.getAccount(),
597598
auctionContext.getTimeoutContext().getTimeout())
598599

599600
.map(categoryMappingResult -> addCategoryMappingErrors(categoryMappingResult, auctionContext));
@@ -1561,7 +1562,7 @@ private Bid toBid(BidInfo bidInfo,
15611562
final String categoryDuration = bidInfo.getCategory();
15621563
targetingKeywords = keywordsCreator != null
15631564
? keywordsCreator.makeFor(
1564-
bid, seat, isWinningBid, cacheId, bidType.getName(), videoCacheId, categoryDuration)
1565+
bid, seat, isWinningBid, cacheId, bidType.getName(), videoCacheId, categoryDuration, account)
15651566
: null;
15661567
} else {
15671568
targetingKeywords = null;

src/main/java/org/prebid/server/auction/CpmRange.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@
33
import org.apache.commons.lang3.ObjectUtils;
44
import org.apache.commons.lang3.StringUtils;
55
import org.prebid.server.proto.openrtb.ext.request.ExtGranularityRange;
6+
import org.prebid.server.settings.model.Account;
7+
import org.prebid.server.settings.model.AccountAuctionConfig;
8+
import org.prebid.server.settings.model.AccountAuctionBidRoundingMode;
69

710
import java.math.BigDecimal;
811
import java.math.RoundingMode;
912
import java.text.NumberFormat;
1013
import java.util.Locale;
14+
import java.util.Optional;
15+
import java.util.concurrent.ThreadLocalRandom;
1116

1217
/**
1318
* Class for price operating with rules defined in {@link PriceGranularity}
@@ -23,8 +28,8 @@ private CpmRange() {
2328
/**
2429
* Rounding price by specified rules defined in {@link PriceGranularity} object and returns it in string format
2530
*/
26-
public static String fromCpm(BigDecimal cpm, PriceGranularity priceGranularity) {
27-
final BigDecimal value = fromCpmAsNumber(cpm, priceGranularity);
31+
public static String fromCpm(BigDecimal cpm, PriceGranularity priceGranularity, Account account) {
32+
final BigDecimal value = fromCpmAsNumber(cpm, priceGranularity, account);
2833
return value != null ? format(value, priceGranularity.getPrecision()) : StringUtils.EMPTY;
2934
}
3035

@@ -47,7 +52,7 @@ private static NumberFormat numberFormat(int precision) {
4752
* Rounding price by specified rules defined in {@link PriceGranularity} object and returns it in {@link BigDecimal}
4853
* format
4954
*/
50-
public static BigDecimal fromCpmAsNumber(BigDecimal cpm, PriceGranularity priceGranularity) {
55+
public static BigDecimal fromCpmAsNumber(BigDecimal cpm, PriceGranularity priceGranularity, Account account) {
5156
if (cpm.compareTo(BigDecimal.ZERO) <= 0) {
5257
return null;
5358
}
@@ -69,14 +74,32 @@ public static BigDecimal fromCpmAsNumber(BigDecimal cpm, PriceGranularity priceG
6974
min = max;
7075
}
7176

72-
return increment != null ? calculate(cpm, min, increment) : null;
77+
return increment != null ? calculate(cpm, min, increment, resolveRoundingMode(account)) : null;
7378
}
7479

75-
private static BigDecimal calculate(BigDecimal cpm, BigDecimal min, BigDecimal increment) {
80+
private static BigDecimal calculate(BigDecimal cpm,
81+
BigDecimal min,
82+
BigDecimal increment,
83+
RoundingMode roundingMode) {
84+
7685
return cpm
7786
.subtract(min)
78-
.divide(increment, 0, RoundingMode.FLOOR)
87+
.divide(increment, 0, roundingMode)
7988
.multiply(increment)
8089
.add(min);
8190
}
91+
92+
private static RoundingMode resolveRoundingMode(Account account) {
93+
final AccountAuctionBidRoundingMode accountRoundingMode = Optional.ofNullable(account)
94+
.map(Account::getAuction)
95+
.map(AccountAuctionConfig::getBidRounding)
96+
.orElse(AccountAuctionBidRoundingMode.DOWN);
97+
98+
return switch (accountRoundingMode) {
99+
case DOWN -> RoundingMode.FLOOR;
100+
case UP -> RoundingMode.CEILING;
101+
case TRUE -> RoundingMode.HALF_UP;
102+
case TIMESPLIT -> ThreadLocalRandom.current().nextBoolean() ? RoundingMode.FLOOR : RoundingMode.CEILING;
103+
};
104+
}
82105
}

src/main/java/org/prebid/server/auction/TargetingKeywordsCreator.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.iab.openrtb.response.Bid;
44
import org.apache.commons.lang3.StringUtils;
55
import org.prebid.server.proto.openrtb.ext.request.ExtPriceGranularity;
6+
import org.prebid.server.settings.model.Account;
67

78
import java.math.BigDecimal;
89
import java.util.ArrayList;
@@ -152,7 +153,8 @@ Map<String, String> makeFor(Bid bid,
152153
String cacheId,
153154
String format,
154155
String vastCacheId,
155-
String categoryDuration) {
156+
String categoryDuration,
157+
Account account) {
156158

157159
final Map<String, String> keywords = makeFor(
158160
bidder,
@@ -164,7 +166,8 @@ Map<String, String> makeFor(Bid bid,
164166
vastCacheId,
165167
categoryDuration,
166168
format,
167-
bid.getDealid());
169+
bid.getDealid(),
170+
account);
168171

169172
if (resolver == null) {
170173
return truncateKeys(keywords);
@@ -188,7 +191,8 @@ private Map<String, String> makeFor(String bidder,
188191
String vastCacheId,
189192
String categoryDuration,
190193
String format,
191-
String dealId) {
194+
String dealId,
195+
Account account) {
192196

193197
final boolean includeDealBid = alwaysIncludeDeals && StringUtils.isNotEmpty(dealId);
194198
final KeywordMap keywordMap = new KeywordMap(
@@ -198,7 +202,10 @@ private Map<String, String> makeFor(String bidder,
198202
includeBidderKeys || includeDealBid,
199203
Collections.emptySet());
200204

201-
final String roundedCpm = isPriceGranularityValid() ? CpmRange.fromCpm(price, priceGranularity) : DEFAULT_CPM;
205+
final String roundedCpm = isPriceGranularityValid()
206+
? CpmRange.fromCpm(price, priceGranularity, account)
207+
: DEFAULT_CPM;
208+
202209
keywordMap.put(this.keyPrefix + PB_KEY, roundedCpm);
203210

204211
keywordMap.put(this.keyPrefix + BIDDER_KEY, bidder);

src/main/java/org/prebid/server/auction/categorymapping/BasicCategoryMappingService.java

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
4343
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo;
4444
import org.prebid.server.settings.ApplicationSettings;
45+
import org.prebid.server.settings.model.Account;
4546
import org.prebid.server.util.ObjectUtil;
4647

4748
import java.math.BigDecimal;
@@ -83,6 +84,7 @@ public BasicCategoryMappingService(ApplicationSettings applicationSettings, Jack
8384
@Override
8485
public Future<CategoryMappingResult> createCategoryMapping(List<BidderResponse> bidderResponses,
8586
BidRequest bidRequest,
87+
Account account,
8688
Timeout timeout) {
8789

8890
final ExtRequestTargeting targeting = targeting(bidRequest);
@@ -110,9 +112,21 @@ public Future<CategoryMappingResult> createCategoryMapping(List<BidderResponse>
110112
final List<RejectedBid> rejectedBids = new ArrayList<>();
111113

112114
return makeBidderToBidCategory(
113-
bidderResponses, withCategory, translateCategories, primaryAdServer, publisher, rejectedBids, timeout)
115+
bidderResponses,
116+
withCategory,
117+
translateCategories,
118+
primaryAdServer,
119+
publisher,
120+
rejectedBids,
121+
timeout)
114122
.map(categoryBidContexts -> resolveBidsCategoriesDurations(
115-
bidderResponses, categoryBidContexts, bidRequest, targeting, withCategory, rejectedBids));
123+
bidderResponses,
124+
categoryBidContexts,
125+
account,
126+
bidRequest,
127+
targeting,
128+
withCategory,
129+
rejectedBids));
116130
}
117131

118132
private static ExtRequestTargeting targeting(BidRequest bidRequest) {
@@ -326,6 +340,7 @@ private static void collectCategoryFetchResults(CompositeFuture compositeFuture,
326340
*/
327341
private CategoryMappingResult resolveBidsCategoriesDurations(List<BidderResponse> bidderResponses,
328342
List<CategoryBidContext> categoryBidContexts,
343+
Account account,
329344
BidRequest bidRequest,
330345
ExtRequestTargeting targeting,
331346
boolean withCategory,
@@ -342,8 +357,15 @@ private CategoryMappingResult resolveBidsCategoriesDurations(List<BidderResponse
342357

343358
final boolean appendBidderNames = BooleanUtils.toBooleanDefaultIfNull(targeting.getAppendbiddernames(), false);
344359
final Map<String, Set<CategoryBidContext>> uniqueCatKeysToCategoryBids = categoryBidContexts.stream()
345-
.map(categoryBidContext -> enrichCategoryBidContext(categoryBidContext, durations, priceGranularity,
346-
withCategory, appendBidderNames, impIdToBiddersDealTear, rejectedBids))
360+
.map(categoryBidContext -> enrichCategoryBidContext(
361+
categoryBidContext,
362+
account,
363+
durations,
364+
priceGranularity,
365+
withCategory,
366+
appendBidderNames,
367+
impIdToBiddersDealTear,
368+
rejectedBids))
347369
.filter(Objects::nonNull)
348370
.collect(Collectors.groupingBy(CategoryBidContext::getCategoryUniqueKey,
349371
Collectors.mapping(Function.identity(), Collectors.toSet())));
@@ -504,6 +526,7 @@ private static boolean isNotRejected(String bidId, String bidder, List<RejectedB
504526
* and creates {@link CategoryBidContext} which is holder for bid category related information.
505527
*/
506528
private CategoryBidContext enrichCategoryBidContext(CategoryBidContext categoryBidContext,
529+
Account account,
507530
List<Integer> durations,
508531
PriceGranularity priceGranularity,
509532
boolean withCategory,
@@ -522,7 +545,7 @@ private CategoryBidContext enrichCategoryBidContext(CategoryBidContext categoryB
522545
return null;
523546
}
524547

525-
final BigDecimal price = CpmRange.fromCpmAsNumber(bid.getPrice(), priceGranularity);
548+
final BigDecimal price = CpmRange.fromCpmAsNumber(bid.getPrice(), priceGranularity, account);
526549
final String rowPrice = CpmRange.format(price, priceGranularity.getPrecision());
527550
final String category = categoryBidContext.getCategory();
528551
final String categoryUniqueKey = createCategoryUniqueKey(withCategory, category, rowPrice, duration);

src/main/java/org/prebid/server/auction/categorymapping/CategoryMappingService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
import org.prebid.server.auction.model.BidderResponse;
66
import org.prebid.server.auction.model.CategoryMappingResult;
77
import org.prebid.server.execution.timeout.Timeout;
8+
import org.prebid.server.settings.model.Account;
89

910
import java.util.List;
1011

1112
public interface CategoryMappingService {
1213

1314
Future<CategoryMappingResult> createCategoryMapping(List<BidderResponse> bidderResponses,
1415
BidRequest bidRequest,
16+
Account account,
1517
Timeout timeout);
1618
}

src/main/java/org/prebid/server/auction/categorymapping/NoOpCategoryMappingService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.prebid.server.auction.model.BidderResponse;
66
import org.prebid.server.auction.model.CategoryMappingResult;
77
import org.prebid.server.execution.timeout.Timeout;
8+
import org.prebid.server.settings.model.Account;
89

910
import java.util.List;
1011

@@ -13,6 +14,7 @@ public class NoOpCategoryMappingService implements CategoryMappingService {
1314
@Override
1415
public Future<CategoryMappingResult> createCategoryMapping(List<BidderResponse> bidderResponses,
1516
BidRequest bidRequest,
17+
Account account,
1618
Timeout timeout) {
1719

1820
return Future.succeededFuture(CategoryMappingResult.of(bidderResponses));
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.prebid.server.settings.model;
2+
3+
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
6+
public enum AccountAuctionBidRoundingMode {
7+
8+
@JsonProperty("down")
9+
@JsonEnumDefaultValue
10+
DOWN,
11+
12+
@JsonProperty("true")
13+
TRUE,
14+
15+
@JsonProperty("timesplit")
16+
TIMESPLIT,
17+
18+
@JsonProperty("up")
19+
UP
20+
}

src/main/java/org/prebid/server/settings/model/AccountAuctionConfig.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ public class AccountAuctionConfig {
4545

4646
AccountTargetingConfig targeting;
4747

48+
@JsonAlias("bid-rounding")
49+
AccountAuctionBidRoundingMode bidRounding;
50+
4851
@JsonProperty("preferredmediatype")
4952
Map<String, MediaType> preferredMediaTypes;
5053

src/test/groovy/org/prebid/server/functional/model/config/AccountAuctionConfig.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming
66
import groovy.transform.ToString
77
import org.prebid.server.functional.model.bidder.BidderName
88
import org.prebid.server.functional.model.request.auction.BidAdjustment
9+
import org.prebid.server.functional.model.request.auction.BidRounding
910
import org.prebid.server.functional.model.request.auction.PaaFormat
1011
import org.prebid.server.functional.model.request.auction.Targeting
1112
import org.prebid.server.functional.model.response.auction.MediaType
@@ -32,6 +33,7 @@ class AccountAuctionConfig {
3233
PrivacySandbox privacySandbox
3334
@JsonProperty("bidadjustments")
3435
BidAdjustment bidAdjustments
36+
BidRounding bidRounding
3537

3638
@JsonProperty("price_granularity")
3739
PriceGranularityType priceGranularitySnakeCase
@@ -49,4 +51,6 @@ class AccountAuctionConfig {
4951
AccountBidValidationConfig bidValidationsSnakeCase
5052
@JsonProperty("price_floors")
5153
AccountPriceFloorsConfig priceFloorsSnakeCase
54+
@JsonProperty("bid_rounding")
55+
BidRounding bidRoundingSnakeCase
5256
}

0 commit comments

Comments
 (0)