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 and is_end_of_month = true
), ),
daily_deal_lifecycle as (select * from {{ ref("int_kpis__lifecycle_daily_deal") }}), 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 ( listings as (
select * select *
from {{ ref("int_kpis__agg_daily_listings") }} from {{ ref("int_kpis__agg_daily_listings") }}
@ -83,6 +86,9 @@ select
ikdd.main_deal_name, ikdd.main_deal_name,
ikdd.has_active_pms, ikdd.has_active_pms,
ikdd.active_pms_list, 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, ikdd.main_billing_country_iso_3_per_deal,
-- DEAL LIFECYCLE -- -- DEAL LIFECYCLE --
@ -279,6 +285,10 @@ left join
daily_deal_lifecycle daily_deal_lifecycle
on d.date = daily_deal_lifecycle.date on d.date = daily_deal_lifecycle.date
and d.dimension_value = daily_deal_lifecycle.id_deal 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 left join
created_bookings created_bookings
on d.date = created_bookings.end_date 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"]) }} {{ config(materialized="table", unique_key=["date", "id_deal"]) }}
with with
int_monthly_aggregated_metrics_history_by_deal as ( int_monthly_aggregated_metrics_history_by_deal as (
@ -12,6 +16,7 @@ with
main_deal_name, main_deal_name,
has_active_pms, has_active_pms,
active_pms_list, active_pms_list,
active_accommodations_per_deal_segmentation,
main_billing_country_iso_3_per_deal, main_billing_country_iso_3_per_deal,
deal_lifecycle_state, deal_lifecycle_state,
'All History' as time_window, 'All History' as time_window,
@ -126,6 +131,7 @@ with
main_deal_name, main_deal_name,
has_active_pms, has_active_pms,
active_pms_list, active_pms_list,
active_accommodations_per_deal_segmentation,
main_billing_country_iso_3_per_deal, main_billing_country_iso_3_per_deal,
deal_lifecycle_state, deal_lifecycle_state,
'Previous 12 months' as time_window, 'Previous 12 months' as time_window,
@ -225,6 +231,7 @@ with
main_deal_name, main_deal_name,
has_active_pms, has_active_pms,
active_pms_list, active_pms_list,
active_accommodations_per_deal_segmentation,
main_billing_country_iso_3_per_deal, main_billing_country_iso_3_per_deal,
deal_lifecycle_state, deal_lifecycle_state,
'Previous 6 months' as time_window, 'Previous 6 months' as time_window,
@ -324,6 +331,7 @@ with
main_deal_name, main_deal_name,
has_active_pms, has_active_pms,
active_pms_list, active_pms_list,
active_accommodations_per_deal_segmentation,
main_billing_country_iso_3_per_deal, main_billing_country_iso_3_per_deal,
deal_lifecycle_state, deal_lifecycle_state,
'Previous 3 months' as time_window, 'Previous 3 months' as time_window,
@ -423,6 +431,7 @@ with
main_deal_name, main_deal_name,
has_active_pms, has_active_pms,
active_pms_list, active_pms_list,
active_accommodations_per_deal_segmentation,
main_billing_country_iso_3_per_deal, main_billing_country_iso_3_per_deal,
deal_lifecycle_state, deal_lifecycle_state,
'Previous month' as time_window, 'Previous month' as time_window,
@ -545,12 +554,59 @@ select
mabd.main_deal_name, mabd.main_deal_name,
mabd.has_active_pms, mabd.has_active_pms,
mabd.active_pms_list, mabd.active_pms_list,
mabd.active_accommodations_per_deal_segmentation,
mabd.main_billing_country_iso_3_per_deal, mabd.main_billing_country_iso_3_per_deal,
mabd.deal_lifecycle_state, mabd.deal_lifecycle_state,
d.deal_hubspot_stage, d.deal_hubspot_stage,
d.account_manager, d.account_manager,
d.live_date_utc, d.live_date_utc,
d.cancellation_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 -- Windowed metrics
coalesce(mabd.sum_created_bookings, 0) as created_bookings, 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 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. 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 - name: main_billing_country_iso_3_per_deal
data_type: string data_type: string
description: | description: |
@ -1568,6 +1584,22 @@ models:
Name of the active PMS associated with the deal. It can have more than 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. 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 - name: main_billing_country_iso_3_per_deal
data_type: string data_type: string
description: | description: |
@ -1604,6 +1636,53 @@ models:
Hubspot. It can be null if the deal has never Hubspot. It can be null if the deal has never
churned. 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 - name: created_bookings
data_type: integer data_type: integer
description: | description: |

View file

@ -21,6 +21,8 @@ select
d.cancellation_category, d.cancellation_category,
d.cancellation_details, d.cancellation_details,
d.amount_of_properties, d.amount_of_properties,
d.last_contacted_date_utc,
d.amount_times_contacted,
d.created_at_utc, d.created_at_utc,
d.created_date_utc, d.created_date_utc,
d.updated_at_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 the owner told us they manage. This is not necessarily the
amount of properties that will be finally booked in our business. 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 - name: created_at_utc
data_type: timestamp with time zone data_type: timestamp with time zone
description: Timestamp of when the record was created in Hubspot description: Timestamp of when the record was created in Hubspot

View file

@ -15,12 +15,21 @@ select
has_active_pms as has_active_pms, has_active_pms as has_active_pms,
client_type as client_type, client_type as client_type,
active_pms_list as active_pms_list, 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, main_billing_country_iso_3_per_deal as main_billing_country_iso_3_per_deal,
deal_lifecycle_state as deal_lifecycle_state, deal_lifecycle_state as deal_lifecycle_state,
deal_hubspot_stage as deal_hubspot_stage, deal_hubspot_stage as deal_hubspot_stage,
account_manager as account_manager, account_manager as account_manager,
live_date_utc as live_date_utc, live_date_utc as live_date_utc,
cancellation_date_utc as cancellation_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, created_bookings as created_bookings,
listings_booked_in_month as listings_booked_in_month, listings_booked_in_month as listings_booked_in_month,
total_revenue_in_gbp as total_revenue_in_gbp, 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 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. 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 - name: main_billing_country_iso_3_per_deal
data_type: string data_type: string
description: | description: |
@ -1596,6 +1612,53 @@ models:
Hubspot. It can be null if the deal has never Hubspot. It can be null if the deal has never
churned. 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 - name: created_bookings
data_type: integer data_type: integer
description: | description: |