Merged PR 4628: Churn deals

# Description

New model for Churn report.
It contains all deals that are churned/cancelled according to HubSpot on a monthly basis, it includes revenue, bookings created (by time windows) and number of listings per deal

# Checklist

- [x] The edited models and dependants run properly with production data.
- [x] The edited models are sufficiently documented.
- [x] The edited models contain PK tests, and I've ran and passed them.
- [ ] I have checked for DRY opportunities with other models and docs.
- [ ] I've picked the right materialization for the affected models.

# Other

- [ ] Check if a full-refresh is required after this PR is merged.

Related work items: #28190
This commit is contained in:
Joaquin Ossa 2025-03-07 10:48:52 +00:00
commit cc9b4ade71
7 changed files with 227 additions and 0 deletions

View file

@ -13,6 +13,9 @@ with
and is_end_of_month = true
),
daily_deal_lifecycle as (select * from {{ ref("int_kpis__lifecycle_daily_deal") }}),
int_kpis__dimension_daily_accommodation as (
select * from {{ ref("int_kpis__dimension_daily_accommodation") }}
),
listings as (
select *
from {{ ref("int_kpis__agg_daily_listings") }}
@ -83,6 +86,9 @@ select
ikdd.main_deal_name,
ikdd.has_active_pms,
ikdd.active_pms_list,
coalesce(
dda.active_accommodations_per_deal_segmentation, 'UNSET'
) as active_accommodations_per_deal_segmentation,
ikdd.main_billing_country_iso_3_per_deal,
-- DEAL LIFECYCLE --
@ -279,6 +285,10 @@ left join
daily_deal_lifecycle
on d.date = daily_deal_lifecycle.date
and d.dimension_value = daily_deal_lifecycle.id_deal
left join
int_kpis__dimension_daily_accommodation as dda
on d.date = dda.date
and d.dimension_value = dda.id_deal
left join
created_bookings
on d.date = created_bookings.end_date

View file

@ -1,3 +1,7 @@
-- HubSpot stage for live deals
{% set live_stage = "Live" %}
{% set churned_state = "05-Churning" %}
{{ config(materialized="table", unique_key=["date", "id_deal"]) }}
with
int_monthly_aggregated_metrics_history_by_deal as (
@ -12,6 +16,7 @@ with
main_deal_name,
has_active_pms,
active_pms_list,
active_accommodations_per_deal_segmentation,
main_billing_country_iso_3_per_deal,
deal_lifecycle_state,
'All History' as time_window,
@ -126,6 +131,7 @@ with
main_deal_name,
has_active_pms,
active_pms_list,
active_accommodations_per_deal_segmentation,
main_billing_country_iso_3_per_deal,
deal_lifecycle_state,
'Previous 12 months' as time_window,
@ -225,6 +231,7 @@ with
main_deal_name,
has_active_pms,
active_pms_list,
active_accommodations_per_deal_segmentation,
main_billing_country_iso_3_per_deal,
deal_lifecycle_state,
'Previous 6 months' as time_window,
@ -324,6 +331,7 @@ with
main_deal_name,
has_active_pms,
active_pms_list,
active_accommodations_per_deal_segmentation,
main_billing_country_iso_3_per_deal,
deal_lifecycle_state,
'Previous 3 months' as time_window,
@ -423,6 +431,7 @@ with
main_deal_name,
has_active_pms,
active_pms_list,
active_accommodations_per_deal_segmentation,
main_billing_country_iso_3_per_deal,
deal_lifecycle_state,
'Previous month' as time_window,
@ -545,12 +554,59 @@ select
mabd.main_deal_name,
mabd.has_active_pms,
mabd.active_pms_list,
mabd.active_accommodations_per_deal_segmentation,
mabd.main_billing_country_iso_3_per_deal,
mabd.deal_lifecycle_state,
d.deal_hubspot_stage,
d.account_manager,
d.live_date_utc,
d.cancellation_date_utc,
case
when mabd.deal_lifecycle_state = '{{churned_state}}'
then
date_part(
'month',
age(coalesce(d.cancellation_date_utc, mabd.date), d.live_date_utc)
)
+ 12
* date_part(
'year',
age(coalesce(d.cancellation_date_utc, mabd.date), d.live_date_utc)
)
else null
end as months_between_live_and_churn,
d.last_contacted_date_utc,
case
when mabd.deal_lifecycle_state = '{{churned_state}}'
then
case
when
coalesce(d.cancellation_date_utc, mabd.date)
- d.last_contacted_date_utc
< 0
then null
else
coalesce(d.cancellation_date_utc, mabd.date)
- d.last_contacted_date_utc
end
else null
end as days_between_last_contacted_and_churn,
d.amount_times_contacted,
d.cancellation_category,
case
when mabd.deal_lifecycle_state = '{{churned_state}}' then true else false
end as is_churning,
case
when
mabd.deal_lifecycle_state = '{{churned_state}}'
and d.cancellation_date_utc is null
then 'INACTIVITY'
when
mabd.deal_lifecycle_state = '{{churned_state}}'
and d.cancellation_date_utc is not null
then 'ACCOUNT CANCELLATION'
else null
end as churn_reason,
-- Windowed metrics
coalesce(mabd.sum_created_bookings, 0) as created_bookings,

View file

@ -385,6 +385,22 @@ models:
Name of the active PMS associated with the deal. It can have more than
one PMS associated with it. It can be null if it doesn't have any PMS associated.
- name: active_accommodations_per_deal_segmentation
data_type: string
description: |
Segment value based on the number of listings booked in 12 months
for a given deal and date.
data_tests:
- not_null
- accepted_values:
values:
- "0"
- "01-05"
- "06-20"
- "21-60"
- "61+"
- "UNSET"
- name: main_billing_country_iso_3_per_deal
data_type: string
description: |
@ -1568,6 +1584,22 @@ models:
Name of the active PMS associated with the deal. It can have more than
one PMS associated with it. It can be null if it doesn't have any PMS associated.
- name: active_accommodations_per_deal_segmentation
data_type: string
description: |
Segment value based on the number of listings booked in 12 months
for a given deal and date.
data_tests:
- not_null
- accepted_values:
values:
- "0"
- "01-05"
- "06-20"
- "21-60"
- "61+"
- "UNSET"
- name: main_billing_country_iso_3_per_deal
data_type: string
description: |
@ -1604,6 +1636,53 @@ models:
Hubspot. It can be null if the deal has never
churned.
- name: months_between_live_and_churn
data_type: integer
description: |
Number of months between the live date and the
churn date.
data_tests:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
strictly: false
- name: last_contacted_date_utc
data_type: date
description: |
Date when the deal was last contacted according to
Hubspot.
- name: days_between_last_contacted_and_churn
data_type: integer
description: |
Number of days between the last contacted date
and the churn date.
data_tests:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
strictly: false
- name: amount_times_contacted
data_type: integer
description: |
Number of times the deal was contacted according
to Hubspot.
- name: is_churning
data_type: boolean
description: |
Flag to identify if the deal is churning or not.
- name: churn_reason
data_type: string
description: |
Reason why the deal is churning.
data_tests:
- accepted_values:
values:
- "INACTIVITY"
- "ACCOUNT CANCELLATION"
- name: created_bookings
data_type: integer
description: |

View file

@ -21,6 +21,8 @@ select
d.cancellation_category,
d.cancellation_details,
d.amount_of_properties,
d.last_contacted_date_utc,
d.amount_times_contacted,
d.created_at_utc,
d.created_date_utc,
d.updated_at_utc,

View file

@ -72,6 +72,14 @@ models:
Amount of properties the owner told us they manage. This is not necessarily the
amount of properties that will be finally booked in our business.
- name: last_contacted_date_utc
data_type: date
description: Date in which the account was last contacted
- name: amount_times_contacted
data_type: integer
description: Amount of times the account has been contacted
- name: created_at_utc
data_type: timestamp with time zone
description: Timestamp of when the record was created in Hubspot

View file

@ -15,12 +15,21 @@ select
has_active_pms as has_active_pms,
client_type as client_type,
active_pms_list as active_pms_list,
active_accommodations_per_deal_segmentation
as active_accommodations_per_deal_segmentation,
main_billing_country_iso_3_per_deal as main_billing_country_iso_3_per_deal,
deal_lifecycle_state as deal_lifecycle_state,
deal_hubspot_stage as deal_hubspot_stage,
account_manager as account_manager,
live_date_utc as live_date_utc,
cancellation_date_utc as cancellation_date_utc,
months_between_live_and_churn as months_between_live_and_churn,
last_contacted_date_utc as last_contacted_date_utc,
days_between_last_contacted_and_churn as days_between_last_contacted_and_churn,
amount_times_contacted as amount_times_contacted,
cancellation_category as cancellation_category,
is_churning as is_churning,
churn_reason as churn_reason,
created_bookings as created_bookings,
listings_booked_in_month as listings_booked_in_month,
total_revenue_in_gbp as total_revenue_in_gbp,

View file

@ -1560,6 +1560,22 @@ models:
Name of the active PMS associated with the deal. It can have more than
one PMS associated with it. It can be null if it doesn't have any PMS associated.
- name: active_accommodations_per_deal_segmentation
data_type: string
description: |
Segment value based on the number of listings booked in 12 months
for a given deal and date.
data_tests:
- not_null
- accepted_values:
values:
- "0"
- "01-05"
- "06-20"
- "21-60"
- "61+"
- "UNSET"
- name: main_billing_country_iso_3_per_deal
data_type: string
description: |
@ -1596,6 +1612,53 @@ models:
Hubspot. It can be null if the deal has never
churned.
- name: months_between_live_and_churn
data_type: integer
description: |
Number of months between the live date and the
churn date.
data_tests:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
strictly: false
- name: last_contacted_date_utc
data_type: date
description: |
Date when the deal was last contacted according to
Hubspot.
- name: days_between_last_contacted_and_churn
data_type: integer
description: |
Number of days between the last contacted date
and the churn date.
data_tests:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
strictly: false
- name: amount_times_contacted
data_type: integer
description: |
Number of times the deal was contacted according
to Hubspot.
- name: is_churning
data_type: boolean
description: |
Flag to identify if the deal is churning or not.
- name: churn_reason
data_type: string
description: |
Reason why the deal is churning.
data_tests:
- accepted_values:
values:
- "INACTIVITY"
- "ACCOUNT CANCELLATION"
- name: created_bookings
data_type: integer
description: |