Public Source Viewer
비나래아카이브 개발자 포털
실제 서비스 구조를 살펴볼 수 있는 공개용 코드 뷰어입니다. 인증, 세션, 외부 연동, 토큰, 관리자 식별 등 보안상 민감한 구현은 파일 단위 또는 줄 단위로 검열됩니다.
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