Skip to main content
Glama
dynamicLineService.ts5.32 kB
import { fetchMTAData } from './mtaService.js'; export interface StationLineInfo { stationName: string; activeLines: string[]; lastUpdated: Date; } export class DynamicLineService { /** * Get currently active lines at a station based on real-time MTA data * No caching - relies on upstream MTA data caching for performance */ static async getActiveLinesAtStation(stationName: string): Promise<string[]> { const normalizedName = stationName.toLowerCase().trim(); try { // Get real-time data const mtaData = await fetchMTAData(); const activeLines = new Set<string>(); // Extract active lines from trip updates and vehicle positions mtaData.entity?.forEach((entity: any) => { if (entity.tripUpdate?.trip?.routeId) { const routeId = entity.tripUpdate.trip.routeId; // Check if this trip serves the requested station const stopUpdates = entity.tripUpdate.stopTimeUpdate || []; const servesStation = stopUpdates.some((update: any) => this.matchesStation(update.stopId, normalizedName) ); if (servesStation) { activeLines.add(routeId); } } if (entity.vehicle?.trip?.routeId) { const routeId = entity.vehicle.trip.routeId; // Vehicle position indicates active service activeLines.add(routeId); } }); const linesArray = Array.from(activeLines).sort(); return linesArray; } catch (error) { console.warn(`Failed to get dynamic lines for ${stationName}:`, error); // Final fallback to static data return this.getStaticLinesForStation(stationName); } } /** * Get active lines for multiple stations (for landmarks with multiple access points) */ static async getActiveLinesAtStations(stationNames: string[]): Promise<string[]> { const allLines = new Set<string>(); for (const station of stationNames) { const lines = await this.getActiveLinesAtStation(station); lines.forEach(line => allLines.add(line)); } return Array.from(allLines).sort(); } /** * Check if current time is weekend (affects service patterns) */ static isWeekend(): boolean { const now = new Date(); const day = now.getDay(); return day === 0 || day === 6; // Sunday = 0, Saturday = 6 } /** * Get service context (weekday/weekend, time of day) * Late night hours based on official MTA definition: midnight to 6:00 AM */ static getServiceContext(): { isWeekend: boolean; timeOfDay: string; serviceNote: string } { const now = new Date(); const hour = now.getHours(); const isWeekend = this.isWeekend(); let timeOfDay = 'daytime'; if (hour >= 0 && hour < 6) timeOfDay = 'late_night'; // MTA official: midnight to 6:00 AM else if (hour >= 6 && hour < 10) timeOfDay = 'morning_rush'; else if (hour >= 10 && hour < 16) timeOfDay = 'midday'; else if (hour >= 16 && hour < 20) timeOfDay = 'evening_rush'; else if (hour >= 20) timeOfDay = 'evening'; let serviceNote = ''; if (isWeekend) { serviceNote = '🗓️ Weekend service - some lines may have modified routes or reduced frequency'; } else if (timeOfDay === 'late_night') { serviceNote = '🌙 Late night service (midnight-6AM) - limited trains and modified routes'; } return { isWeekend, timeOfDay, serviceNote }; } /** * Match stop ID to station name (handles complex MTA stop ID system) */ private static matchesStation(stopId: string, targetStation: string): boolean { if (!stopId) return false; // MTA stop IDs are complex - try multiple matching strategies const stopIdLower = stopId.toLowerCase(); const target = targetStation.toLowerCase(); // Direct match if (stopIdLower.includes(target) || target.includes(stopIdLower)) { return true; } // Try matching against known station mappings // This would need to be expanded with actual MTA stop ID mappings const stationMappings = new Map([ ['times sq', ['times', '42', 'port authority']], ['union sq', ['union', '14']], ['grand central', ['grand', 'central', '42']], ['penn station', ['penn', '34']], ]); for (const [station, keywords] of stationMappings) { if (target.includes(station)) { return keywords.some(keyword => stopIdLower.includes(keyword)); } } return false; } /** * Fallback to static line data when real-time fails */ private static getStaticLinesForStation(stationName: string): string[] { const staticMappings = new Map([ ['times sq-42 st', ['1','2','3','7','N','Q','R','W','S']], ['42 st-port authority bus terminal', ['A','C','E']], ['14 st-union sq', ['4','5','6','L','N','Q','R','W']], ['grand central-42 st', ['4','5','6','7','S']], ['34 st-penn station', ['1','2','3','A','C','E']], ]); const normalized = stationName.toLowerCase(); for (const [station, lines] of staticMappings) { if (normalized.includes(station) || station.includes(normalized)) { return lines; } } return []; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sasabasara/where_is_my_train_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server