/** * Founding Year Extraction Guard — Harness 2 * * Regex-based founding year extraction to complement Gemini Vision. * Acts as a fallback when the LLM misses patterns like "2004년개원 이래". * * Defense layers: * 1. Gemini Vision (primary) * 2. This module on scraped text (secondary) * 3. generate-report post-processing on all channel_data text (tertiary) */ // ─── Patterns ─── /** * Regex patterns for founding year detection. * Each captures a numeric group that is either: * - A 4-digit year (1950–currentYear) * - A 1-2 digit number to subtract from currentYear */ const FOUNDING_PATTERNS: RegExp[] = [ // Direct year expressions /(\d{4})년\s*개원/, // "2004년개원", "2004년 개원" /(\d{4})년개원\s*이래/, // "2004년개원 이래" ← 그랜드 패턴 /개원\s*(\d{4})년/, // "개원 2004년" /설립\s*(\d{4})년/, // "설립 2004년" /(\d{4})년\s*설립/, // "2004년 설립" /since\s*(\d{4})/i, // "SINCE 2004" /established\s*(?:in\s*)?(\d{4})/i, // "Established in 2004" /(\d{4})년\s*오픈/, // "2004년 오픈" /(\d{4})년\s*개업/, // "2004년 개업" /개원일?\s*:\s*(\d{4})/, // "개원: 2004", "개원일: 2004" // Anniversary / relative year expressions /(\d{1,2})주년/, // "22주년" → currentYear-22 /(\d{1,2})년\s*전통/, // "20년 전통" → currentYear-20 /(\d{1,2})년\s*동안/, // "22년 동안" → currentYear-22 /개원\s*(\d{1,2})주년/, // "개원 15주년" → currentYear-15 /(\d{1,2})년\s*역사/, // "20년 역사" → currentYear-20 ]; // ─── Extraction Function ─── /** * Extract founding year from arbitrary text. * Returns a 4-digit year or null if no pattern matches. * * @param text - Any text (HTML markdown, scraped content, vision output) * @param currentYear - For relative calculations (default: current year) */ export function extractFoundingYear( text: string, currentYear: number = new Date().getFullYear(), ): number | null { for (const pattern of FOUNDING_PATTERNS) { const match = text.match(pattern); if (!match) continue; const num = parseInt(match[1], 10); // 4-digit: direct year if (num >= 1950 && num <= currentYear) return num; // 1-2 digit: years ago → subtract from currentYear if (num >= 1 && num <= 80) return currentYear - num; } return null; } // ─── Test Corpus ─── export const FOUNDING_YEAR_TEST_CORPUS: ReadonlyArray = [ ["2004년개원 이래 중국, 베트남 환자들이 찾아오고 있습니다", 2004], ["SINCE 2004", 2004], ["22주년 기념 이벤트", 2004], // 2026-22 ["개원 15주년을 맞이하여", 2011], // 2026-15 ["20년 전통의 성형외과", 2006], // 2026-20 ["2005년 설립된 뷰성형외과", 2005], ["설립 2010년, 강남에 위치한", 2010], ["Established in 2003", 2003], ["2018년 오픈한 신규 클리닉", 2018], ["개원: 2015", 2015], ["아무 관련 없는 텍스트입니다", null], ["전화번호 02-1234-5678", null], ]; /** * Self-test: validate all known patterns. * Uses fixed currentYear=2026 for deterministic results. */ export function validateFoundingYearExtractor(): { pass: boolean; failures: string[] } { const failures: string[] = []; for (const [text, expected] of FOUNDING_YEAR_TEST_CORPUS) { const result = extractFoundingYear(text, 2026); if (result !== expected) { failures.push(`"${text.slice(0, 30)}...": expected ${expected}, got ${result}`); } } return { pass: failures.length === 0, failures }; }