diff --git a/models/intermediate/core/int_core__accommodation_to_product_bundle.sql b/models/intermediate/core/int_core__accommodation_to_product_bundle.sql index 01e7931..9448284 100644 --- a/models/intermediate/core/int_core__accommodation_to_product_bundle.sql +++ b/models/intermediate/core/int_core__accommodation_to_product_bundle.sql @@ -30,6 +30,10 @@ select else greatest(date(atpb.ends_at_utc), upb.effective_end_date_utc) end as effective_end_date_utc, atpb.has_no_end_date, + upb.product_bundle_services, + upb.is_custom_bundle, + upb.has_upgraded_services, + upb.has_billable_services, atpb.created_at_utc, atpb.created_date_utc, atpb.updated_at_utc, diff --git a/models/intermediate/core/int_core__user_product_bundle.sql b/models/intermediate/core/int_core__user_product_bundle.sql index 757422b..acd7773 100644 --- a/models/intermediate/core/int_core__user_product_bundle.sql +++ b/models/intermediate/core/int_core__user_product_bundle.sql @@ -4,8 +4,27 @@ with select * from {{ ref("stg_core__user_product_bundle") }} ), stg_core__protection_plan as (select * from {{ ref("stg_core__protection_plan") }}), - int_core__user_host as (select * from {{ ref("int_core__user_host") }}) - + int_core__user_host as (select * from {{ ref("int_core__user_host") }}), + int_core__user_product_bundle_contains_services as ( + select * from {{ ref("int_core__user_product_bundle_contains_services") }} + ), + product_bundle_services_agg as ( + select + id_user_product_bundle, + case + when sum(cast(not is_default_service as integer)) > 0 + then true + else false + end as has_upgraded_services, + case + when sum(cast(is_billable_service as integer)) > 0 then true else false + end as has_billable_services, + string_agg( + distinct service_name, '|' order by service_name asc + ) as product_bundle_services + from int_core__user_product_bundle_contains_services + group by 1 + ) select upb.id_user_product_bundle, upb.id_user as id_user_host, @@ -17,6 +36,7 @@ select pp.protection_display_name, upb.display_on_front_end, upb.chosen_product_services, + pbsa.product_bundle_services, upb.starts_at_utc as original_starts_at_utc, upb.ends_at_utc as original_ends_at_utc, /* @@ -39,6 +59,11 @@ select upb.created_date_utc, upb.updated_at_utc, upb.updated_date_utc, + case + when upb.id_product_bundle is null then true else false + end as is_custom_bundle, + pbsa.has_upgraded_services, + pbsa.has_billable_services, upb.dwh_extracted_at from stg_core__user_product_bundle upb /* @@ -55,3 +80,6 @@ inner join and uh.is_missing_id_deal = false and uh.is_test_account = false left join stg_core__protection_plan pp on upb.id_protection_plan = pp.id_protection_plan +left join + product_bundle_services_agg pbsa + on upb.id_user_product_bundle = pbsa.id_user_product_bundle diff --git a/models/intermediate/core/int_core__user_product_bundle_contains_services.sql b/models/intermediate/core/int_core__user_product_bundle_contains_services.sql index ad79800..d276327 100644 --- a/models/intermediate/core/int_core__user_product_bundle_contains_services.sql +++ b/models/intermediate/core/int_core__user_product_bundle_contains_services.sql @@ -27,6 +27,7 @@ select pb.product_bundle_name, ps.service_business_type, ps.is_default_service, + ps.is_billable_service, ps.product_service_display_name as service_name from stg_core__user_product_bundle pb inner join int_core__user_host uh on pb.id_user = uh.id_user_host @@ -44,6 +45,7 @@ select pb.product_bundle_name, 'PROTECTION' as service_business_type, pp.is_default_service, + pp.is_billable_service, pp.protection_display_name as service_name from stg_core__user_product_bundle pb inner join int_core__user_host uh on pb.id_user = uh.id_user_host diff --git a/models/intermediate/core/schema.yml b/models/intermediate/core/schema.yml index 6413906..4beaba5 100644 --- a/models/intermediate/core/schema.yml +++ b/models/intermediate/core/schema.yml @@ -1927,6 +1927,11 @@ models: a chosen_product_services = 257 means it has the services 1 + 256, which are the Basic Screening and the Waiver Pro. + - name: product_bundle_services + data_type: string + description: | + List of services that are included in this bundle, separated by "|", ordered alphabetically. + - name: original_starts_at_utc data_type: timestamp description: | @@ -1987,6 +1992,23 @@ models: description: | Date of when this User has Product Bundle was last updated in the Backend. + - name: is_custom_bundle + data_type: boolean + description: | + True if the bundle or program is custom, false if it's a default one. + + - name: has_billable_services + data_type: boolean + description: | + True if the bundle or program contains at least one billable service, false + otherwise. + + - name: has_upgraded_services + data_type: boolean + description: | + True if the bundle or program contains at least one service different than + the default one, false otherwise. + - name: dwh_extracted_at data_type: timestamp description: | @@ -2141,7 +2163,7 @@ models: data_tests: - not_null - - name: product_bundle_name + - name: user_product_bundle_name data_type: string description: | The name of the Product Bundle. @@ -2208,6 +2230,29 @@ models: description: | Date of when this Accommodation to Product Bundle record was last updated in the Backend. + - name: product_bundle_services + data_type: string + description: | + List of services that are included in the bundle applied to the listing, + separated by "|", ordered alphabetically. + + - name: is_custom_bundle + data_type: boolean + description: | + True if the bundle or program applied to the listing is custom, false if it's a default one. + + - name: has_billable_services + data_type: boolean + description: | + True if the bundle or program applied to the listing contains at least one billable service, + false otherwise. + + - name: has_upgraded_services + data_type: boolean + description: | + True if the bundle or program applied to the listing contains at least one service different + than the default one, false otherwise. + - name: dwh_extracted_at data_type: timestamp with timezone description: | @@ -2842,6 +2887,12 @@ models: data_tests: - not_null + - name: is_billable_service + data_type: boolean + description: | + Flag that determines if the service is billable to the + host (True) or not (False). + - name: int_core__product_service_to_price description: | This model provides the information related to the prices of the different diff --git a/models/intermediate/cross/int_new_dash_deal_onboarding.sql b/models/intermediate/cross/int_new_dash_deal_onboarding.sql new file mode 100644 index 0000000..f9e3182 --- /dev/null +++ b/models/intermediate/cross/int_new_dash_deal_onboarding.sql @@ -0,0 +1,282 @@ +{{ config(materialized="table") }} +with + int_core__user_host as (select * from {{ ref("int_core__user_host") }}), + int_hubspot__deal as (select * from {{ ref("int_hubspot__deal") }}), + int_core__accommodation as (select * from {{ ref("int_core__accommodation") }}), + int_core__booking_summary as (select * from {{ ref("int_core__booking_summary") }}), + int_core__user_product_bundle_contains_services as ( + select * from {{ ref("int_core__user_product_bundle_contains_services") }} + ), + int_core__user_product_bundle as ( + select * from {{ ref("int_core__user_product_bundle") }} + ), + int_core__accommodation_to_product_bundle as ( + select * from {{ ref("int_core__accommodation_to_product_bundle") }} + ), + int_xero__sales_denom_mart as ( + select * from {{ ref("int_xero__sales_denom_mart") }} + ), + new_dash_new_business_accounts as ( + select + ihd.id_deal, + ihd.deal_name, + ihd.onboarding_owner, + ihd.account_manager, + ihd.live_date_utc, + ihd.contract_signed_date_utc, + ihd.cancellation_date_utc, + ihd.expressed_service_interest, + string_agg(icuh.company_name, ', ') as platform_company_name, + min( + icuh.user_in_new_dash_since_timestamp_at_utc + ) as backend_account_creation_utc, + count(distinct icuh.id_user_host) as count_platform_accounts + from int_core__user_host icuh + -- Deal needs to exist in HS + inner join int_hubspot__deal ihd on icuh.id_deal = ihd.id_deal + where + -- Filters to replicate New Dash Overview + icuh.is_user_in_new_dash = true + and icuh.is_missing_id_deal = false + and icuh.is_test_account = false + -- Filter to only select new business (i.e., not migrated from Old Dash) + and icuh.has_user_moved_from_old_dash = false + group by 1, 2, 3, 4, 5, 6, 7, 8 + ), + program_creation_per_deal as ( + select + id_deal, + min(icupb.created_at_utc) as first_program_created_at_utc, + min( + case + when icupb.has_upgraded_services then icupb.created_at_utc else null + end + ) as first_upgraded_program_created_at_utc, + count( + distinct icupb.id_user_product_bundle + ) as count_programs_at_deal_level, + count( + distinct case + when icupb.has_upgraded_services + then icupb.id_user_product_bundle + else null + end + ) as count_upgraded_programs_at_deal_level, + string_agg( + distinct service_name, '|' order by service_name asc + ) as services_in_programs_at_deal_level + from int_core__user_product_bundle icupb + inner join int_core__user_host icuh on icupb.id_user_host = icuh.id_user_host + left join + int_core__user_product_bundle_contains_services cs + on icupb.id_user_product_bundle = cs.id_user_product_bundle + where icuh.id_deal is not null + group by 1 + ), + listing_creation_per_deal as ( + select + id_deal, + min(ica.created_at_utc) as first_listing_created_at_utc, + count(distinct ica.id_accommodation) as count_listings, + count( + distinct + case when ica.is_active = true then ica.id_accommodation else null end + ) as count_active_listings + from int_core__accommodation ica + inner join int_core__user_host icuh on ica.id_user_host = icuh.id_user_host + where + -- Filters to replicate New Dash Overview + icuh.is_user_in_new_dash = true + and icuh.is_missing_id_deal = false + and icuh.is_test_account = false + -- Filter to only select new business (i.e., not migrated from Old Dash) + and icuh.has_user_moved_from_old_dash = false + group by 1 + ), + upgraded_program_applied_to_listing_per_deal as ( + select + id_deal, + min( + atpb.created_at_utc + ) as first_upgraded_program_applied_to_listing_at_utc, + count( + distinct atpb.id_user_product_bundle + ) as count_upgraded_programs_at_listing_level, + count( + distinct case + when atpb.has_no_end_date and ica.is_active + then atpb.id_user_product_bundle + else null + end + ) as count_active_upgraded_programs_at_active_listing_level, + count( + distinct atpb.id_accommodation + ) as count_listings_with_upgraded_programs, + count( + distinct case + when atpb.has_no_end_date and ica.is_active + then atpb.id_accommodation + else null + end + ) as count_active_listings_with_active_upgraded_programs, + string_agg( + distinct service_name, '|' order by service_name asc + ) as services_in_programs_applied_to_listings, + string_agg( + distinct case + when atpb.has_no_end_date and ica.is_active + then service_name + else null + end, + '|' + order by + case + when atpb.has_no_end_date and ica.is_active + then service_name + else null + end asc + ) as active_services_in_programs_applied_to_listings + from int_core__accommodation_to_product_bundle atpb + inner join int_core__user_host icuh on atpb.id_user_host = icuh.id_user_host + left join + int_core__user_product_bundle_contains_services cs + on atpb.id_user_product_bundle = cs.id_user_product_bundle + left join + int_core__accommodation ica on atpb.id_accommodation = ica.id_accommodation + where + icuh.id_deal is not null + -- Force that programs in listings have upgraded services + and atpb.has_upgraded_services = true + group by 1 + ), + booking_creation_per_deal as ( + select + id_deal, + min(bs.booking_created_at_utc) as first_booking_created_at_utc, + min( + case + when bs.has_paid_services then bs.booking_created_at_utc else null + end + ) as first_booking_with_paid_services_created_at_utc, + count(distinct bs.id_booking) as count_bookings, + count( + distinct case when bs.has_paid_services then bs.id_booking else null end + ) as count_bookings_with_paid_service + from int_core__booking_summary bs + where id_deal is not null + group by 1 + ), + invoice_per_deal as ( + select + id_deal, + min(document_issued_at_utc) at time zone 'utc' as first_invoice_at_utc + from int_xero__sales_denom_mart + where + -- Select only invoices + upper(document_class) = 'INVOICE' + and upper(document_status) in ('PAID', 'AUTHORISED') + and id_deal is not null + group by 1 + ), + combination_of_sources as ( + select + ndnba.id_deal, + ndnba.deal_name, + ndnba.onboarding_owner, + ndnba.account_manager, + ndnba.platform_company_name, + ndnba.count_platform_accounts, + -- Activity -- + coalesce(l.count_listings, 0) as count_listings, + coalesce(l.count_active_listings, 0) as count_active_listings, + coalesce( + upl.count_listings_with_upgraded_programs, 0 + ) as count_listings_with_upgraded_programs, + coalesce( + upl.count_active_listings_with_active_upgraded_programs, 0 + ) as count_active_listings_with_active_upgraded_programs, + coalesce(b.count_bookings, 0) as count_bookings, + coalesce( + b.count_bookings_with_paid_service, 0 + ) as count_bookings_with_paid_service, + -- Program funnel -- + coalesce(p.count_programs_at_deal_level, 0) as count_programs_at_deal_level, + coalesce( + p.count_upgraded_programs_at_deal_level, 0 + ) as count_upgraded_programs_at_deal_level, + coalesce( + upl.count_upgraded_programs_at_listing_level, 0 + ) as count_upgraded_programs_at_listing_level, + coalesce( + upl.count_active_upgraded_programs_at_active_listing_level, 0 + ) as count_active_upgraded_programs_at_active_listing_level, + -- HubSpot main dates -- + ndnba.contract_signed_date_utc, + ndnba.live_date_utc, + ndnba.cancellation_date_utc, + -- First time timestamps -- + ndnba.backend_account_creation_utc, + l.first_listing_created_at_utc, + b.first_booking_created_at_utc, + p.first_program_created_at_utc, + p.first_upgraded_program_created_at_utc, + upl.first_upgraded_program_applied_to_listing_at_utc, + b.first_booking_with_paid_services_created_at_utc, + inv.first_invoice_at_utc, + -- Services -- + ndnba.expressed_service_interest, + p.services_in_programs_at_deal_level, + upl.services_in_programs_applied_to_listings, + upl.active_services_in_programs_applied_to_listings, + -- Additional for filtering -- + case + when ndnba.cancellation_date_utc is not null then true else false + end as has_churned + from new_dash_new_business_accounts ndnba + left join listing_creation_per_deal l on ndnba.id_deal = l.id_deal + left join program_creation_per_deal p on ndnba.id_deal = p.id_deal + left join + upgraded_program_applied_to_listing_per_deal upl + on ndnba.id_deal = upl.id_deal + left join booking_creation_per_deal b on ndnba.id_deal = b.id_deal + left join invoice_per_deal inv on ndnba.id_deal = inv.id_deal + ) +select + *, + -- Basic Alerts -- + case + when first_listing_created_at_utc is null and count_listings = 0 + then false + else true + end as has_listings, + case when count_active_listings = 0 then false else true end as has_active_listings, + case + when first_booking_created_at_utc is null and count_bookings = 0 + then false + else true + end as has_bookings, + case + when first_invoice_at_utc is null then false else true + end as has_been_invoiced, + + -- Advanced Alerts -- + case + when count_bookings > 0 and count_bookings_with_paid_service = 0 + then true + else false + end as are_all_bookings_free, + case + when + count_bookings_with_paid_service > 0 + and count_active_upgraded_programs_at_active_listing_level = 0 + then true + else false + end as is_account_no_longer_generating_paid_bookings, + case + when + active_services_in_programs_applied_to_listings + <> services_in_programs_applied_to_listings + then true + else false + end as has_account_changed_services_applied_in_listings +from combination_of_sources diff --git a/models/intermediate/cross/schema.yml b/models/intermediate/cross/schema.yml index f82940f..012314d 100644 --- a/models/intermediate/cross/schema.yml +++ b/models/intermediate/cross/schema.yml @@ -3695,3 +3695,239 @@ models: description: | Flag indicating if the growth score is overridden to -1 due to cancellation in the same month. + + - name: int_new_dash_deal_onboarding + description: | + A dedicated model to track the onboarding stages of new accounts (deals) + in New Dash. + This excludes any deal that has been migrated from Old Dash, so it just + contains "new business". + + columns: + - name: id_deal + data_type: text + description: | + Unique identifier of an account. + data_tests: + - not_null + - unique + + - name: deal_name + data_type: text + description: | + Name of the deal according to HubSpot. + + - name: onboarding_owner + data_type: text + description: | + Name of the person that is in charge of onboarding this account. + + - name: account_manager + data_type: text + description: | + Account manager in charge of the account. + + - name: platform_company_name + data_type: text + description: | + Name of the company in Truvi's backend. + + - name: count_platform_accounts + data_type: integer + description: | + Amount of Backend accounts (users) linked to this Deal. + + - name: count_programs_at_deal_level + data_type: integer + description: | + Total amount of programs that this account has. These might not + necessarily be applied to a Listing. + + - name: count_listings + data_type: integer + description: | + Total count of Listings from this account. + + - name: count_active_listings + data_type: integer + description: | + Count of Listings that are currently active, meaning, that have not + been deactivated. + + - name: count_listings_with_upgraded_programs + data_type: integer + description: | + Count of Listings that have had a program applied that contains at least + one service different to Basic Screening. + + - name: count_active_listings_with_active_upgraded_programs + data_type: integer + description: | + Count of Listings that are currently active and that currently have an + active upgraded program, meaning, that contains at least one service + different to Basic Screening. + + - name: count_bookings + data_type: integer + description: | + Total count of Bookings generated from this account. + + - name: count_bookings_with_paid_service + data_type: integer + description: | + Count of Bookings that have at least one paid service. + + - name: count_upgraded_programs_at_deal_level + data_type: integer + description: | + Count of programs that this account has that contain, at least, one + service different to Basic Screening. These might not necessarily + be applied to a Listing. + + - name: count_upgraded_programs_at_listing_level + data_type: integer + description: | + Count of programs that contain at least one service different to + Basic Screening that have been applied to a Listing. + + - name: count_active_upgraded_programs_at_active_listing_level + data_type: integer + description: | + Count of programs that contain at least one service different to + Basic Screening that are currently active and applied to an + active Listing. + + - name: contract_signed_date_utc + data_type: date + description: | + Date in which the contract was signed according to HubSpot. + + - name: live_date_utc + data_type: date + description: | + Date in which the Deal went live according to HubSpot. + + - name: cancellation_date_utc + data_type: date + description: | + Date in which the Deal was cancelled according to HubSpot. + + - name: backend_account_creation_utc + data_type: timestamp without timezone + description: | + Timestamp in which the account was created in the backend. + + - name: first_listing_created_at_utc + data_type: timestamp without timezone + description: | + Timestamp in which the first listing was created in the backend. + + - name: first_booking_created_at_utc + data_type: timestamp without timezone + description: | + Timestamp in which the first booking was created in the backend. + + - name: first_program_created_at_utc + data_type: timestamp without timezone + description: | + Timestamp in which the first program was created at account level + in the backend. + + - name: first_upgraded_program_created_at_utc + data_type: timestamp without timezone + description: | + Timestamp in which the first program was applied to a listing + for this account, in the backend. + + - name: first_upgraded_program_applied_to_listing_at_utc + data_type: timestamp without timezone + description: | + Timestamp in which the first upgraded program was applied to a listing + for this account, in the backend. + + - name: first_booking_with_paid_services_created_at_utc + data_type: timestamp without timezone + description: | + Timestamp in which the first booking that contained paid services + was created for this account, in the backend. + + - name: first_invoice_at_utc + data_type: timestamp without timezone + description: | + Timestamp in which the first invoice happened for this account in Xero. + + - name: expressed_service_interest + data_type: text + description: | + List of services that during onboarding generated interest + to the client. + + - name: services_in_programs_at_deal_level + data_type: text + description: | + List of all distinct services that appear in programs at + deal level, separated by "|" and ordered alphabetically. + + - name: services_in_programs_applied_to_listings + data_type: text + description: | + List of all distinct services that are applied in all listings + for this account, separated by "|" and ordered alphabetically. + + - name: has_churned + data_type: boolean + description: | + True if the account has a cancellation date in HubSpot, + False otherwise. + + - name: has_listings + data_type: boolean + description: | + True if the account has at least one listing appearing in + the backend, False otherwise. + + - name: has_active_listings + data_type: boolean + description: | + True if the account has at least one listing that is currently + active in the backend, False otherwise. + + - name: has_bookings + data_type: boolean + description: | + True if the account has at least one booking appearing in + the backend, False otherwise. + + - name: has_been_invoiced + data_type: boolean + description: | + True if the account has at least one invoice appearing in + Xero, False otherwise. + + - name: are_all_bookings_free + data_type: boolean + description: | + True if the account has bookings but all of them are free, + meaning, there's not a single service being paid. + + - name: is_account_no_longer_generating_paid_bookings + data_type: boolean + description: | + True if the account has had in the past paid bookings but + at the moment there's not a single listing that contains + an active upgraded program. + Encouraged to be used alongside a filter to determine if + the account has churned or not. + + - name: has_account_changed_services_applied_in_listings + data_type: boolean + description: | + True if the active services in programs applied to listings + is different than the services that were historically applied. + By nature, this always means that at least one service was + being applied before that is no longer being applied. This can + indicate a real decrease in services applied that could be linked to + a potential revenue loss (decrease in booking fees), + although it's also possible that the account has changed from a + certain low-level tier to a higher-level one (ex: from Basic + Protection to Protection Pro).