Skip to content
Draft
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
88 changes: 84 additions & 4 deletions server/app.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Chalice application defining the Data Dashboard REST API endpoints."""

import json
import os
import subprocess
Expand Down Expand Up @@ -51,12 +53,28 @@


def parse_user_date(user_date: str):
"""Parse a user-provided date string in YYYY-MM-DD format into a date object.

Args:
user_date: str: Date string in "YYYY-MM-DD" format.

Returns:
date: A datetime.date object.
"""
date_split = user_date.split("-")
[year, month, day] = [int(x) for x in date_split[0:3]]
return date(year=year, month=month, day=day)


def mutlidict_to_dict(mutlidict):
"""Convert a Chalice MultiDict to a plain dict, preserving multiple values per key as lists.

Args:
mutlidict: A Chalice MultiDict from query parameters.

Returns:
dict: A dict mapping each key to a list of its values.
"""
res_dict = {}
for key in mutlidict.keys():
res_dict[key] = mutlidict.getlist(key)
Expand All @@ -65,6 +83,7 @@ def mutlidict_to_dict(mutlidict):

@app.route("/api/healthcheck", cors=cors_config, docs=Docs(response=models.HealthcheckResponse))
def healthcheck():
"""Run API health checks and return pass/fail status with details on any failures."""
# These functions must return True or False :-)
checks = {
"API Key Present": (lambda: len(config.MBTA_V3_API_KEY) > 0),
Expand Down Expand Up @@ -101,6 +120,15 @@ def healthcheck():

@app.route("/api/headways/{user_date}", cors=cors_config, docs=Docs(response=models.HeadwayResponse))
def headways_route(user_date):
"""Retrieve headway data for the given date and stop(s).

Args:
user_date: Date string in "YYYY-MM-DD" format.
Query params - stop: One or more stop IDs.

Returns:
Response: JSON response containing headway event data.
"""
stops = app.current_request.query_params.getlist("stop")
cache_max_age = cache.get_cache_max_age({"date": user_date})

Expand All @@ -120,6 +148,15 @@ def headways_route(user_date):

@app.route("/api/dwells/{user_date}", cors=cors_config, docs=Docs(response=models.DwellResponse))
def dwells_route(user_date):
"""Retrieve dwell time data for the given date and stop(s).

Args:
user_date: Date string in "YYYY-MM-DD" format.
Query params - stop: One or more stop IDs.

Returns:
Response: JSON response containing dwell time event data.
"""
stops = app.current_request.query_params.getlist("stop")
cache_max_age = cache.get_cache_max_age({"date": user_date})

Expand All @@ -139,6 +176,16 @@ def dwells_route(user_date):

@app.route("/api/traveltimes/{user_date}", cors=cors_config, docs=Docs(response=models.TravelTimeResponse))
def traveltime_route(user_date):
"""Retrieve travel time data between stop pairs for the given date.

Args:
user_date: Date string in "YYYY-MM-DD" format.
Query params - from_stop: One or more origin stop IDs.
Query params - to_stop: One or more destination stop IDs.

Returns:
Response: JSON response containing travel time event data.
"""
from_stops = app.current_request.query_params.getlist("from_stop")
to_stops = app.current_request.query_params.getlist("to_stop")
cache_max_age = cache.get_cache_max_age({"date": user_date})
Expand All @@ -161,6 +208,15 @@ def traveltime_route(user_date):

@app.route("/api/alerts/{user_date}", cors=cors_config, docs=Docs(response=models.AlertsRouteResponse))
def alerts_route(user_date):
"""Retrieve transit alerts for the given date and route(s).

Args:
user_date: Date string in "YYYY-MM-DD" format.
Query params - route: One or more route IDs.

