From e4d19b85e80ad8f4bccd903f748d662c689b6f32 Mon Sep 17 00:00:00 2001 From: Joaquin Ossa Date: Fri, 24 Jan 2025 11:36:22 +0100 Subject: [PATCH 1/3] Capped new ratios --- macros/calculate_safe_relative_increment.sql | 38 +++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/macros/calculate_safe_relative_increment.sql b/macros/calculate_safe_relative_increment.sql index 5e9018f..6645136 100644 --- a/macros/calculate_safe_relative_increment.sql +++ b/macros/calculate_safe_relative_increment.sql @@ -7,8 +7,36 @@ It's designed to be placed within a SELECT statement. It ensure safe divide by zero division by applying a nullif function. */ -{% macro calculate_safe_relative_increment(metric, current='current', previous='previous_year') %} - {{ current }}.{{ metric }}, - {{ previous }}.{{ metric }} as {{ previous }}_{{ metric }}, - cast({{ current }}.{{ metric }} as decimal) / nullif({{ previous }}.{{ metric }},0) - 1 as relative_increment_{{ metric }} -{% endmacro %} \ No newline at end of file +{% macro calculate_safe_relative_increment( + metric, current="current", previous="previous_year" +) %} + -- Metrics that need to be capped between -1 and 1 + {% set capped_metrics = [ + "host_resolution_payment_per_created_booking_ratio", + "revenue_retained_post_resolutions_ratio", + "revenue_retained_ratio", + ] %} + {% if metric in capped_metrics %} + -- Cap current and previous values between -1 and 1 + case + when {{ current }}.{{ metric }} is null + then null + else least(1, greatest(-1, {{ current }}.{{ metric }})) + end as {{ metric }}, + case + when {{ previous }}.{{ metric }} is null + then null + else least(1, greatest(-1, {{ previous }}.{{ metric }})) + end as {{ previous }}_{{ metric }}, + cast(least(1, greatest(-1, {{ current }}.{{ metric }})) as decimal) + / nullif(least(1, greatest(-1, {{ previous }}.{{ metric }})), 0) + - 1 as relative_increment_{{ metric }} + {% else %} + -- No capping applied, default behavior + {{ current }}.{{ metric }}, + {{ previous }}.{{ metric }} as {{ previous }}_{{ metric }}, + cast({{ current }}.{{ metric }} as decimal) + / nullif({{ previous }}.{{ metric }}, 0) + - 1 as relative_increment_{{ metric }} + {% endif %} +{% endmacro %} From 720185f23566767fbffa16cbf8a05668655f0955 Mon Sep 17 00:00:00 2001 From: Joaquin Ossa Date: Fri, 24 Jan 2025 14:17:29 +0100 Subject: [PATCH 2/3] Fixed macro --- macros/calculate_safe_relative_increment.sql | 33 ++------- macros/return_capped_value.sql | 20 ++++++ .../int_mtd_vs_previous_year_metrics.sql | 71 ++++++++++++------- 3 files changed, 68 insertions(+), 56 deletions(-) create mode 100644 macros/return_capped_value.sql diff --git a/macros/calculate_safe_relative_increment.sql b/macros/calculate_safe_relative_increment.sql index 6645136..59ba448 100644 --- a/macros/calculate_safe_relative_increment.sql +++ b/macros/calculate_safe_relative_increment.sql @@ -10,33 +10,8 @@ It ensure safe divide by zero division by applying a nullif function. {% macro calculate_safe_relative_increment( metric, current="current", previous="previous_year" ) %} - -- Metrics that need to be capped between -1 and 1 - {% set capped_metrics = [ - "host_resolution_payment_per_created_booking_ratio", - "revenue_retained_post_resolutions_ratio", - "revenue_retained_ratio", - ] %} - {% if metric in capped_metrics %} - -- Cap current and previous values between -1 and 1 - case - when {{ current }}.{{ metric }} is null - then null - else least(1, greatest(-1, {{ current }}.{{ metric }})) - end as {{ metric }}, - case - when {{ previous }}.{{ metric }} is null - then null - else least(1, greatest(-1, {{ previous }}.{{ metric }})) - end as {{ previous }}_{{ metric }}, - cast(least(1, greatest(-1, {{ current }}.{{ metric }})) as decimal) - / nullif(least(1, greatest(-1, {{ previous }}.{{ metric }})), 0) - - 1 as relative_increment_{{ metric }} - {% else %} - -- No capping applied, default behavior - {{ current }}.{{ metric }}, - {{ previous }}.{{ metric }} as {{ previous }}_{{ metric }}, - cast({{ current }}.{{ metric }} as decimal) - / nullif({{ previous }}.{{ metric }}, 0) - - 1 as relative_increment_{{ metric }} - {% endif %} + {{ current }}.{{ metric }}, + {{ previous }}.{{ metric }} as {{ previous }}_{{ metric }}, + cast({{ current }}.{{ metric }} as decimal) / nullif({{ previous }}.{{ metric }}, 0) + - 1 as relative_increment_{{ metric }} {% endmacro %} diff --git a/macros/return_capped_value.sql b/macros/return_capped_value.sql new file mode 100644 index 0000000..b6d2889 --- /dev/null +++ b/macros/return_capped_value.sql @@ -0,0 +1,20 @@ +/* +This macro caps a given value between a specified bottom and top limit, +returning `NULL` if the input value is `NULL`. + +It uses the `LEAST` and `GREATEST` SQL functions to enforce the caps while +preserving the `NULL` values in the input. + +Parameters: +- `value`: The value to be capped. +- `cap_bottom`: The minimum limit for the value. +- `cap_top`: The maximum limit for the value. + +*/ +{% macro return_capped_value(value, cap_bottom, cap_top) %} + CASE + WHEN {{ value }} IS NULL THEN NULL + ELSE LEAST({{ cap_top }}, GREATEST({{ cap_bottom }}, {{ value }})) + END +{% endmacro %} + diff --git a/models/intermediate/cross/int_mtd_vs_previous_year_metrics.sql b/models/intermediate/cross/int_mtd_vs_previous_year_metrics.sql index bc45c22..231df5e 100644 --- a/models/intermediate/cross/int_mtd_vs_previous_year_metrics.sql +++ b/models/intermediate/cross/int_mtd_vs_previous_year_metrics.sql @@ -289,8 +289,14 @@ with cast(host_resolutions.xero_host_resolution_amount_paid_in_gbp as decimal) / created_bookings.created_bookings as host_resolution_amount_paid_per_created_booking, - cast(host_resolutions.xero_host_resolution_payment_count as decimal) - / created_bookings.created_bookings + {{ + return_capped_value( + "cast(host_resolutions.xero_host_resolution_payment_count as decimal) + / created_bookings.created_bookings", + -1, + 1 + ) + }} as host_resolution_payment_per_created_booking_ratio, -- GUEST REVENUE AND PAYMENTS -- @@ -364,31 +370,42 @@ with + coalesce(invoiced_revenue.xero_waiver_paid_back_to_host_in_gbp, 0), 0 ) as revenue_retained_in_gbp, - nullif( - coalesce(guest_payments.total_guest_payments_in_gbp, 0) - + coalesce(invoiced_revenue.xero_operator_net_fees_in_gbp, 0) - + coalesce(invoiced_revenue.xero_apis_net_fees_in_gbp, 0) - + coalesce(invoiced_revenue.xero_waiver_paid_back_to_host_in_gbp, 0), - 0 - ) / nullif( - coalesce(guest_payments.total_guest_payments_in_gbp, 0) - + coalesce(invoiced_revenue.xero_operator_net_fees_in_gbp, 0) - + coalesce(invoiced_revenue.xero_apis_net_fees_in_gbp, 0), - 0 - ) as revenue_retained_ratio, - nullif( - coalesce(guest_payments.total_guest_payments_in_gbp, 0) - + coalesce(invoiced_revenue.xero_operator_net_fees_in_gbp, 0) - + coalesce(invoiced_revenue.xero_apis_net_fees_in_gbp, 0) - + coalesce(invoiced_revenue.xero_waiver_paid_back_to_host_in_gbp, 0) - + coalesce(host_resolutions.xero_host_resolution_amount_paid_in_gbp, 0), - 0 - ) / nullif( - coalesce(guest_payments.total_guest_payments_in_gbp, 0) - + coalesce(invoiced_revenue.xero_operator_net_fees_in_gbp, 0) - + coalesce(invoiced_revenue.xero_apis_net_fees_in_gbp, 0), - 0 - ) as revenue_retained_post_resolutions_ratio, + {{ + return_capped_value( + "nullif( + coalesce(guest_payments.total_guest_payments_in_gbp, 0) + + coalesce(invoiced_revenue.xero_operator_net_fees_in_gbp, 0) + + coalesce(invoiced_revenue.xero_apis_net_fees_in_gbp, 0) + + coalesce(invoiced_revenue.xero_waiver_paid_back_to_host_in_gbp, 0), + 0 + ) / nullif( + coalesce(guest_payments.total_guest_payments_in_gbp, 0) + + coalesce(invoiced_revenue.xero_operator_net_fees_in_gbp, 0) + + coalesce(invoiced_revenue.xero_apis_net_fees_in_gbp, 0), + 0 + )", + -1, + 1 + )}} as revenue_retained_ratio, + {{ + return_capped_value( + "nullif( + coalesce(guest_payments.total_guest_payments_in_gbp, 0) + + coalesce(invoiced_revenue.xero_operator_net_fees_in_gbp, 0) + + coalesce(invoiced_revenue.xero_apis_net_fees_in_gbp, 0) + + coalesce(invoiced_revenue.xero_waiver_paid_back_to_host_in_gbp, 0) + + coalesce(host_resolutions.xero_host_resolution_amount_paid_in_gbp, 0), + 0 + ) / nullif( + coalesce(guest_payments.total_guest_payments_in_gbp, 0) + + coalesce(invoiced_revenue.xero_operator_net_fees_in_gbp, 0) + + coalesce(invoiced_revenue.xero_apis_net_fees_in_gbp, 0), + 0 + )", + -1, + 1 + ) + }} as revenue_retained_post_resolutions_ratio, -- INCOME RETAINED POST RESOLUTIONS-- nullif( From e63a09399189d5ed6447b2beb86585fd6cd316a4 Mon Sep 17 00:00:00 2001 From: Joaquin Ossa Date: Fri, 24 Jan 2025 14:47:41 +0100 Subject: [PATCH 3/3] Added ratios to deal models --- ...hly_aggregated_metrics_history_by_deal.sql | 50 ++++++++++++++++++- .../int_mtd_vs_previous_year_metrics.sql | 20 ++++---- ...hly_aggregated_metrics_history_by_deal.sql | 22 +++++++- models/reporting/general/schema.yml | 44 ++++++++++++++++ 4 files changed, 124 insertions(+), 12 deletions(-) diff --git a/models/intermediate/cross/int_monthly_aggregated_metrics_history_by_deal.sql b/models/intermediate/cross/int_monthly_aggregated_metrics_history_by_deal.sql index 3c89adf..44a40fc 100644 --- a/models/intermediate/cross/int_monthly_aggregated_metrics_history_by_deal.sql +++ b/models/intermediate/cross/int_monthly_aggregated_metrics_history_by_deal.sql @@ -149,6 +149,18 @@ select -- HOST RESOLUTIONS -- host_resolutions.xero_host_resolution_amount_paid_in_gbp, host_resolutions.xero_host_resolution_payment_count, + cast(host_resolutions.xero_host_resolution_amount_paid_in_gbp as decimal) + / created_bookings.created_bookings + as host_resolution_amount_paid_per_created_booking, + {{ + return_capped_value( + "cast(host_resolutions.xero_host_resolution_payment_count as decimal) + / created_bookings.created_bookings", + -1, + 1 + ) + }} + as host_resolution_payment_per_created_booking_ratio, -- GUEST REVENUE AND PAYMENTS -- guest_payments.deposit_fees_in_gbp, @@ -208,6 +220,23 @@ select + coalesce(invoiced_revenue.xero_waiver_paid_back_to_host_in_gbp, 0), 0 ) as revenue_retained_in_gbp, + {{ + return_capped_value( + "nullif( + coalesce(guest_payments.total_guest_payments_in_gbp, 0) + + coalesce(invoiced_revenue.xero_operator_net_fees_in_gbp, 0) + + coalesce(invoiced_revenue.xero_apis_net_fees_in_gbp, 0) + + coalesce(invoiced_revenue.xero_waiver_paid_back_to_host_in_gbp, 0), + 0 + ) / nullif( + coalesce(guest_payments.total_guest_payments_in_gbp, 0) + + coalesce(invoiced_revenue.xero_operator_net_fees_in_gbp, 0) + + coalesce(invoiced_revenue.xero_apis_net_fees_in_gbp, 0), + 0 + )", + -1, + 1 + )}} as revenue_retained_ratio, -- REVENUE RETAINED POST RESOLUTIONS-- nullif( @@ -217,7 +246,26 @@ select + coalesce(invoiced_revenue.xero_waiver_paid_back_to_host_in_gbp, 0) + coalesce(host_resolutions.xero_host_resolution_amount_paid_in_gbp, 0), 0 - ) as revenue_retained_post_resolutions_in_gbp + ) as revenue_retained_post_resolutions_in_gbp, + {{ + return_capped_value( + "nullif( + coalesce(guest_payments.total_guest_payments_in_gbp, 0) + + coalesce(invoiced_revenue.xero_operator_net_fees_in_gbp, 0) + + coalesce(invoiced_revenue.xero_apis_net_fees_in_gbp, 0) + + coalesce(invoiced_revenue.xero_waiver_paid_back_to_host_in_gbp, 0) + + coalesce(host_resolutions.xero_host_resolution_amount_paid_in_gbp, 0), + 0 + ) / nullif( + coalesce(guest_payments.total_guest_payments_in_gbp, 0) + + coalesce(invoiced_revenue.xero_operator_net_fees_in_gbp, 0) + + coalesce(invoiced_revenue.xero_apis_net_fees_in_gbp, 0), + 0 + )", + -1, + 1 + ) + }} as revenue_retained_post_resolutions_ratio from int_kpis__agg_dates_main_kpis d left join int_kpis__dimension_deals ikdd on d.dimension_value = ikdd.id_deal diff --git a/models/intermediate/cross/int_mtd_vs_previous_year_metrics.sql b/models/intermediate/cross/int_mtd_vs_previous_year_metrics.sql index 231df5e..2e74aba 100644 --- a/models/intermediate/cross/int_mtd_vs_previous_year_metrics.sql +++ b/models/intermediate/cross/int_mtd_vs_previous_year_metrics.sql @@ -387,6 +387,16 @@ with -1, 1 )}} as revenue_retained_ratio, + + -- INCOME RETAINED POST RESOLUTIONS-- + nullif( + coalesce(guest_payments.total_guest_payments_in_gbp, 0) + + coalesce(invoiced_revenue.xero_operator_net_fees_in_gbp, 0) + + coalesce(invoiced_revenue.xero_apis_net_fees_in_gbp, 0) + + coalesce(invoiced_revenue.xero_waiver_paid_back_to_host_in_gbp, 0) + + coalesce(host_resolutions.xero_host_resolution_amount_paid_in_gbp, 0), + 0 + ) as revenue_retained_post_resolutions_in_gbp, {{ return_capped_value( "nullif( @@ -407,16 +417,6 @@ with ) }} as revenue_retained_post_resolutions_ratio, - -- INCOME RETAINED POST RESOLUTIONS-- - nullif( - coalesce(guest_payments.total_guest_payments_in_gbp, 0) - + coalesce(invoiced_revenue.xero_operator_net_fees_in_gbp, 0) - + coalesce(invoiced_revenue.xero_apis_net_fees_in_gbp, 0) - + coalesce(invoiced_revenue.xero_waiver_paid_back_to_host_in_gbp, 0) - + coalesce(host_resolutions.xero_host_resolution_amount_paid_in_gbp, 0), - 0 - ) as revenue_retained_post_resolutions_in_gbp, - -- ONBOARDING MRR METRIC -- onboarding_mrr.expected_mrr as expected_mrr diff --git a/models/reporting/general/monthly_aggregated_metrics_history_by_deal.sql b/models/reporting/general/monthly_aggregated_metrics_history_by_deal.sql index 2a4c500..9a489f0 100644 --- a/models/reporting/general/monthly_aggregated_metrics_history_by_deal.sql +++ b/models/reporting/general/monthly_aggregated_metrics_history_by_deal.sql @@ -75,6 +75,16 @@ select then xero_host_resolution_payment_count else null end as xero_host_resolution_payment_count, + case + when {{ is_date_before_previous_month("date") }} + then host_resolution_amount_paid_per_created_booking + else null + end as host_resolution_amount_paid_per_created_booking, + case + when {{ is_date_before_previous_month("date") }} + then host_resolution_payment_per_created_booking_ratio + else null + end as host_resolution_payment_per_created_booking_ratio, case when {{ is_date_before_previous_month("date") }} then xero_booking_net_fees_in_gbp @@ -153,9 +163,19 @@ select then revenue_retained_in_gbp else null end as revenue_retained_in_gbp, + case + when {{ is_date_before_previous_month("date") }} + then revenue_retained_ratio + else null + end as revenue_retained_ratio, case when {{ is_date_before_previous_month("date") }} then revenue_retained_post_resolutions_in_gbp else null - end as revenue_retained_post_resolutions_in_gbp + end as revenue_retained_post_resolutions_in_gbp, + case + when {{ is_date_before_previous_month("date") }} + then revenue_retained_post_resolutions_ratio + else null + end as revenue_retained_post_resolutions_ratio from int_monthly_aggregated_metrics_history_by_deal diff --git a/models/reporting/general/schema.yml b/models/reporting/general/schema.yml index 1194b8c..8ed0a37 100644 --- a/models/reporting/general/schema.yml +++ b/models/reporting/general/schema.yml @@ -525,6 +525,50 @@ models: data_tests: - not_null + - name: host_resolution_amount_paid_per_created_booking + data_type: decimal + description: | + Host resolution amount paid divided by the number of + created bookings in the time window. It can be null if + no resolution payments were made by the host. + It can be negative or positive. + + - name: host_resolution_payment_per_created_booking_ratio + data_type: decimal + description: | + Ratio of Host resolution payment count divided by the + number of created bookings in the time window. It can be null + if no resolution payments were made by the host. + It is capped between -1 and 1. + data_tests: + - dbt_expectations.expect_column_values_to_be_between: + min_value: -1 + max_value: 1 + strictly: false + + - name: revenue_retained_ratio + data_type: decimal + description: | + Ratio of Revenue Retained divided by Total Revenue. + It is capped between -1 and 1. It can be Null + data_tests: + - dbt_expectations.expect_column_values_to_be_between: + min_value: -1 + max_value: 1 + strictly: false + + - name: revenue_retained_post_resolutions_ratio + data_type: decimal + description: | + Ratio of Revenue Retained Post-Resolutions + divided by Total Revenue. + It is capped between -1 and 1. It can be Null + data_tests: + - dbt_expectations.expect_column_values_to_be_between: + min_value: -1 + max_value: 1 + strictly: false + - name: monthly_growth_score_by_deal description: | The main goal of this model is to provide a growth score by deal and month.