diff --git a/AI_ASSIST.md b/AI_ASSIST.md index 8585a41..8a60bad 100644 --- a/AI_ASSIST.md +++ b/AI_ASSIST.md @@ -4,31 +4,102 @@ Document one place you used an LLM during this assignment. ## The problem - - -TODO +Generated side panel for streamlit dashboard using claude ## The prompt - +"""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 +## 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. +and also added a side panel that works +then gave extra information about flags that i might want to change ## Reflection - +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 + --- diff --git a/README.md b/README.md index 91de124..866a511 100644 --- a/README.md +++ b/README.md @@ -48,13 +48,10 @@ Fill in `week11-streamlit/metric_definitions.md`: a five-field definition (name, ## My submission - +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. diff --git a/docs/Metabase - NYC Taxi Analytics_ Bader.pdf b/docs/Metabase - NYC Taxi Analytics_ Bader.pdf new file mode 100644 index 0000000..0968110 Binary files /dev/null and b/docs/Metabase - NYC Taxi Analytics_ Bader.pdf differ diff --git a/docs/metabase screenshot.png b/docs/metabase screenshot.png new file mode 100644 index 0000000..bb81bab Binary files /dev/null and b/docs/metabase screenshot.png differ diff --git a/docs/streamlit screenshot.png b/docs/streamlit screenshot.png new file mode 100644 index 0000000..63c9d4d Binary files /dev/null and b/docs/streamlit screenshot.png differ diff --git a/metric_definitions.template.md b/metric_definitions.template.md deleted file mode 100644 index fc22c21..0000000 --- a/metric_definitions.template.md +++ /dev/null @@ -1,49 +0,0 @@ -# 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: TODO name - -- **Name**: TODO -- **Description**: TODO -- **Calculation**: TODO -- **Data source**: TODO -- **Refresh frequency**: TODO - -### Panel 2: TODO name - -- **Name**: TODO -- **Description**: TODO -- **Calculation**: TODO -- **Data source**: TODO -- **Refresh frequency**: TODO - -### Panel 3: TODO name - -- **Name**: TODO -- **Description**: TODO -- **Calculation**: TODO -- **Data source**: TODO -- **Refresh frequency**: TODO - -## Streamlit panels - - - -### Panel 1: TODO name - -- **Name**: TODO -- **Description**: TODO -- **Calculation**: TODO -- **Data source**: TODO -- **Refresh frequency**: TODO - - diff --git a/week11-streamlit/app.py b/week11-streamlit/app.py index cf06bcb..fd3368d 100644 --- a/week11-streamlit/app.py +++ b/week11-streamlit/app.py @@ -24,14 +24,84 @@ 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 @@ -39,7 +109,3 @@ def run_query(sql: str) -> pd.DataFrame: # 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." -) diff --git a/week11-streamlit/metric_definitions.md b/week11-streamlit/metric_definitions.md new file mode 100644 index 0000000..a0520e3 --- /dev/null +++ b/week11-streamlit/metric_definitions.md @@ -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`) \ No newline at end of file