From cf4c1fd5e7889bf237206e24d7d10a675b6285c0 Mon Sep 17 00:00:00 2001 From: Charlie Wang <2144018+kingman@users.noreply.github.com> Date: Wed, 11 Sep 2024 22:30:12 +0200 Subject: [PATCH 001/110] specify dashboard dependent tables (#188) * specify dashboard dependent tables * remove project-id placeholder --- python/lookerstudio/README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/python/lookerstudio/README.md b/python/lookerstudio/README.md index dc019f63..aa624b3f 100644 --- a/python/lookerstudio/README.md +++ b/python/lookerstudio/README.md @@ -1,5 +1,30 @@ # Marketing Analytics Jumpstart Looker Studio Dashboard +## Prerequisites +This Looker Studio dashboard relies on specific BigQuery tables that should be present in your project. These tables are created during the deployment of the Marketing Analytics Jumpstart and by the data processing pipelines of the solution. +Before deploying the dashboard, make sure the pre-requisite tables exist. If tables are missing, ensure the corresponding pipelines have run successfully. + +| Table | Dataset | Source Process | Troubleshooting Link | +| -------- | ------- | ------- | --------- | +| session_date | marketing_ga4_v1_* | Dataform Execution| [Workflow Execution Logs](https://console.cloud.google.com/bigquery/dataform/locations/us-central1/repositories/marketing-analytics/details/workflows) | +| session_device_daily_metrics | marketing_ga4_v1_* | Dataform Execution| [Workflow Execution Logs](https://console.cloud.google.com/bigquery/dataform/locations/us-central1/repositories/marketing-analytics/details/workflows) | +| latest | aggregated_predictions | feature-store terraform module and aggregated_predictions.aggregate_last_day_predictions stored procedure | [Aggregating stored prodedure](https://console.cloud.google.com/bigquery?ws=!1m5!1m4!6m3!1s!2saggregated_predictions!3saggregate_last_day_predictions) | +| resource_link | maj_dashboard | monitor terraform module | [Dashboard dataset](https://console.cloud.google.com/bigquery?ws=!1m4!1m3!3m2!1s!2smaj_dashboard) | +| dataform_googleapis_com_workflow_invocation_completion | maj_logs | monitor terraform module | [maj_logs dataset](https://console.cloud.google.com/bigquery?ws=!1m4!1m3!3m2!1s!2smaj_logs) | +| event | marketing_ga4_base_* | Dataform Execution | [Workflow Execution Logs](https://console.cloud.google.com/bigquery/dataform/locations/us-central1/repositories/marketing-analytics/details/workflows) | +| session_location_daily_metrics | marketing_ga4_v1_* | Dataform Execution | [Workflow Execution Logs](https://console.cloud.google.com/bigquery/dataform/locations/us-central1/repositories/marketing-analytics/details/workflows) | +| aggregated_value_based_bidding_volume_weekly | aggregated_vbb | feature-store terraform module and aggregated_vbb.invoke_aggregated_value_based_bidding_explanation_preparation stored procedure | [aggregated_value_based_bidding_explanation_preparation](https://console.cloud.google.com/bigquery?ws=!1m5!1m4!6m3!1s!2saggregated_vbb!3sinvoke_aggregated_value_based_bidding_explanation_preparation) | +| event_page | marketing_ga4_v1_* | Dataform Execution| [Workflow Execution Logs](https://console.cloud.google.com/bigquery/dataform/locations/us-central1/repositories/marketing-analytics/details/workflows) | +| unique_page_views | marketing_ga4_v1_* | Dataform Execution| [Workflow Execution Logs](https://console.cloud.google.com/bigquery/dataform/locations/us-central1/repositories/marketing-analytics/details/workflows) | +| aggregated_value_based_bidding_correlation | aggregated_vbb | feature-store terraform module and aggregated_vbb.invoke_aggregated_value_based_bidding_explanation_preparation stored procedure | [aggregated_value_based_bidding_explanation_preparation](https://console.cloud.google.com/bigquery?ws=!1m5!1m4!6m3!1s!2saggregated_vbb!3sinvoke_aggregated_value_based_bidding_explanation_preparation) | +| ad_performance_conversions | marketing_ads_v1_* | Dataform Execution | [Workflow Execution Logs](https://console.cloud.google.com/bigquery/dataform/locations/us-central1/repositories/marketing-analytics/details/workflows) | +| user_behaviour_revenue_insights_daily | gemini_insights | feature-store terraform module and gemini_insights.user_behaviour_revenue_insights stored procedure | [User Behaviour Revenue Insights](https://console.cloud.google.com/bigquery?ws=!1m5!1m4!6m3!1s!2sgemini_insights!3suser_behaviour_revenue_insights) | +| dataflow_googleapis_com_job_message | maj_logs | monitor terraform module | [maj_logs dataset](https://console.cloud.google.com/bigquery?ws=!1m4!1m3!3m2!1s!2smaj_logs) | +| vbb_weights | aggregated_vbb | feature-store terraform module and VBB explanation pipeline | [VBB Explanation Pipeline](https://console.cloud.google.com/vertex-ai/pipelines/schedules) | +| page_session_daily_metrics | marketing_ga4_v1_* | Dataform Execution| [Workflow Execution Logs](https://console.cloud.google.com/bigquery/dataform/locations/us-central1/repositories/marketing-analytics/details/workflows) | +| aiplatform_googleapis_com_pipeline_job_events | maj_logs | monitor terraform module | [maj_logs dataset](https://console.cloud.google.com/bigquery?ws=!1m4!1m3!3m2!1s!2smaj_logs) | +| aggregated_value_based_bidding_volume_daily | aggregated_vbb | feature-store terraform module and aggregated_vbb.invoke_aggregated_value_based_bidding_explanation_preparation stored procedure | [aggregated_value_based_bidding_explanation_preparation](https://console.cloud.google.com/bigquery?ws=!1m5!1m4!6m3!1s!2saggregated_vbb!3sinvoke_aggregated_value_based_bidding_explanation_preparation) | + ## Extract Looker Studio dashboard URL Extract the URL used to create the dashboard from the Terraform output value: From ee726964340a6794ae20a39183eccf49f48504d9 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 20 Sep 2024 14:26:04 -0400 Subject: [PATCH 002/110] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c5c65457..f010736d 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,8 @@ This a list of public websites you can use to learn more about the Google Analyt | Websites | Description | |----------|-------------| +| [github.com/GoogleCloudPlatform/marketing-analytics-jumpstart-dataform](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart-dataform) | Marketing Analytics Jumpstart Dataform Github Repository | +| [console.cloud.google.com/marketplace/product/bigquery-data-connectors/google_ads](https://console.cloud.google.com/marketplace/product/bigquery-data-connectors/google_ads) | BigQuery Data Transfer Service for Google Ads | | [support.google.com/google-ads/*](https://support.google.com/google-ads/) [support.google.com/analytics/*](https://support.google.com/analytics/) | Google Ads and Google Analytics Support | | [support.google.com/looker-studio/*](https://support.google.com/looker-studio/) | Looker Studio Support | | [developers.google.com/analytics/*](https://developers.google.com/analytics/) [developers.google.com/google-ads/*](https://developers.google.com/analytics/) | Google Ads and Google Analytics Developers Guides | From 176e621264cea47c11c33e3f04dc27fecdc87d2c Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 15:37:31 -0400 Subject: [PATCH 003/110] Update README.md --- infrastructure/terraform/README.md | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index 793f9419..c823b588 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -221,30 +221,30 @@ Now that you have deployed all assets successfully in your Google Cloud Project, First, you need to choose what kind of insight you are looking for to define the campaigns. Here are a few insights provided by each one of the use cases already provided to you: -- **Aggregated Value Based Bidding**: Attributes a numerical value to high value conversion events (user action) in relation to a target conversion event (typically purchase) so that Google Ads can improve the bidding strategy for users that reached these conversion events, as of now. -- **Demographic Audience Segmentation**: Attributes a cluster segment to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX days back. -- **Interest based Audience Segmentation**: Attributes a cluster segment to an user using pages navigations data looking XX days back, as of now. -- **Purchase Propensity**: Predicts a purchase propensity decile and a propensity score (likelihood between 0.0 - 0% and 1.0 - 100%) to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX days back to predict XX days ahead, as of now. -- **Customer Lifetime Value**: Predicts a lifetime value gain decile and a lifetime value revenue gain in USD (equal of bigger than 0.0) to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX-XXX days back to predict XX days ahead, as of now. -- **Churn Propensity**: Predicts a churn propensity decile and a propensity score (likelihood between 0.0 - 0% and 1.0 - 100%) to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX days back to predict XX days ahead, as of now. +- **Aggregated Value Based Bidding ([value_based_bidding](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L514))**: Attributes a numerical value to high value conversion events (user action) in relation to a target conversion event (typically purchase) so that Google Ads can improve the bidding strategy for users that reached these conversion events, as of now. +- **Demographic Audience Segmentation ([audience_segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L929))**: Attributes a cluster segment to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX days back. +- **Interest based Audience Segmentation ([auto_audience_segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1018))**: Attributes a cluster segment to an user using pages navigations data looking XX days back, as of now. +- **Purchase Propensity ([purchase_propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L629))**: Predicts a purchase propensity decile and a propensity score (likelihood between 0.0 - 0% and 1.0 - 100%) to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX days back to predict XX days ahead, as of now. +- **Customer Lifetime Value ([customer_ltv](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1215))**: Predicts a lifetime value gain decile and a lifetime value revenue gain in USD (equal of bigger than 0.0) to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX-XXX days back to predict XX days ahead, as of now. +- **Churn Propensity ([churn_propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L779))**: Predicts a churn propensity decile and a propensity score (likelihood between 0.0 - 0% and 1.0 - 100%) to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX days back to predict XX days ahead, as of now. Second, you need to measure how much data you are going to use to obtain the insights you need. Each one of the use cases above requires data in the following intervals, using as key metrics number of days and unique user events. -- **Aggregated Value Based Bidding**: Minimum 30 days and maximum 1 year. The number of unique user events is not a key limitation. Note that you need at least 1000 training examples for the model to be trained successfully, to accomplish that we typically duplicate the rows until we have a minimum of 1000 rows in the training table for the "TRAIN" subset. -- **Demographic Audience Segmentation**: Minimum 30 days and maximum 1 year. Minimum of 1000 unique user events per day. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). -- **Interest based Audience Segmentation**: Minimum 30 days and maximum 1 year. Minimum of 1000 unique user events per day. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). -- **Purchase Propensity**: Minimum 90 days and maximum 2 years. Minimum of 1000 unique user events per day, of which a minimum of 1 target event per week. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). -- **Customer Lifetime Value**: Minimum 180 days and maximum 5 years. Minimum of 1000 unique user events per day, of which a minimum of 1 event per week that increases the lifetime value for an user. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). -- **Churn Propensity**: Minimum 30 days and maximum 2 years. Minimum of 1000 unique user events per day, of which a minimum of 1 target event per week. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). +- **Aggregated Value Based Bidding ([value_based_bidding](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1734))**: Minimum 30 days and maximum 1 year. The number of unique user events is not a key limitation. Note that you need at least 1000 training examples for the model to be trained successfully, to accomplish that we typically duplicate the rows until we have a minimum of 1000 rows in the training table for the "TRAIN" subset. +- **Demographic Audience Segmentation ([audience_segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1779))**: Minimum 30 days and maximum 1 year. Minimum of 1000 unique user events per day. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). +- **Interest based Audience Segmentation ([auto_audience_segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1817))**: Minimum 30 days and maximum 1 year. Minimum of 1000 unique user events per day. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). +- **Purchase Propensity ([purchase_propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1739))**: Minimum 90 days and maximum 2 years. Minimum of 1000 unique user events per day, of which a minimum of 1 target event per week. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). +- **Customer Lifetime Value ([customer_lifetime_value](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1798))**: Minimum 180 days and maximum 5 years. Minimum of 1000 unique user events per day, of which a minimum of 1 event per week that increases the lifetime value for an user. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). +- **Churn Propensity ([churn_propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1758))**: Minimum 30 days and maximum 2 years. Minimum of 1000 unique user events per day, of which a minimum of 1 target event per week. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). Third, the data must be processed by the Marketing Data Store; features must be prepared using the Feature Engineering procedure; and the training and inference pipelines must be triggered. For that, open your `config.yaml.tftpl` configuration file and check the `{pipeline-name}.execution.schedule` block to modify the scheduled time for each pipeline you gonna need to orchestrate that enables your use case. Here is a list of pipelines you need for every use case. -- **Aggregated Value Based Bidding**: `feature-creation-aggregated-value-based-bidding`, `value_based_bidding.training`, `value_based_bidding.explanation` -- **Demographic Audience Segmentation**: `feature-creation-audience-segmentation`, `segmentation.training`, `segmentation.prediction` -- **Interest based Audience Segmentation**: `feature-creation-auto-audience-segmentation`, `auto_segmentation.training`, `auto_segmentation.prediction` -- **Purchase Propensity**: `feature-creation-purchase-propensity`, `purchase_propensity.training`, `purchase_propensity.prediction` -- **Customer Lifetime Value**: `feature-creation-customer-ltv`, `propensity_clv.training`, `clv.training`, `clv.prediction` -- **Churn Propensity**: `feature-creation-churn-propensity`, `churn_propensity.training`, `churn_propensity.prediction` +- **Aggregated Value Based Bidding**: [feature-creation-aggregated-value-based-bidding](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L473), (value_based_bidding.training)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L515], (value_based_bidding.explanation)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L591] +- **Demographic Audience Segmentation**: (feature-creation-audience-segmentation)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L248], (segmentation.training)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L930], (segmentation.prediction)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L973] +- **Interest based Audience Segmentation**: (feature-creation-auto-audience-segmentation)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L170], [auto_segmentation.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1019), (auto_segmentation.prediction)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1061] +- **Purchase Propensity**: (feature-creation-purchase-propensity)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L315], (purchase_propensity.training)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L630], (purchase_propensity.prediction)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L725] +- **Customer Lifetime Value**: (feature-creation-customer-ltv)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L419], (propensity_clv.training)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1110], (clv.training)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1221], (clv.prediction)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1309] +- **Churn Propensity**: (feature-creation-churn-propensity)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L370], (churn_propensity.training)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L780], (churn_propensity.prediction)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L875] After you change these configurations, make sure you apply these changes in your deployed resources by re-running terraform. From 50044567523865f9b92cadda4282e7273084d3dc Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 15:40:54 -0400 Subject: [PATCH 004/110] Update README.md --- infrastructure/terraform/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index c823b588..936eb402 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -239,12 +239,12 @@ Second, you need to measure how much data you are going to use to obtain the ins Third, the data must be processed by the Marketing Data Store; features must be prepared using the Feature Engineering procedure; and the training and inference pipelines must be triggered. For that, open your `config.yaml.tftpl` configuration file and check the `{pipeline-name}.execution.schedule` block to modify the scheduled time for each pipeline you gonna need to orchestrate that enables your use case. Here is a list of pipelines you need for every use case. -- **Aggregated Value Based Bidding**: [feature-creation-aggregated-value-based-bidding](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L473), (value_based_bidding.training)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L515], (value_based_bidding.explanation)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L591] -- **Demographic Audience Segmentation**: (feature-creation-audience-segmentation)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L248], (segmentation.training)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L930], (segmentation.prediction)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L973] -- **Interest based Audience Segmentation**: (feature-creation-auto-audience-segmentation)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L170], [auto_segmentation.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1019), (auto_segmentation.prediction)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1061] -- **Purchase Propensity**: (feature-creation-purchase-propensity)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L315], (purchase_propensity.training)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L630], (purchase_propensity.prediction)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L725] -- **Customer Lifetime Value**: (feature-creation-customer-ltv)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L419], (propensity_clv.training)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1110], (clv.training)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1221], (clv.prediction)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1309] -- **Churn Propensity**: (feature-creation-churn-propensity)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L370], (churn_propensity.training)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L780], (churn_propensity.prediction)[https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L875] +- **Aggregated Value Based Bidding**: [feature-creation-aggregated-value-based-bidding](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L473), [value_based_bidding.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L515), [value_based_bidding.explanation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L591) +- **Demographic Audience Segmentation**: [feature-creation-audience-segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L248), [segmentation.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L930), [segmentation.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L973) +- **Interest based Audience Segmentation**: [feature-creation-auto-audience-segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L170), [auto_segmentation.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1019), [auto_segmentation.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1061) +- **Purchase Propensity**: [feature-creation-purchase-propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L315), [purchase_propensity.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L630), [purchase_propensity.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L725) +- **Customer Lifetime Value**: [feature-creation-customer-ltv](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L419), [propensity_clv.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1110), [clv.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1221), [clv.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1309) +- **Churn Propensity**: [feature-creation-churn-propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L370), [churn_propensity.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L780), [churn_propensity.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L875) After you change these configurations, make sure you apply these changes in your deployed resources by re-running terraform. From da5e108b92977157e9f29e6bb091646b7e71fb72 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 15:44:51 -0400 Subject: [PATCH 005/110] Update README.md --- infrastructure/terraform/README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index 936eb402..0b76437e 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -239,12 +239,14 @@ Second, you need to measure how much data you are going to use to obtain the ins Third, the data must be processed by the Marketing Data Store; features must be prepared using the Feature Engineering procedure; and the training and inference pipelines must be triggered. For that, open your `config.yaml.tftpl` configuration file and check the `{pipeline-name}.execution.schedule` block to modify the scheduled time for each pipeline you gonna need to orchestrate that enables your use case. Here is a list of pipelines you need for every use case. -- **Aggregated Value Based Bidding**: [feature-creation-aggregated-value-based-bidding](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L473), [value_based_bidding.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L515), [value_based_bidding.explanation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L591) -- **Demographic Audience Segmentation**: [feature-creation-audience-segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L248), [segmentation.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L930), [segmentation.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L973) -- **Interest based Audience Segmentation**: [feature-creation-auto-audience-segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L170), [auto_segmentation.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1019), [auto_segmentation.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1061) -- **Purchase Propensity**: [feature-creation-purchase-propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L315), [purchase_propensity.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L630), [purchase_propensity.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L725) -- **Customer Lifetime Value**: [feature-creation-customer-ltv](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L419), [propensity_clv.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1110), [clv.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1221), [clv.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1309) -- **Churn Propensity**: [feature-creation-churn-propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L370), [churn_propensity.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L780), [churn_propensity.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L875) +| Use Case | Pipeline Configuration | +| -------- | ---------------------- | +| **Aggregated Value Based Bidding** | [feature-creation-aggregated-value-based-bidding](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L473)
[value_based_bidding.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L515)
[value_based_bidding.explanation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L591) | +| **Demographic Audience Segmentation** | [feature-creation-audience-segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L248)
[segmentation.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L930)
[segmentation.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L973) | +| **Interest based Audience Segmentation** | [feature-creation-auto-audience-segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L170)
[auto_segmentation.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1019)
[auto_segmentation.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1061) | +| **Purchase Propensity** | [feature-creation-purchase-propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L315)
[purchase_propensity.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L630)
[purchase_propensity.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L725) | +| **Customer Lifetime Value** | [feature-creation-customer-ltv](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L419)
[propensity_clv.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1110)
[clv.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1221)
[clv.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1309) | +| **Churn Propensity** | [feature-creation-churn-propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L370)
[churn_propensity.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L780)
[churn_propensity.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L875) | After you change these configurations, make sure you apply these changes in your deployed resources by re-running terraform. From bd7efee6c1bc31f0584c7c815a83e228f98f863f Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 15:45:58 -0400 Subject: [PATCH 006/110] Update README.md --- infrastructure/terraform/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index 0b76437e..bab5b61f 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -237,7 +237,7 @@ Second, you need to measure how much data you are going to use to obtain the ins - **Customer Lifetime Value ([customer_lifetime_value](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1798))**: Minimum 180 days and maximum 5 years. Minimum of 1000 unique user events per day, of which a minimum of 1 event per week that increases the lifetime value for an user. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). - **Churn Propensity ([churn_propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1758))**: Minimum 30 days and maximum 2 years. Minimum of 1000 unique user events per day, of which a minimum of 1 target event per week. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). -Third, the data must be processed by the Marketing Data Store; features must be prepared using the Feature Engineering procedure; and the training and inference pipelines must be triggered. For that, open your `config.yaml.tftpl` configuration file and check the `{pipeline-name}.execution.schedule` block to modify the scheduled time for each pipeline you gonna need to orchestrate that enables your use case. Here is a list of pipelines you need for every use case. +Third, the data must be processed by the Marketing Data Store; features must be prepared using the Feature Engineering procedure; and the training and inference pipelines must be triggered. For that, open your `config.yaml.tftpl` configuration file and check the `{pipeline-name}.execution.schedule` block to modify the scheduled time for each pipeline you are going to need to orchestrate that enables your use case. Here is a table of pipelines configuration you need to enable for every use case. | Use Case | Pipeline Configuration | | -------- | ---------------------- | From c961b667ee12801ed01b2aae141f170f38d90810 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 21:27:18 -0400 Subject: [PATCH 007/110] Update README.md --- infrastructure/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/infrastructure/README.md b/infrastructure/README.md index 07ec98cc..889374e8 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -5,6 +5,10 @@ Marketing Analytics Jumpstart consists of several components - marketing data store (MDS), feature store, ML pipelines, the activation pipeline and dashboards. This document describes the sequencing of installing these components. +To facilitate the installation, use this Step by Step Installation Video. + +[![Step by Step Installation Video](https://i.sstatic.net/q3ceS.png)](https://youtu.be/JMnsIxTNbE4 "Marketing Analytics Jumpstart Installation Video") + ## Prerequisites ### Marketing Analytics Data Sources From f8d8ac6264bdf2f3be95febe0ed85ca9053be38d Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 21:27:58 -0400 Subject: [PATCH 008/110] Add files via upload --- docs/images/YoutubeScreenshot.png | Bin 0 -> 466777 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/images/YoutubeScreenshot.png diff --git a/docs/images/YoutubeScreenshot.png b/docs/images/YoutubeScreenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..90f211a81183ac8ebfa09865c73a42a37d60cddc GIT binary patch literal 466777 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 z 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%0D)-_>EP~rKhMJ&#&y;i8hdaq1GIQ~ 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

f0X4(W{BJ8GLT2lk{cyDPQT`Q(^k}GT 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 %)XKZ|;O!jWQ?*&5A*i)b5x6aRwFvg2 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�z1> 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 HFFyP)UgE+T literal 0 HcmV?d00001 From 2be01c2e74bdaa68ebad7ddd9df5ea4d46ef601d Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 21:29:00 -0400 Subject: [PATCH 009/110] Update README.md --- infrastructure/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/README.md b/infrastructure/README.md index 889374e8..30bd22d0 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -7,7 +7,7 @@ the activation pipeline and dashboards. This document describes the sequencing o To facilitate the installation, use this Step by Step Installation Video. -[![Step by Step Installation Video](https://i.sstatic.net/q3ceS.png)](https://youtu.be/JMnsIxTNbE4 "Marketing Analytics Jumpstart Installation Video") +[![Step by Step Installation Video](../docs/images/YoutubeScreenshot.png)](https://youtu.be/JMnsIxTNbE4 "Marketing Analytics Jumpstart Installation Video") ## Prerequisites From 466bd906ebd72227600259579693d8704fba45db Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 22:13:06 -0400 Subject: [PATCH 010/110] Update activation_type_configuration_template.tpl --- templates/activation_type_configuration_template.tpl | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/templates/activation_type_configuration_template.tpl b/templates/activation_type_configuration_template.tpl index 22afddc3..9d8e9d68 100644 --- a/templates/activation_type_configuration_template.tpl +++ b/templates/activation_type_configuration_template.tpl @@ -43,5 +43,15 @@ "activation_event_name": "maj_churn_propensity_30_15", "source_query_template": "${churn_propensity_query_template_gcs_path}", "measurement_protocol_payload_template": "${measurement_protocol_payload_template_gcs_path}" + }, + "churn-propensity-15-15": { + "activation_event_name": "maj_churn_propensity_15_15", + "source_query_template": "${churn_propensity_query_template_gcs_path}", + "measurement_protocol_payload_template": "${measurement_protocol_payload_template_gcs_path}" + }, + "churn-propensity-15-7": { + "activation_event_name": "maj_churn_propensity_15_7", + "source_query_template": "${churn_propensity_query_template_gcs_path}", + "measurement_protocol_payload_template": "${measurement_protocol_payload_template_gcs_path}" } -} \ No newline at end of file +} From 3b0ea7c43fbf5fad6d33b7137cb38c93972c9e51 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 22:23:54 -0400 Subject: [PATCH 011/110] Update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index f010736d..048a9c4f 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,14 @@ These insights are used to serve as a basis to optimize paid media efforts and i * Driving a more personalized experience for your highly valued customers and improve return on ads spend (ROAS) via customer lifetime value * Attributing bidding values to specific users according to their journeys through the conversion funnel which Ads platform uses to guide better campaign performance in specific markets +| Use Case | Data Sources | Model | Report | Activation Event | Google Ads Campaign Optimization | +|-------|-------|-------|--------|--------|--------| +| Audience Segmentation | Google Analytics 4 | BQML Kmeans | Demographic based Audience Segmentation | [maj_audience_segmentation_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L2) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | +| Auto Audience Segmentation | Google Analytics 4 | BQML Kmeans | Interest based Audience Segmentation | [maj_auto_audience_segmentation_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L8) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | +| Customer Lifetime Value | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Customer Lifetime Value | [maj_cltv_180_30](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L23) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | +| Purchase Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Purchase | [maj_purchase_propensity_30_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L28) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | +| Churn Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Churn | [maj_churn_propensity_30_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L43) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | +| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | - | [Bid Adjustment]([https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns)) | ## Repository Structure The solution's source code is written in Terraform, Python, SQL, YAML and JSON; and it is organized into five main folders: From 17821a27c66cb5e2bfe270934c5bda79144879ef Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 22:24:55 -0400 Subject: [PATCH 012/110] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 048a9c4f..bd1ab654 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ These insights are used to serve as a basis to optimize paid media efforts and i | Customer Lifetime Value | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Customer Lifetime Value | [maj_cltv_180_30](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L23) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Purchase Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Purchase | [maj_purchase_propensity_30_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L28) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Churn Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Churn | [maj_churn_propensity_30_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L43) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | -| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | - | [Bid Adjustment]([https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns)) | +| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | - | [Bid Adjustment](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) | ## Repository Structure The solution's source code is written in Terraform, Python, SQL, YAML and JSON; and it is organized into five main folders: From 389944fa8bba9957b1833e7ce1bbba1096895385 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 22:26:04 -0400 Subject: [PATCH 013/110] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd1ab654..a72fc8f8 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ These insights are used to serve as a basis to optimize paid media efforts and i * Driving a more personalized experience for your highly valued customers and improve return on ads spend (ROAS) via customer lifetime value * Attributing bidding values to specific users according to their journeys through the conversion funnel which Ads platform uses to guide better campaign performance in specific markets -| Use Case | Data Sources | Model | Report | Activation Event | Google Ads Campaign Optimization | +| Use Case | Data Sources | Model | Looker Report Name | Activation Event | Google Ads Campaign Optimization | |-------|-------|-------|--------|--------|--------| | Audience Segmentation | Google Analytics 4 | BQML Kmeans | Demographic based Audience Segmentation | [maj_audience_segmentation_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L2) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Auto Audience Segmentation | Google Analytics 4 | BQML Kmeans | Interest based Audience Segmentation | [maj_auto_audience_segmentation_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L8) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | From 788525cfe20b32342c395122fceea12b36b02eee Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 22:29:49 -0400 Subject: [PATCH 014/110] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a72fc8f8..16183546 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ These insights are used to serve as a basis to optimize paid media efforts and i | Customer Lifetime Value | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Customer Lifetime Value | [maj_cltv_180_30](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L23) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Purchase Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Purchase | [maj_purchase_propensity_30_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L28) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Churn Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Churn | [maj_churn_propensity_30_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L43) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | -| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | - | [Bid Adjustment](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) | +| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | - | [Static Conversion Values](https://support.google.com/google-ads/answer/13064107?sjid=13060303839552593837-NA#zippy=%2Cset-a-conversion-value%2Cchange-a-conversion-value)
[Bid Adjustment](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) | ## Repository Structure The solution's source code is written in Terraform, Python, SQL, YAML and JSON; and it is organized into five main folders: From 098cae13ab367c4039097d21f95e28ecc27785a4 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 22:30:32 -0400 Subject: [PATCH 015/110] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 16183546..9c3ea1e8 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ These insights are used to serve as a basis to optimize paid media efforts and i | Customer Lifetime Value | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Customer Lifetime Value | [maj_cltv_180_30](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L23) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Purchase Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Purchase | [maj_purchase_propensity_30_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L28) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Churn Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Churn | [maj_churn_propensity_30_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L43) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | -| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | - | [Static Conversion Values](https://support.google.com/google-ads/answer/13064107?sjid=13060303839552593837-NA#zippy=%2Cset-a-conversion-value%2Cchange-a-conversion-value)
[Bid Adjustment](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) | +| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | - | [Static Conversion Values](https://support.google.com/google-ads/answer/13064107?sjid=13060303839552593837-NA#zippy=%2Cset-a-conversion-value%2Cchange-a-conversion-value)

[Bid Adjustment](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) | ## Repository Structure The solution's source code is written in Terraform, Python, SQL, YAML and JSON; and it is organized into five main folders: From 03bfb5d669dbabede4d4a65c765943c82c40b375 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 22:32:57 -0400 Subject: [PATCH 016/110] Update README.md --- infrastructure/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/infrastructure/README.md b/infrastructure/README.md index 30bd22d0..07ec98cc 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -5,10 +5,6 @@ Marketing Analytics Jumpstart consists of several components - marketing data store (MDS), feature store, ML pipelines, the activation pipeline and dashboards. This document describes the sequencing of installing these components. -To facilitate the installation, use this Step by Step Installation Video. - -[![Step by Step Installation Video](../docs/images/YoutubeScreenshot.png)](https://youtu.be/JMnsIxTNbE4 "Marketing Analytics Jumpstart Installation Video") - ## Prerequisites ### Marketing Analytics Data Sources From 0bfe4ee30f51bc54d95c24c88b519e98a235be73 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 22:36:01 -0400 Subject: [PATCH 017/110] Update README.md --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9c3ea1e8..eb01ab28 100644 --- a/README.md +++ b/README.md @@ -114,14 +114,19 @@ This high-level architecture demonstrates how Marketing Analytics Jumpstart inte ## Installation -Please follow the step by step installation guide with Google Cloud Shell. + +To facilitate the installation, use this Step by Step Installation Video. + +[![Step by Step Installation Video](../docs/images/YoutubeScreenshot.png)](https://youtu.be/JMnsIxTNbE4 "Marketing Analytics Jumpstart Installation Video") + +Please follow this step by step [Installation Guide](./infrastructure/README.md). + +Alternatively, follow the step by step installation guide with Google Cloud Shell. [![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart.git&cloudshell_git_branch=main&cloudshell_workspace=&cloudshell_tutorial=infrastructure/cloudshell/tutorial.md) **Note:** If you are working from a forked repository, be sure to update the `cloudshell_git_repo` parameter to the URL of your forked repository for the button link above. -The detailed installation instructions can be found at the [Installation Guide](./infrastructure/README.md). - ## Contributing We welcome all feedback and contributions! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for more information on how From a0ce977dfb99bb70c83185023006b1e8783679db Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 23 Sep 2024 22:37:26 -0400 Subject: [PATCH 018/110] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eb01ab28..72ddd42a 100644 --- a/README.md +++ b/README.md @@ -117,9 +117,9 @@ This high-level architecture demonstrates how Marketing Analytics Jumpstart inte To facilitate the installation, use this Step by Step Installation Video. -[![Step by Step Installation Video](../docs/images/YoutubeScreenshot.png)](https://youtu.be/JMnsIxTNbE4 "Marketing Analytics Jumpstart Installation Video") +[![Step by Step Installation Video](docs/images/YoutubeScreenshot.png)](https://youtu.be/JMnsIxTNbE4 "Marketing Analytics Jumpstart Installation Video") -Please follow this step by step [Installation Guide](./infrastructure/README.md). +Please follow this [Installation Guide](./infrastructure/README.md) to accompany the video. Alternatively, follow the step by step installation guide with Google Cloud Shell. From 81b6da8abf2565c024b319822a7fe89d45e70605 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 25 Sep 2024 11:23:43 -0400 Subject: [PATCH 019/110] Quick install (#196) * predicting for only the users with traffic in the past 72h - purchase propensity * running inference only for users events in the past 72h * including 72h users for all models predictions * considering null values in TabWorkflow models * deleting unused pipfile * upgrading lib versions * implementing reporting preprocessing as a new pipeline * adding more code documentation * adding important information on the main README.md and DEVELOPMENT.md * adding schedule run name and more code documentation * implementing a new scheduler using the vertex ai sdk & adding user_id to procedures for consistency * adding more code documentation * adding code doc to the python custom component * adding more code documentation * fixing aggregated predictions query * removing unnecessary resources from deployment * Writing MDS guide * adding the MDS developer and troubleshooting documentation * fixing deployment for activation pipelines and gemini dataset * Update README.md * Update README.md * Update README.md * Update README.md * removing deprecated api * fixing purchase propensity pipelines names * adding extra condition for when there is not enough data for the window interval to be applied on backfill procedures * adding more instructions for post deployment and fixing issues when GA4 export was configured for less than 10 days * removing unnecessary comments * adding the number of past days to process in the variables files * adding comment about combining data from different ga4 export datasets to data store * fixing small issues with feature engineering and ml pipelines * fixing hyper parameter tuning for kmeans modeling * fixing optuna parameters * adding cloud shell image * fixing the list of all possible users in the propensity training preparation tables * additional guardrails for when there is not enough data * adding more documentation * adding more doc to feature store * add feature store documentation * adding ml pipelines docs * adding ml pipelines docs * adding more documentation * adding user agent client info * fixing scope of client info * fix * removing client_info from vertex components * fixing versioning of tf submodules * reconfiguring meta providers * fixing issue 187 * adding quick installation process * removing state active --------- Co-authored-by: Carlos Timoteo --- notebooks/quick_installation.ipynb | 2471 ++++++++++++++++++++++++++++ scripts/quick-install.sh | 123 ++ 2 files changed, 2594 insertions(+) create mode 100644 notebooks/quick_installation.ipynb create mode 100755 scripts/quick-install.sh diff --git a/notebooks/quick_installation.ipynb b/notebooks/quick_installation.ipynb new file mode 100644 index 00000000..f0089a5f --- /dev/null +++ b/notebooks/quick_installation.ipynb @@ -0,0 +1,2471 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Marketing Analytics Jumpstart Quick Installation\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \"Google
Run in Colab\n", + "
\n", + "
\n", + " \n", + " \"Google
Run in Colab Enterprise\n", + "
\n", + "
\n", + " \n", + " \"GitHub
View on GitHub\n", + "
\n", + "
\n", + " \n", + " \"Vertex
Open in Vertex AI Workbench\n", + "
\n", + "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "Follow this Colab notebook to quick install the Marketing Analytics Jumpstart solution on a Google Cloud Project." + ], + "metadata": { + "id": "AKtB_GVpt2QJ" + } + }, + { + "cell_type": "code", + "source": [ + "# @title Input Google Cloud Project ID\n", + "# prompt: set PROJECT_ID env variable and run gcloud set project\n", + "\n", + "GOOGLE_CLOUD_PROJECT = \"marketing-data-engine-demo\" #@param {type:\"string\"}\n", + "GOOGLE_CLOUD_QUOTA_PROJECT = GOOGLE_CLOUD_PROJECT\n", + "PROJECT_ID = GOOGLE_CLOUD_PROJECT\n", + "!gcloud config set disable_prompts true\n", + "!gcloud config set project {PROJECT_ID}" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "dMcepKg8IQWj", + "outputId": "b71f3a1a-97f8-425e-efa1-a41126bdce62" + }, + "execution_count": 2, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Updated property [core/disable_prompts].\n", + "Updated property [core/project].\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "# @title Authenticate to Google Cloud Platform\n", + "# prompt: authenticate to google cloud project\n", + "from google.colab import auth\n", + "auth.authenticate_user()" + ], + "metadata": { + "id": "9TyPgnleJGGZ" + }, + "execution_count": 1, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# @title Authenticate using application default credentials Google Cloud Platform\n", + "!gcloud config set disable_prompts false\n", + "!gcloud auth application-default login --quiet --scopes=\"openid,https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/sqlservice.login,https://www.googleapis.com/auth/analytics,https://www.googleapis.com/auth/analytics.edit,https://www.googleapis.com/auth/analytics.provision,https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/accounts.reauth\"\n", + "!gcloud auth application-default set-quota-project {PROJECT_ID}\n", + "!export GOOGLE_APPLICATION_CREDENTIALS=/content/.config/application_default_credentials.json" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "3cAwp6CRLSVf", + "outputId": "df1356d3-e7f8-4670-f609-7d5e9a96d277" + }, + "execution_count": 7, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Updated property [core/disable_prompts].\n", + "Go to the following link in your browser, and complete the sign-in prompts:\n", + "\n", + " https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fsdk.cloud.google.com%2Fapplicationdefaultauthcode.html&scope=openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fsqlservice.login+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fanalytics+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fanalytics.edit+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fanalytics.provision+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fanalytics.readonly+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Faccounts.reauth&state=upHeK5pdqi2RFOTouPRbKKe64tObPX&prompt=consent&token_usage=remote&access_type=offline&code_challenge=PKVsYphOmWNHo5bF55Usd3qUN7QB6NFHKky2Satjags&code_challenge_method=S256\n", + "\n", + "Once finished, enter the verification code provided in your browser: 4/0AQlEd8yyRXzlPHea-h9wRjZMXB4aXGWIyqN2dWciuFe3wtCkClAPzq_vQKAM5ib4ikiV1g\n", + "\n", + "Credentials saved to file: [/content/.config/application_default_credentials.json]\n", + "\n", + "These credentials will be used by any library that requests Application Default Credentials (ADC).\n", + "\n", + "Quota project \"marketing-data-engine-demo\" was added to ADC which can be used by Google client libraries for billing and quota. Note that some services may still bill the project owning the resource.\n", + "\n", + "Credentials saved to file: [/content/.config/application_default_credentials.json]\n", + "\n", + "These credentials will be used by any library that requests Application Default Credentials (ADC).\n", + "\n", + "Quota project \"marketing-data-engine-demo\" was added to ADC which can be used by Google client libraries for billing and quota. Note that some services may still bill the project owning the resource.\n" + ] + } + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "wBOKo_6aF-GS", + "outputId": "4eb83fa6-3504-4cac-91db-b76ed0193e67" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Cloning into 'marketing-analytics-jumpstart'...\n", + "remote: Enumerating objects: 2535, done.\u001b[K\n", + "remote: Counting objects: 100% (1609/1609), done.\u001b[K\n", + "remote: Compressing objects: 100% (846/846), done.\u001b[K\n", + "remote: Total 2535 (delta 1133), reused 974 (delta 756), pack-reused 926 (from 1)\u001b[K\n", + "Receiving objects: 100% (2535/2535), 18.58 MiB | 21.67 MiB/s, done.\n", + "Resolving deltas: 100% (1509/1509), done.\n", + "/content/marketing-analytics-jumpstart\n" + ] + } + ], + "source": [ + "# prompt: git clone a repository and setting cd to it\n", + "REPO=\"marketing-analytics-jumpstart\"\n", + "!if [ ! -d \"/content/{REPO}\" ]; then git clone https://github.com/GoogleCloudPlatform/{REPO}.git ; fi\n", + "SOURCE_ROOT=\"/content/\"+REPO\n", + "%cd {SOURCE_ROOT}" + ] + }, + { + "cell_type": "code", + "source": [ + "%%bash\n", + "# prompt: install packages\n", + "apt-get install python3.10\n", + "CLOUDSDK_PYTHON=python3.10\n", + "\n", + "#pip3 install poetry\n", + "sudo apt update\n", + "sudo apt install pipx\n", + "pipx ensurepath\n", + "pipx install poetry\n", + "\n", + "export PATH=\"/root/.local/bin:$PATH\"\n", + "poetry env use python3.10\n", + "poetry --version\n", + "\n", + "git clone --depth=1 https://github.com/tfutils/tfenv.git ~/.tfenv\n", + "echo 'export PATH=\"~/.tfenv/bin:$PATH\"' >> ~/.bash_profile\n", + "echo 'export PATH=$PATH:~/.tfenv/bin' >> ~/.bashrc\n", + "export PATH=\"$PATH:~/.tfenv/bin\"\n", + "\n", + "mkdir -p ~/.local/bin/\n", + ". ~/.profile\n", + "ln -s ~/.tfenv/bin/* ~/.local/bin\n", + "which tfenv\n", + "tfenv --version\n", + "\n", + "tfenv install 1.5.7\n", + "tfenv use 1.5.7\n", + "terraform --version\n", + "\n", + "export PATH=\"$PATH:~/.tfenv/bin\"\n", + "export PROJECT_ID=$(gcloud config get project --format=json | tr -d '\"')\n", + "source ./scripts/generate-tf-backend.sh" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "hmdklTTuQ_9d", + "outputId": "4a133cac-b4ed-48a4-e070-1de791c0d02b" + }, + "execution_count": 9, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Reading package lists...\n", + "Building dependency tree...\n", + "Reading state information...\n", + "python3.10 is already the newest version (3.10.12-1~22.04.6).\n", + "0 upgraded, 0 newly installed, 0 to remove and 49 not upgraded.\n", + "Get:1 https://cloud.r-project.org/bin/linux/ubuntu jammy-cran40/ InRelease [3,626 B]\n", + "Hit:2 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64 InRelease\n", + "Hit:3 http://archive.ubuntu.com/ubuntu jammy InRelease\n", + "Get:4 http://security.ubuntu.com/ubuntu jammy-security InRelease [129 kB]\n", + "Ign:5 https://r2u.stat.illinois.edu/ubuntu jammy InRelease\n", + "Get:6 https://r2u.stat.illinois.edu/ubuntu jammy Release [5,713 B]\n", + "Get:7 http://archive.ubuntu.com/ubuntu jammy-updates InRelease [128 kB]\n", + "Get:8 https://r2u.stat.illinois.edu/ubuntu jammy Release.gpg [793 B]\n", + "Hit:9 https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu jammy InRelease\n", + "Hit:10 https://ppa.launchpadcontent.net/graphics-drivers/ppa/ubuntu jammy InRelease\n", + "Get:11 https://r2u.stat.illinois.edu/ubuntu jammy/main all Packages [8,348 kB]\n", + "Hit:12 http://archive.ubuntu.com/ubuntu jammy-backports InRelease\n", + "Hit:13 https://ppa.launchpadcontent.net/ubuntugis/ppa/ubuntu jammy InRelease\n", + "Get:14 http://security.ubuntu.com/ubuntu jammy-security/main amd64 Packages [2,314 kB]\n", + "Get:15 https://r2u.stat.illinois.edu/ubuntu jammy/main amd64 Packages [2,586 kB]\n", + "Get:16 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 Packages [2,593 kB]\n", + "Get:17 http://archive.ubuntu.com/ubuntu jammy-updates/restricted amd64 Packages [3,191 kB]\n", + "Get:18 http://archive.ubuntu.com/ubuntu jammy-updates/universe amd64 Packages [1,442 kB]\n", + "Fetched 20.7 MB in 3s (7,771 kB/s)\n", + "Reading package lists...\n", + "Building dependency tree...\n", + "Reading state information...\n", + "49 packages can be upgraded. Run 'apt list --upgradable' to see them.\n", + "Reading package lists...\n", + "Building dependency tree...\n", + "Reading state information...\n", + "The following additional packages will be installed:\n", + " fonts-font-awesome fonts-lato javascript-common libjs-bootstrap4\n", + " libjs-highlight.js libjs-lunr libjs-modernizr libjs-popper.js libjs-sizzle\n", + " mkdocs node-jquery python-babel-localedata python3-argcomplete python3-babel\n", + " python3-click python3-colorama python3-jinja2 python3-livereload\n", + " python3-markdown python3-markupsafe python3-packaging python3-pip-whl\n", + " python3-psutil python3-pygments python3-pyinotify python3-setuptools-whl\n", + " python3-tornado python3-tz python3-userpath python3-venv python3-yaml\n", + " python3.10-venv sphinx-rtd-theme-common\n", + "Suggested packages:\n", + " apache2 | lighttpd | httpd libjs-es5-shim ghp-import mkdocs-doc nodejs\n", + " python-jinja2-doc coffeescript node-less node-uglify python-livereload-doc\n", + " python3-django python3-flask python3-slimmer python-markdown-doc\n", + " python-psutil-doc python-pygments-doc ttf-bitstream-vera\n", + " python-pyinotify-doc python3-pycurl python-tornado-doc python3-twisted\n", + "The following NEW packages will be installed:\n", + " fonts-font-awesome fonts-lato javascript-common libjs-bootstrap4\n", + " libjs-highlight.js libjs-lunr libjs-modernizr libjs-popper.js libjs-sizzle\n", + " mkdocs node-jquery pipx python-babel-localedata python3-argcomplete\n", + " python3-babel python3-click python3-colorama python3-jinja2\n", + " python3-livereload python3-markdown python3-markupsafe python3-packaging\n", + " python3-pip-whl python3-psutil python3-pygments python3-pyinotify\n", + " python3-setuptools-whl python3-tornado python3-tz python3-userpath\n", + " python3-venv python3-yaml python3.10-venv sphinx-rtd-theme-common\n", + "0 upgraded, 34 newly installed, 0 to remove and 49 not upgraded.\n", + "Need to get 15.2 MB of archives.\n", + "After this operation, 64.3 MB of additional disk space will be used.\n", + "Get:1 http://archive.ubuntu.com/ubuntu jammy/main amd64 fonts-lato all 2.0-2.1 [2,696 kB]\n", + "Get:2 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-yaml amd64 5.4.1-1ubuntu1 [129 kB]\n", + "Get:3 http://archive.ubuntu.com/ubuntu jammy/main amd64 fonts-font-awesome all 5.0.10+really4.7.0~dfsg-4.1 [516 kB]\n", + "Get:4 http://archive.ubuntu.com/ubuntu jammy/main amd64 javascript-common all 11+nmu1 [5,936 B]\n", + "Get:5 http://archive.ubuntu.com/ubuntu jammy/universe amd64 libjs-popper.js all 1.16.1+ds-5 [53.8 kB]\n", + "Get:6 http://archive.ubuntu.com/ubuntu jammy/universe amd64 libjs-bootstrap4 all 4.6.0+dfsg1-4 [534 kB]\n", + "Get:7 http://archive.ubuntu.com/ubuntu jammy/universe amd64 libjs-highlight.js all 9.18.5+dfsg1-1 [367 kB]\n", + "Get:8 http://archive.ubuntu.com/ubuntu jammy/universe amd64 libjs-lunr all 2.3.9~dfsg-1 [67.0 kB]\n", + "Get:9 http://archive.ubuntu.com/ubuntu jammy/universe amd64 libjs-sizzle all 2.3.6+ds+~2.3.3-1 [32.3 kB]\n", + "Get:10 http://archive.ubuntu.com/ubuntu jammy/universe amd64 libjs-modernizr all 2.6.2+ds1-4 [47.1 kB]\n", + "Get:11 http://archive.ubuntu.com/ubuntu jammy/main amd64 sphinx-rtd-theme-common all 1.0.0+dfsg-1 [991 kB]\n", + "Get:12 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-colorama all 0.4.4-1 [24.5 kB]\n", + "Get:13 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-click all 8.0.3-1 [78.3 kB]\n", + "Get:14 http://archive.ubuntu.com/ubuntu jammy/main amd64 python-babel-localedata all 2.8.0+dfsg.1-7 [4,982 kB]\n", + "Get:15 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 python3-tz all 2022.1-1ubuntu0.22.04.1 [30.7 kB]\n", + "Get:16 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-babel all 2.8.0+dfsg.1-7 [85.1 kB]\n", + "Get:17 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-markupsafe amd64 2.0.1-2build1 [12.7 kB]\n", + "Get:18 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 python3-jinja2 all 3.0.3-1ubuntu0.2 [108 kB]\n", + "Get:19 http://archive.ubuntu.com/ubuntu jammy/universe amd64 python3-tornado amd64 6.1.0-3build1 [287 kB]\n", + "Get:20 http://archive.ubuntu.com/ubuntu jammy/universe amd64 python3-livereload all 2.6.3-2 [24.7 kB]\n", + "Get:21 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-markdown all 3.3.6-1 [68.5 kB]\n", + "Get:22 http://archive.ubuntu.com/ubuntu jammy/universe amd64 mkdocs all 1.1.2+dfsg-2ubuntu1 [59.2 kB]\n", + "Get:23 http://archive.ubuntu.com/ubuntu jammy/universe amd64 node-jquery all 3.6.0+dfsg+~3.5.13-1 [160 kB]\n", + "Get:24 http://archive.ubuntu.com/ubuntu jammy-updates/universe amd64 python3-pip-whl all 22.0.2+dfsg-1ubuntu0.4 [1,680 kB]\n", + "Get:25 http://archive.ubuntu.com/ubuntu jammy-updates/universe amd64 python3-setuptools-whl all 59.6.0-1.2ubuntu0.22.04.2 [788 kB]\n", + "Get:26 http://archive.ubuntu.com/ubuntu jammy-updates/universe amd64 python3.10-venv amd64 3.10.12-1~22.04.6 [5,722 B]\n", + "Get:27 http://archive.ubuntu.com/ubuntu jammy-updates/universe amd64 python3-venv amd64 3.10.6-1~22.04.1 [1,042 B]\n", + "Get:28 http://archive.ubuntu.com/ubuntu jammy/universe amd64 python3-argcomplete all 1.8.1-1.5 [27.2 kB]\n", + "Get:29 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-packaging all 21.3-1 [30.7 kB]\n", + "Get:30 http://archive.ubuntu.com/ubuntu jammy/universe amd64 python3-userpath all 1.8.0-1 [10.0 kB]\n", + "Get:31 http://archive.ubuntu.com/ubuntu jammy/universe amd64 pipx all 1.0.0-1 [371 kB]\n", + "Get:32 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-psutil amd64 5.9.0-1build1 [158 kB]\n", + "Get:33 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-pygments all 2.11.2+dfsg-2 [750 kB]\n", + "Get:34 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-pyinotify all 0.9.6-1.3 [24.8 kB]\n", + "Fetched 15.2 MB in 2s (9,428 kB/s)\n", + "Selecting previously unselected package fonts-lato.\r\n", + "(Reading database ... \r(Reading database ... 5%\r(Reading database ... 10%\r(Reading database ... 15%\r(Reading database ... 20%\r(Reading database ... 25%\r(Reading database ... 30%\r(Reading database ... 35%\r(Reading database ... 40%\r(Reading database ... 45%\r(Reading database ... 50%\r(Reading database ... 55%\r(Reading database ... 60%\r(Reading database ... 65%\r(Reading database ... 70%\r(Reading database ... 75%\r(Reading database ... 80%\r(Reading database ... 85%\r(Reading database ... 90%\r(Reading database ... 95%\r(Reading database ... 100%\r(Reading database ... 123605 files and directories currently installed.)\r\n", + "Preparing to unpack .../00-fonts-lato_2.0-2.1_all.deb ...\r\n", + "Unpacking fonts-lato (2.0-2.1) ...\r\n", + "Selecting previously unselected package python3-yaml.\r\n", + "Preparing to unpack .../01-python3-yaml_5.4.1-1ubuntu1_amd64.deb ...\r\n", + "Unpacking python3-yaml (5.4.1-1ubuntu1) ...\r\n", + "Selecting previously unselected package fonts-font-awesome.\r\n", + "Preparing to unpack .../02-fonts-font-awesome_5.0.10+really4.7.0~dfsg-4.1_all.deb ...\r\n", + "Unpacking fonts-font-awesome (5.0.10+really4.7.0~dfsg-4.1) ...\r\n", + "Selecting previously unselected package javascript-common.\r\n", + "Preparing to unpack .../03-javascript-common_11+nmu1_all.deb ...\r\n", + "Unpacking javascript-common (11+nmu1) ...\r\n", + "Selecting previously unselected package libjs-popper.js.\r\n", + "Preparing to unpack .../04-libjs-popper.js_1.16.1+ds-5_all.deb ...\r\n", + "Unpacking libjs-popper.js (1.16.1+ds-5) ...\r\n", + "Selecting previously unselected package libjs-bootstrap4.\r\n", + "Preparing to unpack .../05-libjs-bootstrap4_4.6.0+dfsg1-4_all.deb ...\r\n", + "Unpacking libjs-bootstrap4 (4.6.0+dfsg1-4) ...\r\n", + "Selecting previously unselected package libjs-highlight.js.\r\n", + "Preparing to unpack .../06-libjs-highlight.js_9.18.5+dfsg1-1_all.deb ...\r\n", + "Unpacking libjs-highlight.js (9.18.5+dfsg1-1) ...\r\n", + "Selecting previously unselected package libjs-lunr.\r\n", + "Preparing to unpack .../07-libjs-lunr_2.3.9~dfsg-1_all.deb ...\r\n", + "Unpacking libjs-lunr (2.3.9~dfsg-1) ...\r\n", + "Selecting previously unselected package libjs-sizzle.\r\n", + "Preparing to unpack .../08-libjs-sizzle_2.3.6+ds+~2.3.3-1_all.deb ...\r\n", + "Unpacking libjs-sizzle (2.3.6+ds+~2.3.3-1) ...\r\n", + "Selecting previously unselected package libjs-modernizr.\r\n", + "Preparing to unpack .../09-libjs-modernizr_2.6.2+ds1-4_all.deb ...\r\n", + "Unpacking libjs-modernizr (2.6.2+ds1-4) ...\r\n", + "Selecting previously unselected package sphinx-rtd-theme-common.\r\n", + "Preparing to unpack .../10-sphinx-rtd-theme-common_1.0.0+dfsg-1_all.deb ...\r\n", + "Unpacking sphinx-rtd-theme-common (1.0.0+dfsg-1) ...\r\n", + "Selecting previously unselected package python3-colorama.\r\n", + "Preparing to unpack .../11-python3-colorama_0.4.4-1_all.deb ...\r\n", + "Unpacking python3-colorama (0.4.4-1) ...\r\n", + "Selecting previously unselected package python3-click.\r\n", + "Preparing to unpack .../12-python3-click_8.0.3-1_all.deb ...\r\n", + "Unpacking python3-click (8.0.3-1) ...\r\n", + "Selecting previously unselected package python-babel-localedata.\r\n", + "Preparing to unpack .../13-python-babel-localedata_2.8.0+dfsg.1-7_all.deb ...\r\n", + "Unpacking python-babel-localedata (2.8.0+dfsg.1-7) ...\r\n", + "Selecting previously unselected package python3-tz.\r\n", + "Preparing to unpack .../14-python3-tz_2022.1-1ubuntu0.22.04.1_all.deb ...\r\n", + "Unpacking python3-tz (2022.1-1ubuntu0.22.04.1) ...\r\n", + "Selecting previously unselected package python3-babel.\r\n", + "Preparing to unpack .../15-python3-babel_2.8.0+dfsg.1-7_all.deb ...\r\n", + "Unpacking python3-babel (2.8.0+dfsg.1-7) ...\r\n", + "Selecting previously unselected package python3-markupsafe.\r\n", + "Preparing to unpack .../16-python3-markupsafe_2.0.1-2build1_amd64.deb ...\r\n", + "Unpacking python3-markupsafe (2.0.1-2build1) ...\r\n", + "Selecting previously unselected package python3-jinja2.\r\n", + "Preparing to unpack .../17-python3-jinja2_3.0.3-1ubuntu0.2_all.deb ...\r\n", + "Unpacking python3-jinja2 (3.0.3-1ubuntu0.2) ...\r\n", + "Selecting previously unselected package python3-tornado.\r\n", + "Preparing to unpack .../18-python3-tornado_6.1.0-3build1_amd64.deb ...\r\n", + "Unpacking python3-tornado (6.1.0-3build1) ...\r\n", + "Selecting previously unselected package python3-livereload.\r\n", + "Preparing to unpack .../19-python3-livereload_2.6.3-2_all.deb ...\r\n", + "Unpacking python3-livereload (2.6.3-2) ...\r\n", + "Selecting previously unselected package python3-markdown.\r\n", + "Preparing to unpack .../20-python3-markdown_3.3.6-1_all.deb ...\r\n", + "Unpacking python3-markdown (3.3.6-1) ...\r\n", + "Selecting previously unselected package mkdocs.\r\n", + "Preparing to unpack .../21-mkdocs_1.1.2+dfsg-2ubuntu1_all.deb ...\r\n", + "Unpacking mkdocs (1.1.2+dfsg-2ubuntu1) ...\r\n", + "Selecting previously unselected package node-jquery.\r\n", + "Preparing to unpack .../22-node-jquery_3.6.0+dfsg+~3.5.13-1_all.deb ...\r\n", + "Unpacking node-jquery (3.6.0+dfsg+~3.5.13-1) ...\r\n", + "Selecting previously unselected package python3-pip-whl.\r\n", + "Preparing to unpack .../23-python3-pip-whl_22.0.2+dfsg-1ubuntu0.4_all.deb ...\r\n", + "Unpacking python3-pip-whl (22.0.2+dfsg-1ubuntu0.4) ...\r\n", + "Selecting previously unselected package python3-setuptools-whl.\r\n", + "Preparing to unpack .../24-python3-setuptools-whl_59.6.0-1.2ubuntu0.22.04.2_all.deb ...\r\n", + "Unpacking python3-setuptools-whl (59.6.0-1.2ubuntu0.22.04.2) ...\r\n", + "Selecting previously unselected package python3.10-venv.\r\n", + "Preparing to unpack .../25-python3.10-venv_3.10.12-1~22.04.6_amd64.deb ...\r\n", + "Unpacking python3.10-venv (3.10.12-1~22.04.6) ...\r\n", + "Selecting previously unselected package python3-venv.\r\n", + "Preparing to unpack .../26-python3-venv_3.10.6-1~22.04.1_amd64.deb ...\r\n", + "Unpacking python3-venv (3.10.6-1~22.04.1) ...\r\n", + "Selecting previously unselected package python3-argcomplete.\r\n", + "Preparing to unpack .../27-python3-argcomplete_1.8.1-1.5_all.deb ...\r\n", + "Unpacking python3-argcomplete (1.8.1-1.5) ...\r\n", + "Selecting previously unselected package python3-packaging.\r\n", + "Preparing to unpack .../28-python3-packaging_21.3-1_all.deb ...\r\n", + "Unpacking python3-packaging (21.3-1) ...\r\n", + "Selecting previously unselected package python3-userpath.\r\n", + "Preparing to unpack .../29-python3-userpath_1.8.0-1_all.deb ...\r\n", + "Unpacking python3-userpath (1.8.0-1) ...\r\n", + "Selecting previously unselected package pipx.\r\n", + "Preparing to unpack .../30-pipx_1.0.0-1_all.deb ...\r\n", + "Unpacking pipx (1.0.0-1) ...\r\n", + "Selecting previously unselected package python3-psutil.\r\n", + "Preparing to unpack .../31-python3-psutil_5.9.0-1build1_amd64.deb ...\r\n", + "Unpacking python3-psutil (5.9.0-1build1) ...\r\n", + "Selecting previously unselected package python3-pygments.\r\n", + "Preparing to unpack .../32-python3-pygments_2.11.2+dfsg-2_all.deb ...\r\n", + "Unpacking python3-pygments (2.11.2+dfsg-2) ...\r\n", + "Selecting previously unselected package python3-pyinotify.\r\n", + "Preparing to unpack .../33-python3-pyinotify_0.9.6-1.3_all.deb ...\r\n", + "Unpacking python3-pyinotify (0.9.6-1.3) ...\r\n", + "Setting up javascript-common (11+nmu1) ...\r\n", + "Setting up python3-tornado (6.1.0-3build1) ...\r\n", + "Setting up python3-setuptools-whl (59.6.0-1.2ubuntu0.22.04.2) ...\r\n", + "Setting up fonts-lato (2.0-2.1) ...\r\n", + "Setting up libjs-popper.js (1.16.1+ds-5) ...\r\n", + "Setting up python3-colorama (0.4.4-1) ...\r\n", + "Setting up python3-pip-whl (22.0.2+dfsg-1ubuntu0.4) ...\r\n", + "Setting up libjs-lunr (2.3.9~dfsg-1) ...\r\n", + "Setting up python3-pyinotify (0.9.6-1.3) ...\r\n", + "Setting up python3-yaml (5.4.1-1ubuntu1) ...\r\n", + "Setting up python3-click (8.0.3-1) ...\r\n", + "Setting up libjs-sizzle (2.3.6+ds+~2.3.3-1) ...\r\n", + "Setting up python3-markupsafe (2.0.1-2build1) ...\r\n", + "Setting up libjs-modernizr (2.6.2+ds1-4) ...\r\n", + "Setting up python3-psutil (5.9.0-1build1) ...\r\n", + "Setting up python3-tz (2022.1-1ubuntu0.22.04.1) ...\r\n", + "Setting up python-babel-localedata (2.8.0+dfsg.1-7) ...\r\n", + "Setting up python3-pygments (2.11.2+dfsg-2) ...\r\n", + "Setting up python3-packaging (21.3-1) ...\r\n", + "Setting up python3-markdown (3.3.6-1) ...\r\n", + "Setting up libjs-highlight.js (9.18.5+dfsg1-1) ...\r\n", + "Setting up python3-livereload (2.6.3-2) ...\r\n", + "Setting up libjs-bootstrap4 (4.6.0+dfsg1-4) ...\r\n", + "Setting up python3-argcomplete (1.8.1-1.5) ...\r\n", + "Setting up fonts-font-awesome (5.0.10+really4.7.0~dfsg-4.1) ...\r\n", + "Setting up sphinx-rtd-theme-common (1.0.0+dfsg-1) ...\r\n", + "Setting up node-jquery (3.6.0+dfsg+~3.5.13-1) ...\r\n", + "Setting up python3-userpath (1.8.0-1) ...\r\n", + "Setting up python3.10-venv (3.10.12-1~22.04.6) ...\r\n", + "Setting up python3-babel (2.8.0+dfsg.1-7) ...\r\n", + "update-alternatives: using /usr/bin/pybabel-python3 to provide /usr/bin/pybabel (pybabel) in auto mode\r\n", + "Setting up python3-jinja2 (3.0.3-1ubuntu0.2) ...\r\n", + "Setting up python3-venv (3.10.6-1~22.04.1) ...\r\n", + "Setting up mkdocs (1.1.2+dfsg-2ubuntu1) ...\r\n", + "Setting up pipx (1.0.0-1) ...\r\n", + "Processing triggers for fontconfig (2.13.1-4.2ubuntu5) ...\r\n", + "Processing triggers for man-db (2.10.2-1) ...\r\n", + "Success! Added /root/.local/bin to the PATH environment variable.\n", + "\n", + "Consider adding shell completions for pipx. Run 'pipx completions' for instructions.\n", + "\n", + "You will need to open a new terminal or re-login for the PATH changes to take effect.\n", + "\n", + "Otherwise pipx is ready to go! ✨ 🌟 ✨\n", + " installed package poetry 1.8.3, installed using Python 3.10.12\n", + " These apps are now globally available\n", + " - poetry\n", + "Using virtualenv: /content/marketing-analytics-jumpstart/.venv\n", + "Poetry (version 1.8.3)\n", + "/root/.local/bin/tfenv\n", + "tfenv 3.0.0\n", + "\u001b[32mInstalling Terraform v1.5.7\u001b[0m\n", + "\u001b[32mDownloading release tarball from https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_linux_amd64.zip\u001b[0m\n", + "\u001b[32mDownloading SHA hash file from https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_SHA256SUMS\u001b[0m\n", + "\u001b[33mNot instructed to use Local PGP (/root/.tfenv/use-{gpgv,gnupg}) & No keybase install found, skipping OpenPGP signature verification\u001b[0m\n", + "\u001b[32mArchive: /tmp/tfenv_download.oUjd2S/terraform_1.5.7_linux_amd64.zip\u001b[0m\n", + "\u001b[32m inflating: /root/.tfenv/versions/1.5.7/terraform \u001b[0m\n", + "\u001b[32mInstallation of terraform v1.5.7 successful. To make this your default version, run 'tfenv use 1.5.7'\u001b[0m\n", + "\u001b[32mSwitching default version to v1.5.7\u001b[0m\n", + "\u001b[32mDefault version (when not overridden by .terraform-version or TFENV_TERRAFORM_VERSION) is now: 1.5.7\u001b[0m\n", + "Terraform v1.5.7\n", + "on linux_amd64\n", + "\n", + "Your version of Terraform is out of date! The latest version\n", + "is 1.9.5. You can update by downloading from https://www.terraform.io/downloads.html\n", + "****************************************************************************************************\n", + "\u001b[0;36mCheck if the necessary dependencies are available: gcloud, gsutil, terraform, poetry\u001b[0m \n", + "****************************************************************************************************\n", + "Google Cloud SDK 494.0.0\n", + "gsutil version: 5.30\n", + "Terraform v1.5.7\n", + "on linux_amd64\n", + "\n", + "Your version of Terraform is out of date! The latest version\n", + "is 1.9.5. You can update by downloading from https://www.terraform.io/downloads.html\n", + "Poetry (version 1.8.3)\n", + "****************************************************************************************************\n", + "\u001b[0;36mCheck if the necessary dependencies are available: gcloud, gsutil, terraform, poetry \u001b[1;36m- done\u001b[0m\n", + "\n", + "\n", + "****************************************************************************************************\n", + "\u001b[0;36mCheck if the necessary variables are set: PROJECT_ID\u001b[0m \n", + "****************************************************************************************************\n", + "****************************************************************************************************\n", + "\u001b[0;36mCheck if the necessary variables are set: PROJECT_ID \u001b[1;36m- done\u001b[0m\n", + "\n", + "\n", + "****************************************************************************************************\n", + "\u001b[0;36mSetting the Google Cloud project to TF_STATE_PROJECT\u001b[0m \n", + "****************************************************************************************************\n", + "****************************************************************************************************\n", + "\u001b[0;36mSetting the Google Cloud project to TF_STATE_PROJECT \u001b[1;36m- done\u001b[0m\n", + "\n", + "\n", + "****************************************************************************************************\n", + "\u001b[0;36mCheck and set the LOCATION variable\u001b[0m \n", + "****************************************************************************************************\n", + "****************************************************************************************************\n", + "\u001b[0;36mCheck and set the LOCATION variable \u001b[1;36m- done\u001b[0m\n", + "\n", + "\n", + "****************************************************************************************************\n", + "\u001b[0;36mCheck and set the TF_STATE_BUCKET variable\u001b[0m \n", + "****************************************************************************************************\n", + "****************************************************************************************************\n", + "\u001b[0;36mCheck and set the TF_STATE_BUCKET variable \u001b[1;36m- done\u001b[0m\n", + "\n", + "\n", + "****************************************************************************************************\n", + "\u001b[0;36mEnable all the required APIs\u001b[0m \n", + "****************************************************************************************************\n", + "cloudresourcemanager.googleapis.com Cloud Resource Manager API\n", + "cloudresourcemanager.googleapis.com api is enabled\n", + "serviceusage.googleapis.com Service Usage API\n", + "serviceusage.googleapis.com api is enabled\n", + "iam.googleapis.com Identity and Access Management (IAM) API\n", + "iam.googleapis.com api is enabled\n", + "logging.googleapis.com Cloud Logging API\n", + "logging.googleapis.com api is enabled\n", + "monitoring.googleapis.com Cloud Monitoring API\n", + "monitoring.googleapis.com api is enabled\n", + "bigquery.googleapis.com BigQuery API\n", + "bigquery.googleapis.com api is enabled\n", + "bigquerystorage.googleapis.com BigQuery Storage API\n", + "bigquerystorage.googleapis.com api is enabled\n", + "dataform.googleapis.com Dataform API\n", + "dataform.googleapis.com api is enabled\n", + "secretmanager.googleapis.com Secret Manager API\n", + "secretmanager.googleapis.com api is enabled\n", + "cloudasset.googleapis.com Cloud Asset API\n", + "cloudasset.googleapis.com api is enabled\n", + "cloudfunctions.googleapis.com Cloud Functions API\n", + "cloudfunctions.googleapis.com api is enabled\n", + "bigquerystorage.googleapis.com BigQuery Storage API\n", + "storage.googleapis.com Cloud Storage API\n", + "storage.googleapis.com api is enabled\n", + "datapipelines.googleapis.com Data pipelines API\n", + "datapipelines.googleapis.com api is enabled\n", + "analyticsadmin.googleapis.com Google Analytics Admin API\n", + "analyticsadmin.googleapis.com api is enabled\n", + "workflows.googleapis.com Workflows API\n", + "workflows.googleapis.com api is enabled\n", + "cloudscheduler.googleapis.com Cloud Scheduler API\n", + "cloudscheduler.googleapis.com api is enabled\n", + "bigquerymigration.googleapis.com BigQuery Migration API\n", + "bigquerymigration.googleapis.com api is enabled\n", + "bigquerydatatransfer.googleapis.com BigQuery Data Transfer API\n", + "bigquerydatatransfer.googleapis.com api is enabled\n", + "dataform.googleapis.com Dataform API\n", + "dataform.googleapis.com api is enabled\n", + "****************************************************************************************************\n", + "\u001b[0;36mEnable all the required APIs \u001b[1;36m- done\u001b[0m\n", + "\n", + "\n", + "****************************************************************************************************\n", + "\u001b[0;36mInstall poetry libraries in the virtual environment for Terraform\u001b[0m \n", + "****************************************************************************************************\n", + "Updating dependencies\n", + "Resolving dependencies...\n", + "\n", + "Package operations: 105 installs, 1 update, 0 removals\n", + "\n", + " - Downgrading pip (24.2 -> 23.3)\n", + " - Installing pyasn1 (0.6.1)\n", + " - Installing cachetools (5.5.0)\n", + " - Installing certifi (2024.8.30)\n", + " - Installing charset-normalizer (3.3.2)\n", + " - Installing idna (3.10)\n", + " - Installing protobuf (3.20.3)\n", + " - Installing pyasn1-modules (0.4.1)\n", + " - Installing rsa (4.9)\n", + " - Installing urllib3 (1.26.18)\n", + " - Installing google-auth (2.35.0)\n", + " - Installing googleapis-common-protos (1.65.0)\n", + " - Installing grpcio (1.66.1)\n", + " - Installing proto-plus (1.24.0)\n", + " - Installing requests (2.32.3)\n", + " - Installing google-api-core (2.20.0)\n", + " - Installing google-crc32c (1.5.0)\n", + " - Installing grpcio-status (1.48.2)\n", + " - Installing oauthlib (3.2.2)\n", + " - Installing six (1.16.0)\n", + " - Installing typing-extensions (4.12.2)\n", + " - Installing annotated-types (0.7.0)\n", + " - Installing google-cloud-core (2.4.1)\n", + " - Installing google-resumable-media (2.7.2)\n", + " - Installing greenlet (3.1.1)\n", + " - Installing grpc-google-iam-v1 (0.13.1)\n", + " - Installing markupsafe (2.1.5)\n", + " - Installing packaging (24.1)\n", + " - Installing pydantic-core (2.23.4)\n", + " - Installing python-dateutil (2.9.0.post0)\n", + " - Installing pyyaml (6.0.2)\n", + " - Installing requests-oauthlib (2.0.0)\n", + " - Installing websocket-client (1.8.0)\n", + " - Installing click (8.1.7)\n", + " - Installing docstring-parser (0.16)\n", + " - Installing google-cloud-bigquery (2.30.0)\n", + " - Installing google-cloud-resource-manager (1.12.5)\n", + " - Installing google-cloud-storage (2.18.2)\n", + " - Installing kfp-pipeline-spec (0.2.2)\n", + " - Installing kfp-server-api (2.0.0rc1)\n", + " - Installing kubernetes (26.1.0)\n", + " - Installing mako (1.3.5)\n", + " - Installing numpy (1.24.4)\n", + " - Installing pydantic (2.9.2)\n", + " - Installing pyparsing (3.1.4)\n", + " - Installing pytz (2024.2)\n", + " - Installing requests-toolbelt (0.10.1)\n", + " - Installing shapely (1.8.5.post1)\n", + " - Installing sqlalchemy (2.0.35)\n", + " - Installing tabulate (0.9.0)\n", + " - Installing alembic (1.13.3)\n", + " - Installing attrs (24.2.0)\n", + " - Installing cmaes (0.11.1)\n", + " - Installing colorlog (6.8.2)\n", + " - Installing distlib (0.3.8)\n", + " - Installing filelock (3.16.1)\n", + " - Installing google-cloud-aiplatform (1.52.0)\n", + " - Installing httplib2 (0.22.0)\n", + " - Installing iniconfig (2.0.0)\n", + " - Installing jinja2 (3.1.2)\n", + " - Installing joblib (1.4.2)\n", + " - Installing kfp (2.0.0rc2)\n", + " - Installing mccabe (0.7.0)\n", + " - Installing pandas (1.3.5)\n", + " - Installing platformdirs (4.3.6)\n", + " - Installing pluggy (1.5.0)\n", + " - Installing py (1.11.0)\n", + " - Installing pyarrow (15.0.2)\n", + " - Installing pycodestyle (2.9.1)\n", + " - Installing pyflakes (2.5.0)\n", + " - Installing scipy (1.10.1)\n", + " - Installing threadpoolctl (3.5.0)\n", + " - Installing tomli (2.0.1)\n", + " - Installing tqdm (4.66.5)\n", + " - Installing cfgv (3.4.0)\n", + " - Installing coverage (6.5.0)\n", + " - Installing db-dtypes (1.2.0)\n", + " - Installing docker (6.1.3)\n", + " - Installing execnet (2.1.1)\n", + " - Installing flake8 (5.0.4)\n", + " - Installing google-auth-oauthlib (1.2.1)\n", + " - Installing google-cloud-pipeline-components (2.6.0)\n", + " - Installing google-cloud-pubsub (2.15.0)\n", + " - Installing identify (2.6.1)\n", + " - Installing mypy-extensions (1.0.0)\n", + " - Installing nodeenv (1.9.1)\n", + " - Installing oauth2client (4.1.3)\n", + " - Installing optuna (3.2.0)\n", + " - Installing pathspec (0.12.1)\n", + " - Installing pytest (7.0.0)\n", + " - Installing scikit-learn (1.2.2)\n", + " - Installing toml (0.10.2)\n", + " - Installing virtualenv (20.26.5)\n", + " - Installing black (22.12.0)\n", + " - Installing flake8-annotations (2.9.1)\n", + " - Installing google-analytics-admin (0.22.7)\n", + " - Installing google-analytics-data (0.18.12)\n", + " - Installing google-cloud (0.34.0)\n", + " - Installing invoke (2.2.0)\n", + " - Installing pre-commit (2.21.0)\n", + " - Installing pytest-cov (4.1.0)\n", + " - Installing pytest-env (0.6.2)\n", + " - Installing pytest-mock (3.7.0)\n", + " - Installing pytest-variables (2.0.0)\n", + " - Installing pytest-xdist (3.6.1)\n", + " - Installing ma-components (1.0.0 /content/marketing-analytics-jumpstart/python/base_component_image)\n", + "\n", + "Writing lock file\n", + "\n", + "Installing the current project: marketing-analytics-jumpstart (1.0.0)\n", + "****************************************************************************************************\n", + "\u001b[0;36mInstall poetry libraries in the virtual environment for Terraform \u001b[1;36m- done\u001b[0m\n", + "\n", + "\n", + "****************************************************************************************************\n", + "\u001b[0;36mCreating a new Google Cloud Storage bucket to store the Terraform state in marketing-data-engine-demo project, bucket: marketing-data-engine-demo-terraform-state\u001b[0m \n", + "****************************************************************************************************\n", + "The marketing-data-engine-demo-terraform-state Google Cloud Storage bucket already exists. \n", + "****************************************************************************************************\n", + "\u001b[0;36mCreating a new Google Cloud Storage bucket to store the Terraform state in marketing-data-engine-demo project, bucket: marketing-data-engine-demo-terraform-state \u001b[1;36m- done\u001b[0m\n", + "\n", + "\n", + "****************************************************************************************************\n", + "\u001b[0;36mCreating terraform backend.tf configuration file\u001b[0m \n", + "****************************************************************************************************\n", + "Generating the Terraform backend configuration file: infrastructure/terraform/backend.tf\n", + "terraform {\n", + " backend \"gcs\" {\n", + " bucket = \"marketing-data-engine-demo-terraform-state\"\n", + " prefix = \"state\"\n", + " }\n", + "}\n", + "****************************************************************************************************\n", + "\u001b[0;36mCreating terraform backend.tf configuration file \u001b[1;36m- done\u001b[0m\n", + "\n", + "\n", + "****************************************************************************************************\n", + "You got the end the of your generate-tf-backend script with everything working. \n", + "****************************************************************************************************\n" + ] + }, + { + "output_type": "stream", + "name": "stderr", + "text": [ + "\n", + "WARNING: apt does not have a stable CLI interface. Use with caution in scripts.\n", + "\n", + "W: Skipping acquire of configured file 'main/source/Sources' as repository 'https://r2u.stat.illinois.edu/ubuntu jammy InRelease' does not seem to provide it (sources.list entry misspelt?)\n", + "\n", + "WARNING: apt does not have a stable CLI interface. Use with caution in scripts.\n", + "\n", + "debconf: unable to initialize frontend: Dialog\n", + "debconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 78, <> line 34.)\n", + "debconf: falling back to frontend: Readline\n", + "debconf: unable to initialize frontend: Readline\n", + "debconf: (This frontend requires a controlling tty.)\n", + "debconf: falling back to frontend: Teletype\n", + "dpkg-preconfigure: unable to re-open stdin: \n", + "creating virtual environment...\n", + "creating shared libraries...\n", + "upgrading shared libraries...\n", + "installing poetry...\n", + "⚠️ Note: '/root/.local/bin' is not on your PATH environment variable. These apps will not be\n", + " globally accessible until your PATH is updated. Run `pipx ensurepath` to automatically add it,\n", + " or manually modify your PATH in your shell's config file (i.e. ~/.bashrc).\n", + "done! ✨ 🌟 ✨\n", + "Creating virtualenv marketing-analytics-jumpstart in /content/marketing-analytics-jumpstart/.venv\n", + "Cloning into '/root/.tfenv'...\n", + "#=#=- # \r\r######################### 27.2%\r############################################################################################# 100.0%\n", + "Updated property [core/project].\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "%%bash\n", + "TERRAFORM_RUN_DIR=$(pwd)/infrastructure/terraform\n", + "cp $TERRAFORM_RUN_DIR/terraform-sample.tfvars $TERRAFORM_RUN_DIR/terraform.tfvars -v" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "NDQl55V1WkwK", + "outputId": "ca59a5f1-3c63-4935-a915-4c294b021753" + }, + "execution_count": 10, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "'/content/marketing-analytics-jumpstart/infrastructure/terraform/terraform-sample.tfvars' -> '/content/marketing-analytics-jumpstart/infrastructure/terraform/terraform.tfvars'\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "%%bash\n", + "export PATH=\"$PATH:~/.tfenv/bin\"\n", + "export GOOGLE_APPLICATION_CREDENTIALS=/content/.config/application_default_credentials.json\n", + "TERRAFORM_RUN_DIR=$(pwd)/infrastructure/terraform\n", + "terraform -chdir=\"${TERRAFORM_RUN_DIR}\" init" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "5UIbC_z9bgy4", + "outputId": "89e04de8-d861-478d-f2c8-c62bb887e4c7" + }, + "execution_count": 11, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "\n", + "\u001b[0m\u001b[1mInitializing the backend...\u001b[0m\n", + "\u001b[0m\u001b[32m\n", + "Successfully configured the backend \"gcs\"! Terraform will automatically\n", + "use this backend unless the backend configuration changes.\u001b[0m\n", + "\u001b[0m\u001b[1mInitializing modules...\u001b[0m\n", + "- activation in modules/activation\n", + "Downloading registry.terraform.io/terraform-google-modules/gcloud/google 3.1.2 for activation.activation_pipeline_container...\n", + "- activation.activation_pipeline_container in .terraform/modules/activation.activation_pipeline_container\n", + "Downloading registry.terraform.io/terraform-google-modules/gcloud/google 3.1.2 for activation.activation_pipeline_template...\n", + "- activation.activation_pipeline_template in .terraform/modules/activation.activation_pipeline_template\n", + "Downloading registry.terraform.io/terraform-google-modules/gcloud/google 3.1.2 for activation.add_invoker_binding...\n", + "- activation.add_invoker_binding in .terraform/modules/activation.add_invoker_binding\n", + "Downloading registry.terraform.io/terraform-google-modules/bigquery/google 5.4.3 for activation.bigquery...\n", + "- activation.bigquery in .terraform/modules/activation.bigquery\n", + "Downloading registry.terraform.io/terraform-google-modules/cloud-storage/google 3.4.1 for activation.build_logs_bucket...\n", + "- activation.build_logs_bucket in .terraform/modules/activation.build_logs_bucket/modules/simple_bucket\n", + "Downloading registry.terraform.io/terraform-google-modules/cloud-storage/google 3.4.1 for activation.function_bucket...\n", + "- activation.function_bucket in .terraform/modules/activation.function_bucket/modules/simple_bucket\n", + "Downloading registry.terraform.io/terraform-google-modules/cloud-storage/google 3.4.1 for activation.pipeline_bucket...\n", + "- activation.pipeline_bucket in .terraform/modules/activation.pipeline_bucket/modules/simple_bucket\n", + "Downloading registry.terraform.io/terraform-google-modules/service-accounts/google 3.0.1 for activation.pipeline_service_account...\n", + "- activation.pipeline_service_account in .terraform/modules/activation.pipeline_service_account\n", + "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for activation.project_services...\n", + "- activation.project_services in .terraform/modules/activation.project_services/modules/project_services\n", + "Downloading registry.terraform.io/GoogleCloudPlatform/secret-manager/google 0.4.0 for activation.secret_manager...\n", + "- activation.secret_manager in .terraform/modules/activation.secret_manager\n", + "Downloading registry.terraform.io/terraform-google-modules/service-accounts/google 3.0.1 for activation.trigger_function_account...\n", + "- activation.trigger_function_account in .terraform/modules/activation.trigger_function_account\n", + "- data_store in modules/data-store\n", + "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for data_store.data_processing_project_services...\n", + "- data_store.data_processing_project_services in .terraform/modules/data_store.data_processing_project_services/modules/project_services\n", + "- data_store.dataform-workflow-dev in modules/dataform-workflow\n", + "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for data_store.dataform-workflow-dev.data_processing_project_services...\n", + "- data_store.dataform-workflow-dev.data_processing_project_services in .terraform/modules/data_store.dataform-workflow-dev.data_processing_project_services/modules/project_services\n", + "- data_store.dataform-workflow-prod in modules/dataform-workflow\n", + "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for data_store.dataform-workflow-prod.data_processing_project_services...\n", + "- data_store.dataform-workflow-prod.data_processing_project_services in .terraform/modules/data_store.dataform-workflow-prod.data_processing_project_services/modules/project_services\n", + "- data_store.dataform-workflow-staging in modules/dataform-workflow\n", + "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for data_store.dataform-workflow-staging.data_processing_project_services...\n", + "- data_store.dataform-workflow-staging.data_processing_project_services in .terraform/modules/data_store.dataform-workflow-staging.data_processing_project_services/modules/project_services\n", + "- feature_store in modules/feature-store\n", + "Downloading registry.terraform.io/terraform-google-modules/bigquery/google 5.4.3 for feature_store.aggregated_predictions...\n", + "- feature_store.aggregated_predictions in .terraform/modules/feature_store.aggregated_predictions\n", + "Downloading registry.terraform.io/terraform-google-modules/bigquery/google 5.4.3 for feature_store.aggregated_vbb...\n", + "- feature_store.aggregated_vbb in .terraform/modules/feature_store.aggregated_vbb\n", + "Downloading registry.terraform.io/terraform-google-modules/bigquery/google 5.4.3 for feature_store.gemini_insights...\n", + "- feature_store.gemini_insights in .terraform/modules/feature_store.gemini_insights\n", + "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for feature_store.project_services...\n", + "- feature_store.project_services in .terraform/modules/feature_store.project_services/modules/project_services\n", + "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for initial_project_services...\n", + "- initial_project_services in .terraform/modules/initial_project_services/modules/project_services\n", + "- monitoring in modules/monitor\n", + "Downloading registry.terraform.io/terraform-google-modules/bigquery/google 5.4.3 for monitoring.dashboard_bigquery...\n", + "- monitoring.dashboard_bigquery in .terraform/modules/monitoring.dashboard_bigquery\n", + "Downloading registry.terraform.io/terraform-google-modules/cloud-storage/google 3.4.1 for monitoring.load_bucket...\n", + "- monitoring.load_bucket in .terraform/modules/monitoring.load_bucket/modules/simple_bucket\n", + "Downloading registry.terraform.io/terraform-google-modules/bigquery/google 5.4.3 for monitoring.log_export_bigquery...\n", + "- monitoring.log_export_bigquery in .terraform/modules/monitoring.log_export_bigquery\n", + "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for monitoring.project_services...\n", + "- monitoring.project_services in .terraform/modules/monitoring.project_services/modules/project_services\n", + "- pipelines in modules/pipelines\n", + "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for pipelines.project_services...\n", + "- pipelines.project_services in .terraform/modules/pipelines.project_services/modules/project_services\n", + "\n", + "\u001b[0m\u001b[1mInitializing provider plugins...\u001b[0m\n", + "- Reusing previous version of hashicorp/random from the dependency lock file\n", + "- Reusing previous version of hashicorp/google from the dependency lock file\n", + "- Reusing previous version of hashicorp/local from the dependency lock file\n", + "- Reusing previous version of hashicorp/null from the dependency lock file\n", + "- Reusing previous version of hashicorp/external from the dependency lock file\n", + "- Reusing previous version of hashicorp/google-beta from the dependency lock file\n", + "- Reusing previous version of hashicorp/template from the dependency lock file\n", + "- Reusing previous version of hashicorp/archive from the dependency lock file\n", + "- Installing hashicorp/random v3.6.2...\n", + "- Installed hashicorp/random v3.6.2 (signed by HashiCorp)\n", + "- Installing hashicorp/google v4.85.0...\n", + "- Installed hashicorp/google v4.85.0 (signed by HashiCorp)\n", + "- Installing hashicorp/local v2.5.1...\n", + "- Installed hashicorp/local v2.5.1 (signed by HashiCorp)\n", + "- Installing hashicorp/null v3.2.2...\n", + "- Installed hashicorp/null v3.2.2 (signed by HashiCorp)\n", + "- Installing hashicorp/external v2.3.3...\n", + "- Installed hashicorp/external v2.3.3 (signed by HashiCorp)\n", + "- Installing hashicorp/google-beta v4.85.0...\n", + "- Installed hashicorp/google-beta v4.85.0 (signed by HashiCorp)\n", + "- Installing hashicorp/template v2.2.0...\n", + "- Installed hashicorp/template v2.2.0 (signed by HashiCorp)\n", + "- Installing hashicorp/archive v2.4.2...\n", + "- Installed hashicorp/archive v2.4.2 (signed by HashiCorp)\n", + "\n", + "\u001b[0m\u001b[1m\u001b[32mTerraform has been successfully initialized!\u001b[0m\u001b[32m\u001b[0m\n", + "\u001b[0m\u001b[32m\n", + "You may now begin working with Terraform. Try running \"terraform plan\" to see\n", + "any changes that are required for your infrastructure. All Terraform commands\n", + "should now work.\n", + "\n", + "If you ever set or change modules or backend configuration for Terraform,\n", + "rerun this command to reinitialize your working directory. If you forget, other\n", + "commands will detect it and remind you to do so if necessary.\u001b[0m\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "%%bash\n", + "export PATH=\"$PATH:~/.tfenv/bin\"\n", + "export PATH=\"/root/.local/bin:$PATH\"\n", + "export GOOGLE_APPLICATION_CREDENTIALS=/content/.config/application_default_credentials.json\n", + "TERRAFORM_RUN_DIR=$(pwd)/infrastructure/terraform\n", + "terraform -chdir=\"${TERRAFORM_RUN_DIR}\" apply -auto-approve" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "BGteib5ebsA-", + "outputId": "c5d16fc9-02e4-401c-cdea-ff50e6c83e6b" + }, + "execution_count": 16, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "\u001b[0m\u001b[1mmodule.activation[0].module.add_invoker_binding.data.external.env_override[0]: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_container.data.external.env_override[0]: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.churn_propensity_query_template_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_template.data.external.env_override[0]: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].data.template_file.resource_link_content: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].data.template_file.resource_link_content: Read complete after 0s [id=64ce73b4b604f03edb5ec083e09452c3331c96b067b1c8b76407addb7db7f9ea]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.auto_audience_segmentation_query_template_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.purchase_propensity_query_template_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.churn_propensity_query_template_file: Read complete after 0s [id=724c878c8d7433cb69440ec7d0072b22c024049e32965018901526063e313d8e]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.purchase_propensity_query_template_file: Read complete after 0s [id=1dcf6acb7f07df18945667501df6467effdb10adc196b46d869d48da8dd7cedb]\u001b[0m\n", + "\u001b[0m\u001b[1mnull_resource.poetry_install: Refreshing state... [id=3295118586910805891]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.cltv_query_template_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_container.data.external.env_override[0]: Read complete after 0s [id=-]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.auto_audience_segmentation_query_template_file: Read complete after 0s [id=dbbfd79a9537c703c751349a98f699a6a056e14458ed0379ab269df1f41fc153]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.archive_file.activation_trigger_source: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_template.data.external.env_override[0]: Read complete after 0s [id=-]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.add_invoker_binding.data.external.env_override[0]: Read complete after 0s [id=-]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.cltv_query_template_file: Read complete after 0s [id=db6dbb8f518920719fad1043a4a9939c8bb43a01dcecd527a5190ce642a757a1]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.archive_file.activation_trigger_source: Read complete after 0s [id=ebf0ad5a3d807457a1566db8eb4e7cbf3bb9fccf]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.audience_segmentation_query_template_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.audience_segmentation_query_template_file: Read complete after 0s [id=06b1ac3524a49e37646faf5a73207e56790449c649bb48f60562984f99461dda]\u001b[0m\n", + "\u001b[0m\u001b[1mdata.external.check_ga4_property_type: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.initial_project_services.google_project_service.project_services[\"iam.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/iam.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mdata.google_project.data_processing_project: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mdata.google_project.feature_store_project: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mdata.google_project.data_project: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].module.project_services.google_project_service.project_services[\"cloudfunctions.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudfunctions.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].module.project_services.google_project_service.project_services[\"bigquerystorage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerystorage.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"secretmanager.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/secretmanager.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].module.project_services.google_project_service.project_services[\"bigquery.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquery.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mdata.google_project.feature_store_project: Read complete after 0s [id=projects/marketing-data-engine-demo]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].module.project_services.google_project_service.project_services[\"logging.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/logging.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mdata.google_project.data_project: Read complete after 0s [id=projects/marketing-data-engine-demo]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].module.project_services.google_project_service.project_services[\"storage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mdata.google_project.data_processing_project: Read complete after 1s [id=projects/marketing-data-engine-demo]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.initial_project_services.google_project_service.project_services[\"serviceusage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/serviceusage.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.initial_project_services.google_project_service.project_services[\"cloudresourcemanager.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudresourcemanager.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mdata.external.check_ga4_property_type: Read complete after 3s [id=-]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.google_project.activation_project: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.google_project.activation_project: Read complete after 0s [id=projects/marketing-data-engine-demo]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"pubsub.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/pubsub.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"run.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/run.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"analyticsadmin.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/analyticsadmin.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"eventarc.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/eventarc.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"cloudfunctions.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudfunctions.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"logging.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/logging.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"cloudbuild.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudbuild.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"aiplatform.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/aiplatform.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"dataflow.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/dataflow.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"bigquerystorage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerystorage.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"storage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.data.google_project.data: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mdata.google_project.activation_project: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.data.google_project.data_processing: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"bigquery.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquery.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"artifactregistry.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/artifactregistry.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"datapipelines.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/datapipelines.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mnull_resource.check_cloudresourcemanager_api: Refreshing state... [id=7670263794778020985]\u001b[0m\n", + "\u001b[0m\u001b[1mnull_resource.check_serviceusage_api: Refreshing state... [id=1492568180087609374]\u001b[0m\n", + "\u001b[0m\u001b[1mnull_resource.check_iam_api: Refreshing state... [id=3508565934624880969]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].module.log_export_bigquery.google_bigquery_dataset.main: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/maj_logs]\u001b[0m\n", + "\u001b[0m\u001b[1mlocal_file.feature_store_configuration: Refreshing state... [id=788628ab0a113d503e087d9c36a9a2811596ce6f]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].null_resource.check_bigquery_api: Refreshing state... [id=2656414359838049201]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].module.load_bucket.google_storage_bucket.bucket: Refreshing state... [id=maj-monitor-marketing-data-engine-demo]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].module.dashboard_bigquery.google_bigquery_dataset.main: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/maj_dashboard]\u001b[0m\n", + "\u001b[0m\u001b[1mnull_resource.generate_sql_queries: Refreshing state... [id=2393578150071004952]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].data.local_file.config_vars: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].data.local_file.config_vars: Read complete after 0s [id=788628ab0a113d503e087d9c36a9a2811596ce6f]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.config_vars: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.config_vars: Read complete after 0s [id=788628ab0a113d503e087d9c36a9a2811596ce6f]\u001b[0m\n", + "\u001b[0m\u001b[1mdata.google_project.activation_project: Read complete after 0s [id=projects/marketing-data-engine-demo]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.data.google_project.data: Read complete after 0s [id=projects/marketing-data-engine-demo]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_scoped_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.data.google_project.data_processing: Read complete after 0s [id=projects/marketing-data-engine-demo]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.audience_segmentation_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_customer_lifetime_value_label_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_scoped_metrics_file: Read complete after 0s [id=760d0f1abad75b40f94cba6f8b755eb9168ddd97]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.audience_segmentation_inference_preparation_file: Read complete after 0s [id=2ca6061d44e46abe67064d4cf174f1b571f45436]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_scoped_segmentation_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_customer_lifetime_value_label_file: Read complete after 0s [id=78b0602291c6392193f8cfd3c671800f28a56ff8]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_dimensions_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_scoped_segmentation_metrics_file: Read complete after 0s [id=deea5f33a03b31cb2bc085e594b76c2590f8b825]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.aggregate_predictions_procedure_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_lookback_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.aggregate_predictions_procedure_file: Read complete after 0s [id=bdbfca8f24a8e81adc69e97a420b2c3201f3144e]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_dimensions_file: Read complete after 0s [id=84fd3207e632f84de0131668d1725120500bd150]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.auto_audience_segmentation_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_scoped_segmentation_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_lookback_metrics_file: Read complete after 0s [id=e7a35f2eefa0ee9e5389e7693c9fa07d70db4506]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.auto_audience_segmentation_inference_preparation_file: Read complete after 0s [id=62861e2b95a13ab037f3dd16cddfa4144c278d48]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_churn_propensity_label_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_scoped_segmentation_metrics_file: Read complete after 0s [id=763088b82fa23df0f6212ebfe1142e011a2099ec]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_rolling_window_lifetime_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_scoped_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_churn_propensity_label_file: Read complete after 0s [id=be3cd744fefb4f0274034f5aa61a72ae28bab11d]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_rolling_window_lifetime_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_scoped_metrics_file: Read complete after 0s [id=bebc3487af20b4d0e99ee7342a835de2e38de1ba]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_rolling_window_lifetime_metrics_file: Read complete after 0s [id=7edd04385f0ec5be4fe48f9c74c15a242a2d1ac2]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_rolling_window_lifetime_metrics_file: Read complete after 0s [id=6374b3b8b7800098fe5c5190237a9003eeb54230]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_lifetime_dimensions_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_dimensions_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_session_event_aggregated_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_churn_propensity_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_lifetime_dimensions_file: Read complete after 0s [id=40c4bc9e723757d1d16663cd697cfbaa48e54c21]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_dimensions_file: Read complete after 0s [id=c80a6fa05d59a8502a39677b71588959e3e63686]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_aggregated_value_based_bidding_explanation_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_rolling_window_lifetime_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_churn_propensity_inference_preparation_file: Read complete after 0s [id=39ef14261e85ab46f26281166b22e963212901c7]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_purchase_propensity_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_churn_propensity_label_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.customer_lifetime_value_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_session_event_aggregated_metrics_file: Read complete after 0s [id=2879b6fe96ffec63ceff10f5ebe4a886837c006e]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_churn_propensity_label_file: Read complete after 0s [id=9188ef958cff6dd66262fd076ddc27d92d03b804]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_customer_lifetime_value_label_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.customer_lifetime_value_training_preparation_file: Read complete after 0s [id=110df07218afe491e0902f8f5a808346f5d831e3]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_rolling_window_lifetime_metrics_file: Read complete after 0s [id=e148532aae05f4088e6034fffa0ef8b7eb0281d4]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_aggregated_value_based_bidding_explanation_preparation_file: Read complete after 0s [id=ee70aca9a40d8a161f00f6562e2968d30eba4869]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_purchase_propensity_training_preparation_file: Read complete after 0s [id=ae2897051f8b33c150bff048d6f2c102b1cb47e6]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_churn_propensity_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_churn_propensity_training_preparation_file: Read complete after 0s [id=49699a3d20984e92433cc7cfd50d29d416171871]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_segmentation_dimensions_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_customer_lifetime_value_label_file: Read complete after 0s [id=07b5288f71916ed9990ccc3a2ecb4945c8569f73]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.audience_segmentation_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_auto_audience_segmentation_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.create_gemini_model_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.aggregated_value_based_bidding_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.purchase_propensity_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_aggregated_value_based_bidding_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_segmentation_dimensions_file: Read complete after 1s [id=5e900e3e599ef5aca9609680c1a8048485bb4d51]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_auto_audience_segmentation_training_preparation_file: Read complete after 0s [id=68764abc7cda326dbb45de97f17286a401b7114d]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.create_gemini_model_file: Read complete after 0s [id=82b596fa76c21048b916e351833200330ae88ca8]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.aggregated_value_based_bidding_training_preparation_file: Read complete after 0s [id=70ff243c2c3e69ba2fd0240882bdf33e23e01e6d]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.audience_segmentation_training_preparation_file: Read complete after 0s [id=d1eec023979a32226d70627bc2e1ba0ed209d0ca]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.purchase_propensity_inference_preparation_file: Read complete after 0s [id=230377cff84fe2855e8e57f7aacc2ca6d2240c92]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_behaviour_revenue_insights_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_aggregated_value_based_bidding_training_preparation_file: Read complete after 0s [id=fb6f9e5120088e00ac529c159e55925b6fb70510]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_audience_segmentation_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.churn_propensity_label_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_scoped_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_customer_lifetime_value_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.aggregated_value_based_bidding_explanation_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_scoped_metrics_file: Read complete after 0s [id=1d0c5da299854209360e9f2926593a32165bf691]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_rolling_window_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_audience_segmentation_training_preparation_file: Read complete after 0s [id=77827024513585dda1b7fdb9f818d75395ac489f]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.churn_propensity_label_file: Read complete after 0s [id=aa1c7025b5e8c528734e8fe3cb1a81e77011aebf]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.customer_lifetime_value_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_customer_lifetime_value_training_preparation_file: Read complete after 0s [id=575308fa039dc402b9622d91ca963d7d6fd64b04]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_behaviour_revenue_insights_file: Read complete after 0s [id=d0469ef07b1af1bd6f5a7734e358876260ebc0ea]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.aggregated_value_based_bidding_explanation_preparation_file: Read complete after 0s [id=5893caf35f39e5712ef4ded8ef5116a804c0af8b]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_scoped_segmentation_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_segmentation_dimensions_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.churn_propensity_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_rolling_window_metrics_file: Read complete after 0s [id=e0f00bc875ea4120b54f83952213b5c62139b899]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_auto_audience_segmentation_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.customer_lifetime_value_inference_preparation_file: Read complete after 0s [id=56b111c18322fb36e668352dc08bd5e36799d48a]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.auto_audience_segmentation_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_scoped_segmentation_metrics_file: Read complete after 0s [id=c576d3bb57d7420c83d5472ae7ed01a272640e89]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_segmentation_dimensions_file: Read complete after 0s [id=57b217004b4745ad21dccfb3b914da4bb297059b]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_scoped_lifetime_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.churn_propensity_training_preparation_file: Read complete after 0s [id=4fcfa23dcd02f8ce06d393539424898a41ec445d]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_auto_audience_segmentation_inference_preparation_file: Read complete after 0s [id=d617c2157a1502028a78ad7cca450a2ef8ba5607]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_scoped_lifetime_metrics_file: Read complete after 0s [id=13940f8e8c94f2efdccc4eb1e9acf5f7d5dc9e93]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_purchase_propensity_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_scoped_lifetime_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_lifetime_dimensions_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_behaviour_revenue_insights_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.auto_audience_segmentation_training_preparation_file: Read complete after 0s [id=bc3b459fe435770cd93d1b61a6956ae96d1d90eb]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_lookback_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_purchase_propensity_inference_preparation_file: Read complete after 0s [id=8a77bdf831da660f03b24d9c1482a6695b69a19b]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.churn_propensity_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_lifetime_dimensions_file: Read complete after 0s [id=947fc99966b0160f1721ccb2abc04b422ba7f059]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_segmentation_dimensions_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_rolling_window_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_lookback_metrics_file: Read complete after 0s [id=5edd7141ec198b01c6b6e993be06549c17019b1e]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_scoped_lifetime_metrics_file: Read complete after 0s [id=d0397322704462e9717bef186fc5cf6c3a9aa4b5]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_behaviour_revenue_insights_file: Read complete after 0s [id=df36f60c61c4c2c9cf9ef5b8c53a64622ff00220]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_session_event_aggregated_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_segmentation_dimensions_file: Read complete after 0s [id=46f042eca23a4fe43c5953e134934317875c1105]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_dimensions_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_behaviour_revenue_insights_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_rolling_window_metrics_file: Read complete after 0s [id=229c5639f871227d270840724a617199f058d3ef]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_customer_lifetime_value_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.churn_propensity_inference_preparation_file: Read complete after 0s [id=5bdf9f82c60521335560c81bb32069888e00a8d9]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.purchase_propensity_label_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.purchase_propensity_label_file: Read complete after 0s [id=a1a11059018e381457080aec68389f4577109488]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_dimensions_file: Read complete after 0s [id=5574be9cca12dbff5cb0eb877f8a94527a13951c]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_session_event_aggregated_metrics_file: Read complete after 0s [id=191d7fbe19131ceba28bbe0838e81beff1dea8f1]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_purchase_propensity_label_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_purchase_propensity_label_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_behaviour_revenue_insights_file: Read complete after 0s [id=5c461d3be6695a79f5483fd019701e6d8538c627]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_customer_lifetime_value_inference_preparation_file: Read complete after 0s [id=55140b739ba351cc125d7be25200ba1b12b7a296]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_scoped_lifetime_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.customer_lifetime_value_label_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_purchase_propensity_label_file: Read complete after 0s [id=066fe56500f86214527b0adc051a0ea376f88622]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_session_event_aggregated_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_purchase_propensity_label_file: Read complete after 0s [id=d78c7179d439382816af0c340a692068922febad]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_lookback_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.customer_lifetime_value_label_file: Read complete after 0s [id=bfc8b316a4b45c3f202e76367a602f90c6fe3bbb]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_audience_segmentation_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_session_event_aggregated_metrics_file: Read complete after 0s [id=a556ca9e2183e3e300a4b1247d55b25f0c99d030]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_rolling_window_metrics_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_scoped_lifetime_metrics_file: Read complete after 0s [id=573da784054b04d57f5493f075616937ed327962]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_lookback_metrics_file: Read complete after 0s [id=a7d1b5ffc634ab9679251276a241821090f3fc4e]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_audience_segmentation_inference_preparation_file: Read complete after 0s [id=8914c65e34723ec4b902f45ae8facae72d4d57cc]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_lifetime_dimensions_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_rolling_window_metrics_file: Read complete after 0s [id=e23d108f49605228fa8f8d385f2b6a619f65f5c8]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.purchase_propensity_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].module.log_export_bigquery.google_bigquery_table.main[\"dataform_googleapis_com_workflow_invocation_completion\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/maj_logs/tables/dataform_googleapis_com_workflow_invocation_completion]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].module.log_export_bigquery.google_bigquery_table.main[\"dataflow_googleapis_com_job_message\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/maj_logs/tables/dataflow_googleapis_com_job_message]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_lifetime_dimensions_file: Read complete after 0s [id=1bc273a710e938405d9011add005299b2cb0c5e1]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].module.dashboard_bigquery.google_bigquery_table.main[\"resource_link\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/maj_dashboard/tables/resource_link]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].module.log_export_bigquery.google_bigquery_table.main[\"aiplatform_googleapis_com_pipeline_job_events\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/maj_logs/tables/aiplatform_googleapis_com_pipeline_job_events]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.purchase_propensity_training_preparation_file: Read complete after 0s [id=192cd59e14b1d2b667af0d28fe115f182f7fec0f]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"bigquerystorage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerystorage.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"datapipelines.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/datapipelines.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"cloudfunctions.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudfunctions.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"dataform.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/dataform.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"bigquery.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquery.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"monitoring.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/monitoring.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"secretmanager.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/secretmanager.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"storage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"cloudasset.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudasset.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"logging.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/logging.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"analyticsadmin.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/analyticsadmin.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].google_logging_project_sink.mds_daily_execution: Refreshing state... [id=projects/marketing-data-engine-demo/sinks/mds_execution_export]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].data.template_file.looker_studio_dashboard_url: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].google_logging_project_sink.vertex_pipeline_execution: Refreshing state... [id=projects/marketing-data-engine-demo/sinks/vertex_pipeline_execution_export]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"monitoring.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/monitoring.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].google_logging_project_sink.activation_pipeline_execution: Refreshing state... [id=projects/marketing-data-engine-demo/sinks/activation_pipeline_execution_export]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].data.template_file.looker_studio_dashboard_url: Read complete after 0s [id=f5f47a24cf6dc9666f197b91b27762ea36a19f0f0cc6105e2e48ea203e2c9eff]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].google_storage_bucket_object.resource_link_load_file: Refreshing state... [id=maj-monitor-marketing-data-engine-demo-load_links.json]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"aiplatform.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/aiplatform.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"bigquerystorage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerystorage.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"storage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"dataflow.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/dataflow.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"bigquery.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquery.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"storage-api.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage-api.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"bigqueryconnection.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigqueryconnection.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"aiplatform.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/aiplatform.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"logging.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/logging.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"artifactregistry.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/artifactregistry.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"artifactregistry.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/artifactregistry.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"cloudbuild.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudbuild.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"logging.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/logging.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"bigquery.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquery.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"bigquerystorage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerystorage.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"storage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"storage-api.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage-api.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"monitoring.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/monitoring.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].google_project_iam_member.activation_pipeline_execution_member: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:service-216235317801@gcp-sa-logging.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_predictions.google_bigquery_dataset.main: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_predictions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_cloudfunctions_api: Refreshing state... [id=7208890644649950059]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_pubsub_api: Refreshing state... [id=7167048528977996500]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_dataflow_api: Refreshing state... [id=6429955671735647781]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].google_project_iam_member.vertex_pipeline_execution_member: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:service-216235317801@gcp-sa-logging.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].google_bigquery_job.monitor_resources_load: Refreshing state... [id=projects/marketing-data-engine-demo/jobs/ec487817-9b8b-b529-3cd1-7877b020d4a4]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.monitoring[0].google_project_iam_member.mds_daily_execution_member: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:service-216235317801@gcp-sa-logging.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_cloudbuild_api: Refreshing state... [id=5865646642453962477]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_artifactregistry_api: Refreshing state... [id=6007568475502613289]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_bigquery_api: Refreshing state... [id=1332910930633736970]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_analyticsadmin_api: Refreshing state... [id=1766041404048401478]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.null_resource.check_dataform_api: Refreshing state... [id=4310407749740216459]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.null_resource.check_secretmanager_api: Refreshing state... [id=2934745949246817981]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_secretmanager_api: Refreshing state... [id=6757606984707921070]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.null_resource.check_bigquery_api: Refreshing state... [id=6485525931220944598]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_pubsub_topic.activation_trigger: Refreshing state... [id=projects/marketing-data-engine-demo/topics/activation-trigger]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.google_project.project: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_artifact_registry_repository.activation_repository: Refreshing state... [id=projects/marketing-data-engine-demo/locations/us-central1/repositories/activation-docker-repo]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].null_resource.create_custom_dimensions: Refreshing state... [id=1067855015837694190]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.google_project_iam_member.email-role[\"roles/dataform.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/dataform.admin/user:ctimoteo@google.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].null_resource.create_custom_events: Refreshing state... [id=7226775609353110261]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.google_project.project: Read complete after 0s [id=projects/marketing-data-engine-demo]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.google_project_iam_member.email-role[\"roles/dataform.editor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/dataform.editor/user:ctimoteo@google.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.google_project_iam_member.email-role[\"roles/iam.serviceAccountUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/iam.serviceAccountUser/user:ctimoteo@google.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.google_secret_manager_secret.github-secret: Refreshing state... [id=projects/marketing-data-engine-demo/secrets/Github_token]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.check_bigquery_api: Refreshing state... [id=6074515469762063310]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.check_dataflow_api: Refreshing state... [id=5913183956771365927]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.check_artifactregistry_api: Refreshing state... [id=3794145044155992161]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.check_aiplatform_api: Refreshing state... [id=1275100169983256448]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_service_account.service_accounts[\"trigger-function\"]: Refreshing state... [id=projects/marketing-data-engine-demo/serviceAccounts/activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.google_service_account.service_accounts[\"dataflow-worker\"]: Refreshing state... [id=projects/marketing-data-engine-demo/serviceAccounts/activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.bigquery.google_bigquery_dataset.main: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/activation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/run.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/run.admin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.builds.editor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.builds.editor/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/iam.serviceAccountUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/iam.serviceAccountUser/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.workerPoolEditor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.workerPoolEditor/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.serviceAgent\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.serviceAgent/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.tokenAccessor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.tokenAccessor/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudfunctions.developer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudfunctions.developer/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.integrations.owner\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.integrations.owner/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.connectionViewer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.connectionViewer/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/storage.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/storage.admin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/container.developer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/container.developer/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/logging.logWriter\"]: Refreshing state... [id=marketing-data-engine-demo/roles/logging.logWriter/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/logging.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/logging.admin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.builds.builder\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.builds.builder/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/iam.serviceAccountAdmin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/iam.serviceAccountAdmin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/appengine.appAdmin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/appengine.appAdmin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.builds.viewer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.builds.viewer/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/owner\"]: Refreshing state... [id=marketing-data-engine-demo/roles/owner/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/viewer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/viewer/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/artifactregistry.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/artifactregistry.admin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.workerPoolOwner\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.workerPoolOwner/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/compute.instanceAdmin.v1\"]: Refreshing state... [id=marketing-data-engine-demo/roles/compute.instanceAdmin.v1/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.workerPoolViewer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.workerPoolViewer/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/secretmanager.secretAccessor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/secretmanager.secretAccessor/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/firebase.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/firebase.admin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/iam.serviceAccountTokenCreator\"]: Refreshing state... [id=marketing-data-engine-demo/roles/iam.serviceAccountTokenCreator/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.readTokenAccessor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.readTokenAccessor/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.integrations.editor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.integrations.editor/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.integrations.viewer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.integrations.viewer/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudkms.cryptoKeyDecrypter\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudkms.cryptoKeyDecrypter/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.connectionAdmin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.connectionAdmin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.builds.approver\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.builds.approver/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.workerPoolUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.workerPoolUser/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_predictions.google_bigquery_table.main[\"latest\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_predictions/tables/latest]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.wait_for_dataflow_worker_sa_creation: Refreshing state... [id=4997368680923922129]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_artifact_registry_repository.pipelines_docker_repo: Refreshing state... [id=projects/marketing-data-engine-demo/locations/us-central1/repositories/pipelines-docker-repo]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_storage_bucket.custom_model_bucket: Refreshing state... [id=marketing-data-engine-demo-custom-models]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_storage_bucket.pipelines_bucket: Refreshing state... [id=marketing-data-engine-demo-pipelines]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_service_account.service_account: Refreshing state... [id=projects/marketing-data-engine-demo/serviceAccounts/vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_artifact_registry_repository.pipelines-repo: Refreshing state... [id=projects/marketing-data-engine-demo/locations/us-central1/repositories/pipelines-repo]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.wait_for_vertex_pipelines_sa_creation: Refreshing state... [id=2244952251481429642]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.secret_manager.google_secret_manager_secret.secrets[\"ga4-measurement-id\"]: Refreshing state... [id=projects/marketing-data-engine-demo/secrets/ga4-measurement-id]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.secret_manager.google_secret_manager_secret.secrets[\"ga4-measurement-secret\"]: Refreshing state... [id=projects/marketing-data-engine-demo/secrets/ga4-measurement-secret]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.google_secret_manager_secret_version.secret-version-github: Refreshing state... [id=projects/216235317801/secrets/Github_token/versions/1]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.data.google_secret_manager_secret.github_secret_name: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.data.google_secret_manager_secret.github_secret_name: Read complete after 0s\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/storage.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/storage.admin/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/secretmanager.secretAccessor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/secretmanager.secretAccessor/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/artifactregistry.reader\"]: Refreshing state... [id=marketing-data-engine-demo/roles/artifactregistry.reader/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/dataflow.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/dataflow.admin/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/bigquery.dataEditor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/iam.serviceAccountUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/iam.serviceAccountUser/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/dataflow.worker\"]: Refreshing state... [id=marketing-data-engine-demo/roles/dataflow.worker/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/pubsub.editor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/pubsub.editor/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"secretmanager.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/secretmanager.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"bigquerymigration.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerymigration.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"cloudscheduler.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudscheduler.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"bigquery.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquery.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"dataform.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/dataform.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"logging.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/logging.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"bigquerydatatransfer.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerydatatransfer.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"monitoring.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/monitoring.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"storage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"bigquerystorage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerystorage.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.data.template_file.keys[\"dataflow-worker\"]: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"workflows.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/workflows.googleapis.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.data.template_file.keys[\"trigger-function\"]: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.data.template_file.keys[\"dataflow-worker\"]: Read complete after 0s [id=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.google_project_iam_member.project-roles[\"dataflow-worker-marketing-data-engine-demo=>roles/bigquery.dataEditor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.data.template_file.keys[\"trigger-function\"]: Read complete after 0s [id=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.google_project_iam_member.project-roles[\"dataflow-worker-marketing-data-engine-demo=>roles/dataflow.worker\"]: Refreshing state... [id=marketing-data-engine-demo/roles/dataflow.worker/serviceAccount:activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.google_project_iam_member.project-roles[\"dataflow-worker-marketing-data-engine-demo=>roles/bigquery.jobUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.jobUser/serviceAccount:activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.google_project_iam_member.project-roles[\"dataflow-worker-marketing-data-engine-demo=>roles/artifactregistry.writer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/artifactregistry.writer/serviceAccount:activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.google_project_iam_member.project-roles[\"dataflow-worker-marketing-data-engine-demo=>roles/dataflow.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/dataflow.admin/serviceAccount:activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/pubsub.publisher\"]: Refreshing state... [id=marketing-data-engine-demo/roles/pubsub.publisher/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/bigquery.jobUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.jobUser/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.build_push_pipelines_components_image: Refreshing state... [id=8222856044757880866]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/compute.osLogin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/compute.osLogin/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/aiplatform.user\"]: Refreshing state... [id=marketing-data-engine-demo/roles/aiplatform.user/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/iap.tunnelResourceAccessor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/iap.tunnelResourceAccessor/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/artifactregistry.reader\"]: Refreshing state... [id=marketing-data-engine-demo/roles/artifactregistry.reader/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/bigquery.dataEditor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/dataflow.developer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/dataflow.developer/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/bigquery.connectionUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.connectionUser/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/storage.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/storage.admin/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_mds_project_roles[\"roles/bigquery.dataViewer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataViewer/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.secret_manager.google_secret_manager_secret_version.secret-version[\"ga4-measurement-id\"]: Refreshing state... [id=projects/216235317801/secrets/ga4-measurement-id/versions/1]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.secret_manager.google_secret_manager_secret_version.secret-version[\"ga4-measurement-secret\"]: Refreshing state... [id=projects/216235317801/secrets/ga4-measurement-secret/versions/1]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.google_dataform_repository.marketing-analytics: Refreshing state... [id=projects/marketing-data-engine-demo/locations/us-central1/repositories/marketing-analytics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_feature_engineering_auto_audience_segmentation_pipeline: Refreshing state... [id=4177109568613300128]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.check_pipeline_docker_image_pushed: Refreshing state... [id=670239096754469362]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].null_resource.check_bigquery_api: Refreshing state... [id=8823785816326872758]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].null_resource.check_aiplatform_api: Refreshing state... [id=592572463848586336]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_feature_engineering_aggregated_value_based_bidding_pipeline: Refreshing state... [id=3313216741825247466]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.build_logs_bucket.google_storage_bucket.bucket: Refreshing state... [id=activation-logs-marketing-data-engine-demo]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_dataset.feature_store: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_connection.vertex_ai_connection: Refreshing state... [id=projects/marketing-data-engine-demo/locations/US/connections/vertex_ai]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_feature_engineering_audience_segmentation_pipeline: Refreshing state... [id=745359039134428554]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_dataset.purchase_propensity: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/purchase_propensity]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_dataset.auto_audience_segmentation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/auto_audience_segmentation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_dataset.audience_segmentation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/audience_segmentation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_dataset.churn_propensity: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/churn_propensity]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.aggregate_last_day_predictions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_predictions/routines/aggregate_last_day_predictions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_dataset.customer_lifetime_value: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/customer_lifetime_value]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.build_logs_bucket.google_storage_bucket_iam_member.members[\"roles/storage.admin serviceAccount:216235317801-compute@developer.gserviceaccount.com\"]: Refreshing state... [id=b/activation-logs-marketing-data-engine-demo/roles/storage.admin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.null_resource.wait_for_dataform_sa_creation: Refreshing state... [id=5068235229441173816]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_feature_engineering_purchase_propensity_pipeline: Refreshing state... [id=6301381851538149321]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.function_bucket.google_storage_bucket.bucket: Refreshing state... [id=activation-trigger-marketing-data-engine-demo]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_bucket.google_storage_bucket.bucket: Refreshing state... [id=activation-app-marketing-data-engine-demo]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.gemini_insights.google_bigquery_dataset.main: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/gemini_insights]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_project_iam_member.vertex_ai_connection_sa_roles[\"roles/bigquery.connectionAdmin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.connectionAdmin/serviceAccount:bqcx-216235317801-k5go@gcp-sa-bigquery-condel.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_project_iam_member.vertex_ai_connection_sa_roles[\"roles/bigquery.jobUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.jobUser/serviceAccount:bqcx-216235317801-k5go@gcp-sa-bigquery-condel.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_project_iam_member.vertex_ai_connection_sa_roles[\"roles/storage.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/storage.admin/serviceAccount:bqcx-216235317801-k5go@gcp-sa-bigquery-condel.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_project_iam_member.vertex_ai_connection_sa_roles[\"roles/storage.objectViewer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/storage.objectViewer/serviceAccount:bqcx-216235317801-k5go@gcp-sa-bigquery-condel.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_project_iam_member.vertex_ai_connection_sa_roles[\"roles/aiplatform.user\"]: Refreshing state... [id=marketing-data-engine-demo/roles/aiplatform.user/serviceAccount:bqcx-216235317801-k5go@gcp-sa-bigquery-condel.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_project_iam_member.vertex_ai_connection_sa_roles[\"roles/bigquery.connectionUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.connectionUser/serviceAccount:bqcx-216235317801-k5go@gcp-sa-bigquery-condel.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_project_iam_member.vertex_ai_connection_sa_roles[\"roles/bigquery.dataEditor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:bqcx-216235317801-k5go@gcp-sa-bigquery-condel.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.purchase_propensity_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/purchase_propensity/tables/purchase_propensity_inference_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.purchase_propensity_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/purchase_propensity/routines/purchase_propensity_inference_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_purchase_propensity_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/purchase_propensity/routines/invoke_purchase_propensity_training_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.purchase_propensity_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/purchase_propensity/routines/purchase_propensity_training_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_purchase_propensity_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/purchase_propensity/routines/invoke_purchase_propensity_inference_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_auto_audience_segmentation_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/auto_audience_segmentation/routines/invoke_auto_audience_segmentation_training_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_vbb.google_bigquery_dataset.main: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_auto_audience_segmentation_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/auto_audience_segmentation/routines/invoke_auto_audience_segmentation_inference_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.auto_audience_segmentation_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/auto_audience_segmentation/routines/auto_audience_segmentation_inference_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.auto_audience_segmentation_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/auto_audience_segmentation/routines/auto_audience_segmentation_training_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.customer_lifetime_value_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/customer_lifetime_value_label]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_purchase_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_purchase_propensity_label]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_segmentation_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_segmentation_dimensions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_segmentation_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_segmentation_dimensions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_lookback_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_lookback_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_customer_lifetime_value_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_customer_lifetime_value_label]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_segmentation_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_segmentation_dimensions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_rolling_window_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_rolling_window_lifetime_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_churn_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_churn_propensity_label]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_purchase_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_purchase_propensity_label]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_dimensions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_rolling_window_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_rolling_window_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_lifetime_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_lifetime_dimensions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_dimensions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_rolling_window_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_rolling_window_lifetime_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.churn_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/churn_propensity_label]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_lookback_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_lookback_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_session_event_aggregated_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_session_event_aggregated_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_scoped_segmentation_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_scoped_segmentation_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_dimensions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_scoped_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_scoped_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.churn_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/churn_propensity_label]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_scoped_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_scoped_lifetime_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_rolling_window_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_rolling_window_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_rolling_window_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_rolling_window_lifetime_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_rolling_window_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_rolling_window_lifetime_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.purchase_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/purchase_propensity_label]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_lookback_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_lookback_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_scoped_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_scoped_lifetime_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_lifetime_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_lifetime_dimensions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_segmentation_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_segmentation_dimensions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_scoped_segmentation_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_scoped_segmentation_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_scoped_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_scoped_lifetime_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_lifetime_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_lifetime_dimensions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_scoped_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_scoped_lifetime_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.purchase_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/purchase_propensity_label]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_scoped_segmentation_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_scoped_segmentation_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_session_event_aggregated_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_session_event_aggregated_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_churn_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_churn_propensity_label]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_rolling_window_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_rolling_window_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_scoped_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_scoped_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_lookback_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_lookback_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_lifetime_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_lifetime_dimensions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_customer_lifetime_value_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_customer_lifetime_value_label]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_session_event_aggregated_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_session_event_aggregated_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_dimensions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_session_event_aggregated_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_session_event_aggregated_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_rolling_window_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_rolling_window_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_scoped_segmentation_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_scoped_segmentation_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_scoped_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_scoped_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.customer_lifetime_value_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/customer_lifetime_value_label]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_scoped_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_scoped_metrics]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_audience_segmentation_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/audience_segmentation/routines/invoke_audience_segmentation_inference_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.audience_segmentation_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/audience_segmentation/routines/audience_segmentation_inference_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.audience_segmentation_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/audience_segmentation/tables/audience_segmentation_inference_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.audience_segmentation_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/audience_segmentation/routines/audience_segmentation_training_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_audience_segmentation_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/audience_segmentation/routines/invoke_audience_segmentation_training_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.google_bigquery_dataset_iam_member.dataform-ga4-export-reader: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/analytics_434623633/roles/bigquery.dataViewer/serviceAccount:service-216235317801@gcp-sa-dataform.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.google_bigquery_dataset_iam_member.dataform-ads-export-reader[0]: Refreshing state... [id=projects/google.com:superb-receiver-344820/datasets/ads_demoverse/roles/bigquery.dataViewer/serviceAccount:service-216235317801@gcp-sa-dataform.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.google_project_iam_member.dataform-serviceaccount[\"roles/bigquery.jobUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.jobUser/serviceAccount:service-216235317801@gcp-sa-dataform.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.google_project_iam_member.dataform-serviceaccount[\"roles/secretmanager.secretAccessor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/secretmanager.secretAccessor/serviceAccount:service-216235317801@gcp-sa-dataform.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.google_project_iam_member.dataform-bigquery-data-owner[\"roles/bigquery.dataOwner\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataOwner/serviceAccount:service-216235317801@gcp-sa-dataform.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_feature_engineering_churn_propensity_pipeline: Refreshing state... [id=3833299164693309312]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_churn_propensity_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/churn_propensity/routines/invoke_churn_propensity_inference_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_churn_propensity_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/churn_propensity/routines/invoke_churn_propensity_training_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.churn_propensity_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/churn_propensity/routines/churn_propensity_inference_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.churn_propensity_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/churn_propensity/tables/churn_propensity_inference_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.churn_propensity_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/churn_propensity/routines/churn_propensity_training_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.customer_lifetime_value_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/customer_lifetime_value/routines/customer_lifetime_value_training_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_customer_lifetime_value_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/customer_lifetime_value/routines/invoke_customer_lifetime_value_training_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.customer_lifetime_value_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/customer_lifetime_value/tables/customer_lifetime_value_inference_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.customer_lifetime_value_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/customer_lifetime_value/routines/customer_lifetime_value_inference_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_customer_lifetime_value_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/customer_lifetime_value/routines/invoke_customer_lifetime_value_inference_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.function_bucket.google_storage_bucket_iam_member.members[\"roles/storage.admin serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com\"]: Refreshing state... [id=b/activation-trigger-marketing-data-engine-demo/roles/storage.admin/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_bucket.google_storage_bucket_iam_member.members[\"roles/storage.admin serviceAccount:activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com\"]: Refreshing state... [id=b/activation-app-marketing-data-engine-demo/roles/storage.admin/serviceAccount:activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.gemini_insights.google_bigquery_table.main[\"user_behaviour_revenue_insights_daily\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/gemini_insights/tables/user_behaviour_revenue_insights_daily]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.gemini_insights.google_bigquery_table.main[\"user_behaviour_revenue_insights_weekly\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/gemini_insights/tables/user_behaviour_revenue_insights_weekly]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.gemini_insights.google_bigquery_table.main[\"user_behaviour_revenue_insights_monthly\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/gemini_insights/tables/user_behaviour_revenue_insights_monthly]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_vbb.google_bigquery_table.main[\"vbb_weights\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/tables/vbb_weights]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_vbb.google_bigquery_table.main[\"aggregated_value_based_bidding_correlation\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/tables/aggregated_value_based_bidding_correlation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_vbb.google_bigquery_table.main[\"aggregated_value_based_bidding_volume_daily\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/tables/aggregated_value_based_bidding_volume_daily]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_vbb.google_bigquery_table.main[\"aggregated_value_based_bidding_volume_weekly\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/tables/aggregated_value_based_bidding_volume_weekly]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_feature_engineering_customer_lifetime_value_pipeline: Refreshing state... [id=4148256259979416441]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.audience_segmentation_query_template_file: Refreshing state... [id=activation-app-marketing-data-engine-demo-configuration/audience_segmentation_query_template.sqlx]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.activation_trigger_archive: Refreshing state... [id=activation-trigger-marketing-data-engine-demo-activation_trigger_source.zip]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.purchase_propensity_query_template_file: Refreshing state... [id=activation-app-marketing-data-engine-demo-configuration/purchase_propensity_query_template.sqlx]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.auto_audience_segmentation_csv_export_query: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.auto_audience_segmentation_csv_export_query: Read complete after 0s [id=2a1b777fbb6328fd3380f0159aac23b5473cdad5b69e0ed24c14450938b9952e]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.audience_segmentation_csv_export_query: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.audience_segmentation_csv_export_query: Read complete after 0s [id=dccb98247aa53c588cebe1033ba41da7baa5ebd346f011e27c6f24d5b73afa1f]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.cltv_query_template_file: Refreshing state... [id=activation-app-marketing-data-engine-demo-configuration/cltv_query_template.sqlx]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.churn_propensity_csv_export_query: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.churn_propensity_csv_export_query: Read complete after 0s [id=b638a2fe7ccfd7a55819164378fd9904d7e0b04beb0cafe22f4cd083bf1bc5c8]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.purchase_propensity_csv_export_query: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.purchase_propensity_csv_export_query: Read complete after 0s [id=0c6e9ef9db797d508fa929999f43aaf37f538ec42621d8c4b6ccea3133656132]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.churn_propensity_query_template_file: Refreshing state... [id=activation-app-marketing-data-engine-demo-configuration/churn_propensity_query_template.sqlx]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.auto_audience_segmentation_query_template_file: Refreshing state... [id=activation-app-marketing-data-engine-demo-configuration/auto_audience_segmentation_query_template.sqlx]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.measurement_protocol_payload_template_file: Refreshing state... [id=activation-app-marketing-data-engine-demo-configuration/app_payload_template.jinja2]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].null_resource.check_gemini_insights_dataset_exists: Refreshing state... [id=8420743605622533331]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.cltv_csv_export_query: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.cltv_csv_export_query: Read complete after 0s [id=1f1fd2457b657c5a4624922d5e9729860fdc87452af04f051fbabf531cc14922]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_aggregated_value_based_bidding_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/routines/invoke_aggregated_value_based_bidding_training_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.aggregated_value_based_bidding_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/routines/aggregated_value_based_bidding_training_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.aggregated_value_based_bidding_explanation_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/routines/aggregated_value_based_bidding_explanation_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_aggregated_value_based_bidding_explanation_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/routines/invoke_aggregated_value_based_bidding_explanation_preparation]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_container.null_resource.module_depends_on[0]: Refreshing state... [id=83278692015671361]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_purchase_propensity_training_pipelines: Refreshing state... [id=6494667367103048029]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_bigquery_routine.export_auto_audience_segmentation_procedure: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/activation/routines/export_auto_audience_segmentation_predictions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_bigquery_routine.export_audience_segmentation_procedure: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/activation/routines/export_audience_segmentation_predictions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_bigquery_routine.export_purchase_propensity_procedure: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/activation/routines/export_purchase_propensity_predictions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_bigquery_routine.export_churn_propensity_procedure: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/activation/routines/export_churn_propensity_predictions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].null_resource.create_gemini_model: Refreshing state... [id=8588488219624433529]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].null_resource.check_cloudscheduler_api: Refreshing state... [id=2245006727529558797]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].null_resource.check_dataform_api: Refreshing state... [id=4577413674659493716]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].null_resource.check_bigquery_api: Refreshing state... [id=6932712767637221730]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].null_resource.check_workflows_api: Refreshing state... [id=6145400500190950450]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_bigquery_routine.export_cltv_procedure: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/activation/routines/export_cltv_predictions]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_purchase_propensity_prediction_pipelines: Refreshing state... [id=2471105579040554729]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_container.null_resource.run_command[0]: Refreshing state... [id=4110452576566970343]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_container.null_resource.run_destroy_command[0]: Refreshing state... [id=8209321593200338708]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].google_service_account.scheduler: Refreshing state... [id=projects/marketing-data-engine-demo/serviceAccounts/workflow-scheduler-prod@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].null_resource.check_gemini_model_exists: Refreshing state... [id=5380384111160946416]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].google_service_account.workflow-dataform: Refreshing state... [id=projects/marketing-data-engine-demo/serviceAccounts/workflow-dataform-prod@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.activation_type_configuration: Reading...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.activation_type_configuration: Read complete after 0s [id=34e91f0be3d922532ab8101964b3614388b215dfa965935ef82cb248836681e0]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].null_resource.wait_for_scheduler_sa_creation: Refreshing state... [id=3576051581150556634]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].null_resource.wait_for_workflows_sa_creation: Refreshing state... [id=5608692098556736051]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_propensity_clv_training_pipelines: Refreshing state... [id=2697769765996893796]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.activation_type_configuration_file: Refreshing state... [id=activation-app-marketing-data-engine-demo-configuration/activation_type_configuration.json]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_behaviour_revenue_insights: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/gemini_insights/routines/invoke_user_behaviour_revenue_insights]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_behaviour_revenue_insights: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/gemini_insights/routines/invoke_backfill_user_behaviour_revenue_insights]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_behaviour_revenue_insights: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/gemini_insights/routines/user_behaviour_revenue_insights]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_clv_training_pipelines: Refreshing state... [id=1341591284116756448]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].google_project_iam_member.scheduler-workflow-invoker: Refreshing state... [id=marketing-data-engine-demo/roles/workflows.invoker/serviceAccount:workflow-scheduler-prod@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].google_workflows_workflow.dataform-incremental-workflow: Refreshing state... [id=projects/marketing-data-engine-demo/locations/us-central1/workflows/dataform-prod-incremental]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].google_project_iam_member.worflow-dataform-dataform-editor: Refreshing state... [id=marketing-data-engine-demo/roles/dataform.editor/serviceAccount:workflow-dataform-prod@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_clv_prediction_pipelines: Refreshing state... [id=5219397379491605160]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_template.null_resource.module_depends_on[0]: Refreshing state... [id=9213524499649794300]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_segmentation_training_pipelines: Refreshing state... [id=4209036632928598274]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Refreshing state... [id=projects/marketing-data-engine-demo/locations/us-central1/functions/activation-trigger]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_template.null_resource.additional_components[0]: Refreshing state... [id=3284535500699919722]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_segmentation_prediction_pipelines: Refreshing state... [id=8422606662598937866]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_template.null_resource.run_command[0]: Refreshing state... [id=7045031403572418317]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_auto_segmentation_training_pipelines: Refreshing state... [id=4338505239913806672]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_template.null_resource.run_destroy_command[0]: Refreshing state... [id=4355940137061160409]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_template.null_resource.additional_components_destroy[0]: Refreshing state... [id=2166393773558090798]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_auto_segmentation_prediction_pipelines: Refreshing state... [id=8725944810905589710]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_value_based_bidding_training_pipelines: Refreshing state... [id=1646002337402538490]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_value_based_bidding_explanation_pipelines: Refreshing state... [id=9016639052566760345]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_reporting_preparation_aggregate_predictions_pipelines: Refreshing state... [id=7138244549178947766]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_churn_propensity_training_pipelines: Refreshing state... [id=5723798157680302731]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].google_cloud_scheduler_job.daily-dataform-increments: Refreshing state... [id=projects/marketing-data-engine-demo/locations/us-central1/jobs/daily-dataform-prod]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_churn_propensity_prediction_pipelines: Refreshing state... [id=3337108508686395106]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_gemini_insights_pipelines: Refreshing state... [id=2341849278084692537]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.add_invoker_binding.null_resource.run_command[0]: Refreshing state... [id=2685704107534572535]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].module.add_invoker_binding.null_resource.run_destroy_command[0]: Refreshing state... [id=8513968182222052250]\u001b[0m\n", + "\n", + "Terraform used the selected providers to generate the following execution\n", + "plan. Resource actions are indicated with the following symbols:\n", + " \u001b[32m+\u001b[0m create\u001b[0m\n", + " \u001b[33m~\u001b[0m update in-place\u001b[0m\n", + "\u001b[31m-\u001b[0m/\u001b[32m+\u001b[0m destroy and then create replacement\u001b[0m\n", + "\n", + "Terraform will perform the following actions:\n", + "\n", + "\u001b[1m # module.activation[0].google_cloudfunctions2_function.activation_trigger_cf\u001b[0m will be updated in-place\n", + "\u001b[0m \u001b[33m~\u001b[0m\u001b[0m resource \"google_cloudfunctions2_function\" \"activation_trigger_cf\" {\n", + " id = \"projects/marketing-data-engine-demo/locations/us-central1/functions/activation-trigger\"\n", + " name = \"activation-trigger\"\n", + " \u001b[90m# (7 unchanged attributes hidden)\u001b[0m\u001b[0m\n", + "\n", + " \u001b[33m~\u001b[0m\u001b[0m service_config {\n", + " \u001b[33m~\u001b[0m\u001b[0m environment_variables = {\n", + " \u001b[31m-\u001b[0m\u001b[0m \"LOG_EXECUTION_ID\" = \"true\" \u001b[90m-> null\u001b[0m\u001b[0m\n", + " \u001b[90m# (7 unchanged elements hidden)\u001b[0m\u001b[0m\n", + " }\n", + " \u001b[90m# (11 unchanged attributes hidden)\u001b[0m\u001b[0m\n", + "\n", + " \u001b[90m# (2 unchanged blocks hidden)\u001b[0m\u001b[0m\n", + " }\n", + "\n", + " \u001b[90m# (2 unchanged blocks hidden)\u001b[0m\u001b[0m\n", + " }\n", + "\n", + "\u001b[1m # module.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/bigquery.dataEditor\"]\u001b[0m will be created\n", + "\u001b[0m \u001b[32m+\u001b[0m\u001b[0m resource \"google_project_iam_member\" \"dataflow_worker_sa_roles\" {\n", + " \u001b[32m+\u001b[0m\u001b[0m etag = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m id = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m member = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m project = \"marketing-data-engine-demo\"\n", + " \u001b[32m+\u001b[0m\u001b[0m role = \"roles/bigquery.dataEditor\"\n", + " }\n", + "\n", + "\u001b[1m # module.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/bigquery.jobUser\"]\u001b[0m will be created\n", + "\u001b[0m \u001b[32m+\u001b[0m\u001b[0m resource \"google_project_iam_member\" \"dataflow_worker_sa_roles\" {\n", + " \u001b[32m+\u001b[0m\u001b[0m etag = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m id = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m member = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m project = \"marketing-data-engine-demo\"\n", + " \u001b[32m+\u001b[0m\u001b[0m role = \"roles/bigquery.jobUser\"\n", + " }\n", + "\n", + "\u001b[1m # module.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/dataflow.worker\"]\u001b[0m will be created\n", + "\u001b[0m \u001b[32m+\u001b[0m\u001b[0m resource \"google_project_iam_member\" \"dataflow_worker_sa_roles\" {\n", + " \u001b[32m+\u001b[0m\u001b[0m etag = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m id = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m member = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m project = \"marketing-data-engine-demo\"\n", + " \u001b[32m+\u001b[0m\u001b[0m role = \"roles/dataflow.worker\"\n", + " }\n", + "\n", + "\u001b[1m # module.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/storage.objectAdmin\"]\u001b[0m will be created\n", + "\u001b[0m \u001b[32m+\u001b[0m\u001b[0m resource \"google_project_iam_member\" \"dataflow_worker_sa_roles\" {\n", + " \u001b[32m+\u001b[0m\u001b[0m etag = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m id = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m member = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m project = \"marketing-data-engine-demo\"\n", + " \u001b[32m+\u001b[0m\u001b[0m role = \"roles/storage.objectAdmin\"\n", + " }\n", + "\n", + "\u001b[1m # module.pipelines[0].google_service_account.dataflow_worker_service_account\u001b[0m will be created\n", + "\u001b[0m \u001b[32m+\u001b[0m\u001b[0m resource \"google_service_account\" \"dataflow_worker_service_account\" {\n", + " \u001b[32m+\u001b[0m\u001b[0m account_id = \"df-worker\"\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"Service Account to run Dataflow jobs\"\n", + " \u001b[32m+\u001b[0m\u001b[0m disabled = false\n", + " \u001b[32m+\u001b[0m\u001b[0m display_name = \"df-worker\"\n", + " \u001b[32m+\u001b[0m\u001b[0m email = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m id = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m member = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m name = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m project = \"marketing-data-engine-demo\"\n", + " \u001b[32m+\u001b[0m\u001b[0m unique_id = (known after apply)\n", + " }\n", + "\n", + "\u001b[1m # module.pipelines[0].google_service_account_iam_member.dataflow_sa_iam\u001b[0m will be created\n", + "\u001b[0m \u001b[32m+\u001b[0m\u001b[0m resource \"google_service_account_iam_member\" \"dataflow_sa_iam\" {\n", + " \u001b[32m+\u001b[0m\u001b[0m etag = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m id = (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m member = \"serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com\"\n", + " \u001b[32m+\u001b[0m\u001b[0m role = \"roles/iam.serviceAccountUser\"\n", + " \u001b[32m+\u001b[0m\u001b[0m service_account_id = (known after apply)\n", + " }\n", + "\n", + "\u001b[1m # module.feature_store[0].module.aggregated_predictions.google_bigquery_table.main[\"latest\"]\u001b[0m must be \u001b[1m\u001b[31mreplaced\u001b[0m\n", + "\u001b[0m\u001b[31m-\u001b[0m/\u001b[32m+\u001b[0m\u001b[0m resource \"google_bigquery_table\" \"main\" {\n", + " \u001b[33m~\u001b[0m\u001b[0m creation_time = 1727272973853 -> (known after apply)\n", + " \u001b[33m~\u001b[0m\u001b[0m etag = \"LyDuL6p3V8G2Eqzr58kqcg==\" -> (known after apply)\n", + " \u001b[33m~\u001b[0m\u001b[0m expiration_time = 0 -> (known after apply)\n", + " \u001b[32m+\u001b[0m\u001b[0m friendly_name = \"latest\"\n", + " \u001b[33m~\u001b[0m\u001b[0m id = \"projects/marketing-data-engine-demo/datasets/aggregated_predictions/tables/latest\" -> (known after apply)\n", + " \u001b[31m-\u001b[0m\u001b[0m labels = {} \u001b[90m-> null\u001b[0m\u001b[0m\n", + " \u001b[33m~\u001b[0m\u001b[0m last_modified_time = 1727272973854 -> (known after apply)\n", + " \u001b[33m~\u001b[0m\u001b[0m location = \"US\" -> (known after apply)\n", + " \u001b[33m~\u001b[0m\u001b[0m num_bytes = 15101769 -> (known after apply)\n", + " \u001b[33m~\u001b[0m\u001b[0m num_long_term_bytes = 0 -> (known after apply)\n", + " \u001b[33m~\u001b[0m\u001b[0m num_rows = 37715 -> (known after apply)\n", + " \u001b[33m~\u001b[0m\u001b[0m schema = jsonencode(\n", + " \u001b[33m~\u001b[0m\u001b[0m [\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"Customer lifetime value revenue gains per user pseudo id\"\n", + " name = \"ltv\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"Processing timestamp of the customer lifetime value\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"user_pseudo_id\" \u001b[33m->\u001b[0m\u001b[0m \"ltv_processed_timestamp\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"STRING\" \u001b[33m->\u001b[0m\u001b[0m \"TIMESTAMP\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"Date the customer lifetime value was predicted\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"ltv_processed_timestamp\" \u001b[33m->\u001b[0m\u001b[0m \"ltv_feature_date\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"TIMESTAMP\" \u001b[33m->\u001b[0m\u001b[0m \"DATE\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"Decile of the customer lifetime value revenue gain per user pseudo id\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"ltv_feature_date\" \u001b[33m->\u001b[0m\u001b[0m \"ltv_decile\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"DATE\" \u001b[33m->\u001b[0m\u001b[0m \"INTEGER\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"Likelihood to purchase per user pseudo id\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"ltv_decile\" \u001b[33m->\u001b[0m\u001b[0m \"likely_to_purchase\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"INTEGER\" \u001b[33m->\u001b[0m\u001b[0m \"STRING\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The prediction related to whether the user is going to purchase in the future per user pseudo id\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"likely_to_purchase\" \u001b[33m->\u001b[0m\u001b[0m \"purchase_score\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"STRING\" \u001b[33m->\u001b[0m\u001b[0m \"FLOAT\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The timestamp of when the prediction was calculated\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchase_score\" \u001b[33m->\u001b[0m\u001b[0m \"purchase_processed_timestamp\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"FLOAT\" \u001b[33m->\u001b[0m\u001b[0m \"TIMESTAMP\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"Date in which the purchase propensity was calculated\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchase_processed_timestamp\" \u001b[33m->\u001b[0m\u001b[0m \"purchase_feature_date\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"TIMESTAMP\" \u001b[33m->\u001b[0m\u001b[0m \"DATE\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"Decile of the purchase propensity likelihood per user pseudo id\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchase_feature_date\" \u001b[33m->\u001b[0m\u001b[0m \"p_p_decile\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"DATE\" \u001b[33m->\u001b[0m\u001b[0m \"INTEGER\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in the past 30 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"p_p_decile\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_1_30_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in the past 30 to 60 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_1_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_30_60_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 60 to 90 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_30_60_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_60_90_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 90 to 120 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_60_90_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_90_120_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 120 to 150 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_90_120_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_120_150_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 150 to 180 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_120_150_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_150_180_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 30 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_150_180_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_1_30_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 30 to 60 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_1_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_30_60_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 60 to 90 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_30_60_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_60_90_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 90 to 120 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_60_90_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_90_120_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 120 to 150 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_90_120_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_120_150_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 150 to 180 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_120_150_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_150_180_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited the website or app in the past 30 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_150_180_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_1_30_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited the website or app in the past 30 to 60 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_1_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_30_60_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited the website or app in the past 60 to 90 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_30_60_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_60_90_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited the website or app in the past 90 to 120 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_60_90_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_90_120_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited the website or app in the past 120 to 150 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_90_120_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_120_150_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited the website or app in the past 150 to 180 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_120_150_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_150_180_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 30 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_150_180_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_1_30_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 30 to 60 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_1_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_30_60_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 60 to 90 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_30_60_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_60_90_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 90 to 120 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_60_90_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_90_120_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 120 to 150 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_90_120_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_120_150_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 150 to 180 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_120_150_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_150_180_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to cart in the past 30 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_150_180_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_1_30_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to cart in the past 30 to 60 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_1_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_30_60_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to cart in the past 60 to 90 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_30_60_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_60_90_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to cart in the past 90 to 120 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_60_90_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_90_120_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to cart in the past 120 to 150 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_90_120_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_120_150_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to cart in the past 150 to 180 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_120_150_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_150_180_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 30 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_150_180_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_1_30_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 30 to 60 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_1_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_30_60_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 30 to 60 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_30_60_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_60_90_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 90 to 120 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_60_90_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_90_120_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 120 to 150 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_90_120_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_120_150_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 150 to 180 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_120_150_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_150_180_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The segment id for that user predicted by the audience segmentation model\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_150_180_day\" \u001b[33m->\u001b[0m\u001b[0m \"Segment_ID\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The segment distance for that user predicted by the audience segmentation model\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"Segment_ID\" \u001b[33m->\u001b[0m\u001b[0m \"Segment_Distance\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"INTEGER\" \u001b[33m->\u001b[0m\u001b[0m \"FLOAT\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"Processing timestamp of the audience segmentation model\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"Segment_Distance\" \u001b[33m->\u001b[0m\u001b[0m \"segment_processed_timestamp\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"FLOAT\" \u001b[33m->\u001b[0m\u001b[0m \"TIMESTAMP\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"Date in which the audience segmentation was calculated\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"segment_processed_timestamp\" \u001b[33m->\u001b[0m\u001b[0m \"segment_feature_date\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"TIMESTAMP\" \u001b[33m->\u001b[0m\u001b[0m \"DATE\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 7 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"segment_feature_date\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_1_7_day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"DATE\" \u001b[33m->\u001b[0m\u001b[0m \"INTEGER\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 7 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_1_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_1_7_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 7 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_1_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_1_7_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 7 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_1_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_1_7_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to cart in the past 7 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_1_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_1_7_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 7 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_1_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_1_7_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The customer lifetime value gained in the past 7 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_1_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"ltv_revenue_past_1_7_day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"INTEGER\" \u001b[33m->\u001b[0m\u001b[0m \"FLOAT\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The customer lifetime value gained in the past 7 to 15 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"ltv_revenue_past_1_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"ltv_revenue_past_7_15_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The device brand name last accessed by the user\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"ltv_revenue_past_7_15_day\" \u001b[33m->\u001b[0m\u001b[0m \"device_mobile_brand_name\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"FLOAT\" \u001b[33m->\u001b[0m\u001b[0m \"STRING\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The device operating system last accessed by the user\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"device_mobile_brand_name\" \u001b[33m->\u001b[0m\u001b[0m \"device_os\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The device language last accessed by the user\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"device_os\" \u001b[33m->\u001b[0m\u001b[0m \"device_language\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The device web browser last accessed by the user\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"device_language\" \u001b[33m->\u001b[0m\u001b[0m \"device_web_browser\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The geo sub continent last accessed by the user\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"device_web_browser\" \u001b[33m->\u001b[0m\u001b[0m \"geo_sub_continent\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The geo metro last accessed by the user\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"geo_sub_continent\" \u001b[33m->\u001b[0m\u001b[0m \"geo_metro\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"A flag indicating whether the user has signed in and contains a user id\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"geo_metro\" \u001b[33m->\u001b[0m\u001b[0m \"has_signed_in_with_user_id\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"STRING\" \u001b[33m->\u001b[0m\u001b[0m \"BOOLEAN\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The current customer lifetime value of the user\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"has_signed_in_with_user_id\" \u001b[33m->\u001b[0m\u001b[0m \"user_ltv_revenue\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"BOOLEAN\" \u001b[33m->\u001b[0m\u001b[0m \"FLOAT\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"user_ltv_revenue\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_1_day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"FLOAT\" \u001b[33m->\u001b[0m\u001b[0m \"INTEGER\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 2nd day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_1_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_2_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 3rd day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_2_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_3_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 4th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_3_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_4_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 5th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_4_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_5_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 6th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_5_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_6_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 7th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_6_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_7_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in the past 15 to 30 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_15_30_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_15_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_1_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 2nd day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_1_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_2_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 3rd day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_2_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_3_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 4th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_3_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_4_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 5th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_4_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_5_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 6th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_5_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_6_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 7th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_6_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_7_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 15 to 30 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_15_30_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_15_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_1_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 2nd day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_1_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_2_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 3rd day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_2_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_3_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 4th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_3_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_4_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 5th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_4_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_5_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 6th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_5_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_6_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 7th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_6_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_7_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 15 to 30 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_15_30_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_15_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_1_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 2nd day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_1_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_2_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 3rd day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_2_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_3_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 4th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_3_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_4_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 5th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_4_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_5_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 6th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_5_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_6_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 7th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_6_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_7_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 15 to 30 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_15_30_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_15_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_1_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 2nd day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_1_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_2_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 3rd day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_2_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_3_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 4th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_3_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_4_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 5th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_4_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_5_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 6th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_5_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_6_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 7th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_6_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_7_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 15 to 30 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_15_30_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_15_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_1_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 2nd day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_1_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_2_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 3rd day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_2_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_3_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 4th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_3_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_4_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 5th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_4_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_5_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 6th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_5_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_6_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 7th day\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_6_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_7_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 15 to 30 days\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_15_30_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The last user identifier logged in\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_15_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"user_id\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"INTEGER\" \u001b[33m->\u001b[0m\u001b[0m \"STRING\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The last device category used\"\n", + " name = \"device_category\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The last device model name used\"\n", + " name = \"device_mobile_model_name\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The last country the user was from\"\n", + " name = \"geo_country\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The last geo region the user was from\"\n", + " name = \"geo_region\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The last geo city the user was from\"\n", + " name = \"geo_city\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The last traffic source medium\"\n", + " name = \"last_traffic_source_medium\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The last traffic source name\"\n", + " name = \"last_traffic_source_name\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The last traffic source source\"\n", + " name = \"last_traffic_source_source\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The first traffic source medium\"\n", + " name = \"first_traffic_source_medium\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The first traffic source name\"\n", + " name = \"first_traffic_source_name\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The first traffic source source\"\n", + " name = \"first_traffic_source_source\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in the past 8 to 14 days\"\n", + " name = \"active_users_past_8_14_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has made a purchase in the past 8 to 14 days\"\n", + " name = \"purchases_past_8_14_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited the site in the past 8 to 14 days\"\n", + " name = \"visits_past_8_14_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 8 to 14 days\"\n", + " name = \"view_items_past_8_14_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 8 to 14 days\"\n", + " name = \"add_to_carts_past_8_14_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 8 to 14 days\"\n", + " name = \"checkouts_past_8_14_day\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"Likelihood to churn per user pseudo id\"\n", + " name = \"likely_to_churn\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"Churn score per user pseudo id\"\n", + " name = \"churn_score\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The timestamp of when the prediction was calculated\"\n", + " name = \"churn_processed_timestamp\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"Date in which the churn propensity was calculated\"\n", + " name = \"churn_feature_date\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"Decile of the churn propensity likelihood per user pseudo id\"\n", + " name = \"c_p_decile\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The user pseudo identifer\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"user_id\" \u001b[33m->\u001b[0m\u001b[0m \"user_pseudo_id\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The timestamp of when the prediction was calculated\"\n", + " name = \"auto_segment_processed_timestamp\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The Auto Segment ID\"\n", + " name = \"Auto_Segment_ID\"\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"INTEGER\" \u001b[33m->\u001b[0m\u001b[0m \"STRING\"\n", + " },\n", + " \u001b[33m~\u001b[0m\u001b[0m {\n", + " \u001b[32m+\u001b[0m\u001b[0m description = \"The segment distance for that user predicted by the auto audience segmentation model\"\n", + " \u001b[33m~\u001b[0m\u001b[0m name = \"Auto_Segment_Distance\" \u001b[33m->\u001b[0m\u001b[0m \"Auto Segment_Distance\"\n", + " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"homepage\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Clearance\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Apparel_Mens_sortci_newest_desc\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"basket_html\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_New_sortci_newest_desc\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Lifestyle_Bags\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"store_html\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Lifestyle_Drinkware\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"signin_html\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Apparel_sortci_newest_desc\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Apparel_Womens\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Stationery_sortci_newest_desc\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"yourinfo_html\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Shop_by_Brand_YouTube\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Apparel_Hats\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"basket_html_vid_20160512512\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Bike_sortci_newest_desc\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"payment_html\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Shop_by_Brand_YouTube_sortci_newest_desc\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"store_html_sortci_newest_desc\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Lifestyle_sortci_newest_desc\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Shop_by_Brand_Google_sortci_newest_desc\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Apparel_Google_Dino_Game_Tee\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Campus_Collection_New_sortci_newest_desc\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Chrome_Dino_sortci_newest_desc\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Stationery_Notebooks_sortci_newest_desc\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"registersuccess_html\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Apparel\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " \u001b[31m-\u001b[0m\u001b[0m {\n", + " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Apparel_Mens_Mens_T_Shirts\"\n", + " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", + " },\n", + " ] \u001b[31m# forces replacement\u001b[0m\u001b[0m\n", + " )\n", + " \u001b[33m~\u001b[0m\u001b[0m self_link = \"https://bigquery.googleapis.com/bigquery/v2/projects/marketing-data-engine-demo/datasets/aggregated_predictions/tables/latest\" -> (known after apply)\n", + " \u001b[33m~\u001b[0m\u001b[0m type = \"TABLE\" -> (known after apply)\n", + " \u001b[90m# (5 unchanged attributes hidden)\u001b[0m\u001b[0m\n", + " }\n", + "\n", + "\u001b[1mPlan:\u001b[0m 7 to add, 1 to change, 1 to destroy.\n", + "\u001b[0m\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_predictions.google_bigquery_table.main[\"latest\"]: Destroying... [id=projects/marketing-data-engine-demo/datasets/aggregated_predictions/tables/latest]\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_service_account.dataflow_worker_service_account: Creating...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Modifying... [id=projects/marketing-data-engine-demo/locations/us-central1/functions/activation-trigger]\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_predictions.google_bigquery_table.main[\"latest\"]: Destruction complete after 0s\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_predictions.google_bigquery_table.main[\"latest\"]: Creating...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_predictions.google_bigquery_table.main[\"latest\"]: Creation complete after 1s [id=projects/marketing-data-engine-demo/datasets/aggregated_predictions/tables/latest]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_service_account.dataflow_worker_service_account: Creation complete after 1s [id=projects/marketing-data-engine-demo/serviceAccounts/df-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/dataflow.worker\"]: Creating...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/storage.objectAdmin\"]: Creating...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_service_account_iam_member.dataflow_sa_iam: Creating...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/bigquery.dataEditor\"]: Creating...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/bigquery.jobUser\"]: Creating...\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_service_account_iam_member.dataflow_sa_iam: Creation complete after 4s [id=projects/marketing-data-engine-demo/serviceAccounts/df-worker@marketing-data-engine-demo.iam.gserviceaccount.com/roles/iam.serviceAccountUser/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/dataflow.worker\"]: Creation complete after 8s [id=marketing-data-engine-demo/roles/dataflow.worker/serviceAccount:df-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/bigquery.jobUser\"]: Creation complete after 8s [id=marketing-data-engine-demo/roles/bigquery.jobUser/serviceAccount:df-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/storage.objectAdmin\"]: Creation complete after 9s [id=marketing-data-engine-demo/roles/storage.objectAdmin/serviceAccount:df-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/bigquery.dataEditor\"]: Creation complete after 9s [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:df-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Still modifying... [id=projects/marketing-data-engine-demo/loc...-central1/functions/activation-trigger, 10s elapsed]\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Still modifying... [id=projects/marketing-data-engine-demo/loc...-central1/functions/activation-trigger, 20s elapsed]\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Still modifying... [id=projects/marketing-data-engine-demo/loc...-central1/functions/activation-trigger, 30s elapsed]\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Still modifying... [id=projects/marketing-data-engine-demo/loc...-central1/functions/activation-trigger, 40s elapsed]\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Still modifying... [id=projects/marketing-data-engine-demo/loc...-central1/functions/activation-trigger, 50s elapsed]\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Modifications complete after 53s [id=projects/marketing-data-engine-demo/locations/us-central1/functions/activation-trigger]\u001b[0m\n", + "\u001b[0m\u001b[1m\u001b[32m\n", + "Apply complete! Resources: 7 added, 1 changed, 1 destroyed.\n", + "\u001b[0m\u001b[0m\u001b[1m\u001b[32m\n", + "Outputs:\n", + "\n", + "\u001b[0mlookerstudio_create_dashboard_url = \"https://lookerstudio.google.com/reporting/create?c.reportId=f61f65fe-4991-45fc-bcdc-80593966f28c&c.explain=true&r.reportName=Marketing%20Analytics%20Sample&ds.GA4_sessions.connector=bigQuery&ds.GA4_sessions.type=TABLE&ds.GA4_sessions.tableId=session_date&ds.GA4_sessions.datasetId=marketing_ga4_v1_prod&ds.GA4_sessions.projectId=marketing-data-engine-demo&ds.GA4_sessions.datasourceName=MDS%20GA4%20Sessions&ds.GA4_session_device.connector=bigQuery&ds.GA4_session_device.type=TABLE&ds.GA4_session_device.tableId=session_device_daily_metrics&ds.GA4_session_device.datasetId=marketing_ga4_v1_prod&ds.GA4_session_device.projectId=marketing-data-engine-demo&ds.GA4_session_device.datasourceName=MDS%20GA4%20Session%20Device&ds.GA4_session_location.connector=bigQuery&ds.GA4_session_location.type=TABLE&ds.GA4_session_location.tableId=session_location_daily_metrics&ds.GA4_session_location.datasetId=marketing_ga4_v1_prod&ds.GA4_session_location.projectId=marketing-data-engine-demo&ds.GA4_session_location.datasourceName=MDS%20GA4%20Session%20Location&ds.GA4_event_page.connector=bigQuery&ds.GA4_event_page.type=TABLE&ds.GA4_event_page.tableId=event_page&ds.GA4_event_page.datasetId=marketing_ga4_v1_prod&ds.GA4_event_page.projectId=marketing-data-engine-demo&ds.GA4_event_page.datasourceName=MDS%20GA4%20Event%20Page&ds.GA4_unique_page_views.connector=bigQuery&ds.GA4_unique_page_views.type=TABLE&ds.GA4_unique_page_views.tableId=unique_page_views&ds.GA4_unique_page_views.datasetId=marketing_ga4_v1_prod&ds.GA4_unique_page_views.projectId=marketing-data-engine-demo&ds.GA4_unique_page_views.datasourceName=MDS%20GA4%20Unique%20Page%20Views&ds.GA4_page_session.connector=bigQuery&ds.GA4_page_session.type=TABLE&ds.GA4_page_session.tableId=page_session_daily_metrics&ds.GA4_page_session.datasetId=marketing_ga4_v1_prod&ds.GA4_page_session.projectId=marketing-data-engine-demo&ds.GA4_page_session.datasourceName=MDS%20GA4%20Page%20Session&ds.Ads_perf_conversions.connector=bigQuery&ds.Ads_perf_conversions.type=TABLE&ds.Ads_perf_conversions.tableId=ad_performance_conversions&ds.Ads_perf_conversions.datasetId=marketing_ads_v1_prod&ds.Ads_perf_conversions.projectId=marketing-data-engine-demo&ds.Ads_perf_conversions.datasourceName=MDS%20Ads%20Ad%20Performance%20x%20Conversions&ds.MAJ_resource_link.connector=bigQuery&ds.MAJ_resource_link.type=TABLE&ds.MAJ_resource_link.tableId=resource_link&ds.MAJ_resource_link.datasetId=maj_dashboard&ds.MAJ_resource_link.projectId=marketing-data-engine-demo&ds.MAJ_resource_link.datasourceName=MAJ%20Resource%20Link&ds.GA4_base_event.connector=bigQuery&ds.GA4_base_event.type=TABLE&ds.GA4_base_event.tableId=event&ds.GA4_base_event.datasetId=marketing_ga4_base_prod&ds.GA4_base_event.projectId=marketing-data-engine-demo&ds.GA4_base_event.datasourceName=MDS%20GA4%20Base%20Event&ds.MDS_execution_log.connector=bigQuery&ds.MDS_execution_log.type=TABLE&ds.MDS_execution_log.tableId=dataform_googleapis_com_workflow_invocation_completion&ds.MDS_execution_log.datasetId=maj_logs&ds.MDS_execution_log.projectId=marketing-data-engine-demo&ds.MDS_execution_log.datasourceName=MDS%20Execution%20Log&ds.Activation_log.connector=bigQuery&ds.Activation_log.type=TABLE&ds.Activation_log.tableId=dataflow_googleapis_com_job_message&ds.Activation_log.datasetId=maj_logs&ds.Activation_log.projectId=marketing-data-engine-demo&ds.Activation_log.datasourceName=Activation%20Execution%20Log&ds.Vertex_log.connector=bigQuery&ds.Vertex_log.type=TABLE&ds.Vertex_log.tableId=aiplatform_googleapis_com_pipeline_job_events&ds.Vertex_log.datasetId=maj_logs&ds.Vertex_log.projectId=marketing-data-engine-demo&ds.Vertex_log.datasourceName=Vertex%20AI%20Pipelines%20Log&ds.Aggregated_vbb_volume_daily.connector=bigQuery&ds.Aggregated_vbb_volume_daily.type=TABLE&ds.Aggregated_vbb_volume_daily.tableId=aggregated_value_based_bidding_volume_daily&ds.Aggregated_vbb_volume_daily.datasetId=aggregated_vbb&ds.Aggregated_vbb_volume_daily.projectId=marketing-data-engine-demo&ds.Aggregated_vbb_volume_daily.datasourceName=Aggregated%20VBB%20Volume%20Daily&ds.Aggregated_vbb_volume_weekly.connector=bigQuery&ds.Aggregated_vbb_volume_weekly.type=TABLE&ds.Aggregated_vbb_volume_weekly.tableId=aggregated_value_based_bidding_volume_weekly&ds.Aggregated_vbb_volume_weekly.datasetId=aggregated_vbb&ds.Aggregated_vbb_volume_weekly.projectId=marketing-data-engine-demo&ds.Aggregated_vbb_volume_weekly.datasourceName=Aggregated%20VBB%20Volume%20Weekly&ds.Aggregated_vbb_correlation.connector=bigQuery&ds.Aggregated_vbb_correlation.type=TABLE&ds.Aggregated_vbb_correlation.tableId=aggregated_value_based_bidding_correlation&ds.Aggregated_vbb_correlation.datasetId=aggregated_vbb&ds.Aggregated_vbb_correlation.projectId=marketing-data-engine-demo&ds.Aggregated_vbb_correlation.datasourceName=Aggregated%20VBB%20Correlation&ds.Aggregated_vbb_weights.connector=bigQuery&ds.Aggregated_vbb_weights.type=TABLE&ds.Aggregated_vbb_weights.tableId=vbb_weights&ds.Aggregated_vbb_weights.datasetId=aggregated_vbb&ds.Aggregated_vbb_weights.projectId=marketing-data-engine-demo&ds.Aggregated_vbb_weights.datasourceName=Aggregated%20VBB%20Weights&ds.Aggregated_predictions.connector=bigQuery&ds.Aggregated_predictions.type=TABLE&ds.Aggregated_predictions.tableId=latest&ds.Aggregated_predictions.datasetId=aggregated_predictions&ds.Aggregated_predictions.projectId=marketing-data-engine-demo&ds.Aggregated_predictions.datasourceName=Aggregated%20Predictions&ds.User_behaviour_revenue_insights_daily.connector=bigQuery&ds.User_behaviour_revenue_insights_daily.type=TABLE&ds.User_behaviour_revenue_insights_daily.tableId=user_behaviour_revenue_insights_daily&ds.User_behaviour_revenue_insights_daily.datasetId=gemini_insights&ds.User_behaviour_revenue_insights_daily.projectId=marketing-data-engine-demo&ds.User_behaviour_revenue_insights_daily.datasourceName=User%20Behaviour%20Revenue%20Insights%20Daily\"\n", + "tf_state_project_id = \"marketing-data-engine-demo\"\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [], + "metadata": { + "id": "EqgA6GSr1t0c" + }, + "execution_count": null, + "outputs": [] + } + ] +} \ No newline at end of file diff --git a/scripts/quick-install.sh b/scripts/quick-install.sh new file mode 100755 index 00000000..908a6399 --- /dev/null +++ b/scripts/quick-install.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env sh + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +#set -x + +. scripts/common.sh + +section_open "Setting the gcloud project id" + # Ask user to input the project id + echo "Input the GCP Project Id where you want to deploy Marketing Analytics Jumpstart:" + read TF_STATE_PROJECT_ID + # Set the project id to the environment variable + export TF_STATE_PROJECT_ID + # Set the project id to the environment variable + export GOOGLE_CLOUD_PROJECT=${TF_STATE_PROJECT_ID} + # Set the project id to the environment variable + export GOOGLE_CLOUD_QUOTA_PROJECT=$GOOGLE_CLOUD_PROJECT + # Set the project id to the environment variable + export PROJECT_ID=$GOOGLE_CLOUD_PROJECT + # Disable prompts + gcloud config set disable_prompts true + # Set the project id to the gcloud configuration + gcloud config set project "${TF_STATE_PROJECT_ID}" +section_close + +section_open "Authenticate to Google Cloud Project" + gcloud auth login --project "${TF_STATE_PROJECT_ID}" + echo "Close the browser tab that was open and press any key to continue.." + read moveon +section_close + +section_open "Setting Google Application Default Credentials" + gcloud config set disable_prompts false + gcloud auth application-default login --quiet --scopes="openid,https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/sqlservice.login,https://www.googleapis.com/auth/analytics,https://www.googleapis.com/auth/analytics.edit,https://www.googleapis.com/auth/analytics.provision,https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/accounts.reauth" + echo "Close the browser tab that was open and press any key to continue.." + read moveon + CREDENTIAL_FILE=`gcloud auth application-default set-quota-project "${PROJECT_ID}" 2>&1 | grep -e "Credentials saved to file:" | cut -d "[" -f2 | cut -d "]" -f1` + export GOOGLE_APPLICATION_CREDENTIALS=${CREDENTIAL_FILE} +section_close + +section_open "Configuring environment" + SOURCE_ROOT=$(pwd) + cd ${SOURCE_ROOT} + + # Install python3.10 + sudo chown -R ctimoteo /usr/local/sbin + chmod u+w /usr/local/sbin + brew install python@3.10 + retVal=$? + if [ $retVal -ne 0 ]; then + apt-get install python3.10 + fi + CLOUDSDK_PYTHON=python3.10 + + # Install pipx + brew install pipx + retVal=$? + if [ $retVal -ne 0 ]; then + sudo apt update + sudo apt install pipx + fi + pipx ensurepath + + #pip3 install poetry + pipx install poetry + export PATH="$HOME/.local/bin:$PATH" + poetry env use python3.10 + poetry --version + + # Install tfenv + tfenv --version + retVal=$? + if [ $retVal -ne 0 ]; then + git clone --depth=1 https://github.com/tfutils/tfenv.git ~/.tfenv + echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bash_profile + echo 'export PATH=$PATH:$HOME/.tfenv/bin' >> ~/.bashrc + fi + export PATH="$PATH:$HOME/.tfenv/bin" + + #mkdir -p ~/.local/bin/ + #. ~/.profile + #ln -s ~/.tfenv/bin/* ~/.local/bin + #which tfenv + + # Install terraform version + tfenv install 1.5.7 + tfenv use 1.5.7 + terraform --version + + # Generate TF backend + . scripts/generate-tf-backend.sh +section_close + +section_open "Preparing Terraform Environment File" + TERRAFORM_RUN_DIR=${SOURCE_ROOT}/infrastructure/terraform + cp -v $TERRAFORM_RUN_DIR/terraform-sample.tfvars $TERRAFORM_RUN_DIR/terraform.tfvars + echo "Edit the terraform.tfvars created and press any key to continue.." + read moveon +section_close + +section_open "Deploying Terraform Infrastructure Resources" + terraform -chdir="${TERRAFORM_RUN_DIR}" init + terraform -chdir="${TERRAFORM_RUN_DIR}" apply +section_close + +#set +x +set +o nounset +set +o errexit From d3425bb009cfad875459030c0601f000ba1847d6 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 25 Sep 2024 11:27:58 -0400 Subject: [PATCH 020/110] Quick install (#197) * predicting for only the users with traffic in the past 72h - purchase propensity * running inference only for users events in the past 72h * including 72h users for all models predictions * considering null values in TabWorkflow models * deleting unused pipfile * upgrading lib versions * implementing reporting preprocessing as a new pipeline * adding more code documentation * adding important information on the main README.md and DEVELOPMENT.md * adding schedule run name and more code documentation * implementing a new scheduler using the vertex ai sdk & adding user_id to procedures for consistency * adding more code documentation * adding code doc to the python custom component * adding more code documentation * fixing aggregated predictions query * removing unnecessary resources from deployment * Writing MDS guide * adding the MDS developer and troubleshooting documentation * fixing deployment for activation pipelines and gemini dataset * Update README.md * Update README.md * Update README.md * Update README.md * removing deprecated api * fixing purchase propensity pipelines names * adding extra condition for when there is not enough data for the window interval to be applied on backfill procedures * adding more instructions for post deployment and fixing issues when GA4 export was configured for less than 10 days * removing unnecessary comments * adding the number of past days to process in the variables files * adding comment about combining data from different ga4 export datasets to data store * fixing small issues with feature engineering and ml pipelines * fixing hyper parameter tuning for kmeans modeling * fixing optuna parameters * adding cloud shell image * fixing the list of all possible users in the propensity training preparation tables * additional guardrails for when there is not enough data * adding more documentation * adding more doc to feature store * add feature store documentation * adding ml pipelines docs * adding ml pipelines docs * adding more documentation * adding user agent client info * fixing scope of client info * fix * removing client_info from vertex components * fixing versioning of tf submodules * reconfiguring meta providers * fixing issue 187 * adding quick installation process * removing state active * fixing notebook header --------- Co-authored-by: Carlos Timoteo --- notebooks/quick_installation.ipynb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/notebooks/quick_installation.ipynb b/notebooks/quick_installation.ipynb index f0089a5f..3cec7db4 100644 --- a/notebooks/quick_installation.ipynb +++ b/notebooks/quick_installation.ipynb @@ -41,18 +41,21 @@ " \"Vertex
Open in Vertex AI Workbench\n", "
\n", " \n", - "\n", - "\n", - "
\n", - "
\n", - "
\n", - "\n", - "Follow this Colab notebook to quick install the Marketing Analytics Jumpstart solution on a Google Cloud Project." + "" ], "metadata": { "id": "AKtB_GVpt2QJ" } }, + { + "cell_type": "markdown", + "source": [ + "Follow this Colab notebook to quick install the Marketing Analytics Jumpstart solution on a Google Cloud Project." + ], + "metadata": { + "id": "mj-8n9jIyTn-" + } + }, { "cell_type": "code", "source": [ From 945cd1cbad9bc61a947d7181719cc3f363334cf3 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 25 Sep 2024 11:33:19 -0400 Subject: [PATCH 021/110] Quick install (#198) * predicting for only the users with traffic in the past 72h - purchase propensity * running inference only for users events in the past 72h * including 72h users for all models predictions * considering null values in TabWorkflow models * deleting unused pipfile * upgrading lib versions * implementing reporting preprocessing as a new pipeline * adding more code documentation * adding important information on the main README.md and DEVELOPMENT.md * adding schedule run name and more code documentation * implementing a new scheduler using the vertex ai sdk & adding user_id to procedures for consistency * adding more code documentation * adding code doc to the python custom component * adding more code documentation * fixing aggregated predictions query * removing unnecessary resources from deployment * Writing MDS guide * adding the MDS developer and troubleshooting documentation * fixing deployment for activation pipelines and gemini dataset * Update README.md * Update README.md * Update README.md * Update README.md * removing deprecated api * fixing purchase propensity pipelines names * adding extra condition for when there is not enough data for the window interval to be applied on backfill procedures * adding more instructions for post deployment and fixing issues when GA4 export was configured for less than 10 days * removing unnecessary comments * adding the number of past days to process in the variables files * adding comment about combining data from different ga4 export datasets to data store * fixing small issues with feature engineering and ml pipelines * fixing hyper parameter tuning for kmeans modeling * fixing optuna parameters * adding cloud shell image * fixing the list of all possible users in the propensity training preparation tables * additional guardrails for when there is not enough data * adding more documentation * adding more doc to feature store * add feature store documentation * adding ml pipelines docs * adding ml pipelines docs * adding more documentation * adding user agent client info * fixing scope of client info * fix * removing client_info from vertex components * fixing versioning of tf submodules * reconfiguring meta providers * fixing issue 187 * adding quick installation process * removing state active * fixing notebook header * removing notebook cells outputs --------- Co-authored-by: Carlos Timoteo --- notebooks/quick_installation.ipynb | 2305 +--------------------------- 1 file changed, 20 insertions(+), 2285 deletions(-) diff --git a/notebooks/quick_installation.ipynb b/notebooks/quick_installation.ipynb index 3cec7db4..4bff574f 100644 --- a/notebooks/quick_installation.ipynb +++ b/notebooks/quick_installation.ipynb @@ -62,30 +62,17 @@ "# @title Input Google Cloud Project ID\n", "# prompt: set PROJECT_ID env variable and run gcloud set project\n", "\n", - "GOOGLE_CLOUD_PROJECT = \"marketing-data-engine-demo\" #@param {type:\"string\"}\n", + "GOOGLE_CLOUD_PROJECT = \"marketing-analytics-jumpstart-project-id\" #@param {type:\"string\"}\n", "GOOGLE_CLOUD_QUOTA_PROJECT = GOOGLE_CLOUD_PROJECT\n", "PROJECT_ID = GOOGLE_CLOUD_PROJECT\n", "!gcloud config set disable_prompts true\n", "!gcloud config set project {PROJECT_ID}" ], "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "dMcepKg8IQWj", - "outputId": "b71f3a1a-97f8-425e-efa1-a41126bdce62" + "id": "dMcepKg8IQWj" }, - "execution_count": 2, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "Updated property [core/disable_prompts].\n", - "Updated property [core/project].\n" - ] - } - ] + "execution_count": null, + "outputs": [] }, { "cell_type": "code", @@ -111,66 +98,18 @@ "!export GOOGLE_APPLICATION_CREDENTIALS=/content/.config/application_default_credentials.json" ], "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "3cAwp6CRLSVf", - "outputId": "df1356d3-e7f8-4670-f609-7d5e9a96d277" + "id": "3cAwp6CRLSVf" }, - "execution_count": 7, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "Updated property [core/disable_prompts].\n", - "Go to the following link in your browser, and complete the sign-in prompts:\n", - "\n", - " https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fsdk.cloud.google.com%2Fapplicationdefaultauthcode.html&scope=openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fsqlservice.login+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fanalytics+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fanalytics.edit+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fanalytics.provision+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fanalytics.readonly+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Faccounts.reauth&state=upHeK5pdqi2RFOTouPRbKKe64tObPX&prompt=consent&token_usage=remote&access_type=offline&code_challenge=PKVsYphOmWNHo5bF55Usd3qUN7QB6NFHKky2Satjags&code_challenge_method=S256\n", - "\n", - "Once finished, enter the verification code provided in your browser: 4/0AQlEd8yyRXzlPHea-h9wRjZMXB4aXGWIyqN2dWciuFe3wtCkClAPzq_vQKAM5ib4ikiV1g\n", - "\n", - "Credentials saved to file: [/content/.config/application_default_credentials.json]\n", - "\n", - "These credentials will be used by any library that requests Application Default Credentials (ADC).\n", - "\n", - "Quota project \"marketing-data-engine-demo\" was added to ADC which can be used by Google client libraries for billing and quota. Note that some services may still bill the project owning the resource.\n", - "\n", - "Credentials saved to file: [/content/.config/application_default_credentials.json]\n", - "\n", - "These credentials will be used by any library that requests Application Default Credentials (ADC).\n", - "\n", - "Quota project \"marketing-data-engine-demo\" was added to ADC which can be used by Google client libraries for billing and quota. Note that some services may still bill the project owning the resource.\n" - ] - } - ] + "execution_count": null, + "outputs": [] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "wBOKo_6aF-GS", - "outputId": "4eb83fa6-3504-4cac-91db-b76ed0193e67" + "id": "wBOKo_6aF-GS" }, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "Cloning into 'marketing-analytics-jumpstart'...\n", - "remote: Enumerating objects: 2535, done.\u001b[K\n", - "remote: Counting objects: 100% (1609/1609), done.\u001b[K\n", - "remote: Compressing objects: 100% (846/846), done.\u001b[K\n", - "remote: Total 2535 (delta 1133), reused 974 (delta 756), pack-reused 926 (from 1)\u001b[K\n", - "Receiving objects: 100% (2535/2535), 18.58 MiB | 21.67 MiB/s, done.\n", - "Resolving deltas: 100% (1509/1509), done.\n", - "/content/marketing-analytics-jumpstart\n" - ] - } - ], + "outputs": [], "source": [ "# prompt: git clone a repository and setting cd to it\n", "REPO=\"marketing-analytics-jumpstart\"\n", @@ -217,550 +156,10 @@ "source ./scripts/generate-tf-backend.sh" ], "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "hmdklTTuQ_9d", - "outputId": "4a133cac-b4ed-48a4-e070-1de791c0d02b" + "id": "hmdklTTuQ_9d" }, - "execution_count": 9, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "Reading package lists...\n", - "Building dependency tree...\n", - "Reading state information...\n", - "python3.10 is already the newest version (3.10.12-1~22.04.6).\n", - "0 upgraded, 0 newly installed, 0 to remove and 49 not upgraded.\n", - "Get:1 https://cloud.r-project.org/bin/linux/ubuntu jammy-cran40/ InRelease [3,626 B]\n", - "Hit:2 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64 InRelease\n", - "Hit:3 http://archive.ubuntu.com/ubuntu jammy InRelease\n", - "Get:4 http://security.ubuntu.com/ubuntu jammy-security InRelease [129 kB]\n", - "Ign:5 https://r2u.stat.illinois.edu/ubuntu jammy InRelease\n", - "Get:6 https://r2u.stat.illinois.edu/ubuntu jammy Release [5,713 B]\n", - "Get:7 http://archive.ubuntu.com/ubuntu jammy-updates InRelease [128 kB]\n", - "Get:8 https://r2u.stat.illinois.edu/ubuntu jammy Release.gpg [793 B]\n", - "Hit:9 https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu jammy InRelease\n", - "Hit:10 https://ppa.launchpadcontent.net/graphics-drivers/ppa/ubuntu jammy InRelease\n", - "Get:11 https://r2u.stat.illinois.edu/ubuntu jammy/main all Packages [8,348 kB]\n", - "Hit:12 http://archive.ubuntu.com/ubuntu jammy-backports InRelease\n", - "Hit:13 https://ppa.launchpadcontent.net/ubuntugis/ppa/ubuntu jammy InRelease\n", - "Get:14 http://security.ubuntu.com/ubuntu jammy-security/main amd64 Packages [2,314 kB]\n", - "Get:15 https://r2u.stat.illinois.edu/ubuntu jammy/main amd64 Packages [2,586 kB]\n", - "Get:16 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 Packages [2,593 kB]\n", - "Get:17 http://archive.ubuntu.com/ubuntu jammy-updates/restricted amd64 Packages [3,191 kB]\n", - "Get:18 http://archive.ubuntu.com/ubuntu jammy-updates/universe amd64 Packages [1,442 kB]\n", - "Fetched 20.7 MB in 3s (7,771 kB/s)\n", - "Reading package lists...\n", - "Building dependency tree...\n", - "Reading state information...\n", - "49 packages can be upgraded. Run 'apt list --upgradable' to see them.\n", - "Reading package lists...\n", - "Building dependency tree...\n", - "Reading state information...\n", - "The following additional packages will be installed:\n", - " fonts-font-awesome fonts-lato javascript-common libjs-bootstrap4\n", - " libjs-highlight.js libjs-lunr libjs-modernizr libjs-popper.js libjs-sizzle\n", - " mkdocs node-jquery python-babel-localedata python3-argcomplete python3-babel\n", - " python3-click python3-colorama python3-jinja2 python3-livereload\n", - " python3-markdown python3-markupsafe python3-packaging python3-pip-whl\n", - " python3-psutil python3-pygments python3-pyinotify python3-setuptools-whl\n", - " python3-tornado python3-tz python3-userpath python3-venv python3-yaml\n", - " python3.10-venv sphinx-rtd-theme-common\n", - "Suggested packages:\n", - " apache2 | lighttpd | httpd libjs-es5-shim ghp-import mkdocs-doc nodejs\n", - " python-jinja2-doc coffeescript node-less node-uglify python-livereload-doc\n", - " python3-django python3-flask python3-slimmer python-markdown-doc\n", - " python-psutil-doc python-pygments-doc ttf-bitstream-vera\n", - " python-pyinotify-doc python3-pycurl python-tornado-doc python3-twisted\n", - "The following NEW packages will be installed:\n", - " fonts-font-awesome fonts-lato javascript-common libjs-bootstrap4\n", - " libjs-highlight.js libjs-lunr libjs-modernizr libjs-popper.js libjs-sizzle\n", - " mkdocs node-jquery pipx python-babel-localedata python3-argcomplete\n", - " python3-babel python3-click python3-colorama python3-jinja2\n", - " python3-livereload python3-markdown python3-markupsafe python3-packaging\n", - " python3-pip-whl python3-psutil python3-pygments python3-pyinotify\n", - " python3-setuptools-whl python3-tornado python3-tz python3-userpath\n", - " python3-venv python3-yaml python3.10-venv sphinx-rtd-theme-common\n", - "0 upgraded, 34 newly installed, 0 to remove and 49 not upgraded.\n", - "Need to get 15.2 MB of archives.\n", - "After this operation, 64.3 MB of additional disk space will be used.\n", - "Get:1 http://archive.ubuntu.com/ubuntu jammy/main amd64 fonts-lato all 2.0-2.1 [2,696 kB]\n", - "Get:2 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-yaml amd64 5.4.1-1ubuntu1 [129 kB]\n", - "Get:3 http://archive.ubuntu.com/ubuntu jammy/main amd64 fonts-font-awesome all 5.0.10+really4.7.0~dfsg-4.1 [516 kB]\n", - "Get:4 http://archive.ubuntu.com/ubuntu jammy/main amd64 javascript-common all 11+nmu1 [5,936 B]\n", - "Get:5 http://archive.ubuntu.com/ubuntu jammy/universe amd64 libjs-popper.js all 1.16.1+ds-5 [53.8 kB]\n", - "Get:6 http://archive.ubuntu.com/ubuntu jammy/universe amd64 libjs-bootstrap4 all 4.6.0+dfsg1-4 [534 kB]\n", - "Get:7 http://archive.ubuntu.com/ubuntu jammy/universe amd64 libjs-highlight.js all 9.18.5+dfsg1-1 [367 kB]\n", - "Get:8 http://archive.ubuntu.com/ubuntu jammy/universe amd64 libjs-lunr all 2.3.9~dfsg-1 [67.0 kB]\n", - "Get:9 http://archive.ubuntu.com/ubuntu jammy/universe amd64 libjs-sizzle all 2.3.6+ds+~2.3.3-1 [32.3 kB]\n", - "Get:10 http://archive.ubuntu.com/ubuntu jammy/universe amd64 libjs-modernizr all 2.6.2+ds1-4 [47.1 kB]\n", - "Get:11 http://archive.ubuntu.com/ubuntu jammy/main amd64 sphinx-rtd-theme-common all 1.0.0+dfsg-1 [991 kB]\n", - "Get:12 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-colorama all 0.4.4-1 [24.5 kB]\n", - "Get:13 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-click all 8.0.3-1 [78.3 kB]\n", - "Get:14 http://archive.ubuntu.com/ubuntu jammy/main amd64 python-babel-localedata all 2.8.0+dfsg.1-7 [4,982 kB]\n", - "Get:15 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 python3-tz all 2022.1-1ubuntu0.22.04.1 [30.7 kB]\n", - "Get:16 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-babel all 2.8.0+dfsg.1-7 [85.1 kB]\n", - "Get:17 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-markupsafe amd64 2.0.1-2build1 [12.7 kB]\n", - "Get:18 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 python3-jinja2 all 3.0.3-1ubuntu0.2 [108 kB]\n", - "Get:19 http://archive.ubuntu.com/ubuntu jammy/universe amd64 python3-tornado amd64 6.1.0-3build1 [287 kB]\n", - "Get:20 http://archive.ubuntu.com/ubuntu jammy/universe amd64 python3-livereload all 2.6.3-2 [24.7 kB]\n", - "Get:21 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-markdown all 3.3.6-1 [68.5 kB]\n", - "Get:22 http://archive.ubuntu.com/ubuntu jammy/universe amd64 mkdocs all 1.1.2+dfsg-2ubuntu1 [59.2 kB]\n", - "Get:23 http://archive.ubuntu.com/ubuntu jammy/universe amd64 node-jquery all 3.6.0+dfsg+~3.5.13-1 [160 kB]\n", - "Get:24 http://archive.ubuntu.com/ubuntu jammy-updates/universe amd64 python3-pip-whl all 22.0.2+dfsg-1ubuntu0.4 [1,680 kB]\n", - "Get:25 http://archive.ubuntu.com/ubuntu jammy-updates/universe amd64 python3-setuptools-whl all 59.6.0-1.2ubuntu0.22.04.2 [788 kB]\n", - "Get:26 http://archive.ubuntu.com/ubuntu jammy-updates/universe amd64 python3.10-venv amd64 3.10.12-1~22.04.6 [5,722 B]\n", - "Get:27 http://archive.ubuntu.com/ubuntu jammy-updates/universe amd64 python3-venv amd64 3.10.6-1~22.04.1 [1,042 B]\n", - "Get:28 http://archive.ubuntu.com/ubuntu jammy/universe amd64 python3-argcomplete all 1.8.1-1.5 [27.2 kB]\n", - "Get:29 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-packaging all 21.3-1 [30.7 kB]\n", - "Get:30 http://archive.ubuntu.com/ubuntu jammy/universe amd64 python3-userpath all 1.8.0-1 [10.0 kB]\n", - "Get:31 http://archive.ubuntu.com/ubuntu jammy/universe amd64 pipx all 1.0.0-1 [371 kB]\n", - "Get:32 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-psutil amd64 5.9.0-1build1 [158 kB]\n", - "Get:33 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-pygments all 2.11.2+dfsg-2 [750 kB]\n", - "Get:34 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-pyinotify all 0.9.6-1.3 [24.8 kB]\n", - "Fetched 15.2 MB in 2s (9,428 kB/s)\n", - "Selecting previously unselected package fonts-lato.\r\n", - "(Reading database ... \r(Reading database ... 5%\r(Reading database ... 10%\r(Reading database ... 15%\r(Reading database ... 20%\r(Reading database ... 25%\r(Reading database ... 30%\r(Reading database ... 35%\r(Reading database ... 40%\r(Reading database ... 45%\r(Reading database ... 50%\r(Reading database ... 55%\r(Reading database ... 60%\r(Reading database ... 65%\r(Reading database ... 70%\r(Reading database ... 75%\r(Reading database ... 80%\r(Reading database ... 85%\r(Reading database ... 90%\r(Reading database ... 95%\r(Reading database ... 100%\r(Reading database ... 123605 files and directories currently installed.)\r\n", - "Preparing to unpack .../00-fonts-lato_2.0-2.1_all.deb ...\r\n", - "Unpacking fonts-lato (2.0-2.1) ...\r\n", - "Selecting previously unselected package python3-yaml.\r\n", - "Preparing to unpack .../01-python3-yaml_5.4.1-1ubuntu1_amd64.deb ...\r\n", - "Unpacking python3-yaml (5.4.1-1ubuntu1) ...\r\n", - "Selecting previously unselected package fonts-font-awesome.\r\n", - "Preparing to unpack .../02-fonts-font-awesome_5.0.10+really4.7.0~dfsg-4.1_all.deb ...\r\n", - "Unpacking fonts-font-awesome (5.0.10+really4.7.0~dfsg-4.1) ...\r\n", - "Selecting previously unselected package javascript-common.\r\n", - "Preparing to unpack .../03-javascript-common_11+nmu1_all.deb ...\r\n", - "Unpacking javascript-common (11+nmu1) ...\r\n", - "Selecting previously unselected package libjs-popper.js.\r\n", - "Preparing to unpack .../04-libjs-popper.js_1.16.1+ds-5_all.deb ...\r\n", - "Unpacking libjs-popper.js (1.16.1+ds-5) ...\r\n", - "Selecting previously unselected package libjs-bootstrap4.\r\n", - "Preparing to unpack .../05-libjs-bootstrap4_4.6.0+dfsg1-4_all.deb ...\r\n", - "Unpacking libjs-bootstrap4 (4.6.0+dfsg1-4) ...\r\n", - "Selecting previously unselected package libjs-highlight.js.\r\n", - "Preparing to unpack .../06-libjs-highlight.js_9.18.5+dfsg1-1_all.deb ...\r\n", - "Unpacking libjs-highlight.js (9.18.5+dfsg1-1) ...\r\n", - "Selecting previously unselected package libjs-lunr.\r\n", - "Preparing to unpack .../07-libjs-lunr_2.3.9~dfsg-1_all.deb ...\r\n", - "Unpacking libjs-lunr (2.3.9~dfsg-1) ...\r\n", - "Selecting previously unselected package libjs-sizzle.\r\n", - "Preparing to unpack .../08-libjs-sizzle_2.3.6+ds+~2.3.3-1_all.deb ...\r\n", - "Unpacking libjs-sizzle (2.3.6+ds+~2.3.3-1) ...\r\n", - "Selecting previously unselected package libjs-modernizr.\r\n", - "Preparing to unpack .../09-libjs-modernizr_2.6.2+ds1-4_all.deb ...\r\n", - "Unpacking libjs-modernizr (2.6.2+ds1-4) ...\r\n", - "Selecting previously unselected package sphinx-rtd-theme-common.\r\n", - "Preparing to unpack .../10-sphinx-rtd-theme-common_1.0.0+dfsg-1_all.deb ...\r\n", - "Unpacking sphinx-rtd-theme-common (1.0.0+dfsg-1) ...\r\n", - "Selecting previously unselected package python3-colorama.\r\n", - "Preparing to unpack .../11-python3-colorama_0.4.4-1_all.deb ...\r\n", - "Unpacking python3-colorama (0.4.4-1) ...\r\n", - "Selecting previously unselected package python3-click.\r\n", - "Preparing to unpack .../12-python3-click_8.0.3-1_all.deb ...\r\n", - "Unpacking python3-click (8.0.3-1) ...\r\n", - "Selecting previously unselected package python-babel-localedata.\r\n", - "Preparing to unpack .../13-python-babel-localedata_2.8.0+dfsg.1-7_all.deb ...\r\n", - "Unpacking python-babel-localedata (2.8.0+dfsg.1-7) ...\r\n", - "Selecting previously unselected package python3-tz.\r\n", - "Preparing to unpack .../14-python3-tz_2022.1-1ubuntu0.22.04.1_all.deb ...\r\n", - "Unpacking python3-tz (2022.1-1ubuntu0.22.04.1) ...\r\n", - "Selecting previously unselected package python3-babel.\r\n", - "Preparing to unpack .../15-python3-babel_2.8.0+dfsg.1-7_all.deb ...\r\n", - "Unpacking python3-babel (2.8.0+dfsg.1-7) ...\r\n", - "Selecting previously unselected package python3-markupsafe.\r\n", - "Preparing to unpack .../16-python3-markupsafe_2.0.1-2build1_amd64.deb ...\r\n", - "Unpacking python3-markupsafe (2.0.1-2build1) ...\r\n", - "Selecting previously unselected package python3-jinja2.\r\n", - "Preparing to unpack .../17-python3-jinja2_3.0.3-1ubuntu0.2_all.deb ...\r\n", - "Unpacking python3-jinja2 (3.0.3-1ubuntu0.2) ...\r\n", - "Selecting previously unselected package python3-tornado.\r\n", - "Preparing to unpack .../18-python3-tornado_6.1.0-3build1_amd64.deb ...\r\n", - "Unpacking python3-tornado (6.1.0-3build1) ...\r\n", - "Selecting previously unselected package python3-livereload.\r\n", - "Preparing to unpack .../19-python3-livereload_2.6.3-2_all.deb ...\r\n", - "Unpacking python3-livereload (2.6.3-2) ...\r\n", - "Selecting previously unselected package python3-markdown.\r\n", - "Preparing to unpack .../20-python3-markdown_3.3.6-1_all.deb ...\r\n", - "Unpacking python3-markdown (3.3.6-1) ...\r\n", - "Selecting previously unselected package mkdocs.\r\n", - "Preparing to unpack .../21-mkdocs_1.1.2+dfsg-2ubuntu1_all.deb ...\r\n", - "Unpacking mkdocs (1.1.2+dfsg-2ubuntu1) ...\r\n", - "Selecting previously unselected package node-jquery.\r\n", - "Preparing to unpack .../22-node-jquery_3.6.0+dfsg+~3.5.13-1_all.deb ...\r\n", - "Unpacking node-jquery (3.6.0+dfsg+~3.5.13-1) ...\r\n", - "Selecting previously unselected package python3-pip-whl.\r\n", - "Preparing to unpack .../23-python3-pip-whl_22.0.2+dfsg-1ubuntu0.4_all.deb ...\r\n", - "Unpacking python3-pip-whl (22.0.2+dfsg-1ubuntu0.4) ...\r\n", - "Selecting previously unselected package python3-setuptools-whl.\r\n", - "Preparing to unpack .../24-python3-setuptools-whl_59.6.0-1.2ubuntu0.22.04.2_all.deb ...\r\n", - "Unpacking python3-setuptools-whl (59.6.0-1.2ubuntu0.22.04.2) ...\r\n", - "Selecting previously unselected package python3.10-venv.\r\n", - "Preparing to unpack .../25-python3.10-venv_3.10.12-1~22.04.6_amd64.deb ...\r\n", - "Unpacking python3.10-venv (3.10.12-1~22.04.6) ...\r\n", - "Selecting previously unselected package python3-venv.\r\n", - "Preparing to unpack .../26-python3-venv_3.10.6-1~22.04.1_amd64.deb ...\r\n", - "Unpacking python3-venv (3.10.6-1~22.04.1) ...\r\n", - "Selecting previously unselected package python3-argcomplete.\r\n", - "Preparing to unpack .../27-python3-argcomplete_1.8.1-1.5_all.deb ...\r\n", - "Unpacking python3-argcomplete (1.8.1-1.5) ...\r\n", - "Selecting previously unselected package python3-packaging.\r\n", - "Preparing to unpack .../28-python3-packaging_21.3-1_all.deb ...\r\n", - "Unpacking python3-packaging (21.3-1) ...\r\n", - "Selecting previously unselected package python3-userpath.\r\n", - "Preparing to unpack .../29-python3-userpath_1.8.0-1_all.deb ...\r\n", - "Unpacking python3-userpath (1.8.0-1) ...\r\n", - "Selecting previously unselected package pipx.\r\n", - "Preparing to unpack .../30-pipx_1.0.0-1_all.deb ...\r\n", - "Unpacking pipx (1.0.0-1) ...\r\n", - "Selecting previously unselected package python3-psutil.\r\n", - "Preparing to unpack .../31-python3-psutil_5.9.0-1build1_amd64.deb ...\r\n", - "Unpacking python3-psutil (5.9.0-1build1) ...\r\n", - "Selecting previously unselected package python3-pygments.\r\n", - "Preparing to unpack .../32-python3-pygments_2.11.2+dfsg-2_all.deb ...\r\n", - "Unpacking python3-pygments (2.11.2+dfsg-2) ...\r\n", - "Selecting previously unselected package python3-pyinotify.\r\n", - "Preparing to unpack .../33-python3-pyinotify_0.9.6-1.3_all.deb ...\r\n", - "Unpacking python3-pyinotify (0.9.6-1.3) ...\r\n", - "Setting up javascript-common (11+nmu1) ...\r\n", - "Setting up python3-tornado (6.1.0-3build1) ...\r\n", - "Setting up python3-setuptools-whl (59.6.0-1.2ubuntu0.22.04.2) ...\r\n", - "Setting up fonts-lato (2.0-2.1) ...\r\n", - "Setting up libjs-popper.js (1.16.1+ds-5) ...\r\n", - "Setting up python3-colorama (0.4.4-1) ...\r\n", - "Setting up python3-pip-whl (22.0.2+dfsg-1ubuntu0.4) ...\r\n", - "Setting up libjs-lunr (2.3.9~dfsg-1) ...\r\n", - "Setting up python3-pyinotify (0.9.6-1.3) ...\r\n", - "Setting up python3-yaml (5.4.1-1ubuntu1) ...\r\n", - "Setting up python3-click (8.0.3-1) ...\r\n", - "Setting up libjs-sizzle (2.3.6+ds+~2.3.3-1) ...\r\n", - "Setting up python3-markupsafe (2.0.1-2build1) ...\r\n", - "Setting up libjs-modernizr (2.6.2+ds1-4) ...\r\n", - "Setting up python3-psutil (5.9.0-1build1) ...\r\n", - "Setting up python3-tz (2022.1-1ubuntu0.22.04.1) ...\r\n", - "Setting up python-babel-localedata (2.8.0+dfsg.1-7) ...\r\n", - "Setting up python3-pygments (2.11.2+dfsg-2) ...\r\n", - "Setting up python3-packaging (21.3-1) ...\r\n", - "Setting up python3-markdown (3.3.6-1) ...\r\n", - "Setting up libjs-highlight.js (9.18.5+dfsg1-1) ...\r\n", - "Setting up python3-livereload (2.6.3-2) ...\r\n", - "Setting up libjs-bootstrap4 (4.6.0+dfsg1-4) ...\r\n", - "Setting up python3-argcomplete (1.8.1-1.5) ...\r\n", - "Setting up fonts-font-awesome (5.0.10+really4.7.0~dfsg-4.1) ...\r\n", - "Setting up sphinx-rtd-theme-common (1.0.0+dfsg-1) ...\r\n", - "Setting up node-jquery (3.6.0+dfsg+~3.5.13-1) ...\r\n", - "Setting up python3-userpath (1.8.0-1) ...\r\n", - "Setting up python3.10-venv (3.10.12-1~22.04.6) ...\r\n", - "Setting up python3-babel (2.8.0+dfsg.1-7) ...\r\n", - "update-alternatives: using /usr/bin/pybabel-python3 to provide /usr/bin/pybabel (pybabel) in auto mode\r\n", - "Setting up python3-jinja2 (3.0.3-1ubuntu0.2) ...\r\n", - "Setting up python3-venv (3.10.6-1~22.04.1) ...\r\n", - "Setting up mkdocs (1.1.2+dfsg-2ubuntu1) ...\r\n", - "Setting up pipx (1.0.0-1) ...\r\n", - "Processing triggers for fontconfig (2.13.1-4.2ubuntu5) ...\r\n", - "Processing triggers for man-db (2.10.2-1) ...\r\n", - "Success! Added /root/.local/bin to the PATH environment variable.\n", - "\n", - "Consider adding shell completions for pipx. Run 'pipx completions' for instructions.\n", - "\n", - "You will need to open a new terminal or re-login for the PATH changes to take effect.\n", - "\n", - "Otherwise pipx is ready to go! ✨ 🌟 ✨\n", - " installed package poetry 1.8.3, installed using Python 3.10.12\n", - " These apps are now globally available\n", - " - poetry\n", - "Using virtualenv: /content/marketing-analytics-jumpstart/.venv\n", - "Poetry (version 1.8.3)\n", - "/root/.local/bin/tfenv\n", - "tfenv 3.0.0\n", - "\u001b[32mInstalling Terraform v1.5.7\u001b[0m\n", - "\u001b[32mDownloading release tarball from https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_linux_amd64.zip\u001b[0m\n", - "\u001b[32mDownloading SHA hash file from https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_SHA256SUMS\u001b[0m\n", - "\u001b[33mNot instructed to use Local PGP (/root/.tfenv/use-{gpgv,gnupg}) & No keybase install found, skipping OpenPGP signature verification\u001b[0m\n", - "\u001b[32mArchive: /tmp/tfenv_download.oUjd2S/terraform_1.5.7_linux_amd64.zip\u001b[0m\n", - "\u001b[32m inflating: /root/.tfenv/versions/1.5.7/terraform \u001b[0m\n", - "\u001b[32mInstallation of terraform v1.5.7 successful. To make this your default version, run 'tfenv use 1.5.7'\u001b[0m\n", - "\u001b[32mSwitching default version to v1.5.7\u001b[0m\n", - "\u001b[32mDefault version (when not overridden by .terraform-version or TFENV_TERRAFORM_VERSION) is now: 1.5.7\u001b[0m\n", - "Terraform v1.5.7\n", - "on linux_amd64\n", - "\n", - "Your version of Terraform is out of date! The latest version\n", - "is 1.9.5. You can update by downloading from https://www.terraform.io/downloads.html\n", - "****************************************************************************************************\n", - "\u001b[0;36mCheck if the necessary dependencies are available: gcloud, gsutil, terraform, poetry\u001b[0m \n", - "****************************************************************************************************\n", - "Google Cloud SDK 494.0.0\n", - "gsutil version: 5.30\n", - "Terraform v1.5.7\n", - "on linux_amd64\n", - "\n", - "Your version of Terraform is out of date! The latest version\n", - "is 1.9.5. You can update by downloading from https://www.terraform.io/downloads.html\n", - "Poetry (version 1.8.3)\n", - "****************************************************************************************************\n", - "\u001b[0;36mCheck if the necessary dependencies are available: gcloud, gsutil, terraform, poetry \u001b[1;36m- done\u001b[0m\n", - "\n", - "\n", - "****************************************************************************************************\n", - "\u001b[0;36mCheck if the necessary variables are set: PROJECT_ID\u001b[0m \n", - "****************************************************************************************************\n", - "****************************************************************************************************\n", - "\u001b[0;36mCheck if the necessary variables are set: PROJECT_ID \u001b[1;36m- done\u001b[0m\n", - "\n", - "\n", - "****************************************************************************************************\n", - "\u001b[0;36mSetting the Google Cloud project to TF_STATE_PROJECT\u001b[0m \n", - "****************************************************************************************************\n", - "****************************************************************************************************\n", - "\u001b[0;36mSetting the Google Cloud project to TF_STATE_PROJECT \u001b[1;36m- done\u001b[0m\n", - "\n", - "\n", - "****************************************************************************************************\n", - "\u001b[0;36mCheck and set the LOCATION variable\u001b[0m \n", - "****************************************************************************************************\n", - "****************************************************************************************************\n", - "\u001b[0;36mCheck and set the LOCATION variable \u001b[1;36m- done\u001b[0m\n", - "\n", - "\n", - "****************************************************************************************************\n", - "\u001b[0;36mCheck and set the TF_STATE_BUCKET variable\u001b[0m \n", - "****************************************************************************************************\n", - "****************************************************************************************************\n", - "\u001b[0;36mCheck and set the TF_STATE_BUCKET variable \u001b[1;36m- done\u001b[0m\n", - "\n", - "\n", - "****************************************************************************************************\n", - "\u001b[0;36mEnable all the required APIs\u001b[0m \n", - "****************************************************************************************************\n", - "cloudresourcemanager.googleapis.com Cloud Resource Manager API\n", - "cloudresourcemanager.googleapis.com api is enabled\n", - "serviceusage.googleapis.com Service Usage API\n", - "serviceusage.googleapis.com api is enabled\n", - "iam.googleapis.com Identity and Access Management (IAM) API\n", - "iam.googleapis.com api is enabled\n", - "logging.googleapis.com Cloud Logging API\n", - "logging.googleapis.com api is enabled\n", - "monitoring.googleapis.com Cloud Monitoring API\n", - "monitoring.googleapis.com api is enabled\n", - "bigquery.googleapis.com BigQuery API\n", - "bigquery.googleapis.com api is enabled\n", - "bigquerystorage.googleapis.com BigQuery Storage API\n", - "bigquerystorage.googleapis.com api is enabled\n", - "dataform.googleapis.com Dataform API\n", - "dataform.googleapis.com api is enabled\n", - "secretmanager.googleapis.com Secret Manager API\n", - "secretmanager.googleapis.com api is enabled\n", - "cloudasset.googleapis.com Cloud Asset API\n", - "cloudasset.googleapis.com api is enabled\n", - "cloudfunctions.googleapis.com Cloud Functions API\n", - "cloudfunctions.googleapis.com api is enabled\n", - "bigquerystorage.googleapis.com BigQuery Storage API\n", - "storage.googleapis.com Cloud Storage API\n", - "storage.googleapis.com api is enabled\n", - "datapipelines.googleapis.com Data pipelines API\n", - "datapipelines.googleapis.com api is enabled\n", - "analyticsadmin.googleapis.com Google Analytics Admin API\n", - "analyticsadmin.googleapis.com api is enabled\n", - "workflows.googleapis.com Workflows API\n", - "workflows.googleapis.com api is enabled\n", - "cloudscheduler.googleapis.com Cloud Scheduler API\n", - "cloudscheduler.googleapis.com api is enabled\n", - "bigquerymigration.googleapis.com BigQuery Migration API\n", - "bigquerymigration.googleapis.com api is enabled\n", - "bigquerydatatransfer.googleapis.com BigQuery Data Transfer API\n", - "bigquerydatatransfer.googleapis.com api is enabled\n", - "dataform.googleapis.com Dataform API\n", - "dataform.googleapis.com api is enabled\n", - "****************************************************************************************************\n", - "\u001b[0;36mEnable all the required APIs \u001b[1;36m- done\u001b[0m\n", - "\n", - "\n", - "****************************************************************************************************\n", - "\u001b[0;36mInstall poetry libraries in the virtual environment for Terraform\u001b[0m \n", - "****************************************************************************************************\n", - "Updating dependencies\n", - "Resolving dependencies...\n", - "\n", - "Package operations: 105 installs, 1 update, 0 removals\n", - "\n", - " - Downgrading pip (24.2 -> 23.3)\n", - " - Installing pyasn1 (0.6.1)\n", - " - Installing cachetools (5.5.0)\n", - " - Installing certifi (2024.8.30)\n", - " - Installing charset-normalizer (3.3.2)\n", - " - Installing idna (3.10)\n", - " - Installing protobuf (3.20.3)\n", - " - Installing pyasn1-modules (0.4.1)\n", - " - Installing rsa (4.9)\n", - " - Installing urllib3 (1.26.18)\n", - " - Installing google-auth (2.35.0)\n", - " - Installing googleapis-common-protos (1.65.0)\n", - " - Installing grpcio (1.66.1)\n", - " - Installing proto-plus (1.24.0)\n", - " - Installing requests (2.32.3)\n", - " - Installing google-api-core (2.20.0)\n", - " - Installing google-crc32c (1.5.0)\n", - " - Installing grpcio-status (1.48.2)\n", - " - Installing oauthlib (3.2.2)\n", - " - Installing six (1.16.0)\n", - " - Installing typing-extensions (4.12.2)\n", - " - Installing annotated-types (0.7.0)\n", - " - Installing google-cloud-core (2.4.1)\n", - " - Installing google-resumable-media (2.7.2)\n", - " - Installing greenlet (3.1.1)\n", - " - Installing grpc-google-iam-v1 (0.13.1)\n", - " - Installing markupsafe (2.1.5)\n", - " - Installing packaging (24.1)\n", - " - Installing pydantic-core (2.23.4)\n", - " - Installing python-dateutil (2.9.0.post0)\n", - " - Installing pyyaml (6.0.2)\n", - " - Installing requests-oauthlib (2.0.0)\n", - " - Installing websocket-client (1.8.0)\n", - " - Installing click (8.1.7)\n", - " - Installing docstring-parser (0.16)\n", - " - Installing google-cloud-bigquery (2.30.0)\n", - " - Installing google-cloud-resource-manager (1.12.5)\n", - " - Installing google-cloud-storage (2.18.2)\n", - " - Installing kfp-pipeline-spec (0.2.2)\n", - " - Installing kfp-server-api (2.0.0rc1)\n", - " - Installing kubernetes (26.1.0)\n", - " - Installing mako (1.3.5)\n", - " - Installing numpy (1.24.4)\n", - " - Installing pydantic (2.9.2)\n", - " - Installing pyparsing (3.1.4)\n", - " - Installing pytz (2024.2)\n", - " - Installing requests-toolbelt (0.10.1)\n", - " - Installing shapely (1.8.5.post1)\n", - " - Installing sqlalchemy (2.0.35)\n", - " - Installing tabulate (0.9.0)\n", - " - Installing alembic (1.13.3)\n", - " - Installing attrs (24.2.0)\n", - " - Installing cmaes (0.11.1)\n", - " - Installing colorlog (6.8.2)\n", - " - Installing distlib (0.3.8)\n", - " - Installing filelock (3.16.1)\n", - " - Installing google-cloud-aiplatform (1.52.0)\n", - " - Installing httplib2 (0.22.0)\n", - " - Installing iniconfig (2.0.0)\n", - " - Installing jinja2 (3.1.2)\n", - " - Installing joblib (1.4.2)\n", - " - Installing kfp (2.0.0rc2)\n", - " - Installing mccabe (0.7.0)\n", - " - Installing pandas (1.3.5)\n", - " - Installing platformdirs (4.3.6)\n", - " - Installing pluggy (1.5.0)\n", - " - Installing py (1.11.0)\n", - " - Installing pyarrow (15.0.2)\n", - " - Installing pycodestyle (2.9.1)\n", - " - Installing pyflakes (2.5.0)\n", - " - Installing scipy (1.10.1)\n", - " - Installing threadpoolctl (3.5.0)\n", - " - Installing tomli (2.0.1)\n", - " - Installing tqdm (4.66.5)\n", - " - Installing cfgv (3.4.0)\n", - " - Installing coverage (6.5.0)\n", - " - Installing db-dtypes (1.2.0)\n", - " - Installing docker (6.1.3)\n", - " - Installing execnet (2.1.1)\n", - " - Installing flake8 (5.0.4)\n", - " - Installing google-auth-oauthlib (1.2.1)\n", - " - Installing google-cloud-pipeline-components (2.6.0)\n", - " - Installing google-cloud-pubsub (2.15.0)\n", - " - Installing identify (2.6.1)\n", - " - Installing mypy-extensions (1.0.0)\n", - " - Installing nodeenv (1.9.1)\n", - " - Installing oauth2client (4.1.3)\n", - " - Installing optuna (3.2.0)\n", - " - Installing pathspec (0.12.1)\n", - " - Installing pytest (7.0.0)\n", - " - Installing scikit-learn (1.2.2)\n", - " - Installing toml (0.10.2)\n", - " - Installing virtualenv (20.26.5)\n", - " - Installing black (22.12.0)\n", - " - Installing flake8-annotations (2.9.1)\n", - " - Installing google-analytics-admin (0.22.7)\n", - " - Installing google-analytics-data (0.18.12)\n", - " - Installing google-cloud (0.34.0)\n", - " - Installing invoke (2.2.0)\n", - " - Installing pre-commit (2.21.0)\n", - " - Installing pytest-cov (4.1.0)\n", - " - Installing pytest-env (0.6.2)\n", - " - Installing pytest-mock (3.7.0)\n", - " - Installing pytest-variables (2.0.0)\n", - " - Installing pytest-xdist (3.6.1)\n", - " - Installing ma-components (1.0.0 /content/marketing-analytics-jumpstart/python/base_component_image)\n", - "\n", - "Writing lock file\n", - "\n", - "Installing the current project: marketing-analytics-jumpstart (1.0.0)\n", - "****************************************************************************************************\n", - "\u001b[0;36mInstall poetry libraries in the virtual environment for Terraform \u001b[1;36m- done\u001b[0m\n", - "\n", - "\n", - "****************************************************************************************************\n", - "\u001b[0;36mCreating a new Google Cloud Storage bucket to store the Terraform state in marketing-data-engine-demo project, bucket: marketing-data-engine-demo-terraform-state\u001b[0m \n", - "****************************************************************************************************\n", - "The marketing-data-engine-demo-terraform-state Google Cloud Storage bucket already exists. \n", - "****************************************************************************************************\n", - "\u001b[0;36mCreating a new Google Cloud Storage bucket to store the Terraform state in marketing-data-engine-demo project, bucket: marketing-data-engine-demo-terraform-state \u001b[1;36m- done\u001b[0m\n", - "\n", - "\n", - "****************************************************************************************************\n", - "\u001b[0;36mCreating terraform backend.tf configuration file\u001b[0m \n", - "****************************************************************************************************\n", - "Generating the Terraform backend configuration file: infrastructure/terraform/backend.tf\n", - "terraform {\n", - " backend \"gcs\" {\n", - " bucket = \"marketing-data-engine-demo-terraform-state\"\n", - " prefix = \"state\"\n", - " }\n", - "}\n", - "****************************************************************************************************\n", - "\u001b[0;36mCreating terraform backend.tf configuration file \u001b[1;36m- done\u001b[0m\n", - "\n", - "\n", - "****************************************************************************************************\n", - "You got the end the of your generate-tf-backend script with everything working. \n", - "****************************************************************************************************\n" - ] - }, - { - "output_type": "stream", - "name": "stderr", - "text": [ - "\n", - "WARNING: apt does not have a stable CLI interface. Use with caution in scripts.\n", - "\n", - "W: Skipping acquire of configured file 'main/source/Sources' as repository 'https://r2u.stat.illinois.edu/ubuntu jammy InRelease' does not seem to provide it (sources.list entry misspelt?)\n", - "\n", - "WARNING: apt does not have a stable CLI interface. Use with caution in scripts.\n", - "\n", - "debconf: unable to initialize frontend: Dialog\n", - "debconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 78, <> line 34.)\n", - "debconf: falling back to frontend: Readline\n", - "debconf: unable to initialize frontend: Readline\n", - "debconf: (This frontend requires a controlling tty.)\n", - "debconf: falling back to frontend: Teletype\n", - "dpkg-preconfigure: unable to re-open stdin: \n", - "creating virtual environment...\n", - "creating shared libraries...\n", - "upgrading shared libraries...\n", - "installing poetry...\n", - "⚠️ Note: '/root/.local/bin' is not on your PATH environment variable. These apps will not be\n", - " globally accessible until your PATH is updated. Run `pipx ensurepath` to automatically add it,\n", - " or manually modify your PATH in your shell's config file (i.e. ~/.bashrc).\n", - "done! ✨ 🌟 ✨\n", - "Creating virtualenv marketing-analytics-jumpstart in /content/marketing-analytics-jumpstart/.venv\n", - "Cloning into '/root/.tfenv'...\n", - "#=#=- # \r\r######################### 27.2%\r############################################################################################# 100.0%\n", - "Updated property [core/project].\n" - ] - } - ] + "execution_count": null, + "outputs": [] }, { "cell_type": "code", @@ -770,22 +169,10 @@ "cp $TERRAFORM_RUN_DIR/terraform-sample.tfvars $TERRAFORM_RUN_DIR/terraform.tfvars -v" ], "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "NDQl55V1WkwK", - "outputId": "ca59a5f1-3c63-4935-a915-4c294b021753" + "id": "NDQl55V1WkwK" }, - "execution_count": 10, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "'/content/marketing-analytics-jumpstart/infrastructure/terraform/terraform-sample.tfvars' -> '/content/marketing-analytics-jumpstart/infrastructure/terraform/terraform.tfvars'\n" - ] - } - ] + "execution_count": null, + "outputs": [] }, { "cell_type": "code", @@ -797,121 +184,10 @@ "terraform -chdir=\"${TERRAFORM_RUN_DIR}\" init" ], "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "5UIbC_z9bgy4", - "outputId": "89e04de8-d861-478d-f2c8-c62bb887e4c7" + "id": "5UIbC_z9bgy4" }, - "execution_count": 11, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "\n", - "\u001b[0m\u001b[1mInitializing the backend...\u001b[0m\n", - "\u001b[0m\u001b[32m\n", - "Successfully configured the backend \"gcs\"! Terraform will automatically\n", - "use this backend unless the backend configuration changes.\u001b[0m\n", - "\u001b[0m\u001b[1mInitializing modules...\u001b[0m\n", - "- activation in modules/activation\n", - "Downloading registry.terraform.io/terraform-google-modules/gcloud/google 3.1.2 for activation.activation_pipeline_container...\n", - "- activation.activation_pipeline_container in .terraform/modules/activation.activation_pipeline_container\n", - "Downloading registry.terraform.io/terraform-google-modules/gcloud/google 3.1.2 for activation.activation_pipeline_template...\n", - "- activation.activation_pipeline_template in .terraform/modules/activation.activation_pipeline_template\n", - "Downloading registry.terraform.io/terraform-google-modules/gcloud/google 3.1.2 for activation.add_invoker_binding...\n", - "- activation.add_invoker_binding in .terraform/modules/activation.add_invoker_binding\n", - "Downloading registry.terraform.io/terraform-google-modules/bigquery/google 5.4.3 for activation.bigquery...\n", - "- activation.bigquery in .terraform/modules/activation.bigquery\n", - "Downloading registry.terraform.io/terraform-google-modules/cloud-storage/google 3.4.1 for activation.build_logs_bucket...\n", - "- activation.build_logs_bucket in .terraform/modules/activation.build_logs_bucket/modules/simple_bucket\n", - "Downloading registry.terraform.io/terraform-google-modules/cloud-storage/google 3.4.1 for activation.function_bucket...\n", - "- activation.function_bucket in .terraform/modules/activation.function_bucket/modules/simple_bucket\n", - "Downloading registry.terraform.io/terraform-google-modules/cloud-storage/google 3.4.1 for activation.pipeline_bucket...\n", - "- activation.pipeline_bucket in .terraform/modules/activation.pipeline_bucket/modules/simple_bucket\n", - "Downloading registry.terraform.io/terraform-google-modules/service-accounts/google 3.0.1 for activation.pipeline_service_account...\n", - "- activation.pipeline_service_account in .terraform/modules/activation.pipeline_service_account\n", - "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for activation.project_services...\n", - "- activation.project_services in .terraform/modules/activation.project_services/modules/project_services\n", - "Downloading registry.terraform.io/GoogleCloudPlatform/secret-manager/google 0.4.0 for activation.secret_manager...\n", - "- activation.secret_manager in .terraform/modules/activation.secret_manager\n", - "Downloading registry.terraform.io/terraform-google-modules/service-accounts/google 3.0.1 for activation.trigger_function_account...\n", - "- activation.trigger_function_account in .terraform/modules/activation.trigger_function_account\n", - "- data_store in modules/data-store\n", - "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for data_store.data_processing_project_services...\n", - "- data_store.data_processing_project_services in .terraform/modules/data_store.data_processing_project_services/modules/project_services\n", - "- data_store.dataform-workflow-dev in modules/dataform-workflow\n", - "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for data_store.dataform-workflow-dev.data_processing_project_services...\n", - "- data_store.dataform-workflow-dev.data_processing_project_services in .terraform/modules/data_store.dataform-workflow-dev.data_processing_project_services/modules/project_services\n", - "- data_store.dataform-workflow-prod in modules/dataform-workflow\n", - "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for data_store.dataform-workflow-prod.data_processing_project_services...\n", - "- data_store.dataform-workflow-prod.data_processing_project_services in .terraform/modules/data_store.dataform-workflow-prod.data_processing_project_services/modules/project_services\n", - "- data_store.dataform-workflow-staging in modules/dataform-workflow\n", - "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for data_store.dataform-workflow-staging.data_processing_project_services...\n", - "- data_store.dataform-workflow-staging.data_processing_project_services in .terraform/modules/data_store.dataform-workflow-staging.data_processing_project_services/modules/project_services\n", - "- feature_store in modules/feature-store\n", - "Downloading registry.terraform.io/terraform-google-modules/bigquery/google 5.4.3 for feature_store.aggregated_predictions...\n", - "- feature_store.aggregated_predictions in .terraform/modules/feature_store.aggregated_predictions\n", - "Downloading registry.terraform.io/terraform-google-modules/bigquery/google 5.4.3 for feature_store.aggregated_vbb...\n", - "- feature_store.aggregated_vbb in .terraform/modules/feature_store.aggregated_vbb\n", - "Downloading registry.terraform.io/terraform-google-modules/bigquery/google 5.4.3 for feature_store.gemini_insights...\n", - "- feature_store.gemini_insights in .terraform/modules/feature_store.gemini_insights\n", - "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for feature_store.project_services...\n", - "- feature_store.project_services in .terraform/modules/feature_store.project_services/modules/project_services\n", - "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for initial_project_services...\n", - "- initial_project_services in .terraform/modules/initial_project_services/modules/project_services\n", - "- monitoring in modules/monitor\n", - "Downloading registry.terraform.io/terraform-google-modules/bigquery/google 5.4.3 for monitoring.dashboard_bigquery...\n", - "- monitoring.dashboard_bigquery in .terraform/modules/monitoring.dashboard_bigquery\n", - "Downloading registry.terraform.io/terraform-google-modules/cloud-storage/google 3.4.1 for monitoring.load_bucket...\n", - "- monitoring.load_bucket in .terraform/modules/monitoring.load_bucket/modules/simple_bucket\n", - "Downloading registry.terraform.io/terraform-google-modules/bigquery/google 5.4.3 for monitoring.log_export_bigquery...\n", - "- monitoring.log_export_bigquery in .terraform/modules/monitoring.log_export_bigquery\n", - "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for monitoring.project_services...\n", - "- monitoring.project_services in .terraform/modules/monitoring.project_services/modules/project_services\n", - "- pipelines in modules/pipelines\n", - "Downloading registry.terraform.io/terraform-google-modules/project-factory/google 14.1.0 for pipelines.project_services...\n", - "- pipelines.project_services in .terraform/modules/pipelines.project_services/modules/project_services\n", - "\n", - "\u001b[0m\u001b[1mInitializing provider plugins...\u001b[0m\n", - "- Reusing previous version of hashicorp/random from the dependency lock file\n", - "- Reusing previous version of hashicorp/google from the dependency lock file\n", - "- Reusing previous version of hashicorp/local from the dependency lock file\n", - "- Reusing previous version of hashicorp/null from the dependency lock file\n", - "- Reusing previous version of hashicorp/external from the dependency lock file\n", - "- Reusing previous version of hashicorp/google-beta from the dependency lock file\n", - "- Reusing previous version of hashicorp/template from the dependency lock file\n", - "- Reusing previous version of hashicorp/archive from the dependency lock file\n", - "- Installing hashicorp/random v3.6.2...\n", - "- Installed hashicorp/random v3.6.2 (signed by HashiCorp)\n", - "- Installing hashicorp/google v4.85.0...\n", - "- Installed hashicorp/google v4.85.0 (signed by HashiCorp)\n", - "- Installing hashicorp/local v2.5.1...\n", - "- Installed hashicorp/local v2.5.1 (signed by HashiCorp)\n", - "- Installing hashicorp/null v3.2.2...\n", - "- Installed hashicorp/null v3.2.2 (signed by HashiCorp)\n", - "- Installing hashicorp/external v2.3.3...\n", - "- Installed hashicorp/external v2.3.3 (signed by HashiCorp)\n", - "- Installing hashicorp/google-beta v4.85.0...\n", - "- Installed hashicorp/google-beta v4.85.0 (signed by HashiCorp)\n", - "- Installing hashicorp/template v2.2.0...\n", - "- Installed hashicorp/template v2.2.0 (signed by HashiCorp)\n", - "- Installing hashicorp/archive v2.4.2...\n", - "- Installed hashicorp/archive v2.4.2 (signed by HashiCorp)\n", - "\n", - "\u001b[0m\u001b[1m\u001b[32mTerraform has been successfully initialized!\u001b[0m\u001b[32m\u001b[0m\n", - "\u001b[0m\u001b[32m\n", - "You may now begin working with Terraform. Try running \"terraform plan\" to see\n", - "any changes that are required for your infrastructure. All Terraform commands\n", - "should now work.\n", - "\n", - "If you ever set or change modules or backend configuration for Terraform,\n", - "rerun this command to reinitialize your working directory. If you forget, other\n", - "commands will detect it and remind you to do so if necessary.\u001b[0m\n" - ] - } - ] + "execution_count": null, + "outputs": [] }, { "cell_type": "code", @@ -924,1548 +200,7 @@ "terraform -chdir=\"${TERRAFORM_RUN_DIR}\" apply -auto-approve" ], "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "BGteib5ebsA-", - "outputId": "c5d16fc9-02e4-401c-cdea-ff50e6c83e6b" - }, - "execution_count": 16, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "\u001b[0m\u001b[1mmodule.activation[0].module.add_invoker_binding.data.external.env_override[0]: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_container.data.external.env_override[0]: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.churn_propensity_query_template_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_template.data.external.env_override[0]: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].data.template_file.resource_link_content: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].data.template_file.resource_link_content: Read complete after 0s [id=64ce73b4b604f03edb5ec083e09452c3331c96b067b1c8b76407addb7db7f9ea]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.auto_audience_segmentation_query_template_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.purchase_propensity_query_template_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.churn_propensity_query_template_file: Read complete after 0s [id=724c878c8d7433cb69440ec7d0072b22c024049e32965018901526063e313d8e]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.purchase_propensity_query_template_file: Read complete after 0s [id=1dcf6acb7f07df18945667501df6467effdb10adc196b46d869d48da8dd7cedb]\u001b[0m\n", - "\u001b[0m\u001b[1mnull_resource.poetry_install: Refreshing state... [id=3295118586910805891]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.cltv_query_template_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_container.data.external.env_override[0]: Read complete after 0s [id=-]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.auto_audience_segmentation_query_template_file: Read complete after 0s [id=dbbfd79a9537c703c751349a98f699a6a056e14458ed0379ab269df1f41fc153]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.archive_file.activation_trigger_source: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_template.data.external.env_override[0]: Read complete after 0s [id=-]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.add_invoker_binding.data.external.env_override[0]: Read complete after 0s [id=-]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.cltv_query_template_file: Read complete after 0s [id=db6dbb8f518920719fad1043a4a9939c8bb43a01dcecd527a5190ce642a757a1]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.archive_file.activation_trigger_source: Read complete after 0s [id=ebf0ad5a3d807457a1566db8eb4e7cbf3bb9fccf]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.audience_segmentation_query_template_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.audience_segmentation_query_template_file: Read complete after 0s [id=06b1ac3524a49e37646faf5a73207e56790449c649bb48f60562984f99461dda]\u001b[0m\n", - "\u001b[0m\u001b[1mdata.external.check_ga4_property_type: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.initial_project_services.google_project_service.project_services[\"iam.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/iam.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mdata.google_project.data_processing_project: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mdata.google_project.feature_store_project: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mdata.google_project.data_project: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].module.project_services.google_project_service.project_services[\"cloudfunctions.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudfunctions.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].module.project_services.google_project_service.project_services[\"bigquerystorage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerystorage.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"secretmanager.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/secretmanager.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].module.project_services.google_project_service.project_services[\"bigquery.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquery.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mdata.google_project.feature_store_project: Read complete after 0s [id=projects/marketing-data-engine-demo]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].module.project_services.google_project_service.project_services[\"logging.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/logging.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mdata.google_project.data_project: Read complete after 0s [id=projects/marketing-data-engine-demo]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].module.project_services.google_project_service.project_services[\"storage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mdata.google_project.data_processing_project: Read complete after 1s [id=projects/marketing-data-engine-demo]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.initial_project_services.google_project_service.project_services[\"serviceusage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/serviceusage.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.initial_project_services.google_project_service.project_services[\"cloudresourcemanager.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudresourcemanager.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mdata.external.check_ga4_property_type: Read complete after 3s [id=-]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.google_project.activation_project: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.google_project.activation_project: Read complete after 0s [id=projects/marketing-data-engine-demo]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"pubsub.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/pubsub.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"run.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/run.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"analyticsadmin.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/analyticsadmin.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"eventarc.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/eventarc.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"cloudfunctions.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudfunctions.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"logging.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/logging.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"cloudbuild.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudbuild.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"aiplatform.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/aiplatform.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"dataflow.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/dataflow.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"bigquerystorage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerystorage.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"storage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.data.google_project.data: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mdata.google_project.activation_project: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.data.google_project.data_processing: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"bigquery.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquery.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"artifactregistry.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/artifactregistry.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.project_services.google_project_service.project_services[\"datapipelines.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/datapipelines.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mnull_resource.check_cloudresourcemanager_api: Refreshing state... [id=7670263794778020985]\u001b[0m\n", - "\u001b[0m\u001b[1mnull_resource.check_serviceusage_api: Refreshing state... [id=1492568180087609374]\u001b[0m\n", - "\u001b[0m\u001b[1mnull_resource.check_iam_api: Refreshing state... [id=3508565934624880969]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].module.log_export_bigquery.google_bigquery_dataset.main: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/maj_logs]\u001b[0m\n", - "\u001b[0m\u001b[1mlocal_file.feature_store_configuration: Refreshing state... [id=788628ab0a113d503e087d9c36a9a2811596ce6f]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].null_resource.check_bigquery_api: Refreshing state... [id=2656414359838049201]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].module.load_bucket.google_storage_bucket.bucket: Refreshing state... [id=maj-monitor-marketing-data-engine-demo]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].module.dashboard_bigquery.google_bigquery_dataset.main: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/maj_dashboard]\u001b[0m\n", - "\u001b[0m\u001b[1mnull_resource.generate_sql_queries: Refreshing state... [id=2393578150071004952]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].data.local_file.config_vars: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].data.local_file.config_vars: Read complete after 0s [id=788628ab0a113d503e087d9c36a9a2811596ce6f]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.config_vars: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.config_vars: Read complete after 0s [id=788628ab0a113d503e087d9c36a9a2811596ce6f]\u001b[0m\n", - "\u001b[0m\u001b[1mdata.google_project.activation_project: Read complete after 0s [id=projects/marketing-data-engine-demo]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.data.google_project.data: Read complete after 0s [id=projects/marketing-data-engine-demo]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_scoped_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.data.google_project.data_processing: Read complete after 0s [id=projects/marketing-data-engine-demo]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.audience_segmentation_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_customer_lifetime_value_label_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_scoped_metrics_file: Read complete after 0s [id=760d0f1abad75b40f94cba6f8b755eb9168ddd97]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.audience_segmentation_inference_preparation_file: Read complete after 0s [id=2ca6061d44e46abe67064d4cf174f1b571f45436]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_scoped_segmentation_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_customer_lifetime_value_label_file: Read complete after 0s [id=78b0602291c6392193f8cfd3c671800f28a56ff8]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_dimensions_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_scoped_segmentation_metrics_file: Read complete after 0s [id=deea5f33a03b31cb2bc085e594b76c2590f8b825]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.aggregate_predictions_procedure_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_lookback_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.aggregate_predictions_procedure_file: Read complete after 0s [id=bdbfca8f24a8e81adc69e97a420b2c3201f3144e]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_dimensions_file: Read complete after 0s [id=84fd3207e632f84de0131668d1725120500bd150]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.auto_audience_segmentation_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_scoped_segmentation_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_lookback_metrics_file: Read complete after 0s [id=e7a35f2eefa0ee9e5389e7693c9fa07d70db4506]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.auto_audience_segmentation_inference_preparation_file: Read complete after 0s [id=62861e2b95a13ab037f3dd16cddfa4144c278d48]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_churn_propensity_label_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_scoped_segmentation_metrics_file: Read complete after 0s [id=763088b82fa23df0f6212ebfe1142e011a2099ec]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_rolling_window_lifetime_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_scoped_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_churn_propensity_label_file: Read complete after 0s [id=be3cd744fefb4f0274034f5aa61a72ae28bab11d]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_rolling_window_lifetime_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_scoped_metrics_file: Read complete after 0s [id=bebc3487af20b4d0e99ee7342a835de2e38de1ba]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_rolling_window_lifetime_metrics_file: Read complete after 0s [id=7edd04385f0ec5be4fe48f9c74c15a242a2d1ac2]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_rolling_window_lifetime_metrics_file: Read complete after 0s [id=6374b3b8b7800098fe5c5190237a9003eeb54230]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_lifetime_dimensions_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_dimensions_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_session_event_aggregated_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_churn_propensity_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_lifetime_dimensions_file: Read complete after 0s [id=40c4bc9e723757d1d16663cd697cfbaa48e54c21]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_dimensions_file: Read complete after 0s [id=c80a6fa05d59a8502a39677b71588959e3e63686]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_aggregated_value_based_bidding_explanation_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_rolling_window_lifetime_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_churn_propensity_inference_preparation_file: Read complete after 0s [id=39ef14261e85ab46f26281166b22e963212901c7]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_purchase_propensity_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_churn_propensity_label_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.customer_lifetime_value_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_session_event_aggregated_metrics_file: Read complete after 0s [id=2879b6fe96ffec63ceff10f5ebe4a886837c006e]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_churn_propensity_label_file: Read complete after 0s [id=9188ef958cff6dd66262fd076ddc27d92d03b804]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_customer_lifetime_value_label_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.customer_lifetime_value_training_preparation_file: Read complete after 0s [id=110df07218afe491e0902f8f5a808346f5d831e3]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_rolling_window_lifetime_metrics_file: Read complete after 0s [id=e148532aae05f4088e6034fffa0ef8b7eb0281d4]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_aggregated_value_based_bidding_explanation_preparation_file: Read complete after 0s [id=ee70aca9a40d8a161f00f6562e2968d30eba4869]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_purchase_propensity_training_preparation_file: Read complete after 0s [id=ae2897051f8b33c150bff048d6f2c102b1cb47e6]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_churn_propensity_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_churn_propensity_training_preparation_file: Read complete after 0s [id=49699a3d20984e92433cc7cfd50d29d416171871]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_segmentation_dimensions_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_customer_lifetime_value_label_file: Read complete after 0s [id=07b5288f71916ed9990ccc3a2ecb4945c8569f73]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.audience_segmentation_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_auto_audience_segmentation_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.create_gemini_model_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.aggregated_value_based_bidding_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.purchase_propensity_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_aggregated_value_based_bidding_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_segmentation_dimensions_file: Read complete after 1s [id=5e900e3e599ef5aca9609680c1a8048485bb4d51]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_auto_audience_segmentation_training_preparation_file: Read complete after 0s [id=68764abc7cda326dbb45de97f17286a401b7114d]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.create_gemini_model_file: Read complete after 0s [id=82b596fa76c21048b916e351833200330ae88ca8]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.aggregated_value_based_bidding_training_preparation_file: Read complete after 0s [id=70ff243c2c3e69ba2fd0240882bdf33e23e01e6d]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.audience_segmentation_training_preparation_file: Read complete after 0s [id=d1eec023979a32226d70627bc2e1ba0ed209d0ca]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.purchase_propensity_inference_preparation_file: Read complete after 0s [id=230377cff84fe2855e8e57f7aacc2ca6d2240c92]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_behaviour_revenue_insights_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_aggregated_value_based_bidding_training_preparation_file: Read complete after 0s [id=fb6f9e5120088e00ac529c159e55925b6fb70510]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_audience_segmentation_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.churn_propensity_label_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_scoped_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_customer_lifetime_value_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.aggregated_value_based_bidding_explanation_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_scoped_metrics_file: Read complete after 0s [id=1d0c5da299854209360e9f2926593a32165bf691]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_rolling_window_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_audience_segmentation_training_preparation_file: Read complete after 0s [id=77827024513585dda1b7fdb9f818d75395ac489f]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.churn_propensity_label_file: Read complete after 0s [id=aa1c7025b5e8c528734e8fe3cb1a81e77011aebf]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.customer_lifetime_value_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_customer_lifetime_value_training_preparation_file: Read complete after 0s [id=575308fa039dc402b9622d91ca963d7d6fd64b04]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_behaviour_revenue_insights_file: Read complete after 0s [id=d0469ef07b1af1bd6f5a7734e358876260ebc0ea]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.aggregated_value_based_bidding_explanation_preparation_file: Read complete after 0s [id=5893caf35f39e5712ef4ded8ef5116a804c0af8b]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_scoped_segmentation_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_segmentation_dimensions_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.churn_propensity_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_rolling_window_metrics_file: Read complete after 0s [id=e0f00bc875ea4120b54f83952213b5c62139b899]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_auto_audience_segmentation_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.customer_lifetime_value_inference_preparation_file: Read complete after 0s [id=56b111c18322fb36e668352dc08bd5e36799d48a]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.auto_audience_segmentation_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_scoped_segmentation_metrics_file: Read complete after 0s [id=c576d3bb57d7420c83d5472ae7ed01a272640e89]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_segmentation_dimensions_file: Read complete after 0s [id=57b217004b4745ad21dccfb3b914da4bb297059b]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_scoped_lifetime_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.churn_propensity_training_preparation_file: Read complete after 0s [id=4fcfa23dcd02f8ce06d393539424898a41ec445d]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_auto_audience_segmentation_inference_preparation_file: Read complete after 0s [id=d617c2157a1502028a78ad7cca450a2ef8ba5607]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_scoped_lifetime_metrics_file: Read complete after 0s [id=13940f8e8c94f2efdccc4eb1e9acf5f7d5dc9e93]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_purchase_propensity_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_scoped_lifetime_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_lifetime_dimensions_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_behaviour_revenue_insights_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.auto_audience_segmentation_training_preparation_file: Read complete after 0s [id=bc3b459fe435770cd93d1b61a6956ae96d1d90eb]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_lookback_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_purchase_propensity_inference_preparation_file: Read complete after 0s [id=8a77bdf831da660f03b24d9c1482a6695b69a19b]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.churn_propensity_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_lifetime_dimensions_file: Read complete after 0s [id=947fc99966b0160f1721ccb2abc04b422ba7f059]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_segmentation_dimensions_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_rolling_window_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_lookback_metrics_file: Read complete after 0s [id=5edd7141ec198b01c6b6e993be06549c17019b1e]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_scoped_lifetime_metrics_file: Read complete after 0s [id=d0397322704462e9717bef186fc5cf6c3a9aa4b5]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_behaviour_revenue_insights_file: Read complete after 0s [id=df36f60c61c4c2c9cf9ef5b8c53a64622ff00220]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_session_event_aggregated_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_segmentation_dimensions_file: Read complete after 0s [id=46f042eca23a4fe43c5953e134934317875c1105]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_dimensions_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_behaviour_revenue_insights_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_rolling_window_metrics_file: Read complete after 0s [id=229c5639f871227d270840724a617199f058d3ef]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_customer_lifetime_value_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.churn_propensity_inference_preparation_file: Read complete after 0s [id=5bdf9f82c60521335560c81bb32069888e00a8d9]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.purchase_propensity_label_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.purchase_propensity_label_file: Read complete after 0s [id=a1a11059018e381457080aec68389f4577109488]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_dimensions_file: Read complete after 0s [id=5574be9cca12dbff5cb0eb877f8a94527a13951c]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_user_session_event_aggregated_metrics_file: Read complete after 0s [id=191d7fbe19131ceba28bbe0838e81beff1dea8f1]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_purchase_propensity_label_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_purchase_propensity_label_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_behaviour_revenue_insights_file: Read complete after 0s [id=5c461d3be6695a79f5483fd019701e6d8538c627]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_customer_lifetime_value_inference_preparation_file: Read complete after 0s [id=55140b739ba351cc125d7be25200ba1b12b7a296]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_scoped_lifetime_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.customer_lifetime_value_label_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_backfill_purchase_propensity_label_file: Read complete after 0s [id=066fe56500f86214527b0adc051a0ea376f88622]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_session_event_aggregated_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_purchase_propensity_label_file: Read complete after 0s [id=d78c7179d439382816af0c340a692068922febad]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_lookback_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.customer_lifetime_value_label_file: Read complete after 0s [id=bfc8b316a4b45c3f202e76367a602f90c6fe3bbb]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_audience_segmentation_inference_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_session_event_aggregated_metrics_file: Read complete after 0s [id=a556ca9e2183e3e300a4b1247d55b25f0c99d030]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_rolling_window_metrics_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_scoped_lifetime_metrics_file: Read complete after 0s [id=573da784054b04d57f5493f075616937ed327962]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_lookback_metrics_file: Read complete after 0s [id=a7d1b5ffc634ab9679251276a241821090f3fc4e]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_audience_segmentation_inference_preparation_file: Read complete after 0s [id=8914c65e34723ec4b902f45ae8facae72d4d57cc]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_lifetime_dimensions_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.invoke_user_rolling_window_metrics_file: Read complete after 0s [id=e23d108f49605228fa8f8d385f2b6a619f65f5c8]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.purchase_propensity_training_preparation_file: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].module.log_export_bigquery.google_bigquery_table.main[\"dataform_googleapis_com_workflow_invocation_completion\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/maj_logs/tables/dataform_googleapis_com_workflow_invocation_completion]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].module.log_export_bigquery.google_bigquery_table.main[\"dataflow_googleapis_com_job_message\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/maj_logs/tables/dataflow_googleapis_com_job_message]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.user_lifetime_dimensions_file: Read complete after 0s [id=1bc273a710e938405d9011add005299b2cb0c5e1]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].module.dashboard_bigquery.google_bigquery_table.main[\"resource_link\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/maj_dashboard/tables/resource_link]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].module.log_export_bigquery.google_bigquery_table.main[\"aiplatform_googleapis_com_pipeline_job_events\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/maj_logs/tables/aiplatform_googleapis_com_pipeline_job_events]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].data.local_file.purchase_propensity_training_preparation_file: Read complete after 0s [id=192cd59e14b1d2b667af0d28fe115f182f7fec0f]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"bigquerystorage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerystorage.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"datapipelines.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/datapipelines.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"cloudfunctions.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudfunctions.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"dataform.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/dataform.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"bigquery.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquery.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"monitoring.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/monitoring.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"secretmanager.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/secretmanager.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"storage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"cloudasset.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudasset.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"logging.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/logging.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.data_processing_project_services.google_project_service.project_services[\"analyticsadmin.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/analyticsadmin.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].google_logging_project_sink.mds_daily_execution: Refreshing state... [id=projects/marketing-data-engine-demo/sinks/mds_execution_export]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].data.template_file.looker_studio_dashboard_url: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].google_logging_project_sink.vertex_pipeline_execution: Refreshing state... [id=projects/marketing-data-engine-demo/sinks/vertex_pipeline_execution_export]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"monitoring.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/monitoring.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].google_logging_project_sink.activation_pipeline_execution: Refreshing state... [id=projects/marketing-data-engine-demo/sinks/activation_pipeline_execution_export]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].data.template_file.looker_studio_dashboard_url: Read complete after 0s [id=f5f47a24cf6dc9666f197b91b27762ea36a19f0f0cc6105e2e48ea203e2c9eff]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].google_storage_bucket_object.resource_link_load_file: Refreshing state... [id=maj-monitor-marketing-data-engine-demo-load_links.json]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"aiplatform.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/aiplatform.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"bigquerystorage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerystorage.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"storage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"dataflow.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/dataflow.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"bigquery.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquery.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"storage-api.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage-api.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"bigqueryconnection.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigqueryconnection.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"aiplatform.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/aiplatform.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"logging.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/logging.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].module.project_services.google_project_service.project_services[\"artifactregistry.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/artifactregistry.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"artifactregistry.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/artifactregistry.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"cloudbuild.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudbuild.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"logging.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/logging.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"bigquery.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquery.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"bigquerystorage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerystorage.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"storage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"storage-api.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage-api.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.project_services.google_project_service.project_services[\"monitoring.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/monitoring.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].google_project_iam_member.activation_pipeline_execution_member: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:service-216235317801@gcp-sa-logging.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_predictions.google_bigquery_dataset.main: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_predictions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_cloudfunctions_api: Refreshing state... [id=7208890644649950059]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_pubsub_api: Refreshing state... [id=7167048528977996500]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_dataflow_api: Refreshing state... [id=6429955671735647781]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].google_project_iam_member.vertex_pipeline_execution_member: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:service-216235317801@gcp-sa-logging.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].google_bigquery_job.monitor_resources_load: Refreshing state... [id=projects/marketing-data-engine-demo/jobs/ec487817-9b8b-b529-3cd1-7877b020d4a4]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.monitoring[0].google_project_iam_member.mds_daily_execution_member: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:service-216235317801@gcp-sa-logging.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_cloudbuild_api: Refreshing state... [id=5865646642453962477]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_artifactregistry_api: Refreshing state... [id=6007568475502613289]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_bigquery_api: Refreshing state... [id=1332910930633736970]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_analyticsadmin_api: Refreshing state... [id=1766041404048401478]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.null_resource.check_dataform_api: Refreshing state... [id=4310407749740216459]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.null_resource.check_secretmanager_api: Refreshing state... [id=2934745949246817981]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].null_resource.check_secretmanager_api: Refreshing state... [id=6757606984707921070]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.null_resource.check_bigquery_api: Refreshing state... [id=6485525931220944598]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_pubsub_topic.activation_trigger: Refreshing state... [id=projects/marketing-data-engine-demo/topics/activation-trigger]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.google_project.project: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_artifact_registry_repository.activation_repository: Refreshing state... [id=projects/marketing-data-engine-demo/locations/us-central1/repositories/activation-docker-repo]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].null_resource.create_custom_dimensions: Refreshing state... [id=1067855015837694190]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.google_project_iam_member.email-role[\"roles/dataform.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/dataform.admin/user:ctimoteo@google.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].null_resource.create_custom_events: Refreshing state... [id=7226775609353110261]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.google_project.project: Read complete after 0s [id=projects/marketing-data-engine-demo]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.google_project_iam_member.email-role[\"roles/dataform.editor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/dataform.editor/user:ctimoteo@google.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.google_project_iam_member.email-role[\"roles/iam.serviceAccountUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/iam.serviceAccountUser/user:ctimoteo@google.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.google_secret_manager_secret.github-secret: Refreshing state... [id=projects/marketing-data-engine-demo/secrets/Github_token]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.check_bigquery_api: Refreshing state... [id=6074515469762063310]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.check_dataflow_api: Refreshing state... [id=5913183956771365927]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.check_artifactregistry_api: Refreshing state... [id=3794145044155992161]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.check_aiplatform_api: Refreshing state... [id=1275100169983256448]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_service_account.service_accounts[\"trigger-function\"]: Refreshing state... [id=projects/marketing-data-engine-demo/serviceAccounts/activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.google_service_account.service_accounts[\"dataflow-worker\"]: Refreshing state... [id=projects/marketing-data-engine-demo/serviceAccounts/activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.bigquery.google_bigquery_dataset.main: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/activation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/run.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/run.admin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.builds.editor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.builds.editor/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/iam.serviceAccountUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/iam.serviceAccountUser/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.workerPoolEditor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.workerPoolEditor/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.serviceAgent\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.serviceAgent/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.tokenAccessor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.tokenAccessor/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudfunctions.developer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudfunctions.developer/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.integrations.owner\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.integrations.owner/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.connectionViewer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.connectionViewer/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/storage.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/storage.admin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/container.developer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/container.developer/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/logging.logWriter\"]: Refreshing state... [id=marketing-data-engine-demo/roles/logging.logWriter/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/logging.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/logging.admin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.builds.builder\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.builds.builder/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/iam.serviceAccountAdmin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/iam.serviceAccountAdmin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/appengine.appAdmin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/appengine.appAdmin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.builds.viewer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.builds.viewer/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/owner\"]: Refreshing state... [id=marketing-data-engine-demo/roles/owner/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/viewer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/viewer/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/artifactregistry.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/artifactregistry.admin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.workerPoolOwner\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.workerPoolOwner/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/compute.instanceAdmin.v1\"]: Refreshing state... [id=marketing-data-engine-demo/roles/compute.instanceAdmin.v1/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.workerPoolViewer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.workerPoolViewer/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/secretmanager.secretAccessor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/secretmanager.secretAccessor/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/firebase.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/firebase.admin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/iam.serviceAccountTokenCreator\"]: Refreshing state... [id=marketing-data-engine-demo/roles/iam.serviceAccountTokenCreator/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.readTokenAccessor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.readTokenAccessor/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.integrations.editor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.integrations.editor/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.integrations.viewer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.integrations.viewer/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudkms.cryptoKeyDecrypter\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudkms.cryptoKeyDecrypter/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.connectionAdmin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.connectionAdmin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.builds.approver\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.builds.approver/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_project_iam_member.cloud_build_job_service_account[\"roles/cloudbuild.workerPoolUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/cloudbuild.workerPoolUser/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_predictions.google_bigquery_table.main[\"latest\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_predictions/tables/latest]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.wait_for_dataflow_worker_sa_creation: Refreshing state... [id=4997368680923922129]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_artifact_registry_repository.pipelines_docker_repo: Refreshing state... [id=projects/marketing-data-engine-demo/locations/us-central1/repositories/pipelines-docker-repo]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_storage_bucket.custom_model_bucket: Refreshing state... [id=marketing-data-engine-demo-custom-models]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_storage_bucket.pipelines_bucket: Refreshing state... [id=marketing-data-engine-demo-pipelines]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_service_account.service_account: Refreshing state... [id=projects/marketing-data-engine-demo/serviceAccounts/vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_artifact_registry_repository.pipelines-repo: Refreshing state... [id=projects/marketing-data-engine-demo/locations/us-central1/repositories/pipelines-repo]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.wait_for_vertex_pipelines_sa_creation: Refreshing state... [id=2244952251481429642]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.secret_manager.google_secret_manager_secret.secrets[\"ga4-measurement-id\"]: Refreshing state... [id=projects/marketing-data-engine-demo/secrets/ga4-measurement-id]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.secret_manager.google_secret_manager_secret.secrets[\"ga4-measurement-secret\"]: Refreshing state... [id=projects/marketing-data-engine-demo/secrets/ga4-measurement-secret]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.google_secret_manager_secret_version.secret-version-github: Refreshing state... [id=projects/216235317801/secrets/Github_token/versions/1]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.data.google_secret_manager_secret.github_secret_name: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.data.google_secret_manager_secret.github_secret_name: Read complete after 0s\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/storage.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/storage.admin/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/secretmanager.secretAccessor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/secretmanager.secretAccessor/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/artifactregistry.reader\"]: Refreshing state... [id=marketing-data-engine-demo/roles/artifactregistry.reader/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/dataflow.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/dataflow.admin/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/bigquery.dataEditor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/iam.serviceAccountUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/iam.serviceAccountUser/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/dataflow.worker\"]: Refreshing state... [id=marketing-data-engine-demo/roles/dataflow.worker/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.google_project_iam_member.project-roles[\"trigger-function-marketing-data-engine-demo=>roles/pubsub.editor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/pubsub.editor/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"secretmanager.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/secretmanager.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"bigquerymigration.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerymigration.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"cloudscheduler.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/cloudscheduler.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"bigquery.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquery.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"dataform.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/dataform.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"logging.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/logging.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"bigquerydatatransfer.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerydatatransfer.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"monitoring.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/monitoring.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"storage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/storage.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"bigquerystorage.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/bigquerystorage.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.data.template_file.keys[\"dataflow-worker\"]: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].module.data_processing_project_services.google_project_service.project_services[\"workflows.googleapis.com\"]: Refreshing state... [id=marketing-data-engine-demo/workflows.googleapis.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.data.template_file.keys[\"trigger-function\"]: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.data.template_file.keys[\"dataflow-worker\"]: Read complete after 0s [id=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.google_project_iam_member.project-roles[\"dataflow-worker-marketing-data-engine-demo=>roles/bigquery.dataEditor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.trigger_function_account.data.template_file.keys[\"trigger-function\"]: Read complete after 0s [id=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.google_project_iam_member.project-roles[\"dataflow-worker-marketing-data-engine-demo=>roles/dataflow.worker\"]: Refreshing state... [id=marketing-data-engine-demo/roles/dataflow.worker/serviceAccount:activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.google_project_iam_member.project-roles[\"dataflow-worker-marketing-data-engine-demo=>roles/bigquery.jobUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.jobUser/serviceAccount:activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.google_project_iam_member.project-roles[\"dataflow-worker-marketing-data-engine-demo=>roles/artifactregistry.writer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/artifactregistry.writer/serviceAccount:activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_service_account.google_project_iam_member.project-roles[\"dataflow-worker-marketing-data-engine-demo=>roles/dataflow.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/dataflow.admin/serviceAccount:activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/pubsub.publisher\"]: Refreshing state... [id=marketing-data-engine-demo/roles/pubsub.publisher/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/bigquery.jobUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.jobUser/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.build_push_pipelines_components_image: Refreshing state... [id=8222856044757880866]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/compute.osLogin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/compute.osLogin/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/aiplatform.user\"]: Refreshing state... [id=marketing-data-engine-demo/roles/aiplatform.user/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/iap.tunnelResourceAccessor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/iap.tunnelResourceAccessor/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/artifactregistry.reader\"]: Refreshing state... [id=marketing-data-engine-demo/roles/artifactregistry.reader/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/bigquery.dataEditor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/dataflow.developer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/dataflow.developer/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/bigquery.connectionUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.connectionUser/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_roles[\"roles/storage.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/storage.admin/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.pipelines_sa_mds_project_roles[\"roles/bigquery.dataViewer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataViewer/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.secret_manager.google_secret_manager_secret_version.secret-version[\"ga4-measurement-id\"]: Refreshing state... [id=projects/216235317801/secrets/ga4-measurement-id/versions/1]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.secret_manager.google_secret_manager_secret_version.secret-version[\"ga4-measurement-secret\"]: Refreshing state... [id=projects/216235317801/secrets/ga4-measurement-secret/versions/1]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.google_dataform_repository.marketing-analytics: Refreshing state... [id=projects/marketing-data-engine-demo/locations/us-central1/repositories/marketing-analytics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_feature_engineering_auto_audience_segmentation_pipeline: Refreshing state... [id=4177109568613300128]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.check_pipeline_docker_image_pushed: Refreshing state... [id=670239096754469362]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].null_resource.check_bigquery_api: Refreshing state... [id=8823785816326872758]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].null_resource.check_aiplatform_api: Refreshing state... [id=592572463848586336]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_feature_engineering_aggregated_value_based_bidding_pipeline: Refreshing state... [id=3313216741825247466]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.build_logs_bucket.google_storage_bucket.bucket: Refreshing state... [id=activation-logs-marketing-data-engine-demo]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_dataset.feature_store: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_connection.vertex_ai_connection: Refreshing state... [id=projects/marketing-data-engine-demo/locations/US/connections/vertex_ai]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_feature_engineering_audience_segmentation_pipeline: Refreshing state... [id=745359039134428554]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_dataset.purchase_propensity: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/purchase_propensity]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_dataset.auto_audience_segmentation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/auto_audience_segmentation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_dataset.audience_segmentation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/audience_segmentation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_dataset.churn_propensity: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/churn_propensity]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.aggregate_last_day_predictions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_predictions/routines/aggregate_last_day_predictions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_dataset.customer_lifetime_value: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/customer_lifetime_value]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.build_logs_bucket.google_storage_bucket_iam_member.members[\"roles/storage.admin serviceAccount:216235317801-compute@developer.gserviceaccount.com\"]: Refreshing state... [id=b/activation-logs-marketing-data-engine-demo/roles/storage.admin/serviceAccount:216235317801-compute@developer.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.null_resource.wait_for_dataform_sa_creation: Refreshing state... [id=5068235229441173816]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_feature_engineering_purchase_propensity_pipeline: Refreshing state... [id=6301381851538149321]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.function_bucket.google_storage_bucket.bucket: Refreshing state... [id=activation-trigger-marketing-data-engine-demo]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_bucket.google_storage_bucket.bucket: Refreshing state... [id=activation-app-marketing-data-engine-demo]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.gemini_insights.google_bigquery_dataset.main: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/gemini_insights]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_project_iam_member.vertex_ai_connection_sa_roles[\"roles/bigquery.connectionAdmin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.connectionAdmin/serviceAccount:bqcx-216235317801-k5go@gcp-sa-bigquery-condel.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_project_iam_member.vertex_ai_connection_sa_roles[\"roles/bigquery.jobUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.jobUser/serviceAccount:bqcx-216235317801-k5go@gcp-sa-bigquery-condel.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_project_iam_member.vertex_ai_connection_sa_roles[\"roles/storage.admin\"]: Refreshing state... [id=marketing-data-engine-demo/roles/storage.admin/serviceAccount:bqcx-216235317801-k5go@gcp-sa-bigquery-condel.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_project_iam_member.vertex_ai_connection_sa_roles[\"roles/storage.objectViewer\"]: Refreshing state... [id=marketing-data-engine-demo/roles/storage.objectViewer/serviceAccount:bqcx-216235317801-k5go@gcp-sa-bigquery-condel.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_project_iam_member.vertex_ai_connection_sa_roles[\"roles/aiplatform.user\"]: Refreshing state... [id=marketing-data-engine-demo/roles/aiplatform.user/serviceAccount:bqcx-216235317801-k5go@gcp-sa-bigquery-condel.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_project_iam_member.vertex_ai_connection_sa_roles[\"roles/bigquery.connectionUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.connectionUser/serviceAccount:bqcx-216235317801-k5go@gcp-sa-bigquery-condel.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_project_iam_member.vertex_ai_connection_sa_roles[\"roles/bigquery.dataEditor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:bqcx-216235317801-k5go@gcp-sa-bigquery-condel.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.purchase_propensity_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/purchase_propensity/tables/purchase_propensity_inference_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.purchase_propensity_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/purchase_propensity/routines/purchase_propensity_inference_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_purchase_propensity_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/purchase_propensity/routines/invoke_purchase_propensity_training_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.purchase_propensity_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/purchase_propensity/routines/purchase_propensity_training_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_purchase_propensity_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/purchase_propensity/routines/invoke_purchase_propensity_inference_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_auto_audience_segmentation_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/auto_audience_segmentation/routines/invoke_auto_audience_segmentation_training_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_vbb.google_bigquery_dataset.main: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_auto_audience_segmentation_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/auto_audience_segmentation/routines/invoke_auto_audience_segmentation_inference_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.auto_audience_segmentation_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/auto_audience_segmentation/routines/auto_audience_segmentation_inference_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.auto_audience_segmentation_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/auto_audience_segmentation/routines/auto_audience_segmentation_training_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.customer_lifetime_value_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/customer_lifetime_value_label]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_purchase_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_purchase_propensity_label]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_segmentation_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_segmentation_dimensions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_segmentation_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_segmentation_dimensions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_lookback_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_lookback_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_customer_lifetime_value_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_customer_lifetime_value_label]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_segmentation_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_segmentation_dimensions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_rolling_window_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_rolling_window_lifetime_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_churn_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_churn_propensity_label]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_purchase_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_purchase_propensity_label]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_dimensions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_rolling_window_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_rolling_window_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_lifetime_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_lifetime_dimensions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_dimensions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_rolling_window_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_rolling_window_lifetime_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.churn_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/churn_propensity_label]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_lookback_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_lookback_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_session_event_aggregated_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_session_event_aggregated_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_scoped_segmentation_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_scoped_segmentation_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_dimensions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_scoped_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_scoped_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.churn_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/churn_propensity_label]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_scoped_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_scoped_lifetime_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_rolling_window_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_rolling_window_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_rolling_window_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_rolling_window_lifetime_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_rolling_window_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_rolling_window_lifetime_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.purchase_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/purchase_propensity_label]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_lookback_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_lookback_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_scoped_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_scoped_lifetime_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_lifetime_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_lifetime_dimensions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_segmentation_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_segmentation_dimensions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_scoped_segmentation_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_scoped_segmentation_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_scoped_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_scoped_lifetime_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_lifetime_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_lifetime_dimensions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_scoped_lifetime_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_scoped_lifetime_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.purchase_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/purchase_propensity_label]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_scoped_segmentation_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/user_scoped_segmentation_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_session_event_aggregated_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_session_event_aggregated_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_churn_propensity_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_churn_propensity_label]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_rolling_window_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_rolling_window_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_scoped_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_scoped_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_lookback_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_lookback_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_lifetime_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_lifetime_dimensions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_customer_lifetime_value_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_customer_lifetime_value_label]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_session_event_aggregated_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_session_event_aggregated_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.user_dimensions: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/user_dimensions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_session_event_aggregated_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_session_event_aggregated_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_rolling_window_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_rolling_window_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_scoped_segmentation_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_scoped_segmentation_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_scoped_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_user_scoped_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.customer_lifetime_value_label: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/tables/customer_lifetime_value_label]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_scoped_metrics: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/feature_store/routines/invoke_backfill_user_scoped_metrics]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_audience_segmentation_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/audience_segmentation/routines/invoke_audience_segmentation_inference_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.audience_segmentation_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/audience_segmentation/routines/audience_segmentation_inference_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.audience_segmentation_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/audience_segmentation/tables/audience_segmentation_inference_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.audience_segmentation_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/audience_segmentation/routines/audience_segmentation_training_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_audience_segmentation_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/audience_segmentation/routines/invoke_audience_segmentation_training_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.google_bigquery_dataset_iam_member.dataform-ga4-export-reader: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/analytics_434623633/roles/bigquery.dataViewer/serviceAccount:service-216235317801@gcp-sa-dataform.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.google_bigquery_dataset_iam_member.dataform-ads-export-reader[0]: Refreshing state... [id=projects/google.com:superb-receiver-344820/datasets/ads_demoverse/roles/bigquery.dataViewer/serviceAccount:service-216235317801@gcp-sa-dataform.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.google_project_iam_member.dataform-serviceaccount[\"roles/bigquery.jobUser\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.jobUser/serviceAccount:service-216235317801@gcp-sa-dataform.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.google_project_iam_member.dataform-serviceaccount[\"roles/secretmanager.secretAccessor\"]: Refreshing state... [id=marketing-data-engine-demo/roles/secretmanager.secretAccessor/serviceAccount:service-216235317801@gcp-sa-dataform.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.google_project_iam_member.dataform-bigquery-data-owner[\"roles/bigquery.dataOwner\"]: Refreshing state... [id=marketing-data-engine-demo/roles/bigquery.dataOwner/serviceAccount:service-216235317801@gcp-sa-dataform.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_feature_engineering_churn_propensity_pipeline: Refreshing state... [id=3833299164693309312]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_churn_propensity_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/churn_propensity/routines/invoke_churn_propensity_inference_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_churn_propensity_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/churn_propensity/routines/invoke_churn_propensity_training_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.churn_propensity_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/churn_propensity/routines/churn_propensity_inference_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.churn_propensity_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/churn_propensity/tables/churn_propensity_inference_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.churn_propensity_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/churn_propensity/routines/churn_propensity_training_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.customer_lifetime_value_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/customer_lifetime_value/routines/customer_lifetime_value_training_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_customer_lifetime_value_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/customer_lifetime_value/routines/invoke_customer_lifetime_value_training_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_table.customer_lifetime_value_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/customer_lifetime_value/tables/customer_lifetime_value_inference_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.customer_lifetime_value_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/customer_lifetime_value/routines/customer_lifetime_value_inference_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_customer_lifetime_value_inference_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/customer_lifetime_value/routines/invoke_customer_lifetime_value_inference_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.function_bucket.google_storage_bucket_iam_member.members[\"roles/storage.admin serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com\"]: Refreshing state... [id=b/activation-trigger-marketing-data-engine-demo/roles/storage.admin/serviceAccount:activation-trigger-function@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.pipeline_bucket.google_storage_bucket_iam_member.members[\"roles/storage.admin serviceAccount:activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com\"]: Refreshing state... [id=b/activation-app-marketing-data-engine-demo/roles/storage.admin/serviceAccount:activation-dataflow-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.gemini_insights.google_bigquery_table.main[\"user_behaviour_revenue_insights_daily\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/gemini_insights/tables/user_behaviour_revenue_insights_daily]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.gemini_insights.google_bigquery_table.main[\"user_behaviour_revenue_insights_weekly\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/gemini_insights/tables/user_behaviour_revenue_insights_weekly]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.gemini_insights.google_bigquery_table.main[\"user_behaviour_revenue_insights_monthly\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/gemini_insights/tables/user_behaviour_revenue_insights_monthly]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_vbb.google_bigquery_table.main[\"vbb_weights\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/tables/vbb_weights]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_vbb.google_bigquery_table.main[\"aggregated_value_based_bidding_correlation\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/tables/aggregated_value_based_bidding_correlation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_vbb.google_bigquery_table.main[\"aggregated_value_based_bidding_volume_daily\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/tables/aggregated_value_based_bidding_volume_daily]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_vbb.google_bigquery_table.main[\"aggregated_value_based_bidding_volume_weekly\"]: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/tables/aggregated_value_based_bidding_volume_weekly]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_feature_engineering_customer_lifetime_value_pipeline: Refreshing state... [id=4148256259979416441]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.audience_segmentation_query_template_file: Refreshing state... [id=activation-app-marketing-data-engine-demo-configuration/audience_segmentation_query_template.sqlx]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.activation_trigger_archive: Refreshing state... [id=activation-trigger-marketing-data-engine-demo-activation_trigger_source.zip]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.purchase_propensity_query_template_file: Refreshing state... [id=activation-app-marketing-data-engine-demo-configuration/purchase_propensity_query_template.sqlx]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.auto_audience_segmentation_csv_export_query: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.auto_audience_segmentation_csv_export_query: Read complete after 0s [id=2a1b777fbb6328fd3380f0159aac23b5473cdad5b69e0ed24c14450938b9952e]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.audience_segmentation_csv_export_query: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.audience_segmentation_csv_export_query: Read complete after 0s [id=dccb98247aa53c588cebe1033ba41da7baa5ebd346f011e27c6f24d5b73afa1f]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.cltv_query_template_file: Refreshing state... [id=activation-app-marketing-data-engine-demo-configuration/cltv_query_template.sqlx]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.churn_propensity_csv_export_query: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.churn_propensity_csv_export_query: Read complete after 0s [id=b638a2fe7ccfd7a55819164378fd9904d7e0b04beb0cafe22f4cd083bf1bc5c8]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.purchase_propensity_csv_export_query: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.purchase_propensity_csv_export_query: Read complete after 0s [id=0c6e9ef9db797d508fa929999f43aaf37f538ec42621d8c4b6ccea3133656132]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.churn_propensity_query_template_file: Refreshing state... [id=activation-app-marketing-data-engine-demo-configuration/churn_propensity_query_template.sqlx]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.auto_audience_segmentation_query_template_file: Refreshing state... [id=activation-app-marketing-data-engine-demo-configuration/auto_audience_segmentation_query_template.sqlx]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.measurement_protocol_payload_template_file: Refreshing state... [id=activation-app-marketing-data-engine-demo-configuration/app_payload_template.jinja2]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].null_resource.check_gemini_insights_dataset_exists: Refreshing state... [id=8420743605622533331]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.cltv_csv_export_query: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.cltv_csv_export_query: Read complete after 0s [id=1f1fd2457b657c5a4624922d5e9729860fdc87452af04f051fbabf531cc14922]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_aggregated_value_based_bidding_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/routines/invoke_aggregated_value_based_bidding_training_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.aggregated_value_based_bidding_training_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/routines/aggregated_value_based_bidding_training_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.aggregated_value_based_bidding_explanation_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/routines/aggregated_value_based_bidding_explanation_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_aggregated_value_based_bidding_explanation_preparation: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/aggregated_vbb/routines/invoke_aggregated_value_based_bidding_explanation_preparation]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_container.null_resource.module_depends_on[0]: Refreshing state... [id=83278692015671361]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_purchase_propensity_training_pipelines: Refreshing state... [id=6494667367103048029]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_bigquery_routine.export_auto_audience_segmentation_procedure: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/activation/routines/export_auto_audience_segmentation_predictions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_bigquery_routine.export_audience_segmentation_procedure: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/activation/routines/export_audience_segmentation_predictions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_bigquery_routine.export_purchase_propensity_procedure: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/activation/routines/export_purchase_propensity_predictions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_bigquery_routine.export_churn_propensity_procedure: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/activation/routines/export_churn_propensity_predictions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].null_resource.create_gemini_model: Refreshing state... [id=8588488219624433529]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].null_resource.check_cloudscheduler_api: Refreshing state... [id=2245006727529558797]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].null_resource.check_dataform_api: Refreshing state... [id=4577413674659493716]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].null_resource.check_bigquery_api: Refreshing state... [id=6932712767637221730]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].null_resource.check_workflows_api: Refreshing state... [id=6145400500190950450]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_bigquery_routine.export_cltv_procedure: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/activation/routines/export_cltv_predictions]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_purchase_propensity_prediction_pipelines: Refreshing state... [id=2471105579040554729]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_container.null_resource.run_command[0]: Refreshing state... [id=4110452576566970343]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_container.null_resource.run_destroy_command[0]: Refreshing state... [id=8209321593200338708]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].google_service_account.scheduler: Refreshing state... [id=projects/marketing-data-engine-demo/serviceAccounts/workflow-scheduler-prod@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].null_resource.check_gemini_model_exists: Refreshing state... [id=5380384111160946416]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].google_service_account.workflow-dataform: Refreshing state... [id=projects/marketing-data-engine-demo/serviceAccounts/workflow-dataform-prod@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.activation_type_configuration: Reading...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].data.template_file.activation_type_configuration: Read complete after 0s [id=34e91f0be3d922532ab8101964b3614388b215dfa965935ef82cb248836681e0]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].null_resource.wait_for_scheduler_sa_creation: Refreshing state... [id=3576051581150556634]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].null_resource.wait_for_workflows_sa_creation: Refreshing state... [id=5608692098556736051]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_propensity_clv_training_pipelines: Refreshing state... [id=2697769765996893796]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_storage_bucket_object.activation_type_configuration_file: Refreshing state... [id=activation-app-marketing-data-engine-demo-configuration/activation_type_configuration.json]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_user_behaviour_revenue_insights: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/gemini_insights/routines/invoke_user_behaviour_revenue_insights]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.invoke_backfill_user_behaviour_revenue_insights: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/gemini_insights/routines/invoke_backfill_user_behaviour_revenue_insights]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].google_bigquery_routine.user_behaviour_revenue_insights: Refreshing state... [id=projects/marketing-data-engine-demo/datasets/gemini_insights/routines/user_behaviour_revenue_insights]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_clv_training_pipelines: Refreshing state... [id=1341591284116756448]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].google_project_iam_member.scheduler-workflow-invoker: Refreshing state... [id=marketing-data-engine-demo/roles/workflows.invoker/serviceAccount:workflow-scheduler-prod@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].google_workflows_workflow.dataform-incremental-workflow: Refreshing state... [id=projects/marketing-data-engine-demo/locations/us-central1/workflows/dataform-prod-incremental]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].google_project_iam_member.worflow-dataform-dataform-editor: Refreshing state... [id=marketing-data-engine-demo/roles/dataform.editor/serviceAccount:workflow-dataform-prod@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_clv_prediction_pipelines: Refreshing state... [id=5219397379491605160]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_template.null_resource.module_depends_on[0]: Refreshing state... [id=9213524499649794300]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_segmentation_training_pipelines: Refreshing state... [id=4209036632928598274]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Refreshing state... [id=projects/marketing-data-engine-demo/locations/us-central1/functions/activation-trigger]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_template.null_resource.additional_components[0]: Refreshing state... [id=3284535500699919722]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_segmentation_prediction_pipelines: Refreshing state... [id=8422606662598937866]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_template.null_resource.run_command[0]: Refreshing state... [id=7045031403572418317]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_auto_segmentation_training_pipelines: Refreshing state... [id=4338505239913806672]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_template.null_resource.run_destroy_command[0]: Refreshing state... [id=4355940137061160409]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.activation_pipeline_template.null_resource.additional_components_destroy[0]: Refreshing state... [id=2166393773558090798]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_auto_segmentation_prediction_pipelines: Refreshing state... [id=8725944810905589710]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_value_based_bidding_training_pipelines: Refreshing state... [id=1646002337402538490]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_value_based_bidding_explanation_pipelines: Refreshing state... [id=9016639052566760345]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_reporting_preparation_aggregate_predictions_pipelines: Refreshing state... [id=7138244549178947766]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_churn_propensity_training_pipelines: Refreshing state... [id=5723798157680302731]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.data_store.module.dataform-workflow-prod[0].google_cloud_scheduler_job.daily-dataform-increments: Refreshing state... [id=projects/marketing-data-engine-demo/locations/us-central1/jobs/daily-dataform-prod]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_churn_propensity_prediction_pipelines: Refreshing state... [id=3337108508686395106]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].null_resource.compile_gemini_insights_pipelines: Refreshing state... [id=2341849278084692537]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.add_invoker_binding.null_resource.run_command[0]: Refreshing state... [id=2685704107534572535]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].module.add_invoker_binding.null_resource.run_destroy_command[0]: Refreshing state... [id=8513968182222052250]\u001b[0m\n", - "\n", - "Terraform used the selected providers to generate the following execution\n", - "plan. Resource actions are indicated with the following symbols:\n", - " \u001b[32m+\u001b[0m create\u001b[0m\n", - " \u001b[33m~\u001b[0m update in-place\u001b[0m\n", - "\u001b[31m-\u001b[0m/\u001b[32m+\u001b[0m destroy and then create replacement\u001b[0m\n", - "\n", - "Terraform will perform the following actions:\n", - "\n", - "\u001b[1m # module.activation[0].google_cloudfunctions2_function.activation_trigger_cf\u001b[0m will be updated in-place\n", - "\u001b[0m \u001b[33m~\u001b[0m\u001b[0m resource \"google_cloudfunctions2_function\" \"activation_trigger_cf\" {\n", - " id = \"projects/marketing-data-engine-demo/locations/us-central1/functions/activation-trigger\"\n", - " name = \"activation-trigger\"\n", - " \u001b[90m# (7 unchanged attributes hidden)\u001b[0m\u001b[0m\n", - "\n", - " \u001b[33m~\u001b[0m\u001b[0m service_config {\n", - " \u001b[33m~\u001b[0m\u001b[0m environment_variables = {\n", - " \u001b[31m-\u001b[0m\u001b[0m \"LOG_EXECUTION_ID\" = \"true\" \u001b[90m-> null\u001b[0m\u001b[0m\n", - " \u001b[90m# (7 unchanged elements hidden)\u001b[0m\u001b[0m\n", - " }\n", - " \u001b[90m# (11 unchanged attributes hidden)\u001b[0m\u001b[0m\n", - "\n", - " \u001b[90m# (2 unchanged blocks hidden)\u001b[0m\u001b[0m\n", - " }\n", - "\n", - " \u001b[90m# (2 unchanged blocks hidden)\u001b[0m\u001b[0m\n", - " }\n", - "\n", - "\u001b[1m # module.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/bigquery.dataEditor\"]\u001b[0m will be created\n", - "\u001b[0m \u001b[32m+\u001b[0m\u001b[0m resource \"google_project_iam_member\" \"dataflow_worker_sa_roles\" {\n", - " \u001b[32m+\u001b[0m\u001b[0m etag = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m id = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m member = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m project = \"marketing-data-engine-demo\"\n", - " \u001b[32m+\u001b[0m\u001b[0m role = \"roles/bigquery.dataEditor\"\n", - " }\n", - "\n", - "\u001b[1m # module.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/bigquery.jobUser\"]\u001b[0m will be created\n", - "\u001b[0m \u001b[32m+\u001b[0m\u001b[0m resource \"google_project_iam_member\" \"dataflow_worker_sa_roles\" {\n", - " \u001b[32m+\u001b[0m\u001b[0m etag = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m id = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m member = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m project = \"marketing-data-engine-demo\"\n", - " \u001b[32m+\u001b[0m\u001b[0m role = \"roles/bigquery.jobUser\"\n", - " }\n", - "\n", - "\u001b[1m # module.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/dataflow.worker\"]\u001b[0m will be created\n", - "\u001b[0m \u001b[32m+\u001b[0m\u001b[0m resource \"google_project_iam_member\" \"dataflow_worker_sa_roles\" {\n", - " \u001b[32m+\u001b[0m\u001b[0m etag = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m id = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m member = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m project = \"marketing-data-engine-demo\"\n", - " \u001b[32m+\u001b[0m\u001b[0m role = \"roles/dataflow.worker\"\n", - " }\n", - "\n", - "\u001b[1m # module.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/storage.objectAdmin\"]\u001b[0m will be created\n", - "\u001b[0m \u001b[32m+\u001b[0m\u001b[0m resource \"google_project_iam_member\" \"dataflow_worker_sa_roles\" {\n", - " \u001b[32m+\u001b[0m\u001b[0m etag = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m id = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m member = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m project = \"marketing-data-engine-demo\"\n", - " \u001b[32m+\u001b[0m\u001b[0m role = \"roles/storage.objectAdmin\"\n", - " }\n", - "\n", - "\u001b[1m # module.pipelines[0].google_service_account.dataflow_worker_service_account\u001b[0m will be created\n", - "\u001b[0m \u001b[32m+\u001b[0m\u001b[0m resource \"google_service_account\" \"dataflow_worker_service_account\" {\n", - " \u001b[32m+\u001b[0m\u001b[0m account_id = \"df-worker\"\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"Service Account to run Dataflow jobs\"\n", - " \u001b[32m+\u001b[0m\u001b[0m disabled = false\n", - " \u001b[32m+\u001b[0m\u001b[0m display_name = \"df-worker\"\n", - " \u001b[32m+\u001b[0m\u001b[0m email = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m id = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m member = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m name = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m project = \"marketing-data-engine-demo\"\n", - " \u001b[32m+\u001b[0m\u001b[0m unique_id = (known after apply)\n", - " }\n", - "\n", - "\u001b[1m # module.pipelines[0].google_service_account_iam_member.dataflow_sa_iam\u001b[0m will be created\n", - "\u001b[0m \u001b[32m+\u001b[0m\u001b[0m resource \"google_service_account_iam_member\" \"dataflow_sa_iam\" {\n", - " \u001b[32m+\u001b[0m\u001b[0m etag = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m id = (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m member = \"serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com\"\n", - " \u001b[32m+\u001b[0m\u001b[0m role = \"roles/iam.serviceAccountUser\"\n", - " \u001b[32m+\u001b[0m\u001b[0m service_account_id = (known after apply)\n", - " }\n", - "\n", - "\u001b[1m # module.feature_store[0].module.aggregated_predictions.google_bigquery_table.main[\"latest\"]\u001b[0m must be \u001b[1m\u001b[31mreplaced\u001b[0m\n", - "\u001b[0m\u001b[31m-\u001b[0m/\u001b[32m+\u001b[0m\u001b[0m resource \"google_bigquery_table\" \"main\" {\n", - " \u001b[33m~\u001b[0m\u001b[0m creation_time = 1727272973853 -> (known after apply)\n", - " \u001b[33m~\u001b[0m\u001b[0m etag = \"LyDuL6p3V8G2Eqzr58kqcg==\" -> (known after apply)\n", - " \u001b[33m~\u001b[0m\u001b[0m expiration_time = 0 -> (known after apply)\n", - " \u001b[32m+\u001b[0m\u001b[0m friendly_name = \"latest\"\n", - " \u001b[33m~\u001b[0m\u001b[0m id = \"projects/marketing-data-engine-demo/datasets/aggregated_predictions/tables/latest\" -> (known after apply)\n", - " \u001b[31m-\u001b[0m\u001b[0m labels = {} \u001b[90m-> null\u001b[0m\u001b[0m\n", - " \u001b[33m~\u001b[0m\u001b[0m last_modified_time = 1727272973854 -> (known after apply)\n", - " \u001b[33m~\u001b[0m\u001b[0m location = \"US\" -> (known after apply)\n", - " \u001b[33m~\u001b[0m\u001b[0m num_bytes = 15101769 -> (known after apply)\n", - " \u001b[33m~\u001b[0m\u001b[0m num_long_term_bytes = 0 -> (known after apply)\n", - " \u001b[33m~\u001b[0m\u001b[0m num_rows = 37715 -> (known after apply)\n", - " \u001b[33m~\u001b[0m\u001b[0m schema = jsonencode(\n", - " \u001b[33m~\u001b[0m\u001b[0m [\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"Customer lifetime value revenue gains per user pseudo id\"\n", - " name = \"ltv\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"Processing timestamp of the customer lifetime value\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"user_pseudo_id\" \u001b[33m->\u001b[0m\u001b[0m \"ltv_processed_timestamp\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"STRING\" \u001b[33m->\u001b[0m\u001b[0m \"TIMESTAMP\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"Date the customer lifetime value was predicted\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"ltv_processed_timestamp\" \u001b[33m->\u001b[0m\u001b[0m \"ltv_feature_date\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"TIMESTAMP\" \u001b[33m->\u001b[0m\u001b[0m \"DATE\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"Decile of the customer lifetime value revenue gain per user pseudo id\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"ltv_feature_date\" \u001b[33m->\u001b[0m\u001b[0m \"ltv_decile\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"DATE\" \u001b[33m->\u001b[0m\u001b[0m \"INTEGER\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"Likelihood to purchase per user pseudo id\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"ltv_decile\" \u001b[33m->\u001b[0m\u001b[0m \"likely_to_purchase\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"INTEGER\" \u001b[33m->\u001b[0m\u001b[0m \"STRING\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The prediction related to whether the user is going to purchase in the future per user pseudo id\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"likely_to_purchase\" \u001b[33m->\u001b[0m\u001b[0m \"purchase_score\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"STRING\" \u001b[33m->\u001b[0m\u001b[0m \"FLOAT\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The timestamp of when the prediction was calculated\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchase_score\" \u001b[33m->\u001b[0m\u001b[0m \"purchase_processed_timestamp\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"FLOAT\" \u001b[33m->\u001b[0m\u001b[0m \"TIMESTAMP\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"Date in which the purchase propensity was calculated\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchase_processed_timestamp\" \u001b[33m->\u001b[0m\u001b[0m \"purchase_feature_date\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"TIMESTAMP\" \u001b[33m->\u001b[0m\u001b[0m \"DATE\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"Decile of the purchase propensity likelihood per user pseudo id\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchase_feature_date\" \u001b[33m->\u001b[0m\u001b[0m \"p_p_decile\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"DATE\" \u001b[33m->\u001b[0m\u001b[0m \"INTEGER\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in the past 30 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"p_p_decile\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_1_30_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in the past 30 to 60 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_1_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_30_60_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 60 to 90 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_30_60_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_60_90_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 90 to 120 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_60_90_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_90_120_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 120 to 150 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_90_120_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_120_150_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 150 to 180 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_120_150_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_150_180_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 30 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_150_180_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_1_30_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 30 to 60 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_1_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_30_60_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 60 to 90 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_30_60_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_60_90_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 90 to 120 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_60_90_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_90_120_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 120 to 150 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_90_120_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_120_150_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 150 to 180 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_120_150_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_150_180_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited the website or app in the past 30 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_150_180_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_1_30_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited the website or app in the past 30 to 60 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_1_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_30_60_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited the website or app in the past 60 to 90 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_30_60_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_60_90_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited the website or app in the past 90 to 120 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_60_90_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_90_120_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited the website or app in the past 120 to 150 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_90_120_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_120_150_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited the website or app in the past 150 to 180 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_120_150_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_150_180_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 30 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_150_180_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_1_30_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 30 to 60 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_1_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_30_60_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 60 to 90 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_30_60_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_60_90_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 90 to 120 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_60_90_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_90_120_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 120 to 150 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_90_120_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_120_150_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 150 to 180 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_120_150_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_150_180_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to cart in the past 30 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_150_180_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_1_30_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to cart in the past 30 to 60 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_1_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_30_60_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to cart in the past 60 to 90 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_30_60_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_60_90_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to cart in the past 90 to 120 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_60_90_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_90_120_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to cart in the past 120 to 150 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_90_120_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_120_150_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to cart in the past 150 to 180 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_120_150_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_150_180_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 30 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_150_180_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_1_30_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 30 to 60 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_1_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_30_60_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 30 to 60 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_30_60_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_60_90_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 90 to 120 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_60_90_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_90_120_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 120 to 150 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_90_120_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_120_150_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 150 to 180 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_120_150_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_150_180_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The segment id for that user predicted by the audience segmentation model\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_150_180_day\" \u001b[33m->\u001b[0m\u001b[0m \"Segment_ID\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The segment distance for that user predicted by the audience segmentation model\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"Segment_ID\" \u001b[33m->\u001b[0m\u001b[0m \"Segment_Distance\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"INTEGER\" \u001b[33m->\u001b[0m\u001b[0m \"FLOAT\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"Processing timestamp of the audience segmentation model\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"Segment_Distance\" \u001b[33m->\u001b[0m\u001b[0m \"segment_processed_timestamp\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"FLOAT\" \u001b[33m->\u001b[0m\u001b[0m \"TIMESTAMP\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"Date in which the audience segmentation was calculated\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"segment_processed_timestamp\" \u001b[33m->\u001b[0m\u001b[0m \"segment_feature_date\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"TIMESTAMP\" \u001b[33m->\u001b[0m\u001b[0m \"DATE\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 7 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"segment_feature_date\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_1_7_day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"DATE\" \u001b[33m->\u001b[0m\u001b[0m \"INTEGER\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 7 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_1_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_1_7_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 7 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_1_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_1_7_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 7 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_1_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_1_7_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to cart in the past 7 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_1_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_1_7_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 7 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_1_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_1_7_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The customer lifetime value gained in the past 7 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_1_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"ltv_revenue_past_1_7_day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"INTEGER\" \u001b[33m->\u001b[0m\u001b[0m \"FLOAT\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The customer lifetime value gained in the past 7 to 15 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"ltv_revenue_past_1_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"ltv_revenue_past_7_15_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The device brand name last accessed by the user\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"ltv_revenue_past_7_15_day\" \u001b[33m->\u001b[0m\u001b[0m \"device_mobile_brand_name\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"FLOAT\" \u001b[33m->\u001b[0m\u001b[0m \"STRING\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The device operating system last accessed by the user\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"device_mobile_brand_name\" \u001b[33m->\u001b[0m\u001b[0m \"device_os\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The device language last accessed by the user\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"device_os\" \u001b[33m->\u001b[0m\u001b[0m \"device_language\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The device web browser last accessed by the user\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"device_language\" \u001b[33m->\u001b[0m\u001b[0m \"device_web_browser\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The geo sub continent last accessed by the user\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"device_web_browser\" \u001b[33m->\u001b[0m\u001b[0m \"geo_sub_continent\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The geo metro last accessed by the user\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"geo_sub_continent\" \u001b[33m->\u001b[0m\u001b[0m \"geo_metro\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"A flag indicating whether the user has signed in and contains a user id\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"geo_metro\" \u001b[33m->\u001b[0m\u001b[0m \"has_signed_in_with_user_id\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"STRING\" \u001b[33m->\u001b[0m\u001b[0m \"BOOLEAN\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The current customer lifetime value of the user\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"has_signed_in_with_user_id\" \u001b[33m->\u001b[0m\u001b[0m \"user_ltv_revenue\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"BOOLEAN\" \u001b[33m->\u001b[0m\u001b[0m \"FLOAT\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"user_ltv_revenue\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_1_day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"FLOAT\" \u001b[33m->\u001b[0m\u001b[0m \"INTEGER\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 2nd day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_1_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_2_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 3rd day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_2_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_3_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 4th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_3_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_4_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 5th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_4_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_5_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 6th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_5_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_6_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in past 7th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_6_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_7_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in the past 15 to 30 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"active_users_past_15_30_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"active_users_past_15_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_1_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 2nd day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_1_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_2_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 3rd day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_2_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_3_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 4th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_3_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_4_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 5th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_4_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_5_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 6th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_5_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_6_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 7th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_6_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_7_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has purchased in the past 15 to 30 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"purchases_past_15_30_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"purchases_past_15_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_1_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 2nd day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_1_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_2_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 3rd day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_2_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_3_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 4th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_3_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_4_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 5th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_4_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_5_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 6th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_5_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_6_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 7th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_6_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_7_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited in the past 15 to 30 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"visits_past_15_30_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"visits_past_15_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_1_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 2nd day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_1_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_2_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 3rd day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_2_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_3_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 4th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_3_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_4_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 5th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_4_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_5_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 6th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_5_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_6_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 7th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_6_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_7_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 15 to 30 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"view_items_past_15_30_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"view_items_past_15_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_1_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 2nd day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_1_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_2_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 3rd day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_2_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_3_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 4th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_3_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_4_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 5th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_4_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_5_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 6th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_5_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_6_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 7th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_6_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_7_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 15 to 30 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"add_to_carts_past_15_30_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"add_to_carts_past_15_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_1_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 2nd day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_1_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_2_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 3rd day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_2_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_3_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 4th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_3_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_4_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 5th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_4_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_5_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 6th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_5_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_6_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 7th day\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_6_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_7_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 15 to 30 days\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_7_day\" \u001b[33m->\u001b[0m\u001b[0m \"checkouts_past_15_30_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The last user identifier logged in\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"checkouts_past_15_30_day\" \u001b[33m->\u001b[0m\u001b[0m \"user_id\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"INTEGER\" \u001b[33m->\u001b[0m\u001b[0m \"STRING\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The last device category used\"\n", - " name = \"device_category\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The last device model name used\"\n", - " name = \"device_mobile_model_name\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The last country the user was from\"\n", - " name = \"geo_country\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The last geo region the user was from\"\n", - " name = \"geo_region\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The last geo city the user was from\"\n", - " name = \"geo_city\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The last traffic source medium\"\n", - " name = \"last_traffic_source_medium\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The last traffic source name\"\n", - " name = \"last_traffic_source_name\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The last traffic source source\"\n", - " name = \"last_traffic_source_source\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The first traffic source medium\"\n", - " name = \"first_traffic_source_medium\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The first traffic source name\"\n", - " name = \"first_traffic_source_name\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The first traffic source source\"\n", - " name = \"first_traffic_source_source\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user was active in the past 8 to 14 days\"\n", - " name = \"active_users_past_8_14_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has made a purchase in the past 8 to 14 days\"\n", - " name = \"purchases_past_8_14_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has visited the site in the past 8 to 14 days\"\n", - " name = \"visits_past_8_14_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has viewed products pages in the past 8 to 14 days\"\n", - " name = \"view_items_past_8_14_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has added products to the cart in the past 8 to 14 days\"\n", - " name = \"add_to_carts_past_8_14_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The number of times the user has checked out in the past 8 to 14 days\"\n", - " name = \"checkouts_past_8_14_day\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"Likelihood to churn per user pseudo id\"\n", - " name = \"likely_to_churn\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"Churn score per user pseudo id\"\n", - " name = \"churn_score\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The timestamp of when the prediction was calculated\"\n", - " name = \"churn_processed_timestamp\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"Date in which the churn propensity was calculated\"\n", - " name = \"churn_feature_date\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"Decile of the churn propensity likelihood per user pseudo id\"\n", - " name = \"c_p_decile\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The user pseudo identifer\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"user_id\" \u001b[33m->\u001b[0m\u001b[0m \"user_pseudo_id\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The timestamp of when the prediction was calculated\"\n", - " name = \"auto_segment_processed_timestamp\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The Auto Segment ID\"\n", - " name = \"Auto_Segment_ID\"\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"INTEGER\" \u001b[33m->\u001b[0m\u001b[0m \"STRING\"\n", - " },\n", - " \u001b[33m~\u001b[0m\u001b[0m {\n", - " \u001b[32m+\u001b[0m\u001b[0m description = \"The segment distance for that user predicted by the auto audience segmentation model\"\n", - " \u001b[33m~\u001b[0m\u001b[0m name = \"Auto_Segment_Distance\" \u001b[33m->\u001b[0m\u001b[0m \"Auto Segment_Distance\"\n", - " \u001b[90m# (1 unchanged attribute hidden)\u001b[0m\u001b[0m\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"homepage\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Clearance\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Apparel_Mens_sortci_newest_desc\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"basket_html\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_New_sortci_newest_desc\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Lifestyle_Bags\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"store_html\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Lifestyle_Drinkware\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"signin_html\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Apparel_sortci_newest_desc\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Apparel_Womens\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Stationery_sortci_newest_desc\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"yourinfo_html\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Shop_by_Brand_YouTube\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Apparel_Hats\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"basket_html_vid_20160512512\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Bike_sortci_newest_desc\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"payment_html\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Shop_by_Brand_YouTube_sortci_newest_desc\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"store_html_sortci_newest_desc\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Lifestyle_sortci_newest_desc\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Shop_by_Brand_Google_sortci_newest_desc\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Apparel_Google_Dino_Game_Tee\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Campus_Collection_New_sortci_newest_desc\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Chrome_Dino_sortci_newest_desc\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Stationery_Notebooks_sortci_newest_desc\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"registersuccess_html\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Apparel\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " \u001b[31m-\u001b[0m\u001b[0m {\n", - " \u001b[31m-\u001b[0m\u001b[0m name = \"Google_Redesign_Apparel_Mens_Mens_T_Shirts\"\n", - " \u001b[31m-\u001b[0m\u001b[0m type = \"INTEGER\"\n", - " },\n", - " ] \u001b[31m# forces replacement\u001b[0m\u001b[0m\n", - " )\n", - " \u001b[33m~\u001b[0m\u001b[0m self_link = \"https://bigquery.googleapis.com/bigquery/v2/projects/marketing-data-engine-demo/datasets/aggregated_predictions/tables/latest\" -> (known after apply)\n", - " \u001b[33m~\u001b[0m\u001b[0m type = \"TABLE\" -> (known after apply)\n", - " \u001b[90m# (5 unchanged attributes hidden)\u001b[0m\u001b[0m\n", - " }\n", - "\n", - "\u001b[1mPlan:\u001b[0m 7 to add, 1 to change, 1 to destroy.\n", - "\u001b[0m\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_predictions.google_bigquery_table.main[\"latest\"]: Destroying... [id=projects/marketing-data-engine-demo/datasets/aggregated_predictions/tables/latest]\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_service_account.dataflow_worker_service_account: Creating...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Modifying... [id=projects/marketing-data-engine-demo/locations/us-central1/functions/activation-trigger]\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_predictions.google_bigquery_table.main[\"latest\"]: Destruction complete after 0s\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_predictions.google_bigquery_table.main[\"latest\"]: Creating...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.feature_store[0].module.aggregated_predictions.google_bigquery_table.main[\"latest\"]: Creation complete after 1s [id=projects/marketing-data-engine-demo/datasets/aggregated_predictions/tables/latest]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_service_account.dataflow_worker_service_account: Creation complete after 1s [id=projects/marketing-data-engine-demo/serviceAccounts/df-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/dataflow.worker\"]: Creating...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/storage.objectAdmin\"]: Creating...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_service_account_iam_member.dataflow_sa_iam: Creating...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/bigquery.dataEditor\"]: Creating...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/bigquery.jobUser\"]: Creating...\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_service_account_iam_member.dataflow_sa_iam: Creation complete after 4s [id=projects/marketing-data-engine-demo/serviceAccounts/df-worker@marketing-data-engine-demo.iam.gserviceaccount.com/roles/iam.serviceAccountUser/serviceAccount:vertex-pipelines-sa@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/dataflow.worker\"]: Creation complete after 8s [id=marketing-data-engine-demo/roles/dataflow.worker/serviceAccount:df-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/bigquery.jobUser\"]: Creation complete after 8s [id=marketing-data-engine-demo/roles/bigquery.jobUser/serviceAccount:df-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/storage.objectAdmin\"]: Creation complete after 9s [id=marketing-data-engine-demo/roles/storage.objectAdmin/serviceAccount:df-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.pipelines[0].google_project_iam_member.dataflow_worker_sa_roles[\"roles/bigquery.dataEditor\"]: Creation complete after 9s [id=marketing-data-engine-demo/roles/bigquery.dataEditor/serviceAccount:df-worker@marketing-data-engine-demo.iam.gserviceaccount.com]\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Still modifying... [id=projects/marketing-data-engine-demo/loc...-central1/functions/activation-trigger, 10s elapsed]\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Still modifying... [id=projects/marketing-data-engine-demo/loc...-central1/functions/activation-trigger, 20s elapsed]\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Still modifying... [id=projects/marketing-data-engine-demo/loc...-central1/functions/activation-trigger, 30s elapsed]\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Still modifying... [id=projects/marketing-data-engine-demo/loc...-central1/functions/activation-trigger, 40s elapsed]\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Still modifying... [id=projects/marketing-data-engine-demo/loc...-central1/functions/activation-trigger, 50s elapsed]\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1mmodule.activation[0].google_cloudfunctions2_function.activation_trigger_cf: Modifications complete after 53s [id=projects/marketing-data-engine-demo/locations/us-central1/functions/activation-trigger]\u001b[0m\n", - "\u001b[0m\u001b[1m\u001b[32m\n", - "Apply complete! Resources: 7 added, 1 changed, 1 destroyed.\n", - "\u001b[0m\u001b[0m\u001b[1m\u001b[32m\n", - "Outputs:\n", - "\n", - "\u001b[0mlookerstudio_create_dashboard_url = \"https://lookerstudio.google.com/reporting/create?c.reportId=f61f65fe-4991-45fc-bcdc-80593966f28c&c.explain=true&r.reportName=Marketing%20Analytics%20Sample&ds.GA4_sessions.connector=bigQuery&ds.GA4_sessions.type=TABLE&ds.GA4_sessions.tableId=session_date&ds.GA4_sessions.datasetId=marketing_ga4_v1_prod&ds.GA4_sessions.projectId=marketing-data-engine-demo&ds.GA4_sessions.datasourceName=MDS%20GA4%20Sessions&ds.GA4_session_device.connector=bigQuery&ds.GA4_session_device.type=TABLE&ds.GA4_session_device.tableId=session_device_daily_metrics&ds.GA4_session_device.datasetId=marketing_ga4_v1_prod&ds.GA4_session_device.projectId=marketing-data-engine-demo&ds.GA4_session_device.datasourceName=MDS%20GA4%20Session%20Device&ds.GA4_session_location.connector=bigQuery&ds.GA4_session_location.type=TABLE&ds.GA4_session_location.tableId=session_location_daily_metrics&ds.GA4_session_location.datasetId=marketing_ga4_v1_prod&ds.GA4_session_location.projectId=marketing-data-engine-demo&ds.GA4_session_location.datasourceName=MDS%20GA4%20Session%20Location&ds.GA4_event_page.connector=bigQuery&ds.GA4_event_page.type=TABLE&ds.GA4_event_page.tableId=event_page&ds.GA4_event_page.datasetId=marketing_ga4_v1_prod&ds.GA4_event_page.projectId=marketing-data-engine-demo&ds.GA4_event_page.datasourceName=MDS%20GA4%20Event%20Page&ds.GA4_unique_page_views.connector=bigQuery&ds.GA4_unique_page_views.type=TABLE&ds.GA4_unique_page_views.tableId=unique_page_views&ds.GA4_unique_page_views.datasetId=marketing_ga4_v1_prod&ds.GA4_unique_page_views.projectId=marketing-data-engine-demo&ds.GA4_unique_page_views.datasourceName=MDS%20GA4%20Unique%20Page%20Views&ds.GA4_page_session.connector=bigQuery&ds.GA4_page_session.type=TABLE&ds.GA4_page_session.tableId=page_session_daily_metrics&ds.GA4_page_session.datasetId=marketing_ga4_v1_prod&ds.GA4_page_session.projectId=marketing-data-engine-demo&ds.GA4_page_session.datasourceName=MDS%20GA4%20Page%20Session&ds.Ads_perf_conversions.connector=bigQuery&ds.Ads_perf_conversions.type=TABLE&ds.Ads_perf_conversions.tableId=ad_performance_conversions&ds.Ads_perf_conversions.datasetId=marketing_ads_v1_prod&ds.Ads_perf_conversions.projectId=marketing-data-engine-demo&ds.Ads_perf_conversions.datasourceName=MDS%20Ads%20Ad%20Performance%20x%20Conversions&ds.MAJ_resource_link.connector=bigQuery&ds.MAJ_resource_link.type=TABLE&ds.MAJ_resource_link.tableId=resource_link&ds.MAJ_resource_link.datasetId=maj_dashboard&ds.MAJ_resource_link.projectId=marketing-data-engine-demo&ds.MAJ_resource_link.datasourceName=MAJ%20Resource%20Link&ds.GA4_base_event.connector=bigQuery&ds.GA4_base_event.type=TABLE&ds.GA4_base_event.tableId=event&ds.GA4_base_event.datasetId=marketing_ga4_base_prod&ds.GA4_base_event.projectId=marketing-data-engine-demo&ds.GA4_base_event.datasourceName=MDS%20GA4%20Base%20Event&ds.MDS_execution_log.connector=bigQuery&ds.MDS_execution_log.type=TABLE&ds.MDS_execution_log.tableId=dataform_googleapis_com_workflow_invocation_completion&ds.MDS_execution_log.datasetId=maj_logs&ds.MDS_execution_log.projectId=marketing-data-engine-demo&ds.MDS_execution_log.datasourceName=MDS%20Execution%20Log&ds.Activation_log.connector=bigQuery&ds.Activation_log.type=TABLE&ds.Activation_log.tableId=dataflow_googleapis_com_job_message&ds.Activation_log.datasetId=maj_logs&ds.Activation_log.projectId=marketing-data-engine-demo&ds.Activation_log.datasourceName=Activation%20Execution%20Log&ds.Vertex_log.connector=bigQuery&ds.Vertex_log.type=TABLE&ds.Vertex_log.tableId=aiplatform_googleapis_com_pipeline_job_events&ds.Vertex_log.datasetId=maj_logs&ds.Vertex_log.projectId=marketing-data-engine-demo&ds.Vertex_log.datasourceName=Vertex%20AI%20Pipelines%20Log&ds.Aggregated_vbb_volume_daily.connector=bigQuery&ds.Aggregated_vbb_volume_daily.type=TABLE&ds.Aggregated_vbb_volume_daily.tableId=aggregated_value_based_bidding_volume_daily&ds.Aggregated_vbb_volume_daily.datasetId=aggregated_vbb&ds.Aggregated_vbb_volume_daily.projectId=marketing-data-engine-demo&ds.Aggregated_vbb_volume_daily.datasourceName=Aggregated%20VBB%20Volume%20Daily&ds.Aggregated_vbb_volume_weekly.connector=bigQuery&ds.Aggregated_vbb_volume_weekly.type=TABLE&ds.Aggregated_vbb_volume_weekly.tableId=aggregated_value_based_bidding_volume_weekly&ds.Aggregated_vbb_volume_weekly.datasetId=aggregated_vbb&ds.Aggregated_vbb_volume_weekly.projectId=marketing-data-engine-demo&ds.Aggregated_vbb_volume_weekly.datasourceName=Aggregated%20VBB%20Volume%20Weekly&ds.Aggregated_vbb_correlation.connector=bigQuery&ds.Aggregated_vbb_correlation.type=TABLE&ds.Aggregated_vbb_correlation.tableId=aggregated_value_based_bidding_correlation&ds.Aggregated_vbb_correlation.datasetId=aggregated_vbb&ds.Aggregated_vbb_correlation.projectId=marketing-data-engine-demo&ds.Aggregated_vbb_correlation.datasourceName=Aggregated%20VBB%20Correlation&ds.Aggregated_vbb_weights.connector=bigQuery&ds.Aggregated_vbb_weights.type=TABLE&ds.Aggregated_vbb_weights.tableId=vbb_weights&ds.Aggregated_vbb_weights.datasetId=aggregated_vbb&ds.Aggregated_vbb_weights.projectId=marketing-data-engine-demo&ds.Aggregated_vbb_weights.datasourceName=Aggregated%20VBB%20Weights&ds.Aggregated_predictions.connector=bigQuery&ds.Aggregated_predictions.type=TABLE&ds.Aggregated_predictions.tableId=latest&ds.Aggregated_predictions.datasetId=aggregated_predictions&ds.Aggregated_predictions.projectId=marketing-data-engine-demo&ds.Aggregated_predictions.datasourceName=Aggregated%20Predictions&ds.User_behaviour_revenue_insights_daily.connector=bigQuery&ds.User_behaviour_revenue_insights_daily.type=TABLE&ds.User_behaviour_revenue_insights_daily.tableId=user_behaviour_revenue_insights_daily&ds.User_behaviour_revenue_insights_daily.datasetId=gemini_insights&ds.User_behaviour_revenue_insights_daily.projectId=marketing-data-engine-demo&ds.User_behaviour_revenue_insights_daily.datasourceName=User%20Behaviour%20Revenue%20Insights%20Daily\"\n", - "tf_state_project_id = \"marketing-data-engine-demo\"\n" - ] - } - ] - }, - { - "cell_type": "code", - "source": [], - "metadata": { - "id": "EqgA6GSr1t0c" + "id": "BGteib5ebsA-" }, "execution_count": null, "outputs": [] From 30fc6d4dbee7a186f5b86a497e2652681a549be9 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 27 Sep 2024 13:08:09 -0400 Subject: [PATCH 022/110] Update quick-install.sh --- scripts/quick-install.sh | 50 +++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/scripts/quick-install.sh b/scripts/quick-install.sh index 908a6399..57d0ed2c 100755 --- a/scripts/quick-install.sh +++ b/scripts/quick-install.sh @@ -38,6 +38,10 @@ section_open "Setting the gcloud project id" gcloud config set project "${TF_STATE_PROJECT_ID}" section_close +section_open "Enable all the required APIs" + enable_all_apis +section_close + section_open "Authenticate to Google Cloud Project" gcloud auth login --project "${TF_STATE_PROJECT_ID}" echo "Close the browser tab that was open and press any key to continue.." @@ -53,6 +57,19 @@ section_open "Setting Google Application Default Credentials" export GOOGLE_APPLICATION_CREDENTIALS=${CREDENTIAL_FILE} section_close +section_open "Check OS system" + unameOut="$(uname -s)" + case "${unameOut}" in + Linux*) machine=Linux;; + Darwin*) machine=Mac;; + CYGWIN*) machine=Cygwin;; + MINGW*) machine=MinGw;; + MSYS_NT*) machine=Git;; + *) machine="UNKNOWN:${unameOut}" + esac + echo ${machine} +section_close + section_open "Configuring environment" SOURCE_ROOT=$(pwd) cd ${SOURCE_ROOT} @@ -60,19 +77,19 @@ section_open "Configuring environment" # Install python3.10 sudo chown -R ctimoteo /usr/local/sbin chmod u+w /usr/local/sbin - brew install python@3.10 - retVal=$? - if [ $retVal -ne 0 ]; then - apt-get install python3.10 + if [ $machine == "Linux" ]; then + sudo DEBIAN_FRONTEND=noninteractive apt-get -qq -o=Dpkg::Use-Pty=0 install python3.10 --assume-yes + elif [ $machine == "Darwin" ]; then + brew install python@3.10 fi CLOUDSDK_PYTHON=python3.10 # Install pipx - brew install pipx - retVal=$? - if [ $retVal -ne 0 ]; then + if [ $machine == "Linux" ]; then sudo apt update sudo apt install pipx + elif [ $machine == "Darwin" ]; then + brew install pipx fi pipx ensurepath @@ -83,20 +100,13 @@ section_open "Configuring environment" poetry --version # Install tfenv - tfenv --version - retVal=$? - if [ $retVal -ne 0 ]; then + if [ ! -d ~/.tfenv ]; then git clone --depth=1 https://github.com/tfutils/tfenv.git ~/.tfenv echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bash_profile echo 'export PATH=$PATH:$HOME/.tfenv/bin' >> ~/.bashrc fi export PATH="$PATH:$HOME/.tfenv/bin" - #mkdir -p ~/.local/bin/ - #. ~/.profile - #ln -s ~/.tfenv/bin/* ~/.local/bin - #which tfenv - # Install terraform version tfenv install 1.5.7 tfenv use 1.5.7 @@ -108,12 +118,16 @@ section_close section_open "Preparing Terraform Environment File" TERRAFORM_RUN_DIR=${SOURCE_ROOT}/infrastructure/terraform - cp -v $TERRAFORM_RUN_DIR/terraform-sample.tfvars $TERRAFORM_RUN_DIR/terraform.tfvars - echo "Edit the terraform.tfvars created and press any key to continue.." - read moveon + if [ ! -f $TERRAFORM_RUN_DIR/terraform.tfvars ]; then + . scripts/set-env.sh + sudo apt-get -qq -o=Dpkg::Use-Pty=0 install gettext + envsubst < "${SOURCE_ROOT}/infrastructure/cloudshell/terraform-template.tfvars" > "${TERRAFORM_RUN_DIR}/terraform.tfvars" + fi section_close section_open "Deploying Terraform Infrastructure Resources" + export PATH="$HOME/.local/bin:$PATH" + export PATH="$PATH:$HOME/.tfenv/bin" terraform -chdir="${TERRAFORM_RUN_DIR}" init terraform -chdir="${TERRAFORM_RUN_DIR}" apply section_close From c08064342dcc1a37d2148d43f244569dc6297eda Mon Sep 17 00:00:00 2001 From: Laurent Grangeau Date: Fri, 4 Oct 2024 22:35:07 +0200 Subject: [PATCH 023/110] Upgrade terraform version (#202) * chore(deps): upgrade terraform providers and modules version * chore(deps): set the provider version * chore: formatting * fix: brand naming * fix: typo --------- Co-authored-by: Laurent Grangeau --- CONTRIBUTING.md | 6 +- DEVELOPMENT.md | 2 +- README.md | 4 +- infrastructure/README.md | 2 - infrastructure/terraform/.terraform.lock.hcl | 196 +++++++++--------- infrastructure/terraform/main.tf | 78 +++---- .../terraform/modules/activation/main.tf | 72 +++---- .../terraform/modules/activation/variables.tf | 2 +- .../terraform/modules/activation/versions.tf | 7 +- .../data-store/data-processing-services.tf | 4 +- .../terraform/modules/data-store/dataform.tf | 8 +- .../modules/data-store/iam-binding.tf | 14 +- .../terraform/modules/data-store/main.tf | 24 +-- .../modules/data-store/secretmanager.tf | 6 +- .../terraform/modules/data-store/variables.tf | 8 +- .../terraform/modules/data-store/versions.tf | 7 +- .../modules/dataform-workflow/README.md | 2 +- .../modules/dataform-workflow/scheduler.tf | 10 +- .../dataform-workflow/service-account.tf | 14 +- .../modules/dataform-workflow/services.tf | 4 +- .../modules/dataform-workflow/variables.tf | 6 +- .../modules/dataform-workflow/versions.tf | 7 +- .../feature-store/bigquery-datasets.tf | 144 ++++++------- .../feature-store/bigquery-procedures.tf | 52 ++--- .../modules/feature-store/bigquery-tables.tf | 136 ++++++------ .../terraform/modules/feature-store/main.tf | 12 +- .../modules/feature-store/versions.tf | 7 +- .../terraform/modules/monitor/main.tf | 8 +- .../terraform/modules/monitor/versions.tf | 7 +- .../terraform/modules/pipelines/main.tf | 4 +- .../terraform/modules/pipelines/pipelines.tf | 26 +-- .../terraform/modules/pipelines/versions.tf | 7 +- .../terraform/terraform-sample.tfvars | 14 +- infrastructure/terraform/variables.tf | 12 +- infrastructure/terraform/versions.tf | 7 +- 35 files changed, 473 insertions(+), 446 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c91a8010..c5a0d10a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,11 +35,11 @@ for this purpose. ### Fork this repo. -Follow the typical Github guide on how to [fork a repo](https://docs.github.com/en/get-started/quickstart/fork-a-repo). +Follow the typical GitHub guide on how to [fork a repo](https://docs.github.com/en/get-started/quickstart/fork-a-repo). **Note**: 1. To keep track of the new releases, configure git to [sync your fork with this upstream repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo#configuring-git-to-sync-your-fork-with-the-upstream-repository). -2. Don't submit a Pull Request to this upstream Github repo if you don't want to expose your environment configuration. You're at your own risk at exposing your company data. +2. Don't submit a Pull Request to this upstream GitHub repo if you don't want to expose your environment configuration. You're at your own risk at exposing your company data. 3. Observe your fork is also public, you cannot make your own fork a private repo. ### Complete the installation guide @@ -48,7 +48,7 @@ Complete the installation guide in a Google Cloud project in which you're develo ### Configure Continuous Integration recipes -Connect your Github repository by following this [guide](https://cloud.google.com/build/docs/automating-builds/github/connect-repo-github). +Connect your GitHub repository by following this [guide](https://cloud.google.com/build/docs/automating-builds/github/connect-repo-github). In your Google Cloud project, configure Cloud Build triggers to be executed when you push code into your branch. Update the Clould build recipes in the `cloudbuild` folder and deploy them. diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index f85ace50..46727a9b 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -2,7 +2,7 @@ Marketing Analytics Jumpstart consists of an easy, extensible and automated implementation of an end-to-end solution that enables Marketing Technology teams to store, transform, enrich with 1PD and analyze marketing data, and programmatically send predictive events to Google Analytics 4 to support conversion optimization and remarketing campaigns. ## Developer pre-requisites -Use Visual Studio Code to develop the solution. Install Gemini Code Assistant, Docker, Github, Hashicopr Terraform, Jinja extensions. +Use Visual Studio Code to develop the solution. Install Gemini Code Assistant, Docker, GitHub, Hashicorp, Terraform, Jinja extensions. You should have Python 3, Poetry, Terraform, Git and Docker installed in your developer terminal environment. ## Preparing development environment diff --git a/README.md b/README.md index 72ddd42a..4de911d1 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ This high-level architecture demonstrates how Marketing Analytics Jumpstart inte - [ ] Google Analytics Property Editor or Owner - [ ] Google Ads Reader - [ ] Project Owner for GCP Project -- [ ] Github or Gitlab account priviledges for repo creation and access token. [Details](https://cloud.google.com/dataform/docs/connect-repository) +- [ ] GitHub or GitLab account priviledges for repo creation and access token. [Details](https://cloud.google.com/dataform/docs/connect-repository) ## Installation @@ -142,7 +142,7 @@ This a list of public websites you can use to learn more about the Google Analyt | Websites | Description | |----------|-------------| -| [github.com/GoogleCloudPlatform/marketing-analytics-jumpstart-dataform](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart-dataform) | Marketing Analytics Jumpstart Dataform Github Repository | +| [github.com/GoogleCloudPlatform/marketing-analytics-jumpstart-dataform](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart-dataform) | Marketing Analytics Jumpstart Dataform GitHub Repository | | [console.cloud.google.com/marketplace/product/bigquery-data-connectors/google_ads](https://console.cloud.google.com/marketplace/product/bigquery-data-connectors/google_ads) | BigQuery Data Transfer Service for Google Ads | | [support.google.com/google-ads/*](https://support.google.com/google-ads/) [support.google.com/analytics/*](https://support.google.com/analytics/) | Google Ads and Google Analytics Support | | [support.google.com/looker-studio/*](https://support.google.com/looker-studio/) | Looker Studio Support | diff --git a/infrastructure/README.md b/infrastructure/README.md index 07ec98cc..1bd79d38 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -91,7 +91,6 @@ The activation application uses sensitive information from the Google Analytics * A [Measurement ID](https://support.google.com/analytics/answer/12270356?hl=en) and [API secret](https://support.google.com/analytics/answer/9814495?sjid=9902804247343448709-NA) collected from the Google Analytics UI. In this [article](https://support.google.com/analytics/answer/9814495?sjid=9902804247343448709-NA) you will find instructions on how to generate the API secret. * Editor or Administrator role to the Google Analytics 4 account or property. In this [article](https://support.google.com/analytics/answer/9305587?hl=en#zippy=%2Cgoogle-analytics) you will find instructions on how to setup. - ## Installing the MDS, ML pipelines, the feature Store, and the activation pipeline Once all the prerequisites are met you can install these components using Terraform scripts. @@ -102,4 +101,3 @@ Follow instructions in [terraform/README.md](terraform/README.md) Looker Studio Dashboards can be installed by following instructions in [../python/lookerstudio/README.md](../python/lookerstudio/README.md) - diff --git a/infrastructure/terraform/.terraform.lock.hcl b/infrastructure/terraform/.terraform.lock.hcl index ece5c079..79205a2b 100644 --- a/infrastructure/terraform/.terraform.lock.hcl +++ b/infrastructure/terraform/.terraform.lock.hcl @@ -2,152 +2,146 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/archive" { - version = "2.4.2" + version = "2.6.0" hashes = [ - "h1:G4v6F6Lhqlo3EKGBKEK/kJRhNcQiRrhEdUiVpBHKHOA=", - "h1:WfIjVbYA9s/uN2FwhGoiffT7CLFydy7MT1waFbt9YrY=", - "zh:08faed7c9f42d82bc3d406d0d9d4971e2d1c2d34eae268ad211b8aca57b7f758", - "zh:3564112ed2d097d7e0672378044a69b06642c326f6f1584d81c7cdd32ebf3a08", - "zh:53cd9afd223c15828c1916e68cb728d2be1cbccb9545568d6c2b122d0bac5102", - "zh:5ae4e41e3a1ce9d40b6458218a85bbde44f21723943982bca4a3b8bb7c103670", - "zh:5b65499218b315b96e95c5d3463ea6d7c66245b59461217c99eaa1611891cd2c", + "h1:rYAubRk7UHC/fzYqFV/VHc+7VIY01ugCxauyTYCNf9E=", + "zh:29273484f7423b7c5b3f5df34ccfc53e52bb5e3d7f46a81b65908e7a8fd69072", + "zh:3cba58ec3aea5f301caf2acc31e184c55d994cc648126cac39c63ae509a14179", + "zh:55170cd17dbfdea842852c6ae2416d057fec631ba49f3bb6466a7268cd39130e", + "zh:7197db402ba35631930c3a4814520f0ebe980ae3acb7f8b5a6f70ec90dc4a388", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:7f45b35a8330bebd184c2545a41782ff58240ed6ba947274d9881dd5da44b02e", - "zh:87e67891033214e55cfead1391d68e6a3bf37993b7607753237e82aa3250bb71", - "zh:de3590d14037ad81fc5cedf7cfa44614a92452d7b39676289b704a962050bc5e", - "zh:e7e6f2ea567f2dbb3baa81c6203be69f9cd6aeeb01204fd93e3cf181e099b610", - "zh:fd24d03c89a7702628c2e5a3c732c0dede56fa75a08da4a1efe17b5f881c88e2", - "zh:febf4b7b5f3ff2adff0573ef6361f09b6638105111644bdebc0e4f575373935f", + "zh:8bf7fe0915d7fb152a3a6b9162614d2ec82749a06dba13fab3f98d33c020ec4f", + "zh:8ce811844fd53adb0dabc9a541f8cb43aacfa7d8e39324e4bd3592b3428f5bfb", + "zh:bca795bca815b8ac90e3054c0a9ab1ccfb16eedbb3418f8ad473fc5ad6bf0ef7", + "zh:d9355a18df5a36cf19580748b23249de2eb445c231c36a353709f8f40a6c8432", + "zh:dc32cc32cfd8abf8752d34f2a783de0d3f7200c573b885ecb64ece5acea173b4", + "zh:ef498e20391bf7a280d0fd6fd6675621c85fbe4e92f0f517ae4394747db89bde", + "zh:f2bc5226c765b0c8055a7b6207d0fe1eb9484e3ec8880649d158827ac6ed3b22", ] } provider "registry.terraform.io/hashicorp/external" { - version = "2.3.3" + version = "2.3.4" constraints = ">= 2.2.2" hashes = [ - "h1:H+3QlVPs/7CDa3I4KU/a23wYeGeJxeBlgvR7bfK1t1w=", - "h1:Qi72kOSrEYgEt5itloFhDfmiFZ7wnRy3+F74XsRuUOw=", - "zh:03d81462f9578ec91ce8e26f887e34151eda0e100f57e9772dbea86363588239", - "zh:37ec2a20f6a3ec3a0fd95d3f3de26da6cb9534b30488bc45723e118a0911c0d8", - "zh:4eb5b119179539f2749ce9de0e1b9629d025990f062f4f4dddc161562bb89d37", - "zh:5a31bb58414f41bee5e09b939012df5b88654120b0238a89dfd6691ba197619a", - "zh:6221a05e52a6a2d4f520ffe7cbc741f4f6080e0855061b0ed54e8be4a84eb9b7", + "h1:XWkRZOLKMjci9/JAtE8X8fWOt7A4u+9mgXSUjc4Wuyo=", + "zh:037fd82cd86227359bc010672cd174235e2d337601d4686f526d0f53c87447cb", + "zh:0ea1db63d6173d01f2fa8eb8989f0809a55135a0d8d424b08ba5dabad73095fa", + "zh:17a4d0a306566f2e45778fbac48744b6fd9c958aaa359e79f144c6358cb93af0", + "zh:298e5408ab17fd2e90d2cd6d406c6d02344fe610de5b7dae943a58b958e76691", + "zh:38ecfd29ee0785fd93164812dcbe0664ebbe5417473f3b2658087ca5a0286ecb", + "zh:59f6a6f31acf66f4ea3667a555a70eba5d406c6e6d93c2c641b81d63261eeace", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:8bb068496b4679bef625e4710d9f3432e301c3a56602271f04e60eadf7f8a94c", - "zh:94742aa5378bab626ce34f79bcef6a373e4f86ea7a8b762e9f71270a899e0d00", - "zh:a485831b5a525cd8f40e8982fa37da40ff70b1ae092c8b755fcde123f0b1238d", - "zh:a647ff16d071eabcabd87ea8183eb90a775a0294ddd735d742075d62fff09193", - "zh:b74710c5954aaa3faf262c18d36a8c2407862d9f842c63e7fa92fa4de3d29df6", - "zh:fa73d83edc92af2e551857594c2232ba6a9e3603ad34b0a5940865202c08d8d7", + "zh:ad0279dfd09d713db0c18469f585e58d04748ca72d9ada83883492e0dd13bd58", + "zh:c69f66fd21f5e2c8ecf7ca68d9091c40f19ad913aef21e3ce23836e91b8cbb5f", + "zh:d4a56f8c48aa86fc8e0c233d56850f5783f322d6336f3bf1916e293246b6b5d4", + "zh:f2b394ebd4af33f343835517e80fc876f79361f4688220833bc3c77655dd2202", + "zh:f31982f29f12834e5d21e010856eddd19d59cd8f449adf470655bfd19354377e", ] } provider "registry.terraform.io/hashicorp/google" { - version = "4.85.0" - constraints = ">= 3.43.0, >= 3.53.0, >= 3.63.0, >= 4.83.0, < 5.0.0, < 6.0.0" + version = "5.44.1" + constraints = ">= 3.43.0, >= 3.53.0, >= 4.83.0, >= 5.3.0, >= 5.22.0, < 6.0.0, < 7.0.0" hashes = [ - "h1:OVJ7KHmd+XnpxTIRwqwXKasUha9q1rxnq6m5iiETmTM=", - "h1:aSRZcEKF2wOi/v24IA+k9J2Y7aKVV1cHi/R0V3EhxXQ=", - "zh:17d60a6a6c1741cf1e09ac6731433a30950285eac88236e623ab4cbf23832ca3", - "zh:1c70254c016439dbb75cab646b4beace6ceeff117c75d81f2cc27d41c312f752", - "zh:35e2aa2cc7ac84ce55e05bb4de7b461b169d3582e56d3262e249ff09d64fe008", - "zh:417afb08d7b2744429f6b76806f4134d62b0354acf98e8a6c00de3c24f2bb6ad", - "zh:622165d09d21d9a922c86f1fc7177a400507f2a8c4a4513114407ae04da2dd29", - "zh:7cdb8e39a8ea0939558d87d2cb6caceded9e21f21003d9e9f9ce648d5db0bc3a", - "zh:851e737dc551d6004a860a8907fda65118fc2c7ede9fa828f7be704a2a39e68f", - "zh:a331ad289a02a2c4473572a573dc389be0a604cdd9e03dd8dbc10297fb14f14d", - "zh:b67fd531251380decd8dd1f849460d60f329f89df3d15f5815849a1dd001f430", - "zh:be8785957acca4f97aa3e800b313b57d1fca07788761c8867c9bc701fbe0bdb5", - "zh:cb6579a259fe020e1f88217d8f6937b2d5ace15b6406370977a1966eb31b1ca5", + "h1:CZ1vR4kLe+2PqoUwXhZROq2ufV60V92ObXAcl89HwZ8=", + "zh:25d4c2926f0e26f0736eb7913114d487212bdddbb636024ebf3ce74b1baad64b", + "zh:282da25af7332aba699f093ece72d6831dc96aba2c8ef1e1c461f47c03bd643b", + "zh:519e5939041003d935ca8b2cbd37962ebab0680eeaf19841160cda4bc2c8b860", + "zh:6a1b5f9d746d9c23a6bfcd50ed42a88612cbd17b60e8b298203cd87500b13fca", + "zh:8703797a82a700d3b739e1ae7d2bc541fe4aa55831d76a235c80dcdbf6e951b9", + "zh:9240c4b3e0946626f73ace992d8953c6e520c12b391c24d71b92cade55e3692d", + "zh:ab7e7f276efc2c20fbbe0cbb8c223420d3feea155d8e87a82cde0587f0ca97b3", + "zh:c2d88d26ddab980adafed9028fe3924613867a39fa7cf4325635f8ffffccf2dc", + "zh:cfb444355f8ae7844a3be616997181ec2fddebc28ff369557e1cb1d5239cb08a", + "zh:d70ed91db0b6850d7c9d5414512994d04a779de5a18bd51b1b5c09c9b64528cf", + "zh:ec302351a34341637eea1325aa6dd4ad09bd08084b2b2dabb481447b8b26967e", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } provider "registry.terraform.io/hashicorp/google-beta" { - version = "4.85.0" - constraints = ">= 3.43.0, >= 4.83.0, < 5.0.0, < 6.0.0" + version = "5.44.1" + constraints = ">= 3.43.0, >= 4.83.0, < 6.0.0, < 7.0.0" hashes = [ - "h1:YkCDGkP0AUZoNobLoxRnM52Pi4alYE9EFXalEu8p8E8=", - "h1:fYXttGgML+C0aEl3rkOiAyo0UizA6rz7VsvNLTw678U=", - "zh:40e9c7ec46955b4d79065a14185043a4ad6af8d0246715853fc5c99208b66980", - "zh:5950a9ba2f96420ea5335b543e315b1a47a705f9a9abfc53c6fec52d084eddcb", - "zh:5dfa98d32246a5d97e018f2b91b0e921cc6f061bc8591884f3b144f0d62f1c20", - "zh:628d0ca35c6d4c35077859bb0a5534c1de44f23a91e190f9c3f06f2358172e75", - "zh:6e78d54fd4de4151968149b4c3521f563a8b5c55aad423dba5968a9114b65ae4", - "zh:91c3bc443188638353285bd35b06d3a3b39b42b3b4cc0637599a430438fba2f7", - "zh:9e91b03363ebf39eea5ec0fbe7675f6979883aa9ad9a36664357d8513a007cf3", - "zh:db9a8d6bfe075fb38c260986ab557d40e8d18e5698c62956a6da8120fae01d59", - "zh:e41169c49f3bb53217905509e2ba8bb4680c373e1f54db7fac1b7f72943a1004", - "zh:f32f55a8af605afbc940814e17493ac83d9d66cd6da9bbc247e0a833a0aa37ec", + "h1:4pdEV8QZSbjR0rRdpq8aU7DR+DurLv6xF70eHzkugkA=", + "zh:206f93a069dc6b28010e6f8e2f617d5a00b6dadf96291adf1ec88a2cfaa91ca8", + "zh:296471c122824c8d6d3597ad40f2716afce11b37af53a4a26e66c3b2e0e26586", + "zh:399244ebe27a60fa2cd78acb3911238eba926be8105d1caf06e604ca28ecfa12", + "zh:3f673c225af9119d51876ea20a5ce0cba31bb879b4b27462fa9efb33103a53a0", + "zh:57dd1e3406054660894df9b36060853b371299af27c2d78ad6789f0ca32a431b", + "zh:75e06806c3c82adfd569ee202d92c3add3e0822680e0d33153f34bb88f333a96", + "zh:84c8aeda08578669f1499b032bafbe54cf37a9f5785b18adc0d01189e7568a4b", + "zh:852ba977c1c947e49ac1743d5a1dfab127466775f2b7998f6e1a40ff966edc12", + "zh:cc33f33aeb61340309968ee675247c1b4eb7476a7204abf23e6ce43c183f8a59", + "zh:e8608e1c26790e321b8981362a62614e1ac14943684d1b459d932cbf75b7f9e7", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - "zh:f6561a6badc3af842f9ad5bb926104954047f07cb90fadcca1357441cc67d91d", + "zh:f9dad1d0653d82aca6821064c6370452d3ba16e4cbfc6957e3d67b9266c89527", ] } provider "registry.terraform.io/hashicorp/local" { - version = "2.5.1" + version = "2.5.2" hashes = [ - "h1:8oTPe2VUL6E2d3OcrvqyjI4Nn/Y/UEQN26WLk5O/B0g=", - "h1:tjcGlQAFA0kmQ4vKkIPPUC4it1UYxLbg4YvHOWRAJHA=", - "zh:0af29ce2b7b5712319bf6424cb58d13b852bf9a777011a545fac99c7fdcdf561", - "zh:126063ea0d79dad1f68fa4e4d556793c0108ce278034f101d1dbbb2463924561", - "zh:196bfb49086f22fd4db46033e01655b0e5e036a5582d250412cc690fa7995de5", - "zh:37c92ec084d059d37d6cffdb683ccf68e3a5f8d2eb69dd73c8e43ad003ef8d24", - "zh:4269f01a98513651ad66763c16b268f4c2da76cc892ccfd54b401fff6cc11667", - "zh:51904350b9c728f963eef0c28f1d43e73d010333133eb7f30999a8fb6a0cc3d8", - "zh:73a66611359b83d0c3fcba2984610273f7954002febb8a57242bbb86d967b635", + "h1:JlMZD6nYqJ8sSrFfEAH0Vk/SL8WLZRmFaMUF9PJK5wM=", + "zh:136299545178ce281c56f36965bf91c35407c11897f7082b3b983d86cb79b511", + "zh:3b4486858aa9cb8163378722b642c57c529b6c64bfbfc9461d940a84cd66ebea", + "zh:4855ee628ead847741aa4f4fc9bed50cfdbf197f2912775dd9fe7bc43fa077c0", + "zh:4b8cd2583d1edcac4011caafe8afb7a95e8110a607a1d5fb87d921178074a69b", + "zh:52084ddaff8c8cd3f9e7bcb7ce4dc1eab00602912c96da43c29b4762dc376038", + "zh:71562d330d3f92d79b2952ffdda0dad167e952e46200c767dd30c6af8d7c0ed3", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:7ae387993a92bcc379063229b3cce8af7eaf082dd9306598fcd42352994d2de0", - "zh:9e0f365f807b088646db6e4a8d4b188129d9ebdbcf2568c8ab33bddd1b82c867", - "zh:b5263acbd8ae51c9cbffa79743fbcadcb7908057c87eb22fd9048268056efbc4", - "zh:dfcd88ac5f13c0d04e24be00b686d069b4879cc4add1b7b1a8ae545783d97520", + "zh:805f81ade06ff68fa8b908d31892eaed5c180ae031c77ad35f82cb7a74b97cf4", + "zh:8b6b3ebeaaa8e38dd04e56996abe80db9be6f4c1df75ac3cccc77642899bd464", + "zh:ad07750576b99248037b897de71113cc19b1a8d0bc235eb99173cc83d0de3b1b", + "zh:b9f1c3bfadb74068f5c205292badb0661e17ac05eb23bfe8bd809691e4583d0e", + "zh:cc4cbcd67414fefb111c1bf7ab0bc4beb8c0b553d01719ad17de9a047adff4d1", ] } provider "registry.terraform.io/hashicorp/null" { - version = "3.2.2" + version = "3.2.3" + constraints = ">= 2.1.0" hashes = [ - "h1:vWAsYRd7MjYr3adj8BVKRohVfHpWQdvkIwUQ2Jf5FVM=", - "h1:zT1ZbegaAYHwQa+QwIFugArWikRJI9dqohj8xb0GY88=", - "zh:3248aae6a2198f3ec8394218d05bd5e42be59f43a3a7c0b71c66ec0df08b69e7", - "zh:32b1aaa1c3013d33c245493f4a65465eab9436b454d250102729321a44c8ab9a", - "zh:38eff7e470acb48f66380a73a5c7cdd76cc9b9c9ba9a7249c7991488abe22fe3", - "zh:4c2f1faee67af104f5f9e711c4574ff4d298afaa8a420680b0cb55d7bbc65606", - "zh:544b33b757c0b954dbb87db83a5ad921edd61f02f1dc86c6186a5ea86465b546", - "zh:696cf785090e1e8cf1587499516b0494f47413b43cb99877ad97f5d0de3dc539", - "zh:6e301f34757b5d265ae44467d95306d61bef5e41930be1365f5a8dcf80f59452", + "h1:+AnORRgFbRO6qqcfaQyeX80W0eX3VmjadjnUFUJTiXo=", + "zh:22d062e5278d872fe7aed834f5577ba0a5afe34a3bdac2b81f828d8d3e6706d2", + "zh:23dead00493ad863729495dc212fd6c29b8293e707b055ce5ba21ee453ce552d", + "zh:28299accf21763ca1ca144d8f660688d7c2ad0b105b7202554ca60b02a3856d3", + "zh:55c9e8a9ac25a7652df8c51a8a9a422bd67d784061b1de2dc9fe6c3cb4e77f2f", + "zh:756586535d11698a216291c06b9ed8a5cc6a4ec43eee1ee09ecd5c6a9e297ac1", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:913a929070c819e59e94bb37a2a253c228f83921136ff4a7aa1a178c7cce5422", - "zh:aa9015926cd152425dbf86d1abdbc74bfe0e1ba3d26b3db35051d7b9ca9f72ae", - "zh:bb04798b016e1e1d49bcc76d62c53b56c88c63d6f2dfe38821afef17c416a0e1", - "zh:c23084e1b23577de22603cff752e59128d83cfecc2e6819edadd8cf7a10af11e", + "zh:9d5eea62fdb587eeb96a8c4d782459f4e6b73baeece4d04b4a40e44faaee9301", + "zh:a6355f596a3fb8fc85c2fb054ab14e722991533f87f928e7169a486462c74670", + "zh:b5a65a789cff4ada58a5baffc76cb9767dc26ec6b45c00d2ec8b1b027f6db4ed", + "zh:db5ab669cf11d0e9f81dc380a6fdfcac437aea3d69109c7aef1a5426639d2d65", + "zh:de655d251c470197bcbb5ac45d289595295acb8f829f6c781d4a75c8c8b7c7dd", + "zh:f5c68199f2e6076bce92a12230434782bf768103a427e9bb9abee99b116af7b5", ] } provider "registry.terraform.io/hashicorp/random" { - version = "3.6.2" + version = "3.6.3" + constraints = ">= 2.1.0" hashes = [ - "h1:R5qdQjKzOU16TziCN1vR3Exr/B+8WGK80glLTT4ZCPk=", - "h1:wmG0QFjQ2OfyPy6BB7mQ57WtoZZGGV07uAPQeDmIrAE=", - "zh:0ef01a4f81147b32c1bea3429974d4d104bbc4be2ba3cfa667031a8183ef88ec", - "zh:1bcd2d8161e89e39886119965ef0f37fcce2da9c1aca34263dd3002ba05fcb53", - "zh:37c75d15e9514556a5f4ed02e1548aaa95c0ecd6ff9af1119ac905144c70c114", - "zh:4210550a767226976bc7e57d988b9ce48f4411fa8a60cd74a6b246baf7589dad", - "zh:562007382520cd4baa7320f35e1370ffe84e46ed4e2071fdc7e4b1a9b1f8ae9b", - "zh:5efb9da90f665e43f22c2e13e0ce48e86cae2d960aaf1abf721b497f32025916", - "zh:6f71257a6b1218d02a573fc9bff0657410404fb2ef23bc66ae8cd968f98d5ff6", + "h1:Fnaec9vA8sZ8BXVlN3Xn9Jz3zghSETIKg7ch8oXhxno=", + "zh:04ceb65210251339f07cd4611885d242cd4d0c7306e86dda9785396807c00451", + "zh:448f56199f3e99ff75d5c0afacae867ee795e4dfda6cb5f8e3b2a72ec3583dd8", + "zh:4b4c11ccfba7319e901df2dac836b1ae8f12185e37249e8d870ee10bb87a13fe", + "zh:4fa45c44c0de582c2edb8a2e054f55124520c16a39b2dfc0355929063b6395b1", + "zh:588508280501a06259e023b0695f6a18149a3816d259655c424d068982cbdd36", + "zh:737c4d99a87d2a4d1ac0a54a73d2cb62974ccb2edbd234f333abd079a32ebc9e", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:9647e18f221380a85f2f0ab387c68fdafd58af6193a932417299cdcae4710150", - "zh:bb6297ce412c3c2fa9fec726114e5e0508dd2638cad6a0cb433194930c97a544", - "zh:f83e925ed73ff8a5ef6e3608ad9225baa5376446349572c2449c0c0b3cf184b7", - "zh:fbef0781cb64de76b1df1ca11078aecba7800d82fd4a956302734999cfd9a4af", + "zh:a357ab512e5ebc6d1fda1382503109766e21bbfdfaa9ccda43d313c122069b30", + "zh:c51bfb15e7d52cc1a2eaec2a903ac2aff15d162c172b1b4c17675190e8147615", + "zh:e0951ee6fa9df90433728b96381fb867e3db98f66f735e0c3e24f8f16903f0ad", + "zh:e3cdcb4e73740621dabd82ee6a37d6cfce7fee2a03d8074df65086760f5cf556", + "zh:eff58323099f1bd9a0bec7cb04f717e7f1b2774c7d612bf7581797e1622613a0", ] } provider "registry.terraform.io/hashicorp/template" { version = "2.2.0" hashes = [ - "h1:0wlehNaxBX7GJQnPfQwTNvvAf38Jm0Nv7ssKGMaG6Og=", "h1:94qn780bi1qjrbC3uQtjJh3Wkfwd5+tTtJHOb7KTg9w=", "zh:01702196f0a0492ec07917db7aaa595843d8f171dc195f4c988d2ffca2a06386", "zh:09aae3da826ba3d7df69efeb25d146a1de0d03e951d35019a0f80e4f58c89b53", diff --git a/infrastructure/terraform/main.tf b/infrastructure/terraform/main.tf index e2186b06..ae6eec05 100644 --- a/infrastructure/terraform/main.tf +++ b/infrastructure/terraform/main.tf @@ -43,22 +43,22 @@ provider "google" { } data "google_project" "feature_store_project" { - provider = google + provider = google project_id = var.feature_store_project_id } data "google_project" "activation_project" { - provider = google + provider = google project_id = var.activation_project_id } data "google_project" "data_processing_project" { - provider = google + provider = google project_id = var.data_processing_project_id } data "google_project" "data_project" { - provider = google + provider = google project_id = var.data_project_id } @@ -66,30 +66,30 @@ data "google_project" "data_project" { # The locals block is used to define variables that are used in the configuration. locals { # The source_root_dir is the root directory of the project. - source_root_dir = "../.." + source_root_dir = "../.." # The config_file_name is the name of the config file. - config_file_name = "config" + config_file_name = "config" # The poetry_run_alias is the alias of the poetry command. - poetry_run_alias = "${var.poetry_cmd} run" + poetry_run_alias = "${var.poetry_cmd} run" # The mds_dataset_suffix is the suffix of the marketing data store dataset. mds_dataset_suffix = var.create_staging_environment ? "staging" : var.create_dev_environment ? "dev" : "prod" # The project_toml_file_path is the path to the project.toml file. - project_toml_file_path = "${local.source_root_dir}/pyproject.toml" + project_toml_file_path = "${local.source_root_dir}/pyproject.toml" # The project_toml_content_hash is the hash of the project.toml file. # This is used for the triggers of the local-exec provisioner. project_toml_content_hash = filesha512(local.project_toml_file_path) # The generated_sql_queries_directory_path is the path to the generated sql queries directory. generated_sql_queries_directory_path = "${local.source_root_dir}/sql/query" # The generated_sql_queries_fileset is the list of files in the generated sql queries directory. - generated_sql_queries_fileset = [for f in fileset(local.generated_sql_queries_directory_path, "*.sqlx") : "${local.generated_sql_queries_directory_path}/${f}"] + generated_sql_queries_fileset = [for f in fileset(local.generated_sql_queries_directory_path, "*.sqlx") : "${local.generated_sql_queries_directory_path}/${f}"] # The generated_sql_queries_content_hash is the sha512 hash of file sha512 hashes in the generated sql queries directory. - generated_sql_queries_content_hash = sha512(join("", [for f in local.generated_sql_queries_fileset : fileexists(f) ? filesha512(f) : sha512("file-not-found")])) + generated_sql_queries_content_hash = sha512(join("", [for f in local.generated_sql_queries_fileset : fileexists(f) ? filesha512(f) : sha512("file-not-found")])) # The generated_sql_procedures_directory_path is the path to the generated sql procedures directory. generated_sql_procedures_directory_path = "${local.source_root_dir}/sql/procedure" # The generated_sql_procedures_fileset is the list of files in the generated sql procedures directory. - generated_sql_procedures_fileset = [for f in fileset(local.generated_sql_procedures_directory_path, "*.sqlx") : "${local.generated_sql_procedures_directory_path}/${f}"] + generated_sql_procedures_fileset = [for f in fileset(local.generated_sql_procedures_directory_path, "*.sqlx") : "${local.generated_sql_procedures_directory_path}/${f}"] # The generated_sql_procedures_content_hash is the sha512 hash of file sha512 hashes in the generated sql procedures directory. - generated_sql_procedures_content_hash = sha512(join("", [for f in local.generated_sql_procedures_fileset : fileexists(f) ? filesha512(f) : sha512("file-not-found")])) + generated_sql_procedures_content_hash = sha512(join("", [for f in local.generated_sql_procedures_fileset : fileexists(f) ? filesha512(f) : sha512("file-not-found")])) } @@ -207,7 +207,7 @@ resource "null_resource" "generate_sql_queries" { module "initial_project_services" { source = "terraform-google-modules/project-factory/google//modules/project_services" - version = "14.1.0" + version = "17.0.0" disable_dependent_services = false disable_services_on_destroy = false @@ -315,14 +315,14 @@ module "data_store" { google_default_region = var.google_default_region # The dataform_region is set in the terraform.tfvars file. Its default value is "us-central1". - dataform_region = var.dataform_region + dataform_region = var.dataform_region # The source_ga4_export_project_id is set in the terraform.tfvars file. # The source_ga4_export_dataset is set in the terraform.tfvars file. # The source_ads_export_data is set in the terraform.tfvars file. - source_ga4_export_project_id = var.source_ga4_export_project_id - source_ga4_export_dataset = var.source_ga4_export_dataset - source_ads_export_data = var.source_ads_export_data + source_ga4_export_project_id = var.source_ga4_export_project_id + source_ga4_export_dataset = var.source_ga4_export_dataset + source_ads_export_data = var.source_ads_export_data ga4_incremental_processing_days_back = var.ga4_incremental_processing_days_back # The data_processing_project_id is set in the terraform.tfvars file. @@ -331,7 +331,7 @@ module "data_store" { data_processing_project_id = var.data_processing_project_id data_project_id = var.data_project_id destination_data_location = var.destination_data_location - + # The dataform_github_repo is set in the terraform.tfvars file. # The dataform_github_token is set in the terraform.tfvars file. dataform_github_repo = var.dataform_github_repo @@ -391,14 +391,14 @@ module "feature_store" { # If the count is 1, the feature store is created. # If the count is 0, the feature store is not created. # This is done to avoid creating the feature store if the `deploy_feature_store` variable is set to false in the terraform.tfvars file. - count = var.deploy_feature_store ? 1 : 0 - project_id = var.feature_store_project_id + count = var.deploy_feature_store ? 1 : 0 + project_id = var.feature_store_project_id # The region is the region in which the feature store is created. # This is set to the default region in the terraform.tfvars file. - region = var.google_default_region + region = var.google_default_region # The sql_dir_input is the path to the sql directory. # This is set to the path to the sql directory in the feature store module. - sql_dir_input = null_resource.generate_sql_queries.id != "" ? "${local.source_root_dir}/sql" : "" + sql_dir_input = null_resource.generate_sql_queries.id != "" ? "${local.source_root_dir}/sql" : "" } @@ -416,13 +416,13 @@ module "pipelines" { # If the count is 1, the pipelines are created. # If the count is 0, the pipelines are not created. # This is done to avoid creating the pipelines if the `deploy_pipelines` variable is set to false in the terraform.tfvars file. - count = var.deploy_pipelines ? 1 : 0 + count = var.deploy_pipelines ? 1 : 0 # The poetry_installed trigger is the ID of the null_resource.poetry_install resource. - # This is used to ensure that the poetry command is run before the pipelines module is created. + # This is used to ensure that the poetry command is run before the pipelines module is created. poetry_installed = null_resource.poetry_install.id # The project_id is the project in which the data is stored. # This is set to the data project ID in the terraform.tfvars file. - mds_project_id = var.data_project_id + mds_project_id = var.data_project_id } @@ -433,53 +433,53 @@ module "pipelines" { # The activation function is created in the `activation_project_id` project. module "activation" { # The source is the path to the activation module. - source = "./modules/activation" + source = "./modules/activation" # The project_id is the project in which the activation function is created. # This is set to the activation project ID in the terraform.tfvars file. - project_id = var.activation_project_id + project_id = var.activation_project_id # The project number of where the activation function is created. # This is retrieved from the activation project id using the google_project data source. - project_number = data.google_project.activation_project.number + project_number = data.google_project.activation_project.number # The location is the google_default_region variable. # This is set to the default region in the terraform.tfvars file. - location = var.google_default_region + location = var.google_default_region # The data_location is the destination_data_location variable. # This is set to the destination data location in the terraform.tfvars file. - data_location = var.destination_data_location + data_location = var.destination_data_location # The trigger_function_location is the location of the trigger function. # The trigger function is used to trigger the activation function. # The trigger function is created in the same region as the activation function. trigger_function_location = var.google_default_region # The poetry_cmd is the poetry_cmd variable. # This can be set on the poetry_cmd in the terraform.tfvars file. - poetry_cmd = var.poetry_cmd + poetry_cmd = var.poetry_cmd # The ga4_measurement_id is the ga4_measurement_id variable. # This can be set on the ga4_measurement_id in the terraform.tfvars file. - ga4_measurement_id = var.ga4_measurement_id + ga4_measurement_id = var.ga4_measurement_id # The ga4_measurement_secret is the ga4_measurement_secret variable. # This can be set on the ga4_measurement_secret in the terraform.tfvars file. - ga4_measurement_secret = var.ga4_measurement_secret + ga4_measurement_secret = var.ga4_measurement_secret # The ga4_property_id is the ga4_property_id variable. # This is set on the ga4_property_id in the terraform.tfvars file. # The ga4_property_id is the property ID of the GA4 data. # You can find the property ID in the GA4 console. - ga4_property_id = var.ga4_property_id + ga4_property_id = var.ga4_property_id # The ga4_stream_id is the ga4_stream_id variable. # This is set on the ga4_stream_id in the terraform.tfvars file. # The ga4_stream_id is the stream ID of the GA4 data. # You can find the stream ID in the GA4 console. - ga4_stream_id = var.ga4_stream_id + ga4_stream_id = var.ga4_stream_id # The count determines if the activation function is created or not. # If the count is 1, the activation function is created. # If the count is 0, the activation function is not created. # This is done to avoid creating the activation function if the `deploy_activation` variable is set # to false in the terraform.tfvars file. - count = var.deploy_activation ? 1 : 0 + count = var.deploy_activation ? 1 : 0 # The poetry_installed is the ID of the null_resource poetry_install # This is used to ensure that the poetry command is run before the activation module is created. - poetry_installed = null_resource.poetry_install.id - mds_project_id = var.data_project_id - mds_dataset_suffix = local.mds_dataset_suffix + poetry_installed = null_resource.poetry_install.id + mds_project_id = var.data_project_id + mds_dataset_suffix = local.mds_dataset_suffix # The project_owner_email is set in the terraform.tfvars file. # An example of a valid email address is "william.mckinley@my-own-personal-domain.com". diff --git a/infrastructure/terraform/modules/activation/main.tf b/infrastructure/terraform/modules/activation/main.tf index 1123410c..137b774e 100644 --- a/infrastructure/terraform/modules/activation/main.tf +++ b/infrastructure/terraform/modules/activation/main.tf @@ -37,15 +37,15 @@ locals { trigger_function_account_name = "trigger-function" trigger_function_account_email = "${local.app_prefix}-${local.trigger_function_account_name}@${var.project_id}.iam.gserviceaccount.com" - builder_service_account_name = "build-job" + builder_service_account_name = "build-job" builder_service_account_email = "${local.app_prefix}-${local.builder_service_account_name}@${var.project_id}.iam.gserviceaccount.com" - activation_type_configuration_file = "${local.source_root_dir}/templates/activation_type_configuration_template.tpl" + activation_type_configuration_file = "${local.source_root_dir}/templates/activation_type_configuration_template.tpl" # This is calculating a hash number on the file content to keep track of changes and trigger redeployment of resources # in case the file content changes. activation_type_configuration_file_content_hash = filesha512(local.activation_type_configuration_file) - app_payload_template_file = "${local.source_root_dir}/templates/app_payload_template.jinja2" + app_payload_template_file = "${local.source_root_dir}/templates/app_payload_template.jinja2" # This is calculating a hash number on the file content to keep track of changes and trigger redeployment of resources # in case the file content changes. app_payload_template_file_content_hash = filesha512(local.activation_type_configuration_file) @@ -69,7 +69,7 @@ data "google_project" "activation_project" { module "project_services" { source = "terraform-google-modules/project-factory/google//modules/project_services" - version = "14.1.0" + version = "17.0.0" disable_dependent_services = false disable_services_on_destroy = false @@ -305,7 +305,7 @@ resource "null_resource" "check_cloudbuild_api" { module "bigquery" { source = "terraform-google-modules/bigquery/google" - version = "~> 5.4" + version = "8.1.0" dataset_id = local.app_prefix dataset_name = local.app_prefix @@ -359,7 +359,7 @@ resource "google_artifact_registry_repository" "activation_repository" { module "pipeline_service_account" { source = "terraform-google-modules/service-accounts/google" - version = "~> 3.0" + version = "4.4.0" project_id = null_resource.check_dataflow_api.id != "" ? module.project_services.project_id : var.project_id prefix = local.app_prefix names = [local.pipeline_service_account_name] @@ -368,7 +368,7 @@ module "pipeline_service_account" { "${module.project_services.project_id}=>roles/dataflow.worker", "${module.project_services.project_id}=>roles/bigquery.dataEditor", "${module.project_services.project_id}=>roles/bigquery.jobUser", - "${module.project_services.project_id}=>roles/artifactregistry.writer", + "${module.project_services.project_id}=>roles/artifactregistry.writer", ] display_name = "Dataflow worker Service Account" description = "Activation Dataflow worker Service Account" @@ -376,7 +376,7 @@ module "pipeline_service_account" { module "trigger_function_account" { source = "terraform-google-modules/service-accounts/google" - version = "~> 3.0" + version = "4.4.0" project_id = null_resource.check_pubsub_api.id != "" ? module.project_services.project_id : var.project_id prefix = local.app_prefix names = [local.trigger_function_account_name] @@ -403,7 +403,7 @@ data "external" "ga4_measurement_properties" { # The count attribute specifies how many times the external data source should be executed. # This means that the external data source will be executed only if either the # var.ga4_measurement_id or var.ga4_measurement_secret variable is not set. - count = (var.ga4_measurement_id == null || var.ga4_measurement_secret == null || var.ga4_measurement_id == "" || var.ga4_measurement_secret == "") ? 1 : 0 + count = (var.ga4_measurement_id == null || var.ga4_measurement_secret == null || var.ga4_measurement_id == "" || var.ga4_measurement_secret == "") ? 1 : 0 depends_on = [ module.project_services @@ -413,7 +413,7 @@ data "external" "ga4_measurement_properties" { # This module stores the values ga4-measurement-id and ga4-measurement-secret in Google Cloud Secret Manager. module "secret_manager" { source = "GoogleCloudPlatform/secret-manager/google" - version = "~> 0.1" + version = "0.4.0" project_id = null_resource.check_secretmanager_api.id != "" ? module.project_services.project_id : var.project_id secrets = [ { @@ -435,11 +435,11 @@ module "secret_manager" { # This module creates a Cloud Storage bucket to be used by the Activation Application module "pipeline_bucket" { - source = "terraform-google-modules/cloud-storage/google//modules/simple_bucket" - version = "~> 3.4.1" - project_id = null_resource.check_dataflow_api.id != "" ? module.project_services.project_id : var.project_id - name = "${local.app_prefix}-app-${module.project_services.project_id}" - location = var.location + source = "terraform-google-modules/cloud-storage/google//modules/simple_bucket" + version = "6.1.0" + project_id = null_resource.check_dataflow_api.id != "" ? module.project_services.project_id : var.project_id + name = "${local.app_prefix}-app-${module.project_services.project_id}" + location = var.location # When deleting a bucket, this boolean option will delete all contained objects. # If false, Terraform will fail to delete buckets which contain objects. force_destroy = true @@ -471,8 +471,8 @@ resource "google_project_iam_member" "cloud_build_job_service_account" { module.project_services, null_resource.check_artifactregistry_api, data.google_project.project, - ] - + ] + project = null_resource.check_artifactregistry_api.id != "" ? module.project_services.project_id : var.project_id member = "serviceAccount:${var.project_number}-compute@developer.gserviceaccount.com" @@ -516,16 +516,16 @@ resource "google_project_iam_member" "cloud_build_job_service_account" { } data "google_project" "project" { - project_id = null_resource.check_cloudbuild_api != "" ? module.project_services.project_id : var.project_id + project_id = null_resource.check_cloudbuild_api != "" ? module.project_services.project_id : var.project_id } # This module creates a Cloud Storage bucket to be used by the Cloud Build Log Bucket module "build_logs_bucket" { - source = "terraform-google-modules/cloud-storage/google//modules/simple_bucket" - version = "~> 3.4.1" - project_id = null_resource.check_cloudbuild_api != "" ? module.project_services.project_id : var.project_id - name = "${local.app_prefix}-logs-${module.project_services.project_id}" - location = var.location + source = "terraform-google-modules/cloud-storage/google//modules/simple_bucket" + version = "6.1.0" + project_id = null_resource.check_cloudbuild_api != "" ? module.project_services.project_id : var.project_id + name = "${local.app_prefix}-logs-${module.project_services.project_id}" + location = var.location # When deleting a bucket, this boolean option will delete all contained objects. # If false, Terraform will fail to delete buckets which contain objects. force_destroy = true @@ -543,8 +543,8 @@ module "build_logs_bucket" { iam_members = [ { - role = "roles/storage.admin" - member = "serviceAccount:${var.project_number}-compute@developer.gserviceaccount.com" + role = "roles/storage.admin" + member = "serviceAccount:${var.project_number}-compute@developer.gserviceaccount.com" } ] @@ -657,9 +657,9 @@ data "template_file" "activation_type_configuration" { # This resource creates a bucket object using as content the activation_type_configuration.json file. resource "google_storage_bucket_object" "activation_type_configuration_file" { - name = "${local.configuration_folder}/activation_type_configuration.json" - content = data.template_file.activation_type_configuration.rendered - bucket = module.pipeline_bucket.name + name = "${local.configuration_folder}/activation_type_configuration.json" + content = data.template_file.activation_type_configuration.rendered + bucket = module.pipeline_bucket.name # Detects md5hash changes to redeploy this file to the GCS bucket. detect_md5hash = base64encode("${local.activation_type_configuration_file_content_hash}${local.activation_application_content_hash}") } @@ -667,7 +667,7 @@ resource "google_storage_bucket_object" "activation_type_configuration_file" { # This module submits a gcloud build to build a docker container image to be used by the Activation Application module "activation_pipeline_container" { source = "terraform-google-modules/gcloud/google" - version = "3.1.2" + version = "3.5.0" platform = "linux" @@ -687,7 +687,7 @@ module "activation_pipeline_container" { # This module executes a gcloud command to build a dataflow flex template and uploads it to Dataflow module "activation_pipeline_template" { source = "terraform-google-modules/gcloud/google" - version = "3.1.2" + version = "3.5.0" additional_components = ["gsutil"] platform = "linux" @@ -718,11 +718,11 @@ data "archive_file" "activation_trigger_source" { # This module creates a Cloud Sorage bucket and sets the trigger_function_account_email as the admin. module "function_bucket" { - source = "terraform-google-modules/cloud-storage/google//modules/simple_bucket" - version = "~> 3.4.1" - project_id = null_resource.check_cloudfunctions_api.id != "" ? module.project_services.project_id : var.project_id - name = "${local.app_prefix}-trigger-${module.project_services.project_id}" - location = var.location + source = "terraform-google-modules/cloud-storage/google//modules/simple_bucket" + version = "6.1.0" + project_id = null_resource.check_cloudfunctions_api.id != "" ? module.project_services.project_id : var.project_id + name = "${local.app_prefix}-trigger-${module.project_services.project_id}" + location = var.location # When deleting a bucket, this boolean option will delete all contained objects. # If false, Terraform will fail to delete buckets which contain objects. force_destroy = true @@ -821,7 +821,7 @@ resource "google_cloudfunctions2_function" "activation_trigger_cf" { # This modules runs cloud commands that adds an invoker policy binding to a Cloud Function, allowing a specific service account to invoke the function. module "add_invoker_binding" { source = "terraform-google-modules/gcloud/google" - version = "3.1.2" + version = "3.5.0" platform = "linux" diff --git a/infrastructure/terraform/modules/activation/variables.tf b/infrastructure/terraform/modules/activation/variables.tf index d3fb4759..6c2d6428 100644 --- a/infrastructure/terraform/modules/activation/variables.tf +++ b/infrastructure/terraform/modules/activation/variables.tf @@ -90,4 +90,4 @@ variable "mds_dataset_suffix" { variable "project_owner_email" { description = "Email address of the project owner." type = string -} \ No newline at end of file +} diff --git a/infrastructure/terraform/modules/activation/versions.tf b/infrastructure/terraform/modules/activation/versions.tf index 5a896e28..29fe3151 100644 --- a/infrastructure/terraform/modules/activation/versions.tf +++ b/infrastructure/terraform/modules/activation/versions.tf @@ -20,7 +20,12 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 3.43.0, >= 3.53.0, >= 3.63.0, >= 4.83.0, < 5.0.0, < 6.0.0" + version = "5.44.1" + } + + google-beta = { + source = "hashicorp/google-beta" + version = "5.44.1" } } diff --git a/infrastructure/terraform/modules/data-store/data-processing-services.tf b/infrastructure/terraform/modules/data-store/data-processing-services.tf index dd7e66b9..86f80a29 100644 --- a/infrastructure/terraform/modules/data-store/data-processing-services.tf +++ b/infrastructure/terraform/modules/data-store/data-processing-services.tf @@ -16,7 +16,7 @@ # https://registry.terraform.io/modules/terraform-google-modules/project-factory/google/latest/submodules/project_services module "data_processing_project_services" { source = "terraform-google-modules/project-factory/google//modules/project_services" - version = "14.1.0" + version = "17.0.0" disable_dependent_services = false disable_services_on_destroy = false @@ -116,4 +116,4 @@ resource "null_resource" "check_dataform_api" { depends_on = [ module.data_processing_project_services ] -} \ No newline at end of file +} diff --git a/infrastructure/terraform/modules/data-store/dataform.tf b/infrastructure/terraform/modules/data-store/dataform.tf index 2c4d600e..24944803 100644 --- a/infrastructure/terraform/modules/data-store/dataform.tf +++ b/infrastructure/terraform/modules/data-store/dataform.tf @@ -54,9 +54,9 @@ locals { resource "google_dataform_repository" "marketing-analytics" { provider = google-beta # This is the name of the Dataform Repository created in your project - name = "marketing-analytics" - project = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id - region = local.dataform_derived_region + name = "marketing-analytics" + project = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id + region = local.dataform_derived_region lifecycle { precondition { @@ -74,4 +74,4 @@ resource "google_dataform_repository" "marketing-analytics" { depends_on = [ module.data_processing_project_services ] -} \ No newline at end of file +} diff --git a/infrastructure/terraform/modules/data-store/iam-binding.tf b/infrastructure/terraform/modules/data-store/iam-binding.tf index 4cac19f7..49329f81 100644 --- a/infrastructure/terraform/modules/data-store/iam-binding.tf +++ b/infrastructure/terraform/modules/data-store/iam-binding.tf @@ -21,7 +21,7 @@ resource "google_project_iam_member" "email-role" { ]) role = each.key member = "user:${var.project_owner_email}" - project = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id + project = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id } # Check the Dataform Service Account Access Requirements for more information @@ -62,14 +62,14 @@ resource "google_project_iam_member" "dataform-serviceaccount" { google_dataform_repository.marketing-analytics, null_resource.check_dataform_api, null_resource.wait_for_dataform_sa_creation - ] + ] for_each = toset([ "roles/secretmanager.secretAccessor", "roles/bigquery.jobUser" ]) role = each.key member = "serviceAccount:${local.dataform_sa}" - project = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id + project = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id } // Owner role to BigQuery in the destination data project the Dataform SA. @@ -79,13 +79,13 @@ resource "google_project_iam_member" "dataform-bigquery-data-owner" { google_dataform_repository.marketing-analytics, null_resource.check_dataform_api, null_resource.wait_for_dataform_sa_creation - ] + ] for_each = toset([ "roles/bigquery.dataOwner", ]) role = each.key member = "serviceAccount:${local.dataform_sa}" - project = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id + project = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id } // Read access to the GA4 exports @@ -94,7 +94,7 @@ resource "google_bigquery_dataset_iam_member" "dataform-ga4-export-reader" { google_dataform_repository.marketing-analytics, null_resource.check_dataform_api, null_resource.wait_for_dataform_sa_creation - ] + ] role = "roles/bigquery.dataViewer" member = "serviceAccount:${local.dataform_sa}" project = var.source_ga4_export_project_id @@ -107,7 +107,7 @@ resource "google_bigquery_dataset_iam_member" "dataform-ads-export-reader" { google_dataform_repository.marketing-analytics, null_resource.check_dataform_api, null_resource.wait_for_dataform_sa_creation - ] + ] count = length(var.source_ads_export_data) role = "roles/bigquery.dataViewer" member = "serviceAccount:${local.dataform_sa}" diff --git a/infrastructure/terraform/modules/data-store/main.tf b/infrastructure/terraform/modules/data-store/main.tf index 214f170c..e6e56b4d 100644 --- a/infrastructure/terraform/modules/data-store/main.tf +++ b/infrastructure/terraform/modules/data-store/main.tf @@ -35,18 +35,18 @@ module "dataform-workflow-dev" { # In this case, it's set to var.create_dev_environment ? 1 : 0, which means that # the module will be created only if the var.create_dev_environment variable is set to `true`. # Check the terraform.tfvars file for more information. - count = var.create_dev_environment ? 1 : 0 + count = var.create_dev_environment ? 1 : 0 # the path to the Terraform module that will be used to create the Dataform workflow environment. source = "../dataform-workflow" - project_id = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id + project_id = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id # The name of the Dataform workflow environment. - environment = "dev" - region = var.google_default_region + environment = "dev" + region = var.google_default_region # The ID of the Dataform repository that will be used by the Dataform workflow environment. dataform_repository_id = google_dataform_repository.marketing-analytics.id # A list of tags that will be used to filter the Dataform files that are included in the Dataform workflow environment. - includedTags = ["ga4"] + includedTags = ["ga4"] source_ga4_export_project_id = var.source_ga4_export_project_id source_ga4_export_dataset = var.source_ga4_export_dataset @@ -70,18 +70,18 @@ module "dataform-workflow-staging" { # In this case, it's set to var.create_staging_environment ? 1 : 0, which means that # the module will be created only if the var.create_staging_environment variable is set to `true`. # Check the terraform.tfvars file for more information. - count = var.create_staging_environment ? 1 : 0 + count = var.create_staging_environment ? 1 : 0 # the path to the Terraform module that will be used to create the Dataform workflow environment. source = "../dataform-workflow" - project_id = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id + project_id = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id # The name of the Dataform workflow environment. - environment = "staging" - region = var.google_default_region + environment = "staging" + region = var.google_default_region # The ID of the Dataform repository that will be used by the Dataform workflow environment. dataform_repository_id = google_dataform_repository.marketing-analytics.id # A list of tags that will be used to filter the Dataform files that are included in the Dataform workflow environment. - includedTags = ["ga4"] + includedTags = ["ga4"] source_ga4_export_project_id = var.source_ga4_export_project_id source_ga4_export_dataset = var.source_ga4_export_dataset @@ -104,11 +104,11 @@ module "dataform-workflow-prod" { # In this case, it's set to var.create_prod_environment ? 1 : 0, which means that # the module will be created only if the var.create_prod_environment variable is set to `true`. # Check the terraform.tfvars file for more information. - count = var.create_prod_environment ? 1 : 0 + count = var.create_prod_environment ? 1 : 0 # the path to the Terraform module that will be used to create the Dataform workflow environment. source = "../dataform-workflow" - project_id = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id + project_id = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id # The name of the Dataform workflow environment. environment = "prod" region = var.google_default_region diff --git a/infrastructure/terraform/modules/data-store/secretmanager.tf b/infrastructure/terraform/modules/data-store/secretmanager.tf index d89b86e5..fb0d0ff3 100644 --- a/infrastructure/terraform/modules/data-store/secretmanager.tf +++ b/infrastructure/terraform/modules/data-store/secretmanager.tf @@ -14,7 +14,7 @@ resource "google_secret_manager_secret" "github-secret" { secret_id = "Github_token" - project = null_resource.check_secretmanager_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id + project = null_resource.check_secretmanager_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id replication { #automatic = true @@ -28,7 +28,7 @@ resource "google_secret_manager_secret" "github-secret" { } resource "google_secret_manager_secret_version" "secret-version-github" { - secret = google_secret_manager_secret.github-secret.id + secret = google_secret_manager_secret.github-secret.id secret_data = var.dataform_github_token #deletion_policy = "DISABLE" @@ -38,4 +38,4 @@ resource "google_secret_manager_secret_version" "secret-version-github" { null_resource.check_dataform_api, null_resource.check_secretmanager_api ] -} \ No newline at end of file +} diff --git a/infrastructure/terraform/modules/data-store/variables.tf b/infrastructure/terraform/modules/data-store/variables.tf index 62bc24f1..3b7f5cfd 100644 --- a/infrastructure/terraform/modules/data-store/variables.tf +++ b/infrastructure/terraform/modules/data-store/variables.tf @@ -38,12 +38,12 @@ variable "project_owner_email" { } variable "dataform_github_repo" { - description = "Private Github repo for Dataform." + description = "Private GitHub repo for Dataform." type = string } variable "dataform_github_token" { - description = "Github token for Dataform repo." + description = "GitHub token for Dataform repo." type = string } @@ -112,7 +112,7 @@ variable "source_ga4_export_dataset" { } variable "ga4_incremental_processing_days_back" { - type = string + type = string default = "3" } @@ -128,4 +128,4 @@ variable "source_ads_export_data" { variable "dataform_region" { description = "Specify dataform region when dataform is not available in the default cloud region of choice" type = string -} \ No newline at end of file +} diff --git a/infrastructure/terraform/modules/data-store/versions.tf b/infrastructure/terraform/modules/data-store/versions.tf index 8821ac39..54e0ceda 100644 --- a/infrastructure/terraform/modules/data-store/versions.tf +++ b/infrastructure/terraform/modules/data-store/versions.tf @@ -20,7 +20,12 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 3.43.0, >= 3.53.0, >= 3.63.0, >= 4.83.0, < 5.0.0, < 6.0.0" + version = "5.44.1" + } + + google-beta = { + source = "hashicorp/google-beta" + version = "5.44.1" } } diff --git a/infrastructure/terraform/modules/dataform-workflow/README.md b/infrastructure/terraform/modules/dataform-workflow/README.md index 8b2bdff5..9ff7546b 100644 --- a/infrastructure/terraform/modules/dataform-workflow/README.md +++ b/infrastructure/terraform/modules/dataform-workflow/README.md @@ -1 +1 @@ -# Dataform workflow module \ No newline at end of file +# Dataform workflow module diff --git a/infrastructure/terraform/modules/dataform-workflow/scheduler.tf b/infrastructure/terraform/modules/dataform-workflow/scheduler.tf index 3947b3b8..d6a6cd8c 100644 --- a/infrastructure/terraform/modules/dataform-workflow/scheduler.tf +++ b/infrastructure/terraform/modules/dataform-workflow/scheduler.tf @@ -14,12 +14,12 @@ # This creates a Cloud Scheduler job that triggers the Dataform incremental workflow on a daily schedule. resource "google_cloud_scheduler_job" "daily-dataform-increments" { - project = module.data_processing_project_services.project_id - name = "daily-dataform-${var.environment}" - description = "Daily Dataform ${var.environment} environment incremental update" + project = module.data_processing_project_services.project_id + name = "daily-dataform-${var.environment}" + description = "Daily Dataform ${var.environment} environment incremental update" # The schedule attribute specifies the schedule for the job. In this case, the job is scheduled to run daily at the specified times. - schedule = var.daily_schedule - time_zone = "America/New_York" + schedule = var.daily_schedule + time_zone = "America/New_York" # The attempt_deadline attribute specifies the maximum amount of time that the job will attempt to run before failing. # In this case, the job will attempt to run for a maximum of 5 minutes before failing. attempt_deadline = "320s" diff --git a/infrastructure/terraform/modules/dataform-workflow/service-account.tf b/infrastructure/terraform/modules/dataform-workflow/service-account.tf index 39d31811..b89e91af 100644 --- a/infrastructure/terraform/modules/dataform-workflow/service-account.tf +++ b/infrastructure/terraform/modules/dataform-workflow/service-account.tf @@ -16,8 +16,8 @@ resource "google_service_account" "scheduler" { depends_on = [ module.data_processing_project_services, null_resource.check_cloudscheduler_api, - ] - + ] + project = null_resource.check_cloudscheduler_api.id != "" ? module.data_processing_project_services.project_id : var.project_id account_id = "workflow-scheduler-${var.environment}" display_name = "Service Account to schedule Dataform workflows in ${var.environment}" @@ -60,7 +60,7 @@ resource "google_project_iam_member" "scheduler-workflow-invoker" { module.data_processing_project_services, null_resource.check_cloudscheduler_api, null_resource.wait_for_scheduler_sa_creation - ] + ] project = null_resource.check_cloudscheduler_api.id != "" ? module.data_processing_project_services.project_id : var.project_id member = "serviceAccount:${google_service_account.scheduler.email}" @@ -71,8 +71,8 @@ resource "google_service_account" "workflow-dataform" { depends_on = [ module.data_processing_project_services, null_resource.check_workflows_api, - ] - + ] + project = null_resource.check_workflows_api.id != "" ? module.data_processing_project_services.project_id : var.project_id account_id = "workflow-dataform-${var.environment}" display_name = "Service Account to run Dataform workflows in ${var.environment}" @@ -110,9 +110,9 @@ resource "google_project_iam_member" "worflow-dataform-dataform-editor" { module.data_processing_project_services, null_resource.check_dataform_api, null_resource.wait_for_workflows_sa_creation - ] + ] project = null_resource.check_workflows_api.id != "" ? module.data_processing_project_services.project_id : var.project_id member = "serviceAccount:${google_service_account.workflow-dataform.email}" role = "roles/dataform.editor" -} \ No newline at end of file +} diff --git a/infrastructure/terraform/modules/dataform-workflow/services.tf b/infrastructure/terraform/modules/dataform-workflow/services.tf index 0271f228..c78dc529 100644 --- a/infrastructure/terraform/modules/dataform-workflow/services.tf +++ b/infrastructure/terraform/modules/dataform-workflow/services.tf @@ -15,7 +15,7 @@ # https://registry.terraform.io/modules/terraform-google-modules/project-factory/google/latest/submodules/project_services module "data_processing_project_services" { source = "terraform-google-modules/project-factory/google//modules/project_services" - version = "14.1.0" + version = "17.0.0" disable_dependent_services = false disable_services_on_destroy = false @@ -142,4 +142,4 @@ resource "null_resource" "check_cloudscheduler_api" { depends_on = [ module.data_processing_project_services ] -} \ No newline at end of file +} diff --git a/infrastructure/terraform/modules/dataform-workflow/variables.tf b/infrastructure/terraform/modules/dataform-workflow/variables.tf index 60b014d8..cacb2b92 100644 --- a/infrastructure/terraform/modules/dataform-workflow/variables.tf +++ b/infrastructure/terraform/modules/dataform-workflow/variables.tf @@ -27,7 +27,7 @@ variable "environment" { } variable "daily_schedule" { - type = string + type = string # This schedule executes every days, each 2 hours between 5AM and 11PM. default = "0 5-23/2 * * *" #"2 5 * * *" } @@ -45,7 +45,7 @@ variable "source_ga4_export_dataset" { } variable "ga4_incremental_processing_days_back" { - type = string + type = string default = "3" } @@ -74,4 +74,4 @@ variable "gitCommitish" { variable "includedTags" { type = list(string) default = [] -} \ No newline at end of file +} diff --git a/infrastructure/terraform/modules/dataform-workflow/versions.tf b/infrastructure/terraform/modules/dataform-workflow/versions.tf index 8821ac39..54e0ceda 100644 --- a/infrastructure/terraform/modules/dataform-workflow/versions.tf +++ b/infrastructure/terraform/modules/dataform-workflow/versions.tf @@ -20,7 +20,12 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 3.43.0, >= 3.53.0, >= 3.63.0, >= 4.83.0, < 5.0.0, < 6.0.0" + version = "5.44.1" + } + + google-beta = { + source = "hashicorp/google-beta" + version = "5.44.1" } } diff --git a/infrastructure/terraform/modules/feature-store/bigquery-datasets.tf b/infrastructure/terraform/modules/feature-store/bigquery-datasets.tf index c51927c3..d7b08539 100644 --- a/infrastructure/terraform/modules/feature-store/bigquery-datasets.tf +++ b/infrastructure/terraform/modules/feature-store/bigquery-datasets.tf @@ -14,14 +14,14 @@ # This resource creates a BigQuery dataset called `feature_store`. resource "google_bigquery_dataset" "feature_store" { - dataset_id = local.config_bigquery.dataset.feature_store.name - friendly_name = local.config_bigquery.dataset.feature_store.friendly_name - project = null_resource.check_bigquery_api.id != "" ? local.feature_store_project_id : var.project_id - description = local.config_bigquery.dataset.feature_store.description - location = local.config_bigquery.dataset.feature_store.location + dataset_id = local.config_bigquery.dataset.feature_store.name + friendly_name = local.config_bigquery.dataset.feature_store.friendly_name + project = null_resource.check_bigquery_api.id != "" ? local.feature_store_project_id : var.project_id + description = local.config_bigquery.dataset.feature_store.description + location = local.config_bigquery.dataset.feature_store.location # The max_time_travel_hours attribute specifies the maximum number of hours that data in the dataset can be accessed using time travel queries. # In this case, the maximum time travel hours is set to the value of the local file config.yaml section bigquery.dataset.feature_store.max_time_travel_hours configuration. - max_time_travel_hours = local.config_bigquery.dataset.feature_store.max_time_travel_hours + max_time_travel_hours = local.config_bigquery.dataset.feature_store.max_time_travel_hours # The delete_contents_on_destroy attribute specifies whether the contents of the dataset should be deleted when the dataset is destroyed. # In this case, the delete_contents_on_destroy attribute is set to false, which means that the contents of the dataset will not be deleted when the dataset is destroyed. delete_contents_on_destroy = false @@ -40,14 +40,14 @@ resource "google_bigquery_dataset" "feature_store" { # This resource creates a BigQuery dataset called `purchase_propensity`. resource "google_bigquery_dataset" "purchase_propensity" { - dataset_id = local.config_bigquery.dataset.purchase_propensity.name - friendly_name = local.config_bigquery.dataset.purchase_propensity.friendly_name - project = null_resource.check_bigquery_api.id != "" ? local.purchase_propensity_project_id : local.feature_store_project_id - description = local.config_bigquery.dataset.purchase_propensity.description - location = local.config_bigquery.dataset.purchase_propensity.location + dataset_id = local.config_bigquery.dataset.purchase_propensity.name + friendly_name = local.config_bigquery.dataset.purchase_propensity.friendly_name + project = null_resource.check_bigquery_api.id != "" ? local.purchase_propensity_project_id : local.feature_store_project_id + description = local.config_bigquery.dataset.purchase_propensity.description + location = local.config_bigquery.dataset.purchase_propensity.location # The max_time_travel_hours attribute specifies the maximum number of hours that data in the dataset can be accessed using time travel queries. # In this case, the maximum time travel hours is set to the value of the local file config.yaml section bigquery.dataset.feature_store.max_time_travel_hours configuration. - max_time_travel_hours = local.config_bigquery.dataset.purchase_propensity.max_time_travel_hours + max_time_travel_hours = local.config_bigquery.dataset.purchase_propensity.max_time_travel_hours # The delete_contents_on_destroy attribute specifies whether the contents of the dataset should be deleted when the dataset is destroyed. # In this case, the delete_contents_on_destroy attribute is set to false, which means that the contents of the dataset will not be deleted when the dataset is destroyed. delete_contents_on_destroy = false @@ -66,14 +66,14 @@ resource "google_bigquery_dataset" "purchase_propensity" { # This resource creates a BigQuery dataset called `churn_propensity`. resource "google_bigquery_dataset" "churn_propensity" { - dataset_id = local.config_bigquery.dataset.churn_propensity.name - friendly_name = local.config_bigquery.dataset.churn_propensity.friendly_name - project = null_resource.check_bigquery_api.id != "" ? local.churn_propensity_project_id : local.feature_store_project_id - description = local.config_bigquery.dataset.churn_propensity.description - location = local.config_bigquery.dataset.churn_propensity.location + dataset_id = local.config_bigquery.dataset.churn_propensity.name + friendly_name = local.config_bigquery.dataset.churn_propensity.friendly_name + project = null_resource.check_bigquery_api.id != "" ? local.churn_propensity_project_id : local.feature_store_project_id + description = local.config_bigquery.dataset.churn_propensity.description + location = local.config_bigquery.dataset.churn_propensity.location # The max_time_travel_hours attribute specifies the maximum number of hours that data in the dataset can be accessed using time travel queries. # In this case, the maximum time travel hours is set to the value of the local file config.yaml section bigquery.dataset.feature_store.max_time_travel_hours configuration. - max_time_travel_hours = local.config_bigquery.dataset.churn_propensity.max_time_travel_hours + max_time_travel_hours = local.config_bigquery.dataset.churn_propensity.max_time_travel_hours # The delete_contents_on_destroy attribute specifies whether the contents of the dataset should be deleted when the dataset is destroyed. # In this case, the delete_contents_on_destroy attribute is set to false, which means that the contents of the dataset will not be deleted when the dataset is destroyed. delete_contents_on_destroy = false @@ -92,14 +92,14 @@ resource "google_bigquery_dataset" "churn_propensity" { # This resource creates a BigQuery dataset called `customer_lifetime_value`. resource "google_bigquery_dataset" "customer_lifetime_value" { - dataset_id = local.config_bigquery.dataset.customer_lifetime_value.name - friendly_name = local.config_bigquery.dataset.customer_lifetime_value.friendly_name - project = null_resource.check_bigquery_api.id != "" ? local.customer_lifetime_value_project_id : local.feature_store_project_id - description = local.config_bigquery.dataset.customer_lifetime_value.description - location = local.config_bigquery.dataset.customer_lifetime_value.location + dataset_id = local.config_bigquery.dataset.customer_lifetime_value.name + friendly_name = local.config_bigquery.dataset.customer_lifetime_value.friendly_name + project = null_resource.check_bigquery_api.id != "" ? local.customer_lifetime_value_project_id : local.feature_store_project_id + description = local.config_bigquery.dataset.customer_lifetime_value.description + location = local.config_bigquery.dataset.customer_lifetime_value.location # The max_time_travel_hours attribute specifies the maximum number of hours that data in the dataset can be accessed using time travel queries. # In this case, the maximum time travel hours is set to the value of the local file config.yaml section bigquery.dataset.customer_lifetime_value.max_time_travel_hours configuration. - max_time_travel_hours = local.config_bigquery.dataset.customer_lifetime_value.max_time_travel_hours + max_time_travel_hours = local.config_bigquery.dataset.customer_lifetime_value.max_time_travel_hours # The delete_contents_on_destroy attribute specifies whether the contents of the dataset should be deleted when the dataset is destroyed. # In this case, the delete_contents_on_destroy attribute is set to false, which means that the contents of the dataset will not be deleted when the dataset is destroyed. delete_contents_on_destroy = false @@ -118,14 +118,14 @@ resource "google_bigquery_dataset" "customer_lifetime_value" { # This resource creates a BigQuery dataset called `audience_segmentation`. resource "google_bigquery_dataset" "audience_segmentation" { - dataset_id = local.config_bigquery.dataset.audience_segmentation.name - friendly_name = local.config_bigquery.dataset.audience_segmentation.friendly_name - project = null_resource.check_bigquery_api.id != "" ? local.audience_segmentation_project_id : local.feature_store_project_id - description = local.config_bigquery.dataset.audience_segmentation.description - location = local.config_bigquery.dataset.audience_segmentation.location + dataset_id = local.config_bigquery.dataset.audience_segmentation.name + friendly_name = local.config_bigquery.dataset.audience_segmentation.friendly_name + project = null_resource.check_bigquery_api.id != "" ? local.audience_segmentation_project_id : local.feature_store_project_id + description = local.config_bigquery.dataset.audience_segmentation.description + location = local.config_bigquery.dataset.audience_segmentation.location # The max_time_travel_hours attribute specifies the maximum number of hours that data in the dataset can be accessed using time travel queries. # In this case, the maximum time travel hours is set to the value of the local file config.yaml section bigquery.dataset.audience_segmentation.max_time_travel_hours configuration. - max_time_travel_hours = local.config_bigquery.dataset.audience_segmentation.max_time_travel_hours + max_time_travel_hours = local.config_bigquery.dataset.audience_segmentation.max_time_travel_hours # The delete_contents_on_destroy attribute specifies whether the contents of the dataset should be deleted when the dataset is destroyed. # In this case, the delete_contents_on_destroy attribute is set to false, which means that the contents of the dataset will not be deleted when the dataset is destroyed. delete_contents_on_destroy = false @@ -144,14 +144,14 @@ resource "google_bigquery_dataset" "audience_segmentation" { # This resource creates a BigQuery dataset called `auto_audience_segmentation`. resource "google_bigquery_dataset" "auto_audience_segmentation" { - dataset_id = local.config_bigquery.dataset.auto_audience_segmentation.name - friendly_name = local.config_bigquery.dataset.auto_audience_segmentation.friendly_name - project = null_resource.check_bigquery_api.id != "" ? local.auto_audience_segmentation_project_id : local.feature_store_project_id - description = local.config_bigquery.dataset.auto_audience_segmentation.description - location = local.config_bigquery.dataset.auto_audience_segmentation.location + dataset_id = local.config_bigquery.dataset.auto_audience_segmentation.name + friendly_name = local.config_bigquery.dataset.auto_audience_segmentation.friendly_name + project = null_resource.check_bigquery_api.id != "" ? local.auto_audience_segmentation_project_id : local.feature_store_project_id + description = local.config_bigquery.dataset.auto_audience_segmentation.description + location = local.config_bigquery.dataset.auto_audience_segmentation.location # The max_time_travel_hours attribute specifies the maximum number of hours that data in the dataset can be accessed using time travel queries. # In this case, the maximum time travel hours is set to the value of the local file config.yaml section bigquery.dataset.auto_audience_segmentation.max_time_travel_hours configuration. - max_time_travel_hours = local.config_bigquery.dataset.auto_audience_segmentation.max_time_travel_hours + max_time_travel_hours = local.config_bigquery.dataset.auto_audience_segmentation.max_time_travel_hours # The delete_contents_on_destroy attribute specifies whether the contents of the dataset should be deleted when the dataset is destroyed. # In this case, the delete_contents_on_destroy attribute is set to false, which means that the contents of the dataset will not be deleted when the dataset is destroyed. delete_contents_on_destroy = false @@ -200,7 +200,7 @@ locals { module "aggregated_vbb" { source = "terraform-google-modules/bigquery/google" - version = "~> 5.4" + version = "8.1.0" dataset_id = local.config_bigquery.dataset.aggregated_vbb.name dataset_name = local.config_bigquery.dataset.aggregated_vbb.friendly_name @@ -216,18 +216,18 @@ module "aggregated_vbb" { } tables = [for table_id in local.aggregated_vbb_tables : - { - table_id = table_id - schema = file("../../sql/schema/table/${table_id}.json") - # The max_time_travel_hours attribute specifies the maximum number of hours that data in the dataset can be accessed using time travel queries. - # In this case, the maximum time travel hours is set to the value of the local file config.yaml section bigquery.dataset.auto_audience_segmentation.max_time_travel_hours configuration. - max_time_travel_hours = local.config_bigquery.dataset.aggregated_vbb.max_time_travel_hours - deletion_protection = false - time_partitioning = null, - range_partitioning = null, - expiration_time = null, - clustering = [], - labels = {}, + { + table_id = table_id + schema = file("../../sql/schema/table/${table_id}.json") + # The max_time_travel_hours attribute specifies the maximum number of hours that data in the dataset can be accessed using time travel queries. + # In this case, the maximum time travel hours is set to the value of the local file config.yaml section bigquery.dataset.auto_audience_segmentation.max_time_travel_hours configuration. + max_time_travel_hours = local.config_bigquery.dataset.aggregated_vbb.max_time_travel_hours + deletion_protection = false + time_partitioning = null, + range_partitioning = null, + expiration_time = null, + clustering = [], + labels = {}, }] } @@ -236,13 +236,13 @@ module "aggregated_vbb" { # the aggregated predictions generated by the predictions pipelines. module "aggregated_predictions" { source = "terraform-google-modules/bigquery/google" - version = "~> 5.4" + version = "8.1.0" - dataset_id = local.config_bigquery.dataset.aggregated_predictions.name - dataset_name = local.config_bigquery.dataset.aggregated_predictions.friendly_name - description = local.config_bigquery.dataset.aggregated_predictions.description - project_id = local.config_bigquery.dataset.aggregated_predictions.project_id - location = local.config_bigquery.dataset.aggregated_predictions.location + dataset_id = local.config_bigquery.dataset.aggregated_predictions.name + dataset_name = local.config_bigquery.dataset.aggregated_predictions.friendly_name + description = local.config_bigquery.dataset.aggregated_predictions.description + project_id = local.config_bigquery.dataset.aggregated_predictions.project_id + location = local.config_bigquery.dataset.aggregated_predictions.location # The delete_contents_on_destroy attribute specifies whether the contents of the dataset should be deleted when the dataset is destroyed. # In this case, the delete_contents_on_destroy attribute is set to true, which means that the contents of the dataset will be deleted when the dataset is destroyed. delete_contents_on_destroy = true @@ -250,7 +250,7 @@ module "aggregated_predictions" { # The tables attribute is used to configure the BigQuery table within the dataset tables = [ { - table_id = "latest" + table_id = "latest" # The schema of the table, defined in a JSON file. schema = file("../../sql/schema/table/aggregated_predictions_latest.json") time_partitioning = null, @@ -291,7 +291,7 @@ locals { module "gemini_insights" { source = "terraform-google-modules/bigquery/google" - version = "~> 5.4" + version = "8.1.0" dataset_id = local.config_bigquery.dataset.gemini_insights.name dataset_name = local.config_bigquery.dataset.gemini_insights.friendly_name @@ -303,23 +303,23 @@ module "gemini_insights" { delete_contents_on_destroy = true dataset_labels = { - version = "prod", - dataset_id = local.config_bigquery.dataset.gemini_insights.name + version = "prod", + dataset_id = local.config_bigquery.dataset.gemini_insights.name } tables = [for table_id in local.gemini_insights_tables : - { - table_id = table_id - schema = file("../../sql/schema/table/${table_id}.json") - # The max_time_travel_hours attribute specifies the maximum number of hours that data in the dataset can be accessed using time travel queries. - # In this case, the maximum time travel hours is set to the value of the local file config.yaml section bigquery.dataset.gemini_insights.max_time_travel_hours configuration. - max_time_travel_hours = local.config_bigquery.dataset.gemini_insights.max_time_travel_hours - deletion_protection = false - time_partitioning = null, - range_partitioning = null, - expiration_time = null, - clustering = [], - labels = {}, + { + table_id = table_id + schema = file("../../sql/schema/table/${table_id}.json") + # The max_time_travel_hours attribute specifies the maximum number of hours that data in the dataset can be accessed using time travel queries. + # In this case, the maximum time travel hours is set to the value of the local file config.yaml section bigquery.dataset.gemini_insights.max_time_travel_hours configuration. + max_time_travel_hours = local.config_bigquery.dataset.gemini_insights.max_time_travel_hours + deletion_protection = false + time_partitioning = null, + range_partitioning = null, + expiration_time = null, + clustering = [], + labels = {}, }] } @@ -347,4 +347,4 @@ resource "null_resource" "check_gemini_insights_dataset_exists" { depends_on = [ module.gemini_insights.google_bigquery_dataset ] -} \ No newline at end of file +} diff --git a/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf b/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf index d34ee0e2..96412569 100644 --- a/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf +++ b/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf @@ -54,13 +54,13 @@ data "local_file" "aggregated_value_based_bidding_training_preparation_file" { # The procedure is typically invoked before running the Aggregated Value Based Bidding model to ensure that the input data # is in the correct format and contains the necessary features for training. resource "google_bigquery_routine" "aggregated_value_based_bidding_training_preparation" { - project = null_resource.check_bigquery_api.id != "" ? local.aggregated_vbb_project_id : local.feature_store_project_id - dataset_id = module.aggregated_vbb.bigquery_dataset.dataset_id - routine_id = "aggregated_value_based_bidding_training_preparation" - routine_type = "PROCEDURE" - language = "SQL" + project = null_resource.check_bigquery_api.id != "" ? local.aggregated_vbb_project_id : local.feature_store_project_id + dataset_id = module.aggregated_vbb.bigquery_dataset.dataset_id + routine_id = "aggregated_value_based_bidding_training_preparation" + routine_type = "PROCEDURE" + language = "SQL" definition_body = data.local_file.aggregated_value_based_bidding_training_preparation_file.content - description = "Procedure that prepares features for Aggregated VBB model training." + description = "Procedure that prepares features for Aggregated VBB model training." } @@ -78,13 +78,13 @@ data "local_file" "aggregated_value_based_bidding_explanation_preparation_file" # The procedure is typically invoked before running the Aggregated Value Based Bidding model to ensure that the input data # is in the correct format and contains the necessary features for explanation. resource "google_bigquery_routine" "aggregated_value_based_bidding_explanation_preparation" { - project = null_resource.check_bigquery_api.id != "" ? local.aggregated_vbb_project_id : local.feature_store_project_id - dataset_id = module.aggregated_vbb.bigquery_dataset.dataset_id - routine_id = "aggregated_value_based_bidding_explanation_preparation" - routine_type = "PROCEDURE" - language = "SQL" + project = null_resource.check_bigquery_api.id != "" ? local.aggregated_vbb_project_id : local.feature_store_project_id + dataset_id = module.aggregated_vbb.bigquery_dataset.dataset_id + routine_id = "aggregated_value_based_bidding_explanation_preparation" + routine_type = "PROCEDURE" + language = "SQL" definition_body = data.local_file.aggregated_value_based_bidding_explanation_preparation_file.content - description = "Procedure that prepares features for Aggregated VBB model explanation." + description = "Procedure that prepares features for Aggregated VBB model explanation." } # This resource reads the contents of a local SQL file named auto_audience_segmentation_inference_preparation.sql and @@ -1242,11 +1242,11 @@ data "local_file" "invoke_aggregated_value_based_bidding_training_preparation_fi # Terraform resource for invoking the bigquery stored procedure resource "google_bigquery_routine" "invoke_aggregated_value_based_bidding_training_preparation" { - project = null_resource.check_bigquery_api.id != "" ? local.aggregated_vbb_project_id : local.feature_store_project_id - dataset_id = module.aggregated_vbb.bigquery_dataset.dataset_id - routine_id = "invoke_aggregated_value_based_bidding_training_preparation" - routine_type = "PROCEDURE" - language = "SQL" + project = null_resource.check_bigquery_api.id != "" ? local.aggregated_vbb_project_id : local.feature_store_project_id + dataset_id = module.aggregated_vbb.bigquery_dataset.dataset_id + routine_id = "invoke_aggregated_value_based_bidding_training_preparation" + routine_type = "PROCEDURE" + language = "SQL" definition_body = data.local_file.invoke_aggregated_value_based_bidding_training_preparation_file.content } @@ -1257,11 +1257,11 @@ data "local_file" "invoke_aggregated_value_based_bidding_explanation_preparation # Terraform resource for invoking the bigquery stored procedure resource "google_bigquery_routine" "invoke_aggregated_value_based_bidding_explanation_preparation" { - project = null_resource.check_bigquery_api.id != "" ? local.aggregated_vbb_project_id : local.feature_store_project_id - dataset_id = module.aggregated_vbb.bigquery_dataset.dataset_id - routine_id = "invoke_aggregated_value_based_bidding_explanation_preparation" - routine_type = "PROCEDURE" - language = "SQL" + project = null_resource.check_bigquery_api.id != "" ? local.aggregated_vbb_project_id : local.feature_store_project_id + dataset_id = module.aggregated_vbb.bigquery_dataset.dataset_id + routine_id = "invoke_aggregated_value_based_bidding_explanation_preparation" + routine_type = "PROCEDURE" + language = "SQL" definition_body = data.local_file.invoke_aggregated_value_based_bidding_explanation_preparation_file.content } @@ -1465,12 +1465,12 @@ data "local_file" "create_gemini_model_file" { resource "null_resource" "create_gemini_model" { triggers = { vertex_ai_connection_exists = google_bigquery_connection.vertex_ai_connection.id, - gemini_dataset_exists = module.gemini_insights.bigquery_dataset.id, + gemini_dataset_exists = module.gemini_insights.bigquery_dataset.id, check_gemini_dataset_listed = null_resource.check_gemini_insights_dataset_exists.id } provisioner "local-exec" { - command = <<-EOT + command = <<-EOT ${local.poetry_run_alias} bq query --use_legacy_sql=false --max_rows=100 --maximum_bytes_billed=10000000 < ${data.local_file.create_gemini_model_file.filename} EOT } @@ -1486,7 +1486,7 @@ resource "null_resource" "create_gemini_model" { resource "null_resource" "check_gemini_model_exists" { triggers = { vertex_ai_connection_exists = google_bigquery_connection.vertex_ai_connection.id - gemini_model_created = null_resource.create_gemini_model.id + gemini_model_created = null_resource.create_gemini_model.id } provisioner "local-exec" { @@ -1530,4 +1530,4 @@ resource "google_bigquery_routine" "invoke_user_behaviour_revenue_insights" { null_resource.check_gemini_model_exists, null_resource.create_gemini_model ] -} \ No newline at end of file +} diff --git a/infrastructure/terraform/modules/feature-store/bigquery-tables.tf b/infrastructure/terraform/modules/feature-store/bigquery-tables.tf index c968bda2..891a4b3e 100644 --- a/infrastructure/terraform/modules/feature-store/bigquery-tables.tf +++ b/infrastructure/terraform/modules/feature-store/bigquery-tables.tf @@ -15,10 +15,10 @@ # This resource creates a BigQuery table named audience_segmentation_inference_preparation # in the dataset specified by google_bigquery_dataset.audience_segmentation.dataset_id. resource "google_bigquery_table" "audience_segmentation_inference_preparation" { - project = google_bigquery_dataset.audience_segmentation.project - dataset_id = google_bigquery_dataset.audience_segmentation.dataset_id - table_id = local.config_bigquery.table.audience_segmentation_inference_preparation.table_name - description = local.config_bigquery.table.audience_segmentation_inference_preparation.table_description + project = google_bigquery_dataset.audience_segmentation.project + dataset_id = google_bigquery_dataset.audience_segmentation.dataset_id + table_id = local.config_bigquery.table.audience_segmentation_inference_preparation.table_name + description = local.config_bigquery.table.audience_segmentation_inference_preparation.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -34,10 +34,10 @@ resource "google_bigquery_table" "audience_segmentation_inference_preparation" { # This resource creates a BigQuery table named customer_lifetime_value_inference_preparation # in the dataset specified by google_bigquery_dataset.customer_lifetime_value.dataset_id. resource "google_bigquery_table" "customer_lifetime_value_inference_preparation" { - project = google_bigquery_dataset.customer_lifetime_value.project - dataset_id = google_bigquery_dataset.customer_lifetime_value.dataset_id - table_id = local.config_bigquery.table.customer_lifetime_value_inference_preparation.table_name - description = local.config_bigquery.table.customer_lifetime_value_inference_preparation.table_description + project = google_bigquery_dataset.customer_lifetime_value.project + dataset_id = google_bigquery_dataset.customer_lifetime_value.dataset_id + table_id = local.config_bigquery.table.customer_lifetime_value_inference_preparation.table_name + description = local.config_bigquery.table.customer_lifetime_value_inference_preparation.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -53,10 +53,10 @@ resource "google_bigquery_table" "customer_lifetime_value_inference_preparation" # This resource creates a BigQuery table named customer_lifetime_value_label # in the dataset specified by google_bigquery_dataset.feature_store.dataset_id. resource "google_bigquery_table" "customer_lifetime_value_label" { - project = google_bigquery_dataset.feature_store.project - dataset_id = google_bigquery_dataset.feature_store.dataset_id - table_id = local.config_bigquery.table.customer_lifetime_value_label.table_name - description = local.config_bigquery.table.customer_lifetime_value_label.table_description + project = google_bigquery_dataset.feature_store.project + dataset_id = google_bigquery_dataset.feature_store.dataset_id + table_id = local.config_bigquery.table.customer_lifetime_value_label.table_name + description = local.config_bigquery.table.customer_lifetime_value_label.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -79,10 +79,10 @@ resource "google_bigquery_table" "customer_lifetime_value_label" { # This resource creates a BigQuery table named purchase_propensity_inference_preparation # in the dataset specified by google_bigquery_dataset.purchase_propensity.dataset_id. resource "google_bigquery_table" "purchase_propensity_inference_preparation" { - project = google_bigquery_dataset.purchase_propensity.project - dataset_id = google_bigquery_dataset.purchase_propensity.dataset_id - table_id = local.config_bigquery.table.purchase_propensity_inference_preparation.table_name - description = local.config_bigquery.table.purchase_propensity_inference_preparation.table_description + project = google_bigquery_dataset.purchase_propensity.project + dataset_id = google_bigquery_dataset.purchase_propensity.dataset_id + table_id = local.config_bigquery.table.purchase_propensity_inference_preparation.table_name + description = local.config_bigquery.table.purchase_propensity_inference_preparation.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -97,10 +97,10 @@ resource "google_bigquery_table" "purchase_propensity_inference_preparation" { # This resource creates a BigQuery table named churn_propensity_inference_preparation # in the dataset specified by google_bigquery_dataset.churn_propensity.dataset_id. resource "google_bigquery_table" "churn_propensity_inference_preparation" { - project = google_bigquery_dataset.churn_propensity.project - dataset_id = google_bigquery_dataset.churn_propensity.dataset_id - table_id = local.config_bigquery.table.churn_propensity_inference_preparation.table_name - description = local.config_bigquery.table.churn_propensity_inference_preparation.table_description + project = google_bigquery_dataset.churn_propensity.project + dataset_id = google_bigquery_dataset.churn_propensity.dataset_id + table_id = local.config_bigquery.table.churn_propensity_inference_preparation.table_name + description = local.config_bigquery.table.churn_propensity_inference_preparation.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -115,10 +115,10 @@ resource "google_bigquery_table" "churn_propensity_inference_preparation" { # This resource creates a BigQuery table named purchase_propensity_label # in the dataset specified by google_bigquery_dataset.feature_store.dataset_id. resource "google_bigquery_table" "purchase_propensity_label" { - project = google_bigquery_dataset.feature_store.project - dataset_id = google_bigquery_dataset.feature_store.dataset_id - table_id = local.config_bigquery.table.purchase_propensity_label.table_name - description = local.config_bigquery.table.purchase_propensity_label.table_description + project = google_bigquery_dataset.feature_store.project + dataset_id = google_bigquery_dataset.feature_store.dataset_id + table_id = local.config_bigquery.table.purchase_propensity_label.table_name + description = local.config_bigquery.table.purchase_propensity_label.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -140,10 +140,10 @@ resource "google_bigquery_table" "purchase_propensity_label" { # This resource creates a BigQuery table named churn_propensity_label # in the dataset specified by google_bigquery_dataset.feature_store.dataset_id. resource "google_bigquery_table" "churn_propensity_label" { - project = google_bigquery_dataset.feature_store.project - dataset_id = google_bigquery_dataset.feature_store.dataset_id - table_id = local.config_bigquery.table.churn_propensity_label.table_name - description = local.config_bigquery.table.churn_propensity_label.table_description + project = google_bigquery_dataset.feature_store.project + dataset_id = google_bigquery_dataset.feature_store.dataset_id + table_id = local.config_bigquery.table.churn_propensity_label.table_name + description = local.config_bigquery.table.churn_propensity_label.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -165,10 +165,10 @@ resource "google_bigquery_table" "churn_propensity_label" { # This resource creates a BigQuery table named user_dimensions # in the dataset specified by google_bigquery_dataset.feature_store.dataset_id. resource "google_bigquery_table" "user_dimensions" { - project = google_bigquery_dataset.feature_store.project - dataset_id = google_bigquery_dataset.feature_store.dataset_id - table_id = local.config_bigquery.table.user_dimensions.table_name - description = local.config_bigquery.table.user_dimensions.table_description + project = google_bigquery_dataset.feature_store.project + dataset_id = google_bigquery_dataset.feature_store.dataset_id + table_id = local.config_bigquery.table.user_dimensions.table_name + description = local.config_bigquery.table.user_dimensions.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -190,10 +190,10 @@ resource "google_bigquery_table" "user_dimensions" { # This resource creates a BigQuery table named user_lifetime_dimensions # in the dataset specified by google_bigquery_dataset.feature_store.dataset_id. resource "google_bigquery_table" "user_lifetime_dimensions" { - project = google_bigquery_dataset.feature_store.project - dataset_id = google_bigquery_dataset.feature_store.dataset_id - table_id = local.config_bigquery.table.user_lifetime_dimensions.table_name - description = local.config_bigquery.table.user_lifetime_dimensions.table_description + project = google_bigquery_dataset.feature_store.project + dataset_id = google_bigquery_dataset.feature_store.dataset_id + table_id = local.config_bigquery.table.user_lifetime_dimensions.table_name + description = local.config_bigquery.table.user_lifetime_dimensions.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -215,10 +215,10 @@ resource "google_bigquery_table" "user_lifetime_dimensions" { # This resource creates a BigQuery table named user_lookback_metrics # in the dataset specified by google_bigquery_dataset.feature_store.dataset_id. resource "google_bigquery_table" "user_lookback_metrics" { - project = google_bigquery_dataset.feature_store.project - dataset_id = google_bigquery_dataset.feature_store.dataset_id - table_id = local.config_bigquery.table.user_lookback_metrics.table_name - description = local.config_bigquery.table.user_lookback_metrics.table_description + project = google_bigquery_dataset.feature_store.project + dataset_id = google_bigquery_dataset.feature_store.dataset_id + table_id = local.config_bigquery.table.user_lookback_metrics.table_name + description = local.config_bigquery.table.user_lookback_metrics.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -240,10 +240,10 @@ resource "google_bigquery_table" "user_lookback_metrics" { # This resource creates a BigQuery table named user_rolling_window_lifetime_metrics # in the dataset specified by google_bigquery_dataset.feature_store.dataset_id. resource "google_bigquery_table" "user_rolling_window_lifetime_metrics" { - project = google_bigquery_dataset.feature_store.project - dataset_id = google_bigquery_dataset.feature_store.dataset_id - table_id = local.config_bigquery.table.user_rolling_window_lifetime_metrics.table_name - description = local.config_bigquery.table.user_rolling_window_lifetime_metrics.table_description + project = google_bigquery_dataset.feature_store.project + dataset_id = google_bigquery_dataset.feature_store.dataset_id + table_id = local.config_bigquery.table.user_rolling_window_lifetime_metrics.table_name + description = local.config_bigquery.table.user_rolling_window_lifetime_metrics.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -265,10 +265,10 @@ resource "google_bigquery_table" "user_rolling_window_lifetime_metrics" { # This resource creates a BigQuery table named user_rolling_window_metrics # in the dataset specified by google_bigquery_dataset.feature_store.dataset_id. resource "google_bigquery_table" "user_rolling_window_metrics" { - project = google_bigquery_dataset.feature_store.project - dataset_id = google_bigquery_dataset.feature_store.dataset_id - table_id = local.config_bigquery.table.user_rolling_window_metrics.table_name - description = local.config_bigquery.table.user_rolling_window_metrics.table_description + project = google_bigquery_dataset.feature_store.project + dataset_id = google_bigquery_dataset.feature_store.dataset_id + table_id = local.config_bigquery.table.user_rolling_window_metrics.table_name + description = local.config_bigquery.table.user_rolling_window_metrics.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -290,10 +290,10 @@ resource "google_bigquery_table" "user_rolling_window_metrics" { # This resource creates a BigQuery table named user_scoped_lifetime_metrics # in the dataset specified by google_bigquery_dataset.feature_store.dataset_id. resource "google_bigquery_table" "user_scoped_lifetime_metrics" { - project = google_bigquery_dataset.feature_store.project - dataset_id = google_bigquery_dataset.feature_store.dataset_id - table_id = local.config_bigquery.table.user_scoped_lifetime_metrics.table_name - description = local.config_bigquery.table.user_scoped_lifetime_metrics.table_description + project = google_bigquery_dataset.feature_store.project + dataset_id = google_bigquery_dataset.feature_store.dataset_id + table_id = local.config_bigquery.table.user_scoped_lifetime_metrics.table_name + description = local.config_bigquery.table.user_scoped_lifetime_metrics.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -315,10 +315,10 @@ resource "google_bigquery_table" "user_scoped_lifetime_metrics" { # This resource creates a BigQuery table named user_scoped_metrics # in the dataset specified by google_bigquery_dataset.feature_store.dataset_id. resource "google_bigquery_table" "user_scoped_metrics" { - project = google_bigquery_dataset.feature_store.project - dataset_id = google_bigquery_dataset.feature_store.dataset_id - table_id = local.config_bigquery.table.user_scoped_metrics.table_name - description = local.config_bigquery.table.user_scoped_metrics.table_description + project = google_bigquery_dataset.feature_store.project + dataset_id = google_bigquery_dataset.feature_store.dataset_id + table_id = local.config_bigquery.table.user_scoped_metrics.table_name + description = local.config_bigquery.table.user_scoped_metrics.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -340,10 +340,10 @@ resource "google_bigquery_table" "user_scoped_metrics" { # This resource creates a BigQuery table named user_scoped_segmentation_metrics # in the dataset specified by google_bigquery_dataset.feature_store.dataset_id. resource "google_bigquery_table" "user_scoped_segmentation_metrics" { - project = google_bigquery_dataset.feature_store.project - dataset_id = google_bigquery_dataset.feature_store.dataset_id - table_id = local.config_bigquery.table.user_scoped_segmentation_metrics.table_name - description = local.config_bigquery.table.user_scoped_segmentation_metrics.table_description + project = google_bigquery_dataset.feature_store.project + dataset_id = google_bigquery_dataset.feature_store.dataset_id + table_id = local.config_bigquery.table.user_scoped_segmentation_metrics.table_name + description = local.config_bigquery.table.user_scoped_segmentation_metrics.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -365,10 +365,10 @@ resource "google_bigquery_table" "user_scoped_segmentation_metrics" { # This resource creates a BigQuery table named user_segmentation_dimensions # in the dataset specified by google_bigquery_dataset.feature_store.dataset_id. resource "google_bigquery_table" "user_segmentation_dimensions" { - project = google_bigquery_dataset.feature_store.project - dataset_id = google_bigquery_dataset.feature_store.dataset_id - table_id = local.config_bigquery.table.user_segmentation_dimensions.table_name - description = local.config_bigquery.table.user_segmentation_dimensions.table_description + project = google_bigquery_dataset.feature_store.project + dataset_id = google_bigquery_dataset.feature_store.dataset_id + table_id = local.config_bigquery.table.user_segmentation_dimensions.table_name + description = local.config_bigquery.table.user_segmentation_dimensions.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false @@ -390,10 +390,10 @@ resource "google_bigquery_table" "user_segmentation_dimensions" { # This resource creates a BigQuery table named user_session_event_aggregated_metrics # in the dataset specified by google_bigquery_dataset.feature_store.dataset_id. resource "google_bigquery_table" "user_session_event_aggregated_metrics" { - project = google_bigquery_dataset.feature_store.project - dataset_id = google_bigquery_dataset.feature_store.dataset_id - table_id = local.config_bigquery.table.user_session_event_aggregated_metrics.table_name - description = local.config_bigquery.table.user_session_event_aggregated_metrics.table_description + project = google_bigquery_dataset.feature_store.project + dataset_id = google_bigquery_dataset.feature_store.dataset_id + table_id = local.config_bigquery.table.user_session_event_aggregated_metrics.table_name + description = local.config_bigquery.table.user_session_event_aggregated_metrics.table_description # The deletion_protection attribute specifies whether the table should be protected from deletion. In this case, it's set to false, which means that the table can be deleted. deletion_protection = false diff --git a/infrastructure/terraform/modules/feature-store/main.tf b/infrastructure/terraform/modules/feature-store/main.tf index a17662bb..25b6830e 100644 --- a/infrastructure/terraform/modules/feature-store/main.tf +++ b/infrastructure/terraform/modules/feature-store/main.tf @@ -35,7 +35,7 @@ locals { module "project_services" { source = "terraform-google-modules/project-factory/google//modules/project_services" - version = "14.1.0" + version = "17.0.0" disable_dependent_services = true disable_services_on_destroy = false @@ -113,10 +113,10 @@ resource "null_resource" "check_aiplatform_api" { ## Note: The cloud resource nested object has only one output only field - serviceAccountId. resource "google_bigquery_connection" "vertex_ai_connection" { connection_id = "vertex_ai" - project = null_resource.check_aiplatform_api.id != "" ? module.project_services.project_id : local.feature_store_project_id - location = local.config_bigquery.region + project = null_resource.check_aiplatform_api.id != "" ? module.project_services.project_id : local.feature_store_project_id + location = local.config_bigquery.region cloud_resource {} -} +} # This resource binds the service account to the required roles @@ -125,8 +125,8 @@ resource "google_project_iam_member" "vertex_ai_connection_sa_roles" { module.project_services, null_resource.check_aiplatform_api, google_bigquery_connection.vertex_ai_connection - ] - + ] + project = null_resource.check_aiplatform_api.id != "" ? module.project_services.project_id : local.feature_store_project_id member = "serviceAccount:${google_bigquery_connection.vertex_ai_connection.cloud_resource[0].service_account_id}" diff --git a/infrastructure/terraform/modules/feature-store/versions.tf b/infrastructure/terraform/modules/feature-store/versions.tf index 5a896e28..29fe3151 100644 --- a/infrastructure/terraform/modules/feature-store/versions.tf +++ b/infrastructure/terraform/modules/feature-store/versions.tf @@ -20,7 +20,12 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 3.43.0, >= 3.53.0, >= 3.63.0, >= 4.83.0, < 5.0.0, < 6.0.0" + version = "5.44.1" + } + + google-beta = { + source = "hashicorp/google-beta" + version = "5.44.1" } } diff --git a/infrastructure/terraform/modules/monitor/main.tf b/infrastructure/terraform/modules/monitor/main.tf index 21252202..ae94c891 100644 --- a/infrastructure/terraform/modules/monitor/main.tf +++ b/infrastructure/terraform/modules/monitor/main.tf @@ -32,7 +32,7 @@ locals { module "project_services" { source = "terraform-google-modules/project-factory/google//modules/project_services" - version = "14.1.0" + version = "17.0.0" disable_dependent_services = false disable_services_on_destroy = false @@ -72,7 +72,7 @@ resource "null_resource" "check_bigquery_api" { module "dashboard_bigquery" { source = "terraform-google-modules/bigquery/google" - version = "~> 5.4" + version = "8.1.0" dataset_id = local.dashboard_dataset_name dataset_name = local.dashboard_dataset_name @@ -95,7 +95,7 @@ module "dashboard_bigquery" { module "load_bucket" { source = "terraform-google-modules/cloud-storage/google//modules/simple_bucket" - version = "~> 3.4.1" + version = "6.1.0" project_id = module.project_services.project_id name = "maj-monitor-${module.project_services.project_id}" location = var.location @@ -163,7 +163,7 @@ locals { module "log_export_bigquery" { source = "terraform-google-modules/bigquery/google" - version = "~> 5.4" + version = "8.1.0" dataset_id = local.log_dataset_name dataset_name = local.log_dataset_name diff --git a/infrastructure/terraform/modules/monitor/versions.tf b/infrastructure/terraform/modules/monitor/versions.tf index 5a896e28..29fe3151 100644 --- a/infrastructure/terraform/modules/monitor/versions.tf +++ b/infrastructure/terraform/modules/monitor/versions.tf @@ -20,7 +20,12 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 3.43.0, >= 3.53.0, >= 3.63.0, >= 4.83.0, < 5.0.0, < 6.0.0" + version = "5.44.1" + } + + google-beta = { + source = "hashicorp/google-beta" + version = "5.44.1" } } diff --git a/infrastructure/terraform/modules/pipelines/main.tf b/infrastructure/terraform/modules/pipelines/main.tf index 05d4969b..40faa747 100644 --- a/infrastructure/terraform/modules/pipelines/main.tf +++ b/infrastructure/terraform/modules/pipelines/main.tf @@ -35,7 +35,7 @@ locals { module "project_services" { source = "terraform-google-modules/project-factory/google//modules/project_services" - version = "14.1.0" + version = "17.0.0" disable_dependent_services = true disable_services_on_destroy = false @@ -159,4 +159,4 @@ resource "null_resource" "check_artifactregistry_api" { depends_on = [ module.project_services ] -} \ No newline at end of file +} diff --git a/infrastructure/terraform/modules/pipelines/pipelines.tf b/infrastructure/terraform/modules/pipelines/pipelines.tf index 1c36ef62..35ee5c34 100644 --- a/infrastructure/terraform/modules/pipelines/pipelines.tf +++ b/infrastructure/terraform/modules/pipelines/pipelines.tf @@ -53,8 +53,8 @@ resource "google_project_iam_member" "pipelines_sa_roles" { module.project_services, null_resource.check_aiplatform_api, null_resource.wait_for_vertex_pipelines_sa_creation - ] - + ] + project = null_resource.check_aiplatform_api.id != "" ? module.project_services.project_id : local.pipeline_vars.project_id member = "serviceAccount:${google_service_account.service_account.email}" @@ -79,8 +79,8 @@ resource "google_project_iam_member" "pipelines_sa_mds_project_roles" { module.project_services, null_resource.check_aiplatform_api, null_resource.wait_for_vertex_pipelines_sa_creation - ] - + ] + project = null_resource.check_bigquery_api.id != "" ? module.project_services.project_id : local.pipeline_vars.project_id member = "serviceAccount:${google_service_account.service_account.email}" @@ -130,8 +130,8 @@ resource "google_project_iam_member" "dataflow_worker_sa_roles" { module.project_services, null_resource.check_dataflow_api, null_resource.wait_for_dataflow_worker_sa_creation - ] - + ] + project = null_resource.check_dataflow_api.id != "" ? module.project_services.project_id : local.pipeline_vars.project_id member = "serviceAccount:${google_service_account.dataflow_worker_service_account.email}" @@ -151,8 +151,8 @@ resource "google_service_account_iam_member" "dataflow_sa_iam" { module.project_services, null_resource.check_dataflow_api, null_resource.wait_for_dataflow_worker_sa_creation - ] - + ] + service_account_id = "projects/${module.project_services.project_id}/serviceAccounts/${google_service_account.dataflow_worker_service_account.email}" role = "roles/iam.serviceAccountUser" member = "serviceAccount:${google_service_account.service_account.email}" @@ -167,7 +167,7 @@ resource "google_storage_bucket" "pipelines_bucket" { uniform_bucket_level_access = true # The force_destroy attribute specifies whether the bucket should be forcibly destroyed # even if it contains objects. In this case, it's set to false, which means that the bucket will not be destroyed if it contains objects. - force_destroy = false + force_destroy = false # The lifecycle block allows you to configure the lifecycle of the bucket. # In this case, the ignore_changes attribute is set to all, which means that Terraform @@ -187,7 +187,7 @@ resource "google_storage_bucket" "custom_model_bucket" { uniform_bucket_level_access = true # The force_destroy attribute specifies whether the bucket should be forcibly destroyed # even if it contains objects. In this case, it's set to false, which means that the bucket will not be destroyed if it contains objects. - force_destroy = false + force_destroy = false # The lifecycle block allows you to configure the lifecycle of the bucket. # In this case, the ignore_changes attribute is set to all, which means that Terraform @@ -246,7 +246,7 @@ resource "google_artifact_registry_repository" "pipelines-repo" { repository_id = local.artifact_registry_vars.pipelines_repo.name description = "Pipelines Repository" # The format is kubeflow pipelines YAML files. - format = "KFP" + format = "KFP" # The lifecycle block of the google_artifact_registry_repository resource defines a precondition that # checks if the specified region is included in the vertex_pipelines_available_locations list. @@ -266,7 +266,7 @@ resource "google_artifact_registry_repository" "pipelines_docker_repo" { repository_id = local.artifact_registry_vars.pipelines_docker_repo.name description = "Docker Images Repository" # The format is Docker images. - format = "DOCKER" + format = "DOCKER" } locals { @@ -811,4 +811,4 @@ resource "null_resource" "compile_gemini_insights_pipelines" { EOT working_dir = self.triggers.working_dir } -} \ No newline at end of file +} diff --git a/infrastructure/terraform/modules/pipelines/versions.tf b/infrastructure/terraform/modules/pipelines/versions.tf index 5a896e28..29fe3151 100644 --- a/infrastructure/terraform/modules/pipelines/versions.tf +++ b/infrastructure/terraform/modules/pipelines/versions.tf @@ -20,7 +20,12 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 3.43.0, >= 3.53.0, >= 3.63.0, >= 4.83.0, < 5.0.0, < 6.0.0" + version = "5.44.1" + } + + google-beta = { + source = "hashicorp/google-beta" + version = "5.44.1" } } diff --git a/infrastructure/terraform/terraform-sample.tfvars b/infrastructure/terraform/terraform-sample.tfvars index d52b9d0f..3595a9d0 100644 --- a/infrastructure/terraform/terraform-sample.tfvars +++ b/infrastructure/terraform/terraform-sample.tfvars @@ -47,19 +47,19 @@ website_url = "Customer Website URL" # i.e. "https://shop.googlemerchandisestore #################### ACTIVATION VARIABLES ################################# -activation_project_id = "Project ID where activation resources will be created" +activation_project_id = "Project ID where activation resources will be created" #################### GA4 VARIABLES ################################# -ga4_property_id = "Google Analytics property id" -ga4_stream_id = "Google Analytics data stream id" -ga4_measurement_id = "Google Analytics measurement id" -ga4_measurement_secret = "Google Analytics measurement secret" +ga4_property_id = "Google Analytics property id" +ga4_stream_id = "Google Analytics data stream id" +ga4_measurement_id = "Google Analytics measurement id" +ga4_measurement_secret = "Google Analytics measurement secret" #################### GITHUB VARIABLES ################################# -project_owner_email = "Project owner email" -dataform_github_repo = "URL of the GitHub or GitLab repo which contains the Dataform scripts. Should start with https://" +project_owner_email = "Project owner email" +dataform_github_repo = "URL of the GitHub or GitLab repo which contains the Dataform scripts. Should start with https://" # Personal access tokens are intended to access GitHub resources on behalf of yourself. # Generate a github developer token for the repo above following this link: # https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic diff --git a/infrastructure/terraform/variables.tf b/infrastructure/terraform/variables.tf index 978b6480..f33c1a90 100644 --- a/infrastructure/terraform/variables.tf +++ b/infrastructure/terraform/variables.tf @@ -56,7 +56,7 @@ variable "project_owner_email" { } variable "dataform_github_repo" { - description = "Private Github repo for Dataform." + description = "Private GitHub repo for Dataform." type = string validation { condition = substr(var.dataform_github_repo, 0, 8) == "https://" @@ -65,7 +65,7 @@ variable "dataform_github_repo" { } variable "dataform_github_token" { - description = "Github token for Dataform repo." + description = "GitHub token for Dataform repo." type = string } @@ -147,8 +147,8 @@ variable "source_ga4_export_dataset" { variable "ga4_incremental_processing_days_back" { description = "Past number of days to process GA4 exported data" - type = string - default = "3" + type = string + default = "3" } variable "source_ads_export_data" { @@ -238,6 +238,6 @@ variable "feature_store_project_id" { variable "website_url" { description = "Website url to be provided to the auto segmentation model" - type = string - default = null + type = string + default = null } diff --git a/infrastructure/terraform/versions.tf b/infrastructure/terraform/versions.tf index 5a896e28..29fe3151 100644 --- a/infrastructure/terraform/versions.tf +++ b/infrastructure/terraform/versions.tf @@ -20,7 +20,12 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 3.43.0, >= 3.53.0, >= 3.63.0, >= 4.83.0, < 5.0.0, < 6.0.0" + version = "5.44.1" + } + + google-beta = { + source = "hashicorp/google-beta" + version = "5.44.1" } } From 272ffeb4886884fb6c9a6d5646cb2ce06b996535 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 4 Oct 2024 16:41:14 -0400 Subject: [PATCH 024/110] Fixing Secrets Issue on TF (#204) * predicting for only the users with traffic in the past 72h - purchase propensity * running inference only for users events in the past 72h * including 72h users for all models predictions * considering null values in TabWorkflow models * deleting unused pipfile * upgrading lib versions * implementing reporting preprocessing as a new pipeline * adding more code documentation * adding important information on the main README.md and DEVELOPMENT.md * adding schedule run name and more code documentation * implementing a new scheduler using the vertex ai sdk & adding user_id to procedures for consistency * adding more code documentation * adding code doc to the python custom component * adding more code documentation * fixing aggregated predictions query * removing unnecessary resources from deployment * Writing MDS guide * adding the MDS developer and troubleshooting documentation * fixing deployment for activation pipelines and gemini dataset * Update README.md * Update README.md * Update README.md * Update README.md * removing deprecated api * fixing purchase propensity pipelines names * adding extra condition for when there is not enough data for the window interval to be applied on backfill procedures * adding more instructions for post deployment and fixing issues when GA4 export was configured for less than 10 days * removing unnecessary comments * adding the number of past days to process in the variables files * adding comment about combining data from different ga4 export datasets to data store * fixing small issues with feature engineering and ml pipelines * fixing hyper parameter tuning for kmeans modeling * fixing optuna parameters * adding cloud shell image * fixing the list of all possible users in the propensity training preparation tables * additional guardrails for when there is not enough data * adding more documentation * adding more doc to feature store * add feature store documentation * adding ml pipelines docs * adding ml pipelines docs * adding more documentation * adding user agent client info * fixing scope of client info * fix * removing client_info from vertex components * fixing versioning of tf submodules * reconfiguring meta providers * fixing issue 187 * chore(deps): upgrade terraform providers and modules version * chore(deps): set the provider version * chore: formatting * fix: brand naming * fix: typo * fixing secrets issue --------- Co-authored-by: Carlos Timoteo Co-authored-by: Laurent Grangeau --- infrastructure/terraform/.terraform.lock.hcl | 11 +++++++++-- infrastructure/terraform/modules/data-store/main.tf | 2 +- .../terraform/modules/data-store/secretmanager.tf | 3 +-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/infrastructure/terraform/.terraform.lock.hcl b/infrastructure/terraform/.terraform.lock.hcl index 79205a2b..d4563d79 100644 --- a/infrastructure/terraform/.terraform.lock.hcl +++ b/infrastructure/terraform/.terraform.lock.hcl @@ -4,6 +4,7 @@ provider "registry.terraform.io/hashicorp/archive" { version = "2.6.0" hashes = [ + "h1:Ou6XKWvpo7IYgZnrFJs5MKzMqQMEYv8Z2iHSJ2mmnFw=", "h1:rYAubRk7UHC/fzYqFV/VHc+7VIY01ugCxauyTYCNf9E=", "zh:29273484f7423b7c5b3f5df34ccfc53e52bb5e3d7f46a81b65908e7a8fd69072", "zh:3cba58ec3aea5f301caf2acc31e184c55d994cc648126cac39c63ae509a14179", @@ -24,6 +25,7 @@ provider "registry.terraform.io/hashicorp/external" { version = "2.3.4" constraints = ">= 2.2.2" hashes = [ + "h1:U6W8rgrdmR2pZ2cicFoGOSQ4GXuIf/4EK7s0vTJN7is=", "h1:XWkRZOLKMjci9/JAtE8X8fWOt7A4u+9mgXSUjc4Wuyo=", "zh:037fd82cd86227359bc010672cd174235e2d337601d4686f526d0f53c87447cb", "zh:0ea1db63d6173d01f2fa8eb8989f0809a55135a0d8d424b08ba5dabad73095fa", @@ -42,8 +44,9 @@ provider "registry.terraform.io/hashicorp/external" { provider "registry.terraform.io/hashicorp/google" { version = "5.44.1" - constraints = ">= 3.43.0, >= 3.53.0, >= 4.83.0, >= 5.3.0, >= 5.22.0, < 6.0.0, < 7.0.0" + constraints = ">= 3.43.0, >= 3.53.0, >= 4.83.0, >= 5.3.0, >= 5.22.0, 5.44.1, < 6.0.0, < 7.0.0" hashes = [ + "h1:/98txhnLWIG/xGcjMLPeRSJgqqlmCjBAvA/1BPgM+J8=", "h1:CZ1vR4kLe+2PqoUwXhZROq2ufV60V92ObXAcl89HwZ8=", "zh:25d4c2926f0e26f0736eb7913114d487212bdddbb636024ebf3ce74b1baad64b", "zh:282da25af7332aba699f093ece72d6831dc96aba2c8ef1e1c461f47c03bd643b", @@ -62,9 +65,10 @@ provider "registry.terraform.io/hashicorp/google" { provider "registry.terraform.io/hashicorp/google-beta" { version = "5.44.1" - constraints = ">= 3.43.0, >= 4.83.0, < 6.0.0, < 7.0.0" + constraints = ">= 3.43.0, >= 4.83.0, 5.44.1, < 6.0.0, < 7.0.0" hashes = [ "h1:4pdEV8QZSbjR0rRdpq8aU7DR+DurLv6xF70eHzkugkA=", + "h1:vzVQ2iIYox+sjIBjR+lyaf102430U+MoBAayeC468Y4=", "zh:206f93a069dc6b28010e6f8e2f617d5a00b6dadf96291adf1ec88a2cfaa91ca8", "zh:296471c122824c8d6d3597ad40f2716afce11b37af53a4a26e66c3b2e0e26586", "zh:399244ebe27a60fa2cd78acb3911238eba926be8105d1caf06e604ca28ecfa12", @@ -84,6 +88,7 @@ provider "registry.terraform.io/hashicorp/local" { version = "2.5.2" hashes = [ "h1:JlMZD6nYqJ8sSrFfEAH0Vk/SL8WLZRmFaMUF9PJK5wM=", + "h1:p99F1AoV9z51aJ4EdItxz/vLwWIyhx/0Iw7L7sWSH1o=", "zh:136299545178ce281c56f36965bf91c35407c11897f7082b3b983d86cb79b511", "zh:3b4486858aa9cb8163378722b642c57c529b6c64bfbfc9461d940a84cd66ebea", "zh:4855ee628ead847741aa4f4fc9bed50cfdbf197f2912775dd9fe7bc43fa077c0", @@ -104,6 +109,7 @@ provider "registry.terraform.io/hashicorp/null" { constraints = ">= 2.1.0" hashes = [ "h1:+AnORRgFbRO6qqcfaQyeX80W0eX3VmjadjnUFUJTiXo=", + "h1:nKUqWEza6Lcv3xRlzeiRQrHtqvzX1BhIzjaOVXRYQXQ=", "zh:22d062e5278d872fe7aed834f5577ba0a5afe34a3bdac2b81f828d8d3e6706d2", "zh:23dead00493ad863729495dc212fd6c29b8293e707b055ce5ba21ee453ce552d", "zh:28299accf21763ca1ca144d8f660688d7c2ad0b105b7202554ca60b02a3856d3", @@ -124,6 +130,7 @@ provider "registry.terraform.io/hashicorp/random" { constraints = ">= 2.1.0" hashes = [ "h1:Fnaec9vA8sZ8BXVlN3Xn9Jz3zghSETIKg7ch8oXhxno=", + "h1:f6jXn4MCv67kgcofx9D49qx1ZEBv8oyvwKDMPBr0A24=", "zh:04ceb65210251339f07cd4611885d242cd4d0c7306e86dda9785396807c00451", "zh:448f56199f3e99ff75d5c0afacae867ee795e4dfda6cb5f8e3b2a72ec3583dd8", "zh:4b4c11ccfba7319e901df2dac836b1ae8f12185e37249e8d870ee10bb87a13fe", diff --git a/infrastructure/terraform/modules/data-store/main.tf b/infrastructure/terraform/modules/data-store/main.tf index e6e56b4d..5add3aab 100644 --- a/infrastructure/terraform/modules/data-store/main.tf +++ b/infrastructure/terraform/modules/data-store/main.tf @@ -21,7 +21,7 @@ data "google_project" "data_processing" { } data "google_secret_manager_secret" "github_secret_name" { - secret_id = google_secret_manager_secret.github-secret.name + secret_id = google_secret_manager_secret.github-secret.secret_id project = var.data_processing_project_id } diff --git a/infrastructure/terraform/modules/data-store/secretmanager.tf b/infrastructure/terraform/modules/data-store/secretmanager.tf index fb0d0ff3..5ead0be0 100644 --- a/infrastructure/terraform/modules/data-store/secretmanager.tf +++ b/infrastructure/terraform/modules/data-store/secretmanager.tf @@ -17,7 +17,6 @@ resource "google_secret_manager_secret" "github-secret" { project = null_resource.check_secretmanager_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id replication { - #automatic = true auto {} } @@ -28,7 +27,7 @@ resource "google_secret_manager_secret" "github-secret" { } resource "google_secret_manager_secret_version" "secret-version-github" { - secret = google_secret_manager_secret.github-secret.id + secret = google_secret_manager_secret.github-secret.secret_id secret_data = var.dataform_github_token #deletion_policy = "DISABLE" From c7af43882035c2fa020b93c1aa58a896112b8818 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 4 Oct 2024 18:31:37 -0400 Subject: [PATCH 025/110] Update main.tf --- infrastructure/terraform/modules/activation/main.tf | 1 - 1 file changed, 1 deletion(-) diff --git a/infrastructure/terraform/modules/activation/main.tf b/infrastructure/terraform/modules/activation/main.tf index 137b774e..d86abc5c 100644 --- a/infrastructure/terraform/modules/activation/main.tf +++ b/infrastructure/terraform/modules/activation/main.tf @@ -688,7 +688,6 @@ module "activation_pipeline_container" { module "activation_pipeline_template" { source = "terraform-google-modules/gcloud/google" version = "3.5.0" - additional_components = ["gsutil"] platform = "linux" create_cmd_body = "dataflow flex-template build --project=${module.project_services.project_id} \"gs://${module.pipeline_bucket.name}/dataflow/templates/${local.activation_container_image_id}.json\" --image \"${local.docker_repo_prefix}/${google_artifact_registry_repository.activation_repository.name}/${local.activation_container_name}:latest\" --sdk-language \"PYTHON\" --metadata-file \"${local.pipeline_source_dir}/metadata.json\"" From dc2fcbee32cf5b1e69ad0ccd6b2320197f30c6fb Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 4 Oct 2024 18:32:15 -0400 Subject: [PATCH 026/110] Update secretmanager.tf --- infrastructure/terraform/modules/data-store/secretmanager.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform/modules/data-store/secretmanager.tf b/infrastructure/terraform/modules/data-store/secretmanager.tf index 5ead0be0..33c71ed7 100644 --- a/infrastructure/terraform/modules/data-store/secretmanager.tf +++ b/infrastructure/terraform/modules/data-store/secretmanager.tf @@ -27,7 +27,7 @@ resource "google_secret_manager_secret" "github-secret" { } resource "google_secret_manager_secret_version" "secret-version-github" { - secret = google_secret_manager_secret.github-secret.secret_id + secret = google_secret_manager_secret.github-secret.id secret_data = var.dataform_github_token #deletion_policy = "DISABLE" From a7861c609277798398089c63fad58eacd49d8a73 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 4 Oct 2024 19:35:35 -0400 Subject: [PATCH 027/110] Update README.md --- infrastructure/terraform/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index bab5b61f..dc811da6 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -112,14 +112,14 @@ Step by step installation guide with [![Open in Cloud Shell](https://gstatic.com # Follow instructions on https://github.com/tfutils/tfenv # Now, install the recommended terraform version - tfenv install 1.5.7 - tfenv use 1.5.7 + tfenv install 1.9.7 + tfenv use 1.9.7 terraform --version ``` For instance, the output on MacOS should be like: ```shell - Terraform v1.5.7 + Terraform v1.9.7 on darwin_amd64 ``` From f9e08b43f509f40cc2ad67aa541c19474cd46071 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 7 Oct 2024 13:58:34 -0400 Subject: [PATCH 028/110] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4de911d1..bdd59454 100644 --- a/README.md +++ b/README.md @@ -40,14 +40,17 @@ These insights are used to serve as a basis to optimize paid media efforts and i | Customer Lifetime Value | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Customer Lifetime Value | [maj_cltv_180_30](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L23) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Purchase Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Purchase | [maj_purchase_propensity_30_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L28) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Churn Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Churn | [maj_churn_propensity_30_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L43) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | -| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | - | [Static Conversion Values](https://support.google.com/google-ads/answer/13064107?sjid=13060303839552593837-NA#zippy=%2Cset-a-conversion-value%2Cchange-a-conversion-value)

[Bid Adjustment](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) | +| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | - | [Static Conversion Values](https://support.google.com/google-ads/answer/13064107?sjid=13060303839552593837-NA#zippy=%2Cset-a-conversion-value%2Cchange-a-conversion-value)

[Bid Adjustment[1](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) [2](https://support.google.com/google-ads/answer/2732132?sjid=8368074830549837931-NA#zippy=%2Cremarketing-lists-for-search-ads-advanced) | ## Repository Structure The solution's source code is written in Terraform, Python, SQL, YAML and JSON; and it is organized into five main folders: * `config/`: This folder contains the configuration file for the solution. This file define the parameters and settings used by the various components of the solution. +* `docs/`: This folder contains the detailed architecture, design principles, deployment, basic operation and troubleshooting guides for all the solution components * `infrastructure/terraform/`: This folder contains the Terraform modules, variables and the installation guide to deploy the solution's infrastructure on GCP. * `infrastructure/terraform/modules/`: This folder contains the Terraform modules and their corresponding Terraform resources. These modules corresponds to the architectural components broken down in the next section. +* `notebooks/`: Contains python notebooks to be used in Workshop sessions. * `python/`: This folder contains most of the Python code. This code implements the activation application, which sends model predictions to Google Analytics 4; and the custom Vertex AI pipelines, its components and the base component docker image used for feature engineering, training, prediction, and explanation pipelines. It also implements the cloud function that triggers the activation application, and the Google Analytics Admin SDK code that creates the custom dimensions on the GA4 property. +* `scripts/`: Miscelaneous scripts to support installation and operation of the solution. * `sql/`: This folder contains the SQL code and table schemas specified in JSON files. This code implements the stored procedures used to transform and enrich the marketing data, as well as the queries used to invoke the stored procedures and retrieve the data for analysis. * `templates/`: This folder contains the templates for generating the Google Analytics 4 Measurement Protocol API payloads used to send model predictions to Google Analytics 4. From 09cbd1fb4a9b681a6e4bb7f5b695b7c7883f3c16 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 7 Oct 2024 13:59:23 -0400 Subject: [PATCH 029/110] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bdd59454..fcb0a252 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ These insights are used to serve as a basis to optimize paid media efforts and i | Customer Lifetime Value | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Customer Lifetime Value | [maj_cltv_180_30](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L23) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Purchase Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Purchase | [maj_purchase_propensity_30_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L28) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Churn Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Churn | [maj_churn_propensity_30_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L43) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | -| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | - | [Static Conversion Values](https://support.google.com/google-ads/answer/13064107?sjid=13060303839552593837-NA#zippy=%2Cset-a-conversion-value%2Cchange-a-conversion-value)

[Bid Adjustment[1](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) [2](https://support.google.com/google-ads/answer/2732132?sjid=8368074830549837931-NA#zippy=%2Cremarketing-lists-for-search-ads-advanced) | +| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | - | [Static Conversion Values](https://support.google.com/google-ads/answer/13064107?sjid=13060303839552593837-NA#zippy=%2Cset-a-conversion-value%2Cchange-a-conversion-value)

Bid Adjustment [1](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) [2](https://support.google.com/google-ads/answer/2732132?sjid=8368074830549837931-NA#zippy=%2Cremarketing-lists-for-search-ads-advanced) | ## Repository Structure The solution's source code is written in Terraform, Python, SQL, YAML and JSON; and it is organized into five main folders: From a2583d517a695c94d680665e60c0b341bb2d558e Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 7 Oct 2024 14:50:16 -0400 Subject: [PATCH 030/110] Update README.md --- infrastructure/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/README.md b/infrastructure/README.md index 1bd79d38..53bfb560 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -78,7 +78,7 @@ copy the SQL scripts from a companion GitHub repo before running the Terraform s cd .. rm -rf marketing-analytics-jumpstart-dataform ``` -6. Generate a GitHub personal access token. It will be used by Dataform to access the repository. For details and +6. Generate a [GitHub personal access token](https://cloud.google.com/dataform/docs/connect-repository#connect-https). It will be used by Dataform to access the repository. For details and additional guidance regarding token type, security and require permissions see [Dataform documentation](https://cloud.google.com/dataform/docs/connect-repository#create-secret). You don't need to create a Cloud Secret - it will be done by the Terraform scripts. You will need to provide the Git URL and the From 7ab97bd35c00fe41ca9fce8ffdfa40b1326bef7b Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 7 Oct 2024 15:00:43 -0400 Subject: [PATCH 031/110] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fcb0a252..a60db91f 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,10 @@ This high-level architecture demonstrates how Marketing Analytics Jumpstart inte ## Installation Permissions and Privileges - [ ] Google Analytics Property Editor or Owner - [ ] Google Ads Reader -- [ ] Project Owner for GCP Project +- [ ] Project Owner* for a Google Cloud Project - [ ] GitHub or GitLab account priviledges for repo creation and access token. [Details](https://cloud.google.com/dataform/docs/connect-repository) +**Note:** Project Owner for a Google Cloud Project is only required to speed up the deployment process. Consult this [guide]() for a more fine-grained permission list, not including the Owner role, to adhere to your company policies. ## Installation From 606a93f6d23ed846e512c406d2100dc3db664198 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 7 Oct 2024 15:12:44 -0400 Subject: [PATCH 032/110] Update README.md --- infrastructure/README.md | 68 +++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/infrastructure/README.md b/infrastructure/README.md index 53bfb560..bdeb4543 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -3,9 +3,38 @@ ## Overview Marketing Analytics Jumpstart consists of several components - marketing data store (MDS), feature store, ML pipelines, -the activation pipeline and dashboards. This document describes the sequencing of installing these components. +the activation pipeline and dashboards. This document describes the permission and data prerequisites for a successful installation. -## Prerequisites +## Permissions Prerequisites + +### Permissions to deploy infrastructure and access source data + +There are multiple ways to configure Google Cloud authentication for the Terraform installations. Terraform's Google +Provider [documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference) +lists all possible options on how the authentication can be done. This installation guide assumes that will be using the +Application Default Credentials. You can change this by, for example, creating a dedicated service account and +setting `GOOGLE_IMPERSONATE_SERVICE_ACCOUNT` environment variable before you run Terraform scripts. We will refer to the +identity which is used in the Terraform scripts (your email or the dedicated service account email) the "Terraform +principal" for brevity. + +The Terraform principal will need to be granted certain permissions in different projects: + +* the Owner role in all projects where the solution is to be installed. Required to install products related to the + solution. +* the BigQuery [Data Owner role](https://cloud.google.com/bigquery/docs/control-access-to-resources-iam#required_roles) + on the datasets containing the GA4 and Ads data exports. Required to grant data read access to + a service account which will be created by the Terraform scripts. Follow the + BigQuery [documentation](https://cloud.google.com/bigquery/docs/control-access-to-resources-iam#grant_access_to_a_dataset) + on how to grant this permission on a dataset level. + +### Google Analytics 4 Configurations and Permissions + +The activation application uses sensitive information from the Google Analytics property, such as Measurement ID and API Secret. These information is stored temporarily on environment variables to be exported manually by the user. + +* A [Measurement ID](https://support.google.com/analytics/answer/12270356?hl=en) and [API secret](https://support.google.com/analytics/answer/9814495?sjid=9902804247343448709-NA) collected from the Google Analytics UI. In this [article](https://support.google.com/analytics/answer/9814495?sjid=9902804247343448709-NA) you will find instructions on how to generate the API secret. +* Editor or Administrator role to the Google Analytics 4 account or property. In this [article](https://support.google.com/analytics/answer/9305587?hl=en#zippy=%2Cgoogle-analytics) you will find instructions on how to setup. + +## Data Prerequisites ### Marketing Analytics Data Sources @@ -33,26 +62,6 @@ access control is desired multiple projects can be used: to accelerate the query originated from the dashboard. -### Permissions to create infrastructure and access source data - -There are multiple ways to configure Google Cloud authentication for the Terraform installations. Terraform's Google -Provider [documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference) -lists all possible options on how the authentication can be done. This installation guide assumes that will be using the -Application Default Credentials. You can change this by, for example, creating a dedicated service account and -setting `GOOGLE_IMPERSONATE_SERVICE_ACCOUNT` environment variable before you run Terraform scripts. We will refer to the -identity which is used in the Terraform scripts (your email or the dedicated service account email) the "Terraform -principal" for brevity. - -The Terraform principal will need to be granted certain permissions in different projects: - -* the Owner role in all projects where the solution is to be installed. Required to install products related to the - solution. -* the BigQuery [Data Owner role](https://cloud.google.com/bigquery/docs/control-access-to-resources-iam#required_roles) - on the datasets containing the GA4 and Ads data exports. Required to grant data read access to - a service account which will be created by the Terraform scripts. Follow the - BigQuery [documentation](https://cloud.google.com/bigquery/docs/control-access-to-resources-iam#grant_access_to_a_dataset) - on how to grant this permission on a dataset level. - ### Dataform Git Repository MDS uses [Dataform](https://cloud.google.com/dataform) as the tool to run the data transformation. Dataform uses a @@ -84,20 +93,13 @@ copy the SQL scripts from a companion GitHub repo before running the Terraform s to create a Cloud Secret - it will be done by the Terraform scripts. You will need to provide the Git URL and the access token to the Terraform scripts using a Terraform variable. -### Google Analytics 4 Configurations and Permissions - -The activation application uses sensitive information from the Google Analytics property, such as Measurement ID and API Secret. These information is stored temporarily on environment variables to be exported manually by the user. - -* A [Measurement ID](https://support.google.com/analytics/answer/12270356?hl=en) and [API secret](https://support.google.com/analytics/answer/9814495?sjid=9902804247343448709-NA) collected from the Google Analytics UI. In this [article](https://support.google.com/analytics/answer/9814495?sjid=9902804247343448709-NA) you will find instructions on how to generate the API secret. -* Editor or Administrator role to the Google Analytics 4 account or property. In this [article](https://support.google.com/analytics/answer/9305587?hl=en#zippy=%2Cgoogle-analytics) you will find instructions on how to setup. - -## Installing the MDS, ML pipelines, the feature Store, and the activation pipeline +## Installing the Marketing Data Store, Machine Learning Pipelines, Feature Store, and the Activation Application -Once all the prerequisites are met you can install these components using Terraform scripts. +Once all the permissions and data prerequisites are met, you can install these components using Terraform scripts. Follow instructions in [terraform/README.md](terraform/README.md) -## Installing Dashboards +## Installing Dashboard -Looker Studio Dashboards can be installed by following instructions +Looker Studio Dashboard can be installed by following instructions in [../python/lookerstudio/README.md](../python/lookerstudio/README.md) From 27220d360d3c2120ced0d8f62400b236b2cf7fb8 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 7 Oct 2024 15:32:25 -0400 Subject: [PATCH 033/110] Update README.md --- README.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a60db91f..2b88b487 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,13 @@ Marketing Analytics Jumpstart is a terraform automated, quick-to-deploy, customi Customers are looking to drive revenue and increase media efficiency be identifying, predicting and targeting valuable users through the use of machine learning. However, marketers first have to solve the challenge of having a number of disparate data sources that prevent them from having a holistic view of customers. Marketers also often don't have the expertise and/or resources in their marketing departments to train, run, and activate ML models on paid channels. Without this solution that enables innovation through predictive analytics, marketers are missing opportunities to advance their marketing program and accelerate key goals and objectives (e.g. acquire new customers, improve customer retention, etc). +## Quick Installation ⏰ -## Benefits +Want to quickly install and use it? Follow this [installation notebook](notebooks/quick_installation.ipynb) and use Marketing Analytics Jumpstart in under 30 minutes. + +If that was just too fast, continue reading this document to learn more in details. + +## Benefits 🫴 After installing the solution users will get: * Scheduled ETL jobs for an extensible logical data model based on the Google Analytics 4 (GA4) and Google Ads (GAds) daily exports * Validated feature engineering SQL transformations from event-level data to user-level data for reporting and machine learning models training and prediction @@ -13,7 +18,7 @@ After installing the solution users will get: * Activation application that sends models prediction to GA4 via Measurement Protocol API -## Who can benefit from this solution? +## Who can benefit from this solution? 🙇‍♀️ This solution is intended for Marketing Technologist teams using GA4 and GAds products. It facilitates efforts to store, transform, analyze marketing data, and programmatically creates audiences segments in Google Ads to support conversion optimization and remarketing campaigns. | Role | User Journeys | Skillset | Can Deploy? | @@ -24,7 +29,7 @@ This solution is intended for Marketing Technologist teams using GA4 and GAds pr | IT/Data Engineer | Building and maintaining marketing data store transformation jobs. Developing and deploying custom marketing use cases reusing a consistent infrastructure. Integrating 1st party data and Google 3rd party data by extending the marketing data store. | Python, SQL, Google Cloud Platform, Data Engineering | Yes | -## Use Cases +## Use Cases 🖱️ This solution enables customer to plan and take action on their marketing campaigns by interpreting the insights provided by four common predictive use cases (purchase propensity, customer lifetime value, audience segmentation and aggregated value based bidding) and an operation dashboard that monitors Campaigns, Traffic, User Behavior and Models Performance, using the best of Google Cloud Data and AI products and practices. These insights are used to serve as a basis to optimize paid media efforts and investments by: @@ -42,7 +47,7 @@ These insights are used to serve as a basis to optimize paid media efforts and i | Churn Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Churn | [maj_churn_propensity_30_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L43) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | - | [Static Conversion Values](https://support.google.com/google-ads/answer/13064107?sjid=13060303839552593837-NA#zippy=%2Cset-a-conversion-value%2Cchange-a-conversion-value)

Bid Adjustment [1](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) [2](https://support.google.com/google-ads/answer/2732132?sjid=8368074830549837931-NA#zippy=%2Cremarketing-lists-for-search-ads-advanced) | -## Repository Structure +## Repository Structure 🏗️ The solution's source code is written in Terraform, Python, SQL, YAML and JSON; and it is organized into five main folders: * `config/`: This folder contains the configuration file for the solution. This file define the parameters and settings used by the various components of the solution. * `docs/`: This folder contains the detailed architecture, design principles, deployment, basic operation and troubleshooting guides for all the solution components @@ -57,7 +62,7 @@ The solution's source code is written in Terraform, Python, SQL, YAML and JSON; In addition to that, there is a `tasks.py` file which implements python invoke tests who hydrate values to the JINJA template files with the `.sqlx` extension located in the `sql/` folder that defines the DDL and DML statements for the BigQuery datasets, tables, procedures and queries. -## High Level Architecture +## High Level Architecture 📋 ![High Level Architecture](docs/images/reference_architecture.png) The provided architecture diagram depicts the high-level architecture of the Marketing Analytics Jumpstart solution. Let's break down the components: @@ -93,7 +98,7 @@ The provided architecture diagram depicts the high-level architecture of the Mar This high-level architecture demonstrates how Marketing Analytics Jumpstart integrates various Google Cloud services to provide a comprehensive solution for analyzing and activating your marketing data. -## Advantages +## Advantages 🔦 1. Easy to deploy: Deploy the resources and use cases that you need. 2. Cost Effective: Pay only for the cost of infrastructure in order to maintain the Data Store, Feature Store and ML Models. 3. Keep control of your data: This solution runs entirely in your environment and doesn’t transfer data out of your ownership or organization. @@ -117,7 +122,7 @@ This high-level architecture demonstrates how Marketing Analytics Jumpstart inte **Note:** Project Owner for a Google Cloud Project is only required to speed up the deployment process. Consult this [guide]() for a more fine-grained permission list, not including the Owner role, to adhere to your company policies. -## Installation +## Installation 👷‍♀️ To facilitate the installation, use this Step by Step Installation Video. @@ -132,7 +137,7 @@ Alternatively, follow the step by step installation guide with Google Cloud Shel **Note:** If you are working from a forked repository, be sure to update the `cloudshell_git_repo` parameter to the URL of your forked repository for the button link above. -## Contributing +## Contributing 🤝 We welcome all feedback and contributions! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for more information on how to publish your contributions. @@ -141,7 +146,7 @@ to publish your contributions. This project is licensed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0). -## Resources +## Resources 📚 This a list of public websites you can use to learn more about the Google Analytics 4, Google Ads, Google Cloud Products we used to build this solution. | Websites | Description | From ef2a309a871f23939a6dea27ba1dc6785f122a8e Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 7 Oct 2024 15:33:23 -0400 Subject: [PATCH 034/110] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2b88b487..a5594cad 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Customers are looking to drive revenue and increase media efficiency be identify ## Quick Installation ⏰ -Want to quickly install and use it? Follow this [installation notebook](notebooks/quick_installation.ipynb) and use Marketing Analytics Jumpstart in under 30 minutes. +Want to quickly install and use it? Follow this [installation notebook 📔](notebooks/quick_installation.ipynb) and use Marketing Analytics Jumpstart in under 30 minutes. If that was just too fast, continue reading this document to learn more in details. From bf1e0bef4fe2a766dad210645ba151261c411a80 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 7 Oct 2024 15:36:20 -0400 Subject: [PATCH 035/110] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a5594cad..ac927178 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Customers are looking to drive revenue and increase media efficiency be identify ## Quick Installation ⏰ -Want to quickly install and use it? Follow this [installation notebook 📔](notebooks/quick_installation.ipynb) and use Marketing Analytics Jumpstart in under 30 minutes. +Want to quickly install and use it? Run this [installation notebook 📔](notebooks/quick_installation.ipynb) on Google Colaboratory and leverage Marketing Analytics Jumpstart in under 30 minutes. If that was just too fast, continue reading this document to learn more in details. From 5c093b260c3c4a79ff55067dda38e599d1de7ae3 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 7 Oct 2024 15:38:11 -0400 Subject: [PATCH 036/110] Add files via upload --- notebooks/quick_installation.ipynb | 225 +++++++++++++++++++++++------ 1 file changed, 180 insertions(+), 45 deletions(-) diff --git a/notebooks/quick_installation.ipynb b/notebooks/quick_installation.ipynb index 4bff574f..425c2581 100644 --- a/notebooks/quick_installation.ipynb +++ b/notebooks/quick_installation.ipynb @@ -3,7 +3,11 @@ "nbformat_minor": 0, "metadata": { "colab": { - "provenance": [] + "provenance": [], + "collapsed_sections": [ + "mOISt4ShqIbc", + "US36yJ8lmqnP" + ] }, "kernelspec": { "name": "python3", @@ -50,77 +54,201 @@ { "cell_type": "markdown", "source": [ - "Follow this Colab notebook to quick install the Marketing Analytics Jumpstart solution on a Google Cloud Project." + "Follow this Colab notebook to quick install the Marketing Analytics Jumpstart solution on a Google Cloud Project.\n", + "\n", + "> **Note:** You need access to the Google Analytics 4 Property, Google Ads Account and a Google Cloud project in which you will deploy Marketing Analytics Jumpstart.\n", + "\n", + "\n", + "\n", + "Total Installation time is around **25-30 minutes**." ], "metadata": { "id": "mj-8n9jIyTn-" } }, + { + "cell_type": "markdown", + "source": [ + "### 1. Authenticate to Google Cloud Platform\n", + "\n", + "Click the ( ▶ ) button to authenticate you to the Google Cloud Project.\n", + "\n", + "***Time: 30 seconds.***" + ], + "metadata": { + "id": "DDGHqJNhq5Oi" + } + }, { "cell_type": "code", "source": [ - "# @title Input Google Cloud Project ID\n", - "# prompt: set PROJECT_ID env variable and run gcloud set project\n", + "# @title\n", + "from google.colab import auth\n", + "auth.authenticate_user()\n", "\n", - "GOOGLE_CLOUD_PROJECT = \"marketing-analytics-jumpstart-project-id\" #@param {type:\"string\"}\n", - "GOOGLE_CLOUD_QUOTA_PROJECT = GOOGLE_CLOUD_PROJECT\n", - "PROJECT_ID = GOOGLE_CLOUD_PROJECT\n", - "!gcloud config set disable_prompts true\n", - "!gcloud config set project {PROJECT_ID}" + "print('Authenticated')" ], "metadata": { - "id": "dMcepKg8IQWj" + "id": "9TyPgnleJGGZ", + "cellView": "form" }, "execution_count": null, "outputs": [] }, + { + "cell_type": "markdown", + "source": [ + "### 2. Input Google Cloud Project ID\n", + "\n", + "Fill-out the form, and Click the ( ▶ ) button.\n", + "\n", + "***Time: 10 minutes.***" + ], + "metadata": { + "id": "mq1yqwr8qcx1" + } + }, { "cell_type": "code", "source": [ - "# @title Authenticate to Google Cloud Platform\n", - "# prompt: authenticate to google cloud project\n", - "from google.colab import auth\n", - "auth.authenticate_user()" + "# @markdown ---\n", + "# @markdown Copy the `Project ID` from the \"Project Info\" card in the console [Dashboard](https://console.cloud.google.com/home/dashboard).\n", + "GOOGLE_CLOUD_PROJECT_ID = \"gcp_project_id\" #@param {type:\"string\"}\n", + "GOOGLE_CLOUD_QUOTA_PROJECT = GOOGLE_CLOUD_PROJECT_ID\n", + "PROJECT_ID = GOOGLE_CLOUD_PROJECT_ID\n", + "# @markdown ---\n", + "MAJ_DEFAULT_PROJECT_ID=GOOGLE_CLOUD_PROJECT_ID\n", + "# @markdown Choose the default compute region for your resources, check detailed [list](https://cloud.google.com/compute/docs/regions-zones#available).\n", + "MAJ_DEFAULT_REGION = \"us-central1\" #@param [\"asia-east1\", \"asia-east2\", \"asia-northeast1\", \"asia-northeast3\", \"asia-south1\", \"asia-southeast1\", \"asia-southeast2\", \"australia-southeast1\", \"europe-west1\", \"europe-west2\", \"europe-west3\", \"europe-west4\", \"europe-west6\", \"europe-west12\", \"me-central1\", \"me-central2\", \"northamerica-northeast1\", \"southamerica-east1\", \"us-central1\", \"us-east1\", \"us-east4\", \"us-east5\", \"us-south1\", \"us-west1\", \"us-west2\", \"us-west4\"] {allow-input: true}\n", + "MAJ_MDS_PROJECT_ID=MAJ_DEFAULT_PROJECT_ID\n", + "MAJ_MDS_DATAFORM_PROJECT_ID=MAJ_DEFAULT_PROJECT_ID\n", + "MAJ_FEATURE_STORE_PROJECT_ID=MAJ_DEFAULT_PROJECT_ID\n", + "MAJ_ACTIVATION_PROJECT_ID=MAJ_DEFAULT_PROJECT_ID\n", + "# @markdown ---\n", + "# @markdown Choose the default data location for your data, check detailed [list](https://cloud.google.com/bigquery/docs/locations).\n", + "MAJ_MDS_DATA_LOCATION = \"US\" #@param [\"US\", \"EU\", \"asia-east1\", \"asia-east2\", \"asia-northeast1\", \"asia-northeast2\", \"asia-northeast3\", \"asia-south1\", \"asia-south2\", \"asia-southeast1\", \"asia-southeast2\", \"australia-southeast1\", \"australia-southeast2\", \"europe-central2\", \"europe-north1\", \"europe-west1\", \"europe-west2\", \"europe-west3\", \"europe-west4\", \"europe-west6\", \"europe-west8\", \"europe-west9\", \"northamerica-northeast1\", \"northamerica-northeast2\", \"southamerica-east1\", \"southamerica-west1\", \"us-central1\", \"us-central2\", \"us-east1\", \"us-east4\", \"us-west1\", \"us-west2\", \"us-west3\", \"us-west4\"] {allow-input: true}\n", + "# @markdown For a quick installation, deploy Marketing Analytics Jumpstart in the same project, which contains the GA4 and GAds exports datasets. If not the case, include \" (double quotes to the Project ID).\n", + "MAJ_GA4_EXPORT_PROJECT_ID = GOOGLE_CLOUD_PROJECT_ID # @param [\"GOOGLE_CLOUD_PROJECT_ID\", \"Your Project ID\"] {\"type\":\"raw\", \"allow-input\":true}\n", + "# @markdown For a quick installation, copy the Google Analytics 4 property number. You will find it in your Google Analytics 4 console or in the exported BigQuery dataset name. It must be in the following format: `\"analytics_PROPERTYNUMBER\"`.\n", + "MAJ_GA4_EXPORT_DATASET = \"analytics_123456789\" #@param {type:\"string\", placeholder:\" analytics_GA4 Property ID (e.g. analytics_434623633)\"}\n", + "# @markdown For a quick installation, deploy Marketing Analytics Jumpstart in the same project, which contains the GA4 and GAds exports datasets. If not the case, include \" (double quotes to the Project ID).\n", + "MAJ_ADS_EXPORT_PROJECT_ID = GOOGLE_CLOUD_PROJECT_ID # @param [\"GOOGLE_CLOUD_PROJECT_ID\", \"Your Project ID\"] {\"type\":\"raw\",\"allow-input\":true}\n", + "# @markdown For a quick installation, deploy Marketing Analytics Jumpstart in the same project, which contains the GA4 and GAds exports datasets. You have defined the dataset for your BigQuery DTS for GAds.\n", + "MAJ_ADS_EXPORT_DATASET = \"ads_dataset\" #@param {type:\"string\", placeholder:\"Dataset Name in the Data Transfer Service (e.g. ads_export)\"}\n", + "# @markdown For a quick installation, copy the Google Ads Account number. You will find it in your Google Ads console or in the exported BigQuery tables suffixes. It must be in the following format: `\"*_CUSTOMERID\"` (without dashes).\n", + "MAJ_ADS_EXPORT_TABLE_SUFFIX = \"_1234567890\" #@param {type:\"string\", placeholder:\"GAds Account Number (e.g. _4717384083)\"}\n", + "# @markdown The website your Google Analytics 4 events are coming from.\n", + "MAJ_WEBSITE_URL = \"https://shop.googlemerchandisestore.com\" #@param {type:\"string\", placeholder:\"Full web URL\"}\n", + "# @markdown For a quick installation, copy the Google Analytics 4 property ID and stream ID. You will find it in your Google Analytics 4 console, under Admin settings.\n", + "MAJ_GA4_PROPERTY_ID = \"123456789\" #@param {type:\"string\"}\n", + "MAJ_GA4_STREAM_ID = \"1234567890\" #@param {type:\"string\"}\n", + "# @markdown For a quick installation, use your email credentials that allows you to create a dataform repository connected to a remote Github repository, more info [here](https://cloud.google.com/dataform/docs/connect-repository).\n", + "MAJ_DATAFORM_REPO_OWNER_EMAIL = \"user@company.com\" #@param {type:\"string\", placeholder:\"user@company.com\"}\n", + "MAJ_DATAFORM_GITHUB_REPO_URL = \"https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart-dataform.git\" #@param {type:\"string\"}\n", + "# @markdown For a quick installation, reuse or create your [GitHub personal access token](https://cloud.google.com/dataform/docs/connect-repository#connect-https)\n", + "MAJ_DATAFORM_GITHUB_TOKEN = \"Your github token\" #@param {type:\"string\"}\n", + "# @markdown ---\n", + "\n", + "import os\n", + "os.environ['LOCATION'] = MAJ_DEFAULT_REGION\n", + "os.environ['GOOGLE_CLOUD_PROJECT_ID'] = GOOGLE_CLOUD_PROJECT_ID\n", + "os.environ['GOOGLE_CLOUD_QUOTA_PROJECT'] = GOOGLE_CLOUD_QUOTA_PROJECT\n", + "os.environ['PROJECT_ID'] = PROJECT_ID\n", + "os.environ['MAJ_DEFAULT_PROJECT_ID'] = MAJ_DEFAULT_PROJECT_ID\n", + "os.environ['MAJ_DEFAULT_REGION'] = MAJ_DEFAULT_REGION\n", + "os.environ['MAJ_MDS_PROJECT_ID'] = MAJ_MDS_PROJECT_ID\n", + "os.environ['MAJ_MDS_DATAFORM_PROJECT_ID'] = MAJ_MDS_DATAFORM_PROJECT_ID\n", + "os.environ['MAJ_FEATURE_STORE_PROJECT_ID'] = MAJ_FEATURE_STORE_PROJECT_ID\n", + "os.environ['MAJ_ACTIVATION_PROJECT_ID'] = MAJ_ACTIVATION_PROJECT_ID\n", + "os.environ['MAJ_MDS_DATA_LOCATION'] = MAJ_MDS_DATA_LOCATION\n", + "os.environ['MAJ_GA4_EXPORT_PROJECT_ID'] = MAJ_GA4_EXPORT_PROJECT_ID\n", + "os.environ['MAJ_GA4_EXPORT_DATASET'] = MAJ_GA4_EXPORT_DATASET\n", + "os.environ['MAJ_ADS_EXPORT_PROJECT_ID'] = MAJ_ADS_EXPORT_PROJECT_ID\n", + "os.environ['MAJ_ADS_EXPORT_DATASET'] = MAJ_ADS_EXPORT_DATASET\n", + "os.environ['MAJ_ADS_EXPORT_TABLE_SUFFIX'] = MAJ_ADS_EXPORT_TABLE_SUFFIX\n", + "os.environ['MAJ_WEBSITE_URL'] = MAJ_WEBSITE_URL\n", + "os.environ['MAJ_GA4_PROPERTY_ID'] = MAJ_GA4_PROPERTY_ID\n", + "os.environ['MAJ_GA4_STREAM_ID'] = MAJ_GA4_STREAM_ID\n", + "os.environ['MAJ_DATAFORM_REPO_OWNER_EMAIL'] = MAJ_DATAFORM_REPO_OWNER_EMAIL\n", + "os.environ['MAJ_DATAFORM_GITHUB_REPO_URL'] = MAJ_DATAFORM_GITHUB_REPO_URL\n", + "os.environ['MAJ_DATAFORM_GITHUB_TOKEN'] = MAJ_DATAFORM_GITHUB_TOKEN\n", + "\n", + "!export SOURCE_ROOT=$(pwd)\n", + "!export TERRAFORM_RUN_DIR={SOURCE_ROOT}/infrastructure/terraform\n", + "REPO=\"marketing-analytics-jumpstart\"\n", + "!if [ ! -d \"/content/{REPO}\" ]; then git clone https://github.com/GoogleCloudPlatform/{REPO}.git ; fi\n", + "SOURCE_ROOT=\"/content/\"+REPO\n", + "%cd {SOURCE_ROOT}\n", + "!sudo apt-get -qq -o=Dpkg::Use-Pty=0 install gettext\n", + "!envsubst < \"{SOURCE_ROOT}/infrastructure/cloudshell/terraform-template.tfvars\" > \"{SOURCE_ROOT}/infrastructure/terraform/terraform.tfvars\"\n", + "\n", + "!gcloud config set disable_prompts true\n", + "!gcloud config set project {PROJECT_ID}\n", + "\n", + "from IPython.display import clear_output\n", + "clear_output(wait=True)\n", + "print(\"SUCCESS\")" ], "metadata": { - "id": "9TyPgnleJGGZ" + "id": "dMcepKg8IQWj", + "cellView": "form" }, - "execution_count": 1, + "execution_count": null, "outputs": [] }, + { + "cell_type": "markdown", + "source": [ + "### 3. Authenticate using application default credentials Google Cloud Platform\n", + "\n", + "Click the ( ▶ ) button to create your Terraform application default credentials to the Google Cloud Project.\n", + "\n", + "***Time: 2 minute.***" + ], + "metadata": { + "id": "mOISt4ShqIbc" + } + }, { "cell_type": "code", "source": [ - "# @title Authenticate using application default credentials Google Cloud Platform\n", + "# @title\n", + "!echo \"Enabling APIs\"\n", + "!source ./scripts/common.sh && enable_all_apis > /dev/null\n", + "!echo \"APIs enabled\"\n", "!gcloud config set disable_prompts false\n", "!gcloud auth application-default login --quiet --scopes=\"openid,https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/sqlservice.login,https://www.googleapis.com/auth/analytics,https://www.googleapis.com/auth/analytics.edit,https://www.googleapis.com/auth/analytics.provision,https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/accounts.reauth\"\n", "!gcloud auth application-default set-quota-project {PROJECT_ID}\n", - "!export GOOGLE_APPLICATION_CREDENTIALS=/content/.config/application_default_credentials.json" + "!export GOOGLE_APPLICATION_CREDENTIALS=/content/.config/application_default_credentials.json\n", + "\n", + "clear_output(wait=True)\n", + "print(\"SUCCESS\")" ], "metadata": { - "id": "3cAwp6CRLSVf" + "id": "3cAwp6CRLSVf", + "cellView": "form" }, "execution_count": null, "outputs": [] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "wBOKo_6aF-GS" - }, - "outputs": [], + "cell_type": "markdown", "source": [ - "# prompt: git clone a repository and setting cd to it\n", - "REPO=\"marketing-analytics-jumpstart\"\n", - "!if [ ! -d \"/content/{REPO}\" ]; then git clone https://github.com/GoogleCloudPlatform/{REPO}.git ; fi\n", - "SOURCE_ROOT=\"/content/\"+REPO\n", - "%cd {SOURCE_ROOT}" - ] + "### 4. Run Installation\n", + "\n", + "Click the ( ▶ ) button to run the installation end-to-end.\n", + "\n", + "***Time: 15 minutes.***" + ], + "metadata": { + "id": "US36yJ8lmqnP" + } }, { "cell_type": "code", "source": [ + "# @title\n", + "%%capture\n", "%%bash\n", "# prompt: install packages\n", "apt-get install python3.10\n", @@ -147,8 +275,8 @@ "which tfenv\n", "tfenv --version\n", "\n", - "tfenv install 1.5.7\n", - "tfenv use 1.5.7\n", + "tfenv install 1.9.7\n", + "tfenv use 1.9.7\n", "terraform --version\n", "\n", "export PATH=\"$PATH:~/.tfenv/bin\"\n", @@ -156,7 +284,8 @@ "source ./scripts/generate-tf-backend.sh" ], "metadata": { - "id": "hmdklTTuQ_9d" + "id": "hmdklTTuQ_9d", + "cellView": "form" }, "execution_count": null, "outputs": [] @@ -164,12 +293,17 @@ { "cell_type": "code", "source": [ + "# @title\n", + "%%capture\n", "%%bash\n", + "export PATH=\"$PATH:~/.tfenv/bin\"\n", + "export GOOGLE_APPLICATION_CREDENTIALS=/content/.config/application_default_credentials.json\n", "TERRAFORM_RUN_DIR=$(pwd)/infrastructure/terraform\n", - "cp $TERRAFORM_RUN_DIR/terraform-sample.tfvars $TERRAFORM_RUN_DIR/terraform.tfvars -v" + "terraform -chdir=\"${TERRAFORM_RUN_DIR}\" init" ], "metadata": { - "id": "NDQl55V1WkwK" + "id": "5UIbC_z9bgy4", + "cellView": "form" }, "execution_count": null, "outputs": [] @@ -177,14 +311,18 @@ { "cell_type": "code", "source": [ + "# @title\n", "%%bash\n", "export PATH=\"$PATH:~/.tfenv/bin\"\n", + "export PATH=\"/root/.local/bin:$PATH\"\n", + "export PATH=\"$PATH:$(which gcloud)\"\n", "export GOOGLE_APPLICATION_CREDENTIALS=/content/.config/application_default_credentials.json\n", "TERRAFORM_RUN_DIR=$(pwd)/infrastructure/terraform\n", - "terraform -chdir=\"${TERRAFORM_RUN_DIR}\" init" + "terraform -chdir=\"${TERRAFORM_RUN_DIR}\" apply -auto-approve" ], "metadata": { - "id": "5UIbC_z9bgy4" + "id": "BGteib5ebsA-", + "cellView": "form" }, "execution_count": null, "outputs": [] @@ -192,15 +330,12 @@ { "cell_type": "code", "source": [ - "%%bash\n", - "export PATH=\"$PATH:~/.tfenv/bin\"\n", - "export PATH=\"/root/.local/bin:$PATH\"\n", - "export GOOGLE_APPLICATION_CREDENTIALS=/content/.config/application_default_credentials.json\n", - "TERRAFORM_RUN_DIR=$(pwd)/infrastructure/terraform\n", - "terraform -chdir=\"${TERRAFORM_RUN_DIR}\" apply -auto-approve" + "# @title\n", + "print(\"SUCCESS!\")" ], "metadata": { - "id": "BGteib5ebsA-" + "id": "1h7k6jFYpLPO", + "cellView": "form" }, "execution_count": null, "outputs": [] From a10355bf4c12a833d0a04f4d87d4d0717262b279 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 7 Oct 2024 15:56:27 -0400 Subject: [PATCH 037/110] Update README.md --- infrastructure/README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/infrastructure/README.md b/infrastructure/README.md index bdeb4543..9d8acb74 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -36,14 +36,22 @@ The activation application uses sensitive information from the Google Analytics ## Data Prerequisites +### Recommended data location + ### Marketing Analytics Data Sources * Set up Google Analytics 4 Export to Bigquery. Please follow the - set-up [documentation](https://support.google.com/analytics/answer/9358801?hl=en). The current version of MDS doesn't - use streaming export tables. + set-up [documentation](https://support.google.com/analytics/answer/9358801?hl=en). Note that the current version of MDS doesn't + support streaming export tables. + + [![Google Analytics 4 BigQuery Export](https://img.youtube.com/vi/u4QlVsNh2Q4/0.jpg)](https://youtube.com/clip/Ugkxo955w1NlF8o5_EZmMdQO7UsxcFxnGt3j?si=zf1X4iEq_8IY_fu2) + + * Set up Google Cloud Data Transfer Service to export Google Ads to Bigquery. Follow these [instructions](https://cloud.google.com/bigquery/docs/google-ads-transfer). + [![Google Analytics 4 BigQuery Export](https://img.youtube.com/vi/svPy0o9r7eI/0.jpg)](https://youtube.com/clip/Ugkx9VT3yyM0GPwDXVVKcBMs2i7qbUmtOH74?si=p6MBZJE32x4EX8bT) + Make sure these exports use the same BigQuery location, either regional or multi-regional one. You can export the data into the same project or different projects - the MDS will be able to get the data from multiple projects. From a2576e015394d68c6cbfaa103edb99b5d4a1a232 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 7 Oct 2024 16:14:47 -0400 Subject: [PATCH 038/110] Update README.md --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index ac927178..471fceee 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,19 @@ This high-level architecture demonstrates how Marketing Analytics Jumpstart inte **Note:** Project Owner for a Google Cloud Project is only required to speed up the deployment process. Consult this [guide]() for a more fine-grained permission list, not including the Owner role, to adhere to your company policies. +## Compute regions and data locations compatibility + +This solution is compatible in all the regions as listed in these listings: + +| | Compute Regions | +|-------|-------| +https://cloud.google.com/compute/docs/regions-zones#available

https://cloud.google.com/vertex-ai/docs/general/locations

https://cloud.google.com/dataflow/docs/resources/locations | "asia-east1", "asia-east2", "asia-northeast1", "asia-northeast3", "asia-south1", "asia-southeast1", "asia-southeast2", "australia-southeast1", "europe-west1", "europe-west2", "europe-west3", "europe-west4", "europe-west6", "europe-west12", "me-central1", "me-central2", "northamerica-northeast1", "southamerica-east1", "us-central1", "us-east1", "us-east4", "us-east5", "us-south1", "us-west1", "us-west2", "us-west4" | + +| | Data Locations | +|-------|-------| +https://cloud.google.com/bigquery/docs/locations | "US", "EU", "asia-east1", "asia-east2", "asia-northeast1", "asia-northeast2", "asia-northeast3", "asia-south1", "asia-south2", "asia-southeast1", "asia-southeast2", "australia-southeast1", "australia-southeast2", "europe-central2", "europe-north1", "europe-west1", "europe-west2", "europe-west3", "europe-west4", "europe-west6", "europe-west8", "europe-west9", "northamerica-northeast1", "northamerica-northeast2", "southamerica-east1", "southamerica-west1", "us-central1", "us-central2", "us-east1", "us-east4", "us-west1", "us-west2", "us-west3", "us-west4" | + + ## Installation 👷‍♀️ To facilitate the installation, use this Step by Step Installation Video. From 69e91cef3810972761db307886cbf03bc0b2e945 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 8 Oct 2024 11:59:51 -0400 Subject: [PATCH 039/110] Update README.md --- infrastructure/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/README.md b/infrastructure/README.md index 9d8acb74..9c57ace8 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -1,4 +1,4 @@ -# Marketing Analytics Jumpstart Installation Guide +# Marketing Analytics Jumpstart Step-by-Step Installation Guide ## Overview From edfd0e328c17a377906810e7be655ea306872d55 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 8 Oct 2024 12:07:38 -0400 Subject: [PATCH 040/110] Update README.md --- infrastructure/terraform/README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index dc811da6..f5e47c6b 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -1,11 +1,13 @@ -# Terraform Scripts +# Installing Terraform Modules -The Terraform scripts in this folder create the infrastructure to start data ingestion -into BigQuery, create feature store, ML pipelines and Dataflow activation pipeline. +The Terraform scripts in this folder and subfolders create the infrastructure to start data ingestion +into BigQuery, create feature store, run ML pipelines and Dataflow activation application. ## Prerequisites -Make sure the prerequisites listed in the [parent README](../README.md) are met. You can run the script +Make sure the prerequisites listed in the [parent README](../README.md) are met. + +You can run the script from [Cloud Shell](https://cloud.google.com/shell/docs/using-cloud-shelld.google.com/shell/docs/using-cloud-shell) or a Linux machine or a Mac with `gcloud` command installed. The instructions provided are for the Cloud Shell installation. @@ -16,10 +18,11 @@ have plenty of disk space before continuing the installation. If that is not your case, following the Cloud Shell documentation to [reset your Cloud Shell](https://cloud.google.com/shell/docs/resetting-cloud-shell). -## Installation Guide -Step by step installation guide with [![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart.git&cloudshell_git_branch=main&cloudshell_workspace=&cloudshell_tutorial=infrastructure/cloudshell/tutorial.md) +## Manual Installation Guide + +In this section, you find all the detailed steps required for you to manually install the Marketing Analytics Jumpstart solution. Following this process, you have greater flexibility and customization allowing you to choose which components of the solution you want to use or not. -**Note:** If you are working from a forked repository, be sure to update the `cloudshell_git_repo` parameter to the URL of your forked repository for the button link above. +Also, this method allows you to extend this solution and develop it to satisfy your own needs. ### Initial Environment Setup From fddb0c4968d3ae2af55ffa4234d1729d4b556155 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 8 Oct 2024 12:09:06 -0400 Subject: [PATCH 041/110] Update README.md --- infrastructure/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/infrastructure/README.md b/infrastructure/README.md index 9c57ace8..7e6f2bbe 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -101,7 +101,13 @@ copy the SQL scripts from a companion GitHub repo before running the Terraform s to create a Cloud Secret - it will be done by the Terraform scripts. You will need to provide the Git URL and the access token to the Terraform scripts using a Terraform variable. -## Installing the Marketing Data Store, Machine Learning Pipelines, Feature Store, and the Activation Application +## Guided Installation of Terraform Modules + +Step by step installation guide with [![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart.git&cloudshell_git_branch=main&cloudshell_workspace=&cloudshell_tutorial=infrastructure/cloudshell/tutorial.md) + +**Note:** If you are working from a forked repository, be sure to update the `cloudshell_git_repo` parameter to the URL of your forked repository for the button link above. + +## Manual Installation of Terraform Modules Once all the permissions and data prerequisites are met, you can install these components using Terraform scripts. From 2b9789d295980d961edd9e594ce9e8fd3a3170f3 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 8 Oct 2024 12:13:49 -0400 Subject: [PATCH 042/110] Update README.md --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 471fceee..c40e4e94 100644 --- a/README.md +++ b/README.md @@ -38,14 +38,14 @@ These insights are used to serve as a basis to optimize paid media efforts and i * Driving a more personalized experience for your highly valued customers and improve return on ads spend (ROAS) via customer lifetime value * Attributing bidding values to specific users according to their journeys through the conversion funnel which Ads platform uses to guide better campaign performance in specific markets -| Use Case | Data Sources | Model | Looker Report Name | Activation Event | Google Ads Campaign Optimization | -|-------|-------|-------|--------|--------|--------| -| Audience Segmentation | Google Analytics 4 | BQML Kmeans | Demographic based Audience Segmentation | [maj_audience_segmentation_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L2) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | -| Auto Audience Segmentation | Google Analytics 4 | BQML Kmeans | Interest based Audience Segmentation | [maj_auto_audience_segmentation_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L8) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | -| Customer Lifetime Value | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Customer Lifetime Value | [maj_cltv_180_30](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L23) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | -| Purchase Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Purchase | [maj_purchase_propensity_30_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L28) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | -| Churn Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Churn | [maj_churn_propensity_30_15](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/templates/activation_type_configuration_template.tpl#L43) | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | -| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | - | [Static Conversion Values](https://support.google.com/google-ads/answer/13064107?sjid=13060303839552593837-NA#zippy=%2Cset-a-conversion-value%2Cchange-a-conversion-value)

Bid Adjustment [1](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) [2](https://support.google.com/google-ads/answer/2732132?sjid=8368074830549837931-NA#zippy=%2Cremarketing-lists-for-search-ads-advanced) | +| Use Case | Data Sources | Model | Looker Report Name | Google Ads Campaign Optimization | +|-------|-------|-------|--------|--------| +| Audience Segmentation | Google Analytics 4 | BQML Kmeans | Demographic based Audience Segmentation | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | +| Auto Audience Segmentation | Google Analytics 4 | BQML Kmeans | Interest based Audience Segmentation | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | +| Customer Lifetime Value | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Customer Lifetime Value | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | +| Purchase Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Purchase | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | +| Churn Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Churn | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | +| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | [Static Conversion Values](https://support.google.com/google-ads/answer/13064107?sjid=13060303839552593837-NA#zippy=%2Cset-a-conversion-value%2Cchange-a-conversion-value)

Bid Adjustment [1](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) [2](https://support.google.com/google-ads/answer/2732132?sjid=8368074830549837931-NA#zippy=%2Cremarketing-lists-for-search-ads-advanced) | ## Repository Structure 🏗️ The solution's source code is written in Terraform, Python, SQL, YAML and JSON; and it is organized into five main folders: From 7f2979d345df5dab0286602e7a39a838b1f83f12 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 8 Oct 2024 12:40:24 -0400 Subject: [PATCH 043/110] Update README.md --- infrastructure/README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/infrastructure/README.md b/infrastructure/README.md index 7e6f2bbe..3b7d0a8b 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -3,7 +3,17 @@ ## Overview Marketing Analytics Jumpstart consists of several components - marketing data store (MDS), feature store, ML pipelines, -the activation pipeline and dashboards. This document describes the permission and data prerequisites for a successful installation. +the activation pipeline and dashboards. + +This document describes the permission and data prerequisites for a successful installation and provides you with two routes to install the solution. These are design for advanced uses of the Marketing Analytics Jumpstart solution. + +1) Guided Installation Tuturial of Terraform Modules on Cloud Shell + This route allows you to install and manage the solution components using our cloud-based Developer workspace. You will have the possibility to tailor the solution components to your needs. Allowing you to use only subcomponents of this solution. For instance, developers wanting to reuse their existing Marketing Data Store will prefer this installation method. + +2) Manual Installation of Terraform Modules + This route allows you to install and manage the solution in any workspace (cloud, local machine, Compute engine instance). This is the prefered method for user who are contributing and extending this solution to implement new features or adapt it to specific business needs. This route must also be taken, in case you need to manage multiple brands installations, multiple tenants installations, multiple regions installations in a comprehensive manner. + +Once you have chosen your route, check the permissions and data prerequisites in detail. ## Permissions Prerequisites @@ -101,19 +111,19 @@ copy the SQL scripts from a companion GitHub repo before running the Terraform s to create a Cloud Secret - it will be done by the Terraform scripts. You will need to provide the Git URL and the access token to the Terraform scripts using a Terraform variable. -## Guided Installation of Terraform Modules +## Guided Installation Tuturial of Terraform Modules on Cloud Shell Step by step installation guide with [![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart.git&cloudshell_git_branch=main&cloudshell_workspace=&cloudshell_tutorial=infrastructure/cloudshell/tutorial.md) **Note:** If you are working from a forked repository, be sure to update the `cloudshell_git_repo` parameter to the URL of your forked repository for the button link above. -## Manual Installation of Terraform Modules +## Manual Installation Guide of Terraform Modules Once all the permissions and data prerequisites are met, you can install these components using Terraform scripts. Follow instructions in [terraform/README.md](terraform/README.md) -## Installing Dashboard +## Looker Studio Dashboard Installation Looker Studio Dashboard can be installed by following instructions in [../python/lookerstudio/README.md](../python/lookerstudio/README.md) From 32c8ccfe559f906f4d90ed95cb093473ba488341 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 8 Oct 2024 12:41:31 -0400 Subject: [PATCH 044/110] Update README.md --- infrastructure/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/infrastructure/README.md b/infrastructure/README.md index 3b7d0a8b..d19971a9 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -7,10 +7,12 @@ the activation pipeline and dashboards. This document describes the permission and data prerequisites for a successful installation and provides you with two routes to install the solution. These are design for advanced uses of the Marketing Analytics Jumpstart solution. -1) Guided Installation Tuturial of Terraform Modules on Cloud Shell +**1) Guided Installation Tuturial of Terraform Modules on Cloud Shell** + This route allows you to install and manage the solution components using our cloud-based Developer workspace. You will have the possibility to tailor the solution components to your needs. Allowing you to use only subcomponents of this solution. For instance, developers wanting to reuse their existing Marketing Data Store will prefer this installation method. -2) Manual Installation of Terraform Modules +**2) Manual Installation of Terraform Modules** + This route allows you to install and manage the solution in any workspace (cloud, local machine, Compute engine instance). This is the prefered method for user who are contributing and extending this solution to implement new features or adapt it to specific business needs. This route must also be taken, in case you need to manage multiple brands installations, multiple tenants installations, multiple regions installations in a comprehensive manner. Once you have chosen your route, check the permissions and data prerequisites in detail. From e448b700066b96a6803ccc36284dfc63e51e8426 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 8 Oct 2024 12:42:09 -0400 Subject: [PATCH 045/110] Update README.md --- infrastructure/terraform/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index f5e47c6b..34794040 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -1,4 +1,4 @@ -# Installing Terraform Modules +# Manual Installation of Terraform Modules The Terraform scripts in this folder and subfolders create the infrastructure to start data ingestion into BigQuery, create feature store, run ML pipelines and Dataflow activation application. From 1c03c1acba198fe28ed28f7bb4516d3d132dbec6 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 8 Oct 2024 12:48:28 -0400 Subject: [PATCH 046/110] Update README.md --- infrastructure/terraform/README.md | 196 +---------------------------- 1 file changed, 4 insertions(+), 192 deletions(-) diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index 34794040..72f2b141 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -216,200 +216,12 @@ At this time, the Terraform scripts in this folder perform the following tasks: - Dataform repository connected to the GitHub repo - Deploys the marketing data store (MDS), feature store, ML pipelines and activation application -The Looker Studio Dashboard deployment is a separate [step](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/python/lookerstudio/README.md). +## Next Steps -## Post-Installation Instructions +Follow the [post-installation guide](./POST-INSTALLATION.md) to start you daily operations. -Now that you have deployed all assets successfully in your Google Cloud Project, you may want to plan for operating the solution to be able to generate the predictions you need to create the audience segments you want for you Ads campaigns. To accomplish that, you gonna to plan a few things. +It is recommended to follow the post-installation guide before deploying the Looker Studio Dashboard, because you need the data and predictions tables to exist before consuming insights in your reports. -First, you need to choose what kind of insight you are looking for to define the campaigns. Here are a few insights provided by each one of the use cases already provided to you: +**The Looker Studio Dashboard deployment is a separate [step](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/python/lookerstudio/README.md).** -- **Aggregated Value Based Bidding ([value_based_bidding](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L514))**: Attributes a numerical value to high value conversion events (user action) in relation to a target conversion event (typically purchase) so that Google Ads can improve the bidding strategy for users that reached these conversion events, as of now. -- **Demographic Audience Segmentation ([audience_segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L929))**: Attributes a cluster segment to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX days back. -- **Interest based Audience Segmentation ([auto_audience_segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1018))**: Attributes a cluster segment to an user using pages navigations data looking XX days back, as of now. -- **Purchase Propensity ([purchase_propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L629))**: Predicts a purchase propensity decile and a propensity score (likelihood between 0.0 - 0% and 1.0 - 100%) to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX days back to predict XX days ahead, as of now. -- **Customer Lifetime Value ([customer_ltv](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1215))**: Predicts a lifetime value gain decile and a lifetime value revenue gain in USD (equal of bigger than 0.0) to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX-XXX days back to predict XX days ahead, as of now. -- **Churn Propensity ([churn_propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L779))**: Predicts a churn propensity decile and a propensity score (likelihood between 0.0 - 0% and 1.0 - 100%) to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX days back to predict XX days ahead, as of now. -Second, you need to measure how much data you are going to use to obtain the insights you need. Each one of the use cases above requires data in the following intervals, using as key metrics number of days and unique user events. - -- **Aggregated Value Based Bidding ([value_based_bidding](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1734))**: Minimum 30 days and maximum 1 year. The number of unique user events is not a key limitation. Note that you need at least 1000 training examples for the model to be trained successfully, to accomplish that we typically duplicate the rows until we have a minimum of 1000 rows in the training table for the "TRAIN" subset. -- **Demographic Audience Segmentation ([audience_segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1779))**: Minimum 30 days and maximum 1 year. Minimum of 1000 unique user events per day. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). -- **Interest based Audience Segmentation ([auto_audience_segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1817))**: Minimum 30 days and maximum 1 year. Minimum of 1000 unique user events per day. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). -- **Purchase Propensity ([purchase_propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1739))**: Minimum 90 days and maximum 2 years. Minimum of 1000 unique user events per day, of which a minimum of 1 target event per week. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). -- **Customer Lifetime Value ([customer_lifetime_value](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1798))**: Minimum 180 days and maximum 5 years. Minimum of 1000 unique user events per day, of which a minimum of 1 event per week that increases the lifetime value for an user. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). -- **Churn Propensity ([churn_propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1758))**: Minimum 30 days and maximum 2 years. Minimum of 1000 unique user events per day, of which a minimum of 1 target event per week. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). - -Third, the data must be processed by the Marketing Data Store; features must be prepared using the Feature Engineering procedure; and the training and inference pipelines must be triggered. For that, open your `config.yaml.tftpl` configuration file and check the `{pipeline-name}.execution.schedule` block to modify the scheduled time for each pipeline you are going to need to orchestrate that enables your use case. Here is a table of pipelines configuration you need to enable for every use case. - -| Use Case | Pipeline Configuration | -| -------- | ---------------------- | -| **Aggregated Value Based Bidding** | [feature-creation-aggregated-value-based-bidding](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L473)
[value_based_bidding.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L515)
[value_based_bidding.explanation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L591) | -| **Demographic Audience Segmentation** | [feature-creation-audience-segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L248)
[segmentation.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L930)
[segmentation.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L973) | -| **Interest based Audience Segmentation** | [feature-creation-auto-audience-segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L170)
[auto_segmentation.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1019)
[auto_segmentation.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1061) | -| **Purchase Propensity** | [feature-creation-purchase-propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L315)
[purchase_propensity.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L630)
[purchase_propensity.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L725) | -| **Customer Lifetime Value** | [feature-creation-customer-ltv](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L419)
[propensity_clv.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1110)
[clv.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1221)
[clv.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1309) | -| **Churn Propensity** | [feature-creation-churn-propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L370)
[churn_propensity.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L780)
[churn_propensity.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L875) | - -After you change these configurations, make sure you apply these changes in your deployed resources by re-running terraform. - -```bash -terraform -chdir="${TERRAFORM_RUN_DIR}" apply -``` - -You can trigger your Cloud Workflow to execute your Dataform workflow at any time, or you can wait until the next day when the Cloud Workflow is going to be executed according to your schedule. There are two components in this solution that requires data for proper installation and functioning. One is the Looker Studio Dashboard, you only deploy the dashboard after you have executed all the steps in this Guide successfully. Another is the ML pipeline, the pipelines compilation requires views and tables to be created so that it can read their schema and define the column transformations to run during the pipeline execution. - -To manually start the data flow you must perform the following tasks: - -1. Run the Cloud Workflow - - On the Google Cloud console, navigate to Workflows page. You will see a Workflow named `dataform-prod-incremental`, then under Actions, click on the three dots and `Execute` the Workflow. - - **Note:** If you have a considerable amount of data (>XXX GBs of data) in your exported GA4 and Ads BigQuery datasets, it can take several minutes or hours to process all the data. Make sure that the processing has completed successfully before you continue to the next step. - -1. Invoke the BigQuery stored procedures having the prefix `invoke_backfill_*` to backfill the feature store in case the GA4 Export has been enabled before installing Marketing Analytics Jumpstart. - - On the Google Cloud console, navigate to BigQuery page. On the query composer, run the following queries to invoke the stored procedures. - ```sql - ## There is no need to backfill the aggregated value based bidding features since there - ## is no aggregations performed before training. The transformation was applied in the - ## Marketing Data Store - - ## Backfill customer ltv tables - CALL `feature_store.invoke_backfill_customer_lifetime_value_label`(); - CALL `feature_store.invoke_backfill_user_lifetime_dimensions`(); - CALL `feature_store.invoke_backfill_user_rolling_window_lifetime_metrics`(); - - ## Backfill purchase propensity tables - CALL `feature_store.invoke_backfill_user_dimensions`(); - CALL `feature_store.invoke_backfill_user_rolling_window_metrics`(); - CALL `feature_store.invoke_backfill_purchase_propensity_label`(); - - ## Backfill audience segmentation tables - CALL `feature_store.invoke_backfill_user_segmentation_dimensions`(); - CALL `feature_store.invoke_backfill_user_lookback_metrics`(); - - ## There is no need to backfill the auto audience segmentation features since - ## they are dynamically prepared in the feature engineering pipeline using - ## python code - - ## Backfill churn propensity tables - ## This use case reuses the user_dimensions and user_rolling_window_metrics, - ## make sure you invoke the backfill for these tables. CALLs are listed above - ## under backfill purchase propensity - CALL `feature_store.invoke_backfill_churn_propensity_label`(); - - ## Backfill for gemini insights - CALL `feature_store.invoke_backfill_user_scoped_metrics`(); - CALL `gemini_insights.invoke_backfill_user_behaviour_revenue_insights`(); - ``` - - **Note:** If you have a considerable amount of data (>XXX GBs of data) in your exported GA4 BigQuery datasets over the last six months, it can take several hours to backfill the feature data so that you can train your ML model. Make sure that the backfill procedures starts without errors before you continue to the next step. - -1. Check whether the feature store tables you have run backfill have rows in it. - - On the Google Cloud console, navigate to BigQuery page. On the query composer, run the following queries to invoke the stored procedures. - ```sql - ## There are no tables used by the aggregated value based bidding use case - ## in the feature store. - - ## Checking customer ltv tables are not empty - SELECT COUNT(user_pseudo_id) FROM `feature_store.customer_lifetime_value_label`; - SELECT COUNT(user_pseudo_id) FROM `feature_store.user_lifetime_dimensions`; - SELECT COUNT(user_pseudo_id) FROM `feature_store.user_rolling_window_lifetime_metrics`; - - ## Checking purchase propensity tables are not empty - SELECT COUNT(user_pseudo_id) FROM `feature_store.user_dimensions`; - SELECT COUNT(user_pseudo_id) FROM `feature_store.user_rolling_window_metrics`; - SELECT COUNT(user_pseudo_id) FROM `feature_store.purchase_propensity_label`; - - ## Checking audience segmentation tables are not empty - SELECT COUNT(user_pseudo_id) FROM `feature_store.user_segmentation_dimensions`; - SELECT COUNT(user_pseudo_id) FROM `feature_store.user_lookback_metrics`; - - ## There are no tables used by the auto audience segmentation use case - ## in the feature store. - - ## Checking churn propensity tables are not empty - ## This use case reuses the user_dimensions and user_rolling_window_metrics, - ## make sure you invoke the backfill for these tables. CALLs are listed above - ## under the instructions for backfill purchase propensity - SELECT COUNT(user_pseudo_id) FROM `feature_store.user_dimensions`; - SELECT COUNT(user_pseudo_id) FROM `feature_store.user_rolling_window_metrics`; - SELECT COUNT(user_pseudo_id) FROM `feature_store.churn_propensity_label`; - - ## Checking gemini insights tables are not empty - SELECT COUNT(feature_date) FROM `feature_store.user_scoped_metrics`; - SELECT COUNT(feature_date) FROM `gemini_insights.user_behaviour_revenue_insights_daily`; - SELECT COUNT(feature_date) FROM `gemini_insights.user_behaviour_revenue_insights_weekly`; - SELECT COUNT(feature_date) FROM `gemini_insights.user_behaviour_revenue_insights_monthly`; - ``` - -1. Redeploy the ML pipelines using Terraform. - - On your code editor, change the variable `deploy_pipelines` from `true` to `false`, on the TF variables file `${TERRAFORM_RUN_DIR}/terraform.tfvars`. - Next, undeploy the ML pipelines component by applying the terraform configuration. - - ```bash - terraform -chdir="${TERRAFORM_RUN_DIR}" apply - ``` - - Now, to deploy the ML pipelines component again, revert your changes on the TF variables file `${TERRAFORM_RUN_DIR}/terraform.tfvars` and apply the terraform configuration by running the commad above again. - - **Note:** The training pipelines use schemas defined by a `custom_transformations` parameter in your `config.yaml` or by the training table/view schema itself. - So at first, during the first deployment i.e. `tf apply`, because the views are not created yet, we assume a fixed schema in case no `custom_transformations` parameter is provided. - Then, you need to redeploy to make sure that since all the table views exist now, redeploy the pipelines to make sure you fetch the right schema to be provided to the training pipelines. - -1. Once the feature store is populated and the pipelines are redeployed, manually invoke the BigQuery procedures for preparing the training datasets, which have the suffix `_training_preparation`. - - On the Google Cloud console, navigate to BigQuery page. On the query composer, run the following queries to invoke the stored procedures. - ```sql - ## Training preparation for Aggregated Value Based Bidding - CALL `aggregated_vbb.invoke_aggregated_value_based_bidding_training_preparation`(); - - ## Training preparation for Customer Lifetime Value - CALL `customer_lifetime_value.invoke_customer_lifetime_value_training_preparation`(); - - ## Training preparation for Purchase Propensity - CALL `purchase_propensity.invoke_purchase_propensity_training_preparation`(); - - ## Training preparation for Audience Segmentation - CALL `audience_segmentation.invoke_audience_segmentation_training_preparation`(); - - ## Training preparation for Auto Audience Segmentation - CALL `auto_audience_segmentation.invoke_auto_audience_segmentation_training_preparation`(); - - ## Training preparation for Churn Propensity - CALL `churn_propensity.invoke_churn_propensity_training_preparation`(); - - ## There is no need to prepare training data for the gemini insights use case. - ## Gemini insights only require feature engineering the inference pipelines. - ## The gemini insights are saved in the gemini insights dataset, specified in the `config.yaml.tftpl` file. - ``` - -1. Check whether the training preparation tables you have run the procedures above have rows in it. - - On the Google Cloud console, navigate to BigQuery page. On the query composer, run the following queries to invoke the stored procedures. - ```sql - ## Checking aggregated value based bidding tables are not empty. - ## For training purposes, your dataset must always include at least 1,000 rows for tabular training data. - SELECT * FROM `aggregated_vbb.aggregated_value_based_bidding_training_full_dataset`; - - ## Checking customer ltv tables are not empty - ## For training purposes, your dataset must always include at least 1,000 rows for tabular training data. - SELECT COUNT(user_pseudo_id) FROM `customer_lifetime_value.customer_lifetime_value_training_full_dataset`; - - ## Checking purchase propensity tables are not empty - ## For training purposes, your dataset must always include at least 1,000 rows for tabular training data. - SELECT COUNT(user_pseudo_id) FROM `purchase_propensity.purchase_propensity_training_full_dataset`; - - ## Checking audience segmentation tables are not empty - ## For training purposes, your dataset must always include at least 1,000 rows for tabular training data. - SELECT COUNT(user_pseudo_id) FROM `audience_segmentation.audience_segmentation_training_full_dataset`; - - ## Checking churn propensity tables are not empty - ## For training purposes, your dataset must always include at least 1,000 rows for tabular training data. - SELECT COUNT(user_pseudo_id) FROM `churn_propensity.churn_propensity_training_full_dataset`; - ``` - -Your Marketing Analytics Jumpstart solution is ready for daily operation. Plan for the days you want your model(s) to be trained, change the scheduler dates in the `config.yaml.tftpl` file or manually trigger training whenever you want. For more information, read the documentations in the [docs/ folder](../../docs/). From 55eefcd8866427896c795e24d192318f076d75e2 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 8 Oct 2024 12:49:19 -0400 Subject: [PATCH 047/110] Create POST-INSTALLATION.md --- infrastructure/terraform/POST-INSTALLATION.md | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 infrastructure/terraform/POST-INSTALLATION.md diff --git a/infrastructure/terraform/POST-INSTALLATION.md b/infrastructure/terraform/POST-INSTALLATION.md new file mode 100644 index 00000000..a4445fb3 --- /dev/null +++ b/infrastructure/terraform/POST-INSTALLATION.md @@ -0,0 +1,196 @@ + +## Post-Installation Guide + +Now that you have deployed all assets successfully in your Google Cloud Project, you may want to plan for operating the solution to be able to generate the predictions you need to create the audience segments you want for you Ads campaigns. To accomplish that, you gonna to plan a few things. + +First, you need to choose what kind of insight you are looking for to define the campaigns. Here are a few insights provided by each one of the use cases already provided to you: + +- **Aggregated Value Based Bidding ([value_based_bidding](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L514))**: Attributes a numerical value to high value conversion events (user action) in relation to a target conversion event (typically purchase) so that Google Ads can improve the bidding strategy for users that reached these conversion events, as of now. +- **Demographic Audience Segmentation ([audience_segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L929))**: Attributes a cluster segment to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX days back. +- **Interest based Audience Segmentation ([auto_audience_segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1018))**: Attributes a cluster segment to an user using pages navigations data looking XX days back, as of now. +- **Purchase Propensity ([purchase_propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L629))**: Predicts a purchase propensity decile and a propensity score (likelihood between 0.0 - 0% and 1.0 - 100%) to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX days back to predict XX days ahead, as of now. +- **Customer Lifetime Value ([customer_ltv](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1215))**: Predicts a lifetime value gain decile and a lifetime value revenue gain in USD (equal of bigger than 0.0) to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX-XXX days back to predict XX days ahead, as of now. +- **Churn Propensity ([churn_propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L779))**: Predicts a churn propensity decile and a propensity score (likelihood between 0.0 - 0% and 1.0 - 100%) to an user using demographics data, including geographic location, device, traffic source and windowed user metrics looking XX days back to predict XX days ahead, as of now. + +Second, you need to measure how much data you are going to use to obtain the insights you need. Each one of the use cases above requires data in the following intervals, using as key metrics number of days and unique user events. + +- **Aggregated Value Based Bidding ([value_based_bidding](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1734))**: Minimum 30 days and maximum 1 year. The number of unique user events is not a key limitation. Note that you need at least 1000 training examples for the model to be trained successfully, to accomplish that we typically duplicate the rows until we have a minimum of 1000 rows in the training table for the "TRAIN" subset. +- **Demographic Audience Segmentation ([audience_segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1779))**: Minimum 30 days and maximum 1 year. Minimum of 1000 unique user events per day. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). +- **Interest based Audience Segmentation ([auto_audience_segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1817))**: Minimum 30 days and maximum 1 year. Minimum of 1000 unique user events per day. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). +- **Purchase Propensity ([purchase_propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1739))**: Minimum 90 days and maximum 2 years. Minimum of 1000 unique user events per day, of which a minimum of 1 target event per week. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). +- **Customer Lifetime Value ([customer_lifetime_value](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1798))**: Minimum 180 days and maximum 5 years. Minimum of 1000 unique user events per day, of which a minimum of 1 event per week that increases the lifetime value for an user. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). +- **Churn Propensity ([churn_propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1758))**: Minimum 30 days and maximum 2 years. Minimum of 1000 unique user events per day, of which a minimum of 1 target event per week. Note that you don't need more than 1M training examples for the model to perform well, make sure your training table doesn't contain more training examples than you need by applying exclusion clauses (i.e. WHERE, LIMIT clauses). + +Third, the data must be processed by the Marketing Data Store; features must be prepared using the Feature Engineering procedure; and the training and inference pipelines must be triggered. For that, open your `config.yaml.tftpl` configuration file and check the `{pipeline-name}.execution.schedule` block to modify the scheduled time for each pipeline you are going to need to orchestrate that enables your use case. Here is a table of pipelines configuration you need to enable for every use case. + +| Use Case | Pipeline Configuration | +| -------- | ---------------------- | +| **Aggregated Value Based Bidding** | [feature-creation-aggregated-value-based-bidding](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L473)
[value_based_bidding.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L515)
[value_based_bidding.explanation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L591) | +| **Demographic Audience Segmentation** | [feature-creation-audience-segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L248)
[segmentation.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L930)
[segmentation.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L973) | +| **Interest based Audience Segmentation** | [feature-creation-auto-audience-segmentation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L170)
[auto_segmentation.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1019)
[auto_segmentation.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1061) | +| **Purchase Propensity** | [feature-creation-purchase-propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L315)
[purchase_propensity.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L630)
[purchase_propensity.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L725) | +| **Customer Lifetime Value** | [feature-creation-customer-ltv](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L419)
[propensity_clv.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1110)
[clv.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1221)
[clv.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L1309) | +| **Churn Propensity** | [feature-creation-churn-propensity](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L370)
[churn_propensity.training](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L780)
[churn_propensity.prediction](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/config/config.yaml.tftpl#L875) | + +After you change these configurations, make sure you apply these changes in your deployed resources by re-running terraform. + +```bash +terraform -chdir="${TERRAFORM_RUN_DIR}" apply +``` + +You can trigger your Cloud Workflow to execute your Dataform workflow at any time, or you can wait until the next day when the Cloud Workflow is going to be executed according to your schedule. There are two components in this solution that requires data for proper installation and functioning. One is the Looker Studio Dashboard, you only deploy the dashboard after you have executed all the steps in this Guide successfully. Another is the ML pipeline, the pipelines compilation requires views and tables to be created so that it can read their schema and define the column transformations to run during the pipeline execution. + +To manually start the data flow you must perform the following tasks: + +1. Run the Cloud Workflow + + On the Google Cloud console, navigate to Workflows page. You will see a Workflow named `dataform-prod-incremental`, then under Actions, click on the three dots and `Execute` the Workflow. + + **Note:** If you have a considerable amount of data (>XXX GBs of data) in your exported GA4 and Ads BigQuery datasets, it can take several minutes or hours to process all the data. Make sure that the processing has completed successfully before you continue to the next step. + +1. Invoke the BigQuery stored procedures having the prefix `invoke_backfill_*` to backfill the feature store in case the GA4 Export has been enabled before installing Marketing Analytics Jumpstart. + + On the Google Cloud console, navigate to BigQuery page. On the query composer, run the following queries to invoke the stored procedures. + ```sql + ## There is no need to backfill the aggregated value based bidding features since there + ## is no aggregations performed before training. The transformation was applied in the + ## Marketing Data Store + + ## Backfill customer ltv tables + CALL `feature_store.invoke_backfill_customer_lifetime_value_label`(); + CALL `feature_store.invoke_backfill_user_lifetime_dimensions`(); + CALL `feature_store.invoke_backfill_user_rolling_window_lifetime_metrics`(); + + ## Backfill purchase propensity tables + CALL `feature_store.invoke_backfill_user_dimensions`(); + CALL `feature_store.invoke_backfill_user_rolling_window_metrics`(); + CALL `feature_store.invoke_backfill_purchase_propensity_label`(); + + ## Backfill audience segmentation tables + CALL `feature_store.invoke_backfill_user_segmentation_dimensions`(); + CALL `feature_store.invoke_backfill_user_lookback_metrics`(); + + ## There is no need to backfill the auto audience segmentation features since + ## they are dynamically prepared in the feature engineering pipeline using + ## python code + + ## Backfill churn propensity tables + ## This use case reuses the user_dimensions and user_rolling_window_metrics, + ## make sure you invoke the backfill for these tables. CALLs are listed above + ## under backfill purchase propensity + CALL `feature_store.invoke_backfill_churn_propensity_label`(); + + ## Backfill for gemini insights + CALL `feature_store.invoke_backfill_user_scoped_metrics`(); + CALL `gemini_insights.invoke_backfill_user_behaviour_revenue_insights`(); + ``` + + **Note:** If you have a considerable amount of data (>XXX GBs of data) in your exported GA4 BigQuery datasets over the last six months, it can take several hours to backfill the feature data so that you can train your ML model. Make sure that the backfill procedures starts without errors before you continue to the next step. + +1. Check whether the feature store tables you have run backfill have rows in it. + + On the Google Cloud console, navigate to BigQuery page. On the query composer, run the following queries to invoke the stored procedures. + ```sql + ## There are no tables used by the aggregated value based bidding use case + ## in the feature store. + + ## Checking customer ltv tables are not empty + SELECT COUNT(user_pseudo_id) FROM `feature_store.customer_lifetime_value_label`; + SELECT COUNT(user_pseudo_id) FROM `feature_store.user_lifetime_dimensions`; + SELECT COUNT(user_pseudo_id) FROM `feature_store.user_rolling_window_lifetime_metrics`; + + ## Checking purchase propensity tables are not empty + SELECT COUNT(user_pseudo_id) FROM `feature_store.user_dimensions`; + SELECT COUNT(user_pseudo_id) FROM `feature_store.user_rolling_window_metrics`; + SELECT COUNT(user_pseudo_id) FROM `feature_store.purchase_propensity_label`; + + ## Checking audience segmentation tables are not empty + SELECT COUNT(user_pseudo_id) FROM `feature_store.user_segmentation_dimensions`; + SELECT COUNT(user_pseudo_id) FROM `feature_store.user_lookback_metrics`; + + ## There are no tables used by the auto audience segmentation use case + ## in the feature store. + + ## Checking churn propensity tables are not empty + ## This use case reuses the user_dimensions and user_rolling_window_metrics, + ## make sure you invoke the backfill for these tables. CALLs are listed above + ## under the instructions for backfill purchase propensity + SELECT COUNT(user_pseudo_id) FROM `feature_store.user_dimensions`; + SELECT COUNT(user_pseudo_id) FROM `feature_store.user_rolling_window_metrics`; + SELECT COUNT(user_pseudo_id) FROM `feature_store.churn_propensity_label`; + + ## Checking gemini insights tables are not empty + SELECT COUNT(feature_date) FROM `feature_store.user_scoped_metrics`; + SELECT COUNT(feature_date) FROM `gemini_insights.user_behaviour_revenue_insights_daily`; + SELECT COUNT(feature_date) FROM `gemini_insights.user_behaviour_revenue_insights_weekly`; + SELECT COUNT(feature_date) FROM `gemini_insights.user_behaviour_revenue_insights_monthly`; + ``` + +1. Redeploy the ML pipelines using Terraform. + + On your code editor, change the variable `deploy_pipelines` from `true` to `false`, on the TF variables file `${TERRAFORM_RUN_DIR}/terraform.tfvars`. + Next, undeploy the ML pipelines component by applying the terraform configuration. + + ```bash + terraform -chdir="${TERRAFORM_RUN_DIR}" apply + ``` + + Now, to deploy the ML pipelines component again, revert your changes on the TF variables file `${TERRAFORM_RUN_DIR}/terraform.tfvars` and apply the terraform configuration by running the commad above again. + + **Note:** The training pipelines use schemas defined by a `custom_transformations` parameter in your `config.yaml` or by the training table/view schema itself. + So at first, during the first deployment i.e. `tf apply`, because the views are not created yet, we assume a fixed schema in case no `custom_transformations` parameter is provided. + Then, you need to redeploy to make sure that since all the table views exist now, redeploy the pipelines to make sure you fetch the right schema to be provided to the training pipelines. + +1. Once the feature store is populated and the pipelines are redeployed, manually invoke the BigQuery procedures for preparing the training datasets, which have the suffix `_training_preparation`. + + On the Google Cloud console, navigate to BigQuery page. On the query composer, run the following queries to invoke the stored procedures. + ```sql + ## Training preparation for Aggregated Value Based Bidding + CALL `aggregated_vbb.invoke_aggregated_value_based_bidding_training_preparation`(); + + ## Training preparation for Customer Lifetime Value + CALL `customer_lifetime_value.invoke_customer_lifetime_value_training_preparation`(); + + ## Training preparation for Purchase Propensity + CALL `purchase_propensity.invoke_purchase_propensity_training_preparation`(); + + ## Training preparation for Audience Segmentation + CALL `audience_segmentation.invoke_audience_segmentation_training_preparation`(); + + ## Training preparation for Auto Audience Segmentation + CALL `auto_audience_segmentation.invoke_auto_audience_segmentation_training_preparation`(); + + ## Training preparation for Churn Propensity + CALL `churn_propensity.invoke_churn_propensity_training_preparation`(); + + ## There is no need to prepare training data for the gemini insights use case. + ## Gemini insights only require feature engineering the inference pipelines. + ## The gemini insights are saved in the gemini insights dataset, specified in the `config.yaml.tftpl` file. + ``` + +1. Check whether the training preparation tables you have run the procedures above have rows in it. + + On the Google Cloud console, navigate to BigQuery page. On the query composer, run the following queries to invoke the stored procedures. + ```sql + ## Checking aggregated value based bidding tables are not empty. + ## For training purposes, your dataset must always include at least 1,000 rows for tabular training data. + SELECT * FROM `aggregated_vbb.aggregated_value_based_bidding_training_full_dataset`; + + ## Checking customer ltv tables are not empty + ## For training purposes, your dataset must always include at least 1,000 rows for tabular training data. + SELECT COUNT(user_pseudo_id) FROM `customer_lifetime_value.customer_lifetime_value_training_full_dataset`; + + ## Checking purchase propensity tables are not empty + ## For training purposes, your dataset must always include at least 1,000 rows for tabular training data. + SELECT COUNT(user_pseudo_id) FROM `purchase_propensity.purchase_propensity_training_full_dataset`; + + ## Checking audience segmentation tables are not empty + ## For training purposes, your dataset must always include at least 1,000 rows for tabular training data. + SELECT COUNT(user_pseudo_id) FROM `audience_segmentation.audience_segmentation_training_full_dataset`; + + ## Checking churn propensity tables are not empty + ## For training purposes, your dataset must always include at least 1,000 rows for tabular training data. + SELECT COUNT(user_pseudo_id) FROM `churn_propensity.churn_propensity_training_full_dataset`; + ``` + +Your Marketing Analytics Jumpstart solution is ready for daily operation. Plan for the days you want your model(s) to be trained, change the scheduler dates in the `config.yaml.tftpl` file or manually trigger training whenever you want. For more information, read the documentations in the [docs/ folder](../../docs/). From 32cad1c1390d97046ef17a1300956ef118719a92 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 8 Oct 2024 14:48:57 -0400 Subject: [PATCH 048/110] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c40e4e94..f75f9ac5 100644 --- a/README.md +++ b/README.md @@ -30,13 +30,13 @@ This solution is intended for Marketing Technologist teams using GA4 and GAds pr ## Use Cases 🖱️ -This solution enables customer to plan and take action on their marketing campaigns by interpreting the insights provided by four common predictive use cases (purchase propensity, customer lifetime value, audience segmentation and aggregated value based bidding) and an operation dashboard that monitors Campaigns, Traffic, User Behavior and Models Performance, using the best of Google Cloud Data and AI products and practices. +This solution enables customer to plan and take action on their marketing campaigns by interpreting the insights provided by these common predictive use cases and reports that informs Campaigns performance, Traffic, User Behavior and Models Predictions insights, using the best of Google Cloud Data and AI products. These insights are used to serve as a basis to optimize paid media efforts and investments by: * Building audience segments by using all Google first party data to identify user interests and demographic characteristics relevant to the campaign -* Improving campaign performance by identifying and targeting users deciles most likely to take an action (i.e. purchase, sign-up, churn, abandon a cart, etc) +* Improving campaign performance by identifying and targeting users deciles most likely to take an action (i.e. purchase, churn, etc) * Driving a more personalized experience for your highly valued customers and improve return on ads spend (ROAS) via customer lifetime value -* Attributing bidding values to specific users according to their journeys through the conversion funnel which Ads platform uses to guide better campaign performance in specific markets +* Attributing bidding values to specific users according to their journeys through the conversion funnel which Ads platform uses to guide maximize conversions in specific markets | Use Case | Data Sources | Model | Looker Report Name | Google Ads Campaign Optimization | |-------|-------|-------|--------|--------| From 0b28ae12d04952a9f2b1221de17b4eec166f91be Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 8 Oct 2024 14:49:36 -0400 Subject: [PATCH 049/110] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f75f9ac5..b82c39dd 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ These insights are used to serve as a basis to optimize paid media efforts and i | Customer Lifetime Value | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Customer Lifetime Value | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Purchase Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Purchase | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Churn Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Churn | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | -| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | [Static Conversion Values](https://support.google.com/google-ads/answer/13064107?sjid=13060303839552593837-NA#zippy=%2Cset-a-conversion-value%2Cchange-a-conversion-value)

Bid Adjustment [1](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) [2](https://support.google.com/google-ads/answer/2732132?sjid=8368074830549837931-NA#zippy=%2Cremarketing-lists-for-search-ads-advanced) | +| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | [Static Conversion Values](https://support.google.com/google-ads/answer/13064107?sjid=13060303839552593837-NA#zippy=%2Cset-a-conversion-value%2Cchange-a-conversion-value)

Bid Adjustment (maximize conversions) [1](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) [2](https://support.google.com/google-ads/answer/2732132?sjid=8368074830549837931-NA#zippy=%2Cremarketing-lists-for-search-ads-advanced) | ## Repository Structure 🏗️ The solution's source code is written in Terraform, Python, SQL, YAML and JSON; and it is organized into five main folders: From 1dfaeb536cbe59ee32fd1476d7ce929c7234704e Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 8 Oct 2024 16:47:29 -0400 Subject: [PATCH 050/110] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b82c39dd..4cc19ea6 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ https://cloud.google.com/compute/docs/regions-zones#available

https://c https://cloud.google.com/bigquery/docs/locations | "US", "EU", "asia-east1", "asia-east2", "asia-northeast1", "asia-northeast2", "asia-northeast3", "asia-south1", "asia-south2", "asia-southeast1", "asia-southeast2", "australia-southeast1", "australia-southeast2", "europe-central2", "europe-north1", "europe-west1", "europe-west2", "europe-west3", "europe-west4", "europe-west6", "europe-west8", "europe-west9", "northamerica-northeast1", "northamerica-northeast2", "southamerica-east1", "southamerica-west1", "us-central1", "us-central2", "us-east1", "us-east4", "us-west1", "us-west2", "us-west3", "us-west4" | -## Installation 👷‍♀️ +## Step by Step Installation 👷‍♀️ To facilitate the installation, use this Step by Step Installation Video. From 918b4cf0bb13fa1a6bbeb8292dc6c57a219d8737 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 8 Oct 2024 17:41:57 -0400 Subject: [PATCH 051/110] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4cc19ea6..33264c6b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Customers are looking to drive revenue and increase media efficiency be identify ## Quick Installation ⏰ -Want to quickly install and use it? Run this [installation notebook 📔](notebooks/quick_installation.ipynb) on Google Colaboratory and leverage Marketing Analytics Jumpstart in under 30 minutes. +Want to quickly install and use it? Run this [installation notebook 📔](https://colab.sandbox.google.com/github/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/notebooks/quick_installation.ipynb) on Google Colaboratory and leverage Marketing Analytics Jumpstart in under 30 minutes. If that was just too fast, continue reading this document to learn more in details. From 6ff5a9bd86586ed11c5b62bcb94480f3d1f9c980 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 9 Oct 2024 11:22:51 -0400 Subject: [PATCH 052/110] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 33264c6b..22c35418 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,9 @@ These insights are used to serve as a basis to optimize paid media efforts and i | Audience Segmentation | Google Analytics 4 | BQML Kmeans | Demographic based Audience Segmentation | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Auto Audience Segmentation | Google Analytics 4 | BQML Kmeans | Interest based Audience Segmentation | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Customer Lifetime Value | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Customer Lifetime Value | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | -| Purchase Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Purchase | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | +| Purchase Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Purchase | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing)

Bid Adjustment (maximize conversions) [1](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) [2](https://support.google.com/google-ads/answer/2732132?sjid=8368074830549837931-NA#zippy=%2Cremarketing-lists-for-search-ads-advanced) | | Churn Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Churn | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | -| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | [Static Conversion Values](https://support.google.com/google-ads/answer/13064107?sjid=13060303839552593837-NA#zippy=%2Cset-a-conversion-value%2Cchange-a-conversion-value)

Bid Adjustment (maximize conversions) [1](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) [2](https://support.google.com/google-ads/answer/2732132?sjid=8368074830549837931-NA#zippy=%2Cremarketing-lists-for-search-ads-advanced) | +| Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | [Static Conversion Values](https://support.google.com/google-ads/answer/13064107?sjid=13060303839552593837-NA#zippy=%2Cset-a-conversion-value%2Cchange-a-conversion-value) | ## Repository Structure 🏗️ The solution's source code is written in Terraform, Python, SQL, YAML and JSON; and it is organized into five main folders: From 0f7d184e458e8bf61cb68cc443db58064febc5a3 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 9 Oct 2024 11:53:00 -0400 Subject: [PATCH 053/110] Update README.md --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 22c35418..385b09c3 100644 --- a/README.md +++ b/README.md @@ -137,17 +137,20 @@ https://cloud.google.com/bigquery/docs/locations | "US", "EU", "asia-east1", "as ## Step by Step Installation 👷‍♀️ -To facilitate the installation, use this Step by Step Installation Video. +To facilitate the step by step installation process, we offer you two routes: -[![Step by Step Installation Video](docs/images/YoutubeScreenshot.png)](https://youtu.be/JMnsIxTNbE4 "Marketing Analytics Jumpstart Installation Video") - -Please follow this [Installation Guide](./infrastructure/README.md) to accompany the video. +* One is a guided step by step installation with the help of Google Cloud Shell Tutorial. +* Another route is to follow the step by step manual installation guide supported by a video recording. -Alternatively, follow the step by step installation guide with Google Cloud Shell. +To understand better which route is more appropriate for your needs, read this [documentation](./infrastructure/README.md). +1. To follow the step by step installation guide with Google Cloud Shell Tutorial. Click the button below. [![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart.git&cloudshell_git_branch=main&cloudshell_workspace=&cloudshell_tutorial=infrastructure/cloudshell/tutorial.md) -**Note:** If you are working from a forked repository, be sure to update the `cloudshell_git_repo` parameter to the URL of your forked repository for the button link above. +**Note:** If you are working from a forked repository, be sure to update the `cloudshell_git_repo` parameter to the URL of your forked repository on the link button above. + +2. To follow the manual installation guide, open the Youtube video below on another tab and read the instructions on this [documentation](./infrastructure/README.md). +[![Step by Step Installation Video](docs/images/YoutubeScreenshot.png)](https://youtu.be/JMnsIxTNbE4 "Marketing Analytics Jumpstart Installation Video") ## Contributing 🤝 From 5ec8332b29d3b946e15ad08e7e3596e3c526c6cf Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 9 Oct 2024 11:59:14 -0400 Subject: [PATCH 054/110] Update README.md --- infrastructure/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/infrastructure/README.md b/infrastructure/README.md index d19971a9..917f0ed9 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -113,9 +113,11 @@ copy the SQL scripts from a companion GitHub repo before running the Terraform s to create a Cloud Secret - it will be done by the Terraform scripts. You will need to provide the Git URL and the access token to the Terraform scripts using a Terraform variable. -## Guided Installation Tuturial of Terraform Modules on Cloud Shell +## Guided Installation Tutorial of Terraform Modules on Cloud Shell -Step by step installation guide with [![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart.git&cloudshell_git_branch=main&cloudshell_workspace=&cloudshell_tutorial=infrastructure/cloudshell/tutorial.md) +Once all the permissions and data prerequisites are met, you can install these components following the step by step installation guide using the Cloud Shell Tutorial, by clicking the button below. + +[![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart.git&cloudshell_git_branch=main&cloudshell_workspace=&cloudshell_tutorial=infrastructure/cloudshell/tutorial.md) **Note:** If you are working from a forked repository, be sure to update the `cloudshell_git_repo` parameter to the URL of your forked repository for the button link above. From 8ac8c9f16852609207d3e42181eda94fc937205d Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 9 Oct 2024 12:12:51 -0400 Subject: [PATCH 055/110] Update README.md --- README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 385b09c3..ebc954d2 100644 --- a/README.md +++ b/README.md @@ -140,16 +140,11 @@ https://cloud.google.com/bigquery/docs/locations | "US", "EU", "asia-east1", "as To facilitate the step by step installation process, we offer you two routes: * One is a guided step by step installation with the help of Google Cloud Shell Tutorial. -* Another route is to follow the step by step manual installation guide supported by a video recording. +* Another is to follow the step by step manual installation guide supported by a video recording. To understand better which route is more appropriate for your needs, read this [documentation](./infrastructure/README.md). -1. To follow the step by step installation guide with Google Cloud Shell Tutorial. Click the button below. -[![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart.git&cloudshell_git_branch=main&cloudshell_workspace=&cloudshell_tutorial=infrastructure/cloudshell/tutorial.md) - -**Note:** If you are working from a forked repository, be sure to update the `cloudshell_git_repo` parameter to the URL of your forked repository on the link button above. - -2. To follow the manual installation guide, open the Youtube video below on another tab and read the instructions on this [documentation](./infrastructure/README.md). +To follow the manual installation guide, open the Youtube video below on another tab and read the instructions on the [documentation](./infrastructure/README.md) above. [![Step by Step Installation Video](docs/images/YoutubeScreenshot.png)](https://youtu.be/JMnsIxTNbE4 "Marketing Analytics Jumpstart Installation Video") From 5d5fb5580a160331b448e616dc3cc4c3818c1a7a Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 9 Oct 2024 12:21:44 -0400 Subject: [PATCH 056/110] Update README.md --- infrastructure/terraform/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index 72f2b141..45f69fe4 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -168,7 +168,8 @@ Also, this method allows you to extend this solution and develop it to satisfy y terraform -chdir="${TERRAFORM_RUN_DIR}" apply ``` - If you don't have a successful execution from the beginning, re-run until all is deployed successfully. + If you run into errors, review and edit the `${TERRAFORM_RUN_DIR}/terraform.tfvars` file. + If you don't have a successful execution of certain resources, re-run `terraform -chdir="${TERRAFORM_RUN_DIR}" apply` a few more times until all is deployed successfully. However, if there are still resources not deployed, open a new [github issue](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/issues/). ### Resume terminal session From d635d0900f25271c9b2f87932f6764bb440cf396 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 9 Oct 2024 14:23:05 -0400 Subject: [PATCH 057/110] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ebc954d2..4a4d52af 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ These insights are used to serve as a basis to optimize paid media efforts and i |-------|-------|-------|--------|--------| | Audience Segmentation | Google Analytics 4 | BQML Kmeans | Demographic based Audience Segmentation | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Auto Audience Segmentation | Google Analytics 4 | BQML Kmeans | Interest based Audience Segmentation | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | -| Customer Lifetime Value | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Customer Lifetime Value | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | +| Customer Lifetime Value | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Customer Lifetime Value | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing)

Bid Adjustment (maximize conversions) [1](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) [2](https://support.google.com/google-ads/answer/2732132?sjid=8368074830549837931-NA#zippy=%2Cremarketing-lists-for-search-ads-advanced) | | Purchase Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Purchase | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing)

Bid Adjustment (maximize conversions) [1](https://support.google.com/google-ads/answer/7068417?hl=en#zippy=%2Ctips-for-setting-up-data-segments-for-search-ads%2Csetting-bids-tailoring-ads-and-copying-campaigns) [2](https://support.google.com/google-ads/answer/2732132?sjid=8368074830549837931-NA#zippy=%2Cremarketing-lists-for-search-ads-advanced) | | Churn Propensity | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | Propensity to Churn | [Custom Data Segments](https://support.google.com/google-ads/answer/2497941?sjid=12303667953034547771-NC#zippy=%2Cyour-data-segments-formerly-known-as-remarketing) | | Aggregated Value Based Bidding | Google Analytics 4 | Vertex AI Tabular Wokflows AutoML | High Value Action | [Static Conversion Values](https://support.google.com/google-ads/answer/13064107?sjid=13060303839552593837-NA#zippy=%2Cset-a-conversion-value%2Cchange-a-conversion-value) | From 6260103d34f635ff86a32206f43d781411ba6f32 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 9 Oct 2024 15:13:25 -0400 Subject: [PATCH 058/110] Update README.md --- infrastructure/terraform/README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index 45f69fe4..328ee8c1 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -161,14 +161,22 @@ Also, this method allows you to extend this solution and develop it to satisfy y or in multi-regions by assigning value such as * `US` or `EU` -1. Run Terraform to create resources: +1. Run Terraform to initialize your environment, and validate if your configurations and variables are set as expected: ```bash terraform -chdir="${TERRAFORM_RUN_DIR}" init - terraform -chdir="${TERRAFORM_RUN_DIR}" apply + terraform -chdir="${TERRAFORM_RUN_DIR}" plan + terraform -chdir="${TERRAFORM_RUN_DIR}" validate ``` - If you run into errors, review and edit the `${TERRAFORM_RUN_DIR}/terraform.tfvars` file. + If you run into errors, review and edit the `${TERRAFORM_RUN_DIR}/terraform.tfvars` file. + +1. Run Terraform to create resources: + + ```bash + terraform -chdir="${TERRAFORM_RUN_DIR}" apply + ``` + If you don't have a successful execution of certain resources, re-run `terraform -chdir="${TERRAFORM_RUN_DIR}" apply` a few more times until all is deployed successfully. However, if there are still resources not deployed, open a new [github issue](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/issues/). ### Resume terminal session From 9bb7c40a6f8850f2b1ea2472bfd62e446c998569 Mon Sep 17 00:00:00 2001 From: Charlie Wang <2144018+kingman@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:52:38 +0200 Subject: [PATCH 059/110] activation for VBB in Google Ads (#206) * load confituration * vbb queries * create new user dimension for vbb * trigger on changed to the ga4_setup source file * add documentation for VBB * remove usage of jinja template for activation payload * remove usage of template for measurement protocol paylaod * remove unused user dimension * only activation for the active users from the last day * change vbb to smart bidding in documentation --- DEVELOPMENT.md | 1 - config/config.yaml.tftpl | 2 +- docs/activation.md | 77 +++++++++++- docs/images/vbb_mark_key_event.png | Bin 0 -> 11085 bytes .../activation/configuration-tables.tf | 45 +++++++ .../terraform/modules/activation/main.tf | 39 +++--- python/activation/main.py | 111 ++++++------------ .../audience_segmentation_query_template.sqlx | 4 +- ..._audience_segmentation_query_template.sqlx | 4 +- .../churn_propensity_query_template.sqlx | 6 +- .../activation_query/cltv_query_template.sqlx | 4 +- .../purchase_propensity_query_template.sqlx | 6 +- ...urchase_propensity_vbb_query_template.sqlx | 35 ++++++ ...activation_type_configuration_template.tpl | 37 +++--- templates/app_payload_template.jinja2 | 20 ---- .../load_vbb_activation_configuration.sql.tpl | 33 ++++++ templates/vbb_activation_configuration.jsonl | 2 + 17 files changed, 282 insertions(+), 144 deletions(-) create mode 100644 docs/images/vbb_mark_key_event.png create mode 100644 infrastructure/terraform/modules/activation/configuration-tables.tf create mode 100644 templates/activation_query/purchase_propensity_vbb_query_template.sqlx delete mode 100644 templates/app_payload_template.jinja2 create mode 100644 templates/load_vbb_activation_configuration.sql.tpl create mode 100644 templates/vbb_activation_configuration.jsonl diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 46727a9b..19c0800f 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -65,7 +65,6 @@ Here's a brief breakdown of the contents of each folder: * * `procedures/`: This folder contains the JINJA template files with the `.sqlx` extension used to generate the stored procedures deployed in BigQuery. * * `queries/`: This folder contains the JINJA template files with the `.sqlx` extension used to generate the queries deployed in BigQuery. * `templates/`: -* * `app_payload_template.jinja2`: This file defines the JINJA template used to generate the payload for the Measurement Protocol API used by the Activation Application. * * `activation_query`: This folder contains the JINJA template files with the `.sqlx` extension used to generate the SQL queries for each use case used by the Activation Application to get all the predictions to be prepared and send to Google Analytics 4. ## Out-of-the-box configuration parameters provided by the solution diff --git a/config/config.yaml.tftpl b/config/config.yaml.tftpl index bc14353e..f644a598 100644 --- a/config/config.yaml.tftpl +++ b/config/config.yaml.tftpl @@ -762,7 +762,7 @@ vertex_ai: positive_label: "1" # THese are parameters to trigger the Activation Application Dataflow. pubsub_activation_topic: "activation-trigger" - pubsub_activation_type: "purchase-propensity-30-15" # purchase-propensity-30-15 | purchase-propensity-15-15 | purchase-propensity-15-7" + pubsub_activation_type: "purchase-propensity-30-15" # purchase-propensity-30-15 | purchase-propensity-vbb-30-15 | purchase-propensity-15-15 | purchase-propensity-15-7" pipeline_parameters_substitutions: null # This pipeline contains the configuration parameters for the churn propensity training and inference pipelines for the churn propensity model. diff --git a/docs/activation.md b/docs/activation.md index 79e42d00..38b53090 100644 --- a/docs/activation.md +++ b/docs/activation.md @@ -38,12 +38,14 @@ For each use case, a corresponding SQL query template dictates how prediction va | Use Case | Query Template | | -------- | --------- | | Purchase Propensity | [purchase_propensity_query_template.sqlx](../templates/activation_query/purchase_propensity_query_template.sqlx)| +| Purchase Propensity for Smart Bidding | [purchase_propensity_vbb_query_template.sqlx](../templates/activation_query/purchase_propensity_vbb_query_template.sqlx)| | Customer Lifetime Value | [cltv_query_template.sqlx](../templates/activation_query/cltv_query_template.sqlx) | | Demographic Audience Segmentation | [audience_segmentation_query_template.sqlx](../templates/activation_query/audience_segmentation_query_template.sqlx) | | Interest based Audience Segmentation | [auto_audience_segmentation_query_template.sqlx](../templates/activation_query/auto_audience_segmentation_query_template.sqlx) | | Churn Propensity | [churn_propensity_query_template.sqlx](../templates/activation_query/churn_propensity_query_template.sqlx)| -The [activation configuration](../templates/activation_type_configuration_template.tpl) file links the GA4 custom events with their corresponding query templates and [GA4 Measurement Protocol payload template](../templates/app_payload_template.jinja2) +**Note:** The dynamic fields in the query template need to be prefixed with `user_prop_` or `event_param_` prefix inorder for the activation process to parse the value into measurement protocol payload. + The payload have the following keys set based on the [payload reference documentation](https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference#payload_post_body): @@ -164,7 +166,78 @@ To build your custom audience, follow the [Create an audience guide](https://sup Now you have a custom audience that is automatically updated as new activation events are sent by the activation process. This custom audience can then be used for targeted remarketing campaigns in Google Ads or other platforms. Follow the [Share audiences guide](https://support.google.com/analytics/answer/12800258) to learn how to export your audience for use in external platforms. -**Important:** If you are using User Data Import only use the customer user properties and remove the custom event filtering. +**Important:** If you are using User Data Import only use the customer user properties and remove the custom event filtering. + +## Activation through Smart Bidding Strategy +To activate purchase propensity predictions via [Smart Bidding Strategy](https://support.google.com/google-ads/answer/7065882), we translate predicted decile segments into monetary values, sent as conversion events to GA4. This allows you to use Google Ads strategies for [maximizing conversion value](https://support.google.com/google-ads/answer/7684216) and [target ROAS](https://support.google.com/google-ads/answer/6268637) with custom event values as the target. + +This also allows you to use [Search Ads 360 bid strategies](https://support.google.com/searchads/answer/6231813?hl=en) + +### Configure translation values +This section explains how to configure the translation of purchase propensity predictions into monetary values for Smart Bidding. + +#### Understanding the Configuration File: +The [vbb_activation_configuration.jsonl](../templates/vbb_activation_configuration.jsonl) file controls how predicted deciles are converted into monetary values. It contains two key fields: + +- `value_norm`: Represents the typical or average transaction value for your GA4 property. This provides a baseline for calculating monetary values. +- `decile_multiplier`: An array of multipliers, one for each decile. These multipliers determine how much each decile is valued relative to the value_norm. + +#### Configuration Steps: + +1. Set `value_norm`: + - Open the [vbb_activation_configuration.jsonl](../templates/vbb_activation_configuration.jsonl) file. + - Locate the entry where `"activation_type":"purchase-propensity"`. + - Modify the `value_norm` field to reflect the average transaction value specific to your GA4 property. For example, if your average transaction value is $200, set `value_norm` to 200. + +1. Set `decile_multiplier`:s: + - For each decile (1 through 10), adjust the `multiplier` value to reflect how much you value users in that decile. + - A higher multiplier signifies a higher value. For example, a multiplier of 3.5 for decile 1 means you value users in that decile 3.5 times more than the average customer. + +**Important**: To exclude lower-value deciles from smart bidding, set their decile_multiplier to 0. This prevents predictions for those deciles from being sent to GA4. + +**Example:** +```json +{"activation_type":"purchase-propensity","value_norm":150,"decile_multiplier":[{"decile":1,"multiplier":5.5},{"decile":2,"multiplier":3},{"decile":3,"multiplier":2},{"decile":4,"multiplier":1},{"decile":5,"multiplier":0},{"decile":6,"multiplier":0},{"decile":7,"multiplier":0},{"decile":8,"multiplier":0},{"decile":9,"multiplier":0},{"decile":10,"multiplier":0}]} +``` +In this example: +- The average transaction value (`value_norm`) is set to $150. +- Users in the top decile are valued 5.5 times higher than the average customer. +- Deciles 5 through 10 are excluded from smart bidding (`multiplier` is 0). + +**Important**: +- Maintain the exact formatting of the JSON file. Do not add extra lines or commas as this will cause errors when importing the configuration into BigQuery. +- The formula for calculating the final monetary value for each decile is:` value_norm * decile_multiplier`. + +### Upload configuration +This section outlines the process of uploading your Smart Bidding configuration to Google Cloud Storage (GCS) and then loading it into BigQuery for use in the activation pipeline. + +1. Run terraform apply to upload configuration into GCS bucket: + ``` + cd infrastructure/terraform + terraform apply -target=module.activation[0].google_storage_bucket_object.vbb_activation_configuration_file + ``` +1. Run [load_vbb_activation_configuration](https://console.cloud.google.com/bigquery?ws=!1m5!1m4!6m3!1s!2sactivation!3sload_vbb_activation_configuration) stored procedure to load configuration into BigQeury + +1. Control the configuration in [vbb_activation_configuration](https://console.cloud.google.com/bigquery?ws=!1m5!1m4!4m3!1s!2sactivation!3svbb_activation_configuration) BigQuery table + +### Send Smart Bidding activation events to GA4 +You can manually trigger a activation pipeline execution for Smart Bidding action by following [activation triggering process](#activation-process-triggering) where you set the `activation_type` value to `purchase-propensity-vbb-30-15` + +To configure the prediction pipeline to automatically trigger activation pipeline for Smart Bidding change the pipeline configuration parameter in [config.yaml.tftpl](../config/config.yaml.tftpl) +and set `vertex_ai.pipelines.purchase_propensity.prediction.pipeline_parameters.pubsub_activation_type` to `purchase-propensity-vbb-30-15` +and re-apply terraform to redeploy the pipeline: + ``` + cd infrastructure/terraform + terraform apply -target=module.pipelines[0].null_resource.compile_purchase_propensity_prediction_pipelines + ``` + +### Google Analytics configuration +After the activation events have been send it will take ruffly 24 hours for them to appear in the `Admin -> Events` view. Once you see `maj_purchase_propensity_vbb_30_15` showing up in the event list mark it as key event. +![alt text](images/vbb_mark_key_event.png) + +### Google Ads configuration + +Follow the [Set up Smart Bidding](https://support.google.com/google-ads/answer/10893605) guide to configure the bidding strategy to optimize for conversion value with `maj_purchase_propensity_vbb_30_15` as the conversion event. ## Monitoring & Troubleshooting The activation process logs all sent Measurement Protocol messages in log tables within the `activation` dataset in BigQuery. This includes both successful and failed transmissions, allowing you to track the progress of the activation, get number of events sent to GA4 and identify any potential issues. diff --git a/docs/images/vbb_mark_key_event.png b/docs/images/vbb_mark_key_event.png new file mode 100644 index 0000000000000000000000000000000000000000..fcc0a8e26f464da6453deaf4a2f7e55c19f516d9 GIT binary patch literal 11085 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', '') _client_id = element['client_id'].replace(r'q=">', '') - - payload_str = self.payload_template.render( - client_id=_client_id, - user_id=self.generate_user_id_key_value_pair(element), - event_timestamp=self.date_to_micro(element["inference_date"]), - event_name=self.event_name, - session_id=element['session_id'], - user_properties=self.generate_user_properties(element), - ) + result = {} - try: - result = json.loads(r'{}'.format(payload_str)) - except json.decoder.JSONDecodeError as e: - logging.error(payload_str) - logging.error(traceback.format_exc()) + result['client_id'] = _client_id + if element['user_id']: + result['user_id'] = element['user_id'] + result['timestamp_micros'] = self.date_to_micro(element["inference_date"]) + result['nonPersonalizedAds'] = False + result['consent'] = self.consent_obj + result['user_properties'] = self.extract_user_properties(element) + result['events'] = [self.extract_event(element)] + yield result @@ -419,62 +409,40 @@ def date_to_micro(self, date_str): return int(datetime.datetime.strptime(date_str, self.date_format).timestamp() * 1E6) - def generate_param_fields(self, element): + def extract_user_properties(self, element): """ - Generates a JSON string containing the parameter fields of the element. + Generates a dictionary containing the user properties of the element. Args: element: The element to be processed. Returns: - A JSON string containing the parameter fields of the element. + A dictionary containing the user properties of the element. """ - element_copy = element.copy() - del element_copy['client_id'] - del element_copy['user_id'] - del element_copy['session_id'] - del element_copy['inference_date'] - element_copy = {k: v for k, v in element_copy.items() if v} - return json.dumps(element_copy, cls=DecimalEncoder) + user_properties = {} + for k, v in element.items(): + if k.startswith(self.user_property_prefix) and v: + user_properties[k[len(self.user_property_prefix):]] = {'value': str(v)} + return user_properties - - def generate_user_properties(self, element): - """ - Generates a JSON string containing the user properties of the element. - - Args: - element: The element to be processed. - - Returns: - A JSON string containing the user properties of the element. + def extract_event(self, element): """ - element_copy = element.copy() - del element_copy['client_id'] - del element_copy['user_id'] - del element_copy['session_id'] - del element_copy['inference_date'] - user_properties_obj = {} - for k, v in element_copy.items(): - if v: - user_properties_obj[k] = {'value': str(v)} - return json.dumps(user_properties_obj, cls=DecimalEncoder) - + Generates a dictionary containing the event parameters from the element. - def generate_user_id_key_value_pair(self, element): - """ - If the user_id field is not empty generate the key/value string with the user_id. - else return empty string Args: element: The element to be processed. Returns: - A string containing the key and value with the user_id. + A dictionary containing the event parameters from the element. """ - user_id = element['user_id'] - if user_id: - return f'"user_id": "{user_id}",' - return "" - + event = { + 'name': self.event_name, + 'params': {} + } + for k, v in element.items(): + if k.startswith(self.event_parameter_prefix) and v: + event['params'][k[len(self.event_parameter_prefix):]] = v + return event @@ -519,8 +487,7 @@ def load_activation_type_configuration(args): # Create the activation type configuration dictionary. configuration = { 'activation_event_name': activation_config['activation_event_name'], - 'source_query_template': Environment(loader=BaseLoader).from_string(gcs_read_file(args.project, activation_config['source_query_template']).replace('\n', ' ')), - 'measurement_protocol_payload_template': gcs_read_file(args.project, activation_config['measurement_protocol_payload_template']) + 'source_query_template': Environment(loader=BaseLoader).from_string(gcs_read_file(args.project, activation_config['source_query_template']).replace('\n', ' ')) } return configuration @@ -589,7 +556,7 @@ def run(argv=None): query=load_from_source_query, use_json_exports=True, use_standard_sql=True) - | 'Prepare Measurement Protocol API payload' >> beam.ParDo(TransformToPayload(activation_type_configuration['measurement_protocol_payload_template'], activation_type_configuration['activation_event_name'])) + | 'Prepare Measurement Protocol API payload' >> beam.ParDo(TransformToPayload(activation_type_configuration['activation_event_name'])) | 'POST event to Measurement Protocol API' >> beam.ParDo(CallMeasurementProtocolAPI(activation_options.ga4_measurement_id, activation_options.ga4_api_secret, debug=activation_options.use_api_validation)) ) diff --git a/templates/activation_query/audience_segmentation_query_template.sqlx b/templates/activation_query/audience_segmentation_query_template.sqlx index 40c8c9a5..badf7605 100644 --- a/templates/activation_query/audience_segmentation_query_template.sqlx +++ b/templates/activation_query/audience_segmentation_query_template.sqlx @@ -1,8 +1,8 @@ SELECT - a.prediction AS a_s_prediction, + a.prediction AS user_prop_a_s_prediction, b.user_pseudo_id AS client_id, b.user_id AS user_id, - b.ga_session_id AS session_id, + b.ga_session_id AS event_param_session_id, CASE WHEN EXTRACT(MICROSECOND FROM b.event_timestamp) = 1 THEN b.event_timestamp ELSE TIMESTAMP_SUB(b.event_timestamp, INTERVAL 1 MICROSECOND) END AS inference_date FROM `${mds_project_id}.marketing_ga4_v1_${mds_dataset_suffix}.latest_event_per_user_last_72_hours` b, diff --git a/templates/activation_query/auto_audience_segmentation_query_template.sqlx b/templates/activation_query/auto_audience_segmentation_query_template.sqlx index 5b6c0eef..b1b19269 100644 --- a/templates/activation_query/auto_audience_segmentation_query_template.sqlx +++ b/templates/activation_query/auto_audience_segmentation_query_template.sqlx @@ -1,8 +1,8 @@ SELECT - a.prediction AS a_a_s_prediction, + a.prediction AS user_prop_a_a_s_prediction, b.user_pseudo_id AS client_id, b.user_id AS user_id, - b.ga_session_id AS session_id, + b.ga_session_id AS event_param_session_id, CASE WHEN EXTRACT(MICROSECOND FROM b.event_timestamp) = 1 THEN b.event_timestamp ELSE TIMESTAMP_SUB(b.event_timestamp, INTERVAL 1 MICROSECOND) END AS inference_date FROM `${mds_project_id}.marketing_ga4_v1_${mds_dataset_suffix}.latest_event_per_user_last_72_hours` b, diff --git a/templates/activation_query/churn_propensity_query_template.sqlx b/templates/activation_query/churn_propensity_query_template.sqlx index 5ab39212..ebf6b67a 100644 --- a/templates/activation_query/churn_propensity_query_template.sqlx +++ b/templates/activation_query/churn_propensity_query_template.sqlx @@ -1,9 +1,9 @@ SELECT - a.prediction AS c_p_prediction, - NTILE(10) OVER (ORDER BY a.prediction_prob DESC) AS c_p_decile, + a.prediction AS user_prop_c_p_prediction, + NTILE(10) OVER (ORDER BY a.prediction_prob DESC) AS user_prop_c_p_decile, b.user_pseudo_id AS client_id, b.user_id AS user_id, - b.ga_session_id AS session_id, + b.ga_session_id AS event_param_session_id, CASE WHEN EXTRACT(MICROSECOND FROM b.event_timestamp) = 1 THEN b.event_timestamp ELSE TIMESTAMP_SUB(b.event_timestamp, INTERVAL 1 MICROSECOND) END AS inference_date FROM `${mds_project_id}.marketing_ga4_v1_${mds_dataset_suffix}.latest_event_per_user_last_72_hours` b, diff --git a/templates/activation_query/cltv_query_template.sqlx b/templates/activation_query/cltv_query_template.sqlx index 3a94982d..2651245e 100644 --- a/templates/activation_query/cltv_query_template.sqlx +++ b/templates/activation_query/cltv_query_template.sqlx @@ -1,8 +1,8 @@ SELECT - NTILE(10) OVER (ORDER BY a.prediction DESC) AS cltv_decile, + NTILE(10) OVER (ORDER BY a.prediction DESC) AS user_prop_cltv_decile, b.user_pseudo_id AS client_id, b.user_id AS user_id, - b.ga_session_id AS session_id, + b.ga_session_id AS event_param_session_id, CASE WHEN EXTRACT(MICROSECOND FROM b.event_timestamp) = 1 THEN b.event_timestamp ELSE TIMESTAMP_SUB(b.event_timestamp, INTERVAL 1 MICROSECOND) END AS inference_date FROM `${mds_project_id}.marketing_ga4_v1_${mds_dataset_suffix}.latest_event_per_user_last_72_hours` b, diff --git a/templates/activation_query/purchase_propensity_query_template.sqlx b/templates/activation_query/purchase_propensity_query_template.sqlx index 40fe5c40..7bae9fbe 100644 --- a/templates/activation_query/purchase_propensity_query_template.sqlx +++ b/templates/activation_query/purchase_propensity_query_template.sqlx @@ -1,9 +1,9 @@ SELECT - a.prediction AS p_p_prediction, - NTILE(10) OVER (ORDER BY a.prediction_prob DESC) AS p_p_decile, + a.prediction AS user_prop_p_p_prediction, + NTILE(10) OVER (ORDER BY a.prediction_prob DESC) AS user_prop_p_p_decile, b.user_pseudo_id AS client_id, b.user_id AS user_id, - b.ga_session_id AS session_id, + b.ga_session_id AS event_param_session_id, CASE WHEN EXTRACT(MICROSECOND FROM b.event_timestamp) = 1 THEN b.event_timestamp ELSE TIMESTAMP_SUB(b.event_timestamp, INTERVAL 1 MICROSECOND) END AS inference_date FROM `${mds_project_id}.marketing_ga4_v1_${mds_dataset_suffix}.latest_event_per_user_last_72_hours` b, diff --git a/templates/activation_query/purchase_propensity_vbb_query_template.sqlx b/templates/activation_query/purchase_propensity_vbb_query_template.sqlx new file mode 100644 index 00000000..f81fce9f --- /dev/null +++ b/templates/activation_query/purchase_propensity_vbb_query_template.sqlx @@ -0,0 +1,35 @@ +WITH user_prediction_decile AS ( + SELECT + a.prediction AS p_p_prediction, + NTILE(10) OVER (ORDER BY a.prediction_prob DESC) AS p_p_decile, + b.user_pseudo_id AS client_id, + b.user_id AS user_id, + b.ga_session_id AS session_id, + CASE + WHEN EXTRACT(MICROSECOND FROM b.event_timestamp) = 1 THEN b.event_timestamp + ELSE TIMESTAMP_SUB(b.event_timestamp, INTERVAL 1 MICROSECOND) + END AS inference_date + FROM + `${mds_project_id}.marketing_ga4_v1_${mds_dataset_suffix}.latest_event_per_user_last_24_hours` b, + `{{source_table}}` a + WHERE + COALESCE(a.user_id, "") = COALESCE(b.user_id, "") + AND a.user_pseudo_id = b.user_pseudo_id) +SELECT + a.p_p_prediction AS user_prop_p_p_prediction, + a.p_p_decile AS user_prop_p_p_decile, + b.value AS event_param_value, + 'USD' AS event_param_currency, + a.client_id, + a.user_id, + a.session_id AS event_param_session_id, + a.inference_date +FROM + user_prediction_decile AS a +LEFT JOIN + `${activation_project_id}.${dataset}.vbb_activation_configuration` AS b +ON + a.p_p_decile = b.decile +WHERE + b.activation_type = 'purchase-propensity' +AND b.value > 0 \ No newline at end of file diff --git a/templates/activation_type_configuration_template.tpl b/templates/activation_type_configuration_template.tpl index 9d8e9d68..d73206c7 100644 --- a/templates/activation_type_configuration_template.tpl +++ b/templates/activation_type_configuration_template.tpl @@ -1,57 +1,50 @@ { "audience-segmentation-15": { "activation_event_name": "maj_audience_segmentation_15", - "source_query_template": "${audience_segmentation_query_template_gcs_path}", - "measurement_protocol_payload_template": "${measurement_protocol_payload_template_gcs_path}" + "source_query_template": "${audience_segmentation_query_template_gcs_path}" }, "auto-audience-segmentation-15": { "activation_event_name": "maj_auto_audience_segmentation_15", - "source_query_template": "${auto_audience_segmentation_query_template_gcs_path}", - "measurement_protocol_payload_template": "${measurement_protocol_payload_template_gcs_path}" + "source_query_template": "${auto_audience_segmentation_query_template_gcs_path}" }, "cltv-180-180": { "activation_event_name": "maj_cltv_180_180", - "source_query_template": "${cltv_query_template_gcs_path}", - "measurement_protocol_payload_template": "${measurement_protocol_payload_template_gcs_path}" + "source_query_template": "${cltv_query_template_gcs_path}" }, "cltv-180-90": { "activation_event_name": "maj_cltv_180_90", - "source_query_template": "${cltv_query_template_gcs_path}", - "measurement_protocol_payload_template": "${measurement_protocol_payload_template_gcs_path}" + "source_query_template": "${cltv_query_template_gcs_path}" }, "cltv-180-30": { "activation_event_name": "maj_cltv_180_30", - "source_query_template": "${cltv_query_template_gcs_path}", - "measurement_protocol_payload_template": "${measurement_protocol_payload_template_gcs_path}" + "source_query_template": "${cltv_query_template_gcs_path}" }, "purchase-propensity-30-15": { "activation_event_name": "maj_purchase_propensity_30_15", - "source_query_template": "${purchase_propensity_query_template_gcs_path}", - "measurement_protocol_payload_template": "${measurement_protocol_payload_template_gcs_path}" + "source_query_template": "${purchase_propensity_query_template_gcs_path}" + }, + "purchase-propensity-vbb-30-15": { + "activation_event_name": "maj_purchase_propensity_vbb_30_15", + "source_query_template": "${purchase_propensity_vbb_query_template_gcs_path}" }, "purchase-propensity-15-15": { "activation_event_name": "maj_purchase_propensity_15_15", - "source_query_template": "${purchase_propensity_query_template_gcs_path}", - "measurement_protocol_payload_template": "${measurement_protocol_payload_template_gcs_path}" + "source_query_template": "${purchase_propensity_query_template_gcs_path}" }, "purchase-propensity-15-7": { "activation_event_name": "maj_purchase_propensity_15_7", - "source_query_template": "${purchase_propensity_query_template_gcs_path}", - "measurement_protocol_payload_template": "${measurement_protocol_payload_template_gcs_path}" + "source_query_template": "${purchase_propensity_query_template_gcs_path}" }, "churn-propensity-30-15": { "activation_event_name": "maj_churn_propensity_30_15", - "source_query_template": "${churn_propensity_query_template_gcs_path}", - "measurement_protocol_payload_template": "${measurement_protocol_payload_template_gcs_path}" + "source_query_template": "${churn_propensity_query_template_gcs_path}" }, "churn-propensity-15-15": { "activation_event_name": "maj_churn_propensity_15_15", - "source_query_template": "${churn_propensity_query_template_gcs_path}", - "measurement_protocol_payload_template": "${measurement_protocol_payload_template_gcs_path}" + "source_query_template": "${churn_propensity_query_template_gcs_path}" }, "churn-propensity-15-7": { "activation_event_name": "maj_churn_propensity_15_7", - "source_query_template": "${churn_propensity_query_template_gcs_path}", - "measurement_protocol_payload_template": "${measurement_protocol_payload_template_gcs_path}" + "source_query_template": "${churn_propensity_query_template_gcs_path}" } } diff --git a/templates/app_payload_template.jinja2 b/templates/app_payload_template.jinja2 deleted file mode 100644 index 33179784..00000000 --- a/templates/app_payload_template.jinja2 +++ /dev/null @@ -1,20 +0,0 @@ -{ - "client_id": "{{client_id}}", - {{user_id}} - "timestamp_micros": "{{event_timestamp}}", - "nonPersonalizedAds": false, - "consent": { - "ad_user_data": "GRANTED", - "ad_personalization": "GRANTED" - }, - "user_properties": - {{user_properties}}, - "events": [ - { - "name": "{{event_name}}", - "params": { - "session_id": "{{session_id}}" - } - } - ] -} diff --git a/templates/load_vbb_activation_configuration.sql.tpl b/templates/load_vbb_activation_configuration.sql.tpl new file mode 100644 index 00000000..b256e9ca --- /dev/null +++ b/templates/load_vbb_activation_configuration.sql.tpl @@ -0,0 +1,33 @@ +-- Copyright 2023 Google LLC +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- Step 1: Load JSON data from GCS into the temporary table +LOAD DATA OVERWRITE `${project_id}.${dataset}.temp_json_data` +FROM FILES ( + format = 'JSON', + uris = ['${config_file_uri}'] +); + +-- Step 2: Transform and load into the final table +CREATE OR REPLACE TABLE `${project_id}.${dataset}.vbb_activation_configuration` AS + SELECT + t.activation_type AS activation_type, + dm.decile, + (t.value_norm * dm.multiplier) AS value + FROM + `${project_id}.${dataset}.temp_json_data` AS t, + UNNEST(t.decile_multiplier) AS dm; + +-- Step 3: Clean up temporary tables +DROP TABLE `${project_id}.${dataset}.temp_json_data`; diff --git a/templates/vbb_activation_configuration.jsonl b/templates/vbb_activation_configuration.jsonl new file mode 100644 index 00000000..a0c64ad1 --- /dev/null +++ b/templates/vbb_activation_configuration.jsonl @@ -0,0 +1,2 @@ +{"activation_type":"purchase-propensity","value_norm":150,"decile_multiplier":[{"decile":1,"multiplier":5.5},{"decile":2,"multiplier":3},{"decile":3,"multiplier":2},{"decile":4,"multiplier":1},{"decile":5,"multiplier":0},{"decile":6,"multiplier":0},{"decile":7,"multiplier":0},{"decile":8,"multiplier":0},{"decile":9,"multiplier":0},{"decile":10,"multiplier":0}]} +{"activation_type":"cltv","value_norm":500,"decile_multiplier":[{"decile":1,"multiplier":5.5},{"decile":2,"multiplier":3},{"decile":3,"multiplier":2},{"decile":4,"multiplier":1},{"decile":5,"multiplier":0},{"decile":6,"multiplier":0},{"decile":7,"multiplier":0},{"decile":8,"multiplier":0},{"decile":9,"multiplier":0},{"decile":10,"multiplier":0}]} \ No newline at end of file From 72b23d0c0bffd57b4013b5f0a63f25911bf3e7bf Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 16 Oct 2024 11:47:21 -0400 Subject: [PATCH 060/110] Update CONTRIBUTING.md --- CONTRIBUTING.md | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c5a0d10a..9ed2b7f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,31 +44,11 @@ Follow the typical GitHub guide on how to [fork a repo](https://docs.github.com/ ### Complete the installation guide -Complete the installation guide in a Google Cloud project in which you're developer and/or owner. - -### Configure Continuous Integration recipes - -Connect your GitHub repository by following this [guide](https://cloud.google.com/build/docs/automating-builds/github/connect-repo-github). - -In your Google Cloud project, configure Cloud Build triggers to be executed when you push code into your branch. Update the Clould build recipes in the `cloudbuild` folder and deploy them. - -### Update GCloud and Install Beta - -```bash -gcloud components update -gcloud components install beta -``` - -### Install packages to define components, run locally and compile pipeline - -```bash -pip install poetry -poetry install -``` +Complete the manual installation guide in a Google Cloud project in which you're developer and/or owner for testing purposes. ### Modify the code and configurations as you prefer -Do all the code changes you wish. +Do all the code changes you wish/need. If you're implementing new use cases, add these resources to the existing terraform module components. Otherwise, in case you're implementing a new component, implement your own terraform module for it. @@ -80,4 +60,4 @@ Change the values in the terraform templates located in the `infrastructure/terr terraform init terraform plan terraform apply -``` \ No newline at end of file +``` From a989853ba893cc4ef8fd96f0677a6de8f0451e95 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 16 Oct 2024 13:09:11 -0400 Subject: [PATCH 061/110] Update terraform-template.tfvars --- infrastructure/cloudshell/terraform-template.tfvars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/cloudshell/terraform-template.tfvars b/infrastructure/cloudshell/terraform-template.tfvars index d014be93..3f42716b 100644 --- a/infrastructure/cloudshell/terraform-template.tfvars +++ b/infrastructure/cloudshell/terraform-template.tfvars @@ -54,4 +54,4 @@ ga4_stream_id = "${MAJ_GA4_STREAM_ID}" project_owner_email = "${MAJ_DATAFORM_REPO_OWNER_EMAIL}" dataform_github_repo = "${MAJ_DATAFORM_GITHUB_REPO_URL}" -dataform_github_token = "GitHub access token generated for your forked dataform repo" +dataform_github_token = "${MAJ_DATAFORM_GITHUB_TOKEN}" From ff07123f6ee22f0ce96f86e4bbb19680ba80fc3c Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 16 Oct 2024 13:32:54 -0400 Subject: [PATCH 062/110] Add files via upload --- notebooks/quick_installation.ipynb | 129 ++++++++++++++++++++--------- 1 file changed, 92 insertions(+), 37 deletions(-) diff --git a/notebooks/quick_installation.ipynb b/notebooks/quick_installation.ipynb index 425c2581..d7c31085 100644 --- a/notebooks/quick_installation.ipynb +++ b/notebooks/quick_installation.ipynb @@ -5,6 +5,7 @@ "colab": { "provenance": [], "collapsed_sections": [ + "DDGHqJNhq5Oi", "mOISt4ShqIbc", "US36yJ8lmqnP" ] @@ -56,7 +57,14 @@ "source": [ "Follow this Colab notebook to quick install the Marketing Analytics Jumpstart solution on a Google Cloud Project.\n", "\n", - "> **Note:** You need access to the Google Analytics 4 Property, Google Ads Account and a Google Cloud project in which you will deploy Marketing Analytics Jumpstart.\n", + "> **Note:** You need access to the Google Analytics 4 Property, Google Ads Account and a Google Cloud project in which you will deploy Marketing Analytics Jumpstart, with the following permissions:\n", + ">> * Google Analytics Property Editor or Owner\n", + ">>\n", + ">> * Google Ads Reader\n", + ">>\n", + ">> * Project Owner for a Google Cloud Project\n", + ">>\n", + ">> * GitHub or GitLab account priviledges for repo creation and access token. [Details](https://cloud.google.com/dataform/docs/connect-repository)\n", "\n", "\n", "\n", @@ -98,7 +106,7 @@ { "cell_type": "markdown", "source": [ - "### 2. Input Google Cloud Project ID\n", + "### 2. Installation Configurations\n", "\n", "Fill-out the form, and Click the ( ▶ ) button.\n", "\n", @@ -112,49 +120,103 @@ "cell_type": "code", "source": [ "# @markdown ---\n", + "# @markdown # Google Cloud Platform\n", "# @markdown Copy the `Project ID` from the \"Project Info\" card in the console [Dashboard](https://console.cloud.google.com/home/dashboard).\n", - "GOOGLE_CLOUD_PROJECT_ID = \"gcp_project_id\" #@param {type:\"string\"}\n", + "GOOGLE_CLOUD_PROJECT_ID = \"your-project-id\" #@param {type:\"string\"}\n", "GOOGLE_CLOUD_QUOTA_PROJECT = GOOGLE_CLOUD_PROJECT_ID\n", "PROJECT_ID = GOOGLE_CLOUD_PROJECT_ID\n", + "MAJ_DEFAULT_PROJECT_ID = GOOGLE_CLOUD_PROJECT_ID\n", "# @markdown ---\n", - "MAJ_DEFAULT_PROJECT_ID=GOOGLE_CLOUD_PROJECT_ID\n", - "# @markdown Choose the default compute region for your resources, check detailed [list](https://cloud.google.com/compute/docs/regions-zones#available).\n", - "MAJ_DEFAULT_REGION = \"us-central1\" #@param [\"asia-east1\", \"asia-east2\", \"asia-northeast1\", \"asia-northeast3\", \"asia-south1\", \"asia-southeast1\", \"asia-southeast2\", \"australia-southeast1\", \"europe-west1\", \"europe-west2\", \"europe-west3\", \"europe-west4\", \"europe-west6\", \"europe-west12\", \"me-central1\", \"me-central2\", \"northamerica-northeast1\", \"southamerica-east1\", \"us-central1\", \"us-east1\", \"us-east4\", \"us-east5\", \"us-south1\", \"us-west1\", \"us-west2\", \"us-west4\"] {allow-input: true}\n", - "MAJ_MDS_PROJECT_ID=MAJ_DEFAULT_PROJECT_ID\n", - "MAJ_MDS_DATAFORM_PROJECT_ID=MAJ_DEFAULT_PROJECT_ID\n", - "MAJ_FEATURE_STORE_PROJECT_ID=MAJ_DEFAULT_PROJECT_ID\n", - "MAJ_ACTIVATION_PROJECT_ID=MAJ_DEFAULT_PROJECT_ID\n", - "# @markdown ---\n", - "# @markdown Choose the default data location for your data, check detailed [list](https://cloud.google.com/bigquery/docs/locations).\n", - "MAJ_MDS_DATA_LOCATION = \"US\" #@param [\"US\", \"EU\", \"asia-east1\", \"asia-east2\", \"asia-northeast1\", \"asia-northeast2\", \"asia-northeast3\", \"asia-south1\", \"asia-south2\", \"asia-southeast1\", \"asia-southeast2\", \"australia-southeast1\", \"australia-southeast2\", \"europe-central2\", \"europe-north1\", \"europe-west1\", \"europe-west2\", \"europe-west3\", \"europe-west4\", \"europe-west6\", \"europe-west8\", \"europe-west9\", \"northamerica-northeast1\", \"northamerica-northeast2\", \"southamerica-east1\", \"southamerica-west1\", \"us-central1\", \"us-central2\", \"us-east1\", \"us-east4\", \"us-west1\", \"us-west2\", \"us-west3\", \"us-west4\"] {allow-input: true}\n", - "# @markdown For a quick installation, deploy Marketing Analytics Jumpstart in the same project, which contains the GA4 and GAds exports datasets. If not the case, include \" (double quotes to the Project ID).\n", - "MAJ_GA4_EXPORT_PROJECT_ID = GOOGLE_CLOUD_PROJECT_ID # @param [\"GOOGLE_CLOUD_PROJECT_ID\", \"Your Project ID\"] {\"type\":\"raw\", \"allow-input\":true}\n", - "# @markdown For a quick installation, copy the Google Analytics 4 property number. You will find it in your Google Analytics 4 console or in the exported BigQuery dataset name. It must be in the following format: `\"analytics_PROPERTYNUMBER\"`.\n", - "MAJ_GA4_EXPORT_DATASET = \"analytics_123456789\" #@param {type:\"string\", placeholder:\" analytics_GA4 Property ID (e.g. analytics_434623633)\"}\n", - "# @markdown For a quick installation, deploy Marketing Analytics Jumpstart in the same project, which contains the GA4 and GAds exports datasets. If not the case, include \" (double quotes to the Project ID).\n", - "MAJ_ADS_EXPORT_PROJECT_ID = GOOGLE_CLOUD_PROJECT_ID # @param [\"GOOGLE_CLOUD_PROJECT_ID\", \"Your Project ID\"] {\"type\":\"raw\",\"allow-input\":true}\n", - "# @markdown For a quick installation, deploy Marketing Analytics Jumpstart in the same project, which contains the GA4 and GAds exports datasets. You have defined the dataset for your BigQuery DTS for GAds.\n", - "MAJ_ADS_EXPORT_DATASET = \"ads_dataset\" #@param {type:\"string\", placeholder:\"Dataset Name in the Data Transfer Service (e.g. ads_export)\"}\n", - "# @markdown For a quick installation, copy the Google Ads Account number. You will find it in your Google Ads console or in the exported BigQuery tables suffixes. It must be in the following format: `\"*_CUSTOMERID\"` (without dashes).\n", - "MAJ_ADS_EXPORT_TABLE_SUFFIX = \"_1234567890\" #@param {type:\"string\", placeholder:\"GAds Account Number (e.g. _4717384083)\"}\n", - "# @markdown The website your Google Analytics 4 events are coming from.\n", - "MAJ_WEBSITE_URL = \"https://shop.googlemerchandisestore.com\" #@param {type:\"string\", placeholder:\"Full web URL\"}\n", + "# @markdown # Google Analytics 4\n", "# @markdown For a quick installation, copy the Google Analytics 4 property ID and stream ID. You will find it in your Google Analytics 4 console, under Admin settings.\n", - "MAJ_GA4_PROPERTY_ID = \"123456789\" #@param {type:\"string\"}\n", + "MAJ_GA4_PROPERTY_ID = \"1234567890\" #@param {type:\"string\"}\n", "MAJ_GA4_STREAM_ID = \"1234567890\" #@param {type:\"string\"}\n", + "# @markdown The website your Google Analytics 4 events are coming from.\n", + "MAJ_WEBSITE_URL = \"https://shop.googlemerchandisestore.com\" #@param {type:\"string\", placeholder:\"Full web URL\"}\n", + "# @markdown ---\n", + "# @markdown # Google Ads\n", + "# @markdown For a quick installation, copy the Google Ads Customer ID. You will find it in your Google Ads console. It must be in the following format: `\"CUSTOMERID\"` (without dashes).\n", + "MAJ_ADS_EXPORT_TABLE_SUFFIX = \"1234567890\" #@param {type:\"string\", placeholder:\"GAds Account Number (e.g. 4717384083)\"}\n", + "MAJ_ADS_EXPORT_TABLE_SUFFIX = \"_\"+MAJ_ADS_EXPORT_TABLE_SUFFIX\n", + "# @markdown ---\n", + "# @markdown # Github\n", "# @markdown For a quick installation, use your email credentials that allows you to create a dataform repository connected to a remote Github repository, more info [here](https://cloud.google.com/dataform/docs/connect-repository).\n", "MAJ_DATAFORM_REPO_OWNER_EMAIL = \"user@company.com\" #@param {type:\"string\", placeholder:\"user@company.com\"}\n", - "MAJ_DATAFORM_GITHUB_REPO_URL = \"https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart-dataform.git\" #@param {type:\"string\"}\n", + "MAJ_DATAFORM_GITHUB_REPO_URL = \"https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart-dataform.git\"\n", "# @markdown For a quick installation, reuse or create your [GitHub personal access token](https://cloud.google.com/dataform/docs/connect-repository#connect-https)\n", "MAJ_DATAFORM_GITHUB_TOKEN = \"Your github token\" #@param {type:\"string\"}\n", "# @markdown ---\n", "\n", "import os\n", - "os.environ['LOCATION'] = MAJ_DEFAULT_REGION\n", "os.environ['GOOGLE_CLOUD_PROJECT_ID'] = GOOGLE_CLOUD_PROJECT_ID\n", "os.environ['GOOGLE_CLOUD_QUOTA_PROJECT'] = GOOGLE_CLOUD_QUOTA_PROJECT\n", "os.environ['PROJECT_ID'] = PROJECT_ID\n", "os.environ['MAJ_DEFAULT_PROJECT_ID'] = MAJ_DEFAULT_PROJECT_ID\n", + "!export SOURCE_ROOT=$(pwd)\n", + "!export TERRAFORM_RUN_DIR={SOURCE_ROOT}/infrastructure/terraform\n", + "REPO=\"marketing-analytics-jumpstart\"\n", + "!if [ ! -d \"/content/{REPO}\" ]; then git clone https://github.com/GoogleCloudPlatform/{REPO}.git ; fi\n", + "SOURCE_ROOT=\"/content/\"+REPO\n", + "%cd {SOURCE_ROOT}\n", + "!echo \"Enabling APIs\"\n", + "!gcloud config set project {GOOGLE_CLOUD_PROJECT_ID}\n", + "!. ~/.bashrc\n", + "!gcloud projects add-iam-policy-binding {GOOGLE_CLOUD_PROJECT_ID} --member user:{MAJ_DATAFORM_REPO_OWNER_EMAIL} --role=roles/bigquery.admin\n", + "!source ./scripts/common.sh && enable_all_apis > /dev/null\n", + "!echo \"APIs enabled\"\n", + "\n", + "from google.cloud import bigquery\n", + "# Construct a BigQuery client object.\n", + "client = bigquery.Client(project=GOOGLE_CLOUD_PROJECT_ID)\n", + "# Replace with your desired dataset ID suffix\n", + "dataset_id_suffix = MAJ_GA4_PROPERTY_ID\n", + "location = ''\n", + "dataset_id = ''\n", + "# Iterate through datasets and find the one with the matching suffix\n", + "for dataset in client.list_datasets():\n", + " dataset_id = dataset.dataset_id\n", + " if dataset_id.endswith(dataset_id_suffix):\n", + " dataset_ref = client.get_dataset(dataset.reference)\n", + " location = dataset_ref.location\n", + " print(f\"GA4 Dataset ID: {dataset_id}, Location: {location}\")\n", + " break\n", + "else:\n", + " print(f\"No dataset found with ID suffix: {dataset_id_suffix}\")\n", + "MAJ_MDS_DATA_LOCATION = location\n", + "MAJ_GA4_EXPORT_PROJECT_ID = GOOGLE_CLOUD_PROJECT_ID\n", + "MAJ_GA4_EXPORT_DATASET = dataset_id\n", + "\n", + "if MAJ_MDS_DATA_LOCATION == 'US':\n", + " MAJ_DEFAULT_REGION = 'us-central1'\n", + "elif MAJ_MDS_DATA_LOCATION == 'EU':\n", + " MAJ_DEFAULT_REGION = 'europe-west1'\n", + "else:\n", + " MAJ_DEFAULT_REGION = MAJ_MDS_DATA_LOCATION\n", + "MAJ_MDS_PROJECT_ID=MAJ_DEFAULT_PROJECT_ID\n", + "MAJ_MDS_DATAFORM_PROJECT_ID=MAJ_DEFAULT_PROJECT_ID\n", + "MAJ_FEATURE_STORE_PROJECT_ID=MAJ_DEFAULT_PROJECT_ID\n", + "MAJ_ACTIVATION_PROJECT_ID=MAJ_DEFAULT_PROJECT_ID\n", + "MAJ_ADS_EXPORT_PROJECT_ID = GOOGLE_CLOUD_PROJECT_ID\n", + "project_id=MAJ_ADS_EXPORT_PROJECT_ID\n", + "location = MAJ_MDS_DATA_LOCATION\n", + "table_suffix = MAJ_ADS_EXPORT_TABLE_SUFFIX\n", + "# Query to find datasets that contain tables with the specified suffix.\n", + "query = f\"\"\"\n", + " SELECT table_schema as dataset_id\n", + " FROM `{project_id}.region-{location}.INFORMATION_SCHEMA.TABLES`\n", + " WHERE table_name LIKE '%{table_suffix}'\n", + " GROUP BY table_schema\n", + "\"\"\"\n", + "# Run the query and fetch the results.\n", + "query_job = client.query(query)\n", + "results = query_job.result()\n", + "# Print the dataset IDs that match the criteria.\n", + "ads_dataset_id = ''\n", + "for row in results:\n", + " ads_dataset_id = row.dataset_id\n", + " print(f\"GAds dataset: {row.dataset_id}, Location: {location}\")\n", + "MAJ_ADS_EXPORT_DATASET = ads_dataset_id\n", + "\n", "os.environ['MAJ_DEFAULT_REGION'] = MAJ_DEFAULT_REGION\n", "os.environ['MAJ_MDS_PROJECT_ID'] = MAJ_MDS_PROJECT_ID\n", "os.environ['MAJ_MDS_DATAFORM_PROJECT_ID'] = MAJ_MDS_DATAFORM_PROJECT_ID\n", @@ -173,12 +235,6 @@ "os.environ['MAJ_DATAFORM_GITHUB_REPO_URL'] = MAJ_DATAFORM_GITHUB_REPO_URL\n", "os.environ['MAJ_DATAFORM_GITHUB_TOKEN'] = MAJ_DATAFORM_GITHUB_TOKEN\n", "\n", - "!export SOURCE_ROOT=$(pwd)\n", - "!export TERRAFORM_RUN_DIR={SOURCE_ROOT}/infrastructure/terraform\n", - "REPO=\"marketing-analytics-jumpstart\"\n", - "!if [ ! -d \"/content/{REPO}\" ]; then git clone https://github.com/GoogleCloudPlatform/{REPO}.git ; fi\n", - "SOURCE_ROOT=\"/content/\"+REPO\n", - "%cd {SOURCE_ROOT}\n", "!sudo apt-get -qq -o=Dpkg::Use-Pty=0 install gettext\n", "!envsubst < \"{SOURCE_ROOT}/infrastructure/cloudshell/terraform-template.tfvars\" > \"{SOURCE_ROOT}/infrastructure/terraform/terraform.tfvars\"\n", "\n", @@ -203,6 +259,8 @@ "\n", "Click the ( ▶ ) button to create your Terraform application default credentials to the Google Cloud Project.\n", "\n", + "*To complete this step, you will be prompted to copy/paste a password from another window into the prompt below*\n", + "\n", "***Time: 2 minute.***" ], "metadata": { @@ -213,9 +271,6 @@ "cell_type": "code", "source": [ "# @title\n", - "!echo \"Enabling APIs\"\n", - "!source ./scripts/common.sh && enable_all_apis > /dev/null\n", - "!echo \"APIs enabled\"\n", "!gcloud config set disable_prompts false\n", "!gcloud auth application-default login --quiet --scopes=\"openid,https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/sqlservice.login,https://www.googleapis.com/auth/analytics,https://www.googleapis.com/auth/analytics.edit,https://www.googleapis.com/auth/analytics.provision,https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/accounts.reauth\"\n", "!gcloud auth application-default set-quota-project {PROJECT_ID}\n", From 1e105aec0a8d8f787a4370fec51268c20280c79a Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Thu, 24 Oct 2024 14:40:28 -0400 Subject: [PATCH 063/110] Update README.md --- infrastructure/terraform/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index 328ee8c1..e0ba4aa9 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -56,7 +56,7 @@ Also, this method allows you to extend this solution and develop it to satisfy y CLOUDSDK_PYTHON=python3.10 ``` -1. Install Python's Poetry and set Poetry to use Python3.8-3.10 version +1. Install Python's Poetry and set Poetry to use Python 3.10 version [Poetry](https://python-poetry.org/docs/) is a Python's tool for dependency management and packaging. @@ -69,6 +69,7 @@ Also, this method allows you to extend this solution and develop it to satisfy y sudo apt update sudo apt install pipx pipx ensurepath + pipx install poetry ``` Verify that `poetry` is on your $PATH variable: ```shell @@ -105,7 +106,7 @@ Also, this method allows you to extend this solution and develop it to satisfy y 1. Review your Terraform version - Make sure you have installed terraform version is 1.5.7. We recommend you to use [tfenv](https://github.com/tfutils/tfenv) to manage your terraform version. + Make sure you have installed terraform version is 1.9.7. We recommend you to use [tfenv](https://github.com/tfutils/tfenv) to manage your terraform version. `Tfenv` is a version manager inspired by rbenv, a Ruby programming language version manager. To install `tfenv`, run the following commands: @@ -169,7 +170,7 @@ Also, this method allows you to extend this solution and develop it to satisfy y terraform -chdir="${TERRAFORM_RUN_DIR}" validate ``` - If you run into errors, review and edit the `${TERRAFORM_RUN_DIR}/terraform.tfvars` file. + If you run into errors, review and edit the `${TERRAFORM_RUN_DIR}/terraform.tfvars` file. However, if there are still configuration errors, open a new [github issue](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/issues/). 1. Run Terraform to create resources: From e22efb2c6dbbaeddde8fda2104a5a384e30473c6 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Thu, 24 Oct 2024 14:42:09 -0400 Subject: [PATCH 064/110] Update tutorial.md --- infrastructure/cloudshell/tutorial.md | 101 ++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 16 deletions(-) diff --git a/infrastructure/cloudshell/tutorial.md b/infrastructure/cloudshell/tutorial.md index 964421cf..b423dc7a 100644 --- a/infrastructure/cloudshell/tutorial.md +++ b/infrastructure/cloudshell/tutorial.md @@ -3,7 +3,7 @@ ## Prerequisites Make sure you have completed all the steps under the [Prerequisites](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/tree/main/infrastructure#prerequisites) section. -## Choose your primary cloud project for MAJ +## Choose your primary cloud project for Marketing Analytics Jumpstart Set the default project id for `gcloud` @@ -12,23 +12,58 @@ export PROJECT_ID="" gcloud config set project $PROJECT_ID ``` -## Install Poetry +## Install or update Python3 +Install a compatible version of Python 3.8-3.10 and set the CLOUDSDK_PYTHON environment variable to point to it. ```sh -curl -sSL https://install.python-poetry.org | python3 - +sudo apt-get install python3.10 +CLOUDSDK_PYTHON=python3.10 ``` -Add poetry to PATH + +## Install Python's Poetry and set Poetry to use Python 3.10 version +[Poetry](https://python-poetry.org/docs/) is a Python's tool for dependency management and packaging. +If you are installing on in Cloud Shell use the following commands: +```sh +pipx install poetry +``` +If you don't have pipx installed - follow the [Pipx installation guide](https://pipx.pypa.io/stable/installation/) ```sh -export PATH="$HOME/.local/bin:$PATH" +sudo apt update +sudo apt install pipx +pipx ensurepath +pipx install poetry +``` +Verify that `poetry` is on your $PATH variable: +```sh +poetry --version +``` +If it fails - add it to your $PATH variable: +```sh +export PATH="$HOME/.local/bin:$PATH" ``` Verify poetry is properly installed, run: ```sh poetry --version ``` +Set poetry to use your latest python3 +```sh +poetry env use python3 +``` Install python dependencies, run: ```sh poetry install ``` +## Authenticate with additional OAuth 2.0 scopes +```sh +gcloud auth login +gcloud auth application-default login --quiet --scopes="openid,https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/sqlservice.login,https://www.googleapis.com/auth/analytics,https://www.googleapis.com/auth/analytics.edit,https://www.googleapis.com/auth/analytics.provision,https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/accounts.reauth" +gcloud auth application-default set-quota-project $PROJECT_ID +export GOOGLE_APPLICATION_CREDENTIALS=/Users//.config/gcloud/application_default_credentials.json +``` +**Note:** You may receive an error message informing the Cloud Resource Manager API has not been used/enabled for your project, similar to the following: +ERROR: (gcloud.auth.application-default.login) User [@.com] does not have permission to access projects instance [:testIamPermissions] (or it may not exist): Cloud Resource Manager API has not been used in project before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudresourcemanager.googleapis.com/overview?project= then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry. +On the next step, the Cloud Resource Manager API will be enabled and, then, your credentials will finally work. + ## Set environment variables Run the set variable script and follow the steps to provide value for every variable: ```sh @@ -36,16 +71,29 @@ Run the set variable script and follow the steps to provide value for every vari ``` ## Create the Terraform variables file - ```sh envsubst < "${SOURCE_ROOT}/infrastructure/cloudshell/terraform-template.tfvars" > "${TERRAFORM_RUN_DIR}/terraform.tfvars" ``` Provide value for the `dataform_github_token` variable in the generated terraform.tfvars file -## Authenticate with additional OAuth 2.0 scopes + +## Review your Terraform version +Make sure you have installed terraform version is 1.9.7. We recommend you to use [tfenv](https://github.com/tfutils/tfenv) to manage your terraform version. +`Tfenv` is a version manager inspired by rbenv, a Ruby programming language version manager. +To install `tfenv`, run the following commands: ```sh -. scripts/common.sh;set_application_default_credentials $(pwd);set +o nounset;set +o errexit +# Install via Homebrew or via Arch User Repository (AUR) +# Follow instructions on https://github.com/tfutils/tfenv +# Now, install the recommended terraform version +tfenv install 1.9.7 +tfenv use 1.9.7 +terraform --version +``` +For instance, the output on MacOS should be like: +```sh +Terraform v1.9.7 +on darwin_amd64 ``` ## Create Terraform remote backend @@ -61,16 +109,37 @@ terraform init: terraform -chdir="${TERRAFORM_RUN_DIR}" init ``` -terraform apply: +terraform plan: ```sh -terraform -chdir="${TERRAFORM_RUN_DIR}" apply +terraform -chdir="${TERRAFORM_RUN_DIR}" plan ``` -## Create Looker Studio Dashboard -Extract the URL used to create the dashboard from the Terraform output value: +terraform validate: +```sh +terraform -chdir="${TERRAFORM_RUN_DIR}" validate +``` +If you run into errors, review and edit the configurations `${TERRAFORM_RUN_DIR}/terraform.tfvars` file. However, if there are still resources not deployed, open a new [github issue](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/issues/). + +terraform apply: ```sh -echo "$(terraform -chdir=${TERRAFORM_RUN_DIR} output -raw lookerstudio_create_dashboard_url)" +terraform -chdir="${TERRAFORM_RUN_DIR}" apply ``` -1. Click on the long URL from the command output. This will take you to the copy dashboard flow in Looker Studio. -1. The copy may take a few moments to execute. If it does not, close the tab and try clicking the link again. -1. Click on the button `Edit and share` to follow through and finish the copy process. +If you don't have a successful execution of certain resources, re-run `terraform -chdir="${TERRAFORM_RUN_DIR}" apply` a few more times until all is deployed successfully. However, if there are still resources not deployed, open a new [github issue](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/issues/). + +## Resources created + +At this time, the Terraform scripts in this folder perform the following tasks: + +- Enables the APIs needed +- IAM bindings needed for the GCP services used +- Secret in GCP Secret manager for the private GitHub repo +- Dataform repository connected to the GitHub repo +- Deploys the marketing data store (MDS), feature store, ML pipelines and activation application + +## Next Steps + +Follow the [post-installation guide](./POST-INSTALLATION.md) to start you daily operations. + +It is recommended to follow the post-installation guide before deploying the Looker Studio Dashboard, because you need the data and predictions tables to exist before consuming insights in your reports. + +**The Looker Studio Dashboard deployment is a separate [step](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/python/lookerstudio/README.md).** From 70fda057fd3d612207b5d154a0d04e0dde4def22 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Thu, 24 Oct 2024 14:43:42 -0400 Subject: [PATCH 065/110] Update tutorial.md --- infrastructure/cloudshell/tutorial.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/cloudshell/tutorial.md b/infrastructure/cloudshell/tutorial.md index b423dc7a..e0293e30 100644 --- a/infrastructure/cloudshell/tutorial.md +++ b/infrastructure/cloudshell/tutorial.md @@ -118,7 +118,7 @@ terraform validate: ```sh terraform -chdir="${TERRAFORM_RUN_DIR}" validate ``` -If you run into errors, review and edit the configurations `${TERRAFORM_RUN_DIR}/terraform.tfvars` file. However, if there are still resources not deployed, open a new [github issue](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/issues/). +If you run into errors, review and edit the configurations `${TERRAFORM_RUN_DIR}/terraform.tfvars` file. However, if there are still configurations errors, open a new [github issue](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/issues/). terraform apply: ```sh From 6db86c14670e3e99e4ea138e74e41ec14534af7a Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Thu, 24 Oct 2024 14:49:27 -0400 Subject: [PATCH 066/110] Update README.md --- infrastructure/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infrastructure/README.md b/infrastructure/README.md index 917f0ed9..51e5c9d3 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -17,6 +17,8 @@ This document describes the permission and data prerequisites for a successful i Once you have chosen your route, check the permissions and data prerequisites in detail. +**Note:** If none of these routes are ideal for you, run this [installation notebook 📔](https://colab.sandbox.google.com/github/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/notebooks/quick_installation.ipynb) on Google Colaboratory and leverage Marketing Analytics Jumpstart in under 30 minutes. + ## Permissions Prerequisites ### Permissions to deploy infrastructure and access source data From ef5a868e3fb7ff28eba82a716705d8946a2575dd Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 25 Oct 2024 03:54:31 -0400 Subject: [PATCH 067/110] Implementing Secrets Regionalization (#212) * predicting for only the users with traffic in the past 72h - purchase propensity * running inference only for users events in the past 72h * including 72h users for all models predictions * considering null values in TabWorkflow models * deleting unused pipfile * upgrading lib versions * implementing reporting preprocessing as a new pipeline * adding more code documentation * adding important information on the main README.md and DEVELOPMENT.md * adding schedule run name and more code documentation * implementing a new scheduler using the vertex ai sdk & adding user_id to procedures for consistency * adding more code documentation * adding code doc to the python custom component * adding more code documentation * fixing aggregated predictions query * removing unnecessary resources from deployment * Writing MDS guide * adding the MDS developer and troubleshooting documentation * fixing deployment for activation pipelines and gemini dataset * Update README.md * Update README.md * Update README.md * Update README.md * removing deprecated api * fixing purchase propensity pipelines names * adding extra condition for when there is not enough data for the window interval to be applied on backfill procedures * adding more instructions for post deployment and fixing issues when GA4 export was configured for less than 10 days * removing unnecessary comments * adding the number of past days to process in the variables files * adding comment about combining data from different ga4 export datasets to data store * fixing small issues with feature engineering and ml pipelines * fixing hyper parameter tuning for kmeans modeling * fixing optuna parameters * adding cloud shell image * fixing the list of all possible users in the propensity training preparation tables * additional guardrails for when there is not enough data * adding more documentation * adding more doc to feature store * add feature store documentation * adding ml pipelines docs * adding ml pipelines docs * adding more documentation * adding user agent client info * fixing scope of client info * fix * removing client_info from vertex components * fixing versioning of tf submodules * reconfiguring meta providers * fixing issue 187 * chore(deps): upgrade terraform providers and modules version * chore(deps): set the provider version * chore: formatting * fix: brand naming * fix: typo * fixing secrets issue * implementing secrets region as tf variable * implementing secrets region as tf variable * last changes requested by lgrangeau * documenting keys location better --------- Co-authored-by: Carlos Timoteo Co-authored-by: Laurent Grangeau --- infrastructure/terraform/.terraform.lock.hcl | 1 + .../terraform/modules/activation/main.tf | 126 +++++++++++++++++- .../modules/data-store/secretmanager.tf | 18 ++- scripts/common.sh | 1 + 4 files changed, 141 insertions(+), 5 deletions(-) diff --git a/infrastructure/terraform/.terraform.lock.hcl b/infrastructure/terraform/.terraform.lock.hcl index d4563d79..a0f311d4 100644 --- a/infrastructure/terraform/.terraform.lock.hcl +++ b/infrastructure/terraform/.terraform.lock.hcl @@ -149,6 +149,7 @@ provider "registry.terraform.io/hashicorp/random" { provider "registry.terraform.io/hashicorp/template" { version = "2.2.0" hashes = [ + "h1:0wlehNaxBX7GJQnPfQwTNvvAf38Jm0Nv7ssKGMaG6Og=", "h1:94qn780bi1qjrbC3uQtjJh3Wkfwd5+tTtJHOb7KTg9w=", "zh:01702196f0a0492ec07917db7aaa595843d8f171dc195f4c988d2ffca2a06386", "zh:09aae3da826ba3d7df69efeb25d146a1de0d03e951d35019a0f80e4f58c89b53", diff --git a/infrastructure/terraform/modules/activation/main.tf b/infrastructure/terraform/modules/activation/main.tf index 093f3fb3..acf901ec 100644 --- a/infrastructure/terraform/modules/activation/main.tf +++ b/infrastructure/terraform/modules/activation/main.tf @@ -90,6 +90,7 @@ module "project_services" { "analyticsadmin.googleapis.com", "eventarc.googleapis.com", "run.googleapis.com", + "cloudkms.googleapis.com" ] } @@ -301,6 +302,32 @@ resource "null_resource" "check_cloudbuild_api" { ] } +# This resource executes gcloud commands to check whether the IAM API is enabled. +# Since enabling APIs can take a few seconds, we need to make the deployment wait until the API is enabled before resuming. +resource "null_resource" "check_cloudkms_api" { + provisioner "local-exec" { + command = <<-EOT + COUNTER=0 + MAX_TRIES=100 + while ! gcloud services list --project=${module.project_services.project_id} | grep -i "cloudkms.googleapis.com" && [ $COUNTER -lt $MAX_TRIES ] + do + sleep 6 + printf "." + COUNTER=$((COUNTER + 1)) + done + if [ $COUNTER -eq $MAX_TRIES ]; then + echo "cloud kms api is not enabled, terraform can not continue!" + exit 1 + fi + sleep 20 + EOT + } + + depends_on = [ + module.project_services + ] +} + module "bigquery" { source = "terraform-google-modules/bigquery/google" version = "8.1.0" @@ -410,26 +437,117 @@ data "external" "ga4_measurement_properties" { ] } +# It's used to create unique names for resources like KMS key rings or crypto keys, +# ensuring they don't clash with existing resources. +resource "random_id" "random_suffix" { + byte_length = 2 +} + +# This ensures that Secret Manager has a service identity within your project. +# This identity is crucial for securely managing secrets and allowing Secret Manager +# to interact with other Google Cloud services on your behalf. +resource "google_project_service_identity" "secretmanager_sa" { + provider = google-beta + project = null_resource.check_cloudkms_api.id != "" ? module.project_services.project_id : var.project_id + service = "secretmanager.googleapis.com" +} + # This Key Ring can then be used to store and manage encryption keys for various purposes, + # such as encrypting data at rest or protecting secrets. +resource "google_kms_key_ring" "key_ring_regional" { + name = "key_ring_regional-${random_id.random_suffix.hex}" + # If you want your replicas in other locations, change the location in the var.location variable passed as a parameter to this submodule. + # if you your replicas stored global, set the location = "global". + location = var.location + project = null_resource.check_cloudkms_api.id != "" ? module.project_services.project_id : var.project_id +} + +# This key can then be used for various encryption operations, +# such as encrypting data before storing it in Google Cloud Storage +# or protecting secrets within your application. +resource "google_kms_crypto_key" "crypto_key_regional" { + name = "crypto-key-${random_id.random_suffix.hex}" + key_ring = google_kms_key_ring.key_ring_regional.id +} + +# Defines an IAM policy that explicitly grants the Secret Manager service account +# the ability to encrypt and decrypt data using a specific CryptoKey. This is a +# common pattern for securely managing secrets, allowing Secret Manager to encrypt +# or decrypt data without requiring direct access to the underlying encryption key material. +data "google_iam_policy" "crypto_key_encrypter_decrypter" { + binding { + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + + members = [ + "serviceAccount:${google_project_service_identity.secretmanager_sa.email}" + ] + } + + depends_on = [ + google_project_service_identity.secretmanager_sa, + google_kms_key_ring.key_ring_regional, + google_kms_crypto_key.crypto_key_regional + ] +} + +# It sets the IAM policy for a KMS CryptoKey, specifically granting permissions defined +# in another data source. +resource "google_kms_crypto_key_iam_policy" "crypto_key" { + crypto_key_id = google_kms_crypto_key.crypto_key_regional.id + policy_data = data.google_iam_policy.crypto_key_encrypter_decrypter.policy_data +} + +# It sets the IAM policy for a KMS Key Ring, granting specific permissions defined +# in a data source. +resource "google_kms_key_ring_iam_policy" "key_ring" { + key_ring_id = google_kms_key_ring.key_ring_regional.id + policy_data = data.google_iam_policy.crypto_key_encrypter_decrypter.policy_data +} + # This module stores the values ga4-measurement-id and ga4-measurement-secret in Google Cloud Secret Manager. module "secret_manager" { source = "GoogleCloudPlatform/secret-manager/google" version = "0.4.0" - project_id = null_resource.check_secretmanager_api.id != "" ? module.project_services.project_id : var.project_id + project_id = google_kms_crypto_key_iam_policy.crypto_key.etag != "" && google_kms_key_ring_iam_policy.key_ring.etag != "" ? module.project_services.project_id : var.project_id secrets = [ { name = "ga4-measurement-id" secret_data = (var.ga4_measurement_id == null || var.ga4_measurement_secret == null) ? data.external.ga4_measurement_properties[0].result["measurement_id"] : var.ga4_measurement_id - automatic_replication = true + automatic_replication = false }, { name = "ga4-measurement-secret" secret_data = (var.ga4_measurement_id == null || var.ga4_measurement_secret == null) ? data.external.ga4_measurement_properties[0].result["measurement_secret"] : var.ga4_measurement_secret - automatic_replication = true + automatic_replication = false }, ] + # By commenting the user_managed_replication block, you will deploy replicas that may store the secret in different locations in the globe. + # This is not a desired behaviour, make sure you're aware of it before doing it. + # By default, to respect resources location, we prevent resources from being deployed globally by deploying secrets in the same region of the compute resources. + user_managed_replication = { + ga4-measurement-id = [ + # If you want your replicas in other locations, uncomment the following lines and add them here. + # Check this example, as reference: https://github.com/GoogleCloudPlatform/terraform-google-secret-manager/blob/main/examples/multiple/main.tf#L91 + { + location = var.location + kms_key_name = google_kms_crypto_key.crypto_key_regional.id + } + ] + ga4-measurement-secret = [ + { + location = var.location + kms_key_name = google_kms_crypto_key.crypto_key_regional.id + } + ] + } + depends_on = [ - data.external.ga4_measurement_properties + data.external.ga4_measurement_properties, + google_kms_crypto_key.crypto_key_regional, + google_kms_key_ring.key_ring_regional, + google_project_service_identity.secretmanager_sa, + google_kms_crypto_key_iam_policy.crypto_key, + google_kms_key_ring_iam_policy.key_ring ] } diff --git a/infrastructure/terraform/modules/data-store/secretmanager.tf b/infrastructure/terraform/modules/data-store/secretmanager.tf index 33c71ed7..2d4d6889 100644 --- a/infrastructure/terraform/modules/data-store/secretmanager.tf +++ b/infrastructure/terraform/modules/data-store/secretmanager.tf @@ -16,8 +16,24 @@ resource "google_secret_manager_secret" "github-secret" { secret_id = "Github_token" project = null_resource.check_secretmanager_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id + # This replication strategy will deploy replicas that may store the secret in different locations in the globe. + # This is not a desired behaviour, make sure you're aware of it before enabling it. + #replication { + # auto {} + #} + + # By default, to respect resources location, we prevent resources from being deployed globally by deploying secrets in the same region of the compute resources. + # If the replication strategy is seto to `auto {}` above, comment the following lines or else there will be an error being issued by terraform. replication { - auto {} + user_managed { + replicas { + location = var.google_default_region + } + # If you want your replicas in other locations, uncomment the following lines and add them here. + #replicas { + # location = "us-east1" + #} + } } depends_on = [ diff --git a/scripts/common.sh b/scripts/common.sh index 926eec7f..57d314fa 100644 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -46,6 +46,7 @@ declare -a apis_array=("cloudresourcemanager.googleapis.com" "bigquerymigration.googleapis.com" "bigquerydatatransfer.googleapis.com" "dataform.googleapis.com" + "cloudkms.googleapis.com" ) get_project_id() { From b8fe77365794e30b420c1e42c5952965907b5668 Mon Sep 17 00:00:00 2001 From: Laurent Grangeau Date: Fri, 25 Oct 2024 09:55:16 +0200 Subject: [PATCH 068/110] feat: add dependabot for automatic updates of tf modules (#205) Co-authored-by: Laurent Grangeau --- .github/dependabot.yaml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/dependabot.yaml diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 00000000..446421eb --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,31 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +version: 2 +updates: + - directories: + - /infrastructure/terraform + commit-message: + prefix: "chore(deps)" + package-ecosystem: "terraform" + schedule: + interval: "daily" + groups: + terraform: + applies-to: version-updates + patterns: + - hashicorp/* + - terraform-google-modules/* + - GoogleCloudPlatform/* From b4d0fa7ccda2d9fbc02e509deb6f9a57b67403ac Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 28 Oct 2024 11:07:45 -0400 Subject: [PATCH 069/110] Update pipeline_ops.py --- python/pipelines/pipeline_ops.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/pipelines/pipeline_ops.py b/python/pipelines/pipeline_ops.py index a1b94675..60a9cd9b 100644 --- a/python/pipelines/pipeline_ops.py +++ b/python/pipelines/pipeline_ops.py @@ -676,6 +676,7 @@ def schedule_pipeline( pipeline_job = aiplatform.PipelineJob( template_path=template_path, pipeline_root=pipeline_root, + location=region, display_name=f"{pipeline_name}", ) @@ -903,4 +904,4 @@ def run_pipeline( if (pl.has_failed): raise RuntimeError("Pipeline execution failed") return pl - \ No newline at end of file + From ec07683a3e85d2b157a8033876920fa5547cd99e Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 29 Oct 2024 13:35:56 -0400 Subject: [PATCH 070/110] Implementing support to use VPC Network Peering (#219) * predicting for only the users with traffic in the past 72h - purchase propensity * running inference only for users events in the past 72h * including 72h users for all models predictions * considering null values in TabWorkflow models * deleting unused pipfile * upgrading lib versions * implementing reporting preprocessing as a new pipeline * adding more code documentation * adding important information on the main README.md and DEVELOPMENT.md * adding schedule run name and more code documentation * implementing a new scheduler using the vertex ai sdk & adding user_id to procedures for consistency * adding more code documentation * adding code doc to the python custom component * adding more code documentation * fixing aggregated predictions query * removing unnecessary resources from deployment * Writing MDS guide * adding the MDS developer and troubleshooting documentation * fixing deployment for activation pipelines and gemini dataset * Update README.md * Update README.md * Update README.md * Update README.md * removing deprecated api * fixing purchase propensity pipelines names * adding extra condition for when there is not enough data for the window interval to be applied on backfill procedures * adding more instructions for post deployment and fixing issues when GA4 export was configured for less than 10 days * removing unnecessary comments * adding the number of past days to process in the variables files * adding comment about combining data from different ga4 export datasets to data store * fixing small issues with feature engineering and ml pipelines * fixing hyper parameter tuning for kmeans modeling * fixing optuna parameters * adding cloud shell image * fixing the list of all possible users in the propensity training preparation tables * additional guardrails for when there is not enough data * adding more documentation * adding more doc to feature store * add feature store documentation * adding ml pipelines docs * adding ml pipelines docs * adding more documentation * adding user agent client info * fixing scope of client info * fix * removing client_info from vertex components * fixing versioning of tf submodules * reconfiguring meta providers * fixing issue 187 * chore(deps): upgrade terraform providers and modules version * chore(deps): set the provider version * chore: formatting * fix: brand naming * fix: typo * fixing secrets issue * implementing secrets region as tf variable * implementing secrets region as tf variable * last changes requested by lgrangeau * documenting keys location better * implementing vpc peering network --------- Co-authored-by: Carlos Timoteo Co-authored-by: Laurent Grangeau --- config/config.yaml.tftpl | 160 ++++++++++++++++++ .../terraform/modules/pipelines/main.tf | 30 +++- .../terraform/modules/pipelines/pipelines.tf | 3 +- pyproject.toml | 13 +- python/base_component_image/pyproject.toml | 14 +- python/pipelines/pipeline_ops.py | 81 ++++++++- python/pipelines/scheduler.py | 4 +- scripts/common.sh | 1 + 8 files changed, 288 insertions(+), 18 deletions(-) diff --git a/config/config.yaml.tftpl b/config/config.yaml.tftpl index f644a598..65dd2e91 100644 --- a/config/config.yaml.tftpl +++ b/config/config.yaml.tftpl @@ -189,6 +189,12 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false # The `state` defines the state of the pipeline. # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED @@ -267,6 +273,12 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false # The `state` defines the state of the pipeline. # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED @@ -326,6 +338,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED pipeline_parameters: project_id: "${project_id}" @@ -381,6 +401,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED pipeline_parameters: project_id: "${project_id}" @@ -430,6 +458,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED pipeline_parameters: project_id: "${project_id}" @@ -485,6 +521,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED pipeline_parameters: project_id: "${project_id}" @@ -525,6 +569,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED # These are pipeline parameters that will be passed to the pipeline to be recompiled pipeline_parameters: @@ -600,6 +652,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED pipeline_parameters: project: "${project_id}" @@ -639,6 +699,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED # These are pipeline parameters that will be passed to the pipeline to be recompiled pipeline_parameters: @@ -734,6 +802,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED pipeline_parameters: project_id: "${project_id}" @@ -789,6 +865,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED # These are pipeline parameters that will be passed to the pipeline to be recompiled pipeline_parameters: @@ -884,6 +968,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED pipeline_parameters: project_id: "${project_id}" @@ -939,6 +1031,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED # These are pipeline parameters that will be passed to the pipeline to be compiled # For Demographics Audience Segmentation model, we use the BQML KMeans clustering algorithm. @@ -982,6 +1082,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED pipeline_parameters: project_id: "${project_id}" @@ -1028,6 +1136,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED # These are pipeline parameters that will be passed to the pipeline to be compiled # For Interest based Auto Audience Segmentation model, we use the BQML KMeans clustering algorithm. @@ -1070,6 +1186,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED pipeline_parameters: project_id: "${project_id}" @@ -1119,6 +1243,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED # These are pipeline parameters that will be passed to the pipeline to be recompiled pipeline_parameters: @@ -1230,6 +1362,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED # These are pipeline parameters that will be passed to the pipeline to be recompiled pipeline_parameters: @@ -1318,6 +1458,14 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED # These are the pipeline parameters to be used in this convoluted prediction pipeline that takes predictions from LTV model and purchase propensity model. pipeline_parameters: @@ -1397,6 +1545,12 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false # The `state` defines the state of the pipeline. # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED @@ -1455,6 +1609,12 @@ vertex_ai: max_concurrent_run_count: 1 start_time: null end_time: null + # The `subnetwork` defines the subnetwork in which the pipeline will be executed. + # The default value is "default". + # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering + subnetwork: "default" + # If you want to use the vpc network defined above, set the following flag to true + use_private_service_access: false # The `state` defines the state of the pipeline. # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED diff --git a/infrastructure/terraform/modules/pipelines/main.tf b/infrastructure/terraform/modules/pipelines/main.tf index 40faa747..86d363d7 100644 --- a/infrastructure/terraform/modules/pipelines/main.tf +++ b/infrastructure/terraform/modules/pipelines/main.tf @@ -52,7 +52,9 @@ module "project_services" { "artifactregistry.googleapis.com", "aiplatform.googleapis.com", "dataflow.googleapis.com", - "bigqueryconnection.googleapis.com" + "bigqueryconnection.googleapis.com", + "servicenetworking.googleapis.com", + "compute.googleapis.com" ] } @@ -160,3 +162,29 @@ resource "null_resource" "check_artifactregistry_api" { module.project_services ] } + +# This resource executes gcloud commands to check whether the Service Networking API is enabled. +# Since enabling APIs can take a few seconds, we need to make the deployment wait until the API is enabled before resuming. +resource "null_resource" "check_servicenetworking_api" { + provisioner "local-exec" { + command = <<-EOT + COUNTER=0 + MAX_TRIES=100 + while ! gcloud services list --project=${module.project_services.project_id} | grep -i "servicenetworking.googleapis.com" && [ $COUNTER -lt $MAX_TRIES ] + do + sleep 6 + printf "." + COUNTER=$((COUNTER + 1)) + done + if [ $COUNTER -eq $MAX_TRIES ]; then + echo "service networking api is not enabled, terraform can not continue!" + exit 1 + fi + sleep 20 + EOT + } + + depends_on = [ + module.project_services + ] +} diff --git a/infrastructure/terraform/modules/pipelines/pipelines.tf b/infrastructure/terraform/modules/pipelines/pipelines.tf index 35ee5c34..bd78fa84 100644 --- a/infrastructure/terraform/modules/pipelines/pipelines.tf +++ b/infrastructure/terraform/modules/pipelines/pipelines.tf @@ -68,7 +68,8 @@ resource "google_project_iam_member" "pipelines_sa_roles" { "roles/artifactregistry.reader", "roles/pubsub.publisher", "roles/dataflow.developer", - "roles/bigquery.connectionUser" + "roles/bigquery.connectionUser", + "roles/compute.networkUser" ]) role = each.key } diff --git a/pyproject.toml b/pyproject.toml index 3e0abe24..98a6876a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,8 @@ packages = [{include = "python"}] [tool.poetry.dependencies] python = ">=3.8,<3.11" -google-cloud-aiplatform = "1.52.0" +#google-cloud-aiplatform = "1.52.0" +google-cloud-aiplatform = "1.70.0" shapely = "<2.0.0" google-cloud = "^0.34.0" jinja2 = ">=3.0.1,<4.0.0" @@ -37,11 +38,13 @@ google-cloud-bigquery = "2.30.0" google-cloud-pipeline-components = "2.6.0" google-auth = "^2.14.1" google-cloud-storage = "^2.6.0" +kfp = "2.4.0" ## Fixing this error: https://stackoverflow.com/questions/76175487/sudden-importerror-cannot-import-name-appengine-from-requests-packages-urlli -kfp = "2.0.0-rc.2" +#kfp = "2.0.0-rc.2" #kfp = {version = "2.0.0-b12", allow-prereleases = true} #kfp = {version = "2.0.0-b16", allow-prereleases = true} -kfp-server-api = "2.0.0-rc.1" +kfp-server-api = "2.0.5" +#kfp-server-api = "2.0.0-rc.1" #kfp-server-api = "2.0.0.a6" #kfp-server-api = "2.0.0b1" urllib3 = "1.26.18" @@ -62,9 +65,11 @@ pyarrow = "15.0.2" google-auth-oauthlib = "^1.2.1" oauth2client = "^4.1.3" google-cloud-core = "^2.4.1" +sympy="1.13.1" +google-cloud-resource-manager="1.13.0" [tool.poetry.group.component_vertex.dependencies] -google-cloud-aiplatform = "1.52.0" +google-cloud-aiplatform = "1.70.0" shapely = "<2.0.0" toml = "0.10.2" diff --git a/python/base_component_image/pyproject.toml b/python/base_component_image/pyproject.toml index 3ce3fc2f..49aabede 100644 --- a/python/base_component_image/pyproject.toml +++ b/python/base_component_image/pyproject.toml @@ -2,25 +2,29 @@ name = "ma-components" version = "1.0.0" description = "contains components used in marketing analytics project. the need is to package the components and containerise so that they can be used from the python function based component" -authors = ["Christos Aniftos "] +authors = ["Marketing Analytics Solutions Architects "] +license = "Apache 2.0" readme = "README.md" packages = [{include = "ma_components"}] [tool.poetry.dependencies] python = ">=3.8,<3.11" pip = "23.3" +kfp = "2.4.0" ## Fixing this error: https://stackoverflow.com/questions/76175487/sudden-importerror-cannot-import-name-appengine-from-requests-packages-urlli -kfp = "2.0.0-rc.2" +#kfp = "2.0.0-rc.2" #kfp = {version = "2.0.0-b12", allow-prereleases = true} #kfp = {version = "2.0.0-b16", allow-prereleases = true} -kfp-server-api = "2.0.0-rc.1" +kfp-server-api = "2.0.5" +#kfp-server-api = "2.0.0-rc.1" #kfp-server-api = "2.0.0.a6" #kfp-server-api = "2.0.0b1" urllib3 = "1.26.18" toml = "^0.10.2" docker = "^6.0.1" google-cloud-bigquery = "2.30.0" -google-cloud-aiplatform = "1.52.0" +#google-cloud-aiplatform = "1.52.0" +google-cloud-aiplatform = "1.70.0" shapely = "<2.0.0" google-cloud-pubsub = "2.15.0" #google-cloud-pipeline-components = "1.0.33" @@ -35,6 +39,8 @@ pyarrow = "15.0.2" google-auth-oauthlib = "^1.2.1" oauth2client = "^4.1.3" google-cloud-core = "^2.4.1" +sympy="1.13.1" +google-cloud-resource-manager="1.13.0" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/python/pipelines/pipeline_ops.py b/python/pipelines/pipeline_ops.py index 60a9cd9b..2b152b29 100644 --- a/python/pipelines/pipeline_ops.py +++ b/python/pipelines/pipeline_ops.py @@ -17,6 +17,7 @@ from tracemalloc import start import pip +from sympy import preview from kfp import compiler from google.cloud.aiplatform.pipeline_jobs import PipelineJob, _set_enable_caching_value from google.cloud.aiplatform import TabularDataset, Artifact @@ -625,6 +626,30 @@ def get_gcp_bearer_token() -> str: return bearer_token +def _get_project_number(project_id) -> str: + """ + Retrieves the project number from a project id + + Returns: + A string containing the project number + + Raises: + Exception: If an error occurs while retrieving the resource manager project object. + """ + from google.cloud import resourcemanager_v3 + + # Create a resource manager client + client = resourcemanager_v3.ProjectsClient() + + # Get the project number + project = client.get_project(name=f"projects/{project_id}").name + project_number = project.split('/')[-1] + + logging.info(f"Project Number: {project_number}") + + return project_number + + # Function to schedule the pipeline. def schedule_pipeline( project_id: str, @@ -637,6 +662,8 @@ def schedule_pipeline( max_concurrent_run_count: str, start_time: str, end_time: str, + subnetwork: str = "default", + use_private_service_access: bool = False, pipeline_parameters: Dict[str, Any] = None, pipeline_parameters_substitutions: Optional[Dict[str, Any]] = None, ) -> dict: @@ -654,6 +681,8 @@ def schedule_pipeline( max_concurrent_run_count: The maximum number of concurrent pipeline runs. start_time: The start time of the schedule. end_time: The end time of the schedule. + subnetwork: The VPC subnetwork name to be used in VPC peering. + use_private_service_access: A flag to define whether to use the VPC private service access or not. Returns: A dictionary containing information about the scheduled pipeline. @@ -663,6 +692,9 @@ def schedule_pipeline( """ from google.cloud import aiplatform + from google.cloud.aiplatform.preview.pipelinejobschedule import ( + pipeline_job_schedules as preview_pipeline_job_schedules, + ) # Substitute pipeline parameters with necessary substitutions if pipeline_parameters_substitutions != None: @@ -680,16 +712,51 @@ def schedule_pipeline( display_name=f"{pipeline_name}", ) - # Create the schedule with the pipeline job defined - pipeline_job_schedule = pipeline_job.create_schedule( + # https://cloud.google.com/python/docs/reference/aiplatform/latest/google.cloud.aiplatform.PipelineJobSchedule + # Create a schedule for the pipeline job + pipeline_job_schedule = preview_pipeline_job_schedules.PipelineJobSchedule( display_name=f"{pipeline_name}", - cron=cron, - max_concurrent_run_count=max_concurrent_run_count, - start_time=start_time, - end_time=end_time, - service_account=pipeline_sa, + pipeline_job=pipeline_job, + location=region ) + # Get the project number to use in the network identifier + project_number = _get_project_number(project_id) + + # Create the schedule using the pipeline job schedule + # Using the VPC private service access or not, depending on the flag + if use_private_service_access: + pipeline_job_schedule.create( + cron_expression=cron, + max_concurrent_run_count=max_concurrent_run_count, + start_time=start_time, + end_time=end_time, + max_run_count=2, + service_account=pipeline_sa, + network=f"projects/{project_number}/global/networks/{subnetwork}", + create_request_timeout=None, + ) + else: + pipeline_job_schedule.create( + cron_expression=cron, + max_concurrent_run_count=max_concurrent_run_count, + start_time=start_time, + end_time=end_time, + max_run_count=2, + service_account=pipeline_sa, + create_request_timeout=None, + ) + + # Old version - Create the schedule with the pipeline job defined + #pipeline_job_schedule = pipeline_job.create_schedule( + # display_name=f"{pipeline_name}", + # cron=cron, + # max_concurrent_run_count=max_concurrent_run_count, + # start_time=start_time, + # end_time=end_time, + # service_account=pipeline_sa, + #) + logging.info(f"Pipeline scheduled : {pipeline_name}") return pipeline_job diff --git a/python/pipelines/scheduler.py b/python/pipelines/scheduler.py index fbdd9933..ce554ddd 100644 --- a/python/pipelines/scheduler.py +++ b/python/pipelines/scheduler.py @@ -138,7 +138,9 @@ def check_extention(file_path: str, type: str = '.yaml'): cron=my_pipeline_vars['schedule']['cron'], max_concurrent_run_count=my_pipeline_vars['schedule']['max_concurrent_run_count'], start_time=my_pipeline_vars['schedule']['start_time'], - end_time=my_pipeline_vars['schedule']['end_time'] + end_time=my_pipeline_vars['schedule']['end_time'], + subnetwork=my_pipeline_vars['schedule']['subnetwork'], + use_private_service_access=my_pipeline_vars['schedule']['use_private_service_access'], ) if my_pipeline_vars['schedule']['state'] == 'PAUSED': diff --git a/scripts/common.sh b/scripts/common.sh index 57d314fa..dbdbff0a 100644 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -47,6 +47,7 @@ declare -a apis_array=("cloudresourcemanager.googleapis.com" "bigquerydatatransfer.googleapis.com" "dataform.googleapis.com" "cloudkms.googleapis.com" + "servicenetworking.googleapis.com" ) get_project_id() { From ccb960d2bb902368861d403ef1fbbd49e4e37585 Mon Sep 17 00:00:00 2001 From: Charlie Wang <2144018+kingman@users.noreply.github.com> Date: Mon, 4 Nov 2024 18:25:04 +0100 Subject: [PATCH 071/110] trigger cf update on source code change (#220) --- infrastructure/terraform/modules/activation/main.tf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/infrastructure/terraform/modules/activation/main.tf b/infrastructure/terraform/modules/activation/main.tf index acf901ec..0fc0fc15 100644 --- a/infrastructure/terraform/modules/activation/main.tf +++ b/infrastructure/terraform/modules/activation/main.tf @@ -29,7 +29,8 @@ locals { activation_container_image_id = "activation-pipeline" docker_repo_prefix = "${var.location}-docker.pkg.dev/${var.project_id}" activation_container_name = "dataflow/${local.activation_container_image_id}" - source_archive_file = "activation_trigger_source.zip" + source_archive_file_prefix = "activation_trigger_source" + source_archive_file = "${local.source_archive_file_prefix}.zip" pipeline_service_account_name = "dataflow-worker" pipeline_service_account_email = "${local.app_prefix}-${local.pipeline_service_account_name}@${var.project_id}.iam.gserviceaccount.com" @@ -878,7 +879,7 @@ module "function_bucket" { # This resource creates a bucket object using as content the activation_trigger_archive zip file. resource "google_storage_bucket_object" "activation_trigger_archive" { - name = local.source_archive_file + name = "${local.source_archive_file_prefix}_${data.archive_file.activation_trigger_source.output_sha256}.zip" source = data.archive_file.activation_trigger_source.output_path bucket = module.function_bucket.name } From 919aaac0076345efd1f174f511b88708f68b3ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Lindblad?= Date: Wed, 6 Nov 2024 15:10:20 +0100 Subject: [PATCH 072/110] Add support for changing time zone via variables (#229) --- config/config.yaml.tftpl | 42 +++++++++---------- infrastructure/terraform/main.tf | 6 ++- .../terraform/modules/data-store/main.tf | 3 ++ .../terraform/modules/data-store/variables.tf | 4 ++ .../modules/dataform-workflow/scheduler.tf | 2 +- .../modules/dataform-workflow/variables.tf | 4 ++ infrastructure/terraform/variables.tf | 6 +++ 7 files changed, 44 insertions(+), 23 deletions(-) diff --git a/config/config.yaml.tftpl b/config/config.yaml.tftpl index 65dd2e91..9c31bf3b 100644 --- a/config/config.yaml.tftpl +++ b/config/config.yaml.tftpl @@ -184,7 +184,7 @@ vertex_ai: schedule: # The `cron` is the cron schedule. Make sure you review the TZ=America/New_York timezone. # More information can be found at https://cloud.google.com/scheduler/docs/configuring/cron-job-schedules. - cron: "TZ=America/New_York 0 1 * * *" + cron: "TZ=${time_zone} 0 1 * * *" # The `max_concurrent_run_count` defines the maximum number of concurrent pipeline runs. max_concurrent_run_count: 1 start_time: null @@ -268,7 +268,7 @@ vertex_ai: schedule: # The `cron` is the cron schedule. Make sure you review the TZ=America/New_York timezone. # More information can be found at https://cloud.google.com/scheduler/docs/configuring/cron-job-schedules. - cron: "TZ=America/New_York 0 1 * * *" + cron: "TZ=${time_zone} 0 1 * * *" # The `max_concurrent_run_count` defines the maximum number of concurrent pipeline runs. max_concurrent_run_count: 1 start_time: null @@ -332,7 +332,7 @@ vertex_ai: # `type` must be "custom", when we're building Python and/or SQL based pipelines for feature engineering purposes. type: "custom" schedule: - cron: "TZ=America/New_York 0 1 * * *" + cron: "TZ=${time_zone} 0 1 * * *" # Define the maximum number of concurrent pipeline runs. # The default value is 1. max_concurrent_run_count: 1 @@ -395,7 +395,7 @@ vertex_ai: # `type` must be "custom", when we're building Python and/or SQL based pipelines for feature engineering purposes. type: "custom" schedule: - cron: "TZ=America/New_York 0 1 * * *" + cron: "TZ=${time_zone} 0 1 * * *" # Define the maximum number of concurrent pipeline runs. # The default value is 1. max_concurrent_run_count: 1 @@ -452,7 +452,7 @@ vertex_ai: # `type` must be "custom", when we're building Python and/or SQL based pipelines for feature engineering purposes. type: "custom" schedule: - cron: "TZ=America/New_York 0 1 * * *" + cron: "TZ=${time_zone} 0 1 * * *" # Define the maximum number of concurrent pipeline runs. # The default value is 1. max_concurrent_run_count: 1 @@ -515,7 +515,7 @@ vertex_ai: type: "custom" schedule: # The cron string is - cron: "TZ=America/New_York 0 1 * * *" + cron: "TZ=${time_zone} 0 1 * * *" # Define the maximum concurrent run count of the pipeline. # The default value is 1. max_concurrent_run_count: 1 @@ -565,7 +565,7 @@ vertex_ai: type: "tabular-workflows" schedule: # define the schedule for the pipeline - cron: "TZ=America/New_York 0 1 * * *" + cron: "TZ=${time_zone} 0 1 * * *" max_concurrent_run_count: 1 start_time: null end_time: null @@ -648,7 +648,7 @@ vertex_ai: # For using Vertex AI Tabular Workflow use the later, for all other modeling approaches use "custom" (i.e. BQML, Scikit-learn). type: "custom" schedule: - cron: "TZ=America/New_York 0 5 * * *" + cron: "TZ=${time_zone} 0 5 * * *" max_concurrent_run_count: 1 start_time: null end_time: null @@ -695,7 +695,7 @@ vertex_ai: # For using Vertex AI Tabular Workflow use the later, for all other modeling approaches use "custom" (i.e. BQML, Scikit-learn). type: "tabular-workflows" schedule: - cron: "TZ=America/New_York 0 8 * * SAT" + cron: "TZ=${time_zone} 0 8 * * SAT" max_concurrent_run_count: 1 start_time: null end_time: null @@ -798,7 +798,7 @@ vertex_ai: # For using Vertex AI Tabular Workflow use the later, for all other modeling approaches use "custom" (i.e. BQML, Scikit-learn). type: "custom" schedule: - cron: "TZ=America/New_York 0 5 * * *" + cron: "TZ=${time_zone} 0 5 * * *" max_concurrent_run_count: 1 start_time: null end_time: null @@ -861,7 +861,7 @@ vertex_ai: # For using Vertex AI Tabular Workflow use the later, for all other modeling approaches use "custom" (i.e. BQML, Scikit-learn). type: "tabular-workflows" schedule: - cron: "TZ=America/New_York 0 8 * * SAT" + cron: "TZ=${time_zone} 0 8 * * SAT" max_concurrent_run_count: 1 start_time: null end_time: null @@ -964,7 +964,7 @@ vertex_ai: # For using Vertex AI Tabular Workflow use the later, for all other modeling approaches use "custom" (i.e. BQML, Scikit-learn). type: "custom" schedule: - cron: "TZ=America/New_York 0 5 * * *" + cron: "TZ=${time_zone} 0 5 * * *" max_concurrent_run_count: 1 start_time: null end_time: null @@ -1027,7 +1027,7 @@ vertex_ai: # For using Vertex AI Tabular Workflow use the later, for all other modeling approaches use "custom" (i.e. BQML, Scikit-learn). type: "custom" schedule: - cron: "TZ=America/New_York 0 12 * * SAT" + cron: "TZ=${time_zone} 0 12 * * SAT" max_concurrent_run_count: 1 start_time: null end_time: null @@ -1078,7 +1078,7 @@ vertex_ai: # For using Vertex AI Tabular Workflow use the later, for all other modeling approaches use "custom" (i.e. BQML, Scikit-learn). type: "custom" schedule: - cron: "TZ=America/New_York 0 7 * * *" + cron: "TZ=${time_zone} 0 7 * * *" max_concurrent_run_count: 1 start_time: null end_time: null @@ -1132,7 +1132,7 @@ vertex_ai: # For using Vertex AI Tabular Workflow use the later, for all other modeling approaches use "custom" (i.e. BQML, Scikit-learn). type: "custom" schedule: - cron: "TZ=America/New_York 0 12 * * SAT" + cron: "TZ=${time_zone} 0 12 * * SAT" max_concurrent_run_count: 1 start_time: null end_time: null @@ -1182,7 +1182,7 @@ vertex_ai: # For using Vertex AI Tabular Workflow use the later, for all other modeling approaches use "custom" (i.e. BQML, Scikit-learn). type: "custom" schedule: - cron: "TZ=America/New_York 0 7 * * *" + cron: "TZ=${time_zone} 0 7 * * *" max_concurrent_run_count: 1 start_time: null end_time: null @@ -1239,7 +1239,7 @@ vertex_ai: # For using Vertex AI Tabular Workflow use the later, for all other modeling approaches use "custom" (i.e. BQML, Scikit-learn). type: "tabular-workflows" schedule: - cron: "TZ=America/New_York 0 16 * * SAT" + cron: "TZ=${time_zone} 0 16 * * SAT" max_concurrent_run_count: 1 start_time: null end_time: null @@ -1358,7 +1358,7 @@ vertex_ai: # For using Vertex AI Tabular Workflow use the later, for all other modeling approaches use "custom" (i.e. BQML, Scikit-learn). type: "tabular-workflows" schedule: - cron: "TZ=America/New_York 0 20 * * SAT" + cron: "TZ=${time_zone} 0 20 * * SAT" max_concurrent_run_count: 1 start_time: null end_time: null @@ -1454,7 +1454,7 @@ vertex_ai: # For using Vertex AI Tabular Workflow use the later, for all other modeling approaches use "custom" (i.e. BQML, Scikit-learn). type: "custom" schedule: - cron: "TZ=America/New_York 0 6 * * *" + cron: "TZ=${time_zone} 0 6 * * *" max_concurrent_run_count: 1 start_time: null end_time: null @@ -1540,7 +1540,7 @@ vertex_ai: schedule: # The `cron` is the cron schedule. Make sure you review the TZ=America/New_York timezone. # More information can be found at https://cloud.google.com/scheduler/docs/configuring/cron-job-schedules. - cron: "TZ=America/New_York 0 8-23/2 * * *" + cron: "TZ=${time_zone} 0 8-23/2 * * *" # The `max_concurrent_run_count` defines the maximum number of concurrent pipeline runs. max_concurrent_run_count: 1 start_time: null @@ -1604,7 +1604,7 @@ vertex_ai: schedule: # The `cron` is the cron schedule. Make sure you review the TZ=America/New_York timezone. # More information can be found at https://cloud.google.com/scheduler/docs/configuring/cron-job-schedules. - cron: "TZ=America/New_York 0 8 * * *" + cron: "TZ=${time_zone} 0 8 * * *" # The `max_concurrent_run_count` defines the maximum number of concurrent pipeline runs. max_concurrent_run_count: 1 start_time: null diff --git a/infrastructure/terraform/main.tf b/infrastructure/terraform/main.tf index ae6eec05..a6ede8bc 100644 --- a/infrastructure/terraform/main.tf +++ b/infrastructure/terraform/main.tf @@ -122,7 +122,8 @@ resource "local_file" "feature_store_configuration" { pipelines_github_owner = var.pipelines_github_owner pipelines_github_repo = var.pipelines_github_repo # TODO: this needs to be specific to environment. - location = var.destination_data_location + location = var.destination_data_location + time_zone = var.time_zone }) } @@ -374,6 +375,9 @@ module "data_store" { # The project_owner_email is set in the terraform.tfvars file. # An example of a valid email address is "william.mckinley@my-own-personal-domain.com". project_owner_email = var.project_owner_email + + # Set the time zone for the scheduled jobs + time_zone = var.time_zone } diff --git a/infrastructure/terraform/modules/data-store/main.tf b/infrastructure/terraform/modules/data-store/main.tf index 5add3aab..35f6011e 100644 --- a/infrastructure/terraform/modules/data-store/main.tf +++ b/infrastructure/terraform/modules/data-store/main.tf @@ -62,6 +62,7 @@ module "dataform-workflow-dev" { # https://support.google.com/analytics/answer/9358801?hl=en#:~:text=A%20full%20export%20of%20data,(see%20Streaming%20export%20below). # Check https://crontab.guru/#0_5-23/4_*_*_* to see next execution times. daily_schedule = "0 5-23/4 * * *" + time_zone = var.time_zone } # This module sets up a Dataform workflow environment for the "staging" environment. @@ -96,6 +97,7 @@ module "dataform-workflow-staging" { # https://support.google.com/analytics/answer/9358801?hl=en#:~:text=A%20full%20export%20of%20data,(see%20Streaming%20export%20below). # Check https://crontab.guru/#0_5-23/4_*_*_* to see next execution times. daily_schedule = "0 5-23/4 * * *" + time_zone = var.time_zone } # This module sets up a Dataform workflow environment for the "prod" environment. @@ -127,4 +129,5 @@ module "dataform-workflow-prod" { # https://support.google.com/analytics/answer/9358801?hl=en#:~:text=A%20full%20export%20of%20data,(see%20Streaming%20export%20below). # Check https://crontab.guru/#0_5-23/2_*_*_* to see next execution times. daily_schedule = "0 5-23/2 * * *" + time_zone = var.time_zone } diff --git a/infrastructure/terraform/modules/data-store/variables.tf b/infrastructure/terraform/modules/data-store/variables.tf index 3b7f5cfd..bd85ad53 100644 --- a/infrastructure/terraform/modules/data-store/variables.tf +++ b/infrastructure/terraform/modules/data-store/variables.tf @@ -129,3 +129,7 @@ variable "dataform_region" { description = "Specify dataform region when dataform is not available in the default cloud region of choice" type = string } + +variable "time_zone" { + type = string +} diff --git a/infrastructure/terraform/modules/dataform-workflow/scheduler.tf b/infrastructure/terraform/modules/dataform-workflow/scheduler.tf index d6a6cd8c..ecb2cbd6 100644 --- a/infrastructure/terraform/modules/dataform-workflow/scheduler.tf +++ b/infrastructure/terraform/modules/dataform-workflow/scheduler.tf @@ -19,7 +19,7 @@ resource "google_cloud_scheduler_job" "daily-dataform-increments" { description = "Daily Dataform ${var.environment} environment incremental update" # The schedule attribute specifies the schedule for the job. In this case, the job is scheduled to run daily at the specified times. schedule = var.daily_schedule - time_zone = "America/New_York" + time_zone = var.time_zone # The attempt_deadline attribute specifies the maximum amount of time that the job will attempt to run before failing. # In this case, the job will attempt to run for a maximum of 5 minutes before failing. attempt_deadline = "320s" diff --git a/infrastructure/terraform/modules/dataform-workflow/variables.tf b/infrastructure/terraform/modules/dataform-workflow/variables.tf index cacb2b92..38a20fee 100644 --- a/infrastructure/terraform/modules/dataform-workflow/variables.tf +++ b/infrastructure/terraform/modules/dataform-workflow/variables.tf @@ -75,3 +75,7 @@ variable "includedTags" { type = list(string) default = [] } + +variable "time_zone" { + type = string +} diff --git a/infrastructure/terraform/variables.tf b/infrastructure/terraform/variables.tf index f33c1a90..e479a7d2 100644 --- a/infrastructure/terraform/variables.tf +++ b/infrastructure/terraform/variables.tf @@ -241,3 +241,9 @@ variable "website_url" { type = string default = null } + +variable "time_zone" { + description = "Timezone for scheduled jobs" + type = string + default = "America/New_York" +} From adde4303e9786f8d3351210234e3652f25c794aa Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 6 Nov 2024 15:12:17 -0500 Subject: [PATCH 073/110] Update README.md --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a4d52af..f52f9212 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,15 @@ # Marketing Analytics Jumpstart -Marketing Analytics Jumpstart is a terraform automated, quick-to-deploy, customizable end-to-end marketing solution on Google Cloud Platform (GCP). This solution aims at helping customer better understand and better use their digital advertising budget. +Marketing Analytics Jumpstart (MAJ) is a terraform automated, quick-to-deploy, customizable end-to-end marketing solution on Google Cloud Platform (GCP). This solution aims at helping customer better understand and better use their digital advertising budget. Customers are looking to drive revenue and increase media efficiency be identifying, predicting and targeting valuable users through the use of machine learning. However, marketers first have to solve the challenge of having a number of disparate data sources that prevent them from having a holistic view of customers. Marketers also often don't have the expertise and/or resources in their marketing departments to train, run, and activate ML models on paid channels. Without this solution that enables innovation through predictive analytics, marketers are missing opportunities to advance their marketing program and accelerate key goals and objectives (e.g. acquire new customers, improve customer retention, etc). +## Version Variants + +| Version Name | Branch | Purpose | +| ------------ | ------ | ------- | +| Multi Stream Activation | [multi-stream-activation](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/tree/multi-stream-activation) | Activate to multiple Google Analytics 4 data streams (websites and application). | +| Multi Property | [multi-property](https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart/tree/multi-property) | Deployment of multiple MAJ resources per each Google Analytics 4 property in the same Google Cloud project. | + ## Quick Installation ⏰ Want to quickly install and use it? Run this [installation notebook 📔](https://colab.sandbox.google.com/github/GoogleCloudPlatform/marketing-analytics-jumpstart/blob/main/notebooks/quick_installation.ipynb) on Google Colaboratory and leverage Marketing Analytics Jumpstart in under 30 minutes. From bb82162b2c1000ab17c4b2ba0584ec5f2c12bfa0 Mon Sep 17 00:00:00 2001 From: Charlie Wang <2144018+kingman@users.noreply.github.com> Date: Sat, 9 Nov 2024 23:27:30 +0100 Subject: [PATCH 074/110] ensure the build bucket is created in the specified region (#230) --- infrastructure/terraform/modules/activation/main.tf | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/infrastructure/terraform/modules/activation/main.tf b/infrastructure/terraform/modules/activation/main.tf index 0fc0fc15..b7d63757 100644 --- a/infrastructure/terraform/modules/activation/main.tf +++ b/infrastructure/terraform/modules/activation/main.tf @@ -801,8 +801,15 @@ module "activation_pipeline_container" { platform = "linux" - #create_cmd_body = "builds submit --project=${module.project_services.project_id} --tag ${local.docker_repo_prefix}/${google_artifact_registry_repository.activation_repository.name}/${local.activation_container_name}:latest ${local.pipeline_source_dir}" - create_cmd_body = "builds submit --project=${module.project_services.project_id} --tag ${local.docker_repo_prefix}/${google_artifact_registry_repository.activation_repository.name}/${local.activation_container_name}:latest --gcs-log-dir=gs://${module.build_logs_bucket.name} ${local.pipeline_source_dir}" + create_cmd_body = <<-EOT + builds submit \ + --project=${module.project_services.project_id} \ + --region ${var.location} \ + --default-buckets-behavior=regional-user-owned-bucket \ + --tag ${local.docker_repo_prefix}/${google_artifact_registry_repository.activation_repository.name}/${local.activation_container_name}:latest \ + --gcs-log-dir=gs://${module.build_logs_bucket.name} \ + ${local.pipeline_source_dir} + EOT destroy_cmd_body = "artifacts docker images delete --project=${module.project_services.project_id} ${local.docker_repo_prefix}/${google_artifact_registry_repository.activation_repository.name}/${local.activation_container_name} --delete-tags" create_cmd_triggers = { From 425b83ee098f9f13b20a5571699320b1461505b1 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 11 Nov 2024 12:43:05 -0500 Subject: [PATCH 075/110] Update audience_segmentation_query_template.sqlx --- .../activation_query/audience_segmentation_query_template.sqlx | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/activation_query/audience_segmentation_query_template.sqlx b/templates/activation_query/audience_segmentation_query_template.sqlx index badf7605..89eec5e0 100644 --- a/templates/activation_query/audience_segmentation_query_template.sqlx +++ b/templates/activation_query/audience_segmentation_query_template.sqlx @@ -3,6 +3,7 @@ SELECT b.user_pseudo_id AS client_id, b.user_id AS user_id, b.ga_session_id AS event_param_session_id, + '100' AS event_param_engagement_time_msec, CASE WHEN EXTRACT(MICROSECOND FROM b.event_timestamp) = 1 THEN b.event_timestamp ELSE TIMESTAMP_SUB(b.event_timestamp, INTERVAL 1 MICROSECOND) END AS inference_date FROM `${mds_project_id}.marketing_ga4_v1_${mds_dataset_suffix}.latest_event_per_user_last_72_hours` b, From de5fa3dcfadeb1234af0407988999bc6eb945786 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 11 Nov 2024 12:43:42 -0500 Subject: [PATCH 076/110] Update auto_audience_segmentation_query_template.sqlx --- .../auto_audience_segmentation_query_template.sqlx | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/activation_query/auto_audience_segmentation_query_template.sqlx b/templates/activation_query/auto_audience_segmentation_query_template.sqlx index b1b19269..d2e960d9 100644 --- a/templates/activation_query/auto_audience_segmentation_query_template.sqlx +++ b/templates/activation_query/auto_audience_segmentation_query_template.sqlx @@ -3,6 +3,7 @@ SELECT b.user_pseudo_id AS client_id, b.user_id AS user_id, b.ga_session_id AS event_param_session_id, + '100' AS event_param_engagement_time_msec, CASE WHEN EXTRACT(MICROSECOND FROM b.event_timestamp) = 1 THEN b.event_timestamp ELSE TIMESTAMP_SUB(b.event_timestamp, INTERVAL 1 MICROSECOND) END AS inference_date FROM `${mds_project_id}.marketing_ga4_v1_${mds_dataset_suffix}.latest_event_per_user_last_72_hours` b, From 326a3649d810823223e1703aca9ab23838ff98db Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 11 Nov 2024 12:44:00 -0500 Subject: [PATCH 077/110] Update churn_propensity_query_template.sqlx --- templates/activation_query/churn_propensity_query_template.sqlx | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/activation_query/churn_propensity_query_template.sqlx b/templates/activation_query/churn_propensity_query_template.sqlx index ebf6b67a..ae42604a 100644 --- a/templates/activation_query/churn_propensity_query_template.sqlx +++ b/templates/activation_query/churn_propensity_query_template.sqlx @@ -4,6 +4,7 @@ SELECT b.user_pseudo_id AS client_id, b.user_id AS user_id, b.ga_session_id AS event_param_session_id, + '100' AS event_param_engagement_time_msec, CASE WHEN EXTRACT(MICROSECOND FROM b.event_timestamp) = 1 THEN b.event_timestamp ELSE TIMESTAMP_SUB(b.event_timestamp, INTERVAL 1 MICROSECOND) END AS inference_date FROM `${mds_project_id}.marketing_ga4_v1_${mds_dataset_suffix}.latest_event_per_user_last_72_hours` b, From da881fe0aa4fc79125b39154328917adbb6bbd88 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 11 Nov 2024 12:44:15 -0500 Subject: [PATCH 078/110] Update cltv_query_template.sqlx --- templates/activation_query/cltv_query_template.sqlx | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/activation_query/cltv_query_template.sqlx b/templates/activation_query/cltv_query_template.sqlx index 2651245e..bdd4bffd 100644 --- a/templates/activation_query/cltv_query_template.sqlx +++ b/templates/activation_query/cltv_query_template.sqlx @@ -3,6 +3,7 @@ SELECT b.user_pseudo_id AS client_id, b.user_id AS user_id, b.ga_session_id AS event_param_session_id, + '100' AS event_param_engagement_time_msec, CASE WHEN EXTRACT(MICROSECOND FROM b.event_timestamp) = 1 THEN b.event_timestamp ELSE TIMESTAMP_SUB(b.event_timestamp, INTERVAL 1 MICROSECOND) END AS inference_date FROM `${mds_project_id}.marketing_ga4_v1_${mds_dataset_suffix}.latest_event_per_user_last_72_hours` b, From 841c523b2974eba89d5ea30c54778217e830ad1f Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 11 Nov 2024 12:44:32 -0500 Subject: [PATCH 079/110] Update purchase_propensity_query_template.sqlx --- .../activation_query/purchase_propensity_query_template.sqlx | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/activation_query/purchase_propensity_query_template.sqlx b/templates/activation_query/purchase_propensity_query_template.sqlx index 7bae9fbe..985edf03 100644 --- a/templates/activation_query/purchase_propensity_query_template.sqlx +++ b/templates/activation_query/purchase_propensity_query_template.sqlx @@ -4,6 +4,7 @@ SELECT b.user_pseudo_id AS client_id, b.user_id AS user_id, b.ga_session_id AS event_param_session_id, + '100' AS event_param_engagement_time_msec, CASE WHEN EXTRACT(MICROSECOND FROM b.event_timestamp) = 1 THEN b.event_timestamp ELSE TIMESTAMP_SUB(b.event_timestamp, INTERVAL 1 MICROSECOND) END AS inference_date FROM `${mds_project_id}.marketing_ga4_v1_${mds_dataset_suffix}.latest_event_per_user_last_72_hours` b, From d5d84d77354d1dda24bf9a92650fed3a32667849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Lindblad?= Date: Thu, 14 Nov 2024 19:30:30 +0100 Subject: [PATCH 080/110] Restrict regions for GCP Cloud Build support (#241) --- .../terraform/modules/activation/main.tf | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/infrastructure/terraform/modules/activation/main.tf b/infrastructure/terraform/modules/activation/main.tf index b7d63757..c2c8f8b8 100644 --- a/infrastructure/terraform/modules/activation/main.tf +++ b/infrastructure/terraform/modules/activation/main.tf @@ -60,6 +60,17 @@ locals { ga4_setup_source_file = "${local.source_root_dir}/python/ga4_setup/setup.py" ga4_setup_source_file_content_hash = filesha512(local.ga4_setup_source_file) + + # GCP Cloud Build is not available in all regions. + cloud_build_available_locations = [ + "us-central1", + "us-west2", + "europe-west1", + "asia-east1", + "australia-southeast1", + "southamerica-east1" + ] + } data "google_project" "activation_project" { @@ -301,6 +312,16 @@ resource "null_resource" "check_cloudbuild_api" { depends_on = [ module.project_services ] + + # The lifecycle block of the google_artifact_registry_repository resource defines a precondition that + # checks if the specified region is included in the vertex_pipelines_available_locations list. + # If the condition is not met, an error message is displayed and the Terraform configuration will fail. + lifecycle { + precondition { + condition = contains(local.cloud_build_available_locations, var.location) + error_message = "Cloud Build is not available in your default region: ${var.location}.\nSet 'google_default_region' variable to a valid Cloud Build location, see Restricted Regions in https://cloud.google.com/build/docs/locations." + } + } } # This resource executes gcloud commands to check whether the IAM API is enabled. @@ -449,13 +470,13 @@ resource "random_id" "random_suffix" { # to interact with other Google Cloud services on your behalf. resource "google_project_service_identity" "secretmanager_sa" { provider = google-beta - project = null_resource.check_cloudkms_api.id != "" ? module.project_services.project_id : var.project_id - service = "secretmanager.googleapis.com" + project = null_resource.check_cloudkms_api.id != "" ? module.project_services.project_id : var.project_id + service = "secretmanager.googleapis.com" } - # This Key Ring can then be used to store and manage encryption keys for various purposes, - # such as encrypting data at rest or protecting secrets. +# This Key Ring can then be used to store and manage encryption keys for various purposes, +# such as encrypting data at rest or protecting secrets. resource "google_kms_key_ring" "key_ring_regional" { - name = "key_ring_regional-${random_id.random_suffix.hex}" + name = "key_ring_regional-${random_id.random_suffix.hex}" # If you want your replicas in other locations, change the location in the var.location variable passed as a parameter to this submodule. # if you your replicas stored global, set the location = "global". location = var.location @@ -494,7 +515,7 @@ data "google_iam_policy" "crypto_key_encrypter_decrypter" { # in another data source. resource "google_kms_crypto_key_iam_policy" "crypto_key" { crypto_key_id = google_kms_crypto_key.crypto_key_regional.id - policy_data = data.google_iam_policy.crypto_key_encrypter_decrypter.policy_data + policy_data = data.google_iam_policy.crypto_key_encrypter_decrypter.policy_data } # It sets the IAM policy for a KMS Key Ring, granting specific permissions defined @@ -530,13 +551,13 @@ module "secret_manager" { # If you want your replicas in other locations, uncomment the following lines and add them here. # Check this example, as reference: https://github.com/GoogleCloudPlatform/terraform-google-secret-manager/blob/main/examples/multiple/main.tf#L91 { - location = var.location + location = var.location kms_key_name = google_kms_crypto_key.crypto_key_regional.id } ] ga4-measurement-secret = [ { - location = var.location + location = var.location kms_key_name = google_kms_crypto_key.crypto_key_regional.id } ] @@ -823,8 +844,8 @@ module "activation_pipeline_container" { # This module executes a gcloud command to build a dataflow flex template and uploads it to Dataflow module "activation_pipeline_template" { - source = "terraform-google-modules/gcloud/google" - version = "3.5.0" + source = "terraform-google-modules/gcloud/google" + version = "3.5.0" platform = "linux" create_cmd_body = "dataflow flex-template build --project=${module.project_services.project_id} \"gs://${module.pipeline_bucket.name}/dataflow/templates/${local.activation_container_image_id}.json\" --image \"${local.docker_repo_prefix}/${google_artifact_registry_repository.activation_repository.name}/${local.activation_container_name}:latest\" --sdk-language \"PYTHON\" --metadata-file \"${local.pipeline_source_dir}/metadata.json\"" From 639bbff6d08e3651b21898e44e829db9ae1dd689 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Thu, 14 Nov 2024 13:59:26 -0500 Subject: [PATCH 081/110] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f52f9212..542f4c62 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ This high-level architecture demonstrates how Marketing Analytics Jumpstart inte - [ ] [Backfill](https://cloud.google.com/bigquery/docs/google-ads-transfer) BigQuery Data Transfer service for Google Ads - [ ] Have existing Google Analytics 4 Property with [Measurement ID](https://support.google.com/analytics/answer/12270356?hl=en) -**Note:** Google Ads Customer Matching currently only works with Google Analytics 4 **Properties** linked to Google Ads Accounts, it won't work for subproperties or Rollup properties. +**Note:** Google Ads Customer Matching currently only works with Google Analytics 4 **Property** and **Subproperty** linked to Google Ads Accounts, it won't work for Rollup properties. ## Installation Permissions and Privileges - [ ] Google Analytics Property Editor or Owner From ec5b667adb4045a81dac6daddca4cee8f2ed4caa Mon Sep 17 00:00:00 2001 From: Charlie Wang <2144018+kingman@users.noreply.github.com> Date: Fri, 15 Nov 2024 19:01:05 +0100 Subject: [PATCH 082/110] Move to uv (#242) * add uv required project table segment in toml file * switch to uv in terraform deployment * switch to uv * remove poetry usage from terraform * format * remove poetry * Add files via upload --- DEVELOPMENT.md | 2 +- infrastructure/cloudshell/tutorial.md | 40 +----- infrastructure/terraform/README.md | 52 ++----- infrastructure/terraform/main.tf | 49 ++----- .../terraform/modules/activation/main.tf | 7 +- .../terraform/modules/activation/variables.tf | 9 +- .../feature-store/bigquery-procedures.tf | 2 +- .../terraform/modules/feature-store/main.tf | 1 - .../modules/feature-store/variables.tf | 6 +- .../terraform/modules/pipelines/pipelines.tf | 129 +++++++++--------- .../terraform/modules/pipelines/variables.tf | 9 +- infrastructure/terraform/variables.tf | 6 +- notebooks/quick_installation.ipynb | 18 +-- pyproject.toml | 9 ++ scripts/generate-tf-backend.sh | 10 +- 15 files changed, 121 insertions(+), 228 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 19c0800f..e72dee1b 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -3,7 +3,7 @@ Marketing Analytics Jumpstart consists of an easy, extensible and automated impl ## Developer pre-requisites Use Visual Studio Code to develop the solution. Install Gemini Code Assistant, Docker, GitHub, Hashicorp, Terraform, Jinja extensions. -You should have Python 3, Poetry, Terraform, Git and Docker installed in your developer terminal environment. +You should have Python 3, uv, Terraform, Git and Docker installed in your developer terminal environment. ## Preparing development environment diff --git a/infrastructure/cloudshell/tutorial.md b/infrastructure/cloudshell/tutorial.md index e0293e30..d4d2e519 100644 --- a/infrastructure/cloudshell/tutorial.md +++ b/infrastructure/cloudshell/tutorial.md @@ -12,45 +12,17 @@ export PROJECT_ID="" gcloud config set project $PROJECT_ID ``` -## Install or update Python3 -Install a compatible version of Python 3.8-3.10 and set the CLOUDSDK_PYTHON environment variable to point to it. -```sh -sudo apt-get install python3.10 -CLOUDSDK_PYTHON=python3.10 -``` +## Install update uv for running python scripts +Install [uv](https://docs.astral.sh/uv/) that manages the python version and dependecies for the solution. -## Install Python's Poetry and set Poetry to use Python 3.10 version -[Poetry](https://python-poetry.org/docs/) is a Python's tool for dependency management and packaging. -If you are installing on in Cloud Shell use the following commands: -```sh -pipx install poetry -``` -If you don't have pipx installed - follow the [Pipx installation guide](https://pipx.pypa.io/stable/installation/) -```sh -sudo apt update -sudo apt install pipx -pipx ensurepath -pipx install poetry -``` -Verify that `poetry` is on your $PATH variable: -```sh -poetry --version -``` -If it fails - add it to your $PATH variable: ```sh +curl -LsSf https://astral.sh/uv/install.sh | sh export PATH="$HOME/.local/bin:$PATH" ``` -Verify poetry is properly installed, run: -```sh -poetry --version -``` -Set poetry to use your latest python3 -```sh -poetry env use python3 -``` -Install python dependencies, run: + +Check uv installation ```sh -poetry install +uv --version ``` ## Authenticate with additional OAuth 2.0 scopes diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index e0ba4aa9..5d49e845 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -43,52 +43,18 @@ Also, this method allows you to extend this solution and develop it to satisfy y gcloud config set project $PROJECT_ID ``` -1. Install or update Python3 - Install a compatible version of Python 3.8-3.10 and set the CLOUDSDK_PYTHON environment variable to point to it. +1. Install update uv for running python scripts + Install [uv](https://docs.astral.sh/uv/) that manages the python version and dependecies for the solution. - ```bash - sudo apt-get install python3.10 - CLOUDSDK_PYTHON=python3.10 + ```sh + curl -LsSf https://astral.sh/uv/install.sh | sh + export PATH="$HOME/.local/bin:$PATH" ``` - If you are installing on a Mac: - ```shell - brew install python@3.10 - CLOUDSDK_PYTHON=python3.10 - ``` - -1. Install Python's Poetry and set Poetry to use Python 3.10 version - - [Poetry](https://python-poetry.org/docs/) is a Python's tool for dependency management and packaging. - If you are installing on in Cloud Shell use the following commands: - ```shell - pipx install poetry - ``` - If you don't have pipx installed - follow the [Pipx installation guide](https://pipx.pypa.io/stable/installation/) - ```shell - sudo apt update - sudo apt install pipx - pipx ensurepath - pipx install poetry - ``` - Verify that `poetry` is on your $PATH variable: - ```shell - poetry --version - ``` - If it fails - add it to your $PATH variable: - ```shell - export PATH="$HOME/.local/bin:$PATH" - ``` - If you are installing on a Mac: - ```shell - brew install poetry - ``` - Set poetry to use your latest python3 - ```shell - SOURCE_ROOT=${HOME}/${REPO} - cd ${SOURCE_ROOT} - poetry env use python3 - ``` + Check uv installation: + ```sh + uv --version + ``` 1. Authenticate with additional OAuth 2.0 scopes needed to use the Google Analytics Admin API: ```shell diff --git a/infrastructure/terraform/main.tf b/infrastructure/terraform/main.tf index a6ede8bc..1136cb91 100644 --- a/infrastructure/terraform/main.tf +++ b/infrastructure/terraform/main.tf @@ -69,8 +69,8 @@ locals { source_root_dir = "../.." # The config_file_name is the name of the config file. config_file_name = "config" - # The poetry_run_alias is the alias of the poetry command. - poetry_run_alias = "${var.poetry_cmd} run" + # The uv_run_alias is the alias of the uv run command. + uv_run_alias = "${var.uv_cmd} run" # The mds_dataset_suffix is the suffix of the marketing data store dataset. mds_dataset_suffix = var.create_staging_environment ? "staging" : var.create_dev_environment ? "dev" : "prod" # The project_toml_file_path is the path to the project.toml file. @@ -127,39 +127,22 @@ resource "local_file" "feature_store_configuration" { }) } -# Runs the poetry command to install the dependencies. -# The command is: poetry install -resource "null_resource" "poetry_install" { - triggers = { - create_command = "${var.poetry_cmd} lock && ${var.poetry_cmd} install" - source_contents_hash = local.project_toml_content_hash - } - - # Only run the command when `terraform apply` executes and the resource doesn't exist. - provisioner "local-exec" { - when = create - command = self.triggers.create_command - working_dir = local.source_root_dir - } -} - data "external" "check_ga4_property_type" { - program = ["bash", "-c", "${local.poetry_run_alias} ga4-setup --ga4_resource=check_property_type --ga4_property_id=${var.ga4_property_id} --ga4_stream_id=${var.ga4_stream_id}"] + program = ["bash", "-c", "${local.uv_run_alias} ga4-setup --ga4_resource=check_property_type --ga4_property_id=${var.ga4_property_id} --ga4_stream_id=${var.ga4_stream_id}"] working_dir = local.source_root_dir - depends_on = [null_resource.poetry_install] } -# Runs the poetry invoke command to generate the sql queries and procedures. +# Runs the uv invoke command to generate the sql queries and procedures. # This command is executed before the feature store is created. resource "null_resource" "generate_sql_queries" { triggers = { # The create command generates the sql queries and procedures. - # The command is: poetry inv [function_name] --env-name=${local.config_file_name} + # The command is: uv inv [function_name] --env-name=${local.config_file_name} # The --env-name argument is the name of the configuration file. create_command = <<-EOT - ${local.poetry_run_alias} inv apply-config-parameters-to-all-queries --env-name=${local.config_file_name} - ${local.poetry_run_alias} inv apply-config-parameters-to-all-procedures --env-name=${local.config_file_name} + ${local.uv_run_alias} inv apply-config-parameters-to-all-queries --env-name=${local.config_file_name} + ${local.uv_run_alias} inv apply-config-parameters-to-all-procedures --env-name=${local.config_file_name} EOT # The destroy command removes the generated sql queries and procedures. @@ -171,10 +154,6 @@ resource "null_resource" "generate_sql_queries" { # The working directory is the root of the project. working_dir = local.source_root_dir - # The poetry_installed trigger is the ID of the null_resource.poetry_install resource. - # This is used to ensure that the poetry command is run before the generate_sql_queries command. - poetry_installed = null_resource.poetry_install.id - # The source_contents_hash trigger is the hash of the project.toml file. # This is used to ensure that the generate_sql_queries command is run only if the project.toml file has changed. # It also ensures that the generate_sql_queries command is run only if the sql queries and procedures have changed. @@ -415,15 +394,12 @@ module "pipelines" { # The source is the path to the pipelines module. source = "./modules/pipelines" config_file_path = local_file.feature_store_configuration.id != "" ? local_file.feature_store_configuration.filename : "" - poetry_run_alias = local.poetry_run_alias + uv_run_alias = local.uv_run_alias # The count determines if the pipelines are created or not. # If the count is 1, the pipelines are created. # If the count is 0, the pipelines are not created. # This is done to avoid creating the pipelines if the `deploy_pipelines` variable is set to false in the terraform.tfvars file. count = var.deploy_pipelines ? 1 : 0 - # The poetry_installed trigger is the ID of the null_resource.poetry_install resource. - # This is used to ensure that the poetry command is run before the pipelines module is created. - poetry_installed = null_resource.poetry_install.id # The project_id is the project in which the data is stored. # This is set to the data project ID in the terraform.tfvars file. mds_project_id = var.data_project_id @@ -454,9 +430,9 @@ module "activation" { # The trigger function is used to trigger the activation function. # The trigger function is created in the same region as the activation function. trigger_function_location = var.google_default_region - # The poetry_cmd is the poetry_cmd variable. - # This can be set on the poetry_cmd in the terraform.tfvars file. - poetry_cmd = var.poetry_cmd + # The uv_run_alias is the uv_run_alias variable. + # This can be set on the uv_cmd in the terraform.tfvars file. + uv_run_alias = local.uv_run_alias # The ga4_measurement_id is the ga4_measurement_id variable. # This can be set on the ga4_measurement_id in the terraform.tfvars file. ga4_measurement_id = var.ga4_measurement_id @@ -479,9 +455,6 @@ module "activation" { # This is done to avoid creating the activation function if the `deploy_activation` variable is set # to false in the terraform.tfvars file. count = var.deploy_activation ? 1 : 0 - # The poetry_installed is the ID of the null_resource poetry_install - # This is used to ensure that the poetry command is run before the activation module is created. - poetry_installed = null_resource.poetry_install.id mds_project_id = var.data_project_id mds_dataset_suffix = local.mds_dataset_suffix diff --git a/infrastructure/terraform/modules/activation/main.tf b/infrastructure/terraform/modules/activation/main.tf index c2c8f8b8..1bf35a9d 100644 --- a/infrastructure/terraform/modules/activation/main.tf +++ b/infrastructure/terraform/modules/activation/main.tf @@ -15,7 +15,6 @@ locals { app_prefix = "activation" source_root_dir = "../.." - poetry_run_alias = "${var.poetry_cmd} run" template_dir = "${local.source_root_dir}/templates" pipeline_source_dir = "${local.source_root_dir}/python/activation" trigger_function_dir = "${local.source_root_dir}/python/function" @@ -373,7 +372,7 @@ resource "null_resource" "create_custom_events" { } provisioner "local-exec" { command = <<-EOT - ${local.poetry_run_alias} ga4-setup --ga4_resource=custom_events --ga4_property_id=${var.ga4_property_id} --ga4_stream_id=${var.ga4_stream_id} + ${var.uv_run_alias} ga4-setup --ga4_resource=custom_events --ga4_property_id=${var.ga4_property_id} --ga4_stream_id=${var.ga4_stream_id} EOT working_dir = local.source_root_dir } @@ -391,7 +390,7 @@ resource "null_resource" "create_custom_dimensions" { } provisioner "local-exec" { command = <<-EOT - ${local.poetry_run_alias} ga4-setup --ga4_resource=custom_dimensions --ga4_property_id=${var.ga4_property_id} --ga4_stream_id=${var.ga4_stream_id} + ${var.uv_run_alias} ga4-setup --ga4_resource=custom_dimensions --ga4_property_id=${var.ga4_property_id} --ga4_stream_id=${var.ga4_stream_id} EOT working_dir = local.source_root_dir } @@ -447,7 +446,7 @@ module "trigger_function_account" { # a python command defined in the module ga4_setup. # This informatoin can then be used in other parts of the Terraform configuration to access the retrieved information. data "external" "ga4_measurement_properties" { - program = ["bash", "-c", "${local.poetry_run_alias} ga4-setup --ga4_resource=measurement_properties --ga4_property_id=${var.ga4_property_id} --ga4_stream_id=${var.ga4_stream_id}"] + program = ["bash", "-c", "${var.uv_run_alias} ga4-setup --ga4_resource=measurement_properties --ga4_property_id=${var.ga4_property_id} --ga4_stream_id=${var.ga4_stream_id}"] working_dir = local.source_root_dir # The count attribute specifies how many times the external data source should be executed. # This means that the external data source will be executed only if either the diff --git a/infrastructure/terraform/modules/activation/variables.tf b/infrastructure/terraform/modules/activation/variables.tf index 6c2d6428..5814361b 100644 --- a/infrastructure/terraform/modules/activation/variables.tf +++ b/infrastructure/terraform/modules/activation/variables.tf @@ -43,8 +43,8 @@ variable "trigger_function_location" { type = string } -variable "poetry_cmd" { - description = "alias for poetry command on the current system" +variable "uv_run_alias" { + description = "alias for uv run command on the current system" type = string } @@ -72,11 +72,6 @@ variable "ga4_stream_id" { type = string } -variable "poetry_installed" { - description = "Construct to specify dependency to poetry installed" - type = string -} - variable "mds_project_id" { type = string description = "MDS Project ID" diff --git a/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf b/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf index 96412569..ff043b77 100644 --- a/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf +++ b/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf @@ -1471,7 +1471,7 @@ resource "null_resource" "create_gemini_model" { provisioner "local-exec" { command = <<-EOT - ${local.poetry_run_alias} bq query --use_legacy_sql=false --max_rows=100 --maximum_bytes_billed=10000000 < ${data.local_file.create_gemini_model_file.filename} + ${var.uv_run_alias} bq query --use_legacy_sql=false --max_rows=100 --maximum_bytes_billed=10000000 < ${data.local_file.create_gemini_model_file.filename} EOT } diff --git a/infrastructure/terraform/modules/feature-store/main.tf b/infrastructure/terraform/modules/feature-store/main.tf index 25b6830e..78c0eb83 100644 --- a/infrastructure/terraform/modules/feature-store/main.tf +++ b/infrastructure/terraform/modules/feature-store/main.tf @@ -21,7 +21,6 @@ locals { config_bigquery = local.config_vars.bigquery feature_store_project_id = local.config_vars.bigquery.dataset.feature_store.project_id sql_dir = var.sql_dir_input - poetry_run_alias = "${var.poetry_cmd} run" builder_repository_id = "marketing-analytics-jumpstart-base-repo" purchase_propensity_project_id = null_resource.check_bigquery_api.id != "" ? local.config_vars.bigquery.dataset.purchase_propensity.project_id : local.feature_store_project_id churn_propensity_project_id = null_resource.check_bigquery_api.id != "" ? local.config_vars.bigquery.dataset.churn_propensity.project_id : local.feature_store_project_id diff --git a/infrastructure/terraform/modules/feature-store/variables.tf b/infrastructure/terraform/modules/feature-store/variables.tf index d20b92b7..a9bc07a5 100644 --- a/infrastructure/terraform/modules/feature-store/variables.tf +++ b/infrastructure/terraform/modules/feature-store/variables.tf @@ -37,8 +37,8 @@ variable "sql_dir_input" { description = "SQL queries directory" } -variable "poetry_cmd" { - description = "alias for poetry command on the current system" +variable "uv_run_alias" { + description = "alias for uv run command on the current system" type = string - default = "poetry" + default = "uv run" } diff --git a/infrastructure/terraform/modules/pipelines/pipelines.tf b/infrastructure/terraform/modules/pipelines/pipelines.tf index bd78fa84..a55e9136 100644 --- a/infrastructure/terraform/modules/pipelines/pipelines.tf +++ b/infrastructure/terraform/modules/pipelines/pipelines.tf @@ -309,13 +309,12 @@ resource "null_resource" "build_push_pipelines_components_image" { docker_repo_id = google_artifact_registry_repository.pipelines_docker_repo.id docker_repo_create_time = google_artifact_registry_repository.pipelines_docker_repo.create_time source_content_hash = local.component_image_content_hash - poetry_installed = var.poetry_installed } # The provisioner block specifies the command that will be executed to build and push the base component image. # This command will execute the build-push function in the base_component_image module, which will build and push the base component image to the specified Docker repository. provisioner "local-exec" { - command = "${var.poetry_run_alias} python -m base_component_image.build-push -c ${local.config_file_path_relative_python_run_dir}" + command = "${var.uv_run_alias} python -m base_component_image.build-push -c ${local.config_file_path_relative_python_run_dir}" working_dir = self.triggers.working_dir } } @@ -367,9 +366,9 @@ resource "null_resource" "compile_feature_engineering_auto_audience_segmentation # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-auto-audience-segmentation.execution -o fe_auto_audience_segmentation.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f fe_auto_audience_segmentation.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-auto-audience-segmentation.execution -i fe_auto_audience_segmentation.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-auto-audience-segmentation.execution -o fe_auto_audience_segmentation.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f fe_auto_audience_segmentation.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-auto-audience-segmentation.execution -i fe_auto_audience_segmentation.yaml EOT working_dir = self.triggers.working_dir } @@ -391,9 +390,9 @@ resource "null_resource" "compile_feature_engineering_aggregated_value_based_bid # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-aggregated-value-based-bidding.execution -o fe_agg_vbb.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f fe_agg_vbb.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-aggregated-value-based-bidding.execution -i fe_agg_vbb.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-aggregated-value-based-bidding.execution -o fe_agg_vbb.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f fe_agg_vbb.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-aggregated-value-based-bidding.execution -i fe_agg_vbb.yaml EOT working_dir = self.triggers.working_dir } @@ -415,9 +414,9 @@ resource "null_resource" "compile_feature_engineering_audience_segmentation_pipe # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-audience-segmentation.execution -o fe_audience_segmentation.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f fe_audience_segmentation.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-audience-segmentation.execution -i fe_audience_segmentation.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-audience-segmentation.execution -o fe_audience_segmentation.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f fe_audience_segmentation.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-audience-segmentation.execution -i fe_audience_segmentation.yaml EOT working_dir = self.triggers.working_dir } @@ -439,9 +438,9 @@ resource "null_resource" "compile_feature_engineering_purchase_propensity_pipeli # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-purchase-propensity.execution -o fe_purchase_propensity.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f fe_purchase_propensity.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-purchase-propensity.execution -i fe_purchase_propensity.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-purchase-propensity.execution -o fe_purchase_propensity.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f fe_purchase_propensity.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-purchase-propensity.execution -i fe_purchase_propensity.yaml EOT working_dir = self.triggers.working_dir } @@ -463,9 +462,9 @@ resource "null_resource" "compile_feature_engineering_churn_propensity_pipeline" # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-churn-propensity.execution -o fe_churn_propensity.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f fe_churn_propensity.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-churn-propensity.execution -i fe_churn_propensity.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-churn-propensity.execution -o fe_churn_propensity.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f fe_churn_propensity.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-churn-propensity.execution -i fe_churn_propensity.yaml EOT working_dir = self.triggers.working_dir } @@ -487,9 +486,9 @@ resource "null_resource" "compile_feature_engineering_customer_lifetime_value_pi # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-customer-ltv.execution -o fe_customer_ltv.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f fe_customer_ltv.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-customer-ltv.execution -i fe_customer_ltv.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-customer-ltv.execution -o fe_customer_ltv.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f fe_customer_ltv.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.feature-creation-customer-ltv.execution -i fe_customer_ltv.yaml EOT working_dir = self.triggers.working_dir } @@ -512,9 +511,9 @@ resource "null_resource" "compile_purchase_propensity_training_pipelines" { # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.purchase_propensity.training -o purchase_propensity_training.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f purchase_propensity_training.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.purchase_propensity.training -i purchase_propensity_training.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.purchase_propensity.training -o purchase_propensity_training.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f purchase_propensity_training.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.purchase_propensity.training -i purchase_propensity_training.yaml EOT working_dir = self.triggers.working_dir } @@ -533,9 +532,9 @@ resource "null_resource" "compile_purchase_propensity_prediction_pipelines" { # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.purchase_propensity.prediction -o purchase_propensity_prediction.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f purchase_propensity_prediction.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.purchase_propensity.prediction -i purchase_propensity_prediction.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.purchase_propensity.prediction -o purchase_propensity_prediction.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f purchase_propensity_prediction.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.purchase_propensity.prediction -i purchase_propensity_prediction.yaml EOT working_dir = self.triggers.working_dir } @@ -554,9 +553,9 @@ resource "null_resource" "compile_propensity_clv_training_pipelines" { # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.propensity_clv.training -o propensity_clv_training.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f propensity_clv_training.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.propensity_clv.training -i propensity_clv_training.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.propensity_clv.training -o propensity_clv_training.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f propensity_clv_training.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.propensity_clv.training -i propensity_clv_training.yaml EOT working_dir = self.triggers.working_dir } @@ -575,9 +574,9 @@ resource "null_resource" "compile_clv_training_pipelines" { # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.clv.training -o clv_training.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f clv_training.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.clv.training -i clv_training.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.clv.training -o clv_training.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f clv_training.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.clv.training -i clv_training.yaml EOT working_dir = self.triggers.working_dir } @@ -596,9 +595,9 @@ resource "null_resource" "compile_clv_prediction_pipelines" { # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.clv.prediction -o clv_prediction.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f clv_prediction.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.clv.prediction -i clv_prediction.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.clv.prediction -o clv_prediction.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f clv_prediction.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.clv.prediction -i clv_prediction.yaml EOT working_dir = self.triggers.working_dir } @@ -617,9 +616,9 @@ resource "null_resource" "compile_segmentation_training_pipelines" { # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.segmentation.training -o segmentation_training.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f segmentation_training.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.segmentation.training -i segmentation_training.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.segmentation.training -o segmentation_training.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f segmentation_training.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.segmentation.training -i segmentation_training.yaml EOT working_dir = self.triggers.working_dir } @@ -638,9 +637,9 @@ resource "null_resource" "compile_segmentation_prediction_pipelines" { # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.segmentation.prediction -o segmentation_prediction.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f segmentation_prediction.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.segmentation.prediction -i segmentation_prediction.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.segmentation.prediction -o segmentation_prediction.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f segmentation_prediction.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.segmentation.prediction -i segmentation_prediction.yaml EOT working_dir = self.triggers.working_dir } @@ -659,9 +658,9 @@ resource "null_resource" "compile_auto_segmentation_training_pipelines" { # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.auto_segmentation.training -o auto_segmentation_training.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f auto_segmentation_training.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.auto_segmentation.training -i auto_segmentation_training.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.auto_segmentation.training -o auto_segmentation_training.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f auto_segmentation_training.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.auto_segmentation.training -i auto_segmentation_training.yaml EOT working_dir = self.triggers.working_dir } @@ -680,9 +679,9 @@ resource "null_resource" "compile_auto_segmentation_prediction_pipelines" { # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.auto_segmentation.prediction -o auto_segmentation_prediction.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f auto_segmentation_prediction.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.auto_segmentation.prediction -i auto_segmentation_prediction.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.auto_segmentation.prediction -o auto_segmentation_prediction.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f auto_segmentation_prediction.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.auto_segmentation.prediction -i auto_segmentation_prediction.yaml EOT working_dir = self.triggers.working_dir } @@ -701,9 +700,9 @@ resource "null_resource" "compile_value_based_bidding_training_pipelines" { # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.value_based_bidding.training -o vbb_training.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f vbb_training.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.value_based_bidding.training -i vbb_training.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.value_based_bidding.training -o vbb_training.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f vbb_training.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.value_based_bidding.training -i vbb_training.yaml EOT working_dir = self.triggers.working_dir } @@ -722,9 +721,9 @@ resource "null_resource" "compile_value_based_bidding_explanation_pipelines" { # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.value_based_bidding.explanation -o vbb_explanation.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f vbb_explanation.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.value_based_bidding.explanation -i vbb_explanation.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.value_based_bidding.explanation -o vbb_explanation.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f vbb_explanation.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.value_based_bidding.explanation -i vbb_explanation.yaml EOT working_dir = self.triggers.working_dir } @@ -743,9 +742,9 @@ resource "null_resource" "compile_churn_propensity_training_pipelines" { # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.churn_propensity.training -o churn_propensity_training.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f churn_propensity_training.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.churn_propensity.training -i churn_propensity_training.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.churn_propensity.training -o churn_propensity_training.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f churn_propensity_training.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.churn_propensity.training -i churn_propensity_training.yaml EOT working_dir = self.triggers.working_dir } @@ -764,9 +763,9 @@ resource "null_resource" "compile_churn_propensity_prediction_pipelines" { # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.churn_propensity.prediction -o churn_propensity_prediction.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f churn_propensity_prediction.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.churn_propensity.prediction -i churn_propensity_prediction.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.churn_propensity.prediction -o churn_propensity_prediction.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f churn_propensity_prediction.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.churn_propensity.prediction -i churn_propensity_prediction.yaml EOT working_dir = self.triggers.working_dir } @@ -785,9 +784,9 @@ resource "null_resource" "compile_reporting_preparation_aggregate_predictions_pi # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.reporting_preparation.execution -o reporting_preparation.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f reporting_preparation.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.reporting_preparation.execution -i reporting_preparation.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.reporting_preparation.execution -o reporting_preparation.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f reporting_preparation.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.reporting_preparation.execution -i reporting_preparation.yaml EOT working_dir = self.triggers.working_dir } @@ -806,9 +805,9 @@ resource "null_resource" "compile_gemini_insights_pipelines" { # which will upload the pipeline YAML file to the specified Artifact Registry repository. The scheduler function will then schedule the pipeline to run on a regular basis. provisioner "local-exec" { command = <<-EOT - ${var.poetry_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.gemini_insights.execution -o gemini_insights.yaml - ${var.poetry_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f gemini_insights.yaml -t ${self.triggers.tag} -t latest - ${var.poetry_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.gemini_insights.execution -i gemini_insights.yaml + ${var.uv_run_alias} python -m pipelines.compiler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.gemini_insights.execution -o gemini_insights.yaml + ${var.uv_run_alias} python -m pipelines.uploader -c ${local.config_file_path_relative_python_run_dir} -f gemini_insights.yaml -t ${self.triggers.tag} -t latest + ${var.uv_run_alias} python -m pipelines.scheduler -c ${local.config_file_path_relative_python_run_dir} -p vertex_ai.pipelines.gemini_insights.execution -i gemini_insights.yaml EOT working_dir = self.triggers.working_dir } diff --git a/infrastructure/terraform/modules/pipelines/variables.tf b/infrastructure/terraform/modules/pipelines/variables.tf index 3afaaed3..3c618cac 100644 --- a/infrastructure/terraform/modules/pipelines/variables.tf +++ b/infrastructure/terraform/modules/pipelines/variables.tf @@ -17,13 +17,8 @@ variable "config_file_path" { description = "pipelines config file" } -variable "poetry_run_alias" { - description = "alias for poetry run command on the current system" - type = string -} - -variable "poetry_installed" { - description = "Construct to specify dependency to poetry installed" +variable "uv_run_alias" { + description = "alias for uv run command on the current system" type = string } diff --git a/infrastructure/terraform/variables.tf b/infrastructure/terraform/variables.tf index e479a7d2..cd1e84ae 100644 --- a/infrastructure/terraform/variables.tf +++ b/infrastructure/terraform/variables.tf @@ -225,10 +225,10 @@ variable "feature_store_config_env" { default = "config" } -variable "poetry_cmd" { - description = "alias for poetry run command on the current system" +variable "uv_cmd" { + description = "alias for uv run command on the current system" type = string - default = "poetry" + default = "uv" } variable "feature_store_project_id" { diff --git a/notebooks/quick_installation.ipynb b/notebooks/quick_installation.ipynb index d7c31085..a2d1be9f 100644 --- a/notebooks/quick_installation.ipynb +++ b/notebooks/quick_installation.ipynb @@ -6,8 +6,7 @@ "provenance": [], "collapsed_sections": [ "DDGHqJNhq5Oi", - "mOISt4ShqIbc", - "US36yJ8lmqnP" + "mOISt4ShqIbc" ] }, "kernelspec": { @@ -305,19 +304,10 @@ "# @title\n", "%%capture\n", "%%bash\n", - "# prompt: install packages\n", - "apt-get install python3.10\n", - "CLOUDSDK_PYTHON=python3.10\n", - "\n", - "#pip3 install poetry\n", - "sudo apt update\n", - "sudo apt install pipx\n", - "pipx ensurepath\n", - "pipx install poetry\n", - "\n", + "# prompt: install uv\n", + "curl -LsSf https://astral.sh/uv/install.sh | sh\n", "export PATH=\"/root/.local/bin:$PATH\"\n", - "poetry env use python3.10\n", - "poetry --version\n", + "uv --version\n", "\n", "git clone --depth=1 https://github.com/tfutils/tfenv.git ~/.tfenv\n", "echo 'export PATH=\"~/.tfenv/bin:$PATH\"' >> ~/.bash_profile\n", diff --git a/pyproject.toml b/pyproject.toml index 98a6876a..e500633a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +[project] +name = "marketing-analytics-jumpstart" +version = "1.0.0" +description = "Marketing Analytics Jumpstart" +authors = ["Marketing Analytics Solutions Architects "] +license = "Apache 2.0" +readme = "README.md" +requires-python = ">=3.8,<3.11" + [tool.poetry] name = "marketing-analytics-jumpstart" version = "1.0.0" diff --git a/scripts/generate-tf-backend.sh b/scripts/generate-tf-backend.sh index 5a1178fc..2611dc0e 100755 --- a/scripts/generate-tf-backend.sh +++ b/scripts/generate-tf-backend.sh @@ -19,15 +19,15 @@ set -o nounset . scripts/common.sh -section_open "Check if the necessary dependencies are available: gcloud, gsutil, terraform, poetry" +section_open "Check if the necessary dependencies are available: gcloud, gsutil, terraform, uv" check_exec_dependency "gcloud" check_exec_version "gcloud" check_exec_dependency "gsutil" check_exec_version "gsutil" check_exec_dependency "terraform" check_exec_version "terraform" - check_exec_dependency "poetry" - check_exec_version "poetry" + check_exec_dependency "uv" + check_exec_version "uv" section_close section_open "Check if the necessary variables are set: PROJECT_ID" @@ -51,10 +51,6 @@ section_open "Enable all the required APIs" enable_all_apis section_close -section_open "Install poetry libraries in the virtual environment for Terraform" - poetry install -section_close - section_open "Creating a new Google Cloud Storage bucket to store the Terraform state in ${TF_STATE_PROJECT} project, bucket: ${TF_STATE_BUCKET}" if gsutil ls -b gs://"${TF_STATE_BUCKET}" >/dev/null 2>&1; then printf "The ${TF_STATE_BUCKET} Google Cloud Storage bucket already exists. \n" From 87917d74ac8a80c591f64f53c081f25e20c7cddd Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 15 Nov 2024 16:54:25 -0500 Subject: [PATCH 083/110] Support property id in resources (#246) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * predicting for only the users with traffic in the past 72h - purchase propensity * running inference only for users events in the past 72h * including 72h users for all models predictions * considering null values in TabWorkflow models * deleting unused pipfile * upgrading lib versions * implementing reporting preprocessing as a new pipeline * adding more code documentation * adding important information on the main README.md and DEVELOPMENT.md * adding schedule run name and more code documentation * implementing a new scheduler using the vertex ai sdk & adding user_id to procedures for consistency * adding more code documentation * adding code doc to the python custom component * adding more code documentation * fixing aggregated predictions query * removing unnecessary resources from deployment * Writing MDS guide * adding the MDS developer and troubleshooting documentation * fixing deployment for activation pipelines and gemini dataset * Update README.md * Update README.md * Update README.md * Update README.md * removing deprecated api * fixing purchase propensity pipelines names * adding extra condition for when there is not enough data for the window interval to be applied on backfill procedures * adding more instructions for post deployment and fixing issues when GA4 export was configured for less than 10 days * removing unnecessary comments * adding the number of past days to process in the variables files * adding comment about combining data from different ga4 export datasets to data store * fixing small issues with feature engineering and ml pipelines * fixing hyper parameter tuning for kmeans modeling * fixing optuna parameters * adding cloud shell image * fixing the list of all possible users in the propensity training preparation tables * additional guardrails for when there is not enough data * adding more documentation * adding more doc to feature store * add feature store documentation * adding ml pipelines docs * adding ml pipelines docs * adding more documentation * adding user agent client info * fixing scope of client info * fix * removing client_info from vertex components * fixing versioning of tf submodules * reconfiguring meta providers * fixing issue 187 * chore(deps): upgrade terraform providers and modules version * chore(deps): set the provider version * chore: formatting * fix: brand naming * fix: typo * fixing secrets issue * implementing secrets region as tf variable * implementing secrets region as tf variable * last changes requested by lgrangeau * documenting keys location better * implementing vpc peering network * Update README.md * Rebase Main into Multi-property (#243) * Update README.md * ensure the build bucket is created in the specified region (#230) * Update audience_segmentation_query_template.sqlx * Update auto_audience_segmentation_query_template.sqlx * Update churn_propensity_query_template.sqlx * Update cltv_query_template.sqlx * Update purchase_propensity_query_template.sqlx * Restrict regions for GCP Cloud Build support (#241) * Update README.md * Move to uv (#242) * add uv required project table segment in toml file * switch to uv in terraform deployment * switch to uv * remove poetry usage from terraform * format * remove poetry * Add files via upload --------- Co-authored-by: Charlie Wang <2144018+kingman@users.noreply.github.com> Co-authored-by: Mårten Lindblad * supporting property id in the resources --------- Co-authored-by: Carlos Timoteo Co-authored-by: Laurent Grangeau Co-authored-by: Charlie Wang <2144018+kingman@users.noreply.github.com> Co-authored-by: Mårten Lindblad --- docs/data_store.md | 7 +- .../cloudshell/terraform-template.tfvars | 5 +- infrastructure/terraform/main.tf | 21 ++--- .../terraform/modules/data-store/main.tf | 79 +------------------ .../terraform/modules/data-store/variables.tf | 22 ++---- .../dataform-workflow/dataform-workflow.tf | 6 +- .../modules/dataform-workflow/scheduler.tf | 4 +- .../dataform-workflow/service-account.tf | 12 +-- .../modules/dataform-workflow/variables.tf | 2 +- .../terraform/terraform-sample.tfvars | 6 +- infrastructure/terraform/variables.tf | 26 +++--- 11 files changed, 46 insertions(+), 144 deletions(-) diff --git a/docs/data_store.md b/docs/data_store.md index 70a4b05e..f015d357 100644 --- a/docs/data_store.md +++ b/docs/data_store.md @@ -107,12 +107,11 @@ To deploy the Marketing Data Store, follow the pre-requisites and instructions i Next, after creating the Terraform variables file by making a copy from the template, set the Terraform variables to create the environments you need for Dataform. ```bash -create_dev_environment = false -create_staging_environment = false -create_prod_environment = true +deploy_dataform = true +property_id = "PROPERTY_ID" ``` -When the `create_dev_environment` variable is set to `true`, a development environment will be created. When the `create_staging_environment` variable is set to `true`, a staging environment will be created. When the `create_prod_environment` variable is set to `true`, a production environment will be created. +When the `deploy_dataform` variable is set to `true`, a dataform workspace will be created. ![Dataform Repository](images/data_store_dataform_github_repository.png) After deploying the Marketing Data Store, the repository called `marketing_analytics` is created in Dataform. diff --git a/infrastructure/cloudshell/terraform-template.tfvars b/infrastructure/cloudshell/terraform-template.tfvars index 3f42716b..c38764da 100644 --- a/infrastructure/cloudshell/terraform-template.tfvars +++ b/infrastructure/cloudshell/terraform-template.tfvars @@ -17,10 +17,7 @@ tf_state_project_id = "${MAJ_DEFAULT_PROJECT_ID}" google_default_region = "${MAJ_DEFAULT_REGION}" -create_dev_environment = false -create_staging_environment = false -create_prod_environment = true - +deploy_dataform = true deploy_activation = true deploy_feature_store = true deploy_pipelines = true diff --git a/infrastructure/terraform/main.tf b/infrastructure/terraform/main.tf index 1136cb91..4bf297a2 100644 --- a/infrastructure/terraform/main.tf +++ b/infrastructure/terraform/main.tf @@ -72,7 +72,7 @@ locals { # The uv_run_alias is the alias of the uv run command. uv_run_alias = "${var.uv_cmd} run" # The mds_dataset_suffix is the suffix of the marketing data store dataset. - mds_dataset_suffix = var.create_staging_environment ? "staging" : var.create_dev_environment ? "dev" : "prod" + mds_dataset_suffix = var.property_id # The project_toml_file_path is the path to the project.toml file. project_toml_file_path = "${local.source_root_dir}/pyproject.toml" # The project_toml_content_hash is the hash of the project.toml file. @@ -284,8 +284,7 @@ resource "null_resource" "check_iam_api" { # Create the data store module. # The data store module creates the marketing data store in BigQuery, creates the ETL pipeline in Dataform # for the marketing data from Google Ads and Google Analytics. -# The data store is created only if the `create_prod_environment`, `create_staging_environment` -# or `create_dev_environment` variable is set to true in the terraform.tfvars file. +# The data store is created only if the `deploy_dataform` variable is set to true in the terraform.tfvars file. # The data store is created in the `data_project_id` project. module "data_store" { # The source directory of the data store module. @@ -317,18 +316,10 @@ module "data_store" { dataform_github_repo = var.dataform_github_repo dataform_github_token = var.dataform_github_token - # The create_dev_environment is set in the terraform.tfvars file. - # The create_dev_environment determines if the dev environment is created. - # When the value is true, the dev environment is created. - # The create_staging_environment is set in the terraform.tfvars file. - # The create_staging_environment determines if the staging environment is created. - # When the value is true, the staging environment is created. - # The create_prod_environment is set in the terraform.tfvars file. - # The create_prod_environment determines if the prod environment is created. - # When the value is true, the prod environment is created. - create_dev_environment = var.create_dev_environment - create_staging_environment = var.create_staging_environment - create_prod_environment = var.create_prod_environment + # The create_dataform determines if dataform is created. + # When the value is true, the dataform environment is created. + deploy_dataform = var.deploy_dataform + property_id = var.property_id # The dev_data_project_id is the project ID of where the dev datasets will created. #If not provided, data_project_id will be used. diff --git a/infrastructure/terraform/modules/data-store/main.tf b/infrastructure/terraform/modules/data-store/main.tf index 35f6011e..9704661b 100644 --- a/infrastructure/terraform/modules/data-store/main.tf +++ b/infrastructure/terraform/modules/data-store/main.tf @@ -29,90 +29,19 @@ provider "google" { region = var.google_default_region } -# This module sets up a Dataform workflow environment for the "dev" environment. -module "dataform-workflow-dev" { - # The count argument specifies how many instances of the module should be created. - # In this case, it's set to var.create_dev_environment ? 1 : 0, which means that - # the module will be created only if the var.create_dev_environment variable is set to `true`. - # Check the terraform.tfvars file for more information. - count = var.create_dev_environment ? 1 : 0 - # the path to the Terraform module that will be used to create the Dataform workflow environment. - source = "../dataform-workflow" - - project_id = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id - # The name of the Dataform workflow environment. - environment = "dev" - region = var.google_default_region - # The ID of the Dataform repository that will be used by the Dataform workflow environment. - dataform_repository_id = google_dataform_repository.marketing-analytics.id - # A list of tags that will be used to filter the Dataform files that are included in the Dataform workflow environment. - includedTags = ["ga4"] - - source_ga4_export_project_id = var.source_ga4_export_project_id - source_ga4_export_dataset = var.source_ga4_export_dataset - ga4_incremental_processing_days_back = var.ga4_incremental_processing_days_back - source_ads_export_data = var.source_ads_export_data - destination_bigquery_project_id = length(var.dev_data_project_id) > 0 ? var.staging_data_project_id : var.data_project_id - destination_bigquery_dataset_location = length(var.dev_destination_data_location) > 0 ? var.dev_destination_data_location : var.destination_data_location - - # The daily schedule for running the Dataform workflow. - # Depending on the hour that your Google Analytics 4 BigQuery Export is set, - # you may have to change this to execute at a later time of the day. - # Observe that the GA4 BigQuery Export Schedule documentation - # https://support.google.com/analytics/answer/9358801?hl=en#:~:text=A%20full%20export%20of%20data,(see%20Streaming%20export%20below). - # Check https://crontab.guru/#0_5-23/4_*_*_* to see next execution times. - daily_schedule = "0 5-23/4 * * *" - time_zone = var.time_zone -} - -# This module sets up a Dataform workflow environment for the "staging" environment. -module "dataform-workflow-staging" { - # The count argument specifies how many instances of the module should be created. - # In this case, it's set to var.create_staging_environment ? 1 : 0, which means that - # the module will be created only if the var.create_staging_environment variable is set to `true`. - # Check the terraform.tfvars file for more information. - count = var.create_staging_environment ? 1 : 0 - # the path to the Terraform module that will be used to create the Dataform workflow environment. - source = "../dataform-workflow" - - project_id = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id - # The name of the Dataform workflow environment. - environment = "staging" - region = var.google_default_region - # The ID of the Dataform repository that will be used by the Dataform workflow environment. - dataform_repository_id = google_dataform_repository.marketing-analytics.id - # A list of tags that will be used to filter the Dataform files that are included in the Dataform workflow environment. - includedTags = ["ga4"] - - source_ga4_export_project_id = var.source_ga4_export_project_id - source_ga4_export_dataset = var.source_ga4_export_dataset - source_ads_export_data = var.source_ads_export_data - destination_bigquery_project_id = length(var.staging_data_project_id) > 0 ? var.staging_data_project_id : var.data_project_id - destination_bigquery_dataset_location = length(var.staging_destination_data_location) > 0 ? var.staging_destination_data_location : var.destination_data_location - - # The daily schedule for running the Dataform workflow. - # Depending on the hour that your Google Analytics 4 BigQuery Export is set, - # you may have to change this to execute at a later time of the day. - # Observe that the GA4 BigQuery Export Schedule documentation - # https://support.google.com/analytics/answer/9358801?hl=en#:~:text=A%20full%20export%20of%20data,(see%20Streaming%20export%20below). - # Check https://crontab.guru/#0_5-23/4_*_*_* to see next execution times. - daily_schedule = "0 5-23/4 * * *" - time_zone = var.time_zone -} - # This module sets up a Dataform workflow environment for the "prod" environment. module "dataform-workflow-prod" { # The count argument specifies how many instances of the module should be created. - # In this case, it's set to var.create_prod_environment ? 1 : 0, which means that - # the module will be created only if the var.create_prod_environment variable is set to `true`. + # In this case, it's set to var.deploy_dataform ? 1 : 0, which means that + # the module will be created only if the var.deploy_dataform variable is set to `true`. # Check the terraform.tfvars file for more information. - count = var.create_prod_environment ? 1 : 0 + count = var.deploy_dataform ? 1 : 0 # the path to the Terraform module that will be used to create the Dataform workflow environment. source = "../dataform-workflow" project_id = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id # The name of the Dataform workflow environment. - environment = "prod" + property_id = var.property_id region = var.google_default_region dataform_repository_id = google_dataform_repository.marketing-analytics.id diff --git a/infrastructure/terraform/modules/data-store/variables.tf b/infrastructure/terraform/modules/data-store/variables.tf index bd85ad53..bd11aab7 100644 --- a/infrastructure/terraform/modules/data-store/variables.tf +++ b/infrastructure/terraform/modules/data-store/variables.tf @@ -47,12 +47,6 @@ variable "dataform_github_token" { type = string } -variable "create_dev_environment" { - description = "Indicates that a development environment needs to be created" - type = bool - default = true -} - variable "dev_data_project_id" { description = "Project ID of where the dev datasets will created. If not provided, data_project_id will be used." type = string @@ -65,12 +59,6 @@ variable "dev_destination_data_location" { default = "" } -variable "create_staging_environment" { - description = "Indicates that a staging environment needs to be created" - type = bool - default = true -} - variable "staging_data_project_id" { description = "Project ID of where the staging datasets will created. If not provided, data_project_id will be used." type = string @@ -83,12 +71,18 @@ variable "staging_destination_data_location" { default = "" } -variable "create_prod_environment" { - description = "Indicates that a production environment needs to be created" +variable "deploy_dataform" { + description = "Indicates that a dataform workspace needs to be created" type = bool default = true } +variable "property_id" { + description = "Google Analytics 4 Property id to create an MDS for it" + type = string + default = "" +} + variable "prod_data_project_id" { description = "Project ID of where the prod datasets will created. If not provided, data_project_id will be used." type = string diff --git a/infrastructure/terraform/modules/dataform-workflow/dataform-workflow.tf b/infrastructure/terraform/modules/dataform-workflow/dataform-workflow.tf index a0d7d153..11914892 100644 --- a/infrastructure/terraform/modules/dataform-workflow/dataform-workflow.tf +++ b/infrastructure/terraform/modules/dataform-workflow/dataform-workflow.tf @@ -22,9 +22,9 @@ locals { # This resources creates a workflow that runs the Dataform incremental pipeline. resource "google_workflows_workflow" "dataform-incremental-workflow" { project = null_resource.check_workflows_api.id != "" ? module.data_processing_project_services.project_id : var.project_id - name = "dataform-${var.environment}-incremental" + name = "dataform-${var.property_id}-incremental" region = var.region - description = "Dataform incremental workflow for ${var.environment} environment" + description = "Dataform incremental workflow for ${var.property_id} ga4 property" service_account = google_service_account.workflow-dataform.email # The source code includes the following steps: # Init: This step initializes the workflow by assigning the value of the dataform_repository_id variable to the repository variable. @@ -49,7 +49,7 @@ main: defaultDatabase: ${var.destination_bigquery_project_id} defaultLocation: ${var.destination_bigquery_dataset_location} vars: - env: ${var.environment} + ga4_property_id: '${var.property_id}' ga4_export_project: ${var.source_ga4_export_project_id} ga4_export_dataset: ${var.source_ga4_export_dataset} ga4_incremental_processing_days_back: '${var.ga4_incremental_processing_days_back}' diff --git a/infrastructure/terraform/modules/dataform-workflow/scheduler.tf b/infrastructure/terraform/modules/dataform-workflow/scheduler.tf index ecb2cbd6..4c8ec0a8 100644 --- a/infrastructure/terraform/modules/dataform-workflow/scheduler.tf +++ b/infrastructure/terraform/modules/dataform-workflow/scheduler.tf @@ -15,8 +15,8 @@ # This creates a Cloud Scheduler job that triggers the Dataform incremental workflow on a daily schedule. resource "google_cloud_scheduler_job" "daily-dataform-increments" { project = module.data_processing_project_services.project_id - name = "daily-dataform-${var.environment}" - description = "Daily Dataform ${var.environment} environment incremental update" + name = "daily-dataform-${var.property_id}" + description = "Daily Dataform ${var.property_id} property export incremental update" # The schedule attribute specifies the schedule for the job. In this case, the job is scheduled to run daily at the specified times. schedule = var.daily_schedule time_zone = var.time_zone diff --git a/infrastructure/terraform/modules/dataform-workflow/service-account.tf b/infrastructure/terraform/modules/dataform-workflow/service-account.tf index b89e91af..8c21dff2 100644 --- a/infrastructure/terraform/modules/dataform-workflow/service-account.tf +++ b/infrastructure/terraform/modules/dataform-workflow/service-account.tf @@ -19,13 +19,13 @@ resource "google_service_account" "scheduler" { ] project = null_resource.check_cloudscheduler_api.id != "" ? module.data_processing_project_services.project_id : var.project_id - account_id = "workflow-scheduler-${var.environment}" - display_name = "Service Account to schedule Dataform workflows in ${var.environment}" + account_id = "workflow-scheduler-${var.property_id}" + display_name = "Service Account to schedule Dataform workflows in ${var.property_id}" } locals { - scheduler_sa = "workflow-scheduler-${var.environment}@${module.data_processing_project_services.project_id}.iam.gserviceaccount.com" - workflows_sa = "workflow-dataform-${var.environment}@${module.data_processing_project_services.project_id}.iam.gserviceaccount.com" + scheduler_sa = "workflow-scheduler-${var.property_id}@${module.data_processing_project_services.project_id}.iam.gserviceaccount.com" + workflows_sa = "workflow-dataform-${var.property_id}@${module.data_processing_project_services.project_id}.iam.gserviceaccount.com" } # Wait for the scheduler service account to be created @@ -74,8 +74,8 @@ resource "google_service_account" "workflow-dataform" { ] project = null_resource.check_workflows_api.id != "" ? module.data_processing_project_services.project_id : var.project_id - account_id = "workflow-dataform-${var.environment}" - display_name = "Service Account to run Dataform workflows in ${var.environment}" + account_id = "workflow-dataform-${var.property_id}" + display_name = "Service Account to run Dataform workflows in ${var.property_id}" } # Wait for the workflows service account to be created diff --git a/infrastructure/terraform/modules/dataform-workflow/variables.tf b/infrastructure/terraform/modules/dataform-workflow/variables.tf index 38a20fee..97d5dc73 100644 --- a/infrastructure/terraform/modules/dataform-workflow/variables.tf +++ b/infrastructure/terraform/modules/dataform-workflow/variables.tf @@ -22,7 +22,7 @@ variable "region" { type = string } -variable "environment" { +variable "property_id" { type = string } diff --git a/infrastructure/terraform/terraform-sample.tfvars b/infrastructure/terraform/terraform-sample.tfvars index 3595a9d0..71f440d7 100644 --- a/infrastructure/terraform/terraform-sample.tfvars +++ b/infrastructure/terraform/terraform-sample.tfvars @@ -16,10 +16,7 @@ tf_state_project_id = "Google Cloud project where the terraform state file is stored" -create_dev_environment = false -create_staging_environment = false -create_prod_environment = true - +deploy_dataform = true deploy_activation = true deploy_feature_store = true deploy_pipelines = true @@ -28,6 +25,7 @@ deploy_monitoring = true #################### DATA VARIABLES ################################# data_project_id = "Project id where the MDS datasets will be created" +property_id = "Google Analytics 4 property id to identify an unique MDS deployment" destination_data_location = "BigQuery location (either regional or multi-regional) for the MDS BigQuery datasets." data_processing_project_id = "Project id where the Dataform will be installed and run" source_ga4_export_project_id = "Project id which contains the GA4 export dataset" diff --git a/infrastructure/terraform/variables.tf b/infrastructure/terraform/variables.tf index cd1e84ae..9819041a 100644 --- a/infrastructure/terraform/variables.tf +++ b/infrastructure/terraform/variables.tf @@ -81,12 +81,6 @@ variable "pipelines_github_owner" { default = "temporarily unused" } -variable "create_dev_environment" { - description = "Indicates that a development environment needs to be created" - type = bool - default = true -} - variable "dev_data_project_id" { description = "Project ID of where the dev datasets will created. If not provided, data_project_id will be used." type = string @@ -99,12 +93,6 @@ variable "dev_destination_data_location" { default = "" } -variable "create_staging_environment" { - description = "Indicates that a staging environment needs to be created" - type = bool - default = true -} - variable "staging_data_project_id" { description = "Project ID of where the staging datasets will created. If not provided, data_project_id will be used." type = string @@ -117,10 +105,10 @@ variable "staging_destination_data_location" { default = "" } -variable "create_prod_environment" { - description = "Indicates that a production environment needs to be created" - type = bool - default = true +variable "property_id" { + description = "Google Analytics 4 Property ID to install the MDS" + type = string + default = "" } variable "prod_data_project_id" { @@ -189,6 +177,12 @@ variable "ga4_measurement_secret" { sensitive = true } +variable "deploy_dataform" { + description = "Toggler for activation module" + type = bool + default = false +} + variable "deploy_activation" { description = "Toggler for activation module" type = bool From bc1fc2b88684bf9709cc746a2a4eb7e77bd88be2 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 15 Nov 2024 17:47:19 -0500 Subject: [PATCH 084/110] Update terraform-template.tfvars --- infrastructure/cloudshell/terraform-template.tfvars | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/cloudshell/terraform-template.tfvars b/infrastructure/cloudshell/terraform-template.tfvars index c38764da..f08e5ebc 100644 --- a/infrastructure/cloudshell/terraform-template.tfvars +++ b/infrastructure/cloudshell/terraform-template.tfvars @@ -27,6 +27,7 @@ deploy_monitoring = true data_project_id = "${MAJ_MDS_PROJECT_ID}" destination_data_location = "${MAJ_MDS_DATA_LOCATION}" +property_id = "${MAJ_GA4_PROPERTY_ID}" data_processing_project_id = "${MAJ_MDS_DATAFORM_PROJECT_ID}" source_ga4_export_project_id = "${MAJ_GA4_EXPORT_PROJECT_ID}" source_ga4_export_dataset = "${MAJ_GA4_EXPORT_DATASET}" From fdad6d1b0416ec91051aecc5f9696265ceaed608 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 15 Nov 2024 17:49:17 -0500 Subject: [PATCH 085/110] Update setup.py --- python/ga4_setup/setup.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/python/ga4_setup/setup.py b/python/ga4_setup/setup.py index 03204812..658f1650 100644 --- a/python/ga4_setup/setup.py +++ b/python/ga4_setup/setup.py @@ -513,9 +513,14 @@ def entry(): if args.ga4_resource == "check_property_type": property = get_property(configuration) - result = { - 'supported': f"{property.property_type == property.property_type.PROPERTY_TYPE_ORDINARY}" - } + is_property_supported = set((property.property_type.PROPERTY_TYPE_ORDINARY, property.property_type.PROPERTY_TYPE_SUBPROPERTY, property.property_type.PROPERTY_TYPE_ROLLUP)) + + result = {} + if property.property_type in is_property_supported: + result = {'supported': "True"} + else: + result = {'supported': "False"} + print(json.dumps(result)) # python setup.py --ga4_resource=custom_events From 74756a73cb9a2a0ab1a1ddfe6a9e2e0dffc4f31f Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Thu, 21 Nov 2024 23:11:50 -0500 Subject: [PATCH 086/110] Solving issues with IAM member roles attribution (#251) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * predicting for only the users with traffic in the past 72h - purchase propensity * running inference only for users events in the past 72h * including 72h users for all models predictions * considering null values in TabWorkflow models * deleting unused pipfile * upgrading lib versions * implementing reporting preprocessing as a new pipeline * adding more code documentation * adding important information on the main README.md and DEVELOPMENT.md * adding schedule run name and more code documentation * implementing a new scheduler using the vertex ai sdk & adding user_id to procedures for consistency * adding more code documentation * adding code doc to the python custom component * adding more code documentation * fixing aggregated predictions query * removing unnecessary resources from deployment * Writing MDS guide * adding the MDS developer and troubleshooting documentation * fixing deployment for activation pipelines and gemini dataset * Update README.md * Update README.md * Update README.md * Update README.md * removing deprecated api * fixing purchase propensity pipelines names * adding extra condition for when there is not enough data for the window interval to be applied on backfill procedures * adding more instructions for post deployment and fixing issues when GA4 export was configured for less than 10 days * removing unnecessary comments * adding the number of past days to process in the variables files * adding comment about combining data from different ga4 export datasets to data store * fixing small issues with feature engineering and ml pipelines * fixing hyper parameter tuning for kmeans modeling * fixing optuna parameters * adding cloud shell image * fixing the list of all possible users in the propensity training preparation tables * additional guardrails for when there is not enough data * adding more documentation * adding more doc to feature store * add feature store documentation * adding ml pipelines docs * adding ml pipelines docs * adding more documentation * adding user agent client info * fixing scope of client info * fix * removing client_info from vertex components * fixing versioning of tf submodules * reconfiguring meta providers * fixing issue 187 * chore(deps): upgrade terraform providers and modules version * chore(deps): set the provider version * chore: formatting * fix: brand naming * fix: typo * fixing secrets issue * implementing secrets region as tf variable * implementing secrets region as tf variable * last changes requested by lgrangeau * documenting keys location better * implementing vpc peering network * Update README.md * Rebase Main into Multi-property (#243) * Update README.md * ensure the build bucket is created in the specified region (#230) * Update audience_segmentation_query_template.sqlx * Update auto_audience_segmentation_query_template.sqlx * Update churn_propensity_query_template.sqlx * Update cltv_query_template.sqlx * Update purchase_propensity_query_template.sqlx * Restrict regions for GCP Cloud Build support (#241) * Update README.md * Move to uv (#242) * add uv required project table segment in toml file * switch to uv in terraform deployment * switch to uv * remove poetry usage from terraform * format * remove poetry * Add files via upload --------- Co-authored-by: Charlie Wang <2144018+kingman@users.noreply.github.com> Co-authored-by: Mårten Lindblad * supporting property id in the resources * fixing iam member roles issues --------- Co-authored-by: Carlos Timoteo Co-authored-by: Laurent Grangeau Co-authored-by: Charlie Wang <2144018+kingman@users.noreply.github.com> Co-authored-by: Mårten Lindblad --- infrastructure/terraform/.terraform.lock.hcl | 19 ++ .../modules/data-store/iam-binding.tf | 206 ++++++++++++++---- 2 files changed, 178 insertions(+), 47 deletions(-) diff --git a/infrastructure/terraform/.terraform.lock.hcl b/infrastructure/terraform/.terraform.lock.hcl index a0f311d4..04cda0d6 100644 --- a/infrastructure/terraform/.terraform.lock.hcl +++ b/infrastructure/terraform/.terraform.lock.hcl @@ -163,3 +163,22 @@ provider "registry.terraform.io/hashicorp/template" { "zh:c979425ddb256511137ecd093e23283234da0154b7fa8b21c2687182d9aea8b2", ] } + +provider "registry.terraform.io/hashicorp/time" { + version = "0.12.1" + hashes = [ + "h1:j+ED7j0ZFJ4EDx7sdna76wsiIf397toylDN0dFi6v0U=", + "zh:090023137df8effe8804e81c65f636dadf8f9d35b79c3afff282d39367ba44b2", + "zh:26f1e458358ba55f6558613f1427dcfa6ae2be5119b722d0b3adb27cd001efea", + "zh:272ccc73a03384b72b964918c7afeb22c2e6be22460d92b150aaf28f29a7d511", + "zh:438b8c74f5ed62fe921bd1078abe628a6675e44912933100ea4fa26863e340e9", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:85c8bd8eefc4afc33445de2ee7fbf33a7807bc34eb3734b8eefa4e98e4cddf38", + "zh:98bbe309c9ff5b2352de6a047e0ec6c7e3764b4ed3dfd370839c4be2fbfff869", + "zh:9c7bf8c56da1b124e0e2f3210a1915e778bab2be924481af684695b52672891e", + "zh:d2200f7f6ab8ecb8373cda796b864ad4867f5c255cff9d3b032f666e4c78f625", + "zh:d8c7926feaddfdc08d5ebb41b03445166df8c125417b28d64712dccd9feef136", + "zh:e2412a192fc340c61b373d6c20c9d805d7d3dee6c720c34db23c2a8ff0abd71b", + "zh:e6ac6bba391afe728a099df344dbd6481425b06d61697522017b8f7a59957d44", + ] +} diff --git a/infrastructure/terraform/modules/data-store/iam-binding.tf b/infrastructure/terraform/modules/data-store/iam-binding.tf index 49329f81..564efd16 100644 --- a/infrastructure/terraform/modules/data-store/iam-binding.tf +++ b/infrastructure/terraform/modules/data-store/iam-binding.tf @@ -12,18 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# TODO: we might not need to have this email role at all. -resource "google_project_iam_member" "email-role" { - for_each = toset([ - "roles/iam.serviceAccountUser", // TODO: is it really needed? - "roles/dataform.admin", - "roles/dataform.editor" - ]) - role = each.key - member = "user:${var.project_owner_email}" - project = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id -} - # Check the Dataform Service Account Access Requirements for more information # https://cloud.google.com/dataform/docs/required-access locals { @@ -38,7 +26,7 @@ resource "null_resource" "wait_for_dataform_sa_creation" { MAX_TRIES=100 while ! gcloud asset search-all-iam-policies --scope=projects/${module.data_processing_project_services.project_id} --flatten="policy.bindings[].members[]" --filter="policy.bindings.members~\"serviceAccount:\"" --format="value(policy.bindings.members.split(sep=\":\").slice(1))" | grep -i "${local.dataform_sa}" && [ $COUNTER -lt $MAX_TRIES ] do - sleep 3 + sleep 10 printf "." COUNTER=$((COUNTER + 1)) done @@ -46,7 +34,7 @@ resource "null_resource" "wait_for_dataform_sa_creation" { echo "dataform service account was not created, terraform can not continue!" exit 1 fi - sleep 20 + sleep 120 EOT } @@ -56,61 +44,185 @@ resource "null_resource" "wait_for_dataform_sa_creation" { ] } +module "email-role" { + source = "terraform-google-modules/iam/google//modules/member_iam" + version = "~> 8.0" + + service_account_address = var.project_owner_email + project_id = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id + project_roles = [ + "roles/iam.serviceAccountUser", // TODO: is it really needed? + "roles/dataform.admin", + "roles/dataform.editor" + ] + prefix = "user" +} +#resource "google_project_iam_member" "email-role" { +# for_each = toset([ +# "roles/iam.serviceAccountUser", // TODO: is it really needed? +# "roles/dataform.admin", +# "roles/dataform.editor" +# ]) +# role = each.key +# member = "user:${var.project_owner_email}" +# project = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id +#} + +# Propagation time for change of access policy typically takes 2 minutes +# according to https://cloud.google.com/iam/docs/access-change-propagation +# this wait make sure the policy changes are propagated before proceeding +# with the build +resource "time_sleep" "wait_for_email_role_propagation" { + create_duration = "120s" + depends_on = [ + module.email-role + ] +} + # This resource sets the Dataform service account IAM member roles -resource "google_project_iam_member" "dataform-serviceaccount" { +module "dataform-serviceaccount" { + source = "terraform-google-modules/iam/google//modules/member_iam" + version = "~> 8.0" depends_on = [ google_dataform_repository.marketing-analytics, null_resource.check_dataform_api, - null_resource.wait_for_dataform_sa_creation + null_resource.wait_for_dataform_sa_creation, + time_sleep.wait_for_email_role_propagation ] - for_each = toset([ + service_account_address = local.dataform_sa + project_id = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id + project_roles = [ "roles/secretmanager.secretAccessor", - "roles/bigquery.jobUser" - ]) - role = each.key - member = "serviceAccount:${local.dataform_sa}" - project = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id + "roles/bigquery.jobUser", + "roles/bigquery.dataOwner", + ] + prefix = "serviceAccount" } +# This resource sets the Dataform service account IAM member roles +#resource "google_project_iam_member" "dataform-serviceaccount" { +# depends_on = [ +# google_dataform_repository.marketing-analytics, +# null_resource.check_dataform_api, +# null_resource.wait_for_dataform_sa_creation, +# time_sleep.wait_for_email_role_propagation +# ] +# for_each = toset([ +# "roles/secretmanager.secretAccessor", +# "roles/bigquery.jobUser", +# "roles/bigquery.dataOwner", +# ]) +# role = each.key +# member = "serviceAccount:${local.dataform_sa}" +# project = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id +#} -// Owner role to BigQuery in the destination data project the Dataform SA. -// Multiple datasets will be created; it requires project-level permissions -resource "google_project_iam_member" "dataform-bigquery-data-owner" { +# Propagation time for change of access policy typically takes 2 minutes +# according to https://cloud.google.com/iam/docs/access-change-propagation +# this wait make sure the policy changes are propagated before proceeding +# with the build +resource "time_sleep" "wait_for_dataform-serviceaccount_role_propagation" { + create_duration = "120s" depends_on = [ - google_dataform_repository.marketing-analytics, - null_resource.check_dataform_api, - null_resource.wait_for_dataform_sa_creation + module.dataform-serviceaccount ] - for_each = toset([ - "roles/bigquery.dataOwner", - ]) - role = each.key - member = "serviceAccount:${local.dataform_sa}" - project = null_resource.check_dataform_api.id != "" ? module.data_processing_project_services.project_id : data.google_project.data_processing.project_id } // Read access to the GA4 exports -resource "google_bigquery_dataset_iam_member" "dataform-ga4-export-reader" { +module "dataform-ga4-export-reader" { + source = "terraform-google-modules/iam/google//modules/bigquery_datasets_iam" + version = "~> 8.0" depends_on = [ google_dataform_repository.marketing-analytics, null_resource.check_dataform_api, - null_resource.wait_for_dataform_sa_creation + null_resource.wait_for_dataform_sa_creation, + time_sleep.wait_for_dataform-serviceaccount_role_propagation + ] + project = var.source_ga4_export_project_id + bigquery_datasets = [ + var.source_ga4_export_dataset, + ] + mode = "authoritative" + + bindings = { + "roles/bigquery.dataViewer" = [ + "serviceAccount:${local.dataform_sa}", + ] + "roles/bigquery.dataEditor" = [ + "serviceAccount:${local.dataform_sa}", + ] + } +} +#resource "google_bigquery_dataset_iam_member" "dataform-ga4-export-reader" { +# depends_on = [ +# google_dataform_repository.marketing-analytics, +# null_resource.check_dataform_api, +# null_resource.wait_for_dataform_sa_creation, +# time_sleep.wait_for_dataform-serviceaccount_role_propagation +# ] +# role = "roles/bigquery.dataViewer" +# member = "serviceAccount:${local.dataform_sa}" +# project = var.source_ga4_export_project_id +# dataset_id = var.source_ga4_export_dataset +#} + +# Propagation time for change of access policy typically takes 2 minutes +# according to https://cloud.google.com/iam/docs/access-change-propagation +# this wait make sure the policy changes are propagated before proceeding +# with the build +resource "time_sleep" "wait_for_dataform-ga4-export-reader_role_propagation" { + create_duration = "120s" + depends_on = [ + module.dataform-ga4-export-reader ] - role = "roles/bigquery.dataViewer" - member = "serviceAccount:${local.dataform_sa}" - project = var.source_ga4_export_project_id - dataset_id = var.source_ga4_export_dataset } // Read access to the Ads datasets -resource "google_bigquery_dataset_iam_member" "dataform-ads-export-reader" { +module "dataform-ads-export-reader" { + source = "terraform-google-modules/iam/google//modules/bigquery_datasets_iam" + version = "~> 8.0" depends_on = [ google_dataform_repository.marketing-analytics, null_resource.check_dataform_api, - null_resource.wait_for_dataform_sa_creation + null_resource.wait_for_dataform_sa_creation, + time_sleep.wait_for_dataform-ga4-export-reader_role_propagation + ] + count = length(var.source_ads_export_data) + project = var.source_ads_export_data[count.index].project + bigquery_datasets = [ + var.source_ads_export_data[count.index].dataset, + ] + mode = "authoritative" + + bindings = { + "roles/bigquery.dataViewer" = [ + "serviceAccount:${local.dataform_sa}", + ] + "roles/bigquery.dataEditor" = [ + "serviceAccount:${local.dataform_sa}", + ] + } +} +#resource "google_bigquery_dataset_iam_member" "dataform-ads-export-reader" { +# depends_on = [ +# google_dataform_repository.marketing-analytics, +# null_resource.check_dataform_api, +# null_resource.wait_for_dataform_sa_creation, +# time_sleep.wait_for_dataform-ga4-export-reader_role_propagation +# ] +# count = length(var.source_ads_export_data) +# role = "roles/bigquery.dataViewer" +# member = "serviceAccount:${local.dataform_sa}" +# project = var.source_ads_export_data[count.index].project +# dataset_id = var.source_ads_export_data[count.index].dataset +#} + +# Propagation time for change of access policy typically takes 2 minutes +# according to https://cloud.google.com/iam/docs/access-change-propagation +# this wait make sure the policy changes are propagated before proceeding +# with the build +resource "time_sleep" "wait_for_dataform-ads-export-reader_role_propagation" { + create_duration = "120s" + depends_on = [ + module.dataform-ads-export-reader ] - count = length(var.source_ads_export_data) - role = "roles/bigquery.dataViewer" - member = "serviceAccount:${local.dataform_sa}" - project = var.source_ads_export_data[count.index].project - dataset_id = var.source_ads_export_data[count.index].dataset } From b9b127fcc8ea94e6d3c78f63be15f2b6386aa29e Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 22 Nov 2024 14:11:23 -0500 Subject: [PATCH 087/110] Fixing issues with BQ to Vertex Service Account IAM member role (#252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * predicting for only the users with traffic in the past 72h - purchase propensity * running inference only for users events in the past 72h * including 72h users for all models predictions * considering null values in TabWorkflow models * deleting unused pipfile * upgrading lib versions * implementing reporting preprocessing as a new pipeline * adding more code documentation * adding important information on the main README.md and DEVELOPMENT.md * adding schedule run name and more code documentation * implementing a new scheduler using the vertex ai sdk & adding user_id to procedures for consistency * adding more code documentation * adding code doc to the python custom component * adding more code documentation * fixing aggregated predictions query * removing unnecessary resources from deployment * Writing MDS guide * adding the MDS developer and troubleshooting documentation * fixing deployment for activation pipelines and gemini dataset * Update README.md * Update README.md * Update README.md * Update README.md * removing deprecated api * fixing purchase propensity pipelines names * adding extra condition for when there is not enough data for the window interval to be applied on backfill procedures * adding more instructions for post deployment and fixing issues when GA4 export was configured for less than 10 days * removing unnecessary comments * adding the number of past days to process in the variables files * adding comment about combining data from different ga4 export datasets to data store * fixing small issues with feature engineering and ml pipelines * fixing hyper parameter tuning for kmeans modeling * fixing optuna parameters * adding cloud shell image * fixing the list of all possible users in the propensity training preparation tables * additional guardrails for when there is not enough data * adding more documentation * adding more doc to feature store * add feature store documentation * adding ml pipelines docs * adding ml pipelines docs * adding more documentation * adding user agent client info * fixing scope of client info * fix * removing client_info from vertex components * fixing versioning of tf submodules * reconfiguring meta providers * fixing issue 187 * chore(deps): upgrade terraform providers and modules version * chore(deps): set the provider version * chore: formatting * fix: brand naming * fix: typo * fixing secrets issue * implementing secrets region as tf variable * implementing secrets region as tf variable * last changes requested by lgrangeau * documenting keys location better * implementing vpc peering network * Update README.md * Rebase Main into Multi-property (#243) * Update README.md * ensure the build bucket is created in the specified region (#230) * Update audience_segmentation_query_template.sqlx * Update auto_audience_segmentation_query_template.sqlx * Update churn_propensity_query_template.sqlx * Update cltv_query_template.sqlx * Update purchase_propensity_query_template.sqlx * Restrict regions for GCP Cloud Build support (#241) * Update README.md * Move to uv (#242) * add uv required project table segment in toml file * switch to uv in terraform deployment * switch to uv * remove poetry usage from terraform * format * remove poetry * Add files via upload --------- Co-authored-by: Charlie Wang <2144018+kingman@users.noreply.github.com> Co-authored-by: Mårten Lindblad * supporting property id in the resources * fixing iam member roles issues * fixing issue with service account iam resources --------- Co-authored-by: Carlos Timoteo Co-authored-by: Laurent Grangeau Co-authored-by: Charlie Wang <2144018+kingman@users.noreply.github.com> Co-authored-by: Mårten Lindblad --- .../dataform-workflow/dataform-workflow.tf | 2 +- .../modules/dataform-workflow/scheduler.tf | 2 +- .../dataform-workflow/service-account.tf | 92 +++++++++++-------- .../terraform/modules/feature-store/main.tf | 33 +++++++ 4 files changed, 87 insertions(+), 42 deletions(-) diff --git a/infrastructure/terraform/modules/dataform-workflow/dataform-workflow.tf b/infrastructure/terraform/modules/dataform-workflow/dataform-workflow.tf index 11914892..99e3921c 100644 --- a/infrastructure/terraform/modules/dataform-workflow/dataform-workflow.tf +++ b/infrastructure/terraform/modules/dataform-workflow/dataform-workflow.tf @@ -25,7 +25,7 @@ resource "google_workflows_workflow" "dataform-incremental-workflow" { name = "dataform-${var.property_id}-incremental" region = var.region description = "Dataform incremental workflow for ${var.property_id} ga4 property" - service_account = google_service_account.workflow-dataform.email + service_account = module.workflow-dataform.email # The source code includes the following steps: # Init: This step initializes the workflow by assigning the value of the dataform_repository_id variable to the repository variable. # Create Compilation Result: This step creates a compilation result for the Dataform repository. The compilation result includes the git commit hash and the code compilation configuration. diff --git a/infrastructure/terraform/modules/dataform-workflow/scheduler.tf b/infrastructure/terraform/modules/dataform-workflow/scheduler.tf index 4c8ec0a8..fed10fc8 100644 --- a/infrastructure/terraform/modules/dataform-workflow/scheduler.tf +++ b/infrastructure/terraform/modules/dataform-workflow/scheduler.tf @@ -35,7 +35,7 @@ resource "google_cloud_scheduler_job" "daily-dataform-increments" { uri = "https://workflowexecutions.googleapis.com/v1/projects/${module.data_processing_project_services.project_id}/locations/${var.region}/workflows/${google_workflows_workflow.dataform-incremental-workflow.name}/executions" oauth_token { - service_account_email = google_service_account.scheduler.email + service_account_email = module.scheduler.email } } } diff --git a/infrastructure/terraform/modules/dataform-workflow/service-account.tf b/infrastructure/terraform/modules/dataform-workflow/service-account.tf index 8c21dff2..95e518e1 100644 --- a/infrastructure/terraform/modules/dataform-workflow/service-account.tf +++ b/infrastructure/terraform/modules/dataform-workflow/service-account.tf @@ -12,20 +12,36 @@ # See the License for the specific language governing permissions and # limitations under the License. -resource "google_service_account" "scheduler" { +locals { + scheduler_sa = "workflow-scheduler-${var.property_id}@${module.data_processing_project_services.project_id}.iam.gserviceaccount.com" + workflows_sa = "workflow-dataform-${var.property_id}@${module.data_processing_project_services.project_id}.iam.gserviceaccount.com" +} + +module "scheduler" { + source = "terraform-google-modules/service-accounts/google//modules/simple-sa" + version = "~> 4.0" + + project_id = null_resource.check_cloudscheduler_api.id != "" ? module.data_processing_project_services.project_id : var.project_id + name = "workflow-scheduler-${var.property_id}" + project_roles = [ + "roles/workflows.invoker" + ] + depends_on = [ module.data_processing_project_services, null_resource.check_cloudscheduler_api, ] - - project = null_resource.check_cloudscheduler_api.id != "" ? module.data_processing_project_services.project_id : var.project_id - account_id = "workflow-scheduler-${var.property_id}" - display_name = "Service Account to schedule Dataform workflows in ${var.property_id}" } -locals { - scheduler_sa = "workflow-scheduler-${var.property_id}@${module.data_processing_project_services.project_id}.iam.gserviceaccount.com" - workflows_sa = "workflow-dataform-${var.property_id}@${module.data_processing_project_services.project_id}.iam.gserviceaccount.com" +# Propagation time for change of access policy typically takes 2 minutes +# according to https://cloud.google.com/iam/docs/access-change-propagation +# this wait make sure the policy changes are propagated before proceeding +# with the build +resource "time_sleep" "wait_for_scheduler_service_account_role_propagation" { + create_duration = "120s" + depends_on = [ + module.scheduler + ] } # Wait for the scheduler service account to be created @@ -37,7 +53,7 @@ resource "null_resource" "wait_for_scheduler_sa_creation" { MAX_TRIES=100 while ! gcloud iam service-accounts list --project=${module.data_processing_project_services.project_id} --filter="EMAIL:${local.scheduler_sa} AND DISABLED:False" --format="table(EMAIL, DISABLED)" && [ $COUNTER -lt $MAX_TRIES ] do - sleep 3 + sleep 10 printf "." COUNTER=$((COUNTER + 1)) done @@ -45,37 +61,44 @@ resource "null_resource" "wait_for_scheduler_sa_creation" { echo "scheduler service account was not created, terraform can not continue!" exit 1 fi - sleep 20 + sleep 120 EOT } depends_on = [ module.data_processing_project_services, - null_resource.check_dataform_api + time_sleep.wait_for_scheduler_service_account_role_propagation, + null_resource.check_dataform_api, + module.scheduler, ] } -resource "google_project_iam_member" "scheduler-workflow-invoker" { - depends_on = [ - module.data_processing_project_services, - null_resource.check_cloudscheduler_api, - null_resource.wait_for_scheduler_sa_creation - ] +module "workflow-dataform" { + source = "terraform-google-modules/service-accounts/google//modules/simple-sa" + version = "~> 4.0" - project = null_resource.check_cloudscheduler_api.id != "" ? module.data_processing_project_services.project_id : var.project_id - member = "serviceAccount:${google_service_account.scheduler.email}" - role = "roles/workflows.invoker" -} + project_id = null_resource.check_workflows_api.id != "" ? module.data_processing_project_services.project_id : var.project_id + name = "workflow-dataform-${var.property_id}" + project_roles = [ + "roles/dataform.editor" + ] -resource "google_service_account" "workflow-dataform" { depends_on = [ module.data_processing_project_services, null_resource.check_workflows_api, + null_resource.check_dataform_api, ] +} - project = null_resource.check_workflows_api.id != "" ? module.data_processing_project_services.project_id : var.project_id - account_id = "workflow-dataform-${var.property_id}" - display_name = "Service Account to run Dataform workflows in ${var.property_id}" +# Propagation time for change of access policy typically takes 2 minutes +# according to https://cloud.google.com/iam/docs/access-change-propagation +# this wait make sure the policy changes are propagated before proceeding +# with the build +resource "time_sleep" "wait_for_workflow_dataform_service_account_role_propagation" { + create_duration = "120s" + depends_on = [ + module.workflow-dataform + ] } # Wait for the workflows service account to be created @@ -86,7 +109,7 @@ resource "null_resource" "wait_for_workflows_sa_creation" { MAX_TRIES=100 while ! gcloud iam service-accounts list --project=${module.data_processing_project_services.project_id} --filter="EMAIL:${local.workflows_sa} AND DISABLED:False" --format="table(EMAIL, DISABLED)" && [ $COUNTER -lt $MAX_TRIES ] do - sleep 3 + sleep 10 printf "." COUNTER=$((COUNTER + 1)) done @@ -94,25 +117,14 @@ resource "null_resource" "wait_for_workflows_sa_creation" { echo "workflows service account was not created, terraform can not continue!" exit 1 fi - sleep 20 + sleep 120 EOT } - depends_on = [ - module.data_processing_project_services, - null_resource.check_dataform_api - ] -} - - -resource "google_project_iam_member" "worflow-dataform-dataform-editor" { depends_on = [ module.data_processing_project_services, null_resource.check_dataform_api, - null_resource.wait_for_workflows_sa_creation + module.workflow-dataform, + time_sleep.wait_for_workflow_dataform_service_account_role_propagation, ] - - project = null_resource.check_workflows_api.id != "" ? module.data_processing_project_services.project_id : var.project_id - member = "serviceAccount:${google_service_account.workflow-dataform.email}" - role = "roles/dataform.editor" } diff --git a/infrastructure/terraform/modules/feature-store/main.tf b/infrastructure/terraform/modules/feature-store/main.tf index 78c0eb83..58aaf81a 100644 --- a/infrastructure/terraform/modules/feature-store/main.tf +++ b/infrastructure/terraform/modules/feature-store/main.tf @@ -139,5 +139,38 @@ resource "google_project_iam_member" "vertex_ai_connection_sa_roles" { "roles/bigquery.connectionAdmin" ]) role = each.key + + # The lifecycle block is used to configure the lifecycle of the table. In this case, the ignore_changes attribute is set to all, which means that Terraform will ignore + # any changes to the table and will not attempt to update the table. The prevent_destroy attribute is set to true, which means that Terraform will prevent the table from being destroyed. + lifecycle { + ignore_changes = all + prevent_destroy = true + } } + +#module "vertex_ai_connection_sa_roles" { +# source = "terraform-google-modules/iam/google//modules/member_iam" +# version = "~> 8.0" +# +# service_account_address = google_bigquery_connection.vertex_ai_connection.cloud_resource[0].service_account_id +# project_id = null_resource.check_aiplatform_api.id != "" ? module.project_services.project_id : local.feature_store_project_id +# project_roles = [ +# "roles/bigquery.jobUser", +# "roles/bigquery.dataEditor", +# "roles/storage.admin", +# "roles/storage.objectViewer", +# "roles/aiplatform.user", +# "roles/bigquery.connectionUser", +# "roles/bigquery.connectionAdmin" +# ] +# prefix = "serviceAccount" +# +# depends_on = [ +# module.project_services, +# null_resource.check_aiplatform_api, +# google_bigquery_connection.vertex_ai_connection +# ] +# +#} + From 1631315cd1685d7dda93135e878f164dfa184faa Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 22 Nov 2024 15:03:47 -0500 Subject: [PATCH 088/110] Fixing issue with Vertex AI Model Connection on BigQuery (#253) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * predicting for only the users with traffic in the past 72h - purchase propensity * running inference only for users events in the past 72h * including 72h users for all models predictions * considering null values in TabWorkflow models * deleting unused pipfile * upgrading lib versions * implementing reporting preprocessing as a new pipeline * adding more code documentation * adding important information on the main README.md and DEVELOPMENT.md * adding schedule run name and more code documentation * implementing a new scheduler using the vertex ai sdk & adding user_id to procedures for consistency * adding more code documentation * adding code doc to the python custom component * adding more code documentation * fixing aggregated predictions query * removing unnecessary resources from deployment * Writing MDS guide * adding the MDS developer and troubleshooting documentation * fixing deployment for activation pipelines and gemini dataset * Update README.md * Update README.md * Update README.md * Update README.md * removing deprecated api * fixing purchase propensity pipelines names * adding extra condition for when there is not enough data for the window interval to be applied on backfill procedures * adding more instructions for post deployment and fixing issues when GA4 export was configured for less than 10 days * removing unnecessary comments * adding the number of past days to process in the variables files * adding comment about combining data from different ga4 export datasets to data store * fixing small issues with feature engineering and ml pipelines * fixing hyper parameter tuning for kmeans modeling * fixing optuna parameters * adding cloud shell image * fixing the list of all possible users in the propensity training preparation tables * additional guardrails for when there is not enough data * adding more documentation * adding more doc to feature store * add feature store documentation * adding ml pipelines docs * adding ml pipelines docs * adding more documentation * adding user agent client info * fixing scope of client info * fix * removing client_info from vertex components * fixing versioning of tf submodules * reconfiguring meta providers * fixing issue 187 * chore(deps): upgrade terraform providers and modules version * chore(deps): set the provider version * chore: formatting * fix: brand naming * fix: typo * fixing secrets issue * implementing secrets region as tf variable * implementing secrets region as tf variable * last changes requested by lgrangeau * documenting keys location better * implementing vpc peering network * Update README.md * Rebase Main into Multi-property (#243) * Update README.md * ensure the build bucket is created in the specified region (#230) * Update audience_segmentation_query_template.sqlx * Update auto_audience_segmentation_query_template.sqlx * Update churn_propensity_query_template.sqlx * Update cltv_query_template.sqlx * Update purchase_propensity_query_template.sqlx * Restrict regions for GCP Cloud Build support (#241) * Update README.md * Move to uv (#242) * add uv required project table segment in toml file * switch to uv in terraform deployment * switch to uv * remove poetry usage from terraform * format * remove poetry * Add files via upload --------- Co-authored-by: Charlie Wang <2144018+kingman@users.noreply.github.com> Co-authored-by: Mårten Lindblad * supporting property id in the resources * fixing iam member roles issues * fixing issue with service account iam resources * fixing issue with connection between vertex and bq --------- Co-authored-by: Carlos Timoteo Co-authored-by: Laurent Grangeau Co-authored-by: Charlie Wang <2144018+kingman@users.noreply.github.com> Co-authored-by: Mårten Lindblad --- .../modules/feature-store/bigquery-procedures.tf | 5 ++++- .../terraform/modules/feature-store/main.tf | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf b/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf index ff043b77..3bf2dd72 100644 --- a/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf +++ b/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf @@ -1487,6 +1487,7 @@ resource "null_resource" "check_gemini_model_exists" { triggers = { vertex_ai_connection_exists = google_bigquery_connection.vertex_ai_connection.id gemini_model_created = null_resource.create_gemini_model.id + role_propagated = time_sleep.wait_for_vertex_ai_connection_sa_role_propagation.id } provisioner "local-exec" { @@ -1509,7 +1510,9 @@ resource "null_resource" "check_gemini_model_exists" { depends_on = [ google_bigquery_connection.vertex_ai_connection, - null_resource.create_gemini_model + null_resource.create_gemini_model, + time_sleep.wait_for_vertex_ai_connection_sa_role_propagation, + google_project_iam_member.vertex_ai_connection_sa_roles ] } diff --git a/infrastructure/terraform/modules/feature-store/main.tf b/infrastructure/terraform/modules/feature-store/main.tf index 58aaf81a..296d812f 100644 --- a/infrastructure/terraform/modules/feature-store/main.tf +++ b/infrastructure/terraform/modules/feature-store/main.tf @@ -148,6 +148,17 @@ resource "google_project_iam_member" "vertex_ai_connection_sa_roles" { } } +# Propagation time for change of access policy typically takes 2 minutes +# according to https://cloud.google.com/iam/docs/access-change-propagation +# this wait make sure the policy changes are propagated before proceeding +# with the build +resource "time_sleep" "wait_for_vertex_ai_connection_sa_role_propagation" { + create_duration = "120s" + depends_on = [ + google_project_iam_member.vertex_ai_connection_sa_roles + ] +} + #module "vertex_ai_connection_sa_roles" { # source = "terraform-google-modules/iam/google//modules/member_iam" From f3d817902399a463ee43ad3ac834851743f2250b Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 22 Nov 2024 16:31:26 -0500 Subject: [PATCH 089/110] Update bigquery-procedures.tf --- .../terraform/modules/feature-store/bigquery-procedures.tf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf b/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf index 3bf2dd72..d4d90af2 100644 --- a/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf +++ b/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf @@ -1467,6 +1467,7 @@ resource "null_resource" "create_gemini_model" { vertex_ai_connection_exists = google_bigquery_connection.vertex_ai_connection.id, gemini_dataset_exists = module.gemini_insights.bigquery_dataset.id, check_gemini_dataset_listed = null_resource.check_gemini_insights_dataset_exists.id + role_propagated = time_sleep.wait_for_vertex_ai_connection_sa_role_propagation.id } provisioner "local-exec" { @@ -1478,7 +1479,8 @@ resource "null_resource" "create_gemini_model" { depends_on = [ google_bigquery_connection.vertex_ai_connection, module.gemini_insights.google_bigquery_dataset, - null_resource.check_gemini_insights_dataset_exists + null_resource.check_gemini_insights_dataset_exists, + time_sleep.wait_for_vertex_ai_connection_sa_role_propagation, ] } From 0311b7b539689748d33d0aa1c6dcfc6e766db4ab Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 22 Nov 2024 16:45:05 -0500 Subject: [PATCH 090/110] Update bigquery-procedures.tf --- .../terraform/modules/feature-store/bigquery-procedures.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf b/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf index d4d90af2..c6617b26 100644 --- a/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf +++ b/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf @@ -1472,6 +1472,7 @@ resource "null_resource" "create_gemini_model" { provisioner "local-exec" { command = <<-EOT + sleep 120 ${var.uv_run_alias} bq query --use_legacy_sql=false --max_rows=100 --maximum_bytes_billed=10000000 < ${data.local_file.create_gemini_model_file.filename} EOT } From 44bf251c5ae3a4ff54d72e62170a4e9df43f9b28 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 22 Nov 2024 17:05:08 -0500 Subject: [PATCH 091/110] Update bigquery-procedures.tf --- .../terraform/modules/feature-store/bigquery-procedures.tf | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf b/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf index c6617b26..1424656c 100644 --- a/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf +++ b/infrastructure/terraform/modules/feature-store/bigquery-procedures.tf @@ -1477,6 +1477,13 @@ resource "null_resource" "create_gemini_model" { EOT } + # The lifecycle block is used to configure the lifecycle of the table. In this case, the ignore_changes attribute is set to all, which means that Terraform will ignore + # any changes to the table and will not attempt to update the table. The prevent_destroy attribute is set to true, which means that Terraform will prevent the table from being destroyed. + lifecycle { + ignore_changes = all + prevent_destroy = true + } + depends_on = [ google_bigquery_connection.vertex_ai_connection, module.gemini_insights.google_bigquery_dataset, From 6b7db8a1630920491f77137289027a90fa444ea2 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 25 Nov 2024 14:05:19 -0500 Subject: [PATCH 092/110] Update README.md --- infrastructure/terraform/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index 5d49e845..a48312e2 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -154,7 +154,7 @@ Because a Cloud Shell session is ephemeral, your Cloud Shell session could termi Reset your Google Cloud Project ID variables: - ```shell + ```bash export PROJECT_ID="[your Google Cloud project id]" gcloud config set project $PROJECT_ID ``` From 8c21114185b1f21dc6553a64b7976d1883e52ab4 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 25 Nov 2024 14:06:08 -0500 Subject: [PATCH 093/110] Update README.md --- infrastructure/terraform/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index a48312e2..f3fd7e76 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -154,10 +154,10 @@ Because a Cloud Shell session is ephemeral, your Cloud Shell session could termi Reset your Google Cloud Project ID variables: - ```bash - export PROJECT_ID="[your Google Cloud project id]" - gcloud config set project $PROJECT_ID - ``` + ```bash + export PROJECT_ID="[your Google Cloud project id]" + gcloud config set project $PROJECT_ID + ``` Follow the authentication workflow, since your credentials expires daily: From 7b9fb8c830c70705dba3c4d4eece20b32f1e2b23 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 26 Nov 2024 11:15:30 -0500 Subject: [PATCH 094/110] Add files via upload --- notebooks/quick_installation.ipynb | 232 +++++++++++++++++++++++++---- 1 file changed, 207 insertions(+), 25 deletions(-) diff --git a/notebooks/quick_installation.ipynb b/notebooks/quick_installation.ipynb index a2d1be9f..ec248028 100644 --- a/notebooks/quick_installation.ipynb +++ b/notebooks/quick_installation.ipynb @@ -5,8 +5,7 @@ "colab": { "provenance": [], "collapsed_sections": [ - "DDGHqJNhq5Oi", - "mOISt4ShqIbc" + "US36yJ8lmqnP" ] }, "kernelspec": { @@ -67,7 +66,7 @@ "\n", "\n", "\n", - "Total Installation time is around **25-30 minutes**." + "Total Installation time is around **35-40 minutes**." ], "metadata": { "id": "mj-8n9jIyTn-" @@ -97,10 +96,22 @@ ], "metadata": { "id": "9TyPgnleJGGZ", - "cellView": "form" + "cellView": "form", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "0051c92e-932a-4067-941a-728c76623b28" }, "execution_count": null, - "outputs": [] + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Authenticated\n" + ] + } + ] }, { "cell_type": "markdown", @@ -128,22 +139,27 @@ "# @markdown ---\n", "# @markdown # Google Analytics 4\n", "# @markdown For a quick installation, copy the Google Analytics 4 property ID and stream ID. You will find it in your Google Analytics 4 console, under Admin settings.\n", - "MAJ_GA4_PROPERTY_ID = \"1234567890\" #@param {type:\"string\"}\n", - "MAJ_GA4_STREAM_ID = \"1234567890\" #@param {type:\"string\"}\n", + "GA4_PROPERTY_ID = \"1234567890\" #@param {type:\"string\"}\n", + "MAJ_GA4_PROPERTY_ID = GA4_PROPERTY_ID\n", + "GA4_STREAM_ID = \"1234567890\" #@param {type:\"string\"}\n", + "MAJ_GA4_STREAM_ID = GA4_STREAM_ID\n", "# @markdown The website your Google Analytics 4 events are coming from.\n", - "MAJ_WEBSITE_URL = \"https://shop.googlemerchandisestore.com\" #@param {type:\"string\", placeholder:\"Full web URL\"}\n", + "WEBSITE_URL = \"https://shop.googlemerchandisestore.com\" #@param {type:\"string\", placeholder:\"Full web URL\"}\n", + "MAJ_WEBSITE_URL = WEBSITE_URL\n", "# @markdown ---\n", "# @markdown # Google Ads\n", "# @markdown For a quick installation, copy the Google Ads Customer ID. You will find it in your Google Ads console. It must be in the following format: `\"CUSTOMERID\"` (without dashes).\n", - "MAJ_ADS_EXPORT_TABLE_SUFFIX = \"1234567890\" #@param {type:\"string\", placeholder:\"GAds Account Number (e.g. 4717384083)\"}\n", - "MAJ_ADS_EXPORT_TABLE_SUFFIX = \"_\"+MAJ_ADS_EXPORT_TABLE_SUFFIX\n", + "GOOGLE_ADS_CUSTOMER_ID= \"1234567890\" #@param {type:\"string\", placeholder:\"GAds Account Number (e.g. 4717384083)\"}\n", + "MAJ_ADS_EXPORT_TABLE_SUFFIX = \"_\"+GOOGLE_ADS_CUSTOMER_ID\n", "# @markdown ---\n", "# @markdown # Github\n", "# @markdown For a quick installation, use your email credentials that allows you to create a dataform repository connected to a remote Github repository, more info [here](https://cloud.google.com/dataform/docs/connect-repository).\n", - "MAJ_DATAFORM_REPO_OWNER_EMAIL = \"user@company.com\" #@param {type:\"string\", placeholder:\"user@company.com\"}\n", + "GITHUB_REPO_OWNER_EMAIL = \"user@company.com\" #@param {type:\"string\", placeholder:\"user@company.com\"}\n", + "MAJ_DATAFORM_REPO_OWNER_EMAIL = GITHUB_REPO_OWNER_EMAIL\n", "MAJ_DATAFORM_GITHUB_REPO_URL = \"https://github.com/GoogleCloudPlatform/marketing-analytics-jumpstart-dataform.git\"\n", "# @markdown For a quick installation, reuse or create your [GitHub personal access token](https://cloud.google.com/dataform/docs/connect-repository#connect-https)\n", - "MAJ_DATAFORM_GITHUB_TOKEN = \"Your github token\" #@param {type:\"string\"}\n", + "GITHUB_PERSONAL_TOKEN = \"your_github_personal_access_token\" #@param {type:\"string\"}\n", + "MAJ_DATAFORM_GITHUB_TOKEN = GITHUB_PERSONAL_TOKEN\n", "# @markdown ---\n", "\n", "import os\n", @@ -246,10 +262,22 @@ ], "metadata": { "id": "dMcepKg8IQWj", - "cellView": "form" + "cellView": "form", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "6abb8686-27d9-4a4d-bd25-bafd7ebc1a1c" }, "execution_count": null, - "outputs": [] + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "SUCCESS\n" + ] + } + ] }, { "cell_type": "markdown", @@ -258,7 +286,11 @@ "\n", "Click the ( ▶ ) button to create your Terraform application default credentials to the Google Cloud Project.\n", "\n", - "*To complete this step, you will be prompted to copy/paste a password from another window into the prompt below*\n", + "*To complete this step, you will be prompted to copy/paste a password from another window into the prompt below.*\n", + "\n", + "**Note:** *Click on the hidden input box after the colon, as shown below.*\n", + "\n", + "![image (14).png](data:image/png;base64,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BAgQIECAAAECBAgQIECAAAECBAgQIECAAIEmC7QJqposrjoCBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFMQFBlIBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECOQiIKjKhV2lBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECgipjgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAIBcBQVUu7ColQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAQVBkDBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECuQgIqnJhVykBAgQIECBAgAABAgQIECBAgAABAgQIECBAgICgyhggQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBDIRUBQlQu7SgkQIECAAAECBAgQIECAAAECBAgQIECAAAECBARVxgABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAuAoKqXNhVSoAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgIKgyBggQIECAAAECBAgQIECAAAECBAgQIECAAAECBHIREFTlwq5SAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABQZUxQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgkIuAoCoXdpUSIECAAAECBAgQIECAAAECBAgQIECAAAECBAgIqowBAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBXAQEVbmwq5QAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQEBQZQwQIECAAAECBAgQIECAAAECBAgQIECAAAECBAjkIiCoyoVdpQQIECBAgAABAgQIECBAgAABAgQIECBAgAABAoIqY4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQCAXAUFVLuwqJUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQEFQZAwQIECBAgAABAgQIECBAgAABAgQIECBAgAABArkICKpyYVcpAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQICAoMoYIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQyEVAUJULu0oJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQEVcYAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBALgKCqlzYVUqAAAECBAgQIECAAAEC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SN+dcezRbmnhq6++GrNnz66sT48GvOiiiyqvzZxc4Ntvv430XU5pqsenvk9eWznXCqrKeVw790pQdVxDUHXcwlx3gXR3brpLN02LFi2KsWPHdi/UAkvqHVS1ANkZ3cWihBFnNGJJG1+UsZFXUFXSw6pbvRAQVPUCS9GaBaovZAmqaqZTkAABAgQIFFag+vxeyKCq8ycbH3zwwZg1a1Y30KlTp1YeC5jKT5s2rVsZC3oWSF90n77jK03p0+8LFizouaClJxQQVJ2QpjQrBFXHD6Wg6riFue4CM2fOjCVLlmQrVqxYUbmrqHvJci8RVJX7+Fb3rihhRHW7vM5foChjQ1CV/1ho1RYIqlr1yDe239UXsgRVjfW2dwIECBAg0AyB6vN7M4Kq/wMAAP//kXopqgAANS9JREFU7V0J/KZT9b+zYCzFWMc+9mEIWWKSQkRIJI2pbCURiSZLSMpSDJWlQlFkaRGSkBgqUhJhNKSSZC+kYlR+//O9de7/vPe9z/M+y31/y/iez+d9n/vc/fne7dxz7jJqQMhVpH/+859urbXWCr6/9a1vuY033ji8q+E73/mOmz59un9dYYUV3I9//GN18s+vf/3rbvbs2W7UqFHumGOOcQ899JD7/ve/737605+6X//612611VZzr3vd63wciyyySEfYopfrr7/eh//Nb37jZs2a5f7xj3+4tdde26233npu9913d5MnTy4K6l544QV38cUXu7vuusun//DDD7tJkya59ddf3+28887JbyyMrKbDI4884s4666wQ6q9//au77rrr/PvSSy/t3vSmNwU3GBZccEF39NFHd9jpyw033OBuu+02fe14HnDAAW78+PEddvblJz/5ifvBD37grfbcc0+32GKLue9+97vuZz/7mbvpppsc8vLa177WHXTQQR4bG9aageXnP/959/LLL1trb37Na17jdtxxxy77lMVzzz3nvva1rznk6w9/+IP7y1/+4r99pZVWcuuuu67baqut3Oabb+7Gjh3bFXzixIneDvUI9eLGG290M2fOdDfffLND2W644YZuyy23dB/4wAeS4W2EbevGiy++6M4//3z385//3N1+++3u1a9+tXv961/vcVh22WXdm9/8Zp/crrvu6mbMmGGTHnZmdBVop7feequ755573L333uvQvlEeb3zjG9073vEO36ZTGW/b5lGnnnjiiRD19773Pd/GYbHLLru4+eabL7jBsOqqq7r3ve99HXb2pW25Iq5PfepTvu9YdNFF3cc+9jH36KOPuq9+9au+vqHOoq2us8467t3vfrfbbrvtetY1m78yM7BHf6X05z//OfSxaB+bbLKJOoXnfvvt57RdqOUee+wRwv3qV7/y+Dbth9vUDc1Pk6ftP3uVOeL/7Gc/65599lmfFMaeBRZYoCvZxx57zF166aW+jmM8Qt+DcQ5luffee7vllluuK0xs0bZuoB49/vjjcbTuVa96lfvwhz/cZW8ttK2pHfpvfBNo6623dosvvrg6+WfRGJerL0ciL730kvvmN7/px1eM/eg70D7WXHNN94Y3vMG9613vchMmTOjIF17Qf6LfRB+O8Rn9N8oD/Q7GdIzTu+22mx+f4sB///vf3YknnuiUxfnXv/7lwJuAkPbb3va2OIh7y1ve0jXmqqc246vG8Z///MfXrV/84hfu7rvv9mMbvgG8yhZbbOG233579dr11HLNyTd1JVLTomm5ajK//e1vPR7g2dCvgTbYYAOPx1577eWWXHJJ9Zp85hpfc7T5ZAYrWH7pS1/y9Xr06NHuuOOOc9dcc4374Q9/6Os8gm+22Wae1wEfCz9lBH4NYyNo6tSpDjwX+LqLLrrItz3wxRgjwDthfEQ7iqlJmeTuh3O0tVx144477vAYoo6Cv0bfgfkJ+GFgOP/888cQdrwj/GWXXeb7MWCL8XnTTTd1qN+f+MQn3JVXXun9o5+Ox+iOiFq8IF3M11K0ww47eB4u5aZ2mHOg78I8AONPG55a46zzRH95+eWX+yAYizFmFRH6++OPP95hvgr6+Mc/7sdN679NP4y29MADD3he9/DDD0+Wv85HkSZ4hqJyzdFe7XfVMf/73//2fDLCYPwBvhhjwN+jfFHWqKfgXcE39KK2PA/iRxvA/FfnFzrvxFwc/Oq8884bsoHx/YQTTvDv4CPgXpVOO+0099RTT/n40OdaAi5XXHGF70chmwDPgTa/zDLLuNVXX93P2TB/XHjhhW2wDnMOfhj1DHwS6IgjjvDzB/ClKCe1R7mh/U6bNs3PLTsy8b+Xr3zlK17Gom6oi+9///v1Nftzjb1v7Yhzzsxp/h1YkogAESACRIAIEIGRiUA8vt9//pS+f8goYagqK6ogYNp2221DpsCsW8ZRHTBpxeRaCRM8MHpKVkAKxhiKkRSBSQXDuNRSS6Wcvd3TTz/tlV2Y3JfR6aefnhRM4Zv2339/LzAqCn/IIYe4D33oQ9kEzTadO++8s3TSZf3CDByBZ4o+/elPe0F5yg0Tj5VXXjnl5O2+/OUvu8985jPeDGEumFtMblN0ySWX+AlMyg2KNkzgU4TJ5ec+97mUU4ddVUxQ5pigxKSTQiiq9t13X3fYYYfFXvw7lIDnnXdeofCnbd2AQBUKgl/+8pfJ9DHhR70EDXdFFcoVk3JMwIsIQt6TTz45OYFr2+YhWMOEsSpBGIRJXYralqvGqfUM/dTVV1/tJ/Mo8xRde+21pQreVJgiOyiTDjzwwCLnpH1qUUHbMtGE2tYNjafJE8oU2wfEY42N83e/+51XcMMOwloor2NCOaK/gEA3Reh/oTTtJbhpWzcQf6r/Lev/Nb+2XNWu7Amh3nve854uLzn6ckSKMvnIRz6S/B6bKBYlLL/88tbK999lfQ48A5Ozzz67g+eAPRR9KaUt3Iroox/9qF+MkXJvM74iPgjGDj30UL/4IhU/7KDsRzopBaot17Z8U1H6dezblCvSwUIY4FFEKNdzzjnHL+xI+ck1vuZq86k8VrHbaaedvBIJfsFrFvFIEEiecsopScG4poN6ceyxx/pXxPPkk0+6k046SZ07nhBAQ0lmqWmZ5O6H27a1HHUDAuszzzzT9/cWI2sGj/nFL37RL66z9mq2i/bUTp8QvkPYDeE8CE8dN7xFxj/0oeCFUwR+HwsFykjz1ZanLkujzO33v/+9X1wGP1hEh4VfqUVqcMeChre//e0wJsf5nP0wFvhgoVJMwFTbVtl8qW17jdOt824VVVgsscoqq3g+NhUHxm/MV8oU5VpHmvDDWESi885U+rDDgrgzzjgjKNdt/pEmlH5VyPZV+G7w5krgeTAGY6FZGUEph/E4Rbn4YcwdddEqFllCoYZ5fIrK8oN5N+bUSlioqQt21C7nMxZkUVGVE13GRQSIABEgAkRgaBCIx/dhp6i65ZZb/O4AwKO7VYqgUqYV7thRZVduWoGLhodACSukoeRS5gxu2AlUpGiAMBECbCscxrvunsLuKp0EQoCOldeWYsUbJkCYvGPF+f333+9Xtqr/o446qnCip36aPP/0pz/5ybCGxWp//X7kR3fcqPtCCy3kFXP6bp8QMmAlqhJWfyo2dRRVGh5ljHIBY28ZW0wYdBWo+tUnVjGivFT/+be//S0I5qooqjBhQZqab9QbCGhQJphgPvjgg0FZAgGTlrWmj6ete2oPJQp2QmBiaZl2TBSxGyimHHXDThAgdEMeoHRFudg8IO3hrKhC+WMFoe7KQH6xi2nFFVd02M2DclDBPvxB+RdT2zaPFZB2RxWUNZom8jLPPPN0JIm6m1o1mKNcNSGtZ2inqIe6cxRKEOzugZAQO3JAORVVWG36jW98Q7Phd3JByA9C2qldrlDGa341YNsyQTw56obmp+kT/Y2uFocgo2jXJpTCqEcghMHYYgkCpSOPPDJYYbUqdj9COINdPVbhjAUU2K1bRIp107oBoY1VzKKNgaooqrATFWOfEuqltl0owGLBGgQzqTrTti9H+lj5i/5bCfnHGL3GGmu4Z555xiuxFNfUGIU2/KMf/cgHx7gAYRp2omLHLYQ3aM9KUEzDjxJWXEPgrYRV9DqOIR82X+oHfTT6sBS1GV8xHqJe6kpoxI/0MSZhTNNvhD0UF1/4whdg7KAc7bUjwhYvbcsVZb3PPvuEHEBgiF0q2ImtO5/VEUJ29Ocx5Rhfc7b5OH9V362iSsOgjWAHJ1aha9uHGxQK6BuKyAq+MS6izoJQ3xHfuHHj/G4F8FexoqptmeTqh5HfNm0N4XPUjYMPPriDz0W/AAzB04LP1v4ZfTzmGdjtaglj8nvf+95gBb55ypQpni8AP6D8i3pAHDpuqF2uJ3gGLEZTwqIN7TtRn6oqqjQ8nnV5ahu2iRnzN+ysAhXx7XCDohbuIOxYs/1M7n44p6Kqbnv1H9jizyp6bDQYl8DbY46C3VVKZcoQ+NG624TnwSJJWz+xyxqnt2DOh/mwthUopDA+6AJZlC36LRDqRq8duPCHHY7geUDYEYmd9UpYkKr9LfpMnecgffTF2OkK8yc/+Um/I1LD6TMnP2wVVcgvdmWCgC/6EvBnaNfIT1nZ2L4Q4amoAgokIkAEiAARIAJEoA4CQ6GogkKhMolyYkAYWP8Txqk0nDCZwa8wvB1+ZfIW3BCfbGXvcJejS4I74hEBU4e7vojyKfiTXTwDIvBSp/CUyeQA0hNBZrBTg0zOQnhZMTYggi118k9ZtRfcV5R8ygrtDvd+vMhKrpCmrGBrlYSszg1xycS0NC5Z/Rf84ltlgjcgSqMQBuFhr7/77rsvuJUZbDhg3Itkch/SQB2bM2dOVxDZRTcgq1gHRPja5QYLzSOeqBciVAv+UJdE6Rj8yPF/wc0a2tYNUeqGNFCHRXAcohdh3IBMLII78ikr+YP7cDOI0DTkVYQTAzJh68iiKKsGRKEa/IjQtcMdL7navEb8zne+M6QXt1v1k3q2LVcbJ8rN/uTYrgFZ+Wu9DIhybUCOnhn44x//2GGf80WECSEf6BOrUo4yyVE3qua3yJ/tM0TwUORtQIQfAae4DovSZMCOWXL0Xse4gzYLOy1vOTZuQAQ9hWmpP322rRvoxxAX8liX0LdoPmSXVt3gwX/dvhx9LfpwTRttNtVnY5xF2cRtBwmjnxQFY3LsRfyyayTEL0cshbymDBhLNC8ov7ZUZ3wVwXZIG2Uoq/47khcBWHBHHmW3eoc7XnK0165IG1i0LVe0JbQHfCd+ouDo4DXQn9tvTfFBOcbX3G2+AZQ+COqiYoHnBRdc0BGVKJo73OWI2Q53+yJK6g6/iE92G3b1ZbIbfUB2hoagOcokRz8cMhQZ6rS1HHVDFhMFHNFe5VjyjhyJkHgA9RL44idHjHa448X2feA50W6U0L61T9c4wPsPFskxrD7fSFsU/D2T1Tzi2ZSn7plIDw92/ikKhaRvURR0jOOYK1jK3Q+LwtdGH8yieAn4gj8roqbttSi+OvaY39lyhRljsSU7L0S5y1Gv1rnDHMdVleeRBV0d+YjrI/gOWYAS/Mix1yFdzANXlHzjh7KtQpaPE6VUCPL888+HuNDmMa+JCfVLFrsMoI9JUU5+GHNTfJf+kCdRZncki/yIgm8A/XkRye7CEAfi6iW7KYqnqv3qe90yYH+a/6rh6Y8IEAEiQASIABEYfgjYsR3mwaBaiirL4JUJBJFxKxSMBddWCCGr2ru+E5NmOZYtMFeyqqrLj+xECu5ghGLBjw2A+GIG2wqGwFCnFCKIw36zndjb+HOaMVnF9+CXEtDUSavO5D6ekKTwgPJK81bGGNs81hVuYhKiachKTxtVZbOGx/PCCy/sCgeFo/pBPYspR92Q4yxDGnJHVZyEF8xZQcVwVVRZrIBZkcJF7lsJ3wuBdEw52ryNs4miKke52jxoHcITZQnh1VBQDkVVk344V91oixkELrYtydErXVHKytNQPzFxj0l23gR3WZEdO4d32eET/MlRM8E+NqBO6C9H3dDvg7CiLg2Vouqqq64KGCDfZQrleHyu+o0oe8StWJcpD4dSUWWF1hBOpgj1Tr9D7rzr8pK7D+1KoKJF23K1Cg2UXYrXiPuWWLGcY3zN3eYrwtflzSqqsJAiRZb3KluMEAu+ZedtKrouuxxlkqMf7srY/yzq8LI56gYW5GhblDu+ktlCf6b9Mvza8R+L8zR8UR23yiL4Be8/WGTTjhUDqTzot+DZhKdOxVnXDvjavj41zkPxoHlN8TS5++Hciqqq7bUudin/saLq1FNPTXkbsG0Bi0iLSHHHsw7PI7vcQ5kVLRxEG9T4obRSsotJsWjFEmQEWPyCxWKW5NjsEJdV+mMRj6bRZCFLPGY1nStpXmNFlV3sqH6qPKmoqoIS/RABIkAEiAARIAJlCAx7RdWMGTMCI9drd4zdYREzt1bgMnPmzCQmiF+ZxtTuHTn6K7j3UpqlEpCt+yG8bKlPefF2WHWr+WiSTmHEBQ7DQVGF1YApwoRWsZCjNVJeuuzqKqrkSJSQBiYkTQSYmkc85ZiWrjzBwk54Yw856oaNP6VoRZp2N8BwVVRBIal4poSnFjs7oY0FxjnavE2riaIqR7naPCgueFYR9tiwOc05FFVN+uFcdSMHFnLPUqinqd2zdoVzShhkF0YUCRiQTyy60HKXe/wKs65+8MxRN1Qgin6lLg2Vouroo4+uhFXd74n9276gSHCIMEOlqMJCGVsfUgJW5A87f9Uf+tKYcvehcfxV39uW67nnnhu+s4jXQF6soA6LDCzlGF9zt3mbvzpmq6iSI++SQeWIsYCZ3CeX9ANLq6iqwz/lKBOk37YfRhwpqqOoals3wK9pO0S/W0bYSaV+5Zjf4PWiiy4K9tgVmiLwphoWz5GiqGrCU6e+v4md3NMTMEspzLCrVjHFTkRL/eiHi8Yby29U3VFVp73a72pqjhVVmKulCHM9xbSsv1Y/eNbheWyZFe2KwjzQtmsdQ6G81HRtv/jiiy8G+3gsVV7KKrzw3dh9t6LkXX+pHd4pfNQuNz9sx79e8hbNQ+qJHVdypHX4YYdtPykWZCme/UyTcRMBIkAEiAARIAL9RSAe3/ub2n9jr7Wjym6zL1r5pJm2iqp4RaIVuMhdUBqk42lXu8bHEcCj3EUSGMrUjpWOyBIvNg/KSPV69pq0JpKpbTUcFFWYZKfI7kgB/lWorqJK7igL5YryAOZY6YdJJ5SGVUjLsUyoa5UqmLBZals35Mzw8A1ldcZObIarosq2ecW1yjNeAW8xbdrmbRlZ4XTZTg0bxuahyjfAT1n52Tjsykyb5mCYcyiqmpRJrrqRAyMICbU8dt99944oceQSylHd4+OAYoGN+uv1LGuzNmyOuqH5L+vTOj7avAyVogrloDjY41dN1ioZcbwNdtpiNxv6bSuw0vj1WYb1UCmqrOAbO82LKK6HsT/bfzVpr3F8Td/blqtdMBAvYrJ5wpGPWq52YUyO8TXGWtPp9Sxr8zbvdcxWUZValIW4rFKjrA5ZRRWOnK1KbctE02nTD2scqWdVRVWOuoEjxHvVg5Q7jgRTsov6Ugsn1J9Vlo4ERVXZ+FPGU+v3tn1iV4lij5MwLNkdLRgvY76+H/1wTkVVnfZqv7upOe4DochLEXaOK+ZFRy4inPrBs2wcjtOwbaAsnD222/aTdseQLpCzPDHyg7oBsnUk1Zfbk2AQDkd2YlFs6hjA+Dty88NWUVU2Tsb5GOr3WJCl9WKo88X0iQARIAJEgAgQgeYIxON785iqh6ylqPr2t789oExHLAyMk1TBGvzHiiYrcImFhhpPrxVpduIar7bVOMqeNn/6TVWeZXHmcBsOiirsakqRPd+96jGIdRVVSNceQxiXCRSgEGCVKSc0DPwWkb0jIz56qG3dsN8cT6ZtfuTy3wHNa2rSZP0OlRm7qDSPdZ44CtBSjjZv42uiqGpbrjZ9mC0esVAk9tvPdzspLzsWKs5D2zLJVTfifDV5h5DFChlUMIG4br/99lBWqXuMsIPKlmVVc9kOWxtHjrqhdbdMUFiE21ApqjTPwEJXQBflscgeddvGY3FNmcsEXUOlqOp1LKr9dquEe/bZZ61Tx71NTfmmjggbvtjyaFKuaDdadijfIrrkkkuCP3sHUI7xtR9tvug7etlbRZXtt+Jwihme9r4j688qqiA4rUpty0TTadMPaxypp+X3Uf5FlKNu4IQFi3VVM44JV7InQhTtEoFfq/QdCYqqpjy14pLjaduLPQ4NO5y1rFLzk370wzkVVXXaaw4craIq3l1k48eiCMUV2BeR+sGzKs8T73KL52I2LXsnnL1OwMoKdAEHjo23+dGdqnZHPMaXmKxSzoaHGeMejmAtaqe5+WGrqErdvx3nfbi8x4IsxXG45I/5IAJEgAgQASJABOojEI/v9WOoH2IUgriKdOONN7p99tnH+1577bWdrDQqDDlx4sTgJhM1t8oqq4T3PfbYw8kOGf8uR5q4RRddNLipQRhNJwoL/yoMpdt0003VyT/33Xdfd/3113uzHOXkNttssw73Xi+TJ092svrSexPhrltyySV7BXFjx46tnU7PSCMPshPFyQozb7vTTjs5uZw18lH99dBDD3XCoPsAKLuVV165MLAcD+CE4ffu55xzjttmm226/NryF8GBO/jgg7v8xBZyfILbcsstvfXOO+/s5Li72EvyXS6vdiJ0cTKxSLqvtNJKTlawusUWW6zLXeveaqutFupI7GmHHXZwssrfW8vF1m7eeecNXtrWjdmzZ7ttt93Wx1fWTmT1sUMZg3bddVcnq3C9eTj9yQpKJ+f++yyJMMuJMqBS9jbaaCO34IILBr852nyITAy77babE0Wft5o1a1ZHWtafNbctVxsXzFrPUAfvuOOO2HnQ3mVy7UTg5dNDecmEuVLabcskV92olNkKns4888zQhuS4JYfvA8luAd+XwCw7Qd2OO+4IYyDbXlFnzzrrrOBWZlhiiSUc6lSKcteNDTbYwIlQzNdz1Pc6NH36dN9XIgz601VXXbVO8OC3bl+uGCACESi6+eefP8RVxfDwww+7zTffPHhdeuml3dSpU90KK6zgxo8f78aMGePdTjjhBIcyBGHcWG655bw5/pPjg9zqq6/urdddd1135ZVXxl5qvVcdX0VR6kSx7uPG91xwwQWF6YDPeeyxx7z7bbfd5iZMmBD8tm2vIaKWhrblOm3aNCcKKp8LUQo41O0UXX755Q58BkiOdXJyrJw32/badHy1ceRq8z5zDf7AA4AXAMndRr5up6KxuCP/48aN6/ImO8+c3HXm7eXuF7fLLrt0+UlZtC0TG2fTftjGEZurtjVbrk3rhhwp54455hifhSlTpjgRGMfZSb6D31x22WW9mx0bzzvvvMADxwExnwJfDbrpppucLePYb8532eXlDjvsMB8l5lqyW6U0es1XU566NPKajjbvmAfKrhcfw9Zbb+3kniFvljvX3PLLL98R83Dsh5u2144Pa/giu48CL4D5lBz/nIwJmAJb0KRJk5wsZEz60zpShx+2eUCk4DFGjx6djP/www93cread0MfgzkcCDyN7Lb2Zjl9w8k9ZH7+hb5g/fXX932qzqUx/9Q59XXXXefWWGMNH87+IQ+i9PT8gcoIrDvMmHtuuOGGHda2zbeZK2mk++23n0MeQZCXgO8ZCbTG3v8d2zWvc2ZO80bINkhEgAgQASJABIjAyEQgHt/vP39K/z+kjm7rrrvuGtDVMVj5W3RUAFYCqz8841W3diV/0xVpJ510Ukij7GiNou+zq/LsMQJF/gfLHqu1FDusIGtDVVehIg27i0mY42Syg7WjyiaOM8hxPwPyZ48WAUa4KyNFil/T1Z9t6wbqu+ah7Jge3Auk/obrjiq7MtEev5TCvcwuR5u38TfZUdW2XG36MGvZla1GjcP04z3Hjqom/XCuupELExEwhDLBcTAgrOy1u1RSOzFhp2WJZw7S+HLVDd3FMpJ2VNldqyibumTv8MOOj6JV2ujnFW9coF5EQ7Wjyu7eKRuT4tXl8ffm7kOLcOpl37Zc7crzst3wsmAmlKvdbZBjfO1Hm++FW5G7HZeKdgvZO1fK+gC7o0p3ERSla+3blomNq2k/bOOIzVV52Rx1QxRGod71OuI8zqe+26PLy47tsm2paKeGxpnzKQL/8I1V7hPS/rWs/7LfUrYzpu13PP/88yHvaAu4v8julrJ3Fdm0BrMfxjF+ilnZrtGm7dV+V1MzxhfNY1mfYu/HwzhcRBpXXZ5HeRuEL+JDkaa9y8ruMLJHOmJOaI/307zj+/C9uoMR73pMYNH3wD92aOHYYZs28onjCuNdrbn5YbujqoyvKcr/UNnHK661XgxVfpguESACRIAIEAEi0B6BeHxvH2PvGGod/ffkk08OKNOBp6xYSqYgO52CPzCEMUOXQ+Bij4XBpLAuyQrNkEdMTIcLUVFVXBKoR7gYdkWpe/gVKYHUvemkum3d6CVw1C+0F25XUVThnhccY6a/IqGWxp/jaY/7xESsKeVo8zZtq6iC4KIKtS3XOA2tZ3Un5nE8bd+toqrssus4nbZlkqtuxPlq826FvrIjZ0BWVvu+AmUFYWcRWWFJSplVFK7IPnfd0PyVCZSK8jJUR//Zo3rKFBJF+bZt/MEHH0x6s8I2YF4m0BkqRZVNt0xAZvmrVJ/Str0mAWxg2bZccSyXtg8Iaovo2GOPDf5k91vwlmt81TaFvORo8yGDNQ22z5IdgcnQ6MsUM/gvoqaC77ZlEufHflOdfjiOR9+rKqpy1A3Lg5dhrXlLPXEMoJYXeNYUgZ9Ff6D+qKhKoZS2O/LIIwNuGFuOO+648I47hVKUqx8+4IADQlpFZbbnnnsGPyNBUbWi9IG4By9FuGMa7vihTy4i9ZMau4rCwN6O82V3WUI5pGnEd0apGxYzyk5c7w9lAEJ+EM7ePVemcPOBEn+Wz0Z88THDufnhXIoq3BuIO571Fx/LnvjUVlaxIAtY4UciAkSACBABIkAERi4C8fg+GF9SS1GFDGG1mjIeRUIGezEzzDHlELjY+32Qn3jXVpxm/G4VHk1XTcZx5ni3k2TuqOpGNBZEdPsYCPWzqaIqR92wu7+wcypF9nLgKooqewcP6rwcd5GKNqudrkhEehDsYYdbE8rR5m26+HbkCT85UsI6FZpzlKuNXNOvOzG3ceQwY3KvealSjzTNtmWSq25ofnI8setPsTj77LMH5Hij8F62IMEKltrsHNRv0DzkqhsqVB9Jiio5nidgL8fxKDSVn3aFftH4jt0KijWeZYoqJKx+gWdbqio8Rzpafki/qB7KkYAhf6ldAW3ba9vv1fBty9XeAVSkCMAuCSvEx+IMSznG19xt3uavjtkqdbATI0VW8VHGFzZVVOUoE5vvpv2wjcOa67S1tnUjVn6XCc9tHq0ZyhPta9CPpQh396ofPIuUHqmwbe1G8o4qfLvlPdBXal+BJ3YfFlGOfhj3FGm54U6jmOS4uJAf+Bspiio5zi7+FP9+4IEHhu+197DFnhWTujyP3c1ZtBgOd5Fp/HiijVqyiksooeBH+TiVR1jFD3jDJqRxI36cMGPJ1knUs6ZzJY3T5rcXX6NhUk+cLoD86q8JL5aKt8guFmRpukX+aU8EiAARIAJEgAgMfwTi8X0wclxbUXXVVVcFhgeTgpiBkjP2gzsYlHvuuafrO3IIXKCwsCuxcHlx0QQFF6hiUmgJxxPq5Ab5hJ8iwsQDxwvG31rkv439K11RhWMYy45ixC4+lBd+RRMidW+qqMpRN6zQEcdNQPBmKVa0VlEwDIWiCnm2ymns2Il3SOp34SgNKOVSE/McbV7TwVPuIPJ1AGUtd9RZp0JzjnK1kWs9K6qH1m8/zTguRfOCCfILL7xQKbkcZZKjblTKbEVPdlcK2ov28XjGwg0bpW2P8ItjgooIR8uce+65pUfHaHnkqhsqYEPe6tJQ7ahCvVT8gYcKjuL8o1zkToiBp59+usPJ7twB3xHTE0884XfVKtZ49hqjMSaofwi/2lAd4bncixHSxQKFmFeBIs7273YHkeYxR3vVuNo825Zr3A+nFnJYRQdWy8dtN8f4mrvNN8XUKqpQN+OFF8DL1g2sjC+ipoqqHGVi89S0H7ZxWHOdtpajblhlLPi3sh13UGTFQn60b+2zUabY2WsJ9dkqSuGHiiqLUG+z7cuBH37gC8soRz+MY0g1PSyEicm6w1+KH9YwTdurhm/zRB3U78ATCt64nsv9VB1+0K6LSOOqy/PIXYwhDfAL8W4p5FPufwp+oJSKySraNR963DDGF7XTpz06UOPCuIZFJEVHAkIOgG/TOOJ8Ip6c/DAVVVoyfBIBIkAEiAARIAJDjcBQKKpG4aPr3IQlQlB/iaheMooLPmWVkb+UVZRS/rJ6dSu6zDjXpeD24mR8A9Lbf//9wwWpcjSaEwGXk6Mg3Mknn+x22223jk+1l/LCARdKv/Wtb3XCiDpZDeVEWOlEweXkDHeHb8JTGNWOOHK/4MJREcz4aPUC2CppCFPuZBLR4VWOc3G4kB10yimndF0uLBNpN++883p32W3iRAnhzXI3hNtmm2282f7h0mdc/gwSwYE7+OCDrbPDZfWyqqzDTgSHTlbMebuNN97Y4VJsS0suuaRbeeWVg5XcReVkVZ3DZfdTp051q6++ultmmWXcI4884mT1opMjH8Nl8yhrXLAbk17q2+bi57Z1A/Vno4028vUG+dtyyy0dLsddfPHFnUySuvK96667uhkzZsSf0vG++eabOzlGJ9jhgmMRmIf3fhnk2C0nQoEQPcoRl1ij3MaMGeNQxiKscSIg8mWTqhu52rxm4uqrr3a4vFgJlymj/S+00ELeChc64yLlmNqWq41P69nSSy/t66Z1G0wzunDULxF0+WSBAy5HX2qppcLF1KiLCy+8cEe2cpRJjrrRkakML3vttZcTgUNHTOi3ZCV0h138gr5J7nUJ1uhbUNfR/6BvxXggK+X9eAJPuGR8nnnmCf6toU3dkJW6TgSdNjrfF6oFxiFL8803X7Kuq5/p06f7y7/xjovHV111VXUqfOboyxG5HG8aLrvHO8a0nXfe2fcdctSQx1BWafv+A+OLHQtEAe3kzgkEcwsuuKATRY2/1H38+PFOjrNxxx9/vBPhknfXPzk6zS233HL62vU85phj3IUXXujt0Ueg3wAPAwxBwEbLzlv876/t+Prcc8+5KVOmhPEAfAT6r+WXX95jcOKJJ4b2i4vt5fhkN3bsWJsFl6O9dkTY4qVNuSLZ008/3Z122mkhB2ibwEeEkr6NnXHGGcENfkWZE95hyDW+5mzzHRms8YI2gfqshHopx1m7Nddc04/34N0wvoImTZrkrrnmGjdq1Cj13vEUBZ879thjvZ3c8ebbWoeHkpe2ZRJH3bQfbtvWctQN9L/ggZXfwhgvCn9fJuh/wI9i7MN4AX4O4wR4C0vgzU444YRgJffqep5QlNKeb0Mbt4QxK9X3WD9NzOgjMVZZQtrod0GYv4GXtIR+ELyqkuarDU+tceV6gt+Mx3TwhZMnTy5MIkc/HM87P/jBDzpR8vi+69prr3XnnXdeR/qYs2y66aYddvrSpr1qHE2fopDp4gU23HBDP6/DGIo+54gjjghjFsZf9EtFpHWkCT+MuZ7OVdH/icLRidLKyWIUd/755zu5NzkkK0rfrrkseN8tttgi+MGYLgp9/w6ZBfpSSyjDcePGWSt3xx13ONlt5JB/fOt6663neQNR3jlZ1ODkSEEnC3F9GMxPZTFJR3i85OSHMV/U7+7F13RlxFiA39J8wxplLIp14yOvMb5sfc7MaT4ByDZIRIAIEAEiQASIwMhEIB7f7z9/St8/pLaiCjmSC0a9UicWEtncYkINgdASSyxhrb05p8AFE8VY+dGVoFikFFWyM8QrB6AcqULDWVFlhZFVvgV+7MQ4h6LKKtmq5gETg1NPPTV4V0VVsCgwYMIMZjsWvsO7TpjaTKpz1A0IA6DQKSJMiB577DHvXEVRtcEGG3QIZuVIGXfWWWcVRZ/VHgpfO2kti3wwFFWyo9JPKu0EzOYJguBYoA/3HOWq6Wg9azIx1zhyPdGWIRgsIgjRIEyzlKsfbls3bJ5ymK+44gonO2w7ooKQIaW4tJ4gQEQ/CoVJFeqXoipu573yAsEOhCxFZMeGqoqqHH058gNhGAS0KhQtyiPsY0UVwsqxNUFInwqLOj169Ogg5Ool0IHgC0pdXUwTxwlh9EEHHRRb+3pRV7hjx1dEiAUzcoxSV9zWAgo5CObitgo/udqrTa+puU25Ik25V9CPjSqcLMrHjjvu6KBwiZV28J9jfM3Z5ou+oZe9VVSBZ4mVChoedQMKQghQi6iN4DtHmdh8Ne2HbX9l4yszx20tR92YNWuWb6+6AKQs/ZSiCn0MlBgQrBcRylT7ovgbisLUtUe/BUzrEBZNgSdVUl6nDU+tceV6PvPMMx1jOuacUBT1orb9MOK3fXGcHsZj8J9QmoFGiqIK+S6a06Pc0fdgYWERaR1pwg9DcbT33nuH+VBRGnIXmZOdiF3OWKyFBVraljDvOuqoo4I/LFRSvq5IUaOKqhCoxFCmEM3FD1NRVVIAdCICRIAIEAEiQAQGFYERo6gCKthFAaEOVhrFtN122/ndOSklAvxaphGrxxdZZJE4Cq9YkiMUvH1KyGoDyBZ8h5XSyohaN5jB2GLCCAY6RRB2I7yuWo39gLHdYYcd/OrUom+KwzR9B65yzIsPvssuu3SsOi6LE6v/5dz5Mi9dblhxhpVnIDl6ya9QhxlCxa222grGDrr55pvDJAET31joZvPeEbDkBbvcoERUwmQBeUHedNKhbvrESnSshMfK1hTphKls4mqFQ1gFlxKEIe62dQPh5fLlrgkY0seOsG233dZ/AnbAYCdZEaUEx1AEa10pCpfTXo488+0EQqAUYScABIuot1oG6i93m0e8EJbecMMNXrCL3Zy2vgAX3TmhebDPtuWKuLByF2na1Zs2jcE2Y5U3hAkQEKoCVPMAYRX6MUs5y6RN3bB5ymHGClgILZTQ78vxO4W7ENQfnhB4yDEyXrlSJLTB6mmMcWjDRTsb2tQNrL6Oy8/mMTb3UlTZsSFWBsVx6XuOvlzjwhN8AgRHKSE88o/+HIrW+eef3wbzgjMoulJKIijqMXbI8UtBKIhdt0XjvEYMgTwUl+gf4vwgLvTXMVkMY7eidzu+qh8I5KBExTMm7CjCoo2i/Odsr3HaTd+blivSQ/+NnVNyHFdX8hDgo75gl3sZ5Rhfc7X5snyWuVleBIo7OfKyi68GL4PFRPG4GseLOg1eFgRsMR7XoRxlouk17YdztbUcdQO7MbDTDLt3LH+h34h6CmU6yjClXMYOQQjXsTvUEvo8lA/6Nd3Fm9opYsM0NaOvw+KhOoTdjuDjlLTe5eCpNc4cT6swwi4cvFehNv0w4sduYMx/Y14YfAdOskCZVpm/tm2vVb61yA/auu6uxiIenKgBBY/uItRw4HcwLulpBWofP9vwPIhLjh91cqyf3zEaxw0eG3Uy5mGtP8wLVTmI9rbZZpsF54svvtjJ/X/+Hf70lI/gQQxYxIKyw675It4P7RwLWRQ3G96ac/DD9nuq8DU2fWvGHNvKaVJKdeu/rTkWZHFHVVtEGZ4IEAEiQASIwNAjEI/vw3ZHlYVqzpw5XugCIekqq6ziGbgiob8N1w8zGG8w2TjyD8KHZZdd1kF4vsACC1RKDpNSKARw9AiOBZgwYYKPI6VIqxQhPTVGAGWJlaxPPfWUnzRAIIAjuDBhqVqejRNPBGxbNyB4hiIU34EV0XW/IV4V2+/JRgKCYKXtDO0Eghi0E/yKBKwh4DA0tC3XYfhJQ5qlualuQHCCseTRRx/1iylwlCKOaouVKUMK+AhLHMdyoV/HOIu+EEcMQQjai2fA0YsIgyeERPj1CjOcocERVFgggXEBYxq+p+6YMJy+r2m54huwyxV8G/DAUZpYvY+xvkgJnPrutuOrxjkUbd4qqiBAB++JfGARF2idddZxUGwMJuUok8HMb1laOeoG5hMQPmM8wE4e7CzBeID+q0o/ZOdJEOir4qcs33QrRwCKQ/DBqkDEseOLLrpoeaDItU0/jDqBuvXAAw847PDHTuh+L2SMst+XV3wTjiIFlqirGKcHk3DsJtoZxnscP4nxoG65ts0vFuqgvWP+iePNMbdBW6+bj7mJH66KaSzIoqKqKnL0RwSIABEgAkRg+CIQj+8jQlE1fOFkzojA3INAfJdGr12Gc8+X80uIABEgAkSACMydCKQUVXPnl/KriEA+BHCkHnbggNCGUjsz86XGmIgAEaiCQCzIoqKqCmr0QwSIABEgAkRgeCMQj+9UVA3v8mLuiMCgIICVmzjyQo+j6HWs3aBkiokQASJABIgAESACrRCgoqoVfAz8CkQAOzBx/K7upuLCrVdgJeAnD0sEYkEWFVXDspiYKSJABIgAESACtRCIx3cqqmrBR89EYO5EAMdg2DvDcOcAzpQnEQEiQASIABEgAiMXASqqRm7ZMeeDh8Ddd9/t734CP4z7vJSwiCt1h6G680kEiMDgIRALsqioGjzsmRIRIAJEgAgQgX4hEI/vVFT1C2nGSwRGEAI4qx2XLuPODtyLgAuPSUSACBABIkAEiMDIRoCKqpFdfsz94CCABVqHHHJIR2KTJk1yF198ce27gzoi4QsRIALZEFjr/T+TeycHQnxzbt7LuZdf8neOBUsaiAARIAJEgAgQgRGDwIsvvezW3e+2kN8xY0a5+76yaXjvl2GUHCv2/xxFv1JhvESACBABIkAEiAARIAJEgAgEBKZPn+5mz57txowZ4y677DI3duzY4EYDESAC/0XgpptucjNmzPAvEydOdJtssonbfvvt3fjx4wkRESACwwSBtxx5p3vo8RdCbv51z6nu5afvoKIqIEIDESACRIAIEIGRhcANd/7VHXD67JDpiRPmd9ed1P/TvaioCpDTQASIABEgAkSACBABIkAEiAARIAJEgAgQASJQFYFjL/i9u3Tm48H75IkLuYuPXNuNm3d0sKOBCBABIkAEiAARGBkIYDfVtJPudbMe+nvI8NQtJrjj9lg5vPfLQEVVv5BlvESACBABIkAEiAARIAJEgAgQASJABIgAEZiLEbjzwefd1BPu6fhCKKs+9Lbl3OsnL0KFVQcyfCECRIAIEAEiMDwRgILqllnPurO+90iHkgq5vfSoddz6q76q7xmnoqrvEDMBIkAEiAARIAJEgAgQASJABIgAESACRIAIzJ0IxLuq5s6v5FcRASJABIgAEXjlITBYu6mALBVVr7z6xS8mAkSACBABIkAEiAARIAJEgAgQASJABIhANgTed+p97qf3PpstPkZEBIgAESACRIAIDC0Cm629iPvqR9catExQUTVoUDMhIkAEiAARIAJEgAgQASJABIgAESACRIAIzJ0IcGfV3Fmu/CoiQASIABF45SEwmDupFF0qqhQJPokAESACRIAIEAEiQASIABEgAkSACBABIkAEKiPw+OOPuwkTJgT/uLPqilufcrf95jn3p6dedP/5z0Bwo4EIEAEiQASIABEYngiMGTPKLb/EOLfJmgu7t09ZouNOqnis79cXUFHVL2QZLxEgAkSACBABIkAEiAARIAJEgAgQASJABIgAESACRIAIEAEiQASIQCkCVFSVwkNHIkAEiAARIAJEgAgQASJABIgAESACRIAIEAEiQASIABEgAkSACBCBfiFARVW/kGW8RIAIEAEiQASIABEgAkSACBABIkAEiAARIAJEgAgQASJABIgAESACpQhQUVUKDx2JABEgAkSACBABIkAEiAARIAJEgAgQASJABIgAESACRIAIEAEiQAT6hQAVVf1ClvESASJABIgAESACRIAIEAEiQASIABEgAkSACBABIkAEiAARIAJEgAiUIkBFVSk8dCQCRIAIEAEiQASIABEgAkSACBABIkAEiAARIAJEgAgQASJABIgAEegXAlRU9QtZxksEiAARIAJEgAgQASJABIgAESACRIAIEAEiQASIABEgAkSACBABIlCKABVVpfDQkQgQASJABIgAESACRIAIEAEiQASIABEgAkSACBABIkAEiAARIAJEoF8IUFHVL2QZLxEgAkSACBABIkAEiAARIAJEgAgQASJABIgAESACRIAIEAEiQASIQCkCVFSVwkNHIkAEiAARIAJEgAgQASJABIgAESACRIAIEAEiQASIABEgAkSACBCBfiFARVW/kGW8RIAIEAEiQASIABEgAkSACBABIkAEiAARIAJEgAgQASJABIgAESACpQhQUVUKDx2JABEgAkSACBABIkAEiAARIAJEgAgQASJABIgAESACRIAIEAEiQAT6hQAVVf1ClvESASJABIgAESACRIAIEAEiQASIABEgAkSACBABIkAEiAARIAJEgAiUIkBFVSk8dCQCRIAIEAEiQASIABEgAkSACBABIkAEiAARIAJEgAgQASJABIgAEegXAlRU9QtZxksEiAARIAJEgAgQASJABIgAESACRIAIEAEiQASIABEgAkSACBABIlCKwP8Bg3oJxuygNj8AAAAASUVORK5CYII=)\n", "\n", "***Time: 2 minute.***" ], @@ -280,22 +312,34 @@ ], "metadata": { "id": "3cAwp6CRLSVf", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "39d3a2a0-811c-4506-d197-af7a7e086d74", "cellView": "form" }, "execution_count": null, - "outputs": [] + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "SUCCESS\n" + ] + } + ] }, { "cell_type": "markdown", "source": [ - "### 4. Run Installation\n", + "### 4. Prepare environment for Installation\n", "\n", - "Click the ( ▶ ) button to run the installation end-to-end.\n", + "Click the ( ▶ ) button to prepare the environment for an end-to-end installation.\n", "\n", - "***Time: 15 minutes.***" + "***Time: 5 minutes.***" ], "metadata": { - "id": "US36yJ8lmqnP" + "id": "WYG5sjFEqX2X" } }, { @@ -304,10 +348,19 @@ "# @title\n", "%%capture\n", "%%bash\n", - "# prompt: install uv\n", - "curl -LsSf https://astral.sh/uv/install.sh | sh\n", + "# prompt: install packages\n", + "apt-get install python3.10\n", + "CLOUDSDK_PYTHON=python3.10\n", + "\n", + "#pip3 install poetry\n", + "sudo apt update\n", + "sudo apt install pipx\n", + "pipx ensurepath\n", + "pipx install poetry\n", + "\n", "export PATH=\"/root/.local/bin:$PATH\"\n", - "uv --version\n", + "poetry env use python3.10\n", + "poetry --version\n", "\n", "git clone --depth=1 https://github.com/tfutils/tfenv.git ~/.tfenv\n", "echo 'export PATH=\"~/.tfenv/bin:$PATH\"' >> ~/.bash_profile\n", @@ -335,6 +388,20 @@ "execution_count": null, "outputs": [] }, + { + "cell_type": "markdown", + "source": [ + "### 5. Run Installation\n", + "\n", + "Click the ( ▶ ) button to run the installation end-to-end.\n", + "After clicking the button, expand this section to observe that all cells have successfully executed without issues.\n", + "\n", + "***Time: 25-30 minutes.***" + ], + "metadata": { + "id": "US36yJ8lmqnP" + } + }, { "cell_type": "code", "source": [ @@ -357,13 +424,14 @@ "cell_type": "code", "source": [ "# @title\n", + "%%capture\n", "%%bash\n", "export PATH=\"$PATH:~/.tfenv/bin\"\n", "export PATH=\"/root/.local/bin:$PATH\"\n", "export PATH=\"$PATH:$(which gcloud)\"\n", "export GOOGLE_APPLICATION_CREDENTIALS=/content/.config/application_default_credentials.json\n", "TERRAFORM_RUN_DIR=$(pwd)/infrastructure/terraform\n", - "terraform -chdir=\"${TERRAFORM_RUN_DIR}\" apply -auto-approve" + "terraform -chdir=\"${TERRAFORM_RUN_DIR}\" apply -target=module.data_store -auto-approve" ], "metadata": { "id": "BGteib5ebsA-", @@ -372,6 +440,108 @@ "execution_count": null, "outputs": [] }, + { + "cell_type": "code", + "source": [ + "# @title\n", + "%%capture\n", + "%%bash\n", + "export PATH=\"$PATH:~/.tfenv/bin\"\n", + "export PATH=\"/root/.local/bin:$PATH\"\n", + "export PATH=\"$PATH:$(which gcloud)\"\n", + "export GOOGLE_APPLICATION_CREDENTIALS=/content/.config/application_default_credentials.json\n", + "TERRAFORM_RUN_DIR=$(pwd)/infrastructure/terraform\n", + "terraform -chdir=\"${TERRAFORM_RUN_DIR}\" apply -target=module.feature_store -auto-approve" + ], + "metadata": { + "cellView": "form", + "id": "dwD5DRRM2Ryl" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# @title\n", + "%%capture\n", + "%%bash\n", + "export PATH=\"$PATH:~/.tfenv/bin\"\n", + "export PATH=\"/root/.local/bin:$PATH\"\n", + "export PATH=\"$PATH:$(which gcloud)\"\n", + "export GOOGLE_APPLICATION_CREDENTIALS=/content/.config/application_default_credentials.json\n", + "TERRAFORM_RUN_DIR=$(pwd)/infrastructure/terraform\n", + "terraform -chdir=\"${TERRAFORM_RUN_DIR}\" apply -target=module.pipelines -auto-approve" + ], + "metadata": { + "cellView": "form", + "id": "KrEr1yXS1_oA" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# @title\n", + "%%capture\n", + "%%bash\n", + "export PATH=\"$PATH:~/.tfenv/bin\"\n", + "export PATH=\"/root/.local/bin:$PATH\"\n", + "export PATH=\"$PATH:$(which gcloud)\"\n", + "export GOOGLE_APPLICATION_CREDENTIALS=/content/.config/application_default_credentials.json\n", + "TERRAFORM_RUN_DIR=$(pwd)/infrastructure/terraform\n", + "terraform -chdir=\"${TERRAFORM_RUN_DIR}\" apply -target=module.activation -auto-approve" + ], + "metadata": { + "collapsed": true, + "cellView": "form", + "id": "7-Qr46vR2bLl" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# @title\n", + "%%capture\n", + "%%bash\n", + "export PATH=\"$PATH:~/.tfenv/bin\"\n", + "export PATH=\"/root/.local/bin:$PATH\"\n", + "export PATH=\"$PATH:$(which gcloud)\"\n", + "export GOOGLE_APPLICATION_CREDENTIALS=/content/.config/application_default_credentials.json\n", + "TERRAFORM_RUN_DIR=$(pwd)/infrastructure/terraform\n", + "terraform -chdir=\"${TERRAFORM_RUN_DIR}\" apply -target=module.monitoring -auto-approve" + ], + "metadata": { + "collapsed": true, + "cellView": "form", + "id": "ElOBpEV3Mtbc" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# @title\n", + "%%capture\n", + "%%bash\n", + "export PATH=\"$PATH:~/.tfenv/bin\"\n", + "export PATH=\"/root/.local/bin:$PATH\"\n", + "export PATH=\"$PATH:$(which gcloud)\"\n", + "export GOOGLE_APPLICATION_CREDENTIALS=/content/.config/application_default_credentials.json\n", + "TERRAFORM_RUN_DIR=$(pwd)/infrastructure/terraform\n", + "terraform -chdir=\"${TERRAFORM_RUN_DIR}\" apply -auto-approve" + ], + "metadata": { + "cellView": "form", + "id": "eyZNdewu2zQI" + }, + "execution_count": null, + "outputs": [] + }, { "cell_type": "code", "source": [ @@ -380,10 +550,22 @@ ], "metadata": { "id": "1h7k6jFYpLPO", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "266267e5-ac12-4621-ec7f-19e051027edb", "cellView": "form" }, "execution_count": null, - "outputs": [] + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "SUCCESS!\n" + ] + } + ] } ] } \ No newline at end of file From 92b585a3c7bd718f7f622c79db23ed5ee5181d00 Mon Sep 17 00:00:00 2001 From: Charlie Wang <2144018+kingman@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:16:41 +0100 Subject: [PATCH 095/110] specify the columns in the backfill procedures, makes sure the scripts are executable with a previous created feature table (#254) --- ...nvoke_backfill_churn_propensity_label.sqlx | 8 ++- ...ackfill_customer_lifetime_value_label.sqlx | 9 ++- ...ke_backfill_purchase_propensity_label.sqlx | 21 ++++++- .../invoke_backfill_user_dimensions.sqlx | 26 +++++++- ...oke_backfill_user_lifetime_dimensions.sqlx | 26 +++++++- ...invoke_backfill_user_lookback_metrics.sqlx | 20 ++++++- ..._user_rolling_window_lifetime_metrics.sqlx | 45 +++++++++++++- ..._backfill_user_rolling_window_metrics.sqlx | 60 ++++++++++++++++++- ...backfill_user_scoped_lifetime_metrics.sqlx | 30 +++++++++- .../invoke_backfill_user_scoped_metrics.sqlx | 30 +++++++++- ...fill_user_scoped_segmentation_metrics.sqlx | 30 +++++++++- ...backfill_user_segmentation_dimensions.sqlx | 26 +++++++- ...user_session_event_aggregated_metrics.sqlx | 40 ++++++++++++- 13 files changed, 358 insertions(+), 13 deletions(-) diff --git a/sql/query/invoke_backfill_churn_propensity_label.sqlx b/sql/query/invoke_backfill_churn_propensity_label.sqlx index 4cbe77ac..9dd41da7 100644 --- a/sql/query/invoke_backfill_churn_propensity_label.sqlx +++ b/sql/query/invoke_backfill_churn_propensity_label.sqlx @@ -119,7 +119,13 @@ GROUP BY ); -- Insert data into the target table, combining user information with churn and bounce status -INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` +INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` ( + processed_timestamp, + feature_date, + user_pseudo_id, + churned, + bounced +) SELECT DISTINCT -- Current timestamp as the processing timestamp CURRENT_TIMESTAMP() AS processed_timestamp, diff --git a/sql/query/invoke_backfill_customer_lifetime_value_label.sqlx b/sql/query/invoke_backfill_customer_lifetime_value_label.sqlx index 27ea59d0..569e5db5 100644 --- a/sql/query/invoke_backfill_customer_lifetime_value_label.sqlx +++ b/sql/query/invoke_backfill_customer_lifetime_value_label.sqlx @@ -109,7 +109,14 @@ CREATE OR REPLACE TEMP TABLE future_revenue_per_user AS ( ); -- Insert data into the target table -INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` +INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` ( + processed_timestamp, + feature_date, + user_pseudo_id, + pltv_revenue_30_days, + pltv_revenue_90_days, + pltv_revenue_180_days +) SELECT DISTINCT -- Current timestamp of the processing CURRENT_TIMESTAMP() AS processed_timestamp, diff --git a/sql/query/invoke_backfill_purchase_propensity_label.sqlx b/sql/query/invoke_backfill_purchase_propensity_label.sqlx index a2c8bee0..b062dc58 100644 --- a/sql/query/invoke_backfill_purchase_propensity_label.sqlx +++ b/sql/query/invoke_backfill_purchase_propensity_label.sqlx @@ -125,7 +125,26 @@ CREATE OR REPLACE TEMP TABLE future_purchases_per_user AS ( ); -- Inserts data into the target table -INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` +INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` ( + processed_timestamp, + feature_date, + user_pseudo_id, + purchase_day_1, + purchase_day_2, + purchase_day_3, + purchase_day_4, + purchase_day_5, + purchase_day_6, + purchase_day_7, + purchase_day_8, + purchase_day_9, + purchase_day_10, + purchase_day_11, + purchase_day_12, + purchase_day_13, + purchase_day_14, + purchase_day_15_30 +) SELECT DISTINCT -- Selects the current timestamp and assigns it to the column processed_timestamp CURRENT_TIMESTAMP() AS processed_timestamp, diff --git a/sql/query/invoke_backfill_user_dimensions.sqlx b/sql/query/invoke_backfill_user_dimensions.sqlx index c27dd299..6c81b412 100644 --- a/sql/query/invoke_backfill_user_dimensions.sqlx +++ b/sql/query/invoke_backfill_user_dimensions.sqlx @@ -122,7 +122,31 @@ CREATE OR REPLACE TEMP TABLE events_users as ( ; -- Inserting aggregated user data into the target table. -INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` +INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` ( + processed_timestamp, + feature_date, + user_pseudo_id, + user_id, + user_ltv_revenue, + device_category, + device_mobile_brand_name, + device_mobile_model_name, + device_os, + device_language, + device_web_browser, + geo_sub_continent, + geo_country, + geo_region, + geo_city, + geo_metro, + last_traffic_source_medium, + last_traffic_source_name, + last_traffic_source_source, + first_traffic_source_medium, + first_traffic_source_name, + first_traffic_source_source, + has_signed_in_with_user_id +) SELECT DISTINCT -- Timestamp of the data processing CURRENT_TIMESTAMP() AS processed_timestamp, diff --git a/sql/query/invoke_backfill_user_lifetime_dimensions.sqlx b/sql/query/invoke_backfill_user_lifetime_dimensions.sqlx index 4001878f..b05611e0 100644 --- a/sql/query/invoke_backfill_user_lifetime_dimensions.sqlx +++ b/sql/query/invoke_backfill_user_lifetime_dimensions.sqlx @@ -137,7 +137,31 @@ CREATE OR REPLACE TEMP TABLE events_users as ( -- This code block inserts data into the specified table, combining information from the "events_users" table -- and the "user_dimensions_event_session_scoped" table. -- It aggregates user-level features for each user and date. -INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` +INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` ( + processed_timestamp, + feature_date, + user_pseudo_id, + user_id, + user_ltv_revenue, + device_category, + device_mobile_brand_name, + device_mobile_model_name, + device_os, + device_language, + device_web_browser, + geo_sub_continent, + geo_country, + geo_region, + geo_city, + geo_metro, + last_traffic_source_medium, + last_traffic_source_name, + last_traffic_source_source, + first_traffic_source_medium, + first_traffic_source_name, + first_traffic_source_source, + has_signed_in_with_user_id +) SELECT DISTINCT -- The current timestamp. CURRENT_TIMESTAMP() AS processed_timestamp, diff --git a/sql/query/invoke_backfill_user_lookback_metrics.sqlx b/sql/query/invoke_backfill_user_lookback_metrics.sqlx index 25e3566b..37bd4563 100644 --- a/sql/query/invoke_backfill_user_lookback_metrics.sqlx +++ b/sql/query/invoke_backfill_user_lookback_metrics.sqlx @@ -230,7 +230,25 @@ AND D.device_os IS NOT NULL -- This code is part of a larger process for building a machine learning model that predicts -- user behavior based on their past activity. The features generated by this code can be used -- as input to the model, helping it learn patterns and make predictions. -INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` +INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` ( + processed_timestamp, + feature_date, + user_pseudo_id, + active_users_past_1_7_day, + active_users_past_8_14_day, + purchases_past_1_7_day, + purchases_past_8_14_day, + visits_past_1_7_day, + visits_past_8_14_day, + view_items_past_1_7_day, + view_items_past_8_14_day, + add_to_carts_past_1_7_day, + add_to_carts_past_8_14_day, + checkouts_past_1_7_day, + checkouts_past_8_14_day, + ltv_revenue_past_1_7_day, + ltv_revenue_past_7_15_day +) SELECT DISTINCT -- Timestamp indicating when the data was processed CURRENT_TIMESTAMP() AS processed_timestamp, diff --git a/sql/query/invoke_backfill_user_rolling_window_lifetime_metrics.sqlx b/sql/query/invoke_backfill_user_rolling_window_lifetime_metrics.sqlx index 2ee219f1..b4a0a415 100644 --- a/sql/query/invoke_backfill_user_rolling_window_lifetime_metrics.sqlx +++ b/sql/query/invoke_backfill_user_rolling_window_lifetime_metrics.sqlx @@ -283,7 +283,50 @@ AND D.device_os IS NOT NULL -- This code is part of a larger process for building a machine learning model that predicts -- user behavior based on their past activity. The features generated by this code can be used -- as input to the model, helping it learn patterns and make predictions. -INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` +INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` ( + processed_timestamp, + feature_date, + user_pseudo_id, + active_users_past_1_30_day, + active_users_past_30_60_day, + active_users_past_60_90_day, + active_users_past_90_120_day, + active_users_past_120_150_day, + active_users_past_150_180_day, + purchases_past_1_30_day, + purchases_past_30_60_day, + purchases_past_60_90_day, + purchases_past_90_120_day, + purchases_past_120_150_day, + purchases_past_150_180_day, + visits_past_1_30_day, + visits_past_30_60_day, + visits_past_60_90_day, + visits_past_90_120_day, + visits_past_120_150_day, + visits_past_150_180_day, + view_items_past_1_30_day, + view_items_past_30_60_day, + view_items_past_60_90_day, + view_items_past_90_120_day, + view_items_past_120_150_day, + view_items_past_150_180_day, + add_to_carts_past_1_30_day, + add_to_carts_past_30_60_day, + add_to_carts_past_60_90_day, + add_to_carts_past_90_120_day, + add_to_carts_past_120_150_day, + add_to_carts_past_150_180_day, + checkouts_past_1_30_day, + checkouts_past_30_60_day, + checkouts_past_60_90_day, + checkouts_past_90_120_day, + checkouts_past_120_150_day, + checkouts_past_150_180_day, + ltv_revenue_past_1_30_day, + ltv_revenue_past_30_90_day, + ltv_revenue_past_90_180_day +) SELECT DISTINCT -- This selects the current timestamp and assigns it to the column processed_timestamp. CURRENT_TIMESTAMP() AS processed_timestamp, diff --git a/sql/query/invoke_backfill_user_rolling_window_metrics.sqlx b/sql/query/invoke_backfill_user_rolling_window_metrics.sqlx index 9317225a..be0a0860 100644 --- a/sql/query/invoke_backfill_user_rolling_window_metrics.sqlx +++ b/sql/query/invoke_backfill_user_rolling_window_metrics.sqlx @@ -272,7 +272,65 @@ CREATE OR REPLACE TEMP TABLE events_users as ( -- table and several temporary tables containing rolling window features. The resulting data -- represents user-level features for each user and date, capturing their past activity within -- different time windows. -INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` +INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` ( + processed_timestamp, + feature_date, + user_pseudo_id, + active_users_past_1_day, + active_users_past_2_day, + active_users_past_3_day, + active_users_past_4_day, + active_users_past_5_day, + active_users_past_6_day, + active_users_past_7_day, + active_users_past_8_14_day, + active_users_past_15_30_day, + purchases_past_1_day, + purchases_past_2_day, + purchases_past_3_day, + purchases_past_4_day, + purchases_past_5_day, + purchases_past_6_day, + purchases_past_7_day, + purchases_past_8_14_day, + purchases_past_15_30_day, + visits_past_1_day, + visits_past_2_day, + visits_past_3_day, + visits_past_4_day, + visits_past_5_day, + visits_past_6_day, + visits_past_7_day, + visits_past_8_14_day, + visits_past_15_30_day, + view_items_past_1_day, + view_items_past_2_day, + view_items_past_3_day, + view_items_past_4_day, + view_items_past_5_day, + view_items_past_6_day, + view_items_past_7_day, + view_items_past_8_14_day, + view_items_past_15_30_day, + add_to_carts_past_1_day, + add_to_carts_past_2_day, + add_to_carts_past_3_day, + add_to_carts_past_4_day, + add_to_carts_past_5_day, + add_to_carts_past_6_day, + add_to_carts_past_7_day, + add_to_carts_past_8_14_day, + add_to_carts_past_15_30_day, + checkouts_past_1_day, + checkouts_past_2_day, + checkouts_past_3_day, + checkouts_past_4_day, + checkouts_past_5_day, + checkouts_past_6_day, + checkouts_past_7_day, + checkouts_past_8_14_day, + checkouts_past_15_30_day +) SELECT DISTINCT -- This selects the current timestamp and assigns it to the column processed_timestamp. CURRENT_TIMESTAMP() AS processed_timestamp, diff --git a/sql/query/invoke_backfill_user_scoped_lifetime_metrics.sqlx b/sql/query/invoke_backfill_user_scoped_lifetime_metrics.sqlx index bfb93869..ed4bf30e 100644 --- a/sql/query/invoke_backfill_user_scoped_lifetime_metrics.sqlx +++ b/sql/query/invoke_backfill_user_scoped_lifetime_metrics.sqlx @@ -163,7 +163,35 @@ CREATE OR REPLACE TEMP TABLE first_purchasers as ( ); -- This SQL code calculates various user engagement and revenue metrics at a daily level and inserts the results into a target table. It leverages several temporary tables created earlier in the script to aggregate data efficiently. -INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` +INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` ( + processed_timestamp, + feature_date, + lifetime_purchasers_users, + lifetime_average_daily_purchasers, + lifetime_active_users, + lifetime_DAU, + lifetime_MAU, + lifetime_WAU, + lifetime_dau_per_mau, + lifetime_dau_per_wau, + lifetime_wau_per_mau, + lifetime_users_engagement_duration_seconds, + lifetime_average_engagement_time, + lifetime_average_engagement_time_per_session, + lifetime_average_sessions_per_user, + lifetime_ARPPU, + lifetime_ARPU, + lifetime_average_daily_revenue, + lifetime_max_daily_revenue, + lifetime_min_daily_revenue, + lifetime_new_users, + lifetime_returning_users, + lifetime_first_time_purchasers, + lifetime_first_time_purchaser_conversion, + lifetime_first_time_purchasers_per_new_user, + lifetime_avg_user_conversion_rate, + lifetime_avg_session_conversion_rate +) SELECT -- Records the current timestamp when the query is executed. CURRENT_TIMESTAMP() AS processed_timestamp, diff --git a/sql/query/invoke_backfill_user_scoped_metrics.sqlx b/sql/query/invoke_backfill_user_scoped_metrics.sqlx index 3cc45b49..c5252519 100644 --- a/sql/query/invoke_backfill_user_scoped_metrics.sqlx +++ b/sql/query/invoke_backfill_user_scoped_metrics.sqlx @@ -183,7 +183,35 @@ CREATE OR REPLACE TEMP TABLE new_users_ as ( ); -- Insert data into the target table after calculating various user engagement and revenue metrics. -INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` +INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` ( + processed_timestamp, + feature_date, + purchasers_users, + average_daily_purchasers, + active_users, + DAU, + MAU, + WAU, + dau_per_mau, + dau_per_wau, + wau_per_mau, + users_engagement_duration_seconds, + average_engagement_time, + average_engagement_time_per_session, + average_sessions_per_user, + ARPPU, + ARPU, + average_daily_revenue, + max_daily_revenue, + min_daily_revenue, + new_users, + returning_users, + first_time_purchasers, + first_time_purchaser_conversion, + first_time_purchasers_per_new_user, + avg_user_conversion_rate, + avg_session_conversion_rate +) SELECT DISTINCT -- Record the current timestamp when the query is executed. CURRENT_TIMESTAMP() AS processed_timestamp, diff --git a/sql/query/invoke_backfill_user_scoped_segmentation_metrics.sqlx b/sql/query/invoke_backfill_user_scoped_segmentation_metrics.sqlx index c6f03aaa..251dfead 100644 --- a/sql/query/invoke_backfill_user_scoped_segmentation_metrics.sqlx +++ b/sql/query/invoke_backfill_user_scoped_segmentation_metrics.sqlx @@ -136,7 +136,35 @@ GROUP BY feature_date ); -- This SQL code calculates various user engagement and revenue metrics at a daily level and inserts the results into a target table. It leverages several temporary tables created earlier in the script to aggregate data efficiently. -INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` +INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` ( + processed_timestamp, + feature_date, + purchasers_users, + average_daily_purchasers, + active_users, + DAU, + MAU, + WAU, + dau_per_mau, + dau_per_wau, + wau_per_mau, + users_engagement_duration_seconds, + average_engagement_time, + average_engagement_time_per_session, + average_sessions_per_user, + ARPPU, + ARPU, + average_daily_revenue, + max_daily_revenue, + min_daily_revenue, + new_users, + returning_users, + first_time_purchasers, + first_time_purchaser_conversion, + first_time_purchasers_per_new_user, + avg_user_conversion_rate, + avg_session_conversion_rate +) SELECT -- Records the current timestamp when the query is executed. CURRENT_TIMESTAMP() AS processed_timestamp, diff --git a/sql/query/invoke_backfill_user_segmentation_dimensions.sqlx b/sql/query/invoke_backfill_user_segmentation_dimensions.sqlx index be402415..cf2dc7ff 100644 --- a/sql/query/invoke_backfill_user_segmentation_dimensions.sqlx +++ b/sql/query/invoke_backfill_user_segmentation_dimensions.sqlx @@ -95,7 +95,31 @@ CREATE OR REPLACE TEMP TABLE events_users as ( -- This code snippet performs a complex aggregation and insertion operation. It combines data from two temporary tables, -- calculates various user-level dimensions, and inserts the aggregated results into a target table. The use of window functions, -- approximate aggregation, and careful joining ensures that the query is efficient and produces meaningful insights from the data. -INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` +INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` ( + processed_timestamp, + feature_date, + user_pseudo_id, + user_id, + user_ltv_revenue, + device_category, + device_mobile_brand_name, + device_mobile_model_name, + device_os, + device_language, + device_web_browser, + geo_sub_continent, + geo_country, + geo_region, + geo_city, + geo_metro, + last_traffic_source_medium, + last_traffic_source_name, + last_traffic_source_source, + first_traffic_source_medium, + first_traffic_source_name, + first_traffic_source_source, + has_signed_in_with_user_id +) -- The DISTINCT keyword ensures that only unique rows are inserted, eliminating any potential duplicates. SELECT DISTINCT CURRENT_TIMESTAMP() AS processed_timestamp, diff --git a/sql/query/invoke_backfill_user_session_event_aggregated_metrics.sqlx b/sql/query/invoke_backfill_user_session_event_aggregated_metrics.sqlx index 7ba0e2f7..4c6f3373 100644 --- a/sql/query/invoke_backfill_user_session_event_aggregated_metrics.sqlx +++ b/sql/query/invoke_backfill_user_session_event_aggregated_metrics.sqlx @@ -354,7 +354,45 @@ CREATE OR REPLACE TEMP TABLE events_users_days as ( -- user_events_per_day_event_scoped (UEPDES): Contains user-level event metrics aggregated on a daily basis. Metrics include add_to_carts, cart_to_view_rate, checkouts, ecommerce_purchases, etc. -- repeated_purchase (R): Stores information about whether a user has made previous purchases, indicated by the how_many_purchased_before column. -- cart_to_purchase (CP): Contains a flag (has_abandoned_cart) indicating whether a user abandoned their cart on a given day. -INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` +INSERT INTO `{{project_id}}.{{dataset}}.{{insert_table}}` ( + processed_timestamp, + feature_date, + user_pseudo_id, + engagement_rate, + engaged_sessions_per_user, + session_conversion_rate, + bounces, + bounce_rate_per_user, + sessions_per_user, + avg_views_per_session, + sum_engagement_time_seconds, + avg_engagement_time_seconds, + new_visits, + returning_visits, + add_to_carts, + cart_to_view_rate, + checkouts, + ecommerce_purchases, + ecommerce_quantity, + ecommerce_revenue, + item_revenue, + item_quantity, + item_refund_amount, + item_view_events, + items_clicked_in_promotion, + items_clicked_in_list, + items_checked_out, + items_added_to_cart, + item_list_click_events, + item_list_view_events, + purchase_revenue, + purchase_to_view_rate, + refunds, + transactions_per_purchaser, + user_conversion_rate, + how_many_purchased_before, + has_abandoned_cart +) SELECT CURRENT_TIMESTAMP() AS processed_timestamp, EUD.feature_date, From e775f69c2cba0a45d0a6e09240b3b9ef6260329e Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 26 Nov 2024 11:41:19 -0500 Subject: [PATCH 096/110] Add files via upload --- notebooks/quick_installation.ipynb | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/notebooks/quick_installation.ipynb b/notebooks/quick_installation.ipynb index ec248028..6f7e54db 100644 --- a/notebooks/quick_installation.ipynb +++ b/notebooks/quick_installation.ipynb @@ -3,10 +3,7 @@ "nbformat_minor": 0, "metadata": { "colab": { - "provenance": [], - "collapsed_sections": [ - "US36yJ8lmqnP" - ] + "provenance": [] }, "kernelspec": { "name": "python3", @@ -352,15 +349,11 @@ "apt-get install python3.10\n", "CLOUDSDK_PYTHON=python3.10\n", "\n", - "#pip3 install poetry\n", - "sudo apt update\n", - "sudo apt install pipx\n", - "pipx ensurepath\n", - "pipx install poetry\n", + "#prompt: install uv\n", + "curl -LsSf https://astral.sh/uv/install.sh | sh\n", "\n", "export PATH=\"/root/.local/bin:$PATH\"\n", - "poetry env use python3.10\n", - "poetry --version\n", + "uv --version\n", "\n", "git clone --depth=1 https://github.com/tfutils/tfenv.git ~/.tfenv\n", "echo 'export PATH=\"~/.tfenv/bin:$PATH\"' >> ~/.bash_profile\n", From d528d17a2ee0e706fec5bb9006ae294c590e5b6b Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 26 Nov 2024 14:43:31 -0500 Subject: [PATCH 097/110] Update README.md --- infrastructure/terraform/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index f3fd7e76..4d7bb889 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -93,6 +93,12 @@ Also, this method allows you to extend this solution and develop it to satisfy y on darwin_amd64 ``` + If you have a Apple Silicon Macbook, you should install terraform by setting the `TFENV_ARCH` environment variable: + ```shell + TFENV_ARCH=amd64 tfenv install 1.9.7 + tfenv use 1.9.7 + ``` + 1. Run the following script to create a Terraform remote backend. Terraform stores state about managed infrastructure to map real-world resources to the configuration, keep track of metadata, and improve performance. Terraform stores this state in a local file by default, but you can also use a Terraform remote backend to store state remotely. [Remote state](https://developer.hashicorp.com/terraform/cdktf/concepts/remote-backends) makes it easier for teams to work together because all members have access to the latest state data in the remote store. From d707e789679d2e4405abdebbdb041f31cc6f221f Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Tue, 26 Nov 2024 14:46:03 -0500 Subject: [PATCH 098/110] Update README.md --- infrastructure/terraform/README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index 4d7bb889..dc2836b0 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -87,18 +87,20 @@ Also, this method allows you to extend this solution and develop it to satisfy y terraform --version ``` + **Note:** If you have a Apple Silicon Macbook, you should install terraform by setting the `TFENV_ARCH` environment variable: + ```shell + TFENV_ARCH=amd64 tfenv install 1.9.7 + tfenv use 1.9.7 + terraform --version + ``` + If not properly terraform version for your architecture is installed, `terraform .. init` will fail. + For instance, the output on MacOS should be like: ```shell Terraform v1.9.7 on darwin_amd64 ``` - If you have a Apple Silicon Macbook, you should install terraform by setting the `TFENV_ARCH` environment variable: - ```shell - TFENV_ARCH=amd64 tfenv install 1.9.7 - tfenv use 1.9.7 - ``` - 1. Run the following script to create a Terraform remote backend. Terraform stores state about managed infrastructure to map real-world resources to the configuration, keep track of metadata, and improve performance. Terraform stores this state in a local file by default, but you can also use a Terraform remote backend to store state remotely. [Remote state](https://developer.hashicorp.com/terraform/cdktf/concepts/remote-backends) makes it easier for teams to work together because all members have access to the latest state data in the remote store. From 2b24996ec456998e6c06f1dcb24b2c5456490d91 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 4 Dec 2024 12:37:36 -0500 Subject: [PATCH 099/110] Update invoke_churn_propensity_training_preparation.sqlx --- .../invoke_churn_propensity_training_preparation.sqlx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sql/query/invoke_churn_propensity_training_preparation.sqlx b/sql/query/invoke_churn_propensity_training_preparation.sqlx index 632fb03b..1c57ebbb 100644 --- a/sql/query/invoke_churn_propensity_training_preparation.sqlx +++ b/sql/query/invoke_churn_propensity_training_preparation.sqlx @@ -57,14 +57,14 @@ SET churners = (SELECT COUNT(DISTINCT user_pseudo_id) ); -- Setting Training Dates --- If there are churners in the training set, then keep the user-defined dates, or else set --- the start and end dates instead. +-- If there are churners in the training set, then keep the calculated dates, or else set +-- the start and end dates to a fixed interval preventing `train_start_date` and `train_end_date` from being NULL. IF churners > 0 THEN - SET train_start_date = GREATEST(train_start_date, min_date); - SET train_end_date = LEAST(train_end_date, max_date); -ELSE SET train_start_date = min_date; SET train_end_date = max_date; +ELSE + SET train_start_date = DATE '2000-01-01'; + SET train_end_date = DATE '2024-12-01'; END IF; -- Finally, the script calls a stored procedure, passing the adjusted training dates and split numbers as arguments. From ea3be2ee7eb3d0eaf714f03305705345af0f64cd Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 4 Dec 2024 12:38:56 -0500 Subject: [PATCH 100/110] Update invoke_purchase_propensity_training_preparation.sqlx --- ..._purchase_propensity_training_preparation.sqlx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/sql/query/invoke_purchase_propensity_training_preparation.sqlx b/sql/query/invoke_purchase_propensity_training_preparation.sqlx index 4d2eab86..6fafc30e 100644 --- a/sql/query/invoke_purchase_propensity_training_preparation.sqlx +++ b/sql/query/invoke_purchase_propensity_training_preparation.sqlx @@ -54,17 +54,18 @@ SET validation_split_end_number = {{validation_split_end_number}}; SET purchasers = (SELECT COUNT(DISTINCT user_pseudo_id) FROM `{{mds_project_id}}.{{mds_dataset}}.event` WHERE event_name = 'purchase' AND - event_date BETWEEN train_start_date AND train_end_date + event_date BETWEEN min_date AND max_date ); --- If there are purchasers no changes to the train_start_date and train_end_date --- Else, expand the interval, hopefully a purchaser will be in the interval -IF purchasers > 0 THEN - SET train_start_date = GREATEST(train_start_date, min_date); - SET train_end_date = LEAST(train_end_date, max_date); -ELSE +-- Setting Training Dates +-- If there are churners in the training set, then keep the calculated dates, or else set +-- the start and end dates to a fixed interval preventing `train_start_date` and `train_end_date` from being NULL. +IF churners > 0 THEN SET train_start_date = min_date; SET train_end_date = max_date; +ELSE + SET train_start_date = DATE '2000-01-01'; + SET train_end_date = DATE '2024-12-01'; END IF; -- Finally, the script calls a stored procedure, passing the adjusted training dates and split numbers as arguments. This stored procedure From fb3aac899a48a8b75f7741fd0638584475c907cd Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 4 Dec 2024 12:43:18 -0500 Subject: [PATCH 101/110] Update invoke_churn_propensity_training_preparation.sqlx --- sql/query/invoke_churn_propensity_training_preparation.sqlx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/query/invoke_churn_propensity_training_preparation.sqlx b/sql/query/invoke_churn_propensity_training_preparation.sqlx index 1c57ebbb..10a48ef4 100644 --- a/sql/query/invoke_churn_propensity_training_preparation.sqlx +++ b/sql/query/invoke_churn_propensity_training_preparation.sqlx @@ -63,8 +63,8 @@ IF churners > 0 THEN SET train_start_date = min_date; SET train_end_date = max_date; ELSE - SET train_start_date = DATE '2000-01-01'; - SET train_end_date = DATE '2024-12-01'; + SET train_start_date = DATE_SUB(CURRENT_DATE(), INTERVAL 3 YEAR); + SET train_end_date = DATE_SUB(CURRENT_DATE(), INTERVAL 5 DAY); END IF; -- Finally, the script calls a stored procedure, passing the adjusted training dates and split numbers as arguments. From d55624c98afa45e822860efeef2f0da0f172403e Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 4 Dec 2024 12:43:41 -0500 Subject: [PATCH 102/110] Update invoke_purchase_propensity_training_preparation.sqlx --- .../invoke_purchase_propensity_training_preparation.sqlx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/query/invoke_purchase_propensity_training_preparation.sqlx b/sql/query/invoke_purchase_propensity_training_preparation.sqlx index 6fafc30e..1793ad1c 100644 --- a/sql/query/invoke_purchase_propensity_training_preparation.sqlx +++ b/sql/query/invoke_purchase_propensity_training_preparation.sqlx @@ -64,8 +64,8 @@ IF churners > 0 THEN SET train_start_date = min_date; SET train_end_date = max_date; ELSE - SET train_start_date = DATE '2000-01-01'; - SET train_end_date = DATE '2024-12-01'; + SET train_start_date = DATE_SUB(CURRENT_DATE(), INTERVAL 3 YEAR); + SET train_end_date = DATE_SUB(CURRENT_DATE(), INTERVAL 5 DAY); END IF; -- Finally, the script calls a stored procedure, passing the adjusted training dates and split numbers as arguments. This stored procedure From a4ca058107e070b7ba0e2f5341335f0834166735 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Wed, 4 Dec 2024 12:45:28 -0500 Subject: [PATCH 103/110] Update invoke_customer_lifetime_value_training_preparation.sqlx --- ...ustomer_lifetime_value_training_preparation.sqlx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sql/query/invoke_customer_lifetime_value_training_preparation.sqlx b/sql/query/invoke_customer_lifetime_value_training_preparation.sqlx index 597dcac8..cfbad806 100644 --- a/sql/query/invoke_customer_lifetime_value_training_preparation.sqlx +++ b/sql/query/invoke_customer_lifetime_value_training_preparation.sqlx @@ -54,17 +54,18 @@ SET validation_split_end_number = {{validation_split_end_number}}; -- IF there are no users in the time interval selected, then set "train_start_date" and "train_end_date" as "max_date" and "min_date". SET purchasers = (SELECT COUNT(DISTINCT user_pseudo_id) FROM `{{mds_project_id}}.{{mds_dataset}}.event` - WHERE event_date BETWEEN train_start_date AND train_end_date + WHERE event_date BETWEEN min_date AND max_date ); --- If there are purchasers no changes to the train_start_date and train_end_date --- Else, expand the interval, hopefully a purchaser will be in the interval +-- Setting Training Dates +-- If there are churners in the training set, then keep the calculated dates, or else set +-- the start and end dates to a fixed interval preventing `train_start_date` and `train_end_date` from being NULL. IF purchasers > 0 THEN - SET train_start_date = train_start_date; - SET train_end_date = train_end_date; -ELSE SET train_start_date = min_date; SET train_end_date = max_date; +ELSE + SET train_start_date = DATE_SUB(CURRENT_DATE(), INTERVAL 3 YEAR); + SET train_end_date = DATE_SUB(CURRENT_DATE(), INTERVAL 5 DAY); END IF; -- Finally, the script calls a stored procedure, passing the adjusted training dates and split numbers as arguments. This stored procedure likely handles the actual data preparation for the model. From fda87629ce436f9c75fe6d857639656193d60c2c Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 9 Dec 2024 09:09:40 -0500 Subject: [PATCH 104/110] Update invoke_purchase_propensity_training_preparation.sqlx --- sql/query/invoke_purchase_propensity_training_preparation.sqlx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/query/invoke_purchase_propensity_training_preparation.sqlx b/sql/query/invoke_purchase_propensity_training_preparation.sqlx index 1793ad1c..cfc5053f 100644 --- a/sql/query/invoke_purchase_propensity_training_preparation.sqlx +++ b/sql/query/invoke_purchase_propensity_training_preparation.sqlx @@ -60,7 +60,7 @@ SET purchasers = (SELECT COUNT(DISTINCT user_pseudo_id) -- Setting Training Dates -- If there are churners in the training set, then keep the calculated dates, or else set -- the start and end dates to a fixed interval preventing `train_start_date` and `train_end_date` from being NULL. -IF churners > 0 THEN +IF purchasers > 0 THEN SET train_start_date = min_date; SET train_end_date = max_date; ELSE From 5a7eb5a5372862e718b009c82a274b868ac5ec75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Lindblad?= Date: Mon, 9 Dec 2024 18:10:49 +0100 Subject: [PATCH 105/110] Set pipeline state as terraform variables (#260) --- config/config.yaml.tftpl | 41 +++------- infrastructure/terraform/main.tf | 1 + infrastructure/terraform/variables.tf | 107 ++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 30 deletions(-) diff --git a/config/config.yaml.tftpl b/config/config.yaml.tftpl index 9c31bf3b..c58b620c 100644 --- a/config/config.yaml.tftpl +++ b/config/config.yaml.tftpl @@ -195,9 +195,7 @@ vertex_ai: subnetwork: "default" # If you want to use the vpc network defined above, set the following flag to true use_private_service_access: false - # The `state` defines the state of the pipeline. - # In case you don't want to schedule the pipeline, set the state to `PAUSED`. - state: PAUSED # possible states ACTIVE or PAUSED + state: '${pipeline_configuration.feature-creation-auto-audience-segmentation.execution.schedule.state}' # The `pipeline_parameters` defines the parameters that are going to be used to compile the pipeline. # Those values may difer depending on the pipeline type and the pipeline steps being used. # Make sure you review the python function the defines the pipeline. @@ -279,9 +277,7 @@ vertex_ai: subnetwork: "default" # If you want to use the vpc network defined above, set the following flag to true use_private_service_access: false - # The `state` defines the state of the pipeline. - # In case you don't want to schedule the pipeline, set the state to `PAUSED`. - state: PAUSED # possible states ACTIVE or PAUSED + state: '${pipeline_configuration.feature-creation-audience-segmentation.execution.schedule.state}' # The `pipeline_parameters` defines the parameters that are going to be used to compile the pipeline. # Those values may difer depending on the pipeline type and the pipeline steps being used. # Make sure you review the python function the defines the pipeline. @@ -344,9 +340,7 @@ vertex_ai: subnetwork: "default" # If you want to use the vpc network defined above, set the following flag to true use_private_service_access: false - # The `state` defines the state of the pipeline. - # In case you don't want to schedule the pipeline, set the state to `PAUSED`. - state: PAUSED # possible states ACTIVE or PAUSED + state: '${pipeline_configuration.feature-creation-purchase-propensity.execution.schedule.state}' pipeline_parameters: project_id: "${project_id}" location: "${location}" @@ -407,9 +401,7 @@ vertex_ai: subnetwork: "default" # If you want to use the vpc network defined above, set the following flag to true use_private_service_access: false - # The `state` defines the state of the pipeline. - # In case you don't want to schedule the pipeline, set the state to `PAUSED`. - state: PAUSED # possible states ACTIVE or PAUSED + state: '${pipeline_configuration.feature-creation-churn-propensity.execution.schedule.state}' pipeline_parameters: project_id: "${project_id}" location: "${location}" @@ -464,9 +456,7 @@ vertex_ai: subnetwork: "default" # If you want to use the vpc network defined above, set the following flag to true use_private_service_access: false - # The `state` defines the state of the pipeline. - # In case you don't want to schedule the pipeline, set the state to `PAUSED`. - state: PAUSED # possible states ACTIVE or PAUSED + state: '${pipeline_configuration.feature-creation-customer-ltv.execution.schedule.state}' pipeline_parameters: project_id: "${project_id}" location: "${location}" @@ -527,9 +517,7 @@ vertex_ai: subnetwork: "default" # If you want to use the vpc network defined above, set the following flag to true use_private_service_access: false - # The `state` defines the state of the pipeline. - # In case you don't want to schedule the pipeline, set the state to `PAUSED`. - state: PAUSED # possible states ACTIVE or PAUSED + state: '${pipeline_configuration.feature-creation-aggregated-value-based-bidding.execution.schedule.state}' pipeline_parameters: project_id: "${project_id}" location: "${location}" @@ -575,9 +563,7 @@ vertex_ai: subnetwork: "default" # If you want to use the vpc network defined above, set the following flag to true use_private_service_access: false - # The `state` defines the state of the pipeline. - # In case you don't want to schedule the pipeline, set the state to `PAUSED`. - state: PAUSED # possible states ACTIVE or PAUSED + state: '${pipeline_configuration.value_based_bidding.training.schedule.state}' # These are pipeline parameters that will be passed to the pipeline to be recompiled pipeline_parameters: project: "${project_id}" @@ -658,9 +644,7 @@ vertex_ai: subnetwork: "default" # If you want to use the vpc network defined above, set the following flag to true use_private_service_access: false - # The `state` defines the state of the pipeline. - # In case you don't want to schedule the pipeline, set the state to `PAUSED`. - state: PAUSED # possible states ACTIVE or PAUSED + state: '${pipeline_configuration.value_based_bidding.explanation.schedule.state}' pipeline_parameters: project: "${project_id}" location: "${cloud_region}" @@ -705,9 +689,7 @@ vertex_ai: subnetwork: "default" # If you want to use the vpc network defined above, set the following flag to true use_private_service_access: false - # The `state` defines the state of the pipeline. - # In case you don't want to schedule the pipeline, set the state to `PAUSED`. - state: PAUSED # possible states ACTIVE or PAUSED + state: '${pipeline_configuration.purchase_propensity.training.schedule.state}' # These are pipeline parameters that will be passed to the pipeline to be recompiled pipeline_parameters: project: "${project_id}" @@ -808,9 +790,7 @@ vertex_ai: subnetwork: "default" # If you want to use the vpc network defined above, set the following flag to true use_private_service_access: false - # The `state` defines the state of the pipeline. - # In case you don't want to schedule the pipeline, set the state to `PAUSED`. - state: PAUSED # possible states ACTIVE or PAUSED + state: '${pipeline_configuration.purchase_propensity.prediction.schedule.state}' pipeline_parameters: project_id: "${project_id}" location: "${cloud_region}" @@ -871,6 +851,7 @@ vertex_ai: subnetwork: "default" # If you want to use the vpc network defined above, set the following flag to true use_private_service_access: false + state: '${pipeline_configuration.churn_propensity.training.schedule.state}' # The `state` defines the state of the pipeline. # In case you don't want to schedule the pipeline, set the state to `PAUSED`. state: PAUSED # possible states ACTIVE or PAUSED diff --git a/infrastructure/terraform/main.tf b/infrastructure/terraform/main.tf index 4bf297a2..8ac99a5d 100644 --- a/infrastructure/terraform/main.tf +++ b/infrastructure/terraform/main.tf @@ -124,6 +124,7 @@ resource "local_file" "feature_store_configuration" { # TODO: this needs to be specific to environment. location = var.destination_data_location time_zone = var.time_zone + pipeline_configuration = var.pipeline_configuration }) } diff --git a/infrastructure/terraform/variables.tf b/infrastructure/terraform/variables.tf index 9819041a..8ce1197d 100644 --- a/infrastructure/terraform/variables.tf +++ b/infrastructure/terraform/variables.tf @@ -241,3 +241,110 @@ variable "time_zone" { type = string default = "America/New_York" } + +variable "pipeline_configuration" { + description = "Pipeline configuration that will alternate certain settings in the config.yaml.tftpl" + type = map( + map( + object({ + schedule = object({ + # The `state` defines the state of the pipeline. + # In case you don't want to schedule the pipeline, set the state to `PAUSED`. + state = string + }) + }) + ) + ) + + default = { + feature-creation-auto-audience-segmentation = { + execution = { + schedule = { + state = "PAUSED" + } + } + } + feature-creation-audience-segmentation = { + execution = { + schedule = { + state = "PAUSED" + } + } + } + feature-creation-purchase-propensity = { + execution = { + schedule = { + state = "PAUSED" + } + } + } + feature-creation-churn-propensity = { + execution = { + schedule = { + state = "PAUSED" + } + } + } + feature-creation-customer-ltv = { + execution = { + schedule = { + state = "PAUSED" + } + } + } + feature-creation-aggregated-value-based-bidding = { + execution = { + schedule = { + state = "PAUSED" + } + } + } + value_based_bidding = { + training = { + schedule = { + state = "PAUSED" + } + } + explanation = { + schedule = { + state = "PAUSED" + } + } + } + purchase_propensity = { + training = { + schedule = { + state = "PAUSED" + } + } + prediction = { + schedule = { + state = "PAUSED" + } + } + } + churn_propensity = { + training = { + schedule = { + state = "PAUSED" + } + } + prediction = { + schedule = { + state = "PAUSED" + } + } + } + } + validation { + condition = alltrue([ + for p in keys(var.pipeline_configuration) : alltrue([ + for c in keys(var.pipeline_configuration[p]) : ( + try(var.pipeline_configuration[p][c].schedule.state, "") == "ACTIVE" || + try(var.pipeline_configuration[p][c].schedule.state, "") == "PAUSED" + ) + ]) + ]) + error_message = "The 'state' field must be either 'PAUSED' or 'ACTIVE' for all pipeline configurations." + } +} From 5dec3321e511d927473ec96fa50a6706d0ff6cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Lindblad?= Date: Mon, 9 Dec 2024 18:11:40 +0100 Subject: [PATCH 106/110] Reset variable reference for purchase propensity (#263) --- sql/query/invoke_purchase_propensity_training_preparation.sqlx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/query/invoke_purchase_propensity_training_preparation.sqlx b/sql/query/invoke_purchase_propensity_training_preparation.sqlx index cfc5053f..b8738465 100644 --- a/sql/query/invoke_purchase_propensity_training_preparation.sqlx +++ b/sql/query/invoke_purchase_propensity_training_preparation.sqlx @@ -58,7 +58,7 @@ SET purchasers = (SELECT COUNT(DISTINCT user_pseudo_id) ); -- Setting Training Dates --- If there are churners in the training set, then keep the calculated dates, or else set +-- If there are purchasers in the training set, then keep the calculated dates, or else set -- the start and end dates to a fixed interval preventing `train_start_date` and `train_end_date` from being NULL. IF purchasers > 0 THEN SET train_start_date = min_date; From 3b98a4be14a2356d279f183a89f3eefb8e026f31 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 9 Dec 2024 13:47:19 -0500 Subject: [PATCH 107/110] Update export-procedures.tf --- .../terraform/modules/activation/export-procedures.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform/modules/activation/export-procedures.tf b/infrastructure/terraform/modules/activation/export-procedures.tf index 55ab1001..e7281356 100644 --- a/infrastructure/terraform/modules/activation/export-procedures.tf +++ b/infrastructure/terraform/modules/activation/export-procedures.tf @@ -118,7 +118,7 @@ resource "google_bigquery_routine" "export_churn_propensity_procedure" { routine_id = "export_churn_propensity_predictions" routine_type = "PROCEDURE" language = "SQL" - definition_body = data.template_file.purchase_propensity_csv_export_query.rendered + definition_body = data.template_file.churn_propensity_csv_export_query.rendered description = "Export purchase propensity predictions as CSV for GA4 User Data Import" arguments { name = "prediction_table_name" From 0631f34e1a5a83946bc32dfe1eb3b63e28d02a28 Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Mon, 9 Dec 2024 17:09:10 -0500 Subject: [PATCH 108/110] Update config.yaml.tftpl --- config/config.yaml.tftpl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/config/config.yaml.tftpl b/config/config.yaml.tftpl index c58b620c..4b4d9914 100644 --- a/config/config.yaml.tftpl +++ b/config/config.yaml.tftpl @@ -850,11 +850,10 @@ vertex_ai: # Follow the guide: https://cloud.google.com/vertex-ai/docs/general/vpc-peering subnetwork: "default" # If you want to use the vpc network defined above, set the following flag to true - use_private_service_access: false - state: '${pipeline_configuration.churn_propensity.training.schedule.state}' + use_private_service_access: false # The `state` defines the state of the pipeline. # In case you don't want to schedule the pipeline, set the state to `PAUSED`. - state: PAUSED # possible states ACTIVE or PAUSED + state: '${pipeline_configuration.churn_propensity.training.schedule.state}' # These are pipeline parameters that will be passed to the pipeline to be recompiled pipeline_parameters: project: "${project_id}" From d7ee88adbbd98d9a1b46778bc97c4d9d6271382f Mon Sep 17 00:00:00 2001 From: Carlos Timoteo Date: Fri, 13 Dec 2024 14:23:43 -0500 Subject: [PATCH 109/110] Update component.py --- python/pipelines/components/bigquery/component.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/pipelines/components/bigquery/component.py b/python/pipelines/components/bigquery/component.py index c4aa542f..e52a511e 100644 --- a/python/pipelines/components/bigquery/component.py +++ b/python/pipelines/components/bigquery/component.py @@ -879,7 +879,7 @@ def bq_dynamic_query_exec_output( # Construct query template template = jinja2.Template(""" CREATE OR REPLACE TABLE `{{project_id}}.{{dataset}}.{{create_table}}` AS ( - SELECT + SELECT DISTINCT feature, ROUND(100 * SUM(users) OVER (ORDER BY users DESC) / SUM(users) OVER (), 2) as cumulative_traffic_percent, @@ -892,7 +892,7 @@ def bq_dynamic_query_exec_output( SELECT user_pseudo_id, user_id, - page_location as page_path + LOWER(page_location) as page_path FROM `{{mds_project_id}}.{{mds_dataset}}.event` WHERE event_name = 'page_view' @@ -1423,4 +1423,4 @@ def execute_query_with_retries(query): logging.error(f"Query failed after retries: {e}") - \ No newline at end of file + From 4a6edcc170104ee56803fa8e0d5bc3176b9f8a31 Mon Sep 17 00:00:00 2001 From: Charlie Wang <2144018+kingman@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:19:14 +0100 Subject: [PATCH 110/110] new view for aggregated stat on purchase propensity predictions (#268) --- .../terraform/modules/monitor/main.tf | 28 +++++++++++++ ...hase_propensity_smart_bidding_view.sql.tpl | 41 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 templates/purchase_propensity_smart_bidding_view.sql.tpl diff --git a/infrastructure/terraform/modules/monitor/main.tf b/infrastructure/terraform/modules/monitor/main.tf index ae94c891..4129828b 100644 --- a/infrastructure/terraform/modules/monitor/main.tf +++ b/infrastructure/terraform/modules/monitor/main.tf @@ -28,6 +28,8 @@ locals { activation_project_url = "${local.p_key}=${var.activation_project_id}" mds_dataform_repo = "marketing-analytics" + + purchase_propensity_dataset = "purchase_propensity" } module "project_services" { @@ -259,3 +261,29 @@ data "template_file" "looker_studio_dashboard_url" { dataflow_log_table_id = local.dataflow_log_table_id } } + +data "template_file" "purchase_propensity_prediction_stats_query" { + template = file("${local.source_root_dir}/templates/purchase_propensity_smart_bidding_view.sql.tpl") + vars = { + project_id = var.feature_store_project_id + purchase_propensity_dataset = local.purchase_propensity_dataset + activation_dataset = "activation" + } +} + +data "google_bigquery_dataset" "purchase_propensity_dataset" { + dataset_id = local.purchase_propensity_dataset + project = var.feature_store_project_id +} + +resource "google_bigquery_table" "purchase_propensity_prediction_stats" { + project = var.feature_store_project_id + dataset_id = data.google_bigquery_dataset.purchase_propensity_dataset.dataset_id + table_id = "purchase_propensity_prediction_stats" + deletion_protection = false + + view { + query = data.template_file.purchase_propensity_prediction_stats_query.rendered + use_legacy_sql = false + } +} diff --git a/templates/purchase_propensity_smart_bidding_view.sql.tpl b/templates/purchase_propensity_smart_bidding_view.sql.tpl new file mode 100644 index 00000000..f3891c15 --- /dev/null +++ b/templates/purchase_propensity_smart_bidding_view.sql.tpl @@ -0,0 +1,41 @@ +-- Copyright 2024 Google LLC +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +SELECT + p_stat.inference_date, + p_stat.p_p_decile, + p_stat.number_of_users, + conf.value*p_stat.number_of_users AS predicted_purchase_value +FROM ( + SELECT + inference_date, + p_p_decile, + COUNT(p_p_decile) AS number_of_users + FROM ( + SELECT + PARSE_DATE('%Y_%m_%d', SUBSTR(_TABLE_SUFFIX, 1,10)) AS inference_date, + NTILE(10) OVER (PARTITION BY _TABLE_SUFFIX ORDER BY b.prediction_prob DESC) AS p_p_decile, + FROM + `${project_id}.${purchase_propensity_dataset}.predictions_*` b + WHERE + ENDS_WITH(_TABLE_SUFFIX, '_view') ) + GROUP BY + inference_date, + p_p_decile ) AS p_stat +JOIN + `${project_id}.${activation_dataset}.vbb_activation_configuration` conf +ON + p_stat.p_p_decile = decile +WHERE + conf.activation_type = 'purchase-propensity' \ No newline at end of file