Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -110,9 +112,21 @@ public Future<CategoryMappingResult> createCategoryMapping(List<BidderResponse>
final List<RejectedBid> rejectedBids = new ArrayList<>();

return makeBidderToBidCategory(
bidderResponses, withCategory, translateCategories, primaryAdServer, publisher, rejectedBids, timeout)
bidderResponses,
withCategory,
translateCategories,
primaryAdServer,
publisher,
rejectedBids,
timeout)
.map(categoryBidContexts -> resolveBidsCategoriesDurations(
bidderResponses, categoryBidContexts, bidRequest, targeting, withCategory, rejectedBids));
bidderResponses,
categoryBidContexts,
account,
bidRequest,
targeting,
withCategory,
rejectedBids));
}

private static ExtRequestTargeting targeting(BidRequest bidRequest) {
Expand Down Expand Up @@ -326,6 +340,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 +357,15 @@ 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))
.filter(Objects::nonNull)
.collect(Collectors.groupingBy(CategoryBidContext::getCategoryUniqueKey,
Collectors.mapping(Function.identity(), Collectors.toSet())));
Expand Down Expand Up @@ -504,6 +526,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 +545,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
Loading