Skip to content

Commit 231e32b

Browse files
authored
Merge pull request #276 from apasel422/simplify-conv
Remove single-value AttributionLogic enum and inline credit field
2 parents 9e6ddc8 + 0fe3e63 commit 231e32b

File tree

6 files changed

+58
-144
lines changed

6 files changed

+58
-144
lines changed

api.bs

Lines changed: 25 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -357,10 +357,10 @@ The histogram created by the [=conversion report=] is constructed as follows:
357357
or the [=privacy budget=] for the site is exhausted,
358358
a histogram consisting entirely of zeros (0) is constructed.
359359

360-
* If one or more matching impressions is found, the browser runs the attribution
361-
logic (default last-n-touch) to select the most recent impression. The provided conversion
360+
* If one or more matching impressions is found, the browser runs the last-n-touch
361+
attribution logic to select the relevant impression(s). The provided conversion
362362
value is added to a histogram at the bucket that was specified at the time of the
363-
attributed impression. All other buckets are set to zero.
363+
attributed impression(s). All other buckets are set to zero.
364364

365365
The browser updates the [=privacy budget store=] to reflect the reported conversion.
366366

@@ -720,16 +720,11 @@ contribute to the histogram, i.e., will be uniformly zero.
720720
};
721721
</xmp>
722722

723-
1. The choice of [=attribution logic=]
724-
that the browser will apply,
725-
plus any parameters that the logic needs.
723+
1. The [=attribution logic=] parameters.
726724

727725
<xmp highlight=js>
728726
const attributionDetails = {
729-
logic: "last-n-touch",
730-
logicOptions: {
731-
credit: [.25, .25, .5]
732-
}
727+
credit: [.25, .25, .5]
733728
value: 3,
734729
maxValue: 7,
735730
};
@@ -767,20 +762,11 @@ dictionary AttributionConversionOptions {
767762
sequence<USVString> impressionSites = [];
768763
sequence<USVString> impressionCallers = [];
769764

770-
AttributionLogic logic = "last-n-touch";
771-
AttributionLogicOptions logicOptions;
765+
sequence<double> credit;
772766
unsigned long value = 1;
773767
unsigned long maxValue = 1;
774768
};
775769

