Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: pbjs-ortb conversion library #8562

Closed
dgirardi opened this issue Jun 14, 2022 · 0 comments · Fixed by #8738
Closed

Proposal: pbjs-ortb conversion library #8562

dgirardi opened this issue Jun 14, 2022 · 0 comments · Fixed by #8738
Assignees

Comments

@dgirardi
Copy link
Collaborator

dgirardi commented Jun 14, 2022

Type of issue

Feature

Description

There is an increasing number of adapters that interface with ORTB backends. Currently, each needs to replicate logic to convert PBJS bid objects to ORTB and back again; a dialect of that logic is also implemented in the PBS adapter.

We should extract that into one canonical implementation, and make it trivial to add a new adapter if it talks to an ORTB backend.

Proposal

  1. Move the enrichmentFpdModule logic into core and extend it to populate any ortb2 field that is not specific to the impression or bidder (for example, device.ua). Currently this is left to each individual adapter, but they rarely should have a reason to differ in their implementation. This is true for "traditional" (non-ORTB) adapters as well; consolidating these items into first-party data should allow some simplification in that regard too.

  2. Implement utilities bids2ortb and ortb2bids such that an adapter may use them in this manner:

  buildRequests(bidRequests, bidderRequest) {
      return {         
           method: ...,
           url: ...,
           data: bids2ortb({bidderRequest})           
      }
  },
  interpretResponse(response, request) {
      return ortb2bids({response: response.body, request: request.data})
  }

these utilities should handle everything that the PBS adapter now understands, namely:

  • banner, video, and native mediatypes
  • coppa / gdpr / usp
  • price floors
  • multibid
  • schain
  • gpid / pbadslot
  • any others?

Adapter-specific customization

Current ortb bidders are surprisingly similar, but not identical. Adapters are free to modify the return value they get from bids2ortb / ortb2bids, but that pattern is not ideal for some cases.

Bid filtering

The Rubicon adapter wants to create a separate ortb request for each one in a set of particular bids:

const videoRequests = bidRequests.filter(bidRequest => bidType(bidRequest) === 'video').map(bidRequest => {
bidRequest.startTime = new Date().getTime();
const data = {
id: bidRequest.transactionId,

Proposed solution - accept an optional list of bid requests in bids2ortb:

  const videoRequests = bidRequests.filter(bidRequest => bidType(bidRequest) === 'video')
         .map(bidRequest => bids2ortb({bidderRequest, bidRequests: [bidRequest]})

Bid-level parameters

For example, from the ADF adapter:

const { mid, inv, mname } = bid.params;
const imp = {
id: id + 1,
tagid: mid,
bidfloor,
bidfloorcur,
ext: {
bidder: {
inv,
mname
}
}
};

Proposed solution - provide an imp-level override in bids2ortb:

bids2ortb({
   bidderRequest, 
   imp: function(bid2imp, bidRequest) {
        const { mid, inv, mname } = bidRequest.params;
        return mergeDeep(bid2Imp({bidRequest}), {           
           tagid: mid,
           ext: {
              bidder: {
                inv,
                mname                 
              }
           }
        });
   }
});

The same idea can be applied on the response side - for example ADF's video renderer:

if (!bid.renderer && mediaType === VIDEO && deepAccess(bid, 'mediaTypes.video.context') === 'outstream') {
result.renderer = Renderer.install({id: bid.bidId, url: OUTSTREAM_RENDERER_URL, adUnitCode: bid.adUnitCode});
result.renderer.setRender(renderer);
}

could look like:

ortb2bids({
    request,
    response,
    bid: function(imp2bid, imp, bidRequest) {
         const bid = imp2bid({imp});
         if (bidRequest.mediaType === VIDEO && deepAccess(bidRequest, 'mediaTypes.video.context') === 'outstream') {
           bid.renderer = Renderer.install({id: bid.bidId, url: OUTSTREAM_RENDERER_URL, adUnitCode: bid.adUnitCode});
           bid.renderer.setRender(renderer);              
         }
         return bid;
    }
})

Feature override

Because it makes practical sense to separate the ortb implementation of individual features (e.g. bidfloor should only be populated if the priceFloors module is installed), it would be relatively straightforward to allow adapters to override their translation. I have not found a great use case for this yet but I'll use the OpenXNew adapter as inspiration:

if (floor > 0) {
imp.bidfloor = floor;
imp.bidfloorcur = 'USD';
} else if (bid.params.customFloor) {
imp.bidfloor = bid.params.customFloor;
}

Suppose that logic was changed to say "if bid.params.customFloor is defined, use that instead of the 'real' floor". We could allow that with something like:

bids2ortb({
   bidderRequest,
   impOverride: {
      bidfloor: function(applyOriginal, imp, bidRequest) {
              if (bidRequest.params.customFloor) {
                   Object.assign(imp, {
                        bidfloor: bidRequest.params.customFloor,
                        bidfloorcur: 'USD'                        
                   })
              } else {
                applyOriginal(imp, bidRequest);
              }
       }
   }
})

which would mean "override the ortb translation logic for the bidfloor feature with this custom logic instead". The difference with simply overwriting the contents of imp like we did in the previous example is that:

  • the original logic would not run at all - which could conceivably help performance if the feature is complex enough;
  • related fields are grouped together under a single feature name, which allows adapters to treat them as a logical entity without worrying about changes in core. For example an adapter that is not interested in GPDR could disable it with:
    return bids2ortb({
      bidderRequest
      override: {
        gdpr: function(){}
      }
    })
    
    instead of learning what fields are currently set by core for gdpr and listing them like so:
    const request = bids2ortb({bidderRequest});
    delete request.regs?.ext?.gdpr;
    delete request.user?.ext?.consent;
    delete request.user?.ext?.ConsentedProvidersSettings?.consented_providers;
    return request;
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

Successfully merging a pull request may close this issue.

1 participant