Attribution engine — spec

Table of Contents

1. Purpose

The attribution engine is the classical multi-touch layer. Given a journey (a vector of touchpoints), each of six models returns a credit vector whose sum is 1.0. No model is privileged; the point of having six is that they disagree.

The engine sits under the attribution-audit v5 layer, which adds what none of the six models can supply on its own: provenance labels, credit-conservation across claimants, and an incrementality holdout. The six models compute credit; the audit layer computes whether credit was earned. The distinction matters: five of the six are purely rules — they will happily attribute credit to an entirely asserted (stuffed) touchpoint. The Shapley model is more principled in structure but our characteristic function is a linear stub (v(S) = |S|/n) because with $0.00 revenue we have no signal to fit a real conversion-probability function (Shapley 1953).

ORACLE: src/wal_sh/adtech/attribution/core.cljc (pure), browser.cljs (UTM parsing + sessionStorage persistence), server.clj (JVM corpus replay).

2. Requirements

E1
Every model is a pure function journey -> [assignment] where assignment has :touchpoint, :credit, :model.
E2
For any non-empty journey, (sum credits) ≈ 1.0 within floating-point epsilon.
E3
Empty journey returns empty vector for every model.
E4
time-decay is host-pure: caller supplies now (millis) so no System/currentTimeMillis or Date.now() is called from core.
E5
Half-life default is 7 days (default-half-life-ms), a literal ported verbatim from the JS module.
E6
Shapley over distinct channels; the characteristic function is a linear stub v(S) = |S|/n documented in the docstring. Per-channel Shapley then splits proportionally across touchpoints sharing that channel.
E7
UTM classification is pure over a parsed query-string map; the browser adapter passes document.referrer hostname when no UTM is present. Classification is :channel, :source, :medium, :campaign.
E8
Six models are dispatched by keyword in models map; compare-models runs all six against one journey. time-decay is folded in explicitly because it takes now.
E9
Credits are floating-point, not rationals; property tests use an epsilon-tolerant sum-check.

3. Contract signature

Pure (core.cljc):

(touchpoint channel source medium campaign [timestamp])   ; -> map
(last-click journey)                                       ; -> [{:touchpoint :credit :model}]
(first-click journey)                                      ; -> ...
(linear journey)                                           ; -> ...
(time-decay journey now [half-life-ms])                    ; -> ...
(u-shape journey)                                          ; -> ... (40/40/20 middle-split)
(data-driven journey)                                      ; -> Shapley over channels
(compare-models journey now [half-life-ms])                ; -> {model-name -> assignments}
(classify-from-utm utm [referrer-host])                    ; -> {:channel :source :medium :campaign}
(total-credit assignments)                                 ; -> sum, should ≈ 1.0

Browser surface:

window._attribution = {
  track:    (fn [] -> touchpoint)                 ; parse URL/referrer, append to journey
  models:   (fn [] -> {model -> assignments})     ; compare all six for current journey
  clear:    (fn [] -> nil)                        ; wipe sessionStorage journey
}

4. Model semantics

Model Weight distribution
last-click 100 % to final touchpoint
first-click 100 % to first touchpoint
linear 1/n to each
time-decay 2^(-Δt/half-life), normalised
u-shape 40 % first, 40 % last, 20 % split across middle
data-driven Shapley over distinct channels; v(S)\ S\ /n=

For n=1 u-shape returns 100 %; for n=2, 50/50. Middle-split is 0.2 / (n-2) for n≥3.

5. Related literature

  • Shapley's original paper introducing the value function that the data-driven model attempts to instantiate (Shapley 1953). A production DDA needs a fitted conversion-probability characteristic function; we have the structure but not the signal.
  • Vickrey on incentive-compatible pricing (Vickrey 1961) and Edelman et al. on GSP auctions (Edelman, Ostrovsky, and Schwarz 2007) motivate why last-click and first-click persist despite their obvious flaws: they are cheap, they are stable, and the incentives at the pricing layer don't reward better models.
  • Bashir et al. trace how a single retargeted-ad journey is fragmented across ad exchanges in practice — the multi-touch journey we model with six touchpoints is often 20+ in the wild (Bashir et al. 2016).
  • Papadopoulos et al. on cookie synchronization (Papadopoulos, Kourtellis, and Markatos 2019) documents why "journey" is itself a fiction: user identity across the touches is reconstructed by cross-domain cookie matches, not a stable identifier.
  • Cook et al.'s tracker-advertiser relationship inference (Cook, Nithyanand, and Shafiq 2020) shows the ecosystem topology that makes multi-touch attribution structurally impossible to verify from outside — the "journey" is opaque per definition.
  • Google's Attribution Reporting API (Google 2022) is the modern proposal to make server-side attribution work without cross-site cookies; our six-model engine is the ledger-side complement.

6. Cross-references

  • attribution-audit/spec.org — v5 layer that audits this engine's credit vectors for provenance + credit-conservation.
  • stuffing-detectors/spec.org — four detectors that flag when a claimant's credit vector is inconsistent with the observed signal timeline.
  • beacon/spec.org — touchpoints are shaped like beacon extras (:source, :medium, :campaign).
  • ORACLE:
    • src/wal_sh/adtech/attribution/core.cljc (pure — six models)
    • src/wal_sh/adtech/attribution/browser.cljs (UTM + sessionStorage)
    • src/wal_sh/adtech/attribution/server.clj (corpus replay)
  • [BROKEN LINK: No match for fuzzy expression: *2007–2013: Programmatic exchange era] on the rise and reign of last-click.

7. Open questions

  • Data-driven Shapley with a real characteristic function. The current stub v(S) = |S|/n is documented as such; running the engine over real revenue events would require a fitted model which we don't have data for and don't want to fake. A simulation-mode with synthesized revenue would let us test the structural correctness of the Shapley aggregation without lying about the underlying signal.
  • Journey inference. The engine assumes a caller-supplied journey. In production a journey is inferred from sessionStorage across visits, and cross-device attribution is the standing hard problem. We record only in-session touches, which sidesteps the hard problem by pretending it's not there.
Bashir, Muhammad Ahmad, Sajjad Arshad, William Robertson, and Christo Wilson. 2016. “Tracing Information Flows between Ad Exchanges Using Retargeted Ads.” In 25Th Usenix Security Symposium (Usenix Security 16), 481–96. https://www.usenix.org/conference/usenixsecurity16/technical-sessions/presentation/bashir.
Cook, John, Rishab Nithyanand, and Zubair Shafiq. 2020. “Inferring Tracker-Advertiser Relationships in the Online Advertising Ecosystem Using Header Bidding.” Proceedings on Privacy Enhancing Technologies (Popets) 2020 (1): 65–82. https://doi.org/10.2478/popets-2020-0005.
Edelman, Benjamin, Michael Ostrovsky, and Michael Schwarz. 2007. “Internet Advertising and the Generalized Second-Price Auction: Selling Billions of Dollars Worth of Keywords.” American Economic Review 97 (1): 242–59. https://doi.org/10.1257/aer.97.1.242.
Google. 2022. “Attribution Reporting Api.” https://developers.google.com/privacy-sandbox/relevance/attribution-reporting.
Papadopoulos, Panagiotis, Nicolas Kourtellis, and Evangelos P. Markatos. 2019. “Cookie Synchronization: Everything You Always Wanted to Know but Were Afraid to Ask.” In The World Wide Web Conference (Www ’19), 1432–42. https://doi.org/10.1145/3308558.3313542.
Shapley, Lloyd S. 1953. “A Value for N-Person Games.” Contributions to the Theory of Games II: 307–17.
Vickrey, William. 1961. “Counterspeculation, Auctions, and Competitive Sealed Tenders.” The Journal of Finance 16 (1): 8–37. https://doi.org/10.1111/j.1540-6261.1961.tb02789.x.