Skip to content
Open
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
2 changes: 2 additions & 0 deletions dbt/dbt_project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,7 @@ models:
marketing_analytics:
staging:
+materialized: view
intermediate:
+materialized: view
marts:
+materialized: view
42 changes: 42 additions & 0 deletions dbt/models/intermediate/_intermediate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
version: 2

models:
- name: int_customer_lifetime_value
description: |
Customer-level aggregation of conversion data.
Grain: One row per user_id.
columns:
- name: user_id
description: Unique identifier for the customer
data_tests:
- not_null
- unique
- name: total_revenue
description: Total conversion value across all purchases
- name: total_orders
description: Number of distinct conversions
- name: first_purchase_date
description: Date of the customer's first purchase
- name: last_purchase_date
description: Date of the customer's most recent purchase

- name: int_campaign_funnel
description: |
Campaign-level funnel metrics aggregated across all dates.
Grain: One row per campaign_id.
columns:
- name: campaign_id
description: Unique identifier for the campaign
data_tests:
- not_null
- unique
- name: total_impressions
description: Total impressions across all dates
- name: total_clicks
description: Total clicks across all dates
- name: total_sessions
description: Total distinct sessions attributed to this campaign
- name: total_conversions
description: Total distinct conversions attributed to this campaign
- name: total_revenue
description: Total conversion revenue attributed to this campaign
50 changes: 50 additions & 0 deletions dbt/models/intermediate/int_campaign_funnel.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
with campaigns as (
select * from {{ ref('stg_campaigns_daily') }}
),

campaign_totals as (
select
campaign_id,
sum(impressions) as total_impressions,
sum(clicks) as total_clicks

from campaigns
group by campaign_id
),

session_counts as (
select
campaign_id,
count(distinct session_id) as total_sessions

from {{ ref('stg_sessions') }}
group by campaign_id
),

conversion_counts as (
select
attributed_campaign_id as campaign_id,
count(distinct conversion_id) as total_conversions,
sum(conversion_value) as total_revenue

from {{ ref('stg_conversions') }}
group by attributed_campaign_id
),

final as (
select
ct.campaign_id,
ct.total_impressions,
ct.total_clicks,
coalesce(sc.total_sessions, 0) as total_sessions,
coalesce(cc.total_conversions, 0) as total_conversions,
coalesce(cc.total_revenue, 0) as total_revenue

from campaign_totals ct
left join session_counts sc
on ct.campaign_id = sc.campaign_id
left join conversion_counts cc
on ct.campaign_id = cc.campaign_id
)

select * from final
17 changes: 17 additions & 0 deletions dbt/models/intermediate/int_customer_lifetime_value.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
with conversions as (
select * from {{ ref('stg_conversions') }}
),

customer_metrics as (
select
user_id,
sum(conversion_value) as total_revenue,
count(distinct conversion_id) as total_orders,
min(date) as first_purchase_date,
max(date) as last_purchase_date

from conversions
group by user_id
)

select * from customer_metrics
60 changes: 60 additions & 0 deletions dbt/models/marts/_marts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,63 @@ models:
- name: overall_cpa
description: Overall cost per acquisition (total spend / total conversions)

- name: customer_lifetime_value
description: |
Customer lifetime value with acquisition channel and tier segmentation.
Answers: "How much is each customer worth and where did they come from?"

Grain: One row per user_id
columns:
- name: user_id
description: Unique identifier for the customer
data_tests:
- not_null
- unique
- name: total_revenue
description: Total conversion value across all purchases
- name: total_orders
description: Number of distinct purchases
- name: first_purchase_date
description: Date of the customer's first purchase
- name: last_purchase_date
description: Date of the customer's most recent purchase
- name: acquisition_channel
description: Channel of the customer's earliest first-touch attribution touchpoint
- name: ltv_tier
description: "Customer segment: high ($500+), medium ($100-499), low (under $100)"

- name: campaign_funnel_analysis
description: |
Campaign funnel with stage-to-stage conversion rates and efficiency score.
Answers: "Where are we losing people in the funnel, per campaign?"

Grain: One row per campaign_id
columns:
- name: campaign_id
description: Unique identifier for the campaign
data_tests:
- not_null
- unique
- name: campaign_name
description: Name of the campaign
- name: channel
description: Marketing channel
- name: total_impressions
description: Total impressions across all dates
- name: total_clicks
description: Total clicks across all dates
- name: total_sessions
description: Total sessions attributed to this campaign
- name: total_conversions
description: Total conversions attributed to this campaign
- name: total_revenue
description: Total revenue from conversions
- name: impression_to_click_rate
description: Click-through rate (clicks / impressions)
- name: click_to_session_rate
description: Rate of clicks that became sessions
- name: session_to_conversion_rate
description: Rate of sessions that converted
- name: efficiency_score
description: Overall funnel throughput (conversions / impressions) for ranking campaigns

54 changes: 54 additions & 0 deletions dbt/models/marts/campaign_funnel_analysis.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
with funnel as (
select * from {{ ref('int_campaign_funnel') }}
),

campaign_details as (
select distinct
campaign_id,
campaign_name,
channel

from {{ ref('stg_campaigns_daily') }}
),

final as (
select
f.campaign_id,
cd.campaign_name,
cd.channel,

-- Funnel counts
f.total_impressions,
f.total_clicks,
f.total_sessions,
f.total_conversions,
f.total_revenue,

-- Stage-to-stage conversion rates
case
when f.total_impressions > 0 then (f.total_clicks::float / f.total_impressions::float)
else 0
end as impression_to_click_rate,

case
when f.total_clicks > 0 then (f.total_sessions::float / f.total_clicks::float)
else 0
end as click_to_session_rate,

case
when f.total_sessions > 0 then (f.total_conversions::float / f.total_sessions::float)
else 0
end as session_to_conversion_rate,

-- Overall efficiency score
case
when f.total_impressions > 0 then (f.total_conversions::float / f.total_impressions::float)
else 0
end as efficiency_score

from funnel f
left join campaign_details cd
on f.campaign_id = cd.campaign_id
)

select * from final
44 changes: 44 additions & 0 deletions dbt/models/marts/customer_lifetime_value.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
with customer_metrics as (
select * from {{ ref('int_customer_lifetime_value') }}
),

first_touch_ranked as (
select
user_id,
channel as acquisition_channel,
row_number() over (partition by user_id order by timestamp asc) as rn

from {{ ref('stg_attribution_touchpoints') }}
where touchpoint_position = 1
),

first_touch as (
select
user_id,
acquisition_channel

from first_touch_ranked
where rn = 1
),

final as (
select
cm.user_id,
cm.total_revenue,
cm.total_orders,
cm.first_purchase_date,
cm.last_purchase_date,
coalesce(ft.acquisition_channel, 'unknown') as acquisition_channel,

case
when cm.total_revenue >= 500 then 'high'
when cm.total_revenue >= 100 then 'medium'
else 'low'
end as ltv_tier

from customer_metrics cm
left join first_touch ft
on cm.user_id = ft.user_id
)

select * from final