Skip to content
Learn Agentic AI
Learn Agentic AI11 min read4 views

Building a Public Transit Information Agent: Route Planning, Delays, and Accessibility

Build an AI agent that provides real-time public transit information including route planning, live delay updates, accessibility features, and multimodal trip suggestions for city residents.

Why Transit Agencies Need AI Agents

Public transit systems are information-heavy. A mid-size city might operate 40 bus routes, a light rail line, and paratransit services — each with different schedules for weekdays, weekends, and holidays. Add real-time delays, service alerts, detours, and accessibility concerns, and the information landscape becomes overwhelming for riders.

Existing transit apps show maps and schedules but cannot answer questions like "I use a wheelchair and need to get from downtown to the hospital by 2 PM — what are my options?" That requires combining schedule data, real-time vehicle positions, accessibility information, and trip planning logic into a conversational interface. This is exactly what an AI agent can do.

Understanding GTFS: The Transit Data Standard

Nearly every public transit agency publishes data in GTFS (General Transit Feed Specification) format. GTFS is a standardized set of CSV files that describe routes, stops, schedules, and geographic shapes. The agent needs to ingest and query this data.

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →
flowchart LR
    INPUT(["User intent"])
    PARSE["Parse plus<br/>classify"]
    PLAN["Plan and tool<br/>selection"]
    AGENT["Agent loop<br/>LLM plus tools"]
    GUARD{"Guardrails<br/>and policy"}
    EXEC["Execute and<br/>verify result"]
    OBS[("Trace and metrics")]
    OUT(["Outcome plus<br/>next action"])
    INPUT --> PARSE --> PLAN --> AGENT --> GUARD
    GUARD -->|Pass| EXEC --> OUT
    GUARD -->|Fail| AGENT
    AGENT --> OBS
    style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
    style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
    style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
    style OUT fill:#059669,stroke:#047857,color:#fff
import csv
from dataclasses import dataclass, field
from pathlib import Path

@dataclass
class Stop:
    stop_id: str
    stop_name: str
    latitude: float
    longitude: float
    wheelchair_accessible: bool = False
    shelter: bool = False

@dataclass
class Route:
    route_id: str
    route_name: str
    route_type: str  # bus, rail, ferry, etc.
    stops: list[Stop] = field(default_factory=list)

def load_stops(gtfs_path: Path) -> dict[str, Stop]:
    """Load stops from GTFS stops.txt file."""
    stops = {}
    with open(gtfs_path / "stops.txt", newline="") as f:
        reader = csv.DictReader(f)
        for row in reader:
            stops[row["stop_id"]] = Stop(
                stop_id=row["stop_id"],
                stop_name=row["stop_name"],
                latitude=float(row["stop_lat"]),
                longitude=float(row["stop_lon"]),
                wheelchair_accessible=row.get("wheelchair_boarding") == "1",
            )
    return stops

def load_routes(gtfs_path: Path) -> dict[str, Route]:
    """Load routes from GTFS routes.txt file."""
    route_types = {
        "0": "light_rail", "1": "subway", "2": "rail",
        "3": "bus", "4": "ferry", "5": "cable_car",
    }
    routes = {}
    with open(gtfs_path / "routes.txt", newline="") as f:
        reader = csv.DictReader(f)
        for row in reader:
            routes[row["route_id"]] = Route(
                route_id=row["route_id"],
                route_name=row.get("route_long_name", row.get("route_short_name", "")),
                route_type=route_types.get(row["route_type"], "bus"),
            )
    return routes

Real-Time Delay Integration

Static schedules are only half the picture. The agent must also consume GTFS-Realtime feeds that provide live vehicle positions and service alerts.

import httpx
from datetime import datetime, timedelta
from dataclasses import dataclass

@dataclass
class ServiceAlert:
    alert_id: str
    route_ids: list[str]
    header: str
    description: str
    severity: str  # info, warning, severe
    start_time: datetime | None = None
    end_time: datetime | None = None

@dataclass
class TripDelay:
    route_id: str
    trip_id: str
    stop_id: str
    delay_seconds: int
    timestamp: datetime

