Merged PR 5069: Account revenue impact from growth in a monthly basis

# Description

Creates an intermediate model to compute the impact in revenue (total or rrpr) due to the growth.
This model also categorises the impact, and provides additional attributes for reporting purposes.

This model aims to substitute, in time, the current existing version of `int_monthly_growth_score_by_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.
- [X] I have checked for DRY opportunities with other models and docs.
- [X] 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: #29374
This commit is contained in:
Oriol Roqué Paniagua 2025-04-28 09:36:25 +00:00
parent 2873f6b980
commit ca5be5c3cf
2 changed files with 543 additions and 0 deletions

View file

@ -0,0 +1,250 @@
{{ config(materialized="table", unique_key=["end_date", "id_deal"]) }}
with
int_billable_items_growth_score_by_deal as (
select * from {{ ref("int_billable_items_growth_score_by_deal") }}
),
int_kpis__agg_monthly_total_and_retained_revenue as (
select * from {{ ref("int_kpis__agg_monthly_total_and_retained_revenue") }}
),
int_kpis__agg_dates_main_kpis as (
select * from {{ ref("int_kpis__agg_dates_main_kpis") }}
),
int_kpis__dimension_deals as (select * from {{ ref("int_kpis__dimension_deals") }}),
int_hubspot__deal as (select * from {{ ref("int_hubspot__deal") }}),
int_kpis__lifecycle_daily_deal as (
select * from {{ ref("int_kpis__lifecycle_daily_deal") }}
),
billable_items_growth_score_refined as (
select
big.start_date,
big.end_date,
big.id_deal,
big.current_month_billable_items,
big.current_month_share_billable_items,
case
when d.cancellation_date_utc between big.start_date and big.end_date
then -1
else big.growth_score
end as growth_score,
big.projection_mean_absolute_error,
big.projection_mean_absolute_percentage_error,
big.are_billable_items_projected,
case
when d.cancellation_date_utc between big.start_date and big.end_date
then true
else false
end as is_growth_score_overridden_due_to_cancellation
from int_billable_items_growth_score_by_deal big
left join int_hubspot__deal d on big.id_deal = d.id_deal
),
revenue_per_deal_and_month as (
select
d.date,
d.dimension_value as id_deal,
-- Metrics current month
coalesce(r.total_revenue_in_gbp, 0) as current_month_total_revenue_in_gbp,
coalesce(
r.revenue_retained_post_resolutions_in_gbp, 0
) as current_month_revenue_retained_post_resolutions_in_gbp,
-- Metrics rolling 12 months - from 11 months ago to current month,
-- inclusive
sum(coalesce(r.total_revenue_in_gbp, 0)) over (
partition by d.dimension_value
order by d.date asc
rows between 11 preceding and current row
) as rolling_12_months_total_revenue_in_gbp,
sum(coalesce(r.revenue_retained_post_resolutions_in_gbp, 0)) over (
partition by d.dimension_value
order by d.date asc
rows between 11 preceding and current row
) as rolling_12_months_revenue_retained_post_resolutions_in_gbp
from int_kpis__agg_dates_main_kpis d
left join
int_kpis__agg_monthly_total_and_retained_revenue r
on d.dimension_value = r.dimension_value
and d.date = r.end_date
where
d.dimension = 'by_deal'
and d.dimension_value <> 'UNSET'
and d.is_end_of_month = true
),
deal_revenue_contribution_per_month as (
select
rpd.date,
-- Transform to next end of month for future join: revenue contribution
-- needs to be multiplied by the current month growth
(
date_trunc('month', rpd.date)::date
+ interval '2 months'
- interval '1 day'
)::date as next_end_of_month,
rpd.id_deal,
-- Deal contribution to metric vs. global
(
rpd.rolling_12_months_total_revenue_in_gbp
) / sum(rpd.rolling_12_months_total_revenue_in_gbp) over (
partition by rpd.date order by rpd.date
) as share_total_revenue_rolling_12_months,
(rpd.rolling_12_months_revenue_retained_post_resolutions_in_gbp)
/ sum(rpd.rolling_12_months_revenue_retained_post_resolutions_in_gbp) over (
partition by rpd.date order by rpd.date
) as share_revenue_retained_post_resolutions_rolling_12_months
from revenue_per_deal_and_month rpd
),
growth_and_impact_scores_per_deal_and_month as (
select
-- Time window
date_trunc('month', drc.next_end_of_month)::date as start_date,
drc.next_end_of_month as end_date,
-- Deal
drc.id_deal,
-- Deal contribution to revenue
drc.share_total_revenue_rolling_12_months,
drc.share_revenue_retained_post_resolutions_rolling_12_months,
-- Growth Score: if Growth is not available, set to 0
coalesce(big.growth_score, 0) as growth_score,
-- Impact Score
coalesce(big.growth_score, 0)
* share_total_revenue_rolling_12_months as impact_score_total_revenue,
coalesce(big.growth_score, 0)
* share_revenue_retained_post_resolutions_rolling_12_months
as impact_score_revenue_retained_post_resolutions,
-- If billable items are not available, set to 0
coalesce(
big.current_month_billable_items, 0
) as current_month_billable_items,
coalesce(
big.current_month_share_billable_items, 0
) as share_billable_items_current_month,
-- Projection metrics
sum(coalesce(big.current_month_billable_items, 0)) over (
partition by drc.date order by drc.date
) as global_monthly_billable_items,
big.projection_mean_absolute_error,
big.projection_mean_absolute_percentage_error,
big.are_billable_items_projected,
big.is_growth_score_overridden_due_to_cancellation
from deal_revenue_contribution_per_month drc
left join
billable_items_growth_score_refined big
on drc.next_end_of_month = big.end_date
and drc.id_deal = big.id_deal
)
select
-- Time window
gai.start_date,
gai.end_date,
-- Deal Static Attributes
gai.id_deal,
gai.id_deal || '-' || coalesce(ikdd.main_deal_name, '') as deal,
ikdd.client_type,
ikdd.has_active_pms,
ikdd.active_pms_list,
ikdd.main_billing_country_iso_3_per_deal,
-- Deal Lifecycle --
deal_lifecycle.deal_lifecycle_state,
-- Deal Hubspot Attributes --
d.deal_hubspot_stage,
d.account_manager,
d.live_date_utc,
d.cancellation_date_utc,
-- Growth & Impact Scores
gai.growth_score,
gai.impact_score_total_revenue,
gai.impact_score_revenue_retained_post_resolutions,
-- Categorisation
case
when impact_score_revenue_retained_post_resolutions <= -0.3 / 100
then 'MAJOR DECLINE'
when
impact_score_revenue_retained_post_resolutions < -0.03 / 100
and impact_score_revenue_retained_post_resolutions > -0.3 / 100
then 'DECLINE'
when
impact_score_revenue_retained_post_resolutions >= -0.03 / 100
and impact_score_revenue_retained_post_resolutions <= 0.03 / 100
then 'FLAT'
when
impact_score_revenue_retained_post_resolutions > 0.03 / 100
and impact_score_revenue_retained_post_resolutions < 0.3 / 100
then 'GAIN'
when impact_score_revenue_retained_post_resolutions >= 0.3 / 100
then 'MAJOR GAIN'
else 'UNSET'
end as categorisation_impact_score_revenue_retained_post_resolutions,
-- Rank per Impact Score
row_number() over (
partition by gai.end_date
order by gai.impact_score_total_revenue desc, gai.id_deal
) as rank_impact_score_total_revenue,
row_number() over (
partition by gai.end_date
order by gai.impact_score_revenue_retained_post_resolutions desc, gai.id_deal
) as rank_impact_score_revenue_retained_post_resolutions,
-- Total Revenue Metrics
rpd.current_month_total_revenue_in_gbp,
rpd.rolling_12_months_total_revenue_in_gbp,
gai.share_total_revenue_rolling_12_months,
row_number() over (
partition by gai.end_date
order by gai.share_total_revenue_rolling_12_months desc, gai.id_deal
) as rank_total_revenue_rolling_12_months,
-- Revenue Retained Post Resolutions Metrics
rpd.current_month_revenue_retained_post_resolutions_in_gbp,
rpd.rolling_12_months_revenue_retained_post_resolutions_in_gbp,
gai.share_revenue_retained_post_resolutions_rolling_12_months,
row_number() over (
partition by gai.end_date
order by
gai.share_revenue_retained_post_resolutions_rolling_12_months desc,
gai.id_deal
) as rank_revenue_retained_post_resolutions_rolling_12_months,
-- Billable Items Metrics
gai.current_month_billable_items,
gai.share_billable_items_current_month,
row_number() over (
partition by gai.end_date
order by gai.current_month_billable_items desc, gai.id_deal
) as rank_billable_items_current_month,
gai.projection_mean_absolute_error,
gai.projection_mean_absolute_percentage_error,
gai.are_billable_items_projected,
gai.is_growth_score_overridden_due_to_cancellation
from growth_and_impact_scores_per_deal_and_month gai
left join
revenue_per_deal_and_month rpd
-- Keep Revenue attributed to the real month (will be null in the ongoing month)
on gai.id_deal = rpd.id_deal
and gai.end_date = rpd.date
left join int_kpis__dimension_deals ikdd on gai.id_deal = ikdd.id_deal
left join int_hubspot__deal d on gai.id_deal = d.id_deal
left join
int_kpis__lifecycle_daily_deal deal_lifecycle
-- Retrieve Deal Lifecycle State for the end of the month or yesterday if it's the
-- ongoing month
on (
gai.end_date = deal_lifecycle.date
or (
current_date - interval '1 day' = deal_lifecycle.date
and date_trunc('month', gai.end_date)::date
= date_trunc('month', deal_lifecycle.date)::date
)
)
and gai.id_deal = deal_lifecycle.id_deal

View file

@ -3504,3 +3504,296 @@ models:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 1
strictly: false
- name: int_monthly_account_revenue_impact_from_growth
description: |
This model computes the monthly revenue impact from the growth of
billable items for each deal. The revenue impact is computed as the
product of the growth score and the deal contribution to the total revenue
in the previous 12 months.
There's 2 impact scores computed depending on the revenue metric, namely:
- impact_score_total_revenue: based on Total Revenue
- impact_score_revenue_retained_post_resolutions: based on Revenue Retained
Post Resolutions
It is important to note that if we check the ongoing month, the count of
billable items and the corresponding share will be based on the projection,
rather than the actual figure. In this case, the MAE and MAPE of the projected
value are indicated in the model.
While the growth and impact scores are computed at a monthly basis, their values
will update every day with the latest available projection.
data_tests:
- dbt_utils.unique_combination_of_columns:
combination_of_columns:
- end_date
- id_deal
- dbt_utils.unique_combination_of_columns:
combination_of_columns:
- start_date
- id_deal
columns:
- name: start_date
data_type: date
description: |
Start date of the period for which the revenue impact is computed.
Corresponds to the first day of the month.
data_tests:
- not_null
- name: end_date
data_type: date
description: |
End date of the period for which the revenue impact is computed.
Corresponds to the last day of the month.
data_tests:
- not_null
- name: id_deal
data_type: string
description: |
Unique ID for a deal, or account.
data_tests:
- not_null
- name: deal
data_type: string
description: |
Concatenation of the deal ID and the deal name.
- name: client_type
data_type: string
description: |
Type of the client, PLATFORM or API.
- name: has_active_pms
data_type: boolean
description: |
Flag indicating if the deal has an active PMS or not.
- name: active_pms_list
data_type: string
description: |
List of active PMS for the deal. It can be null if the deal has no
active PMS.
- name: main_billing_country_iso_3_per_deal
data_type: string
description: |
Main billing country for the deal. It can be null.
- name: deal_lifecycle_state
data_type: string
description: |
Lifecycle state of the deal.
- name: deal_hubspot_stage
data_type: string
description: |
Hubspot stage of the deal.
- name: account_manager
data_type: string
description: |
Account manager of the deal. It can be null.
- name: live_date_utc
data_type: date
description: |
Live date of the deal according to HubSpot. It can be null.
- name: cancellation_date_utc
data_type: date
description: |
Cancellation date of the deal according to HubSpot. It can be null.
- name: growth_score
data_type: decimal
description: |
Growth score of the billable items, based on the average between:
- The billable items of a given month vs. the average of the previous
3 months.
- The share a deal has in terms of billable items of a given month
if compared to the rest of the deals vs. the average of the previous 3
months.
The growth score is capped between -1 and 1.
It can be overridden to -1 in case the deal is cancelled in the same month.
It cannot be null.
data_tests:
- not_null
- dbt_expectations.expect_column_values_to_be_between:
min_value: -1
max_value: 1
strictly: false
- name: impact_score_total_revenue
data_type: decimal
description: |
Impact score of the growth score on the total revenue.
It is computed as the product of the growth score and the deal
contribution to the total revenue in the previous 12 months.
It cannot be null.
data_tests:
- not_null
- dbt_expectations.expect_column_values_to_be_between:
min_value: -1
max_value: 1
strictly: false
- name: impact_score_revenue_retained_post_resolutions
data_type: decimal
description: |
Impact score of the growth score on the revenue retained post
resolutions. It is computed as the product of the growth score and
the deal contribution to the revenue retained post resolutions in
the previous 12 months.
It cannot be null.
data_tests:
- not_null
- dbt_expectations.expect_column_values_to_be_between:
min_value: -1
max_value: 1
strictly: false
- name: categorisation_impact_score_revenue_retained_post_resolutions
data_type: string
description: |
Categorisation of the impact score on the revenue retained post
resolutions. It cannot be null.
data_tests:
- not_null
- accepted_values:
values:
- MAJOR DECLINE
- DECLINE
- FLAT
- GAIN
- MAJOR GAIN
- name: rank_impact_score_total_revenue
data_type: integer
description: |
Monthly rank of the deal in terms of impact score on the total revenue.
- name: rank_impact_score_revenue_retained_post_resolutions
data_type: integer
description: |
Monthly rank of the deal in terms of impact score on the revenue
retained post resolutions.
- name: current_month_total_revenue_in_gbp
data_type: decimal
description: |
Total revenue in GBP for the current month.
If the month is in progress then this value will be null.
- name: rolling_12_months_total_revenue_in_gbp
data_type: decimal
description: |
Total revenue in GBP for the previous 12 months.
It can be null.
- name: share_total_revenue_rolling_12_months
data_type: decimal
description: |
Share of the deal in terms of total revenue in the previous 12 months.
It cannot be null.
data_tests:
- not_null
- name: rank_total_revenue_rolling_12_months
data_type: integer
description: |
Monthly rank of the deal in terms of total revenue in the previous
12 months.
- name: current_month_revenue_retained_post_resolutions_in_gbp
data_type: decimal
description: |
Revenue retained post resolutions in GBP for the current month.
If the month is in progress then this value will be null.
- name: rolling_12_months_revenue_retained_post_resolutions_in_gbp
data_type: decimal
description: |
Revenue retained post resolutions in GBP for the previous 12 months.
It can be null.
- name: share_revenue_retained_post_resolutions_rolling_12_months
data_type: decimal
description: |
Share of the deal in terms of revenue retained post resolutions in
the previous 12 months.
It cannot be null.
data_tests:
- not_null
- name: rank_revenue_retained_post_resolutions_rolling_12_months
data_type: integer
description: |
Monthly rank of the deal in terms of revenue retained post
resolutions in the previous 12 months.
- name: current_month_billable_items
data_type: integer
description: |
Monthly billable items. If the month is in progress
then this value might be projected.
data_tests:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
strictly: false
- name: share_billable_items_current_month
data_type: decimal
description: |
Share of the billable items for a given deal in the current month.
If the month is in progress then this value might be projected.
data_tests:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
strictly: false
- name: rank_billable_items_current_month
data_type: integer
description: |
Monthly rank of the deal in terms of billable items in the current month.
If the month is in progress then this value might be projected.
- name: projection_mean_absolute_error
data_type: decimal
description: |
Mean absolute error of the projection of the billable items.
It is null if the month is not in progress or value is projected
but there's no prior data to compare the projection against.
data_tests:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
strictly: false
- name: projection_mean_absolute_percentage_error
data_type: decimal
description: |
Mean absolute percentage error of the projection of the billable items.
It is null if the month is not in progress or value is projected
but there's no prior data to compare the projection against.
data_tests:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
strictly: false
- name: are_billable_items_projected
data_type: boolean
description: |
Flag indicating if the billable items are projected or not.
If the month is in progress then this value might be projected.
It can be null if there's no projection for that deal.
- name: is_growth_score_overridden_due_to_cancellation
data_type: boolean
description: |
Flag indicating if the growth score is overridden to -1 due to
cancellation in the same month.