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
99 changes: 85 additions & 14 deletions AI_ASSIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,102 @@ Document one place you used an LLM during this assignment.

## The problem

<!-- TODO: describe the specific problem you asked an LLM about.
Example: "My Streamlit KPI panel kept re-querying Postgres on every
sidebar interaction even though I wrapped run_query in @st.cache_data." -->

TODO
Generated side panel for streamlit dashboard using claude

## The prompt

<!-- TODO: paste the exact prompt you sent to the LLM. -->
"""NYC Taxi metrics dashboard — Week 11 assignment starter.
Reads the Week 10 dbt mart ``fct_trips`` from Azure Postgres. The page
setup and the ``run_query`` caching helper are already wired up (same
pattern taught in "Building a Metrics Dashboard"). Your job: implement the
three TODO-stubbed KPI queries below with your own SQL, then (Required tier)
add the hour-of-day trend, freshness panel, and payment-type filter
described in the assignment brief.
"""
import os
import pandas as pd
import sqlalchemy
import streamlit as st
from dotenv import load_dotenv
load_dotenv() # reads .env file if present
POSTGRES_URL = os.environ["POSTGRES_URL"]
DB_SCHEMA = os.environ.get("DB_SCHEMA", "dev_yourname")
st.set_page_config(page_title="NYC Taxi Metrics", layout="wide")
st.title("NYC Taxi Metrics")
def get_engine() -> sqlalchemy.engine.Engine:
return sqlalchemy.create_engine(POSTGRES_URL)
@st.cache_data(ttl=300)
def run_query(sql: str) -> pd.DataFrame:
with get_engine().connect() as conn:
return pd.read_sql(sql, conn)
st.subheader("Headline KPIs")
kpis = run_query(f"""
SELECT COUNT(*) AS trip_count,
AVG(fare_amount) AS avg_fare,
SUM(fare_amount) AS total_fare
FROM {DB_SCHEMA}.fct_trips
""").iloc[0]
col1, col2, col3 = st.columns(3)
col1.metric("Total trips", f"{int(kpis['trip_count']):,}")
col2.metric("Average fare", f"${kpis['avg_fare']:.2f}")
col3.metric("Total revenue", f"${kpis['total_fare']:,.0f}")
st.subheader("Trips by Hour of Day")
hour_df = run_query(f"""
select
extract(hour from pickup_datetime)::int as pickup_hour,
count(*) as trip_count
from {DB_SCHEMA}.fct_trips
{where_clause}
group by 1
order by 1
""")
if hour_df.empty:
st.info("No trips found for this filter.")
else:
hour_df = hour_df.set_index("pickup_hour").reindex(range(24), fill_value=0)
st.line_chart(hour_df["trip_count"])

TODO
st.subheader("Data freshness")
fresh = run_query(f"""
SELECT COUNT(*) AS row_count,
MAX(pickup_datetime) AS last_pickup
FROM {DB_SCHEMA}.fct_trips
""").iloc[0]
col1, col2 = st.columns(2)
col1.metric("Row count", f"{int(fresh['row_count']):,}")
col2.metric(
"Last pickup", str(fresh["last_pickup"])[:16] if fresh["last_pickup"] else "unknown"
)
# TODO: query total trip count, average trip_distance, and average
# fare_per_mile from {DB_SCHEMA}.fct_trips through run_query(), then
# render three tiles side by side with st.columns(3) and .metric().
# This is deliberately not the total-trips/avg-fare/total-revenue trio
# from the chapter: trip_distance and fare_per_mile are different columns,
# so copying the chapter's SQL verbatim will not answer this.

## The response
i want toAdd a sidebar
st.selectbox
to filter every panel by
payment_type_label
(not
pickup_borough
, which Building a Metrics Dashboard already covers).

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

TODO
## The response

LLM added alittle bit of fluff to the file with extra comments and lines
added every single todo in the file as it is following instructions from the code itself.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can ask the LLM to just display the answer and not implement anything, so you avoid it adding stuff everywhere unasked.

and also added a side panel that works
then gave extra information about flags that i might want to change

## Reflection

