Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/application-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ There are two ways to configure application settings: database and file. This do
- `auction.bidadjustments.mediatype.*.*.*[].value` - value of the bid adjustment
- `auction.bidadjustments.mediatype.*.*.*[].currency` - currency of the bid adjustment
- `auction.events.enabled` - enables events for account if true
- `auction.bid-rounding` - bid rounding options are:
- **down** - rounding down to the lower price bucket
- **up** - rounding up to the higher price bucket
- **timesplit** - 50% of the time rounding down to the lower PB and 50% of the time rounding up to the higher price bucket
- **true** - if the price >= 50% of the range, rounding up to the higher price bucket, otherwise rounding down
- `auction.price-floors.enabled` - enables price floors for account if true. Defaults to true.
- `auction.price-floors.fetch.enabled`- enables data fetch for price floors for account if true. Defaults to false.
- `auction.price-floors.fetch.url` - url to fetch price floors data from.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,7 @@ private Future<CategoryMappingResult> createCategoryMapping(AuctionContext aucti
return categoryMappingService.createCategoryMapping(
bidderResponses,
auctionContext.getBidRequest(),
auctionContext.getAccount(),
auctionContext.getTimeoutContext().getTimeout())

.map(categoryMappingResult -> addCategoryMappingErrors(categoryMappingResult, auctionContext));
Expand Down Expand Up @@ -1561,7 +1562,7 @@ private Bid toBid(BidInfo bidInfo,
final String categoryDuration = bidInfo.getCategory();
targetingKeywords = keywordsCreator != null
? keywordsCreator.makeFor(
bid, seat, isWinningBid, cacheId, bidType.getName(), videoCacheId, categoryDuration)
bid, seat, isWinningBid, cacheId, bidType.getName(), videoCacheId, categoryDuration, account)
: null;
} else {
targetingKeywords = null;
Expand Down
35 changes: 29 additions & 6 deletions src/main/java/org/prebid/server/auction/CpmRange.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.proto.openrtb.ext.request.ExtGranularityRange;
import org.prebid.server.settings.model.Account;
import org.prebid.server.settings.model.AccountAuctionConfig;
import org.prebid.server.settings.model.AccountAuctionBidRoundingMode;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;

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

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

return increment != null ? calculate(cpm, min, increment) : null;
return increment != null ? calculate(cpm, min, increment, resolveRoundingMode(account)) : null;
}