class RealtimeTransitClient:
    """Client for consuming GTFS-Realtime feeds."""

    def __init__(self, alerts_url: str, updates_url: str):
        self.alerts_url = alerts_url
        self.updates_url = updates_url
        self._alerts_cache: list[ServiceAlert] = []
        self._delays_cache: dict[str, TripDelay] = {}
        self._last_fetch: datetime | None = None

    async def fetch_alerts(self) -> list[ServiceAlert]:
        """Fetch current service alerts from the transit agency."""
        async with httpx.AsyncClient() as client:
            response = await client.get(self.alerts_url)
            data = response.json()

        alerts = []
        for entry in data.get("entity", []):
            alert_data = entry.get("alert", {})
            route_ids = [
                s["route_id"]
                for s in alert_data.get("informed_entity", [])
                if "route_id" in s
            ]
            alerts.append(ServiceAlert(
                alert_id=entry["id"],
                route_ids=route_ids,
                header=alert_data.get("header_text", {})
                    .get("translation", [{}])[0].get("text", ""),
                description=alert_data.get("description_text", {})
                    .get("translation", [{}])[0].get("text", ""),
                severity=alert_data.get("severity_level", "info"),
            ))

        self._alerts_cache = alerts
        return alerts

    def get_alerts_for_route(self, route_id: str) -> list[ServiceAlert]:
        """Get active alerts affecting a specific route."""
        return [a for a in self._alerts_cache if route_id in a.route_ids]

Building the Trip Planner

The trip planner combines static schedule data with real-time delays to suggest routes. Accessibility filtering is built in from the start, not bolted on as an afterthought.

@dataclass
class TripOption:
    departure_time: str
    arrival_time: str
    duration_minutes: int
    transfers: int
    routes_used: list[str]
    walking_minutes: int
    wheelchair_accessible: bool
    alerts: list[str]
    adjusted_for_delays: bool = False

def plan_trip(
    origin: str,
    destination: str,
    departure_after: datetime,
    require_accessible: bool = False,
    max_transfers: int = 2,
    routes: dict = None,
    stops: dict = None,
    realtime: RealtimeTransitClient = None,
) -> list[TripOption]:
    """Plan a transit trip with optional accessibility filtering."""

    # Step 1: Find nearest stops to origin and destination
    origin_stops = find_nearest_stops(origin, stops, radius_meters=400)
    dest_stops = find_nearest_stops(destination, stops, radius_meters=400)

    if require_accessible:
        origin_stops = [s for s in origin_stops if s.wheelchair_accessible]
        dest_stops = [s for s in dest_stops if s.wheelchair_accessible]

        if not origin_stops or not dest_stops:
            return []  # No accessible stops nearby

    # Step 2: Find connecting routes (simplified graph search)
    options = []
    for o_stop in origin_stops[:3]:
        for d_stop in dest_stops[:3]:
            route_options = find_routes_between(
                o_stop, d_stop, departure_after,
                max_transfers=max_transfers,
            )
            for route_opt in route_options:
                # Step 3: Apply real-time delays
                alerts = []
                if realtime:
                    for rid in route_opt["route_ids"]:
                        route_alerts = realtime.get_alerts_for_route(rid)
                        alerts.extend([a.header for a in route_alerts])

                options.append(TripOption(
                    departure_time=route_opt["depart"],
                    arrival_time=route_opt["arrive"],
                    duration_minutes=route_opt["duration"],
                    transfers=route_opt["transfers"],
                    routes_used=route_opt["route_ids"],
                    walking_minutes=route_opt["walk_min"],
                    wheelchair_accessible=route_opt["accessible"],
                    alerts=alerts,
                ))

    # Sort by arrival time
    options.sort(key=lambda x: x.arrival_time)
    return options[:5]

def find_nearest_stops(
    location: str, stops: dict, radius_meters: int = 400
) -> list[Stop]:
    """Find stops within walking distance of a location.
    In production, geocode the location string first."""
    # Placeholder: would use geocoding + haversine distance
    return list(stops.values())[:5]

def find_routes_between(origin, dest, depart_after, max_transfers=2):
    """Graph search through the transit network.
    In production, use a proper routing engine like OpenTripPlanner."""
    return []

Conversational Agent Layer

The conversational layer wraps the trip planner and real-time data into a natural dialogue.