Returns:
Response: JSON response containing alert data.
"""
query_params = mutlidict_to_dict(app.current_request.query_params)
cache_max_age = cache.get_cache_max_age({"date": user_date})

Expand All @@ -180,6 +236,7 @@ def alerts_route(user_date):

@app.route("/api/aggregate/traveltimes", cors=cors_config, docs=Docs(response=models.TravelTimeAggregateResponse))
def traveltime_aggregate_route():
"""Retrieve aggregated travel time data over a date range, grouped by date."""
query_params = app.current_request.query_params or {}
cache_max_age = cache.get_cache_max_age(query_params)

Expand All @@ -202,6 +259,7 @@ def traveltime_aggregate_route():

@app.route("/api/aggregate/traveltimes2", cors=cors_config, docs=Docs(response=models.TravelTimeAggregateResponse))
def traveltime_aggregate_route_2():
"""Retrieve aggregated travel time data with by-time-of-day and by-date breakdowns."""
query_params = app.current_request.query_params or {}
cache_max_age = cache.get_cache_max_age(query_params)

Expand All @@ -224,6 +282,7 @@ def traveltime_aggregate_route_2():

@app.route("/api/aggregate/headways", cors=cors_config, docs=Docs(response=models.HeadwaysAggregateResponse))
def headways_aggregate_route():
"""Retrieve aggregated headway data over a date range for the given stop(s)."""
query_params = app.current_request.query_params or {}
cache_max_age = cache.get_cache_max_age(query_params)

Expand All @@ -245,6 +304,7 @@ def headways_aggregate_route():

@app.route("/api/aggregate/dwells", cors=cors_config, docs=Docs(response=models.DwellsAggregateResponse))
def dwells_aggregate_route():
"""Retrieve aggregated dwell time data over a date range for the given stop(s)."""
query_params = app.current_request.query_params or {}
cache_max_age = cache.get_cache_max_age(query_params)

Expand All @@ -266,6 +326,7 @@ def dwells_aggregate_route():

@app.route("/api/git_id", cors=cors_config, docs=Docs(response=models.GitIdResponse))
def get_git_id():
"""Return the current git commit ID. Only available on localhost."""
# Only do this on localhost
if TM_FRONTEND_HOST == "localhost":
git_id = str(subprocess.check_output(["git", "describe", "--always", "--dirty", "--abbrev=10"]))[2:-3]
Expand All @@ -276,6 +337,7 @@ def get_git_id():

@app.route("/api/alerts", cors=cors_config, docs=Docs(response=models.AlertsRouteResponse))
def get_alerts():
"""Fetch current live alerts from the MBTA v3 API."""
data = mbta_v3.getAlerts(app.current_request.query_params)

return Response(
Expand All @@ -290,6 +352,7 @@ def get_alerts():
docs=Docs(request=models.AlertDelaysByLineParams, response=models.LineDelaysResponse),
)
def get_delays_by_line():
"""Retrieve alert-based delay data aggregated by line over a date range."""
query_params = app.current_request.query_params or {}
cache_max_age = cache.get_cache_max_age(query_params)

Expand All @@ -312,6 +375,7 @@ def get_delays_by_line():
docs=Docs(request=models.TripMetricsByLineParams, response=models.TripMetricsResponse),
)
def get_trips_by_line():
"""Retrieve trip metrics (speed, travel time, etc.) aggregated by line over a date range."""
query_params = app.current_request.query_params or {}
cache_max_age = cache.get_cache_max_age(query_params)

Expand All @@ -334,6 +398,7 @@ def get_trips_by_line():
docs=Docs(request=models.ScheduledServiceParams, response=models.GetScheduledServiceResponse),
)
def get_scheduled_service():
"""Retrieve scheduled service counts for a route over a date range."""
query_params = app.current_request.query_params or {}
cache_max_age = cache.get_cache_max_age(query_params)

Expand Down Expand Up @@ -363,6 +428,7 @@ def get_scheduled_service():
"/api/ridership", cors=cors_config, docs=Docs(request=models.RidershipParams, response=models.RidershipResponse)
)
def get_ridership():
"""Retrieve ridership data for a line over a date range."""
query_params = app.current_request.query_params or {}
cache_max_age = cache.get_cache_max_age(query_params)

Expand All @@ -388,6 +454,7 @@ def get_ridership():

@app.route("/api/facilities", cors=cors_config, docs=Docs(response=models.Facility))
def get_facilities():
"""Fetch facility data (elevators, escalators) from the MBTA v3 API."""
data = mbta_v3.getV3("facilities", app.current_request.query_params)

return Response(
Expand All @@ -402,6 +469,7 @@ def get_facilities():
docs=Docs(request=models.SpeedRestrictionsParams, response=models.SpeedRestrictionsResponse),
)
def get_speed_restrictions():
"""Retrieve speed restriction data for a line on a given date."""
query_params = app.current_request.query_params or {}
cache_max_age = cache.get_cache_max_age(query_params)

Expand Down Expand Up @@ -429,6 +497,7 @@ def get_speed_restrictions():
docs=Docs(request=models.ServiceHoursParams, response=models.ServiceHoursResponse),
)
def get_service_hours():
"""Retrieve delivered service hours for a line over a date range."""
query_params = app.current_request.query_params or {}
cache_max_age = cache.get_cache_max_age(query_params)

Expand Down Expand Up @@ -456,6 +525,7 @@ def get_service_hours():

@app.route("/api/time_predictions", cors=cors_config, docs=Docs(response=models.TimePredictionResponse))
def get_time_predictions():
"""Retrieve time prediction accuracy data for a route."""
query_params = app.current_request.query_params or {}

if config.BACKEND_SOURCE == "static":
Expand All @@ -480,6 +550,7 @@ def get_time_predictions():
docs=Docs(response=models.ServiceRidershipDashboardResponse),
)
def get_service_ridership_dashboard():
"""Retrieve combined service and ridership data for the overview dashboard."""
if config.BACKEND_SOURCE == "static":
data = static_data.get_service_ridership_dashboard()
elif config.BACKEND_SOURCE == "prod":
Expand All @@ -495,9 +566,13 @@ def get_service_ridership_dashboard():

@app.route("/api/routes", cors=cors_config, docs=Docs(response=models.RoutesResponse))
def get_routes():
"""
Get a manifest of all available routes supported in the dashboard.
"""Get a manifest of all available routes supported in the dashboard.
Returns route IDs grouped by category: rapid_transit, bus, commuter_rail, ferry.

Args:

Returns:

"""
data = route_manifest.get_all_routes_manifest()