<!-- TODO: what did you change, keep, or discard after reviewing the LLM's answer?
Be specific: "I kept the cache_data suggestion but changed ttl from 60 to 300
to match the mart's once-a-day refresh cadence." -->
discarded most of what AI suggested most importantly all the fluff around it.
used the panel that AI suggested and it worked out of the box as all the information was mentioned inside the code.
kept some of the comments as they make the code look much nicer

TODO

---

Expand Down
9 changes: 3 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,10 @@ Fill in `week11-streamlit/metric_definitions.md`: a five-field definition (name,

## My submission

<!-- TODO: 1) Save your Metabase dashboard into the shared "Week 11 Submissions" collection.
2) Paste its link below, plus screenshots or a PDF export in this repo.
3) Paste your 5-minute presentation recording link (keep it PRIVATE). -->
1) Metabase dashboard : https://metabase-hyf.politepebble-abd3ebc2.westeurope.azurecontainerapps.io/dashboard/45-nyc-taxi-analytics-bader
2) PDF export and screenshots: In docs folder
3) Presentation recording: https://hyfstoragedev.blob.core.windows.net/student-submissions/week-11/mohamad-bader-almsaddi-alzin.mp4?sp=r&st=2026-07-16T19:27:11Z&se=2026-07-23T03:42:11Z&skoid=93f1bc1c-033f-4ed7-bcef-086bdff0302a&sktid=07a14c4e-d88c-42f7-83b3-13af7e57ff3d&skt=2026-07-16T19:27:11Z&ske=2026-07-23T03:42:11Z&sks=b&skv=2026-02-06&spr=https&sv=2026-02-06&sr=b&sig=KhzhGPWxB7ZdMpJI15KGTaf%2BrCGivOwB5wyfLzDojIA%3D.

- Metabase dashboard (in the **Week 11 Submissions** collection): TODO
- Screenshots / PDF export: TODO
- Presentation recording (private, hosted in the Azure `student-submissions` container): TODO

> ⚠️ **Keep the recording private.** It shows your screen and voice. Never make it public and never commit the `.mp4` (git history is forever). Check the frame for passwords, `.env` contents, or connection strings before uploading.

Expand Down
Binary file added docs/Metabase - NYC Taxi Analytics_ Bader.pdf
Binary file not shown.
Binary file added docs/metabase screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/streamlit screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 0 additions & 49 deletions metric_definitions.template.md

This file was deleted.

78 changes: 72 additions & 6 deletions week11-streamlit/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,88 @@
st.title("NYC Taxi Metrics")


def get_engine() -> sqlalchemy.engine.Engine:
return sqlalchemy.create_engine(POSTGRES_URL)


@st.cache_data(ttl=300)
def run_query(sql: str) -> pd.DataFrame:
engine = sqlalchemy.create_engine(POSTGRES_URL)
with engine.connect() as conn:
with get_engine().connect() as conn:
return pd.read_sql(sql, conn)

# Sidebar filter — applies to every panel below


# ---------------------------------------------------------------------------
payment_options = ["All"] + run_query(f"""
select distinct payment_type_label
from {DB_SCHEMA}.fct_trips
where payment_type_label is not null
order by payment_type_label
""")["payment_type_label"].tolist()

selected_payment = st.sidebar.selectbox("Payment type", payment_options)

where_clause = (
""
if selected_payment == "All"
else f"where payment_type_label = '{selected_payment}'"
)


st.subheader("Headline KPIs")
kpis = run_query(f"""
SELECT COUNT(*) AS trip_count,
AVG(fare_amount) AS avg_fare,
SUM(fare_amount) AS total_fare
FROM {DB_SCHEMA}.fct_trips
""").iloc[0]

col1, col2, col3 = st.columns(3)
col1.metric("Total trips", f"{int(kpis['trip_count']):,}")
col2.metric("Average fare", f"${kpis['avg_fare']:.2f}")
col3.metric("Total revenue", f"${kpis['total_fare']:,.0f}")

st.subheader("Trips by Hour of Day")

hour_df = run_query(f"""
select
extract(hour from pickup_datetime)::int as pickup_hour,
count(*) as trip_count
from {DB_SCHEMA}.fct_trips
{where_clause}
group by 1
order by 1
""")

if hour_df.empty:
st.info("No trips found for this filter.")
else:
hour_df = hour_df.set_index("pickup_hour").reindex(range(24), fill_value=0)
st.line_chart(hour_df["trip_count"])


# ---------------------------------------------------------------------------
# Freshness panel
# ---------------------------------------------------------------------------

st.subheader("Data freshness")
fresh = run_query(f"""
SELECT COUNT(*) AS row_count,
MAX(pickup_datetime) AS last_pickup
FROM {DB_SCHEMA}.fct_trips
""").iloc[0]

col1, col2 = st.columns(2)
col1.metric("Row count", f"{int(fresh['row_count']):,}")
col2.metric(
"Last pickup", str(fresh["last_pickup"])[:16] if fresh["last_pickup"] else "unknown"
)


# TODO: query total trip count, average trip_distance, and average
# fare_per_mile from {DB_SCHEMA}.fct_trips through run_query(), then
# render three tiles side by side with st.columns(3) and .metric().
# This is deliberately not the total-trips/avg-fare/total-revenue trio
# from the chapter: trip_distance and fare_per_mile are different columns,
# so copying the chapter's SQL verbatim will not answer this.
raise NotImplementedError(
"TODO: implement the headline KPIs panel (total trips, avg trip "
"distance, avg fare per mile) from fct_trips."
)
60 changes: 60 additions & 0 deletions week11-streamlit/metric_definitions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Metric definitions

Five fields per metric: Name, Description, Calculation, Data source, Refresh frequency. One block per panel. Copy this file to `metric_definitions.md` inside your `week11-streamlit/` folder and fill it in.

## Metabase panels


### Panel 1: Trip count

- **Name**: Trip count by payment type
- **Description**: bar chart to understand which payment methods are most popular among riders.
Y axis is trip count, X axis is payment type
- **Calculation**: `COUNT(*)` grouped by `payment_type_label` where `payment_type_label IS NOT NULL`
- **Data source**: dev_bader.fct_trips
- **Refresh frequency**: once per day

### Panel 2: Average fare

- **Name**: Average Fare per Mile by Dropoff Borough
- **Description**: Identify which destinations have the highest average fares per mile
X is dropoff borough and Y is average fare per mile
- **Calculation**: `AVG(fare_per_mile)` grouped by `dropoff_borough` where `dropoff_borough IS NOT NULL`
- **Data source**: dev_bader.fct_trips
- **Refresh frequency**: once per day

### Panel 3: Average trip duration

- **Name**: Average Trip Duration by hour of day
- **Description**: Track how the average trip length in minutes during hours of the day
X axis and Y are hours of the day, Line is the average trip duration in minutes
- **Calculation**: `AVG((dropoff_datetime - pickup_datetime) in minutes)` grouped by `EXTRACT(HOUR FROM pickup_datetime)`
- **Data source**: dev_bader.fct_trips
- **Refresh frequency**: once per day

## Streamlit panels


### Panel 1: Headline KPIs

- **Name**: Total Trips, Average Trip Distance, Average Fare per Mile
- **Description**: Count of trips, mean trip distance, and mean fare per mile
- **Calculation**: `count(*)`, `avg(trip_distance)`, `avg(fare_per_mile)`
- **Data source**: `dev_bader.fct_trips`
- **Refresh frequency**: 5 min cache (`ttl=300`)

### Panel 2: Trips by Hour of Day

- **Name**: Trip Count by Pickup Hour
- **Description**: Trip count grouped by hour of day
- **Calculation**: `count(*) group by extract(hour from pickup_datetime)`
- **Data source**: `dev_bader.fct_trips`
- **Refresh frequency**: 5 min cache (`ttl=300`)

### Panel 3: Data Freshness

- **Name**: Row Count, Latest Pickup Timestamp
- **Description**: Total rows and most recent pickup timestamp
- **Calculation**: `count(*)`, `max(pickup_datetime)`
- **Data source**: `dev_bader.fct_trips`
- **Refresh frequency**: 5 min cache (`ttl=300`)