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

[ACT-97] Support IAD SDA test in Permutive module #18

Closed
wants to merge 1 commit into from
Closed
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
22 changes: 20 additions & 2 deletions integrationExamples/gpt/permutiveRtdProvider_example.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
}
},
bids: [
{
bidder: 'ix',
params: {
siteId: '123456',
}
},
{
bidder: 'appnexus',
params: {
Expand Down Expand Up @@ -135,15 +141,27 @@
pbjs.que.push(function() {
pbjs.setConfig({
debug: true,
pageUrl: 'http://www.test.com/test.html',
realTimeData: {
auctionDelay: 80, // maximum time for RTD modules to respond
dataProviders: [
{
name: 'permutive',
waitForIt: true,
params: {
acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'],
acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx', 'ix'],
maxSegs: 500,
transformations: [
{
id: 'iabAudienceTaxonomy11',
config: {
iabIds: {
1000001: '777777',
1000002: '888888'
}
}
}
],
overwrites: {
rubicon: function (bid, data, acEnabled, utils, defaultFn) {
if (defaultFn){
Expand All @@ -160,7 +178,7 @@
}
});
pbjs.setBidderConfig({
bidders: ['appnexus', 'rubicon'],
bidders: ['appnexus', 'rubicon', 'ix'],
config: {
ortb2: {
site: {
Expand Down
70 changes: 55 additions & 15 deletions modules/permutiveRtdProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,12 @@ export function setBidderRtb (auctionDetails, customModuleConfig) {
const moduleConfig = getModuleConfig(customModuleConfig)
const acBidders = deepAccess(moduleConfig, 'params.acBidders')
const maxSegs = deepAccess(moduleConfig, 'params.maxSegs')
const transformationConfigs = deepAccess(moduleConfig, 'params.transformations') || []
const segmentData = getSegments(maxSegs)

acBidders.forEach(function (bidder) {
const currConfig = bidderConfig[bidder] || {}
const nextConfig = mergeOrtbConfig(currConfig, segmentData)
const nextConfig = mergeOrtbConfig(currConfig, segmentData.ac, transformationConfigs) // ORTB2 uses the `ac` segment IDs

config.setBidderConfig({
bidders: [bidder],
Expand All @@ -84,23 +85,34 @@ export function setBidderRtb (auctionDetails, customModuleConfig) {
}

/**
* Merges segments into existing bidder config
* Merges segment data into existing bidder config
* Segments are retrieved from the `ac` property of `segmentData`
* @param {Object} currConfig - Current bidder config
* @param {Object} segmentData - Segment data
* @param {Object[]} transformationConfigs - array of objects with `id` and `config` properties, used to determine
* the transformations on user data to include the ORTB2 object
* @param {string[]} segmentIDs - Permutive segment IDs
* @return {Object} Merged ortb2 object
*/
function mergeOrtbConfig (currConfig, segmentData) {
const segment = segmentData.ac.map(seg => {
return { id: seg }
})
function mergeOrtbConfig (currConfig, segmentIDs, transformationConfigs) {
const name = 'permutive.com'

const permutiveUserData = {
name,
segment: segmentIDs.map(segmentId => ({ id: segmentId })),
}

const transformedUserData = transformationConfigs
.filter(({ id }) => ortb2UserDataTransformations.hasOwnProperty(id))
.map(({ id, config }) => ortb2UserDataTransformations[id](permutiveUserData, config))

const ortbConfig = mergeDeep({}, currConfig)
const currSegments = deepAccess(ortbConfig, 'ortb2.user.data') || []
const userSegment = currSegments
const currentUserData = deepAccess(ortbConfig, 'ortb2.user.data') || []

const updatedUserData = currentUserData
.filter(el => el.name !== name)
.concat({ name, segment })
.concat(permutiveUserData, transformedUserData)

deepSetValue(ortbConfig, 'ortb2.user.data', userSegment)
deepSetValue(ortbConfig, 'ortb2.user.data', updatedUserData)

return ortbConfig
}
Expand Down Expand Up @@ -236,11 +248,11 @@ export function getSegments (maxSegs) {
ac: [..._pcrprs, ..._ppam, ...legacySegs],
rubicon: readSegments('_prubicons'),
appnexus: readSegments('_papns'),
gam: readSegments('_pdfps')
gam: readSegments('_pdfps'),
}

for (const type in segments) {
segments[type] = segments[type].slice(0, maxSegs)
for (const bidder in segments) {
segments[bidder] = segments[bidder].slice(0, maxSegs)
}

return segments
Expand All @@ -260,6 +272,34 @@ function readSegments (key) {
}
}

const unknownIabSegmentId = '_unknown_'

/**
* Functions to apply to ORT2B2 `user.data` objects.
* Each function should return an a new object containing a `name`, (optional) `ext` and `segment`
* properties. The result of the each transformation defined here will be appended to the array
* under `user.data` in the bid request.
*/
const ortb2UserDataTransformations = {
iabAudienceTaxonomy11: (userData, config) => ({
name: userData.name,
ext: { segtax: '4' },
segment: (userData.segment || [])
.map(segment => ({ id: iabSegmentId(segment.id, config.iabIds) }))
.filter(segment => segment.id !== unknownIabSegmentId)
})
}

/**
* Transform a Permutive segment ID into an IAB audience taxonomy ID.
* @param {string} permutiveSegmentId
* @param {Object} iabIds object of mappings between Permutive and IAB segment IDs (key: permutive ID, value: IAB ID)
* @return {string} IAB audience taxonomy ID associated with the Permutive segment ID
*/
function iabSegmentId(permutiveSegmentId, iabIds) {
return iabIds[permutiveSegmentId] || unknownIabSegmentId
}

/** @type {RtdSubmodule} */
export const permutiveSubmodule = {
name: MODULE_NAME,
Expand All @@ -272,7 +312,7 @@ export const permutiveSubmodule = {
onAuctionInitEvent: function (auctionDetails, customModuleConfig) {
makeSafe(function () {
// Route for bidders supporting ORTB2
setBidderRtb(auctionDetails, customModuleConfig)
setBidderRtb(auctionDetails, customModuleConfig, ortb2UserDataTransformations)
})
},
init: init
Expand Down
38 changes: 24 additions & 14 deletions modules/permutiveRtdProvider.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# Permutive Real-time Data Submodule

This submodule reads cohorts from Permutive and attaches them as targeting keys to bid requests. Using this module will deliver best targeting results, leveraging Permutive's real-time segmentation and modelling capabilities.

## Usage

Compile the Permutive RTD module into your Prebid build:

```
gulp build --modules=rtdModule,permutiveRtdProvider
```
Expand All @@ -29,25 +32,32 @@ pbjs.setConfig({
```

## Supported Bidders

The Permutive RTD module sets Audience Connector cohorts as bidder-specific `ortb2.user.data` first-party data, following the Prebid `ortb2` convention, for any bidder included in `acBidders`. The module also supports bidder-specific data locations per ad unit (custom parameters) for the below bidders:

| Bidder | ID | Custom Cohorts | Audience Connector |
| ----------- | ---------- | -------------------- | ------------------ |
| Xandr | `appnexus` | Yes | Yes |
| Magnite | `rubicon` | Yes | No |
| Ozone | `ozone` | No | Yes |
| Bidder | ID | Custom Cohorts | Audience Connector |
| ------- | ---------- | -------------- | ------------------ |
| Xandr | `appnexus` | Yes | Yes |
| Magnite | `rubicon` | Yes | No |
| Ozone | `ozone` | No | Yes |

Key-values details for custom parameters:
* **Custom Cohorts:** When enabling the respective Activation for a cohort in Permutive, this module will automatically attach that cohort ID to the bid request. There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in your Permutive dashboard. Permutive cohorts will be sent in the `permutive` key-value.

* **Audience Connector:** You'll need to define which bidders should receive Audience Connector cohorts. You need to include the `ID` of any bidder in the `acBidders` array. Audience Connector cohorts will be sent in the `p_standard` key-value.
- **Custom Cohorts:** When enabling the respective Activation for a cohort in Permutive, this module will automatically attach that cohort ID to the bid request. There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in your Permutive dashboard. Permutive cohorts will be sent in the `permutive` key-value.

- **Audience Connector:** You'll need to define which bidders should receive Audience Connector cohorts. You need to include the `ID` of any bidder in the `acBidders` array. Audience Connector cohorts will be sent in the `p_standard` key-value.

## Parameters
| Name | Type | Description | Default |
| ----------------- | -------------------- | ------------------ | ------------------ |
| name | String | This should always be `permutive` | - |
| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` |
| params | Object | | - |
| params.acBidders | String[] | An array of bidders which should receive AC cohorts. | `[]` |
| params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` |

| Name | Type | Description | Default |
| ---------------------- | -------- | --------------------------------------------------------------------------------------------- | ------- |
| name | String | This should always be `permutive` | - |
| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` |
| params | Object | | - |
| params.acBidders | String[] | An array of bidders which should receive AC cohorts. | `[]` |
| params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` |
| params.transformations | Object[] | An array of configurations for ORTB2 user data transformations |

### The `transformations` parameter

This array contains configurations for transformations we'll apply to the Permutive object in the ORTB2 `user.data` array. The results of these transformations will be appended to the `user.data` array that's attached to ORTB2 bid requests.
57 changes: 54 additions & 3 deletions test/spec/modules/permutiveRtdProvider_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,45 @@ describe('permutiveRtdProvider', function () {
}])
})
})
it('should include ortb2 user data transformation for IAB audience taxonomy', function() {
const moduleConfig = getConfig()
const bidderConfig = config.getBidderConfig()
const acBidders = moduleConfig.params.acBidders
const expectedTargetingData = transformedTargeting().ac.map(seg => {
return { id: seg }
})

Object.assign(
moduleConfig.params,
{
transformations: [{
id: 'iabAudienceTaxonomy11',
config: {
iabIds: {
1000001: '9000009',
1000002: '9000008'
}
}
}]
}
)

setBidderRtb({}, moduleConfig)

acBidders.forEach(bidder => {
expect(bidderConfig[bidder].ortb2.user.data).to.deep.include.members([
{
name: 'permutive.com',
segment: expectedTargetingData
},
{
name: 'permutive.com',
ext: { segtax: '4' },
segment: [{ id: '9000009' }, { id: '9000008' }]
}
])
})
})
it('should not overwrite ortb2 config', function () {
const moduleConfig = getConfig()
const bidderConfig = config.getBidderConfig()
Expand Down Expand Up @@ -78,7 +117,15 @@ describe('permutiveRtdProvider', function () {
config: sampleOrtbConfig
})

setBidderRtb({}, moduleConfig)
const transformedUserData = {
name: 'transformation',
ext: { test: true },
segment: [1, 2, 3]
}

setBidderRtb({}, moduleConfig, {
testTransformation: userData => transformedUserData
})

acBidders.forEach(bidder => {
expect(bidderConfig[bidder].ortb2.site.name).to.equal(sampleOrtbConfig.ortb2.site.name)
Expand Down Expand Up @@ -293,6 +340,10 @@ describe('permutiveRtdProvider', function () {
expect(isAcEnabled({ params: { acBidders: ['ozone'] } }, 'ozone')).to.equal(true)
expect(isAcEnabled({ params: { acBidders: ['kjdvb'] } }, 'ozone')).to.equal(false)
})
it('checks if AC is enabled for Index', function () {
expect(isAcEnabled({ params: { acBidders: ['ix'] } }, 'ix')).to.equal(true)
expect(isAcEnabled({ params: { acBidders: ['kjdvb'] } }, 'ix')).to.equal(false)
})
})
})

Expand All @@ -313,7 +364,7 @@ function getConfig () {
name: 'permutive',
waitForIt: true,
params: {
acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'],
acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx', 'ix'],
maxSegs: 500
}
}
Expand All @@ -326,7 +377,7 @@ function transformedTargeting () {
ac: [...data._pcrprs, ...data._ppam, ...data._psegs.filter(seg => seg >= 1000000)],
appnexus: data._papns,
rubicon: data._prubicons,
gam: data._pdfps
gam: data._pdfps,
}
}

Expand Down