@siva-sub/mcp-public-transport
Version:
A Model Context Protocol server for Singapore transport data with real-time information and routing
211 lines (210 loc) • 9.13 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TrafficConditionsTool = void 0;
const zod_1 = require("zod");
const base_js_1 = require("../base.js");
const validation_js_1 = require("../../utils/validation.js");
const logger_js_1 = require("../../utils/logger.js");
const TrafficConditionsInputSchema = zod_1.z.object({
area: zod_1.z.string().optional(),
road: zod_1.z.string().optional(),
includeIncidents: zod_1.z.boolean().default(true),
});
class TrafficConditionsTool extends base_js_1.BaseTool {
constructor(ltaService) {
super();
this.ltaService = ltaService;
}
getDefinitions() {
return [
{
name: 'get_traffic_conditions',
description: 'Get current traffic conditions, incidents, and road speeds',
inputSchema: this.createSchema({
area: {
type: 'string',
description: 'Specific area (e.g., "CBD", "Orchard", "Airport")',
},
road: {
type: 'string',
description: 'Specific road/expressway (e.g., "PIE", "CTE", "ECP")',
},
includeIncidents: {
type: 'boolean',
default: true,
description: 'Include traffic incidents in response',
},
}),
},
];
}
canHandle(toolName) {
return toolName === 'get_traffic_conditions';
}
async execute(toolName, args) {
try {
const { area, road, includeIncidents } = (0, validation_js_1.validateInput)(TrafficConditionsInputSchema, args);
logger_js_1.logger.info('Getting traffic conditions', { area, road, includeIncidents });
const incidents = includeIncidents ? await this.ltaService.getTrafficIncidents() : [];
// Filter incidents by area or road if specified
const filteredIncidents = incidents.filter(incident => {
if (area && !incident.message.toLowerCase().includes(area.toLowerCase())) {
return false;
}
if (road && !incident.message.toLowerCase().includes(road.toLowerCase())) {
return false;
}
return true;
});
const overallCondition = this.assessOverallCondition(filteredIncidents);
const majorIncidents = filteredIncidents.filter(incident => this.isMajorIncident(incident));
return {
overallCondition,
area: area || 'Island-wide',
road: road || 'All roads',
summary: this.createTrafficSummary(overallCondition, filteredIncidents.length),
majorIncidents: majorIncidents.map(incident => ({
type: incident.type,
message: incident.message,
coordinates: incident.coordinates,
severity: this.assessIncidentSeverity(incident),
estimatedDelay: this.estimateDelay(incident),
alternativeRoutes: this.suggestAlternativeRoutes(incident),
})),
allIncidents: filteredIncidents,
recommendations: this.generateTrafficRecommendations(overallCondition, majorIncidents),
expresswaySummary: this.getExpresswayStatus(filteredIncidents),
timestamp: new Date().toISOString(),
};
}
catch (error) {
logger_js_1.logger.error(`Traffic conditions tool failed: ${toolName}`, error);
return this.formatError(error, toolName);
}
}
assessOverallCondition(incidents) {
if (incidents.length === 0)
return 'smooth';
if (incidents.length <= 2)
return 'light';
if (incidents.length <= 5)
return 'moderate';
return 'heavy';
}
isMajorIncident(incident) {
const message = incident.message.toLowerCase();
return message.includes('accident') ||
message.includes('breakdown') ||
message.includes('road closure') ||
message.includes('flood');
}
assessIncidentSeverity(incident) {
const message = incident.message.toLowerCase();
if (message.includes('road closure') || message.includes('flood')) {
return 'severe';
}
if (message.includes('accident') || message.includes('breakdown')) {
return 'moderate';
}
return 'minor';
}
estimateDelay(incident) {
const severity = this.assessIncidentSeverity(incident);
const message = incident.message.toLowerCase();
if (severity === 'severe')
return '30+ minutes';
if (severity === 'moderate') {
if (message.includes('lane') && message.includes('blocked')) {
return '15-30 minutes';
}
return '10-20 minutes';
}
return '5-10 minutes';
}
suggestAlternativeRoutes(incident) {
const message = incident.message.toLowerCase();
const alternatives = [];
// Simple route suggestions based on common expressways
if (message.includes('pie')) {
alternatives.push('Consider ECP or CTE as alternatives');
}
else if (message.includes('ecp')) {
alternatives.push('Consider PIE or Marine Parade Road');
}
else if (message.includes('cte')) {
alternatives.push('Consider PIE or Thomson Road');
}
else if (message.includes('aye')) {
alternatives.push('Consider West Coast Highway or Clementi Road');
}
if (alternatives.length === 0) {
alternatives.push('Use alternative roads and local routes');
}
return alternatives;
}
generateTrafficRecommendations(condition, majorIncidents) {
const recommendations = [];
switch (condition) {
case 'smooth':
recommendations.push('Traffic is flowing smoothly across the network');
break;
case 'light':
recommendations.push('Light traffic conditions - good time to travel');
break;
case 'moderate':
recommendations.push('Moderate traffic with some congestion - allow extra time for your journey');
recommendations.push('Consider using public transport for city center destinations');
break;
case 'heavy':
recommendations.push('Heavy traffic conditions - significant delays expected');
recommendations.push('Strongly consider public transport or delay non-essential trips');
break;
}
if (majorIncidents.length > 0) {
recommendations.push(`${majorIncidents.length} major incident${majorIncidents.length > 1 ? 's' : ''} affecting traffic`);
recommendations.push('Check alternative routes before departing');
}
// Time-based recommendations
const hour = new Date().getHours();
if ((hour >= 7 && hour <= 9) || (hour >= 17 && hour <= 20)) {
recommendations.push('Peak hour traffic - expect longer journey times');
}
else if (hour >= 22 || hour <= 6) {
recommendations.push('Off-peak hours - generally lighter traffic');
}
return recommendations;
}
getExpresswayStatus(incidents) {
const expressways = ['PIE', 'ECP', 'CTE', 'AYE', 'BKE', 'KPE', 'SLE', 'TPE'];
const status = {};
expressways.forEach(expressway => {
const expresswayIncidents = incidents.filter(incident => incident.message.toLowerCase().includes(expressway.toLowerCase()));
status[expressway] = {
condition: expresswayIncidents.length === 0 ? 'clear' :
expresswayIncidents.length <= 1 ? 'light' : 'congested',
incidents: expresswayIncidents.length,
majorIssues: expresswayIncidents.filter(incident => this.isMajorIncident(incident)).length,
};
});
return status;
}
createTrafficSummary(condition, incidentCount) {
const conditionMap = {
smooth: 'Traffic is flowing smoothly',
light: 'Light traffic conditions',
moderate: 'Moderate traffic with some congestion',
heavy: 'Heavy traffic with significant delays',
};
const base = conditionMap[condition] || 'Traffic conditions unknown';
if (incidentCount === 0) {
return `${base} - no incidents reported`;
}
else if (incidentCount === 1) {
return `${base} - 1 incident reported`;
}
else {
return `${base} - ${incidentCount} incidents reported`;
}
}
}
exports.TrafficConditionsTool = TrafficConditionsTool;