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.tsxAbout
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
- 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.
<Attribution
journeys={journeys}
model="last"
halfLife={7}
/>Examples
Switch the model, watch the winner change
<Attribution journeys={journeys} defaultModel="last" />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'>.
| Prop | Type | Default | Description |
|---|---|---|---|
| journeys | Journey[] | required | { id, touchpoints: [{ channel, at }], value? }. Raw paths — the models are computed here. |
| model | AttributionModel | — | 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. |
| defaultModel | AttributionModel | 'last' | — |
| onModelChange | (model: AttributionModel) => void | — | — |
| models | AttributionModel[] | ['first', 'last', 'linear', 'decay', 'position'] | Time decay uses a configurable half-life; position based is 40/20/40 across first, middle and last. |
| halfLife | number | 7 | Half-life in days for time decay. |
| valueFormat | (value: number) => string | (value) => value.toFixed(1) | — |
| emptyLabel | string | 'No journeys.' | — |
| label | string | 'Attribution' | — |
| footnote | ReactNode | — | — |
| is | not 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.
| Field | Type | Description |
|---|---|---|
| channel | string | — |
| at | Date | string | — |
Journey
| Field | Type | Description |
|---|---|---|
| id | string | — |
| touchpoints | Touchpoint[] | — |
| value? | number | Revenue or count. Defaults to 1 conversion. |
| Type | Definition | Description |
|---|---|---|
AttributionModel | 'first' | 'last' | 'linear' | 'decay' | 'position' | — |
Behaviour
| Topic | Detail | Description |
|---|---|---|
| not causal | stated plainly | Every 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 show | sensitivity | A channel whose share collapses when you change the rule was never really driving conversions. |