Expand All @@ -509,9 +584,14 @@ def get_routes():

@app.route("/api/stops/{route_id}", cors=cors_config, docs=Docs(response=models.StopsResponse))
def get_stops(route_id):
"""
Get the stop information for a specific route.
"""Get the stop information for a specific route.
Returns the station/stop data including names, IDs, and directions.

Args:
route_id:

Returns:

"""
data = route_manifest.get_route_stops(route_id)

Expand Down
2 changes: 2 additions & 0 deletions server/bus/bus2train.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Convert raw bus event data into train-style event format for processing."""

import argparse
import pathlib
import pandas as pd
Expand Down
2 changes: 2 additions & 0 deletions server/bus/compare_manifest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Compare bus route manifests to detect changes in stop configurations."""

import json
import sys

Expand Down
2 changes: 2 additions & 0 deletions server/bus/gtfs_archive.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Download and process GTFS archives to compute scheduled headways for bus routes."""

import datetime
import pandas as pd
import pathlib
Expand Down
2 changes: 2 additions & 0 deletions server/bus/manifest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Generate bus route manifest JSON files from GTFS checkpoint data."""

import argparse
import pandas as pd
import json
Expand Down
2 changes: 2 additions & 0 deletions server/bus/stop_list.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Generate stop ID lists for bus routes from GTFS checkpoint data."""

import argparse
import json
import pandas as pd
Expand Down
Loading
Loading