from openai import OpenAI

client = OpenAI()

TRANSIT_AGENT_PROMPT = """You are a public transit information agent.
You help riders plan trips, check delays, and find accessible routes.

You have access to these tools:
- plan_trip(origin, destination, time, accessible): Plan a transit trip
- get_alerts(route_id): Get service alerts for a route
- find_stop(name): Find a transit stop by name

When a rider asks about accessibility, ALWAYS filter for wheelchair-accessible
stops and routes. Never suggest a route that includes an inaccessible stop
to a rider who needs accessibility.

When there are active delays or alerts on a suggested route, proactively
mention them — do not wait for the rider to ask.
"""

The accessibility-first design is essential for government services. The Americans with Disabilities Act requires that transit information systems provide equivalent access. Building accessibility filtering into the agent's core logic — rather than as an optional add-on — ensures compliance and serves all riders.

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

FAQ

How does the agent handle transit systems that do not publish GTFS data?

For agencies without GTFS feeds, the agent can ingest schedule data from other formats (spreadsheets, PDFs, or web scraping) and normalize it into the same internal data structures. The GTFS loader is just one ingest pathway. The trip planning and real-time layers work against the internal Stop, Route, and TripOption models regardless of the original data source. However, real-time delay information will be unavailable without a live feed, so the agent should clearly inform riders that arrival times are based on published schedules only.

How do you keep the schedule data current when agencies update routes seasonally?

Transit agencies typically publish new GTFS feeds quarterly or when service changes take effect. The agent runs an automated pipeline that checks the agency's GTFS feed URL on a daily schedule, downloads any updates, validates the data, and rebuilds the internal route graph. A version log tracks which GTFS feed is currently active. If a rider asks about a trip on a date that falls after an announced service change, the agent loads the future-effective schedule for planning.

Can the agent suggest multimodal trips combining transit with bikeshare or rideshare?

Yes. The agent extends the trip planner to include first-mile and last-mile options. If the nearest transit stop is more than a 10-minute walk, the agent checks bikeshare station availability via the GBFS (General Bikeshare Feed Specification) standard or suggests a rideshare connection. The trip option clearly labels each segment — "Walk 3 min to bikeshare station, ride 7 min to Metro station, take Blue Line 4 stops, walk 2 min to destination" — so the rider understands the full journey.


#GovernmentAI #PublicTransit #GTFS #RoutePlanning #Accessibility #AgenticAI #LearnAI #AIEngineering

Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.

Related Articles You May Like

AI Voice Agents

Voice Agent for Elderly & Accessibility: Designing for Everyone (2026)

Voice interfaces lift task completion 40%+ for users with motor impairments — but only if speech rate, pause budgets, and feedback patterns adapt. We map ADA-aligned UX and CallSphere's senior-friendly mode.

AI Strategy

Retail AI Voice & ADA Effective Communication in 2026

Title III lawsuits against retail digital channels hit a record in 2025. Here is the effective-communication, multi-modal, and consent stack a retail AI voice agent needs to ship in 2026.

AI Strategy

ADA Title III & AI Receptionists in Hospitality (2026)

ADA Title III requires automated-attendant systems to be accessible. Here is what hotels, restaurants, and resorts need to know about deploying AI voice receptionists without inviting a Title III lawsuit in 2026.

AI Strategy

WCAG 2.2 and ADA Title II for AI Voice Accessibility in 2026

DOJ's ADA Title II Web and Mobile Accessibility Rule reaches its first compliance deadline April 24, 2026. AI voice and chat platforms supporting public entities and healthcare must meet WCAG 2.1 AA — and WCAG 2.2 is the working baseline.

AI Voice Agents

WebRTC + AI Captioning for Live Church and Faith Services in 2026

Live faith services in 2026 ship multilingual AI captions over WebRTC to congregations spanning 100+ languages. Here is the production stack with on-prem ASR, accessible overlays, and donation flows.

Learn Agentic AI

Accessibility Auditing with GPT Vision: Automated WCAG Compliance Checking

Use GPT Vision to perform automated accessibility audits that detect visual WCAG violations including contrast issues, missing labels, touch target sizes, and reading order problems — generating actionable compliance reports.