import regionsData from '../data/regions.json' import keywordsData from '../data/keywords.json' import curriculumsData from '../data/curriculums.json' export interface RegionInfo { fullName: string sido: string sigungu: string dong: string } export interface ParsedRouteParams { region: RegionInfo sido: string shortSido: string sigungu: string dong: string shortRegionName: string grade: string gradeShort: string subjectKey: string subjectName: string targetTerm: string fullKeyword: string isRegionOnly?: boolean } // 5대 핵심 과목 정의 export const SUBJECTS_MAP: Record = { korean: { key: 'korean', name: '국어', badge: '문해력·서술형', aliases: ['국어', '문학', '독서', '비문학', '화법'] }, english: { key: 'english', name: '영어', badge: '구문독해·내신', aliases: ['영어', '영문법', '영어독해', '공통영어'] }, math: { key: 'math', name: '수학', badge: '개념정립·킬러문항', aliases: ['수학', '수1', '수2', '미적분', '확통', '기하'] }, science: { key: 'science', name: '과학', badge: '통합과학·물화생지', aliases: ['과학', '통합과학', '물리', '화학', '생명과학', '지구과학', '과탐'] }, social: { key: 'social', name: '사회', badge: '통합사회·한국사', aliases: ['사회', '통합사회', '한국사', '사탐', '역사', '사회문화', '생활과윤리'] } } // 학년 정의 export const GRADES_LIST = [ { key: 'middle', name: '중학생', shortName: '중등', aliases: ['중학생', '중1', '중2', '중3', '중등'] }, { key: 'high', name: '고등학생', shortName: '고등', aliases: ['고등학생', '고1', '고2', '고3', '고등', '예비고1', 'N수', '재수'] }, { key: 'elem', name: '초등학생', shortName: '초등', aliases: ['초등학생', '초등', '초1', '초2', '초3', '초4', '초5', '초6'] } ] // 동 이름으로 지역 검색 export function getRegionByDong(dongQuery: string): RegionInfo | null { if (!dongQuery) return null const clean = dongQuery.trim().replace(/[동읍면리가]$/, '') const map = regionsData.dongMap as Record // 1. 정확 매칭 if (map[dongQuery] && map[dongQuery].length > 0) { return map[dongQuery][0] } // 2. '동', '읍', '면' 붙여서 매칭 for (const suffix of ['동', '읍', '면']) { if (map[clean + suffix] && map[clean + suffix].length > 0) { return map[clean + suffix][0] } } // 3. 리스트 전체 순회 매칭 const found = (regionsData.list as RegionInfo[]).find(r => r.dong === dongQuery || r.dong === (clean + '동') || r.dong === (clean + '읍') || r.dong === (clean + '면') || (clean.length >= 2 && r.dong.startsWith(clean)) ) return found || null } // 17개 시도 축약 매핑 및 헬퍼 함수 (예: 대구광역시 -> 대구, 서울특별시 -> 서울) export function getShortSido(sido: string): string { if (!sido) return '' const trimmed = sido.trim() if (trimmed.includes('서울')) return '서울' if (trimmed.includes('대구')) return '대구' if (trimmed.includes('부산')) return '부산' if (trimmed.includes('인천')) return '인천' if (trimmed.includes('광주')) return '광주' if (trimmed.includes('대전')) return '대전' if (trimmed.includes('울산')) return '울산' if (trimmed.includes('세종')) return '세종' if (trimmed.includes('경기')) return '경기' if (trimmed.includes('강원')) return '강원' if (trimmed.includes('충북') || trimmed.includes('충청북')) return '충북' if (trimmed.includes('충남') || trimmed.includes('충청남')) return '충남' if (trimmed.includes('전북') || trimmed.includes('전라북')) return '전북' if (trimmed.includes('전남') || trimmed.includes('전라남')) return '전남' if (trimmed.includes('경북') || trimmed.includes('경상북')) return '경북' if (trimmed.includes('경남') || trimmed.includes('경상남')) return '경남' if (trimmed.includes('제주')) return '제주' return trimmed.slice(0, 2) } // 축약 지역명 생성 (예: '대구 황금동', '서울 대치동', '경기 정발산동') export function getShortRegionName(sido: string, dong: string, sigungu?: string): string { const meta = regionsData as any const prefix = meta.keywordPrefix !== undefined ? meta.keywordPrefix : `${getShortSido(sido)} ` if (prefix === '') { // 서울 (예: 강남구 개포동) return `${sigungu ? sigungu + ' ' : ''}${dong || ''}`.trim() } // 경기, 인천, 광주, 부산, 대구, 대전 (예: 경기 수원 영통동, 대구 수성구 범어동) return `${prefix}${sigungu ? sigungu + ' ' : ''}${dong || ''}`.trim() } // 지능형 URL 슬러그 파서 export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams { let tokens: string[] = [] if (Array.isArray(slugInput)) { tokens = slugInput.flatMap(s => decodeURIComponent(s).split(/[-_\s/]+/)) } else if (typeof slugInput === 'string' && slugInput.trim()) { tokens = decodeURIComponent(slugInput).split(/[-_\s/]+/) } tokens = tokens.map(t => t.trim()).filter(Boolean) let matchedRegion: RegionInfo | null = null let detectedGrade = '중학생' let detectedGradeShort = '중등' let detectedSubjectKey = 'english' let detectedSubjectName = '영어' let detectedTerm = '과외' // 1. 토큰들 중 동 이름 매칭 for (const token of tokens) { const reg = getRegionByDong(token) if (reg) { matchedRegion = reg break } } // 2. 동 이름이 토큰에 없으면 전체 리스트에서 부분 일치 검색 if (!matchedRegion && tokens.length > 0) { for (const token of tokens) { const found = (regionsData.list as RegionInfo[]).find(r => token.includes(r.dong) || (token.length >= 2 && r.dong.includes(token))) if (found) { matchedRegion = found break } } } // 기본값 (타겟 지역 중 첫 번째 기준) if (!matchedRegion) { matchedRegion = (regionsData.list as RegionInfo[])[0] || { fullName: '대구광역시 수성구 범어동', sido: '대구광역시', sigungu: '수성구', dong: '범어동' } } // 3. 학년 추출 let hasExplicitGrade = false const fullText = tokens.join(' ') for (const g of GRADES_LIST) { if (g.aliases.some(a => fullText.includes(a))) { detectedGrade = g.name detectedGradeShort = g.shortName hasExplicitGrade = true break } } const gradeShort = detectedGradeShort || (detectedGrade === '초등학생' ? '초등' : detectedGrade === '고등학생' ? '고등' : '중등') // 4. 과목 추출 let hasExplicitSubject = false for (const [key, sub] of Object.entries(SUBJECTS_MAP)) { if (sub.aliases.some(a => fullText.includes(a))) { detectedSubjectKey = key detectedSubjectName = sub.name hasExplicitSubject = true break } } // 5. 목적/행동 추출 if (fullText.includes('내신')) detectedTerm = '내신대비' else if (fullText.includes('학원')) detectedTerm = '학원추천' else if (fullText.includes('수능')) detectedTerm = '수능대비' else if (fullText.includes('기출')) detectedTerm = '기출문제분석' else detectedTerm = '1:1 맞춤과외' const shortSido = getShortSido(matchedRegion.sido) const shortRegionName = getShortRegionName(matchedRegion.sido, matchedRegion.dong, matchedRegion.sigungu) const fullKeyword = `${shortRegionName} ${detectedGrade} ${detectedSubjectName} ${detectedTerm}` return { region: matchedRegion, sido: matchedRegion.sido, shortSido, sigungu: matchedRegion.sigungu, dong: matchedRegion.dong, shortRegionName, grade: detectedGrade, gradeShort, subjectKey: detectedSubjectKey, subjectName: detectedSubjectName, targetTerm: detectedTerm, fullKeyword, isRegionOnly: !hasExplicitGrade && !hasExplicitSubject } } export function getAllSubjectsList() { return Object.values(SUBJECTS_MAP) } export function getCurriculumBySubject(subjectKey: string) { const data = curriculumsData as any return data[subjectKey] || data['english'] } export function getPopularRegions(count = 20) { return (regionsData.list as RegionInfo[]).slice(0, count) } export function getTotalRegionsCount() { return regionsData.total }