{"record":{"id":"e6a041ec3d1dc88c","repo":"jackwener/OpenCLI","slug":"no-trains-found-from-fromstation-name-to-tost","errorCode":null,"errorMessage":"No trains found from ${fromStation.name} to ${toStation.name} on ${date}","messagePattern":"No trains found from (.+?) to (.+?) on (.+?)","errorType":"exception","errorClass":"EmptyResultError","httpStatus":null,"severity":"warning","filePath":"clis/12306/trains.js","lineNumber":145,"sourceCode":"        const date = validateDate(kwargs.date);\n        const limit = normalizeLimit(kwargs.limit, 50, MAX_LIMIT);\n\n        const stations = await fetchStationBundle();\n        const fromStation = resolveStation(stations, fromArg);\n        const toStation = resolveStation(stations, toArg);\n        if (fromStation.code === toStation.code) {\n            throw new ArgumentError(`<from> and <to> must differ; both resolved to ${fromStation.name} (${fromStation.code})`);\n        }\n        const stationByCode = new Map(stations.map((s) => [s.code, s]));\n\n        const cookieHeader = await mintSession();\n        const rawRows = await queryLeftTickets(cookieHeader, fromStation.code, toStation.code, date);\n        const decoded = rawRows\n            .map((line) => parseTrainRecord(decodeURIComponent(line.replace(/%0A/g, '')), stationByCode))\n            .filter(Boolean);\n\n        if (decoded.length === 0) {\n            throw new EmptyResultError(\n                `No trains found from ${fromStation.name} to ${toStation.name} on ${date}`,\n                'Try a different date or check whether the route is operated by 12306.',\n            );\n        }\n        return decoded.slice(0, limit);\n    },\n});\n\nexport const __test__ = { extractQueryEndpoint, queryLeftTickets };\n","sourceCodeStart":127,"sourceCodeEnd":155,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/12306/trains.js#L127-L155","documentation":"EmptyResultError thrown when the 12306 query succeeded but, after decoding and parsing every returned train row, zero valid records remained. This distinguishes a genuinely empty result (no trains for that route/date) from a protocol error, and suggests trying a different date or verifying the route exists.","triggerScenarios":"A query for a route/date where 12306 returns no train rows (or rows that all fail parseTrainRecord's `f.length < 33` check, so filter(Boolean) drops them): a date beyond the 15-day booking window, a route with no direct trains, a past date, or a schedule change leaving the response empty.","commonSituations":"Booking too far in advance (outside the pre-sale period); querying weekend-only or seasonal routes on non-operating days; querying a past date; typo'd but valid stations with no direct connection (e.g. small stations requiring transfer); 12306 schedule updates removing a service.","solutions":["Pick a date within the 12306 pre-sale window (typically 15 days ahead) and in the future.","Verify the route has direct service — try the major stations of each city (e.g. 北京 to 上海) to confirm the corridor has trains.","Try adjacent dates; some routes run only on certain weekdays or seasons.","If you expected trains but get none consistently, check whether parseTrainRecord is silently dropping rows (12306 may have changed the record field count); log rawRows length vs decoded length."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const days = (Date.parse(date + 'T00:00:00Z') - Date.now()) / 86400000;\nif (days < 0 || days > 15) {\n  console.warn(`date ${date} is outside the 12306 pre-sale window (0-15 days ahead); expect no trains`);\n}","typeGuard":"function isWithinPresaleWindow(dateStr) {\n  const t = Date.parse(dateStr + 'T00:00:00Z');\n  if (Number.isNaN(t)) return false;\n  const days = (t - Date.now()) / 86400000;\n  return days >= 0 && days <= 15;\n}","tryCatchPattern":"try {\n  rows = await trains({ from, to, date });\n} catch (e) {\n  if (e.name === 'EmptyResultError' && /No trains found/.test(e.message)) {\n    console.warn(e.message, '-', e.hint || 'try the city main stations or an adjacent date');\n    return []; // handle empty gracefully instead of crashing\n  }\n  throw e;\n}","preventionTips":["Query dates within the pre-sale window (roughly 15 days ahead) and in the future.","Use major city stations to confirm a corridor has service before narrowing to small stations.","Try adjacent dates for weekend-only or seasonal routes.","If consistently empty for a known-served route, suspect a parsing regression and compare rawRows vs decoded counts."],"tags":["empty-result","no-data","date-range","schedule"],"backgroundTag":"empty-result-set","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}