private static BigDecimal calculate(BigDecimal cpm, BigDecimal min, BigDecimal increment) {
private static BigDecimal calculate(BigDecimal cpm,
BigDecimal min,
BigDecimal increment,
RoundingMode roundingMode) {

return cpm
.subtract(min)
.divide(increment, 0, RoundingMode.FLOOR)
.divide(increment, 0, roundingMode)
.multiply(increment)
.add(min);
}

private static RoundingMode resolveRoundingMode(Account account) {
final AccountAuctionBidRoundingMode accountRoundingMode = Optional.ofNullable(account)
.map(Account::getAuction)
.map(AccountAuctionConfig::getBidRounding)
.orElse(AccountAuctionBidRoundingMode.DOWN);

return switch (accountRoundingMode) {
case DOWN -> RoundingMode.FLOOR;
case UP -> RoundingMode.CEILING;
case TRUE -> RoundingMode.HALF_UP;
case TIMESPLIT -> ThreadLocalRandom.current().nextBoolean() ? RoundingMode.FLOOR : RoundingMode.CEILING;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.iab.openrtb.response.Bid;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.proto.openrtb.ext.request.ExtPriceGranularity;
import org.prebid.server.settings.model.Account;

import java.math.BigDecimal;
import java.util.ArrayList;
Expand Down Expand Up @@ -152,7 +153,8 @@ Map<String, String> makeFor(Bid bid,
String cacheId,
String format,
String vastCacheId,
String categoryDuration) {
String categoryDuration,
Account account) {

final Map<String, String> keywords = makeFor(
bidder,
Expand All @@ -164,7 +166,8 @@ Map<String, String> makeFor(Bid bid,
vastCacheId,
categoryDuration,
format,
bid.getDealid());
bid.getDealid(),
account);

if (resolver == null) {
return truncateKeys(keywords);
Expand All @@ -188,7 +191,8 @@ private Map<String, String> makeFor(String bidder,
String vastCacheId,
String categoryDuration,
String format,
String dealId) {
String dealId,
Account account) {

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

final String roundedCpm = isPriceGranularityValid() ? CpmRange.fromCpm(price, priceGranularity) : DEFAULT_CPM;
final String roundedCpm = isPriceGranularityValid()
? CpmRange.fromCpm(price, priceGranularity, account)
: DEFAULT_CPM;

keywordMap.put(this.keyPrefix + PB_KEY, roundedCpm);

keywordMap.put(this.keyPrefix + BIDDER_KEY, bidder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo;
import org.prebid.server.settings.ApplicationSettings;
import org.prebid.server.settings.model.Account;
import org.prebid.server.util.ObjectUtil;

import java.math.BigDecimal;
Expand Down Expand Up @@ -83,6 +84,7 @@ public BasicCategoryMappingService(ApplicationSettings applicationSettings, Jack
@Override
public Future<CategoryMappingResult> createCategoryMapping(List<BidderResponse> bidderResponses,
BidRequest bidRequest,
Account account,
Timeout timeout) {

final ExtRequestTargeting targeting = targeting(bidRequest);
Expand Down Expand Up @@ -111,8 +113,8 @@ public Future<CategoryMappingResult> createCategoryMapping(List<BidderResponse>

return makeBidderToBidCategory(
bidderResponses, withCategory, translateCategories, primaryAdServer, publisher, rejectedBids, timeout)
.map(categoryBidContexts -> resolveBidsCategoriesDurations(
bidderResponses, categoryBidContexts, bidRequest, targeting, withCategory, rejectedBids));
.map(categoryBidContexts -> resolveBidsCategoriesDurations(bidderResponses, categoryBidContexts,
account, bidRequest, targeting, withCategory, rejectedBids));
Comment thread
AntoxaAntoxic marked this conversation as resolved.
Outdated
}

private static ExtRequestTargeting targeting(BidRequest bidRequest) {
Expand Down Expand Up @@ -326,6 +328,7 @@ private static void collectCategoryFetchResults(CompositeFuture compositeFuture,
*/
private CategoryMappingResult resolveBidsCategoriesDurations(List<BidderResponse> bidderResponses,
List<CategoryBidContext> categoryBidContexts,
Account account,
BidRequest bidRequest,
ExtRequestTargeting targeting,
boolean withCategory,
Expand All @@ -342,8 +345,8 @@ private CategoryMappingResult resolveBidsCategoriesDurations(List<BidderResponse

final boolean appendBidderNames = BooleanUtils.toBooleanDefaultIfNull(targeting.getAppendbiddernames(), false);
final Map<String, Set<CategoryBidContext>> uniqueCatKeysToCategoryBids = categoryBidContexts.stream()
.map(categoryBidContext -> enrichCategoryBidContext(categoryBidContext, durations, priceGranularity,
withCategory, appendBidderNames, impIdToBiddersDealTear, rejectedBids))
.map(categoryBidContext -> enrichCategoryBidContext(categoryBidContext, account, durations,
priceGranularity, withCategory, appendBidderNames, impIdToBiddersDealTear, rejectedBids))
Comment thread
AntoxaAntoxic marked this conversation as resolved.
Outdated
.filter(Objects::nonNull)
.collect(Collectors.groupingBy(CategoryBidContext::getCategoryUniqueKey,
Collectors.mapping(Function.identity(), Collectors.toSet())));
Expand Down Expand Up @@ -504,6 +507,7 @@ private static boolean isNotRejected(String bidId, String bidder, List<RejectedB
* and creates {@link CategoryBidContext} which is holder for bid category related information.
*/
private CategoryBidContext enrichCategoryBidContext(CategoryBidContext categoryBidContext,
Account account,
List<Integer> durations,
PriceGranularity priceGranularity,
boolean withCategory,
Expand All @@ -522,7 +526,7 @@ private CategoryBidContext enrichCategoryBidContext(CategoryBidContext categoryB
return null;
}

final BigDecimal price = CpmRange.fromCpmAsNumber(bid.getPrice(), priceGranularity);
final BigDecimal price = CpmRange.fromCpmAsNumber(bid.getPrice(), priceGranularity, account);
final String rowPrice = CpmRange.format(price, priceGranularity.getPrecision());
final String category = categoryBidContext.getCategory();
final String categoryUniqueKey = createCategoryUniqueKey(withCategory, category, rowPrice, duration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import org.prebid.server.auction.model.BidderResponse;
import org.prebid.server.auction.model.CategoryMappingResult;
import org.prebid.server.execution.timeout.Timeout;
import org.prebid.server.settings.model.Account;

import java.util.List;

public interface CategoryMappingService {

Future<CategoryMappingResult> createCategoryMapping(List<BidderResponse> bidderResponses,
BidRequest bidRequest,
Account account,
Timeout timeout);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.prebid.server.auction.model.BidderResponse;
import org.prebid.server.auction.model.CategoryMappingResult;
import org.prebid.server.execution.timeout.Timeout;
import org.prebid.server.settings.model.Account;

import java.util.List;

Expand All @@ -13,6 +14,7 @@ public class NoOpCategoryMappingService implements CategoryMappingService {
@Override
public Future<CategoryMappingResult> createCategoryMapping(List<BidderResponse> bidderResponses,
BidRequest bidRequest,
Account account,
Timeout timeout) {

return Future.succeededFuture(CategoryMappingResult.of(bidderResponses));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.prebid.server.settings.model;

import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
import com.fasterxml.jackson.annotation.JsonProperty;

public enum AccountAuctionBidRoundingMode {

@JsonProperty("down")
@JsonEnumDefaultValue
DOWN,

@JsonProperty("true")
TRUE,

@JsonProperty("timesplit")
TIMESPLIT,

@JsonProperty("up")
UP
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public class AccountAuctionConfig {

AccountTargetingConfig targeting;

@JsonAlias("bid-rounding")
AccountAuctionBidRoundingMode bidRounding;

@JsonProperty("preferredmediatype")
Map<String, MediaType> preferredMediaTypes;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming
import groovy.transform.ToString
import org.prebid.server.functional.model.bidder.BidderName
import org.prebid.server.functional.model.request.auction.BidAdjustment
import org.prebid.server.functional.model.request.auction.BidRounding
import org.prebid.server.functional.model.request.auction.PaaFormat
import org.prebid.server.functional.model.request.auction.Targeting
import org.prebid.server.functional.model.response.auction.MediaType
Expand All @@ -31,6 +32,7 @@ class AccountAuctionConfig {
PrivacySandbox privacySandbox
@JsonProperty("bidadjustments")
BidAdjustment bidAdjustments
BidRounding bidRounding

@JsonProperty("price_granularity")
PriceGranularityType priceGranularitySnakeCase
Expand All @@ -48,4 +50,6 @@ class AccountAuctionConfig {
AccountBidValidationConfig bidValidationsSnakeCase
@JsonProperty("price_floors")
AccountPriceFloorsConfig priceFloorsSnakeCase
@JsonProperty("bid_rounding")
BidRounding bidRoundingSnakeCase
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.prebid.server.functional.model.request.auction

import com.fasterxml.jackson.annotation.JsonValue

enum BidRounding {

UP("up"),
DOWN("down"),
TRUE("true"),
TIME_SPLIT("timesplit"),
UNKNOWN("unknown"),

private String value

BidRounding(String value) {
this.value = value
}

@Override
@JsonValue
String toString() {
return value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class AmpSpec extends BaseSpec {

then: "Response should contain information from stored response"
def price = storedAuctionResponse.bid[0].price
assert response.targeting["hb_pb"] == getRoundedTargetingValueWithDefaultPrecision(price)
assert response.targeting["hb_pb"] == getRoundedTargetingValueWithDownPrecision(price)
assert response.targeting["hb_size"] == "${storedAuctionResponse.bid[0].weight}x${storedAuctionResponse.bid[0].height}"

and: "PBS not send request to bidder"
Expand Down
20 changes: 18 additions & 2 deletions src/test/groovy/org/prebid/server/functional/tests/BaseSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ import org.prebid.server.functional.util.ObjectMapperWrapper
import org.prebid.server.functional.util.PBSUtils
import spock.lang.Specification

import java.math.RoundingMode

import static java.math.RoundingMode.DOWN
import static java.math.RoundingMode.HALF_UP
import static java.math.RoundingMode.UP
import static org.prebid.server.functional.testcontainers.Dependencies.networkServiceContainer
import static org.prebid.server.functional.util.SystemProperties.DEFAULT_TIMEOUT

Expand Down Expand Up @@ -80,8 +84,16 @@ abstract class BaseSpec extends Specification implements ObjectMapperWrapper {
logs.findAll { it.contains(text) }
}

protected static String getRoundedTargetingValueWithDefaultPrecision(BigDecimal value) {
"${value.setScale(DEFAULT_TARGETING_PRECISION, DOWN)}0"
protected static String getRoundedTargetingValueWithDownPrecision(BigDecimal value) {
roundWithDefaultPrecisionAndRoundingType(value, DOWN)
}

protected static String getRoundedTargetingValueWithHalfUpPrecision(BigDecimal value) {
roundWithDefaultPrecisionAndRoundingType(value, HALF_UP)
}

protected static String getRoundedTargetingValueWithUpPrecision(BigDecimal value) {
roundWithDefaultPrecisionAndRoundingType(value, UP)
}

protected static Map<String, List<BidderRequest>> getRequests(BidResponse bidResponse) {
Expand All @@ -100,4 +112,8 @@ abstract class BaseSpec extends Specification implements ObjectMapperWrapper {
List<BidderCall> bidderCalls) {
[(bidderName): bidderCalls.collect { bidderCall -> decode(bidderCall.requestBody as String, BidderRequest) }]
}

private static GString roundWithDefaultPrecisionAndRoundingType(BigDecimal value, RoundingMode roundingMode) {
"${value.setScale(DEFAULT_TARGETING_PRECISION, roundingMode)}0"
}
}
Loading