import { ApiError } from './errors.mjs'; import { coordinates } from './coordinates.mjs'; const routeNumber = value => typeof value === 'string' ? value.replace(/[\s№]/g, '').toLocaleUpperCase('ru') : ''; const positive = value => Number.isSafeInteger(Number(value)) && Number(value) > 0; export function normalizeAlerts(raw, cityId) { if (!Array.isArray(raw)) throw new ApiError(502); const seen = new Set(); return raw.flatMap(alert => { if (!alert || !positive(alert.id)) throw new ApiError(502); if (alert.is_active === false || (alert.city_id != null && Number(alert.city_id) !== cityId) || seen.has(Number(alert.id))) return []; if ((alert.route_numbers != null && (!Array.isArray(alert.route_numbers) || alert.route_numbers.some(value => !routeNumber(value)))) || (alert.route_ids != null && (!Array.isArray(alert.route_ids) || alert.route_ids.some(value => !positive(value))))) throw new ApiError(502); const numbers = Array.isArray(alert.route_numbers) ? alert.route_numbers.map(routeNumber).filter(Boolean) : []; const ids = Array.isArray(alert.route_ids) ? alert.route_ids.filter(positive).map(Number) : []; if (!numbers.length && routeNumber(alert.route_number)) numbers.push(routeNumber(alert.route_number)); if (!ids.length && positive(alert.route_id)) ids.push(Number(alert.route_id)); const startTime = Date.parse(alert.start_time); const endTime = alert.end_time == null ? null : Date.parse(alert.end_time); if (!Number.isFinite(startTime) || (endTime !== null && !Number.isFinite(endTime)) || typeof alert.title !== 'string' || typeof alert.message !== 'string') throw new ApiError(502); seen.add(Number(alert.id)); return [{ id: Number(alert.id), cityId, title: alert.title.trim(), message: alert.message.trim(), type: ['cancellation', 'delay', 'detour'].includes(alert.alert_type) ? alert.alert_type : 'info', routeNumbers: [...new Set(numbers)].sort((a, b) => a.localeCompare(b, 'ru', { numeric: true })), routeIds: [...new Set(ids)], isCityWide: !numbers.length && !ids.length, startTime: new Date(startTime).toISOString(), endTime: endTime === null ? null : new Date(endTime).toISOString() }]; }); } export function normalizeNearby(raw) { if (!Array.isArray(raw)) throw new ApiError(502); return raw.map(stop => { const distance = Number(stop?.distance_meters); if (!stop || !positive(stop.id) || typeof stop.name !== 'string' || !stop.name.trim() || stop.distance_meters == null || !Number.isFinite(distance) || distance < 0) throw new ApiError(502); return { id: Number(stop.id), name: stop.name.trim(), direction: typeof stop.description === 'string' ? stop.description.trim() : '', routesCount: Number(stop.routes_count) || 0, distance: Math.round(distance), ...coordinates(stop.latitude, stop.longitude) }; }).sort((a, b) => a.distance - b.distance); }