Skip to content

Commit 000411a

Browse files
committed
Permutive RTD module: support IAB Audience taxonomy
Updates the Permutive RTD module to facilitate for segmentation by the new IAB Audience taxonomy. To achieve this, this change introduces the concept of "transformations" on the ORT2B `user.data` object. There are two components to these transformations: a new `transformations` property on the Prebid config, to be set by the publisher, and logic in the module for the actual behaviour of the transformation. We plan to use the transformation logic in this PR, combined with configuration we'll share with publishers, to send IAB Audience taxonomy cohort IDs to bidders.
1 parent 15f7ec1 commit 000411a

File tree

4 files changed

+153
-34
lines changed

4 files changed

+153
-34
lines changed

integrationExamples/gpt/permutiveRtdProvider_example.html

+20-2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@
4545
}
4646
},
4747
bids: [
48+
{
49+
bidder: 'ix',
50+
params: {
51+
siteId: '123456',
52+
}
53+
},
4854
{
4955
bidder: 'appnexus',
5056
params: {
@@ -135,15 +141,27 @@
135141
pbjs.que.push(function() {
136142
pbjs.setConfig({
137143
debug: true,
144+
pageUrl: 'http://www.test.com/test.html',
138145
realTimeData: {
139146
auctionDelay: 80, // maximum time for RTD modules to respond
140147
dataProviders: [
141148
{
142149
name: 'permutive',
143150
waitForIt: true,
144151
params: {
145-
acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'],
152+
acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx', 'ix'],
146153
maxSegs: 500,
154+
transformations: [
155+
{
156+
id: 'iabAudienceTaxonomy11',
157+
config: {
158+
iabIds: {
159+
1000001: '777777',
160+
1000002: '888888'
161+
}
162+
}
163+
}
164+
],
147165
overwrites: {
148166
rubicon: function (bid, data, acEnabled, utils, defaultFn) {
149167
if (defaultFn){
@@ -160,7 +178,7 @@
160178
}
161179
});
162180
pbjs.setBidderConfig({
163-
bidders: ['appnexus', 'rubicon'],
181+
bidders: ['appnexus', 'rubicon', 'ix'],
164182
config: {
165183
ortb2: {
166184
site: {

modules/permutiveRtdProvider.js

+55-15
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,12 @@ export function setBidderRtb (auctionDetails, customModuleConfig) {
7070
const moduleConfig = getModuleConfig(customModuleConfig)
7171
const acBidders = deepAccess(moduleConfig, 'params.acBidders')
7272
const maxSegs = deepAccess(moduleConfig, 'params.maxSegs')
73+
const transformationConfigs = deepAccess(moduleConfig, 'params.transformations') || []
7374
const segmentData = getSegments(maxSegs)
7475

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

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

8687
/**
87-
* Merges segments into existing bidder config
88+
* Merges segment data into existing bidder config
89+
* Segments are retrieved from the `ac` property of `segmentData`
8890
* @param {Object} currConfig - Current bidder config
89-
* @param {Object} segmentData - Segment data
91+
* @param {Object[]} transformationConfigs - array of objects with `id` and `config` properties, used to determine
92+
* the transformations on user data to include the ORTB2 object
93+
* @param {string[]} segmentIDs - Permutive segment IDs
9094
* @return {Object} Merged ortb2 object
9195
*/
92-
function mergeOrtbConfig (currConfig, segmentData) {
93-
const segment = segmentData.ac.map(seg => {
94-
return { id: seg }
95-
})
96+
function mergeOrtbConfig (currConfig, segmentIDs, transformationConfigs) {
9697
const name = 'permutive.com'
98+
99+
const permutiveUserData = {
100+
name,
101+
segment: segmentIDs.map(segmentId => ({ id: segmentId })),
102+
}
103+
104+
const transformedUserData = transformationConfigs
105+
.filter(({ id }) => ortb2UserDataTransformations.hasOwnProperty(id))
106+
.map(({ id, config }) => ortb2UserDataTransformations[id](permutiveUserData, config))
107+
97108
const ortbConfig = mergeDeep({}, currConfig)
98-
const currSegments = deepAccess(ortbConfig, 'ortb2.user.data') || []
99-
const userSegment = currSegments
109+
const currentUserData = deepAccess(ortbConfig, 'ortb2.user.data') || []
110+
111+
const updatedUserData = currentUserData
100112
.filter(el => el.name !== name)
101-
.concat({ name, segment })
113+
.concat(permutiveUserData, transformedUserData)
102114

103-
deepSetValue(ortbConfig, 'ortb2.user.data', userSegment)
115+
deepSetValue(ortbConfig, 'ortb2.user.data', updatedUserData)
104116

105117
return ortbConfig
106118
}
@@ -236,11 +248,11 @@ export function getSegments (maxSegs) {
236248
ac: [..._pcrprs, ..._ppam, ...legacySegs],
237249
rubicon: readSegments('_prubicons'),
238250
appnexus: readSegments('_papns'),
239-
gam: readSegments('_pdfps')
251+
gam: readSegments('_pdfps'),
240252
}
241253

242-
for (const type in segments) {
243-
segments[type] = segments[type].slice(0, maxSegs)
254+
for (const bidder in segments) {
255+
segments[bidder] = segments[bidder].slice(0, maxSegs)
244256
}
245257

246258
return segments
@@ -260,6 +272,34 @@ function readSegments (key) {
260272
}
261273
}
262274

275+
const unknownIabSegmentId = '_unknown_'
276+
277+
/**
278+
* Functions to apply to ORT2B2 `user.data` objects.
279+
* Each function should return an a new object containing a `name`, (optional) `ext` and `segment`
280+
* properties. The result of the each transformation defined here will be appended to the array
281+
* under `user.data` in the bid request.
282+
*/
283+
const ortb2UserDataTransformations = {
284+
iabAudienceTaxonomy11: (userData, config) => ({
285+
name: userData.name,
286+
ext: { segtax: '4' },
287+
segment: (userData.segment || [])
288+
.map(segment => ({ id: iabSegmentId(segment.id, config.iabIds) }))
289+
.filter(segment => segment.id !== unknownIabSegmentId)
290+
})
291+
}
292+
293+
/**
294+
* Transform a Permutive segment ID into an IAB audience taxonomy ID.
295+
* @param {string} permutiveSegmentId
296+
* @param {Object} iabIds object of mappings between Permutive and IAB segment IDs (key: permutive ID, value: IAB ID)
297+
* @return {string} IAB audience taxonomy ID associated with the Permutive segment ID
298+
*/
299+
function iabSegmentId(permutiveSegmentId, iabIds) {
300+
return iabIds[permutiveSegmentId] || unknownIabSegmentId
301+
}
302+
263303
/** @type {RtdSubmodule} */
264304
export const permutiveSubmodule = {
265305
name: MODULE_NAME,
@@ -272,7 +312,7 @@ export const permutiveSubmodule = {
272312
onAuctionInitEvent: function (auctionDetails, customModuleConfig) {
273313
makeSafe(function () {
274314
// Route for bidders supporting ORTB2
275-
setBidderRtb(auctionDetails, customModuleConfig)
315+
setBidderRtb(auctionDetails, customModuleConfig, ortb2UserDataTransformations)
276316
})
277317
},
278318
init: init

modules/permutiveRtdProvider.md

+24-14
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
# Permutive Real-time Data Submodule
2+
23
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.
34

45
## Usage
6+
57
Compile the Permutive RTD module into your Prebid build:
8+
69
```
710
gulp build --modules=rtdModule,permutiveRtdProvider
811
```
@@ -29,25 +32,32 @@ pbjs.setConfig({
2932
```
3033

3134
## Supported Bidders
35+
3236
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:
3337

34-
| Bidder | ID | Custom Cohorts | Audience Connector |
35-
| ----------- | ---------- | -------------------- | ------------------ |
36-
| Xandr | `appnexus` | Yes | Yes |
37-
| Magnite | `rubicon` | Yes | No |
38-
| Ozone | `ozone` | No | Yes |
38+
| Bidder | ID | Custom Cohorts | Audience Connector |
39+
| ------- | ---------- | -------------- | ------------------ |
40+
| Xandr | `appnexus` | Yes | Yes |
41+
| Magnite | `rubicon` | Yes | No |
42+
| Ozone | `ozone` | No | Yes |
3943

4044
Key-values details for custom parameters:
41-
* **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.
4245

43-
* **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.
46+
- **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.
4447

48+
- **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.
4549

4650
## Parameters
47-
| Name | Type | Description | Default |
48-
| ----------------- | -------------------- | ------------------ | ------------------ |
49-
| name | String | This should always be `permutive` | - |
50-
| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` |
51-
| params | Object | | - |
52-
| params.acBidders | String[] | An array of bidders which should receive AC cohorts. | `[]` |
53-
| params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` |
51+
52+
| Name | Type | Description | Default |
53+
| ---------------------- | -------- | --------------------------------------------------------------------------------------------- | ------- |
54+
| name | String | This should always be `permutive` | - |
55+
| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` |
56+
| params | Object | | - |
57+
| params.acBidders | String[] | An array of bidders which should receive AC cohorts. | `[]` |
58+
| params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` |
59+
| params.transformations | Object[] | An array of configurations for ORTB2 user data transformations |
60+
61+
### The `transformations` parameter
62+
63+
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.

test/spec/modules/permutiveRtdProvider_spec.js

+54-3
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,45 @@ describe('permutiveRtdProvider', function () {
5151
}])
5252
})
5353
})
54+
it('should include ortb2 user data transformation for IAB audience taxonomy', function() {
55+
const moduleConfig = getConfig()
56+
const bidderConfig = config.getBidderConfig()
57+
const acBidders = moduleConfig.params.acBidders
58+
const expectedTargetingData = transformedTargeting().ac.map(seg => {
59+
return { id: seg }
60+
})
61+
62+
Object.assign(
63+
moduleConfig.params,
64+
{
65+
transformations: [{
66+
id: 'iabAudienceTaxonomy11',
67+
config: {
68+
iabIds: {
69+
1000001: '9000009',
70+
1000002: '9000008'
71+
}
72+
}
73+
}]
74+
}
75+
)
76+
77+
setBidderRtb({}, moduleConfig)
78+
79+
acBidders.forEach(bidder => {
80+
expect(bidderConfig[bidder].ortb2.user.data).to.deep.include.members([
81+
{
82+
name: 'permutive.com',
83+
segment: expectedTargetingData
84+
},
85+
{
86+
name: 'permutive.com',
87+
ext: { segtax: '4' },
88+
segment: [{ id: '9000009' }, { id: '9000008' }]
89+
}
90+
])
91+
})
92+
})
5493
it('should not overwrite ortb2 config', function () {
5594
const moduleConfig = getConfig()
5695
const bidderConfig = config.getBidderConfig()
@@ -78,7 +117,15 @@ describe('permutiveRtdProvider', function () {
78117
config: sampleOrtbConfig
79118
})
80119

81-
setBidderRtb({}, moduleConfig)
120+
const transformedUserData = {
121+
name: 'transformation',
122+
ext: { test: true },
123+
segment: [1, 2, 3]
124+
}
125+
126+
setBidderRtb({}, moduleConfig, {
127+
testTransformation: userData => transformedUserData
128+
})
82129

83130
acBidders.forEach(bidder => {
84131
expect(bidderConfig[bidder].ortb2.site.name).to.equal(sampleOrtbConfig.ortb2.site.name)
@@ -293,6 +340,10 @@ describe('permutiveRtdProvider', function () {
293340
expect(isAcEnabled({ params: { acBidders: ['ozone'] } }, 'ozone')).to.equal(true)
294341
expect(isAcEnabled({ params: { acBidders: ['kjdvb'] } }, 'ozone')).to.equal(false)
295342
})
343+
it('checks if AC is enabled for Index', function () {
344+
expect(isAcEnabled({ params: { acBidders: ['ix'] } }, 'ix')).to.equal(true)
345+
expect(isAcEnabled({ params: { acBidders: ['kjdvb'] } }, 'ix')).to.equal(false)
346+
})
296347
})
297348
})
298349

@@ -313,7 +364,7 @@ function getConfig () {
313364
name: 'permutive',
314365
waitForIt: true,
315366
params: {
316-
acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'],
367+
acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx', 'ix'],
317368
maxSegs: 500
318369
}
319370
}
@@ -326,7 +377,7 @@ function transformedTargeting () {
326377
ac: [...data._pcrprs, ...data._ppam, ...data._psegs.filter(seg => seg >= 1000000)],
327378
appnexus: data._papns,
328379
rubicon: data._prubicons,
329-
gam: data._pdfps
380+
gam: data._pdfps,
330381
}
331382
}
332383

0 commit comments

Comments
 (0)