From 7f1098a048d18ec84e2cf760350a8175fc489644 Mon Sep 17 00:00:00 2001 From: Sid Sahoo Date: Fri, 25 Oct 2024 20:36:10 +0000 Subject: [PATCH 01/12] Make bidding and decision logic script more readable --- package.json | 2 +- .../js/dsp/default/auction-bidding-logic.js | 79 ++++++++++------ .../js/ssp/default/auction-decision-logic.js | 89 +++++++++++++------ .../dsp/buyer-contextual-bidder-router.ts | 2 + 4 files changed, 118 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 6b2f46f4..9d83b927 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "docker": "docker-compose build && docker-compose up", "deploy": "./scripts/cloudrun_deploy.sh && ./scripts/firebase_deploy.sh", "clean": "docker-compose rm -f && docker volume prune -f && docker image prune -f && docker rmi -f $(docker images -q)", - "fmt": "prettier --write ." + "fmt": "pre-commit run --all-files" }, "devDependencies": { "firebase-tools": "^11.23.0", diff --git a/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js b/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js index 3dbc89cd..0c31558a 100644 --- a/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js +++ b/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js @@ -29,17 +29,61 @@ CURR_HOST = ''; AUCTION_ID = ''; /** Logs to console. */ -function log(msg, context) { +function log(message, context) { console.log( '[PSDemo] Buyer', CURR_HOST, 'bidding logic', AUCTION_ID, - msg, + message, + message, JSON.stringify({context}, ' ', ' '), ); } +/** Logs execution context for demonstrative purposes. */ +function logContextForDemo(message, context) { + const { + interestGroup, + auctionSignals, + perBuyerSignals, + // UNUSED trustedBiddingSignals, + // UNUSED browserSignals, + sellerSignals, + } = context; + AUCTION_ID = auctionSignals.auctionId; + if (interestGroup) { + CURR_HOST = interestGroup.owner.substring('https://'.length); + } else if (perBuyerSignals && perBuyerSignals.buyerHost) { + CURR_HOST = perBuyerSignals.buyerHost; + } else if (sellerSignals && sellerSignals.buyer) { + CURR_HOST = sellerSignals.buyer.substring('https://'.length); + } + log(message, context); +} + +/** Checks whether the current ad campaign is active. */ +function isCurrentCampaignActive(biddingContext) { + const { + // UNUSED interestGroup, + // UNUSED auctionSignals, + // UNUSED perBuyerSignals, + trustedBiddingSignals, + browserSignals, + } = biddingContext; + if ('true' !== trustedBiddingSignals['isActive']) { + // Don't place a bid if campaign is inactive. + log('not bidding since campaign is inactive', { + trustedBiddingSignals, + seller: browserSignals.seller, + topLevelSeller: browserSignals.topLevelSeller, + dataVersion: browserSignals.dataVersion, + }); + return false; + } + return true; +} + /** Calculates a bid price based on real-time signals. */ function calculateBidAmount(trustedBiddingSignals) { const minBid = Number(trustedBiddingSignals.minBid) || 0.5; @@ -172,37 +216,26 @@ function generateBid( trustedBiddingSignals, browserSignals, ) { - CURR_HOST = interestGroup.owner.substring('https://'.length); - AUCTION_ID = auctionSignals.auctionId; - log('generating bid', { + const biddingContext = { interestGroup, auctionSignals, perBuyerSignals, trustedBiddingSignals, browserSignals, - }); - if ('true' !== trustedBiddingSignals['isActive']) { - // Don't place a bid if campaign is inactive. - log('not bidding since campaign is inactive', { - trustedBiddingSignals, - seller: browserSignals.seller, - topLevelSeller: browserSignals.topLevelSeller, - dataVersion: browserSignals.dataVersion, - }); + }; + logContextForDemo('generateBid()', biddingContext); + if (!isCurrentCampaignActive(biddingContext)) { return; } - const biddingContext = { - interestGroup, - trustedBiddingSignals, - browserSignals, - }; const bid = 'VIDEO' === auctionSignals.adType ? getBidForVideoAd(biddingContext) : getBidForDisplayAd(biddingContext); if (bid) { - log('returning bid', {bid}); + log('returning bid', {bid, biddingContext}); return bid; + } else { + log('not bidding', {biddingContext}); } } @@ -212,11 +245,7 @@ function reportWin( sellerSignals, browserSignals, ) { - if (sellerSignals.buyer) { - CURR_HOST = sellerSignals.buyer.substring('https://'.length); - } - AUCTION_ID = auctionSignals.auctionId; - log('reporting win', { + logContextForDemo('reportWin()', { auctionSignals, perBuyerSignals, sellerSignals, diff --git a/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js b/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js index 1fd191c9..570be42d 100644 --- a/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js +++ b/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js @@ -29,36 +29,50 @@ CURR_HOST = ''; AUCTION_ID = ''; /** Logs to console. */ -function log(msg, context) { +function log(message, context) { console.log( '[PSDemo] Seller', CURR_HOST, 'decision logic', AUCTION_ID, - msg, + message, + message, JSON.stringify({context}, ' ', ' '), ); } -// ******************************************************** -// Top-level decision logic functions -// ******************************************************** -function scoreAd( - adMetadata, - bid, - auctionConfig, - trustedScoringSignals, - browserSignals, -) { +/** Logs execution context for demonstrative purposes. */ +function logContextForDemo(message, context) { + const { + // UNUSED adMetadata, + // UNUSED bid, + auctionConfig, + // UNUSED trustedScoringSignals, + browserSignals, + } = context; CURR_HOST = auctionConfig.seller.substring('https://'.length); AUCTION_ID = auctionConfig.auctionSignals.auctionId; - log('scoring ad', { - adMetadata, - bid, - auctionConfig, + log(message, context); + // Log reporting IDs if found. + const {buyerAndSellerReportingId, selectedBuyerAndSellerReportingId} = + context.browserSignals; + if (buyerAndSellerReportingId || selectedBuyerAndSellerReportingId) { + log('found reporting IDs', { + buyerAndSellerReportingId, + selectedBuyerAndSellerReportingId, + }); + } +} + +/** Returns a rejection score response if scored creative is blocked. */ +function getRejectScoreIfCreativeBlocked(scoringContext) { + const { + // UNUSED adMetadata, + // UNUSED bid, + // UNUSED auctionConfig, trustedScoringSignals, browserSignals, - }); + } = scoringContext; const {renderURL} = browserSignals; if (trustedScoringSignals && trustedScoringSignals.renderURL[renderURL]) { const parsedScoringSignals = JSON.parse( @@ -68,27 +82,48 @@ function scoreAd( parsedScoringSignals && 'BLOCKED' === parsedScoringSignals.label.toUpperCase() ) { + // Reject bid if creative is blocked. + // Reject bid if creative is blocked. log('rejecting bid blocked by publisher', { parsedScoringSignals, trustedScoringSignals, renderURL, buyer: browserSignals.interestGroupOwner, dataVersion: browserSignals.dataVersion, + scoringContext, + scoringContext, }); return { - // Reject bid if creative is blocked. desirability: 0, allowComponentAuction: true, rejectReason: 'blocked-by-publisher', }; } } - const {buyerAndSellerReportingId, selectedBuyerAndSellerReportingId} = - browserSignals; - log('found reporting IDs', { - buyerAndSellerReportingId, - selectedBuyerAndSellerReportingId, - }); +} + +// ******************************************************** +// Top-level decision logic functions +// ******************************************************** +function scoreAd( + adMetadata, + bid, + auctionConfig, + trustedScoringSignals, + browserSignals, +) { + const scoringContext = { + adMetadata, + bid, + auctionConfig, + trustedScoringSignals, + browserSignals, + }; + logContextForDemo('scoreAd()', scoringContext); + const rejectScore = getRejectScoreIfCreativeBlocked(scoringContext); + if (rejectScore) { + return rejectScore; + } return { desirability: bid, allowComponentAuction: true, @@ -97,11 +132,9 @@ function scoreAd( } function reportResult(auctionConfig, browserSignals) { - CURR_HOST = auctionConfig.seller.substring('https://'.length); - AUCTION_ID = auctionConfig.auctionSignals.auctionId; - log('reporting result', {auctionConfig, browserSignals}); + logContextForDemo('reportResult()', {auctionConfig, browserSignals}); sendReportTo(auctionConfig.seller + '/reporting?report=result'); - return { + return /* sellerSignals= */ { success: true, auctionId: AUCTION_ID, buyer: browserSignals.interestGroupOwner, diff --git a/services/ad-tech/src/routes/dsp/buyer-contextual-bidder-router.ts b/services/ad-tech/src/routes/dsp/buyer-contextual-bidder-router.ts index d3242b41..fa3a4021 100644 --- a/services/ad-tech/src/routes/dsp/buyer-contextual-bidder-router.ts +++ b/services/ad-tech/src/routes/dsp/buyer-contextual-bidder-router.ts @@ -82,6 +82,8 @@ BuyerContextualBidderRouter.get('/', async (req: Request, res: Response) => { renderURL, buyerSignals: { auctionId, + buyerHost: HOSTNAME, + buyerOrigin: CURRENT_ORIGIN, contextualBid: bid, contextualRenderURL: renderURL, contextualAdvertiser: ADVERTISER_CONTEXTUAL, From aaa5d356a0c46be8c05a6efd74c0b3e7583282ba Mon Sep 17 00:00:00 2001 From: Sid Sahoo Date: Fri, 25 Oct 2024 20:39:47 +0000 Subject: [PATCH 02/12] Remove extra log statement --- .../ad-tech/src/public/js/dsp/default/auction-bidding-logic.js | 1 - .../ad-tech/src/public/js/ssp/default/auction-decision-logic.js | 1 - 2 files changed, 2 deletions(-) diff --git a/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js b/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js index 0c31558a..b7b640bd 100644 --- a/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js +++ b/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js @@ -36,7 +36,6 @@ function log(message, context) { 'bidding logic', AUCTION_ID, message, - message, JSON.stringify({context}, ' ', ' '), ); } diff --git a/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js b/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js index 570be42d..eb856629 100644 --- a/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js +++ b/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js @@ -36,7 +36,6 @@ function log(message, context) { 'decision logic', AUCTION_ID, message, - message, JSON.stringify({context}, ' ', ' '), ); } From 18f0e96c27876a89cb9710dd3aa4b4d318cab49c Mon Sep 17 00:00:00 2001 From: Sid Sahoo Date: Fri, 25 Oct 2024 20:44:28 +0000 Subject: [PATCH 03/12] Remove extra comment line --- .../ad-tech/src/public/js/ssp/default/auction-decision-logic.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js b/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js index eb856629..c308f6e0 100644 --- a/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js +++ b/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js @@ -81,7 +81,6 @@ function getRejectScoreIfCreativeBlocked(scoringContext) { parsedScoringSignals && 'BLOCKED' === parsedScoringSignals.label.toUpperCase() ) { - // Reject bid if creative is blocked. // Reject bid if creative is blocked. log('rejecting bid blocked by publisher', { parsedScoringSignals, @@ -90,7 +89,6 @@ function getRejectScoreIfCreativeBlocked(scoringContext) { buyer: browserSignals.interestGroupOwner, dataVersion: browserSignals.dataVersion, scoringContext, - scoringContext, }); return { desirability: 0, From 60bfae62267eadd20d0976e2567344d25b89aecc Mon Sep 17 00:00:00 2001 From: Sid Sahoo <5680745+siddharth-sahoo@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:38:40 -0700 Subject: [PATCH 04/12] Update index.ejs Fix case typo. --- services/ad-tech/src/views/ssp/index.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/ad-tech/src/views/ssp/index.ejs b/services/ad-tech/src/views/ssp/index.ejs index 038c95b5..7c1588f7 100644 --- a/services/ad-tech/src/views/ssp/index.ejs +++ b/services/ad-tech/src/views/ssp/index.ejs @@ -6,7 +6,7 @@ - <%= title %> + <%= TITLE %> Date: Tue, 29 Oct 2024 21:38:45 +0000 Subject: [PATCH 05/12] Initial implementation of deals e2e --- .../js/dsp/default/auction-bidding-logic.js | 66 +++++++---- .../js/ssp/default/auction-decision-logic.js | 60 +++++++++- .../top-level-auction-decision-logic.js | 104 ++++++++++++++---- .../js/ssp/run-sequential-ad-auction.js | 9 +- .../src/views/display-ad-with-deals-only.ejs | 64 +++++++++++ 5 files changed, 252 insertions(+), 51 deletions(-) create mode 100644 services/news/src/views/display-ad-with-deals-only.ejs diff --git a/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js b/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js index b7b640bd..91ab6da4 100644 --- a/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js +++ b/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js @@ -95,35 +95,52 @@ function calculateBidAmount(trustedBiddingSignals) { } /** Selects a deal ID from selectable buyer and seller reporting IDs. */ -function selectDealId(selectedAd) { +function selectDealId(selectedAd, auctionSignals) { const { buyerReportingId, buyerAndSellerReportingId, selectableBuyerAndSellerReportingIds, } = selectedAd; - if (selectableBuyerAndSellerReportingIds?.length) { - const countOfSelectableIds = selectableBuyerAndSellerReportingIds.length; - const randomIndex = Math.floor(Math.random() * countOfSelectableIds); - const selectedId = selectableBuyerAndSellerReportingIds[randomIndex]; - log('found reporting IDs', { - buyerReportingId, - buyerAndSellerReportingId, - selectableBuyerAndSellerReportingIds, - selectedBuyerAndSellerReportingId: selectedId, - }); - return selectedId; - } else { - log('found reporting IDs', { - buyerReportingId, - buyerAndSellerReportingId, - selectableBuyerAndSellerReportingIds, - }); + if ( + !selectableBuyerAndSellerReportingIds || + !selectableBuyerAndSellerReportingIds.length + ) { + // No deal IDs in interest group to choose from. + return; + } + // Filter deals in interest group with deals from bid request. + const eligibleDeals = ((availableDeals) => { + if (availableDeals && availableDeals.length) { + return selectableBuyerAndSellerReportingIds.filter((id) => + auctionSignals.availableDeals.includes(id), + ); + } else { + return selectableBuyerAndSellerReportingIds; + } + })(auctionSignals.availableDeals.split(',')); + if (!eligibleDeals || !eligibleDeals.length) { + // No eligible deals for this bid request. + return; } + // Choose one of the eligible deals at random. + const countOfEligibleIds = eligibleDeals.length; + const randomIndex = Math.floor(Math.random() * countOfEligibleIds); + const selectedId = eligibleDeals[randomIndex]; + // Log reporting IDs to console. + log('found reporting IDs', { + buyerReportingId, + buyerAndSellerReportingId, + selectableBuyerAndSellerReportingIds, + selectedId, + }); + return selectedId; } /** Returns the bid response for a video ad request. */ function getBidForVideoAd({ interestGroup, + // UNUSED auctionSignals, + // UNUSED perBuyerSignals, trustedBiddingSignals, browserSignals, }) { @@ -158,6 +175,8 @@ function getBidForVideoAd({ /** Returns the bid response for a display ad request. */ function getBidForDisplayAd({ interestGroup, + auctionSignals, + // UNUSED perBuyerSignals, trustedBiddingSignals, browserSignals, }) { @@ -184,7 +203,7 @@ function getBidForDisplayAd({ height: selectedAd.metadata.adSizes[0].height, }, // Specify selected deal ID for reporting. - selectedBuyerAndSellerReportingId: selectDealId(selectedAd), + selectedBuyerAndSellerReportingId: selectDealId(selectedAd, auctionSignals), /* TODO: Use-case: Ad cost reporting adCost: optionalAdCost, @@ -250,6 +269,15 @@ function reportWin( sellerSignals, browserSignals, }); + const reportingContext = { + auctionId: AUCTION_ID, + pageURL: auctionSignals.pageURL, + componentSeller: browserSignals.seller, + topLevelSeller: browserSignals.topLevelSeller, + renderURL: browserSignals.renderURL, + bid: browserSignals.bid, + bidCurrency: browserSignals.bidCurrency, + }; // Add query parameters from renderURL to beacon URL. const additionalQueryParams = browserSignals.renderURL .substring(browserSignals.renderURL.indexOf('?') + 1) diff --git a/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js b/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js index c308f6e0..2c30188f 100644 --- a/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js +++ b/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js @@ -47,7 +47,7 @@ function logContextForDemo(message, context) { // UNUSED bid, auctionConfig, // UNUSED trustedScoringSignals, - browserSignals, + // UNUSED browserSignals, } = context; CURR_HOST = auctionConfig.seller.substring('https://'.length); AUCTION_ID = auctionConfig.auctionSignals.auctionId; @@ -99,6 +99,31 @@ function getRejectScoreIfCreativeBlocked(scoringContext) { } } +/** Returns a rejection score if the deal in bid is not eligible. */ +function getRejectScoreIfDealIneligible(scoringContext) { + const {selectedBuyerAndSellerReportingId} = scoringContext.browserSignals; + const {availableDeals, strictRejectForDeals} = + scoringContext.auctionConfig.auctionSignals; + if (availableDeals && availableDeals.length) { + if (!availableDeals.includes(selectedBuyerAndSellerReportingId)) { + if (strictRejectForDeals) { + // For strict rejections on deals, assign score of 0. + return { + desirability: 0, + allowComponentAuction: true, + rejectReason: 'blocked-by-publisher', + }; + } else { + // For lax rejections on deals, execute first price auction. + return { + desirability: scoringContext.bid, + allowComponentAuction: true, + }; + } + } + } +} + // ******************************************************** // Top-level decision logic functions // ******************************************************** @@ -117,10 +142,19 @@ function scoreAd( browserSignals, }; logContextForDemo('scoreAd()', scoringContext); - const rejectScore = getRejectScoreIfCreativeBlocked(scoringContext); - if (rejectScore) { - return rejectScore; + // Check if ad creative is blocked. + const creativeBlockedRejectScore = + getRejectScoreIfCreativeBlocked(scoringContext); + if (creativeBlockedRejectScore) { + return creativeBlockedRejectScore; } + // Check if DSP responded with the correct deal. + const dealIneligibleRejectScore = + getRejectScoreIfDealIneligible(scoringContext); + if (dealIneligibleRejectScore) { + return dealIneligibleRejectScore; + } + // Finally execute a first-price auction. return { desirability: bid, allowComponentAuction: true, @@ -130,7 +164,23 @@ function scoreAd( function reportResult(auctionConfig, browserSignals) { logContextForDemo('reportResult()', {auctionConfig, browserSignals}); - sendReportTo(auctionConfig.seller + '/reporting?report=result'); + const reportingContext = { + auctionId: AUCTION_ID, + pageURL: auctionConfig.auctionSignals.pageURL, + topLevelSeller: browserSignals.topLevelSeller, + winningBuyer: browserSignals.interestGroupOwner, + renderURL: browserSignals.renderURL, + bid: browserSignals.bid, + bidCurrency: browserSignals.bidCurrency, + buyerAndSellerReportingId: browserSignals.buyerAndSellerReportingId, + selectedBuyerAndSellerReportingId: + browserSignals.selectedBuyerAndSellerReportingId, + }; + let reportUrl = auctionConfig.seller + '/reporting?report=result'; + for (const [key, value] of Object.entries(reportingContext)) { + reportUrl = `${reportUrl}&${key}=${value}`; + } + sendReportTo(reportUrl); return /* sellerSignals= */ { success: true, auctionId: AUCTION_ID, diff --git a/services/ad-tech/src/public/js/ssp/default/top-level-auction-decision-logic.js b/services/ad-tech/src/public/js/ssp/default/top-level-auction-decision-logic.js index 26890a43..6fd04021 100644 --- a/services/ad-tech/src/public/js/ssp/default/top-level-auction-decision-logic.js +++ b/services/ad-tech/src/public/js/ssp/default/top-level-auction-decision-logic.js @@ -40,28 +40,42 @@ function log(msg, context) { ); } -// ******************************************************** -// Top-level decision logic functions -// ******************************************************** -function scoreAd( - adMetadata, - bid, - auctionConfig, - trustedScoringSignals, - browserSignals, -) { - CURR_HOST = auctionConfig.seller.substring('https://'.length); - const {componentSeller} = browserSignals; - AUCTION_ID = auctionConfig.componentAuctions.find( - (componentAuction) => componentSeller === componentAuction.seller, - ).auctionSignals.auctionId; - log('scoring ad', { - adMetadata, - bid, +/** Logs execution context for demonstrative purposes. */ +function logContextForDemo(message, context) { + const { + // UNUSED adMetadata, + // UNUSED bid, auctionConfig, + // UNUSED trustedScoringSignals, + browserSignals, + } = context; + CURR_HOST = auctionConfig.seller.substring('https://'.length); + const winningComponentSeller = browserSignals.componentSeller; + const winningComponentAuctionConfig = auctionConfig.componentAuctions.find( + (componentAuction) => winningComponentSeller === componentAuction.seller, + ); + AUCTION_ID = winningComponentAuctionConfig.auctionSignals.auctionId; + log(message, context); + // Log reporting IDs if found. + const {buyerAndSellerReportingId, selectedBuyerAndSellerReportingId} = + context.browserSignals; + if (buyerAndSellerReportingId || selectedBuyerAndSellerReportingId) { + log('found reporting IDs', { + buyerAndSellerReportingId, + selectedBuyerAndSellerReportingId, + }); + } +} + +/** Returns a rejection score response if scored creative is blocked. */ +function getRejectScoreIfCreativeBlocked(scoringContext) { + const { + // UNUSED adMetadata, + // UNUSED bid, + // UNUSED auctionConfig, trustedScoringSignals, browserSignals, - }); + } = scoringContext; const {renderURL} = browserSignals; if (trustedScoringSignals && trustedScoringSignals.renderURL[renderURL]) { const parsedScoringSignals = JSON.parse( @@ -71,21 +85,47 @@ function scoreAd( parsedScoringSignals && 'BLOCKED' === parsedScoringSignals.label.toUpperCase() ) { + // Reject bid if creative is blocked. log('rejecting bid blocked by publisher', { parsedScoringSignals, trustedScoringSignals, renderURL, buyer: browserSignals.interestGroupOwner, dataVersion: browserSignals.dataVersion, + scoringContext, }); return { - // Reject bid if creative is blocked. desirability: 0, allowComponentAuction: true, rejectReason: 'blocked-by-publisher', }; } } +} + +// ******************************************************** +// Top-level decision logic functions +// ******************************************************** +function scoreAd( + adMetadata, + bid, + auctionConfig, + trustedScoringSignals, + browserSignals, +) { + const scoringContext = { + adMetadata, + bid, + auctionConfig, + trustedScoringSignals, + browserSignals, + }; + logContextForDemo('scoreAd()', scoringContext); + const creativeBlockedRejectScore = + getRejectScoreIfCreativeBlocked(scoringContext); + if (creativeBlockedRejectScore) { + return creativeBlockedRejectScore; + } const {buyerAndSellerReportingId, selectedBuyerAndSellerReportingId} = browserSignals; log('found reporting IDs', { @@ -100,13 +140,29 @@ function scoreAd( } function reportResult(auctionConfig, browserSignals) { - CURR_HOST = auctionConfig.seller.substring('https://'.length); + logContextForDemo('reportResult()', {auctionConfig, browserSignals}); const winningComponentSeller = browserSignals.componentSeller; - AUCTION_ID = auctionConfig.componentAuctions.find( + const winningComponentAuctionConfig = auctionConfig.componentAuctions.find( (componentAuction) => winningComponentSeller === componentAuction.seller, - ).auctionSignals.auctionId; + ); log('reporting result', {auctionConfig, browserSignals}); - sendReportTo(auctionConfig.seller + '/reporting?report=result'); + const reportingContext = { + auctionId: AUCTION_ID, + pageURL: winningComponentAuctionConfig.auctionSignals.pageURL, + winningComponentSeller, + winningBuyer: browserSignals.interestGroupOwner, + renderURL: browserSignals.renderURL, + bid: browserSignals.bid, + bidCurrency: browserSignals.bidCurrency, + buyerAndSellerReportingId: browserSignals.buyerAndSellerReportingId, + selectedBuyerAndSellerReportingId: + browserSignals.selectedBuyerAndSellerReportingId, + }; + let reportUrl = auctionConfig.seller + '/reporting?report=result'; + for (const [key, value] of Object.entries(reportingContext)) { + reportUrl = `${reportUrl}&${key}=${value}`; + } + sendReportTo(reportUrl); return { success: true, auctionId: AUCTION_ID, diff --git a/services/ad-tech/src/public/js/ssp/run-sequential-ad-auction.js b/services/ad-tech/src/public/js/ssp/run-sequential-ad-auction.js index 2cc1bcdf..991ecf32 100644 --- a/services/ad-tech/src/public/js/ssp/run-sequential-ad-auction.js +++ b/services/ad-tech/src/public/js/ssp/run-sequential-ad-auction.js @@ -37,12 +37,14 @@ const iframeUrl = new URL(window.location.href); const publisher = iframeUrl.searchParams.get('publisher'); if (message.origin !== publisher) { - return log('ignoring message from unknown origin', {message, publisher}); + log('ignoring message from unknown origin', {message, publisher}); + return []; } try { const {adUnit, otherSellers} = JSON.parse(message.data); if (!adUnit.adType) { - return log('stopping as adType not found in adUnit', {adUnit}); + log('stopping as adType not found in adUnit', {adUnit}); + return []; } if (!otherSellers || !otherSellers.length) { log('did not find other sellers', {adUnit, otherSellers}); @@ -50,7 +52,8 @@ } return [adUnit, otherSellers]; } catch (e) { - return log('encountered error in parsing adUnit config', {message}); + log('encountered error in parsing adUnit config', {message}); + return []; } }; diff --git a/services/news/src/views/display-ad-with-deals-only.ejs b/services/news/src/views/display-ad-with-deals-only.ejs new file mode 100644 index 00000000..dac1e63f --- /dev/null +++ b/services/news/src/views/display-ad-with-deals-only.ejs @@ -0,0 +1,64 @@ + + + + + + + <%= TITLE %> + + + + + + + + + + <%- include('components/header') %> +
+
+

<%= TEXT_LOREM %>

+ <%- include('components/display-ads') %> +

<%= TEXT_LOREM %>

+

<%= TEXT_LOREM %>

+

<%= TEXT_LOREM %>

+
+ <%- include('components/aside') %> +
+ <%- include('components/footer', {HOME_HOST, EXTERNAL_PORT}) %> + + + From d8f03b2e685abc8dbde5882ae374b2dafeb2d6f3 Mon Sep 17 00:00:00 2001 From: Sid Sahoo Date: Tue, 29 Oct 2024 21:51:03 +0000 Subject: [PATCH 06/12] Add reporting for buyers --- .../js/dsp/default/auction-bidding-logic.js | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js b/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js index 91ab6da4..9a43d925 100644 --- a/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js +++ b/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js @@ -269,6 +269,10 @@ function reportWin( sellerSignals, browserSignals, }); + // Assemble query parameters for event logs. + let additionalQueryParams = browserSignals.renderURL.substring( + browserSignals.renderURL.indexOf('?') + 1, + ); const reportingContext = { auctionId: AUCTION_ID, pageURL: auctionSignals.pageURL, @@ -277,15 +281,22 @@ function reportWin( renderURL: browserSignals.renderURL, bid: browserSignals.bid, bidCurrency: browserSignals.bidCurrency, + redirect: browserSignals.seller, + buyerReportingId: browserSignals.buyerReportingId, + buyerAndSellerReportingId: browserSignals.buyerAndSellerReportingId, + selectedBuyerAndSellerReportingId: + browserSignals.selectedBuyerAndSellerReportingId, }; - // Add query parameters from renderURL to beacon URL. - const additionalQueryParams = browserSignals.renderURL - .substring(browserSignals.renderURL.indexOf('?') + 1) - .concat(`&redirect=${browserSignals.seller}`); + for (const [key, value] of Object.entries(reportingContext)) { + additionalQueryParams = additionalQueryParams.concat(`&${key}=${value}`); + } registerAdBeacon({ 'impression': `${browserSignals.interestGroupOwner}/reporting?report=impression&${additionalQueryParams}`, 'reserved.top_navigation_start': `${browserSignals.interestGroupOwner}/reporting?report=top_navigation_start&${additionalQueryParams}`, 'reserved.top_navigation_commit': `${browserSignals.interestGroupOwner}/reporting?report=top_navigation_commit&${additionalQueryParams}`, }); - sendReportTo(browserSignals.interestGroupOwner + '/reporting?report=win'); + sendReportTo( + browserSignals.interestGroupOwner + + `/reporting?report=win&${additionalQueryParams}`, + ); } From 9468f4b8b3885670736f5b8ce6c80aab920dbad8 Mon Sep 17 00:00:00 2001 From: Sid Sahoo <5680745+siddharth-sahoo@users.noreply.github.com> Date: Thu, 31 Oct 2024 18:11:11 -0700 Subject: [PATCH 07/12] Minor update to test push a change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c878f87c..2fe037b5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Privacy Sandbox Demos -A use case demo library for [Privacy Sandbox APIs](https://developer.chrome.com/en/docs/privacy-sandbox/) on the web. +A use case demo library for the [Privacy Sandbox APIs](https://developer.chrome.com/en/docs/privacy-sandbox/) on the web. ## Motivation From df7d7fe3e84c0bcdb38d61f32ba743e1a45bf3f4 Mon Sep 17 00:00:00 2001 From: Sid Sahoo Date: Fri, 25 Oct 2024 20:36:10 +0000 Subject: [PATCH 08/12] Implement deals with update IG flow --- .../src/controllers/key-value-store.ts | 2 +- services/ad-tech/src/lib/constants.ts | 36 +++- .../ad-tech/src/lib/interest-group-helper.ts | 179 ++++++++++++++++-- .../js/dsp/default/auction-bidding-logic.js | 69 ++++++- .../js/ssp/default/auction-decision-logic.js | 108 ++++++----- .../src/routes/dsp/bidding-signals-router.ts | 55 +++--- .../dsp/buyer-contextual-bidder-router.ts | 8 +- .../ad-tech/src/routes/dsp/buyer-router.ts | 118 ++++++------ services/news/src/views/components/aside.ejs | 11 ++ ...=> display-ad-with-deals-multi-seller.ejs} | 18 +- .../display-ad-with-deals-single-seller.ejs | 70 +++++++ 11 files changed, 516 insertions(+), 158 deletions(-) rename services/news/src/views/{display-ad-with-deals-only.ejs => display-ad-with-deals-multi-seller.ejs} (74%) create mode 100644 services/news/src/views/display-ad-with-deals-single-seller.ejs diff --git a/services/ad-tech/src/controllers/key-value-store.ts b/services/ad-tech/src/controllers/key-value-store.ts index 5e54a3c1..21099ad1 100644 --- a/services/ad-tech/src/controllers/key-value-store.ts +++ b/services/ad-tech/src/controllers/key-value-store.ts @@ -27,7 +27,7 @@ export class KeyValueStore { defaultValues: string[][] = [], resetIntervalInMins: number = 30, ) { - console.log('Initializing in-memory key value store.'); + console.log('Initializing in-memory key value store.', {defaultValues}); this.defaultData = defaultValues; this.rewriteDefaults(); setInterval(this.rewriteDefaults, 1000 * 60 * resetIntervalInMins); diff --git a/services/ad-tech/src/lib/constants.ts b/services/ad-tech/src/lib/constants.ts index ab64c029..3623c826 100644 --- a/services/ad-tech/src/lib/constants.ts +++ b/services/ad-tech/src/lib/constants.ts @@ -69,17 +69,47 @@ export const CURRENT_ORIGIN = new URL( ).toString(); // **************************************************************************** -// CONTEXTUAL AUCTION METADATA +// BIDDING SIGNALS - CONTEXTUAL AUCTIONS // **************************************************************************** +// NOTE: These bidding signals are only used on the server-side to respond to a +// contextual bid request from the browser or server-to-server. /** * Name of the contextual advertiser. * This is used in the buyer's bid response to contextual bid requests. */ export const ADVERTISER_CONTEXTUAL = 'ContextNext'; /** Max bid CPM for contextual auctions. */ -export const MAX_CONTEXTUAL_BID = 1.5; +export const CONTEXTUAL_BID_MAX = 1.5; /** Min bid CPM for contextual auctions. */ -export const MIN_CONTEXTUAL_BID = 0.5; +export const CONTEXTUAL_BID_MIN = 0.5; + +// **************************************************************************** +// BIDDING SIGNALS - PROTECTED AUDIENCE AUCTIONS +// **************************************************************************** +// NOTE: These bidding signals are stored in the BYOS Key-Value Server, where +// values are stored as strings. +export const BIDDING_SIGNAL_KEY_IS_ACTIVE = 'isActive'; +export const BIDDING_SIGNAL_KEY_BID_MIN = 'minBid'; +export const BIDDING_SIGNAL_KEY_BID_MAX = 'maxBid'; +export const BIDDING_SIGNAL_KEY_BID_MULTIPLIER = 'multiplier'; +/** The deafult bidding signals for the BYOS Key-Value Serice. */ +export const BIDDING_SIGNALS_DEFAULT = [ + // Whether the campaign is active. + [BIDDING_SIGNAL_KEY_IS_ACTIVE, 'true'], + // Min bid CPM for Protected Audience auctions. + [BIDDING_SIGNAL_KEY_BID_MIN, '3.5'], + // Max bid CPM for Protected Audience auctions. + [BIDDING_SIGNAL_KEY_BID_MAX, '4.5'], + // Default bid multiplier for Protected Audience auctions. + [BIDDING_SIGNAL_KEY_BID_MULTIPLIER, '1.1'], +]; +export const BIDDING_SIGNALS_DEALS = [ + // Deal specific bid multipliers for Protected Audience auctions. + // Template for keys: `multiplier-${dealId}` + ['multiplier-deal-1', '0.5'], // Deal ID: deal-1 + ['multiplier-deal-2', '0.4'], // Deal ID: deal-2 + ['multiplier-deal-3', '0.45'], // Deal ID: deal-3 +]; // **************************************************************************** // ADVERTISER METADATA diff --git a/services/ad-tech/src/lib/interest-group-helper.ts b/services/ad-tech/src/lib/interest-group-helper.ts index badfede5..8baf4f6d 100644 --- a/services/ad-tech/src/lib/interest-group-helper.ts +++ b/services/ad-tech/src/lib/interest-group-helper.ts @@ -12,19 +12,25 @@ */ import { + CURRENT_ORIGIN, + BIDDING_SIGNALS_DEFAULT, EXTERNAL_PORT, HOSTNAME, MACRO_DISPLAY_RENDER_URL_AD_SIZE, MACRO_VIDEO_RENDER_URL_SSP_VAST, + BIDDING_SIGNALS_DEALS, } from './constants.js'; +// **************************************************************************** +// ENUMS AND INTERFACES +// **************************************************************************** /** Supported ad types. */ export enum AdType { DISPLAY = 'DISPLAY', VIDEO = 'VIDEO', } -/** Generalized interface for an interest group ad object. */ +/** Interface for an interest group ad object. */ export interface InterestGroupAd { // REQUIRED FIELDS renderURL: string; @@ -41,32 +47,87 @@ export interface InterestGroupAd { buyerAndSellerReportingId?: string; } +/** Generalized interface of an ad size. */ +export interface InterestGroupAdSize { + width: string; + height: string; +} + +/** Top-level interface of an interest group. */ +export interface InterestGroup { + /** Name of the interest group: ${advertiser}-${usecase} */ + name: string; + /** Origin for the ad buyer. */ + owner: string; + /** URL to script defining generateBid() and reportWin(). */ + biddingLogicURL?: string; + /** Endpoint for real-time bidding signals. */ + trustedBiddingSignalsURL?: string; + /** Real-time bidding signals to query for. */ + trustedBiddingSignalsKeys?: string[]; + /** Endpoint to periodically update the interest group. */ + updateURL?: string; + /** User-specific bidding signals to consider while bidding. */ + userBiddingSignals?: {[key: string]: string}; + /** All ads to consider while bidding. */ + ads?: InterestGroupAd[]; + /** Map of ad sizes indexed by a label for the size. */ + adSizes?: {[key: string]: InterestGroupAdSize}; + /** Map of ad size labels indexed by the ad size group label. */ + sizeGroups?: {[key: string]: string[]}; +} + +/** Generalized interface of the interest targeting context. */ +export interface TargetingContext { + // REQUIRED FIELDS + /** Hostname of the advertiser. */ + advertiser: string; + /** Usecase for targeting, or 'default'. */ + usecase: string; + /** Whether this is a fresh request or an update. */ + isUpdateRequest: boolean; + // OPTIONAL FIELDS + /** Advertiser prouct item ID. */ + itemId?: string; + /** Real-time bidding signal keys to include in the interest group. */ + biddingSignalKeys?: string[]; + /** All other undocumented custom query parameters. */ + additionalContext?: {[key: string]: string[]}; +} + // **************************************************************************** // HELPER FUNCTIONS // **************************************************************************** /** Returns video ads for a given advertiser and SSP hosts to integrate. */ -const getVideoAdForRequest = (advertiser: string): InterestGroupAd => { +const getVideoAdForRequest = ( + targetingContext: TargetingContext, +): InterestGroupAd => { + const {advertiser} = targetingContext; const renderUrl = new URL( `https://${HOSTNAME}:${EXTERNAL_PORT}/ads/video-ads?`, ); renderUrl.searchParams.append('advertiser', advertiser); - return { + const ad: InterestGroupAd = { renderURL: `${renderUrl.toString()}&${MACRO_VIDEO_RENDER_URL_SSP_VAST}`, metadata: { advertiser, adType: AdType.VIDEO, }, - selectableBuyerAndSellerReportingIds: ['deal1', 'deal2', 'deal3'], buyerReportingId: 'buyerSpecificInfo1', buyerAndSellerReportingId: 'seatid-1234', }; + if (targetingContext.isUpdateRequest) { + // Only include deal IDs in update requests. + ad.selectableBuyerAndSellerReportingIds = ['deal-1', 'deal-2', 'deal-3']; + } + return ad; }; /** Returns the interest group display ad to for the given advertiser. */ const getDisplayAdForRequest = ( - advertiser: string, - itemId?: string, + targetingContext: TargetingContext, ): InterestGroupAd => { + const {advertiser, itemId} = targetingContext; const renderUrl = new URL( `https://${HOSTNAME}:${EXTERNAL_PORT}/ads/display-ads`, ); @@ -74,7 +135,7 @@ const getDisplayAdForRequest = ( if (itemId) { renderUrl.searchParams.append('itemId', itemId); } - return { + const ad: InterestGroupAd = { renderURL: `${renderUrl.toString()}&${MACRO_DISPLAY_RENDER_URL_AD_SIZE}`, metadata: { advertiser, @@ -82,24 +143,104 @@ const getDisplayAdForRequest = ( adSizes: [{width: '300px', height: '250px'}], }, sizeGroup: 'medium-rectangle', - selectableBuyerAndSellerReportingIds: ['deal1', 'deal2', 'deal3'], buyerReportingId: 'buyerSpecificInfo1', buyerAndSellerReportingId: 'seatid-1234', }; + if (targetingContext.isUpdateRequest) { + // Only include deal IDs in update requests. + ad.selectableBuyerAndSellerReportingIds = ['deal-1', 'deal-2', 'deal-3']; + } + return ad; +}; + +/** Returns the interest group name to use. */ +const getInterestGroupName = (targetingContext: TargetingContext): string => { + const {advertiser, usecase} = targetingContext; + return `${advertiser}-${usecase}`; +}; + +/** Returns the update URL with the targeting context. */ +const constructInterestGroupUpdateUrl = ( + targetingContext: TargetingContext, +): string => { + const interestGroupName = getInterestGroupName(targetingContext); + const updateURL = new URL( + `https://${HOSTNAME}:${EXTERNAL_PORT}/dsp/interest-group-update.json`, + ); + updateURL.searchParams.append('interestGroupName', interestGroupName); + updateURL.searchParams.append('advertiser', targetingContext.advertiser); + updateURL.searchParams.append('usecase', targetingContext.usecase); + if (targetingContext.itemId) { + updateURL.searchParams.append('itemId', targetingContext.itemId); + } + if (targetingContext.biddingSignalKeys?.length) { + updateURL.searchParams.append( + 'biddingSignalKeys', + targetingContext.biddingSignalKeys.join(','), + ); + } + if (targetingContext.additionalContext) { + for (const [key, value] of Object.entries( + targetingContext.additionalContext, + )) { + updateURL.searchParams.append(key, value.join(',')); + } + } + return updateURL.toString(); +}; + +/** Returns the bidding signal keys to include in the interest group. */ +const getBiddingSignalKeys = (targetingContext: TargetingContext): string[] => { + const biddingSignalsKeys = [...BIDDING_SIGNALS_DEFAULT.map(([key]) => key)]; + if (targetingContext.biddingSignalKeys) { + biddingSignalsKeys.push(...targetingContext.biddingSignalKeys); + } + if (targetingContext.isUpdateRequest) { + // Only include keys related to deals for update requests. + biddingSignalsKeys.push(...BIDDING_SIGNALS_DEALS.map(([key]) => key)); + } + return biddingSignalsKeys; }; // **************************************************************************** // EXPORTED FUNCTIONS // **************************************************************************** -/** Returns the interest groups ads for the given advertiser. */ -export const getAdsForRequest = ( - advertiser: string, - itemId?: string, -): InterestGroupAd[] => { - // Include all types of ads in the interest group and filter based on - // opportunity at bidding time. - return [ - getDisplayAdForRequest(advertiser, itemId), - getVideoAdForRequest(advertiser), - ]; +/** Returns the interest group for the given targeting context. */ +export const getInterestGroup = ( + targetingContext: TargetingContext, +): InterestGroup => { + const {usecase} = targetingContext; + const userBiddingSignals: {[key: string]: string} = { + 'user-signal-key-1': 'user-signal-value-1', + }; + if (targetingContext.additionalContext) { + for (const [key, values] of Object.entries( + targetingContext.additionalContext, + )) { + userBiddingSignals[key] = JSON.stringify(values); + } + } + return { + name: getInterestGroupName(targetingContext), + owner: CURRENT_ORIGIN, + biddingLogicURL: new URL( + `https://${HOSTNAME}:${EXTERNAL_PORT}/js/dsp/${usecase}/auction-bidding-logic.js`, + ).toString(), + trustedBiddingSignalsURL: new URL( + `https://${HOSTNAME}:${EXTERNAL_PORT}/dsp/realtime-signals/bidding-signal.json`, + ).toString(), + trustedBiddingSignalsKeys: getBiddingSignalKeys(targetingContext), + updateURL: constructInterestGroupUpdateUrl(targetingContext), + userBiddingSignals, + ads: [ + getDisplayAdForRequest(targetingContext), + getVideoAdForRequest(targetingContext), + ], + adSizes: { + 'medium-rectangle-default': {'width': '300px', 'height': '250px'}, + }, + sizeGroups: { + 'medium-rectangle': ['medium-rectangle-default'], + }, + }; }; diff --git a/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js b/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js index 9a43d925..dfaf8e71 100644 --- a/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js +++ b/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js @@ -83,11 +83,60 @@ function isCurrentCampaignActive(biddingContext) { return true; } +/** Logs execution context for demonstrative purposes. */ +function logContextForDemo(message, context) { + const { + interestGroup, + auctionSignals, + perBuyerSignals, + // UNUSED trustedBiddingSignals, + // UNUSED browserSignals, + sellerSignals, + } = context; + AUCTION_ID = auctionSignals.auctionId; + if (interestGroup) { + CURR_HOST = interestGroup.owner.substring('https://'.length); + } else if (perBuyerSignals && perBuyerSignals.buyerHost) { + CURR_HOST = perBuyerSignals.buyerHost; + } else if (sellerSignals && sellerSignals.buyer) { + CURR_HOST = sellerSignals.buyer.substring('https://'.length); + } + log(message, context); +} + +/** Checks whether the current ad campaign is active. */ +function isCurrentCampaignActive(biddingContext) { + const { + // UNUSED interestGroup, + // UNUSED auctionSignals, + // UNUSED perBuyerSignals, + trustedBiddingSignals, + browserSignals, + } = biddingContext; + if ('true' !== trustedBiddingSignals['isActive']) { + // Don't place a bid if campaign is inactive. + log('not bidding since campaign is inactive', { + trustedBiddingSignals, + seller: browserSignals.seller, + topLevelSeller: browserSignals.topLevelSeller, + dataVersion: browserSignals.dataVersion, + }); + return false; + } + return true; +} + /** Calculates a bid price based on real-time signals. */ -function calculateBidAmount(trustedBiddingSignals) { +function calculateBidAmount(trustedBiddingSignals, dealId) { const minBid = Number(trustedBiddingSignals.minBid) || 0.5; const maxBid = Number(trustedBiddingSignals.maxBid) || 1.5; - const multiplier = Number(trustedBiddingSignals.multiplier) || 1.0; + let multiplier = 1.0; + if (dealId) { + // If an eligible deal is found, use the corresponding bid multiplier. + multiplier = Number(trustedBiddingSignals[`multiplier-${dealId}`]) || 0.5; + } else { + multiplier = Number(trustedBiddingSignals.multiplier) || 1.0; + } let bid = Math.random() * (maxBid - minBid) + minBid; bid = (bid * multiplier).toFixed(2); log('calculated bid price', {bid, minBid, maxBid, multiplier}); @@ -145,22 +194,26 @@ function getBidForVideoAd({ browserSignals, }) { const {ads} = interestGroup; - // Filter for video ads specifically mapped to current SSP. + // Select an ad meeting the auction requirements. const [selectedAd] = ads.filter((ad) => 'VIDEO' === ad.metadata.adType); if (!selectedAd) { log('didnt find eligible video ad in IG', {interestGroup, browserSignals}); return {bid: '0.0'}; } + // Check if any deals are eligible. + const dealId = selectDealId(selectedAd, auctionSignals); return { ad: { ...selectedAd.metadata, seller: browserSignals.seller, topLevelSeller: browserSignals.topLevelSeller, }, - bid: calculateBidAmount(trustedBiddingSignals), + bid: calculateBidAmount(trustedBiddingSignals, dealId), bidCurrency: 'USD', allowComponentAuction: true, render: selectedAd.renderURL, + // Specify selected deal ID for reporting. + selectedBuyerAndSellerReportingId: dealId, /* TODO: Use-case: Ad cost reporting adCost: optionalAdCost, @@ -180,6 +233,7 @@ function getBidForDisplayAd({ trustedBiddingSignals, browserSignals, }) { + // Select an ad meeting the auction requirements. const [selectedAd] = interestGroup.ads.filter( (ad) => 'DISPLAY' === ad.metadata.adType, ); @@ -187,13 +241,15 @@ function getBidForDisplayAd({ log("can't select display ad, no matching ad type found", {interestGroup}); return {bid: '0.0'}; } + // Check if any deals are eligible. + const dealId = selectDealId(selectedAd, auctionSignals); return { ad: { ...selectedAd.metadata, seller: browserSignals.seller, topLevelSeller: browserSignals.topLevelSeller, }, - bid: calculateBidAmount(trustedBiddingSignals), + bid: calculateBidAmount(trustedBiddingSignals, dealId), bidCurrency: 'USD', allowComponentAuction: true, render: { @@ -203,7 +259,7 @@ function getBidForDisplayAd({ height: selectedAd.metadata.adSizes[0].height, }, // Specify selected deal ID for reporting. - selectedBuyerAndSellerReportingId: selectDealId(selectedAd, auctionSignals), + selectedBuyerAndSellerReportingId: dealId, /* TODO: Use-case: Ad cost reporting adCost: optionalAdCost, @@ -243,6 +299,7 @@ function generateBid( }; logContextForDemo('generateBid()', biddingContext); if (!isCurrentCampaignActive(biddingContext)) { + log('not bidding as campaign is inactive', biddingContext); return; } const bid = diff --git a/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js b/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js index 2c30188f..e5c08aab 100644 --- a/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js +++ b/services/ad-tech/src/public/js/ssp/default/auction-decision-logic.js @@ -63,8 +63,23 @@ function logContextForDemo(message, context) { } } -/** Returns a rejection score response if scored creative is blocked. */ -function getRejectScoreIfCreativeBlocked(scoringContext) { +/** Checks whether the bid is below the winning contextual bid. */ +function isBidBelowAuctionFloor({ + // UNUSED adMetadata, + bid, + auctionConfig, + // UNUSED trustedScoringSignals, + // UNUSED browserSignals, +}) { + const {winningContextualBid} = auctionConfig.sellerSignals; + if (!winningContextualBid) { + return false; + } + return bid < Number(winningContextualBid.bid); +} + +/** Checks real-time signals to see whether the ad creative is blocked. */ +function isCreativeBlocked(scoringContext) { const { // UNUSED adMetadata, // UNUSED bid, @@ -90,38 +105,26 @@ function getRejectScoreIfCreativeBlocked(scoringContext) { dataVersion: browserSignals.dataVersion, scoringContext, }); - return { - desirability: 0, - allowComponentAuction: true, - rejectReason: 'blocked-by-publisher', - }; + return true; } } + return false; } -/** Returns a rejection score if the deal in bid is not eligible. */ -function getRejectScoreIfDealIneligible(scoringContext) { - const {selectedBuyerAndSellerReportingId} = scoringContext.browserSignals; - const {availableDeals, strictRejectForDeals} = - scoringContext.auctionConfig.auctionSignals; - if (availableDeals && availableDeals.length) { - if (!availableDeals.includes(selectedBuyerAndSellerReportingId)) { - if (strictRejectForDeals) { - // For strict rejections on deals, assign score of 0. - return { - desirability: 0, - allowComponentAuction: true, - rejectReason: 'blocked-by-publisher', - }; - } else { - // For lax rejections on deals, execute first price auction. - return { - desirability: scoringContext.bid, - allowComponentAuction: true, - }; - } - } +/** Checks whether the bid includes a valid and eligible deal ID. */ +function doesBidHaveEligibleDeal({ + // UNUSED adMetadata, + // UNUSED bid, + auctionConfig, + // UNUSED trustedScoringSignals, + browserSignals, +}) { + const {availableDeals} = auctionConfig.auctionSignals; + if (!availableDeals || !availableDeals.length) { + return false; // No deals available. } + const {selectedBuyerAndSellerReportingId} = browserSignals; + return availableDeals.includes(selectedBuyerAndSellerReportingId); } // ******************************************************** @@ -142,24 +145,41 @@ function scoreAd( browserSignals, }; logContextForDemo('scoreAd()', scoringContext); - // Check if ad creative is blocked. - const creativeBlockedRejectScore = - getRejectScoreIfCreativeBlocked(scoringContext); - if (creativeBlockedRejectScore) { - return creativeBlockedRejectScore; - } - // Check if DSP responded with the correct deal. - const dealIneligibleRejectScore = - getRejectScoreIfDealIneligible(scoringContext); - if (dealIneligibleRejectScore) { - return dealIneligibleRejectScore; - } - // Finally execute a first-price auction. - return { + // Initialize ad score defaulting to a first-price auction. + const score = { desirability: bid, allowComponentAuction: true, - // incomingBidInSellerCurrency: optional }; + // Check if ad creative is blocked. + if (isCreativeBlocked(scoringContext)) { + score.desirability = 0; + score.rejectReason = 'disapproved-by-exchange'; + log('rejecting bid with blocked creative', scoringContext); + return score; + } + // Check if DSP responded with an eligible deal ID. + const bidHasEligibleDeal = doesBidHaveEligibleDeal(scoringContext); + const {strictRejectForDeals} = auctionConfig.auctionSignals; + if (strictRejectForDeals && !bidHasEligibleDeal) { + // Only accepting bids with eligible bids. + score.desirability = 0; + score.rejectReason = 'invalid-bid'; + log('rejecting bid with ineligible deal', scoringContext); + return score; + } else if (bidHasEligibleDeal) { + // Boost desirability score by 10 points for bids with eligible deals. + score.desirability = bid + 10.0; + log('boosting bid with eligible deal', scoringContext); + return score; + } + // Check if bid is below auction floor. + if (isBidBelowAuctionFloor(scoringContext)) { + score.desirability = 0; + score.rejectReason = 'bid-below-auction-floor'; + return score; + } + // In all other cases, default to a first-price auction. + return score; } function reportResult(auctionConfig, browserSignals) { diff --git a/services/ad-tech/src/routes/dsp/bidding-signals-router.ts b/services/ad-tech/src/routes/dsp/bidding-signals-router.ts index 21362174..b9a62797 100644 --- a/services/ad-tech/src/routes/dsp/bidding-signals-router.ts +++ b/services/ad-tech/src/routes/dsp/bidding-signals-router.ts @@ -11,8 +11,14 @@ limitations under the License. */ -import express, {Request, Response} from 'express'; -import {HOSTNAME} from '../../lib/constants.js'; +import express, {query, Request, Response} from 'express'; +import { + BIDDING_SIGNALS_DEALS, + BIDDING_SIGNALS_DEFAULT, + HOSTNAME, + SHOP_HOST, + TRAVEL_HOST, +} from '../../lib/constants.js'; import {KeyValueStore} from '../../controllers/key-value-store.js'; /** @@ -28,14 +34,10 @@ export const BiddingSignalsRouter = express.Router(); // ************************************************************************ // BYOS implementation of Key - Value store // ************************************************************************ -const trustedBiddingSignalStore = new KeyValueStore( - /* defaultValues= */ [ - ['isActive', 'true'], - ['minBid', '3.5'], - ['maxBid', '4.5'], - ['multiplier', '1.1'], - ], -); +const trustedBiddingSignalStore = new KeyValueStore([ + ...BIDDING_SIGNALS_DEFAULT, + ...BIDDING_SIGNALS_DEALS, +]); // ************************************************************************ // HTTP handlers @@ -49,24 +51,31 @@ BiddingSignalsRouter.get( const signalsFromKeyValueStore = trustedBiddingSignalStore.getMultiple( queryKeys!, ); - console.log('KV BYOS', {publisher, queryKeys}); - res.setHeader('X-Allow-FLEDGE', 'true'); - res.setHeader('X-fledge-bidding-signals-format-version', '2'); + const interestGroupNames = req.query + .interestGroupNames!.toString() + .split(','); + const perInterestGroupData: {[key: string]: any} = {}; + for (const name of interestGroupNames) { + perInterestGroupData[name] = { + 'priorityVector': { + 'signal1': 100, + 'signal2': 200, + }, + 'updateIfOlderThanMs': 1, + }; + } const biddingSignals = { keys: {...signalsFromKeyValueStore}, - perInterestGroupData: { - 'name1': { - 'priorityVector': { - 'signal1': 100, - 'signal2': 200, - }, - }, - }, + perInterestGroupData, }; - console.log('Returning trusted bidding signals: ', { - url: `${req.baseUrl}${req.path}`, + console.log('Returning bidding signals: ', { + url: req.originalUrl, + publisher, + queryKeys, biddingSignals, }); + res.setHeader('X-Allow-FLEDGE', 'true'); + res.setHeader('X-fledge-bidding-signals-format-version', '2'); res.json(biddingSignals); }, ); diff --git a/services/ad-tech/src/routes/dsp/buyer-contextual-bidder-router.ts b/services/ad-tech/src/routes/dsp/buyer-contextual-bidder-router.ts index fa3a4021..44d05542 100644 --- a/services/ad-tech/src/routes/dsp/buyer-contextual-bidder-router.ts +++ b/services/ad-tech/src/routes/dsp/buyer-contextual-bidder-router.ts @@ -17,8 +17,8 @@ import { CURRENT_ORIGIN, EXTERNAL_PORT, HOSTNAME, - MAX_CONTEXTUAL_BID, - MIN_CONTEXTUAL_BID, + CONTEXTUAL_BID_MAX, + CONTEXTUAL_BID_MIN, } from '../../lib/constants.js'; import {AdType} from '../../lib/interest-group-helper.js'; import {ContextualBidResponse} from '../../lib/contextual-auction-helper.js'; @@ -36,8 +36,8 @@ export const BuyerContextualBidderRouter = express.Router(); // ************************************************************************ /** Returns a random bid price with 2 decimal digits. */ const getContextualBidPrice = (): string => { - const minBid = MIN_CONTEXTUAL_BID; - const maxBid = MAX_CONTEXTUAL_BID; + const minBid = CONTEXTUAL_BID_MIN; + const maxBid = CONTEXTUAL_BID_MAX; const bid = (Math.random() * (maxBid - minBid) + minBid).toFixed(2); return bid; }; diff --git a/services/ad-tech/src/routes/dsp/buyer-router.ts b/services/ad-tech/src/routes/dsp/buyer-router.ts index cbf6037a..d8c11f89 100644 --- a/services/ad-tech/src/routes/dsp/buyer-router.ts +++ b/services/ad-tech/src/routes/dsp/buyer-router.ts @@ -12,9 +12,12 @@ */ import express, {Request, Response} from 'express'; -import {CURRENT_ORIGIN, EXTERNAL_PORT, HOSTNAME} from '../../lib/constants.js'; +import {HOSTNAME} from '../../lib/constants.js'; import {getTemplateVariables} from '../../lib/template-utils.js'; -import {getAdsForRequest} from '../../lib/interest-group-helper.js'; +import { + getInterestGroup, + TargetingContext, +} from '../../lib/interest-group-helper.js'; /** * This is the main ad buyer router and is responsible for a variety of @@ -39,61 +42,21 @@ BuyerRouter.get( }, ); -/** Returns the interest group to join on advertiser page. */ +/** Returns the interest group to join on an advertiser page. */ BuyerRouter.get('/interest-group.json', async (req: Request, res: Response) => { - // Set advertiser from query or fallback to current host. - const advertiser = req.query.advertiser?.toString() || HOSTNAME!; - const itemId = req.query.itemId?.toString() || ''; - // Set usecase if included in query, else 'default'. - const usecase = ((usecase) => { - if (!usecase) { - return 'default'; - } - return Array.isArray(usecase) ? usecase[0] : usecase; - })(req.query.usecase); - // Add to keys if query includes tbsKey=. - const trustedBiddingSignalsKeys = ((keys) => { - const defaultKeys = ['isActive', 'minBid', 'maxBid', 'multiplier']; - if (!keys) { - return defaultKeys; - } else if (Array.isArray(keys)) { - return [...defaultKeys, ...keys]; - } else { - return [...defaultKeys, keys]; - } - })(req.query.tbsKey); - console.log('Returning IG JSON: ', req.query); - res.json({ - name: `${advertiser}-${usecase}`, - owner: CURRENT_ORIGIN, - biddingLogicURL: new URL( - `https://${HOSTNAME}:${EXTERNAL_PORT}/js/dsp/${usecase}/auction-bidding-logic.js`, - ).toString(), - trustedBiddingSignalsURL: new URL( - `https://${HOSTNAME}:${EXTERNAL_PORT}/dsp/realtime-signals/bidding-signal.json`, - ).toString(), - trustedBiddingSignalsKeys, - // Daily update is not implemented yet. - // updateURL: new URL( - // `https://${HOSTNAME}:${EXTERNAL_PORT}/dsp/daily-update-url`, - // ), - userBiddingSignals: { - 'user_bidding_signals': 'user_bidding_signals', - ...req.query, // Copy query from request URL. - }, - adSizes: { - 'medium-rectangle-default': {'width': '300px', 'height': '250px'}, - }, - sizeGroups: { - 'medium-rectangle': ['medium-rectangle-default'], - }, - ads: getAdsForRequest(advertiser, itemId), - }); + const targetingContext = assembleTargetingContext(req.query); + res.json(getInterestGroup(targetingContext)); }); -// TODO: Implement -// BuyerRouter.get('/daily-update-url', async (req: Request, res: Response) => { -// }) +/** Returns the updated interest group, usually daily, may be overridden. */ +BuyerRouter.get( + '/interest-group-update.json', + async (req: Request, res: Response) => { + const targetingContext = assembleTargetingContext(req.query); + targetingContext.isUpdateRequest = true; + res.json(getInterestGroup(targetingContext)); + }, +); /** Iframe document used as context to test Private Aggregation. */ BuyerRouter.get( @@ -108,3 +71,50 @@ BuyerRouter.get( }); }, ); + +const KNOWN_TARGETING_CONTEXT_KEYS = [ + 'advertiser', + 'usecase', + 'itemId', + 'biddingSignalKeys', +]; +/** Assembles the targeting context from query parameters. */ +const assembleTargetingContext = (query: any): TargetingContext => { + const {advertiser, usecase, itemId, biddingSignalKeys} = query; + const targetingContext: TargetingContext = { + advertiser: advertiser?.toString() || HOSTNAME!, // Default to current host + usecase: usecase?.toString() || 'default', + itemId: itemId?.toString() || '', + isUpdateRequest: false, + }; + targetingContext.biddingSignalKeys = []; + if (biddingSignalKeys) { + if (Array.isArray(biddingSignalKeys)) { + targetingContext.biddingSignalKeys.push( + ...biddingSignalKeys.map((key) => key.toString()), + ); + } else { + targetingContext.biddingSignalKeys.push( + biddingSignalKeys.toString().split(','), + ); + } + } + targetingContext.additionalContext = {}; + for (const [key, value] of Object.entries(query)) { + if (KNOWN_TARGETING_CONTEXT_KEYS.includes(key)) { + continue; + } + if (value) { + if (Array.isArray(value)) { + targetingContext.additionalContext[key.toString()] = value.map( + (value) => value.toString(), + ); + } else { + targetingContext.additionalContext[key.toString()] = [ + ...value.toString().split(','), + ]; + } + } + } + return targetingContext; +}; diff --git a/services/news/src/views/components/aside.ejs b/services/news/src/views/components/aside.ejs index 473e5fb2..782f6714 100644 --- a/services/news/src/views/components/aside.ejs +++ b/services/news/src/views/components/aside.ejs @@ -46,4 +46,15 @@ single-seller +

Display ads with deals

+ diff --git a/services/news/src/views/display-ad-with-deals-only.ejs b/services/news/src/views/display-ad-with-deals-multi-seller.ejs similarity index 74% rename from services/news/src/views/display-ad-with-deals-only.ejs rename to services/news/src/views/display-ad-with-deals-multi-seller.ejs index dac1e63f..522124eb 100644 --- a/services/news/src/views/display-ad-with-deals-only.ejs +++ b/services/news/src/views/display-ad-with-deals-multi-seller.ejs @@ -14,6 +14,17 @@ href="/img/spy.svg" /> diff --git a/services/news/src/views/display-ad-with-deals-single-seller.ejs b/services/news/src/views/display-ad-with-deals-single-seller.ejs new file mode 100644 index 00000000..2346d4d0 --- /dev/null +++ b/services/news/src/views/display-ad-with-deals-single-seller.ejs @@ -0,0 +1,70 @@ + + + + + + + <%= TITLE %> + + + + + + + + + + <%- include('components/header') %> +
+
+

<%= TEXT_LOREM %>

+ <%- include('components/display-ads') %> +

<%= TEXT_LOREM %>

+

<%= TEXT_LOREM %>

+

<%= TEXT_LOREM %>

+
+ <%- include('components/aside') %> +
+ <%- include('components/footer', {HOME_HOST, EXTERNAL_PORT}) %> + + + From 0341d8b48aa1f7c139a2bb2e3d5b7518ff1cfdb3 Mon Sep 17 00:00:00 2001 From: Sid Sahoo Date: Fri, 1 Nov 2024 20:23:48 +0000 Subject: [PATCH 09/12] Fix comments, accidental duplications from merges --- services/ad-tech/src/lib/constants.ts | 14 +++--- .../ad-tech/src/lib/interest-group-helper.ts | 2 +- .../js/dsp/default/auction-bidding-logic.js | 45 ------------------- 3 files changed, 6 insertions(+), 55 deletions(-) diff --git a/services/ad-tech/src/lib/constants.ts b/services/ad-tech/src/lib/constants.ts index 3623c826..0df7139d 100644 --- a/services/ad-tech/src/lib/constants.ts +++ b/services/ad-tech/src/lib/constants.ts @@ -88,23 +88,19 @@ export const CONTEXTUAL_BID_MIN = 0.5; // **************************************************************************** // NOTE: These bidding signals are stored in the BYOS Key-Value Server, where // values are stored as strings. -export const BIDDING_SIGNAL_KEY_IS_ACTIVE = 'isActive'; -export const BIDDING_SIGNAL_KEY_BID_MIN = 'minBid'; -export const BIDDING_SIGNAL_KEY_BID_MAX = 'maxBid'; -export const BIDDING_SIGNAL_KEY_BID_MULTIPLIER = 'multiplier'; /** The deafult bidding signals for the BYOS Key-Value Serice. */ export const BIDDING_SIGNALS_DEFAULT = [ // Whether the campaign is active. - [BIDDING_SIGNAL_KEY_IS_ACTIVE, 'true'], + ['isActive', 'true'], // Min bid CPM for Protected Audience auctions. - [BIDDING_SIGNAL_KEY_BID_MIN, '3.5'], + ['minBid', '3.5'], // Max bid CPM for Protected Audience auctions. - [BIDDING_SIGNAL_KEY_BID_MAX, '4.5'], + ['maxBid', '4.5'], // Default bid multiplier for Protected Audience auctions. - [BIDDING_SIGNAL_KEY_BID_MULTIPLIER, '1.1'], + ['multiplier', '1.1'], ]; +/** Deal specific bid multipliers for Protected Audience auctions. */ export const BIDDING_SIGNALS_DEALS = [ - // Deal specific bid multipliers for Protected Audience auctions. // Template for keys: `multiplier-${dealId}` ['multiplier-deal-1', '0.5'], // Deal ID: deal-1 ['multiplier-deal-2', '0.4'], // Deal ID: deal-2 diff --git a/services/ad-tech/src/lib/interest-group-helper.ts b/services/ad-tech/src/lib/interest-group-helper.ts index 8baf4f6d..6cd3cd04 100644 --- a/services/ad-tech/src/lib/interest-group-helper.ts +++ b/services/ad-tech/src/lib/interest-group-helper.ts @@ -77,7 +77,7 @@ export interface InterestGroup { sizeGroups?: {[key: string]: string[]}; } -/** Generalized interface of the interest targeting context. */ +/** Generalized interface of the interest group targeting context. */ export interface TargetingContext { // REQUIRED FIELDS /** Hostname of the advertiser. */ diff --git a/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js b/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js index dfaf8e71..dd332994 100644 --- a/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js +++ b/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js @@ -83,49 +83,6 @@ function isCurrentCampaignActive(biddingContext) { return true; } -/** Logs execution context for demonstrative purposes. */ -function logContextForDemo(message, context) { - const { - interestGroup, - auctionSignals, - perBuyerSignals, - // UNUSED trustedBiddingSignals, - // UNUSED browserSignals, - sellerSignals, - } = context; - AUCTION_ID = auctionSignals.auctionId; - if (interestGroup) { - CURR_HOST = interestGroup.owner.substring('https://'.length); - } else if (perBuyerSignals && perBuyerSignals.buyerHost) { - CURR_HOST = perBuyerSignals.buyerHost; - } else if (sellerSignals && sellerSignals.buyer) { - CURR_HOST = sellerSignals.buyer.substring('https://'.length); - } - log(message, context); -} - -/** Checks whether the current ad campaign is active. */ -function isCurrentCampaignActive(biddingContext) { - const { - // UNUSED interestGroup, - // UNUSED auctionSignals, - // UNUSED perBuyerSignals, - trustedBiddingSignals, - browserSignals, - } = biddingContext; - if ('true' !== trustedBiddingSignals['isActive']) { - // Don't place a bid if campaign is inactive. - log('not bidding since campaign is inactive', { - trustedBiddingSignals, - seller: browserSignals.seller, - topLevelSeller: browserSignals.topLevelSeller, - dataVersion: browserSignals.dataVersion, - }); - return false; - } - return true; -} - /** Calculates a bid price based on real-time signals. */ function calculateBidAmount(trustedBiddingSignals, dealId) { const minBid = Number(trustedBiddingSignals.minBid) || 0.5; @@ -163,8 +120,6 @@ function selectDealId(selectedAd, auctionSignals) { return selectableBuyerAndSellerReportingIds.filter((id) => auctionSignals.availableDeals.includes(id), ); - } else { - return selectableBuyerAndSellerReportingIds; } })(auctionSignals.availableDeals.split(',')); if (!eligibleDeals || !eligibleDeals.length) { From c3ddc233aa75e891282a3363fdfc2608d4d108d5 Mon Sep 17 00:00:00 2001 From: Sid Sahoo Date: Fri, 1 Nov 2024 21:04:55 +0000 Subject: [PATCH 10/12] Simplify top level seller decision logic --- .../top-level-auction-decision-logic.js | 51 +------------------ 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/services/ad-tech/src/public/js/ssp/default/top-level-auction-decision-logic.js b/services/ad-tech/src/public/js/ssp/default/top-level-auction-decision-logic.js index 6fd04021..e8ca94ca 100644 --- a/services/ad-tech/src/public/js/ssp/default/top-level-auction-decision-logic.js +++ b/services/ad-tech/src/public/js/ssp/default/top-level-auction-decision-logic.js @@ -67,42 +67,6 @@ function logContextForDemo(message, context) { } } -/** Returns a rejection score response if scored creative is blocked. */ -function getRejectScoreIfCreativeBlocked(scoringContext) { - const { - // UNUSED adMetadata, - // UNUSED bid, - // UNUSED auctionConfig, - trustedScoringSignals, - browserSignals, - } = scoringContext; - const {renderURL} = browserSignals; - if (trustedScoringSignals && trustedScoringSignals.renderURL[renderURL]) { - const parsedScoringSignals = JSON.parse( - trustedScoringSignals.renderURL[renderURL], - ); - if ( - parsedScoringSignals && - 'BLOCKED' === parsedScoringSignals.label.toUpperCase() - ) { - // Reject bid if creative is blocked. - log('rejecting bid blocked by publisher', { - parsedScoringSignals, - trustedScoringSignals, - renderURL, - buyer: browserSignals.interestGroupOwner, - dataVersion: browserSignals.dataVersion, - scoringContext, - }); - return { - desirability: 0, - allowComponentAuction: true, - rejectReason: 'blocked-by-publisher', - }; - } - } -} - // ******************************************************** // Top-level decision logic functions // ******************************************************** @@ -113,24 +77,12 @@ function scoreAd( trustedScoringSignals, browserSignals, ) { - const scoringContext = { + logContextForDemo('scoreAd()', { adMetadata, bid, auctionConfig, trustedScoringSignals, browserSignals, - }; - logContextForDemo('scoreAd()', scoringContext); - const creativeBlockedRejectScore = - getRejectScoreIfCreativeBlocked(scoringContext); - if (creativeBlockedRejectScore) { - return creativeBlockedRejectScore; - } - const {buyerAndSellerReportingId, selectedBuyerAndSellerReportingId} = - browserSignals; - log('found reporting IDs', { - buyerAndSellerReportingId, - selectedBuyerAndSellerReportingId, }); return { desirability: bid, @@ -145,7 +97,6 @@ function reportResult(auctionConfig, browserSignals) { const winningComponentAuctionConfig = auctionConfig.componentAuctions.find( (componentAuction) => winningComponentSeller === componentAuction.seller, ); - log('reporting result', {auctionConfig, browserSignals}); const reportingContext = { auctionId: AUCTION_ID, pageURL: winningComponentAuctionConfig.auctionSignals.pageURL, From a2431221038d839d1edbe1a05c96fb150221ab02 Mon Sep 17 00:00:00 2001 From: Sid Sahoo Date: Fri, 1 Nov 2024 21:28:44 +0000 Subject: [PATCH 11/12] Dont redirect auction win, add comments --- .../public/js/dsp/default/auction-bidding-logic.js | 12 +++++++----- .../ad-tech/src/routes/dsp/bidding-signals-router.ts | 2 ++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js b/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js index dd332994..9d02885a 100644 --- a/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js +++ b/services/ad-tech/src/public/js/dsp/default/auction-bidding-logic.js @@ -293,7 +293,6 @@ function reportWin( renderURL: browserSignals.renderURL, bid: browserSignals.bid, bidCurrency: browserSignals.bidCurrency, - redirect: browserSignals.seller, buyerReportingId: browserSignals.buyerReportingId, buyerAndSellerReportingId: browserSignals.buyerAndSellerReportingId, selectedBuyerAndSellerReportingId: @@ -302,13 +301,16 @@ function reportWin( for (const [key, value] of Object.entries(reportingContext)) { additionalQueryParams = additionalQueryParams.concat(`&${key}=${value}`); } + sendReportTo( + browserSignals.interestGroupOwner + + `/reporting?report=win&${additionalQueryParams}`, + ); + additionalQueryParams = additionalQueryParams.concat( + `&redirect=${browserSignals.seller}`, + ); registerAdBeacon({ 'impression': `${browserSignals.interestGroupOwner}/reporting?report=impression&${additionalQueryParams}`, 'reserved.top_navigation_start': `${browserSignals.interestGroupOwner}/reporting?report=top_navigation_start&${additionalQueryParams}`, 'reserved.top_navigation_commit': `${browserSignals.interestGroupOwner}/reporting?report=top_navigation_commit&${additionalQueryParams}`, }); - sendReportTo( - browserSignals.interestGroupOwner + - `/reporting?report=win&${additionalQueryParams}`, - ); } diff --git a/services/ad-tech/src/routes/dsp/bidding-signals-router.ts b/services/ad-tech/src/routes/dsp/bidding-signals-router.ts index b9a62797..8ba7a150 100644 --- a/services/ad-tech/src/routes/dsp/bidding-signals-router.ts +++ b/services/ad-tech/src/routes/dsp/bidding-signals-router.ts @@ -51,6 +51,7 @@ BiddingSignalsRouter.get( const signalsFromKeyValueStore = trustedBiddingSignalStore.getMultiple( queryKeys!, ); + // Return perInterestGroupData for requested interes groups. const interestGroupNames = req.query .interestGroupNames!.toString() .split(','); @@ -61,6 +62,7 @@ BiddingSignalsRouter.get( 'signal1': 100, 'signal2': 200, }, + // Force an interest group update. 'updateIfOlderThanMs': 1, }; } From d3ec512108a5cc0e9058f333745b5fca3f9717f7 Mon Sep 17 00:00:00 2001 From: Sid Sahoo Date: Fri, 1 Nov 2024 21:35:29 +0000 Subject: [PATCH 12/12] Add interest group interface comments --- services/ad-tech/src/lib/interest-group-helper.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/services/ad-tech/src/lib/interest-group-helper.ts b/services/ad-tech/src/lib/interest-group-helper.ts index 6cd3cd04..d2196b1f 100644 --- a/services/ad-tech/src/lib/interest-group-helper.ts +++ b/services/ad-tech/src/lib/interest-group-helper.ts @@ -33,17 +33,26 @@ export enum AdType { /** Interface for an interest group ad object. */ export interface InterestGroupAd { // REQUIRED FIELDS + /** Main creative URL. */ renderURL: string; + /** Custom ad metadata stored by ad-tech. */ metadata: { + /** Hostname of the advertiser. */ advertiser: string; + /** DSIPLAY or VIDEO */ adType: AdType; - adSizes?: {width: string; height: string}[]; + /** List of compatible ad sizes. */ + adSizes?: InterestGroupAdSize[]; }; // OPTIONAL FIELDS + /** Associated ad size group label. */ sizeGroup?: string; // [Optional] Reporting IDs + /** Selectable reporting ID accessible to both buyer and seller. */ selectableBuyerAndSellerReportingIds?: string[]; + /** Non-selectable reporting ID accessible to buyer only. */ buyerReportingId?: string; + /** Non-selectable reporting ID accessible to both buyer and seller. */ buyerAndSellerReportingId?: string; }