From 08d32bb7810264d45d2298e5adc488acdee4fb42 Mon Sep 17 00:00:00 2001 From: Ben Shi Date: Tue, 23 Jun 2026 15:52:11 -0400 Subject: [PATCH 1/3] feat: add sem_prospect_funnel.sql (dbt-managed Snowflake semantic view) --- .../semantic_views/sem_prospect_funnel.sql | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 yardi_demo/models/semantic_views/sem_prospect_funnel.sql diff --git a/yardi_demo/models/semantic_views/sem_prospect_funnel.sql b/yardi_demo/models/semantic_views/sem_prospect_funnel.sql new file mode 100644 index 0000000..2177296 --- /dev/null +++ b/yardi_demo/models/semantic_views/sem_prospect_funnel.sql @@ -0,0 +1,33 @@ +{{ + config( + materialized = 'table', + schema = 'marts', + tags = ['semantic_view'], + post_hook = [ "{{ create_prospect_semantic_view() }}" ] + ) +}} + +{#- + sem_prospect_funnel (control model) + ==================================== + dbt does NOT natively materialize a Snowflake SEMANTIC VIEW, so this model + is the version-controlled control point for the native object: + + - This SELECT builds a tiny one-row "anchor" table whose ONLY job is to + (a) create a dependency on fct_prospects via ref(), so dbt schedules + the semantic view AFTER the fact table is built, and + (b) give the post_hook a model to attach to. + - The post_hook calls the create_prospect_semantic_view() macro, which + runs CREATE OR REPLACE SEMANTIC VIEW ... over fct_prospects. + + Net effect: `dbt run --select sem_prospect_funnel` (or a full run) creates + / refreshes the native Snowflake SEMANTIC VIEW + YARDI.MARTS.SEM_PROSPECT_FUNNEL, with the DDL fully controlled from this + dbt repo. Cortex Analyst / SHOW SEMANTIC VIEWS then see the object. +-#} + +select + '{{ ref('fct_prospects') }}' as built_over, + count(*) as prospect_rows, + current_timestamp() as _dbt_built_at +from {{ ref('fct_prospects') }} From 92e1cf5a2ef9d0e1093ad4f79e54d1bc72864eca Mon Sep 17 00:00:00 2001 From: Ben Shi Date: Tue, 23 Jun 2026 15:52:13 -0400 Subject: [PATCH 2/3] feat: add _sem_prospect_funnel.yml (dbt-managed Snowflake semantic view) --- .../semantic_views/_sem_prospect_funnel.yml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 yardi_demo/models/semantic_views/_sem_prospect_funnel.yml diff --git a/yardi_demo/models/semantic_views/_sem_prospect_funnel.yml b/yardi_demo/models/semantic_views/_sem_prospect_funnel.yml new file mode 100644 index 0000000..e8cfc85 --- /dev/null +++ b/yardi_demo/models/semantic_views/_sem_prospect_funnel.yml @@ -0,0 +1,23 @@ +version: 2 + +models: + - name: sem_prospect_funnel + description: > + Control model for the native Snowflake SEMANTIC VIEW + YARDI.MARTS.SEM_PROSPECT_FUNNEL. This model itself is a tiny one-row + anchor table; its post_hook runs create_prospect_semantic_view(), which + issues CREATE OR REPLACE SEMANTIC VIEW over fct_prospects. The ref() + on fct_prospects guarantees dbt builds the fact first. + + The semantic view exposes governed leasing-funnel metrics + (total_prospects, tour_rate, application_rate, approval_rate, + lead_to_lease_conversion, avg_days_to_apply, avg_days_to_movein, + avg_desired_rent) to Snowflake Cortex Analyst and any + SEMANTIC_VIEW(...) query. + columns: + - name: built_over + description: "Fully-qualified fact table the semantic view is built on" + - name: prospect_rows + description: "Row count of fct_prospects at build time (sanity check)" + tests: + - not_null From 47ca74115dd07577fcd853aa5293665e88840721 Mon Sep 17 00:00:00 2001 From: Ben Shi Date: Tue, 23 Jun 2026 15:52:14 -0400 Subject: [PATCH 3/3] feat: add create_prospect_semantic_view.sql (dbt-managed Snowflake semantic view) --- .../macros/create_prospect_semantic_view.sql | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 yardi_demo/macros/create_prospect_semantic_view.sql diff --git a/yardi_demo/macros/create_prospect_semantic_view.sql b/yardi_demo/macros/create_prospect_semantic_view.sql new file mode 100644 index 0000000..8f6efeb --- /dev/null +++ b/yardi_demo/macros/create_prospect_semantic_view.sql @@ -0,0 +1,75 @@ +{% macro create_prospect_semantic_view() %} +{#- + create_prospect_semantic_view + ============================= + Issues the native Snowflake CREATE OR REPLACE SEMANTIC VIEW DDL over + fct_prospects. Invoked as a post_hook on the sem_prospect_funnel control + model so the object is (re)created on every dbt run, AFTER fct_prospects + is built. The DDL lives here so it is fully version-controlled in the dbt + repo rather than applied manually in Snowflake. + + Target object: {database}.MARTS.SEM_PROSPECT_FUNNEL + Grain of base table: one row per prospect (guest card). +-#} + +{% set fq_fact = ref('fct_prospects') %} +{% set view_name = (target.database ~ '.MARTS.SEM_PROSPECT_FUNNEL') %} + +create or replace semantic view {{ view_name }} + + tables ( + prospects as {{ fq_fact }} + primary key (prospect_id) + with synonyms ('leads', 'guest cards', 'leasing funnel') + comment = 'Leasing-funnel guest cards, one row per prospect' + ) + + facts ( + prospects.toured as (case when reached_tour then 1 else 0 end), + prospects.applied as (case when reached_application then 1 else 0 end), + prospects.approved as (case when reached_approval then 1 else 0 end), + prospects.leased as (case when reached_lease then 1 else 0 end), + prospects.days_to_apply_fact as days_to_apply, + prospects.days_to_movein_fact as days_to_movein, + prospects.desired_rent_fact as desired_rent + ) + + dimensions ( + prospects.prospect_status as prospect_status + with synonyms ('funnel stage','stage') + comment = 'new/contacted/toured/applied/approved/denied/waitlist/leased', + prospects.lead_source as lead_source + with synonyms ('marketing channel','source'), + prospects.desired_bedrooms as desired_bedrooms, + prospects.leasing_agent as leasing_agent, + prospects.property_name as property_name, + prospects.property_region as property_region with synonyms ('region'), + prospects.property_fund as property_fund with synonyms ('fund'), + prospects.contact_date as contact_date, + prospects.contact_month as contact_month + ) + + metrics ( + prospects.total_prospects AS COUNT(prospects.prospect_id) + comment = 'Count of prospects (leads)', + prospects.leased_prospects AS SUM(prospects.leased) + comment = 'Count of converted prospects', + prospects.tour_rate AS SUM(prospects.toured) / NULLIF(COUNT(prospects.prospect_id), 0) + comment = 'Share of prospects that toured', + prospects.application_rate AS SUM(prospects.applied) / NULLIF(COUNT(prospects.prospect_id), 0) + comment = 'Share of prospects that applied', + prospects.approval_rate AS SUM(prospects.approved) / NULLIF(SUM(prospects.applied), 0) + comment = 'Approved share of applicants', + prospects.lead_to_lease_conversion AS SUM(prospects.leased) / NULLIF(COUNT(prospects.prospect_id), 0) + comment = 'Overall lead-to-lease conversion', + prospects.avg_days_to_apply AS SUM(prospects.days_to_apply_fact) / NULLIF(SUM(prospects.applied), 0) + comment = 'Average days from contact to application', + prospects.avg_days_to_movein AS SUM(prospects.days_to_movein_fact) / NULLIF(SUM(prospects.leased), 0) + comment = 'Average days from application to move-in', + prospects.avg_desired_rent AS AVG(prospects.desired_rent_fact) + comment = 'Average desired monthly rent' + ) + + comment = 'Prospect leasing funnel semantic view (dbt-managed; built over fct_prospects)' + +{% endmacro %}