Skip to content
Merged
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
44 changes: 37 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,23 +437,28 @@ The response has the shape:
type ContextualSegmentsResponse = {
classifications: {
categories: { id: string; name: string; score: number; taxonomy: string }[];
keywords: { keyword: string; prominence: number }[];
keywords: { keyword: string; prominence: number /* 1 = most prominent */ }[];
brandSafety: {
assessed: boolean;
categories: { name: string; riskLevel: "low" | "medium" | "high" | "floor" | "not_assessed" | "" }[];
};
sentiment:
| { scored: false }
| { scored: true; id: "positive" | "neutral" | "negative"; name: string; score: number };
emotions: {
id: "joy" | "trust" | "fear" | "surprise" | "sadness" | "disgust" | "anger" | "anticipation";
name: string;
score: number;
}[];
};
};
```

`classifications` groups results by classification method; the DCN populates only the methods it has enabled. `categories` are taxonomy classifications (each carrying its own `taxonomy`); `keywords` are free-form terms extracted from the page; `brandSafety` is the page's brand-safety assessment, described [below](#brand-safety); `sentiment` is the page's polarity, described [below](#sentiment). A category's `score` is a relevance score from 0 to 1, whereas a keyword's `prominence` is a per-page ordinal rank (1 = most prominent), not a comparable score.
`classifications` groups results by classification method; the DCN populates only the methods it has enabled. `categories` and `keywords` are empty when the DCN never classified the page or does not run that classifier.

Each call to `ctxSegments()` caches its response on the SDK instance (calling it again refreshes the cache). When `initContextual: true`, the SDK calls `ctxSegments()` for you during initialization, so the cache is populated automatically.
> **Note:** The requested URL must already have been classified by the DCN.
Comment thread
pierluc-codes marked this conversation as resolved.
Resolved

> **Note:** The requested URL must already have been classified by the DCN. If the DCN has no classification for the URL, the response will contain empty `categories` and `keywords` arrays, `brandSafety.assessed` will be `false`, and `sentiment.scored` will be `false`.
Each call to `ctxSegments()` caches its response on the SDK instance (calling it again refreshes the cache). When `initContextual: true`, the SDK calls `ctxSegments()` for you during initialization, so the cache is populated automatically.

#### Brand safety

Expand Down Expand Up @@ -487,7 +492,7 @@ if (sdk.ctxMaxRiskLevel() === "floor") {

#### Sentiment

`ctxSentiment()` reads the page's sentiment off the cached `ctxSegments()` response:
`ctxSentiment()` reads the page's sentiment (its polarity) off the cached `ctxSegments()` response:

```javascript
const sentiment = sdk.ctxSentiment();
Expand All @@ -500,16 +505,31 @@ if (sentiment.scored) {

The label in `id` is derived by the DCN from the score. Use it as served rather than bucketing `score` yourself, so your targeting follows the DCN's thresholds.

#### Emotions

`ctxEmotions()` reads the emotions the page evokes off the cached `ctxSegments()` response, as a list of `{ id, name, score }`:

```javascript
sdk.ctxEmotions();
// => [{ id: "joy", name: "Joy", score: 0.9 }, { id: "sadness", name: "Sadness", score: 0.7 }]
```

`id` is one of the eight [Plutchik](https://en.wikipedia.org/wiki/Robert_Plutchik#Plutchik's_wheel_of_emotions) emotions: `joy`, `trust`, `fear`, `surprise`, `sadness`, `disgust`, `anger` or `anticipation`. `score` is from 0.0 to 1.0, and the DCN serves the list highest score first. It is multi-label: a page can evoke several emotions at once, opposites included (a bittersweet story can carry both `joy` and `sadness`).

The list is empty when the page has no emotion: the DCN never classified it, does not run the emotion classifier, or nothing reached its cutoff. The DCN applies that cutoff before serving, so use the list as served rather than filtering on `score` yourself.

#### Contextual targeting key-values

`ctxTargetingKeyValues(taxonomyKeys?, options?)` reads the cached `ctxSegments()` response and builds a `Record<string, string[]>`, ready to pass to an ad server such as Google Ad Manager via `googletag.pubads().setTargeting()`. It emits category ids grouped by taxonomy, plus, by default, the page's keywords under `ctx_kw` and its brand-safety tier under `ctx_bs_max`:
`ctxTargetingKeyValues(taxonomyKeys?, options?)` reads the cached `ctxSegments()` response and builds a `Record<string, string[]>`, ready to pass to an ad server such as Google Ad Manager via `googletag.pubads().setTargeting()`. It emits category ids grouped by taxonomy, plus, by default, the page's keywords under `ctx_kw`, its brand-safety tier under `ctx_bs_max`, its sentiment label under `ctx_sentiment` and its emotions under `ctx_emotion`:

```javascript
sdk.ctxTargetingKeyValues();
// => {
// "iab_ct_3_1": ["53", "91", "58", "115", "90", "52"],
// "ctx_kw": ["advertising", "programmatic", "ad tech"],
// "ctx_bs_max": ["no_flags"]
// "ctx_bs_max": ["no_flags"],
// "ctx_sentiment": ["positive"],
// "ctx_emotion": ["joy", "anticipation"]
// }
```

Expand Down Expand Up @@ -568,6 +588,16 @@ sdk.ctxTargetingKeyValues(undefined, { sentimentKey: "" });
// => { "iab_ct_3_1": ["53", ...], "ctx_kw": [...], "ctx_bs_max": ["no_flags"] }
```

Every one of the page's [emotions](#emotions) is emitted by default under `ctx_emotion`, as a multi-value key (e.g. `ctx_emotion=[joy,sadness]`). Like keywords, the key is omitted when the page has no emotion, and scores are never emitted. Use `emotionKey` to rename the key, or pass an empty `emotionKey` to opt out:

```javascript
sdk.ctxTargetingKeyValues(undefined, { emotionKey: "feel" });
// => { "iab_ct_3_1": ["53", ...], "ctx_kw": [...], "ctx_bs_max": ["no_flags"], "feel": ["joy", "sadness"] }

sdk.ctxTargetingKeyValues(undefined, { emotionKey: "" });
// => { "iab_ct_3_1": ["53", ...], "ctx_kw": [...], "ctx_bs_max": ["no_flags"] }
```

A typical Google Ad Manager activation uses a `loadGAM()` helper:

```javascript
Expand Down
96 changes: 85 additions & 11 deletions demos/vanilla/nocookies/targeting/ctx_segments.html.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@
<a href="#classifications-sentiment"><code>sentiment</code>: polarity of the page</a>
<span class="toc-note">Positive, neutral or negative label, with the score it was derived from.</span>
</li>
<li>
<a href="#classifications-emotions"><code>emotions</code>: emotions the page evokes</a>
<span class="toc-note">Every Plutchik emotion that reached the DCN's cutoff, with its score.</span>
</li>
</ul>
</li>
<li>
Expand All @@ -146,8 +150,8 @@
returns for it: taxonomy categories (e.g. against the
<a href="https://iabtechlab.com/standards/content-taxonomy/">IAB Content Taxonomy</a>), free-form keywords,
and/or a brand-safety assessment (against the
<a href="https://www.brandsafetyinstitute.com/resources/frameworks/brand-safety-floor-suitability">Brand Safety Floor + Suitability Framework</a>), and/or the page's sentiment, depending on which
classifiers the DCN has enabled.
<a href="https://www.brandsafetyinstitute.com/resources/frameworks/brand-safety-floor-suitability">Brand Safety Floor + Suitability Framework</a>), and/or the page's sentiment and emotions, depending
on which classifiers the DCN has enabled.
</p>
<pre><code>// Classify the URL of the current page (defaults to window.location.href):
optable.instance.ctxSegments();
Expand All @@ -161,11 +165,12 @@ optable.instance.ctxSegments("https://optable.co/");</code></pre>
keywords: [{ keyword, prominence }],
brandSafety: { assessed, categories: [{ name, riskLevel }] },
sentiment: { scored, id, name, score },
emotions: [{ id, name, score }],
},
}</code></pre>
<p>
The <code>classifications</code> object groups results by classification method, and the DCN includes only
the methods it has enabled. Four methods exist today:
the methods it has enabled. Five methods exist today:
</p>
<h6 id="classifications-categories"><code>categories</code>: taxonomy category ids scored for the page</h6>
<table class="u-full-width">
Expand All @@ -182,6 +187,7 @@ optable.instance.ctxSegments("https://optable.co/");</code></pre>
</tr>
</tbody>
</table>
<p>Empty when the DCN never classified the page or does not run a taxonomy classifier.</p>
<h6 id="classifications-keywords"><code>keywords</code>: free-form terms extracted from the page</h6>
<table class="u-full-width">
<thead>
Expand All @@ -198,6 +204,7 @@ optable.instance.ctxSegments("https://optable.co/");</code></pre>
</tr>
</tbody>
</table>
<p>Empty when the DCN never classified the page or does not extract keywords.</p>
<h6 id="classifications-brand-safety"><code>brandSafety</code>: brand-safety assessment of the page</h6>
<p>
The categories and the risk tiers follow the
Expand Down Expand Up @@ -311,6 +318,36 @@ optable.instance.ctxSegments("https://optable.co/");</code></pre>
<pre><code>var sentiment = optable.instance.ctxSentiment();
if (sentiment.scored && sentiment.id === "negative") {
// Skip campaigns that should not run next to negative content.
}</code></pre>
<h6 id="classifications-emotions"><code>emotions</code>: emotions the page evokes</h6>
<table class="u-full-width">
<thead>
<tr><th>Field</th><th>Description</th></tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>
Stable label, one of the eight
<a href="https://en.wikipedia.org/wiki/Robert_Plutchik#Plutchik's_wheel_of_emotions">Plutchik</a>
emotions: <code>joy</code>, <code>trust</code>, <code>fear</code>, <code>surprise</code>,
<code>sadness</code>, <code>disgust</code>, <code>anger</code> or <code>anticipation</code>.
</td>
</tr>
<tr><td><code>name</code></td><td>Human-readable label (e.g. <code>Joy</code>).</td></tr>
<tr><td><code>score</code></td><td>Strength of the emotion from 0.0 to 1.0.</td></tr>
</tbody>
</table>
<p>
The list is multi-label, and the DCN serves it highest score first: a page can evoke several emotions
at once, opposites included. It is empty when the DCN never classified the page, does not run the
emotion classifier, or nothing reached its cutoff. The DCN applies that cutoff before serving, so use the list as
served rather than filtering on the score yourself. <code>ctxEmotions()</code> reads it off the cached
response:
</p>
<pre><code>var emotions = optable.instance.ctxEmotions().map(function (e) { return e.id; });
if (emotions.indexOf("joy") !== -1) {
// Favor campaigns that fit uplifting content.
}</code></pre>
<p>
Alternatively, configure the SDK with <code>initContextual</code> set to a callback. The SDK will
Expand All @@ -334,10 +371,7 @@ if (sentiment.scored && sentiment.id === "negative") {
<div class="twelve column">
<p>
<strong>Note:</strong> the URL requested must have been classified by the DCN. For the demo DCN used by
this page, the URL <code>https://optable.co/</code> should have been classified, so you can try that. A
URL the DCN holds no classification for comes back with empty <code>categories</code> and
<code>keywords</code> arrays, <code>brandSafety.assessed</code> set to <code>false</code>, and
<code>sentiment.scored</code> set to <code>false</code>.
this page, the URL <code>https://optable.co/</code> should have been classified, so you can try that.
</p>
</div>
</div>
Expand Down Expand Up @@ -395,14 +429,16 @@ if (sentiment.scored && sentiment.id === "negative") {
<p>
By default the returned map has one key per taxonomy the DCN classified into (keyed by the raw taxonomy
value), the page's keywords under <code>ctx_kw</code>, its brand-safety tier under
<code>ctx_bs_max</code>, and its sentiment label under <code>ctx_sentiment</code>. For example,
<code>ctx_bs_max</code>, its sentiment label under <code>ctx_sentiment</code>, and its emotions under
<code>ctx_emotion</code>. For example,
<code>ctxTargetingKeyValues()</code> might return:
</p>
<pre><code>{
"iab_ct_3_1": ["53", "91", "58", "115", "90", "52"],
"ctx_kw": ["advertising", "programmatic", "ad tech"],
"ctx_bs_max": ["no_flags"],
"ctx_sentiment": ["positive"]
"ctx_sentiment": ["positive"],
"ctx_emotion": ["joy", "anticipation"]
}</code></pre>
<p>
If you want <code>loadGAM()</code> to run as soon as the contextual segments arrive — without making a
Expand Down Expand Up @@ -512,6 +548,17 @@ loadGAM(optable.instance.ctxTargetingKeyValues(undefined, { sentimentKey: "mood"

// Opt out of the sentiment key-value:
loadGAM(optable.instance.ctxTargetingKeyValues(undefined, { sentimentKey: "" }));</code></pre>
<p>
Every one of the page's <a href="#classifications-emotions">emotions</a> is emitted by default under
<code>ctx_emotion</code>, as a multi-value key (e.g. <code>ctx_emotion=[joy,sadness]</code>). Like keywords,
the key is omitted when the page has no emotion, and scores are never emitted. Use
<code>emotionKey</code> to rename the key, or pass an empty <code>emotionKey</code> to opt out:
</p>
<pre><code>// Rename the emotion key:
loadGAM(optable.instance.ctxTargetingKeyValues(undefined, { emotionKey: "feel" }));

// Opt out of the emotion key-value:
loadGAM(optable.instance.ctxTargetingKeyValues(undefined, { emotionKey: "" }));</code></pre>
<div class="twelve column code-result" id="kv-result">—</div>
</div>
</div>
Expand Down Expand Up @@ -555,8 +602,15 @@ loadGAM(optable.instance.ctxTargetingKeyValues(undefined, { sentimentKey: "" }))
// ctxSegments() has cached the response by now, so the helper reads it
// synchronously off the instance. Missing and unscored both read scored false.
var sentiment = optable.instance.ctxSentiment();

if (categories.length === 0 && keywords.length === 0 && !brandSafety.assessed && !sentiment.scored) {
var emotions = optable.instance.ctxEmotions();

if (
categories.length === 0 &&
keywords.length === 0 &&
!brandSafety.assessed &&
!sentiment.scored &&
emotions.length === 0
) {
return "<p><em>No contextual classifications were returned for this URL.</em></p>";
}

Expand Down Expand Up @@ -679,6 +733,26 @@ loadGAM(optable.instance.ctxTargetingKeyValues(undefined, { sentimentKey: "" }))
"</td></tr></tbody></table>";
}

if (emotions.length > 0) {
html +=
"<h6>Emotions</h6>" +
'<table class="u-full-width"><thead><tr><th>Label</th><th>Name</th><th>Score</th></tr></thead><tbody>' +
emotions
.map(function (e) {
return (
"<tr><td><code>" +
escapeHtml(e.id) +
"</code></td><td>" +
escapeHtml(e.name) +
"</td><td>" +
escapeHtml(typeof e.score === "number" ? e.score.toFixed(2) : "n/a") +
"</td></tr>"
);
})
.join("") +
"</tbody></table>";
}

return html;
}

Expand Down
Loading