Files
bullish-skill/scripts/query.js
2026-04-30 18:18:56 +09:00

150 lines
6.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* Busan Airport Limousine Schedule Query Tool
*
* Usage:
* node query.js <stop-name> # to-airport + from-airport for that stop's line
* node query.js <stop-name> --to # to-airport only
* node query.js <stop-name> --from # from-airport only
* node query.js --from-airport --line 1 # all from-airport departures, line 1
* node query.js --from-airport --line 2 # all from-airport departures, line 2
*/
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DATA = join(__dirname, '../data/schedule.jsonl');
const trips = readFileSync(DATA, 'utf8')
.trim().split('\n')
.map(l => JSON.parse(l));
const args = process.argv.slice(2);
if (args.length === 0) {
console.error('Usage: node query.js <stop-name>');
console.error(' node query.js --from-airport --line 1|2');
process.exit(1);
}
const fromAirportFlag = args.includes('--from-airport');
const lineFlag = args.includes('--line') ? args[args.indexOf('--line') + 1] : null;
const toOnly = args.includes('--to');
const fromOnly = args.includes('--from');
const stopQuery = args.find(a => !a.startsWith('--') && a !== lineFlag) || null;
// ── Helper ──────────────────────────────────────────────────────────────────
function pad(s, n) {
return String(s).padEnd(n);
}
function header(title) {
console.log(`\n${title}`);
console.log('─'.repeat(title.length));
}
// ── From-airport by line (--from-airport --line N) ──────────────────────────
if (fromAirportFlag && lineFlag) {
const line = parseInt(lineFlag);
if (line !== 1 && line !== 2) {
console.error('--line must be 1 or 2'); process.exit(1);
}
// Line 1 = pages 34, Line 2 = pages 56
const pages = line === 1 ? [3, 4] : [5, 6];
const termLabels = { 3: 'Domestic', 4: 'International', 5: 'Domestic', 6: 'International' };
const dest = line === 1 ? 'Haeundae/Gijang' : 'Seomyeon/Bujeon';
header(`[From Airport] Line ${line}: Gimhae Airport → ${dest}`);
console.log(`${'Airport dep'.padEnd(14)} ${'Est. arrival'.padEnd(14)} Terminal`);
console.log(`${'─'.repeat(14)} ${'─'.repeat(14)} ─────────────`);
for (const page of pages) {
const pageTrips = trips.filter(t => t.page === page).sort((a, b) => {
const [ah, am] = a.departure.time.split(':').map(Number);
const [bh, bm] = b.departure.time.split(':').map(Number);
return (ah * 60 + am) - (bh * 60 + bm);
});
for (const t of pageTrips) {
console.log(`${pad(t.departure.time, 14)} ${pad(t.arrival.time, 14)} ${termLabels[page]}`);
}
}
console.log('\n(arrival times estimated — see arrival_note in schedule.jsonl)');
process.exit(0);
}
// ── Stop lookup ──────────────────────────────────────────────────────────────
if (!stopQuery) {
console.error('Provide a stop name or use --from-airport --line 1|2');
process.exit(1);
}
// Find all to-airport trips where the stop appears (partial match)
const matchedTrips = trips.filter(t =>
t.direction === 'to_airport' &&
t.all_stops.some(s => s.stop.includes(stopQuery))
);
if (matchedTrips.length === 0) {
console.error(`No stops found matching "${stopQuery}"`);
console.error('Line 1 stops: 반얀트리 아난티코브 오시리아 장산역 해운대온천사거리 해운대해수욕장 동백섬입구 한화리조트해운대 파크하얏트부산 요트경기장 벡스코 신세계센텀시티 상수도남부사업소');
console.error('Line 2 stops: 부전시장 부전역 서면/롯데호텔백화점 동의대역 주례역');
process.exit(1);
}
// Determine which line this stop belongs to
const line = matchedTrips[0].page === 1 ? 1 : 2;
const dest = line === 1 ? 'Haeundae/Gijang' : 'Seomyeon/Bujeon';
// Find the canonical matched stop name
const matchedStopName = matchedTrips[0].all_stops.find(s => s.stop.includes(stopQuery)).stop;
// ── To-airport section ───────────────────────────────────────────────────────
if (!fromOnly) {
const sorted = [...matchedTrips].sort((a, b) => {
const sa = a.all_stops.find(s => s.stop.includes(stopQuery));
const sb = b.all_stops.find(s => s.stop.includes(stopQuery));
const [ah, am] = sa.time.split(':').map(Number);
const [bh, bm] = sb.time.split(':').map(Number);
return (ah * 60 + am) - (bh * 60 + bm);
});
header(`[To Airport] Line ${line}: ${matchedStopName} → Gimhae Airport`);
console.log(`${'Dep'.padEnd(10)} ${'Airport arr (est)'.padEnd(18)}`);
console.log(`${'─'.repeat(10)} ${'─'.repeat(18)}`);
for (const t of sorted) {
const stopEntry = t.all_stops.find(s => s.stop.includes(stopQuery));
console.log(`${pad(stopEntry.time, 10)} ${t.arrival.time}`);
}
}
// ── From-airport section ─────────────────────────────────────────────────────
if (!toOnly) {
const pages = line === 1 ? [3, 4] : [5, 6];
const termLabels = { 3: 'Dom', 4: 'Int\'l', 5: 'Dom', 6: 'Int\'l' };
header(`[From Airport] Line ${line}: Gimhae Airport → ${dest}`);
console.log(`${'Airport dep'.padEnd(14)} ${'Est. arrival'.padEnd(14)} Terminal`);
console.log(`${'─'.repeat(14)} ${'─'.repeat(14)} ────────`);
const fromTrips = trips
.filter(t => pages.includes(t.page))
.sort((a, b) => {
const [ah, am] = a.departure.time.split(':').map(Number);
const [bh, bm] = b.departure.time.split(':').map(Number);
return (ah * 60 + am) - (bh * 60 + bm);
});
for (const t of fromTrips) {
console.log(`${pad(t.departure.time, 14)} ${pad(t.arrival.time, 14)} ${termLabels[t.page]}`);
}
console.log('\n(arrival times estimated)');
}