Skip to main content

zomato-ai-data-engineering-pipeline

End-to-end batch data pipeline with Snowflake, dbt, Airflow, and OpenAI for food delivery analytics

Aller à l'installation

Informations de source

Dépôt
reason-machines/data-skills
Dernière activité de la source
1 août 2026 à 20:54
Langue détectée de SKILL.md
anglais
Étoiles
5
Forks
1

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
zomato-ai-data-engineering-pipeline
description
End-to-end batch data pipeline with Snowflake, dbt, Airflow, and OpenAI for food delivery analytics
triggers
["build a zomato data pipeline","set up snowflake medallion architecture","create dbt incremental models for zomato","orchestrate data pipeline with airflow","enrich reviews with openai llm","implement rag for text data","build text to sql with openai","configure s3 snowflake integration"]
# zomato-ai-data-engineering-pipeline > Skill by [ara.so](https://ara.so) — Data Skills collection. Complete batch data engineering pipeline that processes food delivery data through a medallion architecture (Bronze → Silver → Gold) using Amazon S3, Snowflake, dbt, Airflow orchestration, and OpenAI-powered AI capabilities (LLM enrichment, RAG, text-to-SQL). ## Project Overview **Pipeline Flow:** ``` CSVs → S3 Data Lake → Snowflake RAW (Bronze) → dbt STAGING (Silver) → dbt MARTS (Gold) → AI Layer ``` **Architecture Layers:** - **Bronze (RAW)**: Direct `COPY INTO` from S3 via storage integration - **Silver (STAGING)**: dbt views for cleaning, typing, renaming - **Gold (MARTS)**: Dimensions, incremental facts (MERGE), business aggregates, SCD2 snapshots - **AI**: LLM enrichment, RAG chat, text-to-SQL queries **Data Scale:** - 10M orders - 23M order items - 300K text reviews - 7 source tables (restaurants, users, food, menu, orders, order_items, reviews) ## Installation & Setup ### Prerequisites ```bash # Clone and get dataset git clone https://github.com/darshilparmar/zomato-ai-data-engineering-end-to-end-project cd zomato-ai-data-engineering-end-to-end-project # Download CSVs from Google Drive (link in README) → place in data/ ``` ### AWS S3 Setup ```bash # 1. Create S3 bucket aws s3 mb s3://your-zomato-bucket # 2. Upload data to S3 aws s3 sync data/ s3://your-zomato-bucket/raw/ --exclude "*" \ --include "restaurants/*" \ --include "users/*" \ --include "food/*" \ --include "menu/*" \ --include "orders/*" \ --include "order_items/*" \ --include "reviews/*" # 3. Create IAM policy (use aws/iam/s3-read-policy.json) aws iam create-policy \ --policy-name zomato-s3-read \ --policy-document file://aws/iam/s3-read-policy.json # 4. Create IAM role with initial trust policy aws iam create-role \ --role-name snowflake-s3-role \ --assume-role-policy-document file://aws/iam/snowflake-role-trust-policy-initial.json # 5. Attach policy to role aws iam attach-role-policy \ --role-name snowflake-s3-role \ --policy-arn arn:aws:iam::YOUR_ACCOUNT:policy/zomato-s3-read ``` ### Snowflake Setup ```sql -- 1. Create warehouse, database, schemas CREATE WAREHOUSE ZOMATO_WH WITH WAREHOUSE_SIZE = 'MEDIUM' AUTO_SUSPEND = 60 AUTO_RESUME = TRUE; CREATE DATABASE ZOMATO; USE DATABASE ZOMATO; CREATE SCHEMA RAW; CREATE SCHEMA STAGING; CREATE SCHEMA MARTS; CREATE SCHEMA SNAPSHOTS; CREATE SCHEMA AI; -- 2. Create role and grant permissions CREATE ROLE DBT_ROLE; GRANT USAGE ON WAREHOUSE ZOMATO_WH TO ROLE DBT_ROLE; GRANT ALL ON DATABASE ZOMATO TO ROLE DBT_ROLE; GRANT ALL ON ALL SCHEMAS IN DATABASE ZOMATO TO ROLE DBT_ROLE; GRANT ROLE DBT_ROLE TO USER YOUR_USER; -- 3. Create storage integration (replace with your IAM role ARN) CREATE STORAGE INTEGRATION s3_zomato_integration TYPE = EXTERNAL_STAGE STORAGE_PROVIDER = 'S3' ENABLED = TRUE STORAGE_AWS_ROLE_ARN = 'arn:aws:iam::YOUR_ACCOUNT:role/snowflake-s3-role' STORAGE_ALLOWED_LOCATIONS = ('s3://your-zomato-bucket/raw/'); -- 4. Get Snowflake's IAM user ARN and external ID DESC STORAGE INTEGRATION s3_zomato_integration; -- Copy STORAGE_AWS_IAM_USER_ARN and STORAGE_AWS_EXTERNAL_ID -- 5. Update IAM role trust policy with these values (aws/iam/snowflake-role-trust-policy-final.json) -- 6. Create external stage CREATE STAGE s3_stage STORAGE_INTEGRATION = s3_zomato_integration URL = 's3://your-zomato-bucket/raw/'; -- 7. Create RAW tables CREATE TABLE RAW.RESTAURANTS ( restaurant_id NUMBER, name VARCHAR, city VARCHAR, rating FLOAT, rating_count NUMBER, cost VARCHAR, cuisine VARCHAR, lic_no VARCHAR, link VARCHAR, address VARCHAR, menu VARCHAR ); CREATE TABLE RAW.USERS ( user_id NUMBER, name VARCHAR, email VARCHAR, password VARCHAR, age NUMBER, gender VARCHAR, marital_status VARCHAR, occupation VARCHAR, monthly_income NUMBER, educational_qualifications VARCHAR, family_size NUMBER ); CREATE TABLE RAW.FOOD ( food_id NUMBER, item VARCHAR, veg_or_non_veg VARCHAR ); CREATE TABLE RAW.MENU ( menu_id NUMBER, restaurant_id NUMBER, food_id NUMBER, cuisine VARCHAR, price NUMBER ); CREATE TABLE RAW.ORDERS ( order_id NUMBER, user_id NUMBER, restaurant_id NUMBER, order_date DATE, order_time TIME, order_status VARCHAR, order_value NUMBER ); CREATE TABLE RAW.ORDER_ITEMS ( order_item_id NUMBER, order_id NUMBER, food_id NUMBER, quantity NUMBER, price NUMBER ); CREATE TABLE RAW.REVIEWS ( review_id NUMBER, order_id NUMBER, restaurant_id NUMBER, user_id NUMBER, rating NUMBER, review_text VARCHAR, review_date DATE ); ``` ### dbt Configuration ```bash cd zomato # Create profiles.yml (or update ~/.dbt/profiles.yml) cat > profiles.yml <<EOF zomato: target: dev outputs: dev: type: snowflake account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}" user: "{{ env_var('SNOWFLAKE_USER') }}" password: "{{ env_var('SNOWFLAKE_PASSWORD') }}" role: DBT_ROLE database: ZOMATO warehouse: ZOMATO_WH schema: STAGING threads: 4 EOF # Set environment variables export SNOWFLAKE_ACCOUNT=your_account.region export SNOWFLAKE_USER=your_user export SNOWFLAKE_PASSWORD=your_password # Test connection dbt debug # Install dependencies dbt deps ``` ### Airflow Setup ```bash cd airflow # Create .env from example cp example.env .env # Edit .env with your credentials # SNOWFLAKE_ACCOUNT=your_account.region # SNOWFLAKE_USER=your_user # SNOWFLAKE_PASSWORD=your_password # SNOWFLAKE_ROLE=DBT_ROLE # SNOWFLAKE_WAREHOUSE=ZOMATO_WH # SNOWFLAKE_DATABASE=ZOMATO # OPENAI_API_KEY=your_openai_key # S3_BUCKET=your-zomato-bucket # SAMPLE_N=1000 # Build and start Airflow docker compose build docker compose up -d # Access Airflow UI # http://localhost:8080 (admin/admin) ``` ## Key dbt Models ### Staging (Silver Layer) ```yaml # models/staging/schema.yml version: 2 sources: - name: raw database: ZOMATO schema: RAW tables: - name: restaurants - name: users - name: food - name: menu - name: orders - name: order_items - name: reviews ``` ```sql -- models/staging/stg_restaurants.sql WITH source AS ( SELECT * FROM {{ source('raw', 'restaurants') }} ), cleaned AS ( SELECT restaurant_id, TRIM(name) AS restaurant_name, LOWER(TRIM(city)) AS city, rating, rating_count, -- Parse cost: '₹ 200' → 200, '--' → NULL TRY_CAST( REPLACE(REPLACE(cost, '₹', ''), ' ', '') AS NUMBER ) AS avg_cost_for_two, TRIM(cuisine) AS cuisine, NULLIF(TRIM(lic_no), '--') AS license_number, link AS restaurant_url, address, menu AS menu_url FROM source ) SELECT * FROM cleaned ``` ```sql -- models/staging/stg_orders.sql WITH source AS ( SELECT * FROM {{ source('raw', 'orders') }} ), cleaned AS ( SELECT order_id, user_id, restaurant_id, order_date, order_time, LOWER(TRIM(order_status)) AS order_status, order_value, -- Derive delivery flag CASE WHEN order_status = 'delivered' THEN TRUE ELSE FALSE END AS is_delivered, -- Derive cancellation flag CASE WHEN order_status IN ('cancelled', 'canceled') THEN TRUE ELSE FALSE END AS is_cancelled FROM source ) SELECT * FROM cleaned ``` ### Marts (Gold Layer) ```sql -- models/marts/dim_restaurants.sql {{ config( materialized='table' ) }} SELECT restaurant_id, restaurant_name, city, rating, rating_count, avg_cost_for_two, cuisine, license_number, restaurant_url, address FROM {{ ref('stg_restaurants') }} ``` ```sql -- models/marts/dim_customer.sql {{ config( materialized='table' ) }} WITH customers AS ( SELECT user_id, name AS customer_name, LOWER(email) AS email, age, gender, marital_status, occupation, monthly_income, educational_qualifications, family_size, -- Age segmentation CASE WHEN age < 25 THEN '18-24' WHEN age BETWEEN 25 AND 34 THEN '25-34' WHEN age BETWEEN 35 AND 44 THEN '35-44' WHEN age BETWEEN 45 AND 54 THEN '45-54' WHEN age >= 55 THEN '55+' ELSE 'Unknown' END AS age_segment FROM {{ ref('stg_users') }} ) SELECT * FROM customers ``` ```sql -- models/marts/fct_orders.sql {{ config( materialized='incremental', unique_key='order_id', on_schema_change='append_new_columns' ) }} WITH orders AS ( SELECT order_id, user_id, restaurant_id, order_date, order_time, order_status, order_value, is_delivered, is_cancelled FROM {{ ref('stg_orders') }} {% if is_incremental() %} WHERE order_date > (SELECT MAX(order_date) FROM {{ this }}) {% endif %} ) SELECT * FROM orders ``` ```sql -- models/marts/fact_order_items.sql {{ config( materialized='incremental', unique_key='order_item_id', on_schema_change='append_new_columns' ) }} WITH order_items AS ( SELECT oi.order_item_id, oi.order_id, oi.food_id, oi.quantity, oi.price, oi.quantity * oi.price AS line_total, o.order_date FROM {{ ref('stg_order_items') }} oi JOIN {{ ref('stg_orders') }} o ON oi.order_id = o.order_id {% if is_incremental() %} WHERE o.order_date > (SELECT MAX(order_date) FROM {{ this }}) {% endif %} ) SELECT * FROM order_items ``` ```sql -- models/marts/mart_daily_city_revenue.sql {{ config( materialized='table' ) }} WITH daily_metrics AS ( SELECT o.order_date, r.city, COUNT(DISTINCT o.order_id) AS total_orders, COUNT(DISTINCT CASE WHEN o.is_delivered THEN o.order_id END) AS delivered_orders, COUNT(DISTINCT CASE WHEN o.is_cancelled THEN o.order_id END) AS cancelled_orders, SUM(CASE WHEN o.is_delivered THEN o.order_value ELSE 0 END) AS gmv, AVG(CASE WHEN o.is_delivered THEN o.order_value END) AS aov, COUNT(DISTINCT o.user_id) AS active_customers, COUNT(DISTINCT o.restaurant_id) AS active_restaurants FROM {{ ref('fct_orders') }} o JOIN {{ ref('dim_restaurants') }} r ON o.restaurant_id = r.restaurant_id GROUP BY o.order_date, r.city ) SELECT order_date, city, total_orders, delivered_orders, cancelled_orders, ROUND(cancelled_orders::FLOAT / NULLIF(total_orders, 0) * 100, 2) AS cancellation_rate_pct, gmv, aov, active_customers, active_restaurants, ROUND(gmv / NULLIF(active_restaurants, 0), 2) AS revenue_per_restaurant FROM daily_metrics ``` ### dbt Testing ```yaml # models/marts/schema.yml version: 2 models: - name: dim_restaurants description: Restaurant dimension columns: - name: restaurant_id description: Primary key tests: - unique - not_null - name: city tests: - not_null - name: fct_orders description: Orders fact table (incremental) columns: - name: order_id description: Primary key tests: - unique - not_null - name: user_id tests: - not_null - relationships: to: ref('dim_customer') field: user_id - name: restaurant_id tests: - not_null - relationships: to: ref('dim_restaurants') field: restaurant_id - name: order_status tests: - accepted_values: values: ['delivered', 'cancelled', 'pending', 'preparing'] ``` ## Airflow DAG
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub