Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .hyf/grader_lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,23 @@
# Source this at the top of test.sh:
# source "$(dirname "$0")/grader_lib.sh"
#
# Provides: pass(), fail(), warn(), print_results(), write_score(),
# Provides: pass(), fail(), warn(), blocker(), print_results(), write_score(),
# and a set of common static-analysis checks derived from recurring
# PR review patterns across cohort c55.
#
# blocker(): use for leaked-secret findings (a committed profiles.yml/.env,
# a hardcoded password/connection string). It behaves like fail() for the
# printed report, but also flips a flag that forces write_score() to report
# pass=false regardless of the earned point total -- a leaked secret must
# be fixed before the PR can pass, it cannot be "pointed around."

_grader_details=()
_grader_blocker=false

pass() { _grader_details+=("✓ PASS $1"); }
fail() { _grader_details+=("✗ FAIL $1"); }
warn() { _grader_details+=("⚠ WARN $1"); }
blocker() { _grader_details+=("🚫 BLOCKER $1"); _grader_blocker=true; }

print_results() {
local header="${1:-Autograder Results}"
Expand All @@ -28,6 +36,10 @@ write_score() {
local outfile="${3:-$(dirname "${BASH_SOURCE[0]}")/score.json}"
local pass_flag="false"
[[ "$score" -ge "$passing" ]] && pass_flag="true"
if [[ "$_grader_blocker" == true ]]; then
pass_flag="false"
echo "🚫 A blocker was found (leaked secret) -- forcing pass=false regardless of score." >&2
fi
cat > "$outfile" << JSON
{
"score": $score,
Expand Down
76 changes: 38 additions & 38 deletions .hyf/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,25 +63,25 @@ for f in "${required_files[@]}"; do
pass "found $f"
else
fail "missing required file: $f"
((missing += 1))
missing=$((missing + 1))
fi
done
if ls "$REPO_ROOT/tests/"*.sql &>/dev/null 2>&1; then
pass "found singular test in tests/"
else
fail "no .sql file found in tests/ -- add at least one singular test"
((missing += 1))
missing=$((missing + 1))
fi
if [[ -s "$REPO_ROOT/docs/lineage.png" ]]; then
pass "found docs/lineage.png"
else
fail "docs/lineage.png missing -- run dbt docs generate + serve and screenshot the lineage graph"
((missing += 1))
missing=$((missing + 1))
fi
if [[ "$missing" -eq 0 ]]; then
l1=10
fi
((score += l1))
score=$((score + l1))
pass "Level 1: required files ($l1/10 pts)"

# ── Level 2 (15 pts): secrets hygiene ───────────────────────────────────────
Expand All @@ -90,23 +90,23 @@ gi="$REPO_ROOT/.gitignore"
ex="$REPO_ROOT/profiles.yml.example"

if [[ -f "$gi" ]] && grep -qE "^profiles\.yml$" "$gi"; then
((l2 += 5)); pass ".gitignore: profiles.yml excluded"
l2=$((l2 + 5)); pass ".gitignore: profiles.yml excluded"
else
fail ".gitignore must contain 'profiles.yml' on its own line"
fi

if [[ -f "$ex" ]] && grep -qE "env_var\(" "$ex"; then
((l2 += 5)); pass "profiles.yml.example: uses env_var() -- no hardcoded password"
l2=$((l2 + 5)); pass "profiles.yml.example: uses env_var() -- no hardcoded password"
else
fail "profiles.yml.example must use env_var('PG_PASSWORD') instead of a hardcoded password"
fi

if [[ -f "$REPO_ROOT/profiles.yml" ]]; then
fail "profiles.yml is committed -- BLOCKER: run: git rm --cached profiles.yml"
blocker "profiles.yml is committed -- run: git rm --cached profiles.yml, then rotate the Postgres password since it was pushed"
else
((l2 += 5)); pass "profiles.yml not committed (correctly git-ignored)"
l2=$((l2 + 5)); pass "profiles.yml not committed (correctly git-ignored)"
fi
((score += l2))
score=$((score + l2))
pass "Level 2: secrets hygiene ($l2/15 pts)"

# ── Level 3 (20 pts): staging models ────────────────────────────────────────
Expand All @@ -115,79 +115,79 @@ st="$REPO_ROOT/models/staging/stg_trips.sql"
sz="$REPO_ROOT/models/staging/stg_zones.sql"

if file_is_filled "$st" && sqlgrep "\{\{[[:space:]]*source\(" "$st"; then
((l3 += 4)); pass "stg_trips.sql: uses {{ source() }} reference"
l3=$((l3 + 4)); pass "stg_trips.sql: uses {{ source() }} reference"
else
fail "stg_trips.sql: must use {{ source('nyc_taxi', 'raw_trips') }}"
fi

if file_is_filled "$st" && sqlgrep "pickup_location_id" "$st" && sqlgrep "IS NOT NULL|NOT NULL" "$st"; then
((l3 += 4)); pass "stg_trips.sql: filters NULL pickup_location_id"
l3=$((l3 + 4)); pass "stg_trips.sql: filters NULL pickup_location_id"
else
fail "stg_trips.sql: must filter WHERE pickup_location_id IS NOT NULL"
fi

if file_is_filled "$st" && sqlgrep "fare_amount" "$st" && sqlgrep ">=.*0|> -1" "$st"; then
((l3 += 4)); pass "stg_trips.sql: filters negative fares (fare_amount >= 0)"
l3=$((l3 + 4)); pass "stg_trips.sql: filters negative fares (fare_amount >= 0)"
else
fail "stg_trips.sql: must filter WHERE fare_amount >= 0"
fi

if file_is_filled "$st" && sqlgrep "tip_pct|safe_divide" "$st"; then
((l3 += 4)); pass "stg_trips.sql: tip_pct column present"
l3=$((l3 + 4)); pass "stg_trips.sql: tip_pct column present"
else
fail "stg_trips.sql: tip_pct column missing -- add {{ safe_divide('tip_amount', 'fare_amount') }} AS tip_pct"
fi

if file_is_filled "$sz" && sqlgrep "\{\{[[:space:]]*source\(" "$sz"; then
((l3 += 4)); pass "stg_zones.sql: uses {{ source() }} and is filled"
l3=$((l3 + 4)); pass "stg_zones.sql: uses {{ source() }} and is filled"
else
fail "stg_zones.sql: must use {{ source('nyc_taxi', 'raw_zones') }}"
fi
((score += l3))
score=$((score + l3))
pass "Level 3: staging models ($l3/20 pts)"

# ── Level 4 (20 pts): mart model ─────────────────────────────────────────────
l4=0
mart="$REPO_ROOT/models/marts/fct_daily_borough_stats.sql"

if file_is_filled "$mart"; then
((l4 += 2)); pass "fct_daily_borough_stats.sql: file filled"
l4=$((l4 + 2)); pass "fct_daily_borough_stats.sql: file filled"

if sqlgrep "\{\{[[:space:]]*ref\('stg_trips'\)" "$mart" && sqlgrep "\{\{[[:space:]]*ref\('stg_zones'\)" "$mart"; then
((l4 += 4)); pass "mart: ref() to both stg_trips and stg_zones"
l4=$((l4 + 4)); pass "mart: ref() to both stg_trips and stg_zones"
else
fail "mart: must reference both {{ ref('stg_trips') }} and {{ ref('stg_zones') }}"
fi

if sqlgrep "(INNER|LEFT)?[[:space:]]*JOIN" "$mart"; then
((l4 += 4)); pass "mart: JOIN to zones present"
l4=$((l4 + 4)); pass "mart: JOIN to zones present"
else
fail "mart: no JOIN -- must join stg_trips to stg_zones on pickup_location_id = location_id"
fi

if sqlgrep "GROUP[[:space:]]+BY" "$mart"; then
((l4 += 4)); pass "mart: GROUP BY present"
l4=$((l4 + 4)); pass "mart: GROUP BY present"
else
fail "mart: no GROUP BY -- must aggregate to (pickup_borough, pickup_date) grain"
fi

cols_ok=0
for col in pickup_borough pickup_date trip_count total_fare avg_tip_pct avg_trip_distance; do
if sqlgrep "$col" "$mart"; then
((cols_ok += 1))
cols_ok=$((cols_ok + 1))
else
fail "mart: required output column '$col' not found"
fi
done
if [[ "$cols_ok" -eq 6 ]]; then
((l4 += 6)); pass "mart: all 6 required output columns present"
l4=$((l4 + 6)); pass "mart: all 6 required output columns present"
elif [[ "$cols_ok" -ge 4 ]]; then
((l4 += 3)); warn "mart: $cols_ok/6 required columns present"
l4=$((l4 + 3)); warn "mart: $cols_ok/6 required columns present"
fi
else
fail "fct_daily_borough_stats.sql: still contains TODO stubs"
fi
((score += l4))
score=$((score + l4))
pass "Level 4: mart model ($l4/20 pts)"

# ── Level 5 (15 pts): tests ─────────────────────────────────────────────────
Expand All @@ -196,28 +196,28 @@ mart_yml="$REPO_ROOT/models/marts/_fct_daily_borough_stats.yml"
singular_test=$(ls "$REPO_ROOT/tests/"*.sql 2>/dev/null | head -1 || true)

if [[ -f "$mart_yml" ]] && grep -qiE "unique_combination_of_columns" "$mart_yml"; then
((l5 += 5)); pass "mart YAML: dbt_utils.unique_combination_of_columns test present"
l5=$((l5 + 5)); pass "mart YAML: dbt_utils.unique_combination_of_columns test present"
else
fail "mart YAML: missing dbt_utils.unique_combination_of_columns on (pickup_borough, pickup_date)"
fi

stg_yml="$REPO_ROOT/models/staging/_stg_trips.yml"
if [[ -f "$stg_yml" ]] && grep -qiE "not_null" "$stg_yml"; then
((l5 += 5)); pass "staging YAML: not_null tests present"
l5=$((l5 + 5)); pass "staging YAML: not_null tests present"
else
fail "staging YAML (_stg_trips.yml): not_null tests missing on key columns"
fi

if [[ -n "$singular_test" ]] && file_is_filled "$singular_test"; then
if sqlgrep "\{\{[[:space:]]*ref\('fct_daily_borough_stats'\)" "$singular_test"; then
((l5 += 5)); pass "singular test: filled and references fct_daily_borough_stats"
l5=$((l5 + 5)); pass "singular test: filled and references fct_daily_borough_stats"
else
((l5 += 3)); pass "singular test: filled (does not reference fct_daily_borough_stats -- check)"
l5=$((l5 + 3)); pass "singular test: filled (does not reference fct_daily_borough_stats -- check)"
fi
else
fail "singular test: empty or still a TODO stub"
fi
((score += l5))
score=$((score + l5))
pass "Level 5: tests ($l5/15 pts)"

# ── Level 6 (10 pts): documentation ─────────────────────────────────────────
Expand All @@ -226,7 +226,7 @@ mart_yml="$REPO_ROOT/models/marts/_fct_daily_borough_stats.yml"

if [[ -f "$mart_yml" ]] && grep -qiE "grain|one row per" "$mart_yml"; then
if ! grep -B5 -A5 "grain\|one row per" "$mart_yml" | grep -qiE "TODO"; then
((l6 += 5)); pass "mart YAML: grain stated in model description"
l6=$((l6 + 5)); pass "mart YAML: grain stated in model description"
else
fail "mart YAML: grain mentioned but description still has TODO -- fill it in"
fi
Expand All @@ -237,14 +237,14 @@ fi
if [[ -f "$mart_yml" ]]; then
filled_descs=$(grep "description:" "$mart_yml" | grep -v "TODO" | grep -cvE '^\s*description:\s*("")?\s*$' || true)
if [[ "$filled_descs" -ge 4 ]]; then
((l6 += 5)); pass "mart YAML: $filled_descs column descriptions filled"
l6=$((l6 + 5)); pass "mart YAML: $filled_descs column descriptions filled"
elif [[ "$filled_descs" -ge 2 ]]; then
((l6 += 3)); warn "mart YAML: only $filled_descs column descriptions filled (target: all 6)"
l6=$((l6 + 3)); warn "mart YAML: only $filled_descs column descriptions filled (target: all 6)"
else
fail "mart YAML: column descriptions are empty -- explain meaning and units for each column"
fi
fi
((score += l6))
score=$((score + l6))
pass "Level 6: documentation ($l6/10 pts)"

# ── Level 7 (10 pts): business answers + AI log ─────────────────────────────
Expand All @@ -255,11 +255,11 @@ ai="$REPO_ROOT/AI_ASSIST.md"
if file_is_filled "$ans"; then
sql_blocks=$(grep -c "^\`\`\`sql" "$ans" 2>/dev/null || echo 0)
if [[ "$sql_blocks" -ge 4 ]]; then
((l7 += 5)); pass "reports/answers.md: 4 SQL blocks present and filled"
l7=$((l7 + 5)); pass "reports/answers.md: 4 SQL blocks present and filled"
elif [[ "$sql_blocks" -ge 2 ]]; then
((l7 += 3)); warn "reports/answers.md: $sql_blocks SQL block(s) present (need 4)"
l7=$((l7 + 3)); warn "reports/answers.md: $sql_blocks SQL block(s) present (need 4)"
else
((l7 += 2)); warn "reports/answers.md: filled but SQL blocks missing"
l7=$((l7 + 2)); warn "reports/answers.md: filled but SQL blocks missing"
fi
else
fail "reports/answers.md: empty or still contains TODO stubs"
Expand All @@ -268,14 +268,14 @@ fi
if file_is_filled "$ai"; then
chars=$(wc -c < "$ai" | tr -d ' ')
if [[ "$chars" -ge 800 ]]; then
((l7 += 5)); pass "AI_ASSIST.md: filled (${chars} chars)"
l7=$((l7 + 5)); pass "AI_ASSIST.md: filled (${chars} chars)"
else
((l7 += 2)); warn "AI_ASSIST.md: present but brief (${chars} chars -- target 800+)"
l7=$((l7 + 2)); warn "AI_ASSIST.md: present but brief (${chars} chars -- target 800+)"
fi
else
fail "AI_ASSIST.md: empty or still contains TODO stubs"
fi
((score += l7))
score=$((score + l7))
pass "Level 7: business answers + AI log ($l7/10 pts)"

# ── Final ─────────────────────────────────────────────────────────────────────
Expand Down
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
1 change: 1 addition & 0 deletions .user.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
id: b7c01a20-043b-414e-9aec-8e45d62cec80
66 changes: 62 additions & 4 deletions AI_ASSIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,85 @@ Document one place you used an LLM during this assignment.
Example: "My safe_divide macro compiled but returned NULL for all rows even
when tip_amount and fare_amount were both non-zero." -->

TODO
dbt run --select +fct_daily_borough_stats
17:45:39  Running with dbt=1.11.12
17:45:39  Registered adapter: postgres=1.10.2
17:45:41  Found 1 test, 3 models, 2 sources, 476 macros
17:45:41  
17:45:41  Concurrency: 1 threads (target='dev')
17:45:41  
17:45:43  1 of 3 START sql view model dev_mareh.stg_trips ................................ [RUN]
17:45:45  1 of 3 OK created sql view model dev_mareh.stg_trips ........................... [CREATE VIEW in 1.34s]
17:45:45  2 of 3 START sql view model dev_mareh.stg_zones ................................ [RUN]
17:45:46  2 of 3 OK created sql view model dev_mareh.stg_zones ........................... [CREATE VIEW in 1.22s]
17:45:46  3 of 3 START sql table model dev_mareh.fct_daily_borough_stats ................. [RUN]
17:45:47  3 of 3 ERROR creating sql table model dev_mareh.fct_daily_borough_stats ........ [ERROR in 0.92s]
17:45:48  
17:45:48  Finished running 1 table model, 2 view models in 0 hours 0 minutes and 6.84 seconds (6.84s).
17:45:48  
17:45:48  Completed with 1 error, 0 partial successes, and 0 warnings:
17:45:48  
17:45:48  Failure in model fct_daily_borough_stats (models\marts\fct_daily_borough_stats.sql)
17:45:48    Database Error in model fct_daily_borough_stats (models\marts\fct_daily_borough_stats.sql)
  function avg(text) does not exist
  LINE 44:     avg(t.tip_pct) AS avg_tip_pct,
               ^
  HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
  compiled code at target\run\nyc_taxi_borough_daily\models\marts\fct_daily_borough_stats.sql
17:45:48  
17:45:48    compiled code at target\compiled\nyc_taxi_borough_daily\models\marts\fct_daily_borough_stats.sql
17:45:48  
17:45:48  Done. PASS=2 WARN=0 ERROR=1 SKIP=0 NO-OP=0 TOTAL=3
(c55-data-week-10) 

## The prompt

<!-- TODO: paste the exact prompt you sent to the LLM. -->

TODO
Why i get an error here and is that error have a problem with type of the column?

## The response

<!-- TODO: summarise or paste what the LLM returned. -->

TODO
Yes, your intuition is spot on! The error is 100% related to the data type of the column.
🔍 Breaking Down the Error
The key line in your error log is:

function avg(text) does not exist

In PostgreSQL, the avg() function calculates the mathematical average. Because of this, it expects a numeric data type (like integer, decimal, or numeric).

The error tells us that the tip_pct column is currently being treated as a text (string) data type. PostgreSQL is essentially saying: "I don't know how to calculate a mathematical average of text words or characters."

💡 Why is it text?
When we used the macro {{ safe_divide('tip_amount', 'fare_amount') }} in your stg_trips.sql model, dbt compiled that division in a way that PostgreSQL returned it as a generic or text-like format, instead of a strict numeric type.

🛠️ How to Fix It
Just like the database hint suggested (You might need to add explicit type casts), you need to explicitly tell PostgreSQL to convert (cast) that column into a number before trying to calculate the average.

Go to your fct_daily_borough_stats.sql file, look at line 44, and change this:

SQL
avg(t.tip_pct) AS avg_tip_pct,
To this (adding ::numeric inside the brackets):

SQL
avg(t.tip_pct::numeric) AS avg_tip_pct,
🚀 Try running it again!
Save your file (Ctrl + S) and run your command once more in the terminal:

Bash
dbt run --select +fct_daily_borough_stats
Let me know if it clears up and passes with that beautiful green success message!

## Reflection

<!-- TODO: what did you change, keep, or discard after reviewing the LLM's answer?
Be specific: "I kept the NULLIF suggestion but changed the column alias from
'ratio' to 'tip_pct' to match the assignment schema." -->

TODO
I tried the solution that llm gives me and it's work.

---

Expand Down
Binary file added docs/Lineage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion macros/safe_divide.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
-- 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
{{ numerator }} / NULLIF({{ denominator }}, 0)
{% endmacro %}
6 changes: 6 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def main():
print("Hello from c55-data-week-10!")


if __name__ == "__main__":
main()
Loading