Attribution

Credit for a conversion split across touchpoints, with the model as a control rather than a buried setting — because the disagreement between models is the whole finding.

src/components/ui/attribution.tsx

About

From the component's own source, where it was written.

Credit for a conversion, split across the touchpoints that led to it.

**The model is a control, not a setting buried in a config.** Last-touch says paid search won; first-touch says the blog post did; linear says everyone helped. These are the *same journeys* — the disagreement is entirely in the rule, and any single number presented without its model is a claim masquerading as a measurement. Switching models in place is the point of the component: a channel whose share collapses when you change the rule was never really driving conversions.

**Five models, computed here from the raw journeys:** - *First touch* — all credit to the first interaction. - *Last touch* — all credit to the last. The default in most tools, and the most flattering to bottom-of-funnel spend. - *Linear* — split evenly. No assumption, which is its virtue and its flaw. - *Time decay* — later touches earn more, on a configurable half-life. - *Position based* — 40/20/40 across first, middle and last.

**None of them are causal.** Every model here divides observed credit among touchpoints that were present; none establishes that a touchpoint *caused* anything, and none can see the channels that never got a click. Incrementality testing answers that question; this one shows you how sensitive your answer is to an arbitrary rule, which is the honest thing a chart can do.

Install

npx astralyx-ui add attribution

Usage

import { Attribution } from '@/components/ui/attribution'

<Attribution journeys={journeys} defaultModel="last" />

Composer

Change the props and the source below follows.

Attribution

Attribution model
  • Direct$9175(100%)
  • Organic search$0(0%)
  • Email$0(0%)
  • Paid search$0(0%)
  • Social$0(0%)
  • Referral$0(0%)

40 journeys under last touch. Attribution divides observed credit; it does not establish cause.

Props
tsx
<Attribution
  journeys={journeys}
  model="last"
  halfLife={7}
/>

Examples

Switch the model, watch the winner change

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

Attribution

Extends Omit<ComponentProps<'div'>, 'onChange'>.

PropTypeDefaultDescription
journeysJourney[]required{ id, touchpoints: [{ channel, at }], value? }. Raw paths — the models are computed here.
modelAttributionModel—Last-touch says paid search won; first-touch says the blog did; linear says everyone helped. Same journeys — the disagreement is entirely in the rule, and a number without its model is a claim dressed as a measurement.
defaultModelAttributionModel'last'—
onModelChange(model: AttributionModel) => void——
modelsAttributionModel[]['first', 'last', 'linear', 'decay', 'position']Time decay uses a configurable half-life; position based is 40/20/40 across first, middle and last.
halfLifenumber7Half-life in days for time decay.
valueFormat(value: number) => string(value) => value.toFixed(1)—
emptyLabelstring'No journeys.'—
labelstring'Attribution'—
footnoteReactNode——
isnot a setting—Last-touch says paid search won; first-touch says the blog did; linear says everyone helped. Same journeys — the disagreement is entirely in the rule, and a number without its model is a claim dressed as a measurement.

Types

Touchpoint

Credit for a conversion, split across the touchpoints that led to it.

FieldTypeDescription
channelstring—
atDate | string—

Journey

FieldTypeDescription
idstring—
touchpointsTouchpoint[]—
value?numberRevenue or count. Defaults to 1 conversion.
TypeDefinitionDescription
AttributionModel
'first' | 'last' | 'linear' | 'decay' | 'position'
—

Behaviour

TopicDetailDescription
not causalstated plainlyEvery model divides credit among touchpoints that were present. None establishes cause, and none can see the channels that never got a click — that is incrementality testing.
what it does showsensitivityA channel whose share collapses when you change the rule was never really driving conversions.