776-
enum AttributionLogic {
777-
"last-n-touch",
778-
};
779-
780-
dictionary AttributionLogicOptions {
781-
sequence<double> credit;
782-
};
783-
784770
dictionary AttributionConversionResult {
785771
required Uint8Array report;
786772
};
@@ -823,11 +809,8 @@ The arguments to <a method for=Attribution>measureConversion()</a> are as follow
823809
only [=impressions=] recorded by one of the listed sites
824810
are eligible to match this [=conversion=].
825811
</dd>
826-
<dt><dfn>logic</dfn></dt>
827-
<dd>
828-
A selection from <a enum>AttributionLogic</a> indicating the
829-
[=attribution logic=] to use.
830-
</dd>
812+
<dt><dfn>credit</dfn></dt>
813+
<dd>A [=list=] of numbers.</dd>
831814
<dt><dfn>value</dfn></dt>
832815
<dd>
833816
The [=conversion value=]. If an attribution is made and [[#dp|privacy]]
@@ -1558,8 +1541,7 @@ The <dfn method for=Attribution>measureConversion(|options|)</dfn> method steps
15581541
<dfn>Match Values</dfn>: A [=set=] of [=32-bit unsigned integers=].
15591542
<dfn>Impression Sites</dfn>: A [=set=] of [=sites=].
15601543
<dfn>Impression Callers</dfn>: A [=set=] of [=sites=].
1561-
<dfn>Logic</dfn>: An {{AttributionLogic}}.
1562-
<dfn>Logic Options</dfn>: An {{AttributionLogicOptions}}.
1544+
<dfn>Credit</dfn>: A [=list=] of numbers.
15631545
<dfn>Value</dfn>: A [=32-bit unsigned integer=].
15641546
<dfn>Max Value</dfn>: A [=32-bit unsigned integer=].
15651547
</pre>
@@ -1582,25 +1564,15 @@ To <dfn>validate {{AttributionConversionOptions}}</dfn> |options|:
15821564
or is greater than the <dfn ignore>maximum aggregation-service histogram size</dfn>,
15831565
if any, for |options|.{{AttributionConversionOptions/aggregationService}},
15841566
throw a {{RangeError}}.
1585-
1. Let |credit| be «1».
1586-
1. Switch on the value of |options|.{{AttributionConversionOptions/logic}}:
1587-
<dl class="switch">
1588-
: <a enum-value for=AttributionLogic>"last-n-touch"</a>
1589-
:: Perform the following steps:
1590-
1. If |options|.{{AttributionConversionOptions/value}} is 0,
1591-
throw a {{RangeError}}.
1592-
1. If |options|.{{AttributionConversionOptions/value}}
1593-
is greater than |options|.{{AttributionConversionOptions/maxValue}},
1594-
throw a {{RangeError}}.
1595-
1. If |options|.{{AttributionConversionOptions/logicOptions}}.{{AttributionLogicOptions/credit}} [=map/exists=]:
1596-
1. Set |credit| to |options|.{{AttributionConversionOptions/logicOptions}}.{{AttributionLogicOptions/credit}}.
1597-
1. If |credit| is not a [=list=] or [=list/is empty=], throw a {{RangeError}}.
1598-
1. If any of the [=list/items=] of |credit| are less than or equal to 0, throw a {{RangeError}}.
1599-
1. If the [=list/size=] of |credit| exceeds an [=implementation-defined=] maximum, throw a {{RangeError}}.
1600-
</dl>
1601-
1. Let |validatedLogicOptions| be a {{AttributionLogicOptions}} with the following fields:
1602-
: {{AttributionLogicOptions/credit}}
1603-
:: |credit|
1567+
1. If |options|.{{AttributionConversionOptions/value}} is 0,
1568+
throw a {{RangeError}}.
1569+
1. If |options|.{{AttributionConversionOptions/value}}
1570+
is greater than |options|.{{AttributionConversionOptions/maxValue}},
1571+
throw a {{RangeError}}.
1572+
1. Let |credit| be |options|.{{AttributionConversionOptions/credit}} if it [=map/exists=], «1» otherwise.
1573+
1. If |credit| [=list/is empty=], throw a {{RangeError}}.
1574+
1. If any of the [=list/items=] of |credit| are less than or equal to 0, throw a {{RangeError}}.
1575+
1. If the [=list/size=] of |credit| exceeds an [=implementation-defined=] maximum, throw a {{RangeError}}.
16041576
1. Let |lookback| be |options|.{{AttributionConversionOptions/lookbackDays}} [=days=]
16051577
if it [=map/exists=], the [=implementation-defined=] maximum otherwise.
16061578
1. Set |lookback| to the [=implementation-defined=] maximum
@@ -1640,10 +1612,8 @@ To <dfn>validate {{AttributionConversionOptions}}</dfn> |options|:
16401612
:: |impressionSites|
16411613
: [=validated conversion options/Impression Callers=]
16421614
:: |impressionCallers|
1643-
: [=validated conversion options/Logic=]
1644-
:: |options|.{{AttributionConversionOptions/logic}}
1645-
: [=validated conversion options/Logic Options=]
1646-
:: |validatedLogicOptions|
1615+
: [=validated conversion options/Credit=]
1616+
:: |credit|
16471617
: [=validated conversion options/Value=]
16481618
:: |options|.{{AttributionConversionOptions/value}}
16491619
: [=validated conversion options/Max Value=]
@@ -1654,14 +1624,7 @@ To <dfn>validate {{AttributionConversionOptions}}</dfn> |options|:
16541624

16551625
### Attribution Logic ### {#s-logic}
16561626

1657-
A site that measures conversions can specify <dfn>attribution logic</dfn>,
1658-
which determines how the [=conversion value=] is allocated to histogram buckets.
1659-
The <a method for=Attribution>measureConversion()</a> function
1660-
accepts a <a dict-member for=AttributionConversionOptions>logic</a> parameter
1661-
that specifies the [=attribution logic=].
1662-
1663-
Each attribution logic specifies a process for allocating values to histogram buckets,
1664-
after the [=common matching logic=] is applied and privacy budgeting occurs.
1627+
<dfn>Attribution logic</dfn> determines how the [=conversion value=] is allocated to histogram buckets.
16651628

16661629
<div algorithm>
16671630
To <dfn>do attribution and fill a histogram</dfn>, given
@@ -1710,19 +1673,10 @@ To <dfn>do attribution and fill a histogram</dfn>, given
17101673
[=create an all-zero histogram=] with
17111674
|options|' [=validated conversion options/histogram size=].
17121675

1713-
1. Let |histogram| be null.
1714-
1715-
1. Switch on |options|' [=validated conversion options/logic=]:
1716-
<dl class="switch">
1717-
: <a enum-value for=AttributionLogic>"last-n-touch"</a>
1718-
:: Set |histogram| to the result of [=fill a histogram with last-n-touch attribution=] with |matchedImpressions|,
1719-
|options|' [=validated conversion options/histogram size=],
1720-
|options|' [=validated conversion options/value=], and
1721-
|options|' [=validated conversion options/logic options=].{{AttributionLogicOptions/credit}}.
1722-
1723-
</dl>
1724-
1725-
1. [=Assert=]: |histogram| is not null.
1676+
1. Set |histogram| to the result of [=fill a histogram with last-n-touch attribution=] with |matchedImpressions|,
1677+
|options|' [=validated conversion options/histogram size=],
1678+
|options|' [=validated conversion options/value=], and
1679+
|options|' [=validated conversion options/credit=].
17261680

17271681
1. If |singleEpoch| is true:
17281682
1. Let |l1Norm| be the sum of the [=list/items=] in |histogram|.

impl/e2e-tests/measure-conversion-errors.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
"options": {
8484
"aggregationService": "https://agg-service.example",
8585
"histogramSize": 1,
86-
"logicOptions": { "credit": [] }
86+
"credit": []
8787
},
8888
"expected": "RangeError"
8989
},
@@ -94,7 +94,7 @@
9494
"options": {
9595
"aggregationService": "https://agg-service.example",
9696
"histogramSize": 1,
97-
"logicOptions": { "credit": [0] }
97+
"credit": [0]
9898
},
9999
"expected": "RangeError"
100100
},
@@ -106,7 +106,7 @@
106106
"options": {
107107
"aggregationService": "https://agg-service.example",
108108
"histogramSize": 1,
109-
"logicOptions": { "credit": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] }
109+
"credit": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
110110
},
111111
"expected": "RangeError"
112112
},

impl/index.html

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,6 @@ <h2>Measure Conversion</h2>
206206
<dd>
207207
<input id="matchValues" pattern="^\s*([0-9]+)(\s+[0-9]+)*\s*$" />
208208
</dd>
209-
<dt><label for="logic">Logic</label></dt>
210-
<dd>
211-
<select id="logic">
212-
<option>last-n-touch</option>
213-
</select>
214-
</dd>
215209
<dt><label for="credit">Credit</label></dt>
216210
<dd>
217211
<input

impl/src/backend.ts

Lines changed: 28 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type {
55
AttributionConversionResult,
66
AttributionImpressionOptions,
77
AttributionImpressionResult,
8-
AttributionLogic,
98
} from "./index";
109

1110
import * as index from "./index";
@@ -43,16 +42,11 @@ interface ValidatedConversionOptions {
4342
matchValues: Set<number>;
4443
impressionSites: Set<string>;
4544
impressionCallers: Set<string>;
46-
logic: AttributionLogic;
47-
logicOptions: ValidatedLogicOptions;
45+
credit: readonly number[];
4846
value: number;
4947
maxValue: number;
5048
}
5149

52-
interface ValidatedLogicOptions {
53-
credit: readonly number[];
54-
}
55-
5650
export function days(days: number): Temporal.Duration {
5751
// We use `hours: X` here instead of `days` because days are considered to be
5852
// "calendar" units, making them incapable of being used in calculations
@@ -216,8 +210,7 @@ export class Backend {
216210
impressionSites = [],
217211
impressionCallers = [],
218212
lookbackDays = this.#delegate.maxLifetimeDays,
219-
logic = index.DEFAULT_CONVERSION_LOGIC,
220-
logicOptions,
213+
credit = [1],
221214
maxValue = index.DEFAULT_CONVERSION_MAX_VALUE,
222215
matchValues = [],
223216
value = index.DEFAULT_CONVERSION_VALUE,
@@ -245,37 +238,26 @@ export class Backend {
245238
);
246239
}
247240

248-
let credit = [1];
249-
250-
switch (logic) {
251-
case "last-n-touch":
252-
if (value <= 0 || !Number.isInteger(value)) {
253-
throw new RangeError("value must be a positive integer");
254-
}
255-
if (maxValue <= 0 || !Number.isInteger(value)) {
256-
throw new RangeError("maxValue must be a positive integer");
257-
}
258-
if (value > maxValue) {
259-
throw new RangeError("value must be <= maxValue");
260-
}
261-
if (logicOptions?.credit) {
262-
credit = logicOptions.credit;
263-
const maxCreditSize = this.#delegate.maxCreditSize;
264-
if (credit.length === 0 || credit.length > maxCreditSize) {
265-
throw new RangeError(
266-
`credit size must be in the range [1, ${maxCreditSize}]`,
267-
);
268-
}
269-
for (const c of credit) {
270-
if (c <= 0 || !Number.isFinite(value)) {
271-
throw new RangeError("credit must be positive and finite");
272-
}
273-
}
274-
}
241+
if (value <= 0 || !Number.isInteger(value)) {
242+
throw new RangeError("value must be a positive integer");
243+
}
244+
if (maxValue <= 0 || !Number.isInteger(value)) {
245+
throw new RangeError("maxValue must be a positive integer");
246+
}
247+
if (value > maxValue) {
248+
throw new RangeError("value must be <= maxValue");
249+
}
275250

276-
break;
277-
default:
278-
throw new RangeError("unknown logic");
251+
const maxCreditSize = this.#delegate.maxCreditSize;
252+
if (credit.length === 0 || credit.length > maxCreditSize) {
253+
throw new RangeError(
254+
`credit size must be in the range [1, ${maxCreditSize}]`,
255+
);
256+
}
257+
for (const c of credit) {
258+
if (c <= 0 || !Number.isFinite(value)) {
259+
throw new RangeError("credit must be positive and finite");
260+
}
279261
}
280262

281263
if (lookbackDays <= 0 || !Number.isInteger(lookbackDays)) {
@@ -299,8 +281,7 @@ export class Backend {
299281
matchValues: matchValueSet,
300282
impressionSites: parseSites(impressionSites),
301283
impressionCallers: parseSites(impressionCallers),
302-
logic,
303-
logicOptions: { credit },
284+
credit,
304285
value,
305286
maxValue,
306287
};
@@ -465,17 +446,12 @@ export class Backend {
465446
return allZeroHistogram(options.histogramSize);
466447
}
467448

468-
let histogram;
469-
switch (options.logic) {
470-
case "last-n-touch":
471-
histogram = this.#fillHistogramWithLastNTouchAttribution(
472-
matchedImpressions,
473-
options.histogramSize,
474-
options.value,
475-
options.logicOptions.credit,
476-
);
477-
break;
478-
}
449+
let histogram = this.#fillHistogramWithLastNTouchAttribution(
450+
matchedImpressions,
451+
options.histogramSize,
452+
options.value,
453+
options.credit,
454+
);
479455

480456
if (singleEpoch) {
481457
const l1Norm = histogram.reduce((a, b) => a + b);

impl/src/index.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,7 @@ export interface AttributionImpressionOptions {
2424

2525
export type AttributionImpressionResult = object;
2626

27-
export type AttributionLogic = "last-n-touch";
28-
29-
export interface AttributionLogicOptions {
30-
credit?: number[] | undefined;
31-
}
32-
3327
export const DEFAULT_CONVERSION_EPSILON = 1.0;
34-
export const DEFAULT_CONVERSION_LOGIC = "last-n-touch";
3528
export const DEFAULT_CONVERSION_VALUE = 1;
3629
export const DEFAULT_CONVERSION_MAX_VALUE = 1;
3730

@@ -48,8 +41,7 @@ export interface AttributionConversionOptions {
4841
impressionSites?: string[] | undefined; // = []
4942
impressionCallers?: string[] | undefined; // = []
5043

51-
logic?: AttributionLogic | undefined; // = DEFAULT_CONVERSION_LOGIC
52-
logicOptions?: AttributionLogicOptions | undefined;
44+
credit?: number[] | undefined;
5345
value?: number | undefined; // = DEFAULT_CONVERSION_VALUE
5446
maxValue?: number | undefined; // = DEFAULT_CONVERSION_MAX_VALUE
5547
}

impl/src/simulator.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,7 @@ function updateBudgetAndEpochTables() {
319319
matchValues: spaceSeparated(matchValues).map((v) =>
320320
Number.parseInt(v, 10),
321321
),
322-
logicOptions: {
323-
credit: spaceSeparated(credit).map(Number.parseFloat),
324-
},
322+
credit: spaceSeparated(credit).map(Number.parseFloat),
325323
lookbackDays: numberOrUndefined(lookbackDays),
326324
maxValue: numberOrUndefined(maxValue),
327325
value: numberOrUndefined(value),

0 commit comments

Comments
 (0)