WICG / turtledove

TURTLEDOVE
https://wicg.github.io/turtledove/
Other
521 stars 225 forks source link

Deal Pricing: Need to Know if Deal was Targeted By a Bid #682

Open thegreatfatzby opened 1 year ago

thegreatfatzby commented 1 year ago

Background Sellers and Buyers can make "deals" which will typically give the buyer access to valued inventory with a special price. The deal as a whole is not specific to an SSP or DSP, but rather defined and interoperable via the OpenRTB bid object (see 3.2.12 on page 24 here); however exchanges can have their own nuances in implementation, although I'm fairly sure the idea is the same and contains the idea of a bid floor.

In our system, given that a Deal is eligible on an auction and a buyer has a campaign that can target it, the campaign has the flexibility to choose to target or not target that deal, and that choice can have an impact on final pricing logic if there is a deal floor.

generateBid can return an ad object that is flexible in it's structure, so things like a deal can be included. The ad object is not passed to the sellers reporting function; in the browser signals it gets the highest bid and second highest bid (to accommodate standard 2nd price dynamics).

I understand the intention as of now is that "pricing finalization", so 2nd price reduction, fee calculations, etc, will happen in seller reporting.

Challenge The gist of it is that the application of the bid floor can't be done in isolation at Bid Time or at Score Time without access to the other bids; at Seller Report Time you can know the deal's floor given it's ID (can stash that in auctionSignals) but if you don't know whether a deal was targeted or not and what it was you can't find or apply the floor, and that is currently not in the browser signals passed at scoring time.

We have docs you can see here, key line for this particular issue is:

If a buyer targeting the deal submits the highest bid and the bid clears the deal's ask price, that buyer wins the auction, paying either the second-highest bid (plus one cent) or the ask price, whichever is higher

combined with the fact that the campaign can choose to target the deal or not.

Examples So I believe motivating examples would be Winning Campaign C Targets Eligible Deal, Wins, Need Apply Bid Floor.

  1. Bids are $4, $3, highest bid did target deal with 3.50 floor.
  2. In this case the price resolves to 3.51 due to floor being higher than 2nd bid.

Winning Campaign C Targets Eligible Deal, Wins, Don't Apply Bid Floor

  1. Bids are $4, $3.75, highest bid did target deal with 3.50 floor.
  2. In this case the price resolves to 3.75 due to second price being higher than floor.

Winning Campaign C Could Target Eligible Deal But Doesn't, Wins, Don't Apply Bid Floor

  1. Bids are $4, $3, highest bid could have but did not target deal with 3.50 floor.
  2. In this case the price resolves to 3.01 as the floor isn't applied since the deal wasn't targeted.

Proposal The privacy-naive-obvious thing would be to pass the full ad object, but I'm sure that would be too leaky (although if it's not, let's do that).

I know we are trying to strike a balance here between not baking certain concepts into the system to avoid coupling core things to higher level things, keep dependencies low and concepts light, etc. That said, given Deals are a lingua franca in OpenRTB I think it would be OK to recognize that via a deal property, perhaps the system could look for a deal_id in the ad object, or add that to the return of generateBid, and have that passed along. We could also make it an object modeled after the OpenRTB one, but I think this is directionally good enough to start talking about.

(One note that I need to ponder the private marketplace piece mentioned in our docs a bit but I'm thinking there's an issue to suss out there, and if so I can try to merge proposals. EDIT: not an issue b/c the full ad object is passed to the scoreAd function (right?)).

michaelkleber commented 1 year ago

Hey Isaac, I believe the buyerAndSellerReportingId was pretty much designed to support this kind of need. Check it out in the explainer or the discussion in #165 and see if it helps.

thegreatfatzby commented 1 year ago

Hey there, had some time to circle back here, I do think the specifics I called out above are covered if you put the deal ID in for one of those IDs, but I'd like to understand better.

IDs

Is it correct that effectively, only one of (buyerAndSellerReportingId to both the buyer and seller) OR (buyerReportingId goes to buyer, nothing to seller)? I ask because I believe there are 3 relevant IDs in most cases, 1 of them I thiiiiink can reliably be backed out from another, but pretty sure not the third. (The one that I think can be backed out is the Deal Line Item, which is how Deal Eligibility is determined, it's not 1-1 with a Deal but I think in a given auction it is; the one that cannot is the Campaign Line Item, which is used for buy side targeting, revenue type, win determination, etc.

EDIT: SEE NEXT COMMENT, THESE ARE STRINGS W/NO LIMITATIONS?

K-Anon

Also, if one of those IDs is provided for the ad, doesn't matter which so I'll go with buyerReportingId, is the following correct:

Deal Capping

Deals can also have frequency/recency caps on them. I'm super tired and need to sleep, I'll ponder more tomorrow, but I want to get the question on paper so sorry if I've missed something: is there any support for capping based on Deal ID? Normal creative capping is done in browserSignals.prevWins, I'm not sure how that would happen for Deals. I could see there being some information made available to a hook in the Seller Front End (or pre bidding in the case of on device) that would be able to filter a deal ID out based on data in shared storage, or browserSignals.deals.ID_X.prevWins, or something like that.

thegreatfatzby commented 1 year ago

Ah ha's! From here t seems both buyerAndSellerReportingId and buyerReportingId are strings, and I don't see any validations on it (length, bits, etc). So for my IDs question I believe the answer is we can concatenate...really anything...in there. Jah?

I think the second two questions still stand...and this generates another one...what to do if you have some IDs you want to go to the seller and not the buyer (the Deal Line), some to both (the Deal), and some to the buyer and not the seller (the Campaign Line, IO, Flight, and Billing Period? It might still be useful to be able to pass info for both buyerAndSellerReportingId and buyerReportingId, or have sellerReportId that is also a free form string.

Also it should be pointed out that while some (all?) of these object graphs require the association IDs due to not being 1-1, in many cases it is required anyway if the association mapping is mutable and it is critical to get the ID as the delivery/fullfillment engine saw it at the time of auction (i.e. joining in later is not an option if you can't tolerate mis-allocation of budget, let's say).

JensenPaul commented 8 months ago

seems both buyerAndSellerReportingId and buyerReportingId are strings, and I don't see any validations on it (length, bits, etc). So for my IDs question I believe the answer is we can concatenate...really anything...in there. Jah?

Yes, both buyerAndSellerReportingId and buyerReportingId are strings and you are free to put whatever you want in them, and it will be passed to the reporting scripts if it is jointly k-anonymous.

bartek-siudeja commented 8 months ago

We run into a similar multiple-ids issue (one absolutely needs to be in renderURL, the other maybe not, maybe just in buyerReportingId).

it's not impossible that the ads list for a single URL would contain the same renderUrl twice with different reporting IDs or metadata

I was wondering how can a browser know which ID is relevant based on bidding function return values. There is render dictionary in there, and it might need to include buyerReportingId. Otherwise renderURL does not identify "a candidate" in ads array. We solved this by stuffing full buyerReportingId into renderURL, at some cost to k-anonymity of the ad (no real cost in event reporting in reportWin). But without this stuffing, how would aggregated reportWin know about buyerReportingId.

As a side note, stuffing everything inside renderURL works, but makes creatives syncing with ADX so much harder, way more cases to approve.