From 235aff9ed023e9b88162de5c205ed11451b03ed6 Mon Sep 17 00:00:00 2001 From: Halyna Romanyshyn <30292604+halyna1995@users.noreply.github.com> Date: Thu, 9 Jul 2026 22:15:01 +0200 Subject: [PATCH] Complete Week 10 dbt borough stats assignment --- .gitignore | 8 +++ .user.yml | 1 + AI_ASSIST.md | 45 +++++++++---- docs/lineage.png | Bin 0 -> 68342 bytes macros/safe_divide.sql | 7 +-- models/marts/_fct_daily_borough_stats.yml | 58 +++++++++++++---- models/marts/fct_daily_borough_stats.sql | 32 ++++------ models/staging/_sources.yml | 6 +- models/staging/_stg_trips.yml | 27 +++++--- models/staging/_stg_zones.yml | 14 +++-- models/staging/stg_trips.sql | 16 ++--- models/staging/stg_zones.sql | 4 +- package-lock.yml | 5 ++ packages.yml | 6 +- profiles.yml.example | 15 ++--- reports/answers.md | 70 +++++++++++++++++---- tests/assert_avg_tip_pct_within_bounds.sql | 7 ++- 17 files changed, 217 insertions(+), 104 deletions(-) create mode 100644 .user.yml create mode 100644 docs/lineage.png create mode 100644 package-lock.yml diff --git a/.gitignore b/.gitignore index d083004..9c4bee0 100644 --- a/.gitignore +++ b/.gitignore @@ -162,3 +162,11 @@ dist vite.config.js.timestamp-* vite.config.ts.timestamp-* + +# Local secrets and dbt generated files +.env +profiles.yml +dbt_packages/ +target/ +logs/ +grader_output.txt diff --git a/.user.yml b/.user.yml new file mode 100644 index 0000000..f40245d --- /dev/null +++ b/.user.yml @@ -0,0 +1 @@ +id: be40aff5-745e-4ac9-9496-e9d5fb6c1776 diff --git a/AI_ASSIST.md b/AI_ASSIST.md index 76f6a37..e04297d 100644 --- a/AI_ASSIST.md +++ b/AI_ASSIST.md @@ -4,33 +4,52 @@ Document one place you used an LLM during this assignment. ## The problem - - -TODO +I used an LLM while debugging my Week 10 dbt assignment after I ran into several errors that blocked the project from running correctly. The main issue was not writing the assignment from scratch, but understanding and fixing setup/runtime problems: VS Code showed false SQL syntax errors for dbt/Jinja syntax, `dbt deps` failed on Windows because `dbt_packages` was locked by another process, YAML parsing failed in `_fct_daily_borough_stats.yml`, and `dbt build` failed because the local `profiles.yml` connection settings were incorrect. ## The prompt - +I asked the LLM several debugging questions while I was trying to get the dbt project to run correctly. The main prompt was: + +```text +I am working on a Week 10 dbt assignment. I already have the dbt models and YAML files, but I am getting several errors while trying to run and validate the project. + +Please help me debug the errors step by step. I want to understand what is wrong, which files need to be fixed, and how to verify that the project works correctly. -TODO +The errors include: +- VS Code shows SQL syntax errors near dbt/Jinja syntax such as {{ ref(...) }}, {{ source(...) }}, and {% macro %}. +- dbt deps fails on Windows with a file-locking error in dbt_packages. +- dbt compile fails with: Env var required but not provided: 'PG_HOST'. +- A YAML file fails with: mapping values are not allowed in this context. +- dbt build fails because of PostgreSQL profile/connection settings. + +Please explain which errors are real dbt problems, which ones are only editor/linter warnings, and give the exact commands I should run to verify the project. +``` ## The response - +The LLM explained that many red VS Code “SQL syntax” errors were false positives because VS Code was parsing dbt/Jinja files as plain PostgreSQL. It advised me to validate the project with dbt compile and dbt build instead of relying only on the Problems panel. + +For the real errors, the LLM identified concrete fixes: -TODO +add dbt_utils correctly in packages.yml; +clean and reinstall dbt_packages after the Windows file-lock error; +fix _fct_daily_borough_stats.yml by using a YAML multiline description with description: |; +use a local profiles.yml only for running dbt, but keep profiles.yml.example for the repo; +make sure the profile points to the correct database, schema, SSL mode, and password environment variable; +run the final validation with: +dbt deps +dbt debug --profiles-dir . +dbt compile --profiles-dir . +dbt build --profiles-dir . --select +fct_daily_borough_stats ## Reflection - +I used the LLM output as debugging guidance, not as a blind code generator. I kept the advice that dbt/Jinja syntax should be checked with dbt compile rather than by the generic SQL linter in VS Code. I also applied the YAML fix using description: |, corrected the package setup, and checked the dbt project with dbt build. -TODO +I did not paste any real password or private connection string into the LLM. I reviewed the suggested changes before applying them and kept the assignment logic focused on the required dbt models, tests, documentation, and business-answer queries. --- > Remember: never paste real connection strings, passwords, or PII into an LLM. > The NYC TLC dataset is public so sample rows are safe here, but practise the habit. +``` diff --git a/docs/lineage.png b/docs/lineage.png new file mode 100644 index 0000000000000000000000000000000000000000..455c361c92a81164ba038b48a06742f5095ef611 GIT binary patch literal 68342 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 U4K?G;K3_7sc=bZXIk(&Y2RV0K+5i9m literal 0 HcmV?d00001 diff --git a/macros/safe_divide.sql b/macros/safe_divide.sql index e30c8fa..2ea1023 100644 --- a/macros/safe_divide.sql +++ b/macros/safe_divide.sql @@ -3,8 +3,5 @@ -- Use for tip_pct = tip_amount / fare_amount and similar ratio columns. {% macro safe_divide(numerator, denominator) %} - -- TODO: implement the macro body. - -- Use NULLIF(denominator, 0) to avoid division-by-zero errors. - -- Return only the SQL expression (no SELECT, no semicolon). - NULL -{% endmacro %} + {{ numerator }} / nullif({{ denominator }}, 0) +{% endmacro %} \ No newline at end of file diff --git a/models/marts/_fct_daily_borough_stats.yml b/models/marts/_fct_daily_borough_stats.yml index 15bba90..e1e0614 100644 --- a/models/marts/_fct_daily_borough_stats.yml +++ b/models/marts/_fct_daily_borough_stats.yml @@ -2,23 +2,55 @@ version: 2 models: - name: fct_daily_borough_stats - description: > - TODO: state the grain (one row per ___), the source lineage - (built from ___ and ___), and at least one known caveat - (e.g. rows dropped in staging, any WARN-severity tests). - # TODO: Task 5 -- add the compound uniqueness test on the mart's primary - # key (pickup_borough, pickup_date). You need the dbt_utils package for - # this: declare it in packages.yml and run `dbt deps` first. + description: | + Daily borough-level mart for NYC green taxi pickup activity. + + Grain: one row per pickup_borough and pickup_date. + + Source lineage: built from stg_trips joined with stg_zones. + stg_trips reads nyc_taxi.raw_trips. + stg_zones reads nyc_taxi.raw_zones. + + Known caveats: stg_trips drops rows with missing pickup_location_id + or negative fare_amount. The mart uses an inner join to drop trips + whose pickup_location_id does not match a known zone. Real TLC labels + such as EWR, Unknown, and dirty NaN borough values remain in the mart. + The avg_tip_pct singular test uses WARN severity because very small + borough/day groups can legitimately exceed 1 due to outliers. + + tests: + - dbt_utils.unique_combination_of_columns: + combination_of_columns: + - pickup_borough + - pickup_date + columns: - name: pickup_borough - description: "TODO: explain what this column contains and where it comes from" + description: Borough label from stg_zones.borough for the trip pickup zone. + tests: + - not_null + - name: pickup_date - description: "TODO: explain units and how it is derived" + description: Calendar date derived from pickup_datetime. Unit is one local pickup day. + tests: + - not_null + - name: trip_count - description: "TODO: explain what is counted (unit: number of trips)" + description: Number of trips picked up in this borough on this date. Unit is trips. + tests: + - not_null + - name: total_fare - description: "TODO: explain units (USD) and what fare_amount represents" + description: Sum of fare_amount for this borough and date. Unit is US dollars. + tests: + - not_null + - name: avg_tip_pct - description: "TODO: explain the ratio (tip_amount / fare_amount)" + description: Average tip percentage for this borough and date. Calculated as tip_amount divided by fare_amount. + tests: + - not_null + - name: avg_trip_distance - description: "TODO: explain units (miles, from TLC source data)" + description: Average trip distance for this borough and date. Unit is miles. + tests: + - not_null \ No newline at end of file diff --git a/models/marts/fct_daily_borough_stats.sql b/models/marts/fct_daily_borough_stats.sql index bf47070..774c650 100644 --- a/models/marts/fct_daily_borough_stats.sql +++ b/models/marts/fct_daily_borough_stats.sql @@ -14,25 +14,15 @@ zones AS ( ) SELECT - -- TODO: join trips to zones on pickup_location_id = location_id. - -- Use an INNER JOIN: a few trips have a pickup_location_id with no matching - -- zone (e.g. 999). INNER JOIN drops those so pickup_borough is never NULL and - -- can serve as part of the mart's primary key (your not_null test needs this). - -- TODO: aggregate to grain (pickup_borough, pickup_date) - -- Required output columns: - -- pickup_borough TEXT - z.borough - -- pickup_date DATE - pickup_datetime::date - -- trip_count BIGINT - count(*) - -- total_fare NUMERIC - sum(fare_amount) - -- avg_tip_pct NUMERIC - avg(tip_pct) - -- avg_trip_distance NUMERIC - avg(trip_distance) - NULL AS pickup_borough, - NULL AS pickup_date, - NULL AS trip_count, - NULL AS total_fare, - NULL AS avg_tip_pct, - NULL AS avg_trip_distance - + z.borough as pickup_borough, + t.pickup_datetime::date as pickup_date, + count(*)::bigint as trip_count, + sum(t.fare_amount)::numeric as total_fare, + avg(t.tip_pct)::numeric as avg_tip_pct, + avg(t.trip_distance)::numeric as avg_trip_distance FROM trips t --- TODO: add JOIN to zones here --- TODO: add GROUP BY here +inner join zones z + on t.pickup_location_id = z.location_id +group by + z.borough, + t.pickup_datetime::date \ No newline at end of file diff --git a/models/staging/_sources.yml b/models/staging/_sources.yml index 4bbee9c..454cb10 100644 --- a/models/staging/_sources.yml +++ b/models/staging/_sources.yml @@ -2,9 +2,9 @@ version: 2 sources: - name: nyc_taxi - schema: nyc_taxi # TODO: confirm this matches the schema where raw_trips and raw_zones live + schema: nyc_taxi tables: - name: raw_trips - description: "TODO: one sentence on what this table contains and its grain" + description: One row per raw NYC green taxi trip record from the loaded January 2024 dataset. - name: raw_zones - description: "TODO: one sentence on what this table contains" + description: One row per TLC taxi zone, mapping location IDs to borough and zone metadata. diff --git a/models/staging/_stg_trips.yml b/models/staging/_stg_trips.yml index 14829a8..fee8189 100644 --- a/models/staging/_stg_trips.yml +++ b/models/staging/_stg_trips.yml @@ -2,19 +2,28 @@ version: 2 models: - name: stg_trips - description: "TODO: state the grain (one row per ___) and what source this reads from" + description: One row per cleaned NYC green taxi trip, read from nyc_taxi.raw_trips. Rows with missing pickup_location_id or negative fare_amount are removed before aggregation. columns: - name: pickup_datetime - description: "TODO" - # TODO: Task 5 -- add not_null tests on every column used as a join or - # group-by key. See the chapter's dbt Tests section for the syntax. + description: Wall-clock timestamp when the trip started; used to derive pickup_date in the mart. + tests: + - not_null - name: pickup_location_id - description: "TODO" + description: TLC pickup zone identifier; join key to stg_zones.location_id. + tests: + - not_null + - relationships: + to: ref('stg_zones') + field: location_id + severity: warn - name: fare_amount - description: "TODO" + description: Metered trip fare in USD before tip; negative fares are filtered out in this staging model. + tests: + - not_null - name: tip_amount - description: "TODO" + description: Tip amount in USD recorded for the trip. - name: trip_distance - description: "TODO" + description: Trip distance in miles recorded for the trip. - name: tip_pct - description: "TODO" + description: Ratio tip_amount / fare_amount; NULL when fare_amount is zero. + diff --git a/models/staging/_stg_zones.yml b/models/staging/_stg_zones.yml index c4b785d..be92701 100644 --- a/models/staging/_stg_zones.yml +++ b/models/staging/_stg_zones.yml @@ -2,11 +2,15 @@ version: 2 models: - name: stg_zones - description: "TODO: state the grain and what source this reads from" + description: One row per TLC taxi zone from nyc_taxi.raw_zones, used as the borough lookup for pickup_location_id. columns: - name: location_id - description: "TODO" - # TODO: Task 5 -- this column is the join key. Which two generic tests - # guarantee a clean one-to-many join from stg_trips? + description: TLC taxi zone identifier; primary key of the zone lookup and join target for stg_trips.pickup_location_id. + tests: + - unique + - not_null - name: borough - description: "TODO" + description: Borough or special TLC borough label for the zone, including Manhattan, Brooklyn, Queens, Bronx, Staten Island, EWR, Unknown, and dirty NaN labels. + tests: + - not_null: + severity: warn diff --git a/models/staging/stg_trips.sql b/models/staging/stg_trips.sql index b5f7b81..8f17a68 100644 --- a/models/staging/stg_trips.sql +++ b/models/staging/stg_trips.sql @@ -3,11 +3,13 @@ -- Downstream: fct_daily_borough_stats joins this to stg_zones. SELECT - -- TODO: select the columns you need for the mart: - -- pickup_datetime, pickup_location_id, fare_amount, tip_amount, trip_distance - -- - -- TODO: add tip_pct using {{ safe_divide('tip_amount', 'fare_amount') }} - -- - -- TODO: filter out rows where pickup_location_id IS NULL or fare_amount < 0 - + pickup_datetime, + pickup_location_id, + fare_amount, + tip_amount, + trip_distance, + {{ safe_divide('tip_amount', 'fare_amount') }} as tip_pct FROM {{ source('nyc_taxi', 'raw_trips') }} +where pickup_location_id is not null + and fare_amount >= 0 + diff --git a/models/staging/stg_zones.sql b/models/staging/stg_zones.sql index cc34d23..b378c7a 100644 --- a/models/staging/stg_zones.sql +++ b/models/staging/stg_zones.sql @@ -2,6 +2,6 @@ -- Exposes location_id and borough for use as a lookup in the mart. SELECT - -- TODO: select location_id and borough from {{ source('nyc_taxi', 'raw_zones') }} - + location_id, + borough FROM {{ source('nyc_taxi', 'raw_zones') }} diff --git a/package-lock.yml b/package-lock.yml new file mode 100644 index 0000000..f683b32 --- /dev/null +++ b/package-lock.yml @@ -0,0 +1,5 @@ +packages: + - name: dbt_utils + package: dbt-labs/dbt_utils + version: 1.4.1 +sha1_hash: e6424ba9e5a22487e47f023803aa4f0411946808 diff --git a/packages.yml b/packages.yml index bbb2357..fb5320a 100644 --- a/packages.yml +++ b/packages.yml @@ -1,5 +1,7 @@ -# TODO: Task 5 -- declare the dbt-labs/dbt_utils package here, then run + # `dbt deps` to install it. You need it for the compound uniqueness test # on the mart. See https://hub.getdbt.com/dbt-labs/dbt_utils/latest/ # for the package block syntax. -packages: [] +packages: + - package: dbt-labs/dbt_utils + version: [">=1.1.0", "<2.0.0"] diff --git a/profiles.yml.example b/profiles.yml.example index 82c6ae0..18dca60 100644 --- a/profiles.yml.example +++ b/profiles.yml.example @@ -3,14 +3,11 @@ nyc_taxi_borough_daily: outputs: dev: type: postgres - host: "{{ env_var('PG_HOST') }}" + host: hyf-data-pg.postgres.database.azure.com port: 5432 - user: "{{ env_var('PG_USER') }}" + user: hyfadmin password: "{{ env_var('PG_PASSWORD') }}" - dbname: "{{ env_var('PG_DBNAME', 'postgres') }}" - schema: "dev_" # TODO: replace with your first name (the schema you already own) - threads: 1 - -# Copy this file to profiles.yml (same directory), fill in your name, and ensure -# PG_HOST, PG_USER, PG_PASSWORD, and PG_DBNAME are set in your environment. -# profiles.yml is git-ignored — never commit it with a real password. + dbname: team1 + schema: dev_ + sslmode: require + threads: 4 diff --git a/reports/answers.md b/reports/answers.md index 8fb8cf2..e5df234 100644 --- a/reports/answers.md +++ b/reports/answers.md @@ -7,12 +7,22 @@ Queries run against `dev_.fct_daily_borough_stats`. **SQL:** ```sql --- TODO: query fct_daily_borough_stats grouped by pickup_borough, sum total_fare, order DESC +select + pickup_borough, + round(sum(total_fare)::numeric, 2) as dataset_total_fare_usd +from dev_halyna.fct_daily_borough_stats +group by pickup_borough +order by dataset_total_fare_usd desc +limit 1; ``` -**Result:** TODO +**Result:** -**Interpretation:** TODO (one sentence) +| pickup_borough | dataset_total_fare_usd | +| -------------- | ---------------------: | +| Manhattan | 493955.62 | + +**Interpretation:** Manhattan had the highest total fare in the loaded dataset, with total fare revenue of $493,955.62. --- @@ -21,12 +31,22 @@ Queries run against `dev_.fct_daily_borough_stats`. **SQL:** ```sql --- TODO: query fct_daily_borough_stats grouped by pickup_date, sum trip_count, order DESC LIMIT 1 +select + pickup_date, + sum(trip_count) as total_trips +from dev_halyna.fct_daily_borough_stats +group by pickup_date +order by total_trips desc +limit 1; ``` -**Result:** TODO +**Result:** + +| pickup_date | total_trips | +| ----------- | ----------: | +| 2024-01-17 | 2221 | -**Interpretation:** TODO (one sentence) +**Interpretation:** January 17, 2024 had the highest overall trip volume in the loaded dataset, with 2,221 trips across all pickup boroughs. --- @@ -35,12 +55,27 @@ Queries run against `dev_.fct_daily_borough_stats`. **SQL:** ```sql --- TODO: query fct_daily_borough_stats order by avg_tip_pct DESC LIMIT 5 +select + pickup_borough, + pickup_date, + trip_count, + round(avg_tip_pct::numeric, 4) as avg_tip_pct +from dev_halyna.fct_daily_borough_stats +order by avg_tip_pct desc nulls last +limit 5; ``` -**Result:** TODO +**Result:** -**Interpretation:** TODO — note whether any avg_tip_pct > 1 rows appear and what causes them +| pickup_borough | pickup_date | trip_count | avg_tip_pct | +| -------------- | ----------: | ---------: | ----------: | +| Unknown | 2024-01-30 | 4 | 2.5001 | +| Unknown | 2024-01-07 | 3 | 1.3396 | +| Unknown | 2024-01-11 | 8 | 1.1045 | +| Unknown | 2024-01-16 | 2 | 1.0000 | +| Unknown | 2024-01-18 | 3 | 0.6111 | + +**Interpretation:** The highest average tip percentage was 2.5001 for the Unknown borough on 2024-01-30. This is an outlier from a very small group of only 4 trips, so it should be treated as a data-quality or small-sample warning rather than a typical tipping pattern. --- @@ -49,9 +84,20 @@ Queries run against `dev_.fct_daily_borough_stats`. **SQL:** ```sql --- TODO: use percentile_cont(0.5) WITHIN GROUP (ORDER BY trip_count) filtered by borough +select + pickup_borough, + percentile_cont(0.5) within group (order by trip_count) as median_daily_trip_count +from dev_halyna.fct_daily_borough_stats +where pickup_borough in ('Manhattan', 'Brooklyn') +group by pickup_borough +order by pickup_borough; ``` -**Result:** TODO +**Result:** + +| pickup_borough | median_daily_trip_count | +|---|---:| +| Brooklyn | 248 | +| Manhattan | 1169.5 | -**Interpretation:** TODO (one sentence on the ratio) +**Interpretation:** Manhattan had a much higher median daily trip volume than Brooklyn: 1,169.5 trips per day compared with 248 trips per day. The `.5` value appears because `percentile_cont` calculates a continuous median and can interpolate between two middle daily counts. diff --git a/tests/assert_avg_tip_pct_within_bounds.sql b/tests/assert_avg_tip_pct_within_bounds.sql index b0fab32..5e94f58 100644 --- a/tests/assert_avg_tip_pct_within_bounds.sql +++ b/tests/assert_avg_tip_pct_within_bounds.sql @@ -13,7 +13,8 @@ -- -- The test passes (no WARN) when zero rows are returned; any returned rows are flagged. --- TODO: write the SELECT here. -- Query {{ ref('fct_daily_borough_stats') }} and return rows where avg_tip_pct > 1. -SELECT NULL AS pickup_borough, NULL AS pickup_date, NULL AS avg_tip_pct -WHERE FALSE -- TODO: replace with the real query +SELECT pickup_borough, pickup_date, avg_tip_pct +FROM {{ ref('fct_daily_borough_stats') }} +WHERE avg_tip_pct > 1 +