본문으로 건너뛰기
Public Source Viewer

비나래아카이브 개발자 포털

실제 서비스 구조를 살펴볼 수 있는 공개용 코드 뷰어입니다. 인증, 세션, 외부 연동, 토큰, 관리자 식별 등 보안상 민감한 구현은 파일 단위 또는 줄 단위로 검열됩니다.

Redacted View
src/services/hinana-rail-reference.service.ts
공개 가능
1 import fs from 'fs';
2 import path from 'path';
3 import { CONFIG_DIR } from '../utils/paths';
4
5 interface Reference {
6 distanceSource: { url: string; date: string };
7 corridors: { line: string; kind: string; stations: { name: string; km: number }[] }[];
8 sources: { title: string; effective: string; url: string; group: string; trains: { number: string; line: string; stops: { station: string; time: string }[] }[] }[];
9 }
10 export interface RailLookupInput { date: string; trainNumber: string; trainType: string; departure: string; arrival: string; departureTime?: string }
11 let cached: Reference;
12 export function normalizeRailStation(value: string): string {
13 const name = value.trim().replace(/\s/g, '').replace(/역$/, '').replace(/\(.*\)/g, '');
14 return ({ 신경주: '경주', 평내호: '평내호평' } as Record<string,string>)[name] || (value.includes('김천') && value.includes('구미') ? '김천구미' : name);
15 }
16 export function lookupRailReference(input: RailLookupInput, reference?: Reference) {
17 if (!/^\d{4}-\d{2}-\d{2}$/.test(input.date) || !Number.isFinite(Date.parse(input.date)) || new Date(input.date).toISOString().slice(0,10) !== input.date || !/^\d{1,5}$/.test(input.trainNumber)) {
18 throw new Error('이용 날짜와 열차 번호를 확인해 주세요.');
19 }
20 const departure=normalizeRailStation(input.departure), arrival=normalizeRailStation(input.arrival);
21 if(!departure || !arrival || departure===arrival)throw new Error('출발역과 도착역을 확인해 주세요.');
22 if(!reference) {
23 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
24 reference=cached;
25 }
26 const group=input.trainType.startsWith('KTX')?'KTX':input.trainType==='ITX-청춘'?'ITX-청춘':input.trainType==='SRT'?'SRT':'general';
27 const sources=reference.sources.filter(s=>s.group===group && s.effective<=input.date);
28 const latest=sources.map(s=>s.effective).sort().pop();
29 const unavailable={ matched:false, message:'해당 날짜·열차·승차 구간을 공식 시간표에서 확인하지 못했습니다. 노선과 거리는 직접 입력할 수 있습니다.' };
30 if(!latest || Date.parse(input.date)-Date.parse(latest)>180*86400000)return unavailable;
31 const matches=[];
32 for(const source of sources.filter(s=>s.effective===latest))for(const train of source.trains) {
33 if(train.number!==String(Number(input.trainNumber)))continue;
34 const from=train.stops.findIndex(s=>normalizeRailStation(s.station)===departure);
35 const to=train.stops.findIndex(s=>normalizeRailStation(s.station)===arrival);
36 if(from<0 || to<=from || (input.departureTime && train.stops[from].time!==input.departureTime))continue;
37 const corridors=reference.corridors.filter(c=>c.line===train.line && (group==='KTX'?c.kind==='high-speed':c.kind===input.trainType));
38 const distances=[];
39 // Only use a published corridor that contains the complete ordered stopping pattern.
40 // Never substitute a straight-line or shortest-path estimate for the travelled distance.
41 for(const corridor of corridors) {
42 const indexes=train.stops.map(s=>corridor.stations.findIndex(p=>normalizeRailStation(p.name)===normalizeRailStation(s.station)));
43 if(indexes.some(n=>n<0))continue;
44 if(!indexes.every((n,i)=>i===0||n>indexes[i-1]) && !indexes.every((n,i)=>i===0||n<indexes[i-1]))continue;
45 distances.push(Math.round(Math.abs(corridor.stations[indexes[to]].km-corridor.stations[indexes[from]].km)*10)/10);
46 }
47 const unique=[...new Set(distances)];
48 matches.push({line:train.line,distance:unique.length===1?unique[0]:null,source});
49 }
50 if(!matches.length)return unavailable;
51 if(new Set(matches.map(m=>m.line)).size!==1)return {matched:false,message:'시간표에서 여러 노선이 확인됩니다. 출발 시간을 입력해 다시 조회해 주세요.'};
52 const distances=[...new Set(matches.map(m=>m.distance))];
53 const distance=distances.length===1?distances[0]:null;
54 const match=matches[0];
55 return {matched:true, draft:{line:match.line,distance}, source:{title:match.source.title,url:match.source.url,distanceDate:reference.distanceSource.date,distanceUrl:reference.distanceSource.url},
56 message:distance===null?'노선을 확인했습니다. 해당 경유 구간의 거리는 자료에서 확인되지 않았습니다.':`승차 거리 ${distance} km · 거리표 ${reference.distanceSource.date} 기준. 경유 구간과 운행일을 확인해 주세요.`};
57 }
58