diff --git a/week11-streamlit/app.py b/week11-streamlit/app.py index cf06bcb..d774fb4 100644 --- a/week11-streamlit/app.py +++ b/week11-streamlit/app.py @@ -31,15 +31,120 @@ def run_query(sql: str) -> pd.DataFrame: return pd.read_sql(sql, conn) +payment_options_query = f""" +SELECT DISTINCT + CASE + WHEN payment_type = 1 THEN 'Credit card' + WHEN payment_type = 2 THEN 'Cash' + WHEN payment_type = 3 THEN 'No charge' + WHEN payment_type = 4 THEN 'Dispute' + WHEN payment_type = 5 THEN 'Unknown' + WHEN payment_type = 6 THEN 'Voided trip' + WHEN payment_type IS NULL THEN 'Not Recorded' + ELSE 'Other' + END AS payment_type_label +FROM {DB_SCHEMA}.vw_fact_trips; +""" + +payment_df = run_query(payment_options_query) +payment_options = ["All"] + sorted( + [val for val in payment_df["payment_type_label"].unique() if val is not None] +) +selected_payment = st.sidebar.selectbox("Filter by Payment Type", payment_options) + st.subheader("Headline KPIs") +kpi_query = f""" +WITH base_trips AS ( + SELECT + *, + CASE + WHEN payment_type = 1 THEN 'Credit card' + WHEN payment_type = 2 THEN 'Cash' + WHEN payment_type = 3 THEN 'No charge' + WHEN payment_type = 4 THEN 'Dispute' + WHEN payment_type = 5 THEN 'Unknown' + WHEN payment_type = 6 THEN 'Voided trip' + WHEN payment_type IS NULL THEN 'Not Recorded' + ELSE 'Other' + END AS payment_type_label + FROM {DB_SCHEMA}.vw_fact_trips +) +SELECT + COUNT(*) AS total_trips, + AVG(trip_distance) AS avg_trip_distance, + AVG( + CASE + WHEN trip_distance > 0 THEN fare_amount / trip_distance + ELSE 0 + END + ) AS avg_fare_per_mile +FROM base_trips +WHERE 1=1 +""" + +if selected_payment != "All": + kpi_query += f" AND payment_type_label = '{selected_payment}'" + +kpi_df = run_query(kpi_query) +total_trips = kpi_df["total_trips"].iloc[0] or 0 +avg_trip_distance = kpi_df["avg_trip_distance"].iloc[0] or 0 +avg_fare_per_mile = kpi_df["avg_fare_per_mile"].iloc[0] or 0 + +col1, col2, col3 = st.columns(3) +with col1: + st.metric(label="Total Trips", value=f"{total_trips:,}") +with col2: + st.metric(label="Avg Trip Distance", value=f"{avg_trip_distance:.2f} miles") +with col3: + st.metric(label="Avg Fare per Mile", value=f"${avg_fare_per_mile:.2f}") -# 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." +st.subheader("Trip Count by Hour of Day") + +hour_query = f""" +WITH base_trips AS ( + SELECT + *, + CASE + WHEN payment_type = 1 THEN 'Credit card' + WHEN payment_type = 2 THEN 'Cash' + WHEN payment_type = 3 THEN 'No charge' + WHEN payment_type = 4 THEN 'Dispute' + WHEN payment_type = 5 THEN 'Unknown' + WHEN payment_type = 6 THEN 'Voided trip' + WHEN payment_type IS NULL THEN 'Not Recorded' + ELSE 'Other' + END AS payment_type_label + FROM {DB_SCHEMA}.vw_fact_trips ) +SELECT + EXTRACT(HOUR FROM pickup_datetime) AS hour_of_day, + COUNT(*) AS trip_count +FROM base_trips +WHERE 1=1 +""" + +if selected_payment != "All": + hour_query += f" AND payment_type_label = '{selected_payment}'" + +hour_query += " GROUP BY 1 ORDER BY 1;" + +hour_df = run_query(hour_query) + +st.line_chart(data=hour_df.set_index("hour_of_day"), y="trip_count") + +st.subheader("Data Freshness Status") +freshness_query = f""" +SELECT + COUNT(*) AS total_rows, + MAX(pickup_datetime) AS latest_pickup +FROM {DB_SCHEMA}.vw_fact_trips; +""" +fresh_df = run_query(freshness_query) +total_rows = fresh_df["total_rows"].iloc[0] or 0 +latest_pickup = fresh_df["latest_pickup"].iloc[0] + +col_f1, col_f2 = st.columns(2) +with col_f1: + st.metric(label="Total Database Rows", value=f"{total_rows:,}") +with col_f2: + st.metric(label="Latest Pickup Datetime", value=str(latest_pickup)) diff --git a/week11-streamlit/metric_definitions.md b/week11-streamlit/metric_definitions.md new file mode 100644 index 0000000..58f948b --- /dev/null +++ b/week11-streamlit/metric_definitions.md @@ -0,0 +1,66 @@ +# 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 by Payment Type + +- **Name**: Trip Count by Payment Type +- **Description**: Displays the total number of taxi trips grouped by their payment method (e.g., Credit card, Cash, Dispute, etc.) to understand customer payment preferences. Missing or null values are classified explicitly as "Not Recorded" or "Unknown". +- **Calculation**: $$\text{Trip Count} = \text{COUNT}(*)$$Categorized using a CASE WHEN statement mapping payment_type codes (1 to 6) to their official descriptions, grouping by this category, and sorting in descending order. +- **Data source**: dev_mareh.vw_fact_trips +- **Refresh frequency**: Daily (or upon ETL pipeline execution) + +### Panel 2: Average Fare per Mile by Dropoff Borough + +- **Name**: Average Fare per Mile by Dropoff Borough +- **Description**: Visualizes the average cost of a taxi ride per mile based on where passengers are dropped off (Dropoff Borough). This helps identify which destinations yield the highest fare rates relative to the distance traveled. +- **Calculation**: $$\text{Average Fare per Mile} = \text{AVG}\left(\frac{\text{fare\_amount}}{\text{trip\_distance}}\right)$$Note: A conditional check ($\text{trip\_distance} > 0$) is implemented to prevent division-by-zero errors. +- **Data source**: Joined tables dev_mareh.vw_fact_trips (fact) and dev_mareh.vw_dim_zones (dimension) on location IDs. +- **Refresh frequency**: Daily (or upon ETL pipeline execution) + +### Panel 3: Average Trip Duration by Hour of Day + +- **Name**: Average Trip Duration by Hour of Day +- **Description**: A line chart illustrating how the average duration of taxi trips (in minutes) fluctuates across different hours of the day (from 0 to 23). This highlights peak traffic congestion hours and off-peak travel times. +- **Calculation**: $$\text{Trip Duration (Minutes)} = \frac{\text{EXTRACT}(\text{EPOCH FROM } (\text{dropoff\_datetime} - \text{pickup\_datetime}))}{60}$$ +$$\text{Average Trip Duration} = \text{AVG}(\text{Trip Duration})$$Grouped by $\text{EXTRACT}(\text{HOUR FROM } \text{pickup\_datetime})$ and sorted chronologically. +- **Data source**: dev_mareh.vw_fact_trips +- **Refresh frequency**: Daily (or upon ETL pipeline execution) + +## Streamlit panels + +## Streamlit panels + +### Panel 1: Headline KPIs + +- **Name**: Headline KPIs (Total Trips, Avg Trip Distance, Avg Fare per Mile) +- **Description**: Shows three main numbers to understand how many rides happened, how long they were on average, and the average cost for each mile. +- **Calculation**: + - `total_trips`: `COUNT(*)` (Total number of rides). + - `avg_trip_distance`: `AVG(trip_distance)` (Average distance of a single ride, measured in miles). + - `avg_fare_per_mile`: `AVG(CASE WHEN trip_distance > 0 THEN fare_amount / trip_distance ELSE 0 END)` (Calculates the cost per mile for each ride first, then finds the average of those rates. It safely ignores rides with zero distance). +- **Data source**: `dev_mareh.vw_fact_trips` +- **Refresh frequency**: Daily / Whenever the database is updated. + +### Panel 2: Trip Count by Hour of Day + +- **Name**: Hourly Trip Distribution +- **Description**: A line chart that shows the number of rides for every hour of the day (from 0 to 23) to see when the busiest times are. +- **Calculation**: `COUNT(*)` grouped by the hour of the ride start time using `EXTRACT(HOUR FROM pickup_datetime)`. +- **Data source**: `dev_mareh.vw_fact_trips` +- **Refresh frequency**: Daily / Whenever the database is updated. + +### Panel 3: Data Freshness Status + +- **Name**: Data Freshness and Row Count +- **Description**: Shows the total number of rows in the database and the time of the very last ride to make sure our data is up-to-date and not missing anything. +- **Calculation**: + - `total_rows`: `COUNT(*)` (Total rows in the table). + - `latest_pickup`: `MAX(pickup_datetime)` (The timestamp of the newest ride recorded). +- **Data source**: `dev_mareh.vw_fact_trips` +- **Refresh frequency**: Daily / Whenever the database is updated. \ No newline at end of file