Skip to main content
Glama

HRFCO Service

water-data-pipeline.ts9.09 kB
// 실시간 수문 데이터 조회 파이프라인 import { smartStationMapper, SearchResult } from './smart-station-mapper'; import { detectDataType, extractStationName, analyzeQueryIntent, generateDirectAnswer, generateSummary } from './natural-language-processor'; export interface WaterData { water_level?: string; storage_rate?: string; inflow?: string; outflow?: string; rainfall?: string; status?: string; trend?: string; last_updated?: string; } export interface StationData { code: string; name: string; region: string; type: 'dam' | 'waterlevel' | 'rainfall'; keywords: string[]; agency: string; score: number; current_data?: WaterData; error?: string; } export interface PipelineResult { query: string; found_stations: number; stations: StationData[]; timestamp: string; direct_answer?: string; summary?: string; no_additional_query_needed?: boolean; query_analysis?: { dataType: 'dam' | 'waterlevel' | 'rainfall'; stationName: string; intent: 'current_value' | 'status' | 'trend' | 'general'; confidence: number; }; } // 데이터 캐시 (5분 TTL) const dataCache = new Map<string, { data: WaterData; timestamp: number }>(); const CACHE_TTL = 5 * 60 * 1000; // 5분 /** * 관측소명으로 실시간 데이터 조회 (완전한 응답 생성) */ export async function getWaterDataByName( query: string, dataType?: 'dam' | 'waterlevel' | 'rainfall' ): Promise<PipelineResult> { try { // 1단계: 자연어 처리로 데이터 타입 및 의도 분석 const queryAnalysis = analyzeQueryIntent(query); // 지원되지 않는 쿼리 우선 처리 if (queryAnalysis.intent === 'unsupported') { return { query, found_stations: 0, stations: [], timestamp: new Date().toISOString(), direct_answer: '죄송합니다. 실시간 데이터가 아닌 시계열 비교나 추상적인 질문은 현재 지원되지 않습니다.', summary: '지원되지 않는 질문 유형', no_additional_query_needed: true }; } const detectedDataType = dataType || queryAnalysis.dataType; const extractedStationName = queryAnalysis.stationName || query; // 추출된 이름이 없으면 원본 쿼리 사용 // 2단계: 이름과 **타입**으로 코드 찾기 (정확도 향상) const mapper = smartStationMapper; const stations = mapper.searchByName(extractedStationName, detectedDataType); if (stations.length === 0) { return { query: query, found_stations: 0, stations: [], timestamp: new Date().toISOString(), direct_answer: `'${extractedStationName}'에 해당하는 ${detectedDataType} 관측소를 찾을 수 없습니다. 관측소 이름을 확인해주세요.`, summary: `'${extractedStationName}' 관측소 없음`, no_additional_query_needed: true }; } // 3단계: 코드로 실시간 데이터 조회 (병렬 처리) const results = await Promise.all( stations.slice(0, 3).map(async (station) => { try { const liveData = await getCachedStationData(station.code, station.type); return { ...station, current_data: liveData } as StationData; } catch (error) { return { ...station, error: `데이터 조회 실패: ${error instanceof Error ? error.message : 'Unknown error'}` } as StationData; } }) ); // 4단계: 완전한 응답 생성 const primaryStation = results[0]; const directAnswer = generateDirectAnswer(query, primaryStation, detectedDataType); const summary = generateSummary(primaryStation, detectedDataType); console.log('🔍 Pipeline result generation:', { query, detectedDataType, primaryStation: primaryStation.name, directAnswer, summary }); return { query: query, found_stations: results.length, stations: results, timestamp: new Date().toISOString(), direct_answer: directAnswer, summary: summary, no_additional_query_needed: true, query_analysis: queryAnalysis }; } catch (error) { return { query: query, found_stations: 0, stations: [], timestamp: new Date().toISOString(), direct_answer: `'${query}' 조회 중 오류가 발생했습니다.`, summary: `'${query}' 조회 실패`, no_additional_query_needed: true }; } } /** * 캐시된 관측소 데이터 조회 */ async function getCachedStationData(code: string, type: string): Promise<WaterData> { const cacheKey = `data:${code}:${type}`; const cached = dataCache.get(cacheKey); // 캐시 확인 if (cached && (Date.now() - cached.timestamp) < CACHE_TTL) { return cached.data; } // 실시간 데이터 조회 const freshData = await fetchHRFCOStationData(code, type); // 캐시 저장 dataCache.set(cacheKey, { data: freshData, timestamp: Date.now() }); return freshData; } /** * HRFCO API에서 관측소 데이터 조회 */ async function fetchHRFCOStationData(code: string, type: string): Promise<WaterData> { try { const apiKey = process.env.HRFCO_API_KEY; if (!apiKey) { throw new Error('HRFCO_API_KEY가 설정되지 않았습니다'); } // 데이터 타입별 API 엔드포인트 let endpoint: string; switch (type) { case 'dam': endpoint = 'dam/data.json'; break; case 'waterlevel': endpoint = 'waterlevel/data.json'; break; case 'rainfall': endpoint = 'rainfall/data.json'; break; default: throw new Error(`지원하지 않는 데이터 타입: ${type}`); } const url = `http://api.hrfco.go.kr/${apiKey}/${endpoint}`; const response = await fetch(url); if (!response.ok) { throw new Error(`API 호출 실패: ${response.status}`); } const data = await response.json() as any; if (!data.content) { throw new Error('API 응답에 데이터가 없습니다'); } // 해당 코드의 데이터 찾기 const stationData = data.content.find((item: any) => { const itemCode = item.dmobscd || item.wlobscd || item.rfobscd || ''; return itemCode === code; }); if (!stationData) { throw new Error(`관측소 코드 ${code}에 대한 데이터를 찾을 수 없습니다`); } // 데이터 타입별로 다른 필드 매핑 return mapStationData(stationData, type); } catch (error) { console.error(`HRFCO API 조회 실패 (${code}, ${type}):`, error); // API 실패 시 데모 데이터 반환 return generateDemoData(code, type); } } /** * API 응답 데이터를 표준 형식으로 매핑 */ function mapStationData(data: any, type: string): WaterData { switch (type) { case 'dam': return { water_level: data.wl || data.water_level || 'N/A', storage_rate: data.storage_rate || 'N/A', inflow: data.inflow || 'N/A', outflow: data.outflow || 'N/A', status: data.status || '정상', trend: data.trend || '안정', last_updated: data.obsdt || new Date().toISOString() }; case 'waterlevel': return { water_level: data.wl || data.water_level || 'N/A', status: data.status || '정상', trend: data.trend || '안정', last_updated: data.obsdt || new Date().toISOString() }; case 'rainfall': return { rainfall: data.rf || data.rainfall || 'N/A', status: data.status || '정상', last_updated: data.obsdt || new Date().toISOString() }; default: return { status: '알 수 없음', last_updated: new Date().toISOString() }; } } /** * 데모 데이터 생성 (API 실패 시) */ function generateDemoData(code: string, type: string): WaterData { const now = new Date(); const timestamp = now.toISOString(); switch (type) { case 'dam': return { water_level: '120.5m', storage_rate: '78.5%', inflow: '15.2㎥/s', outflow: '12.8㎥/s', status: '정상', trend: '안정', last_updated: timestamp }; case 'waterlevel': return { water_level: '8.5m', status: '정상', trend: '안정', last_updated: timestamp }; case 'rainfall': return { rainfall: '0.0mm', status: '정상', last_updated: timestamp }; default: return { status: '데모 데이터', last_updated: timestamp }; } } /** * 캐시 초기화 */ export function clearDataCache(): void { dataCache.clear(); } /** * 캐시 통계 */ export function getCacheStats() { return { size: dataCache.size, entries: Array.from(dataCache.keys()) }; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kwenhwang/hrfco-service'

If you have feedback or need assistance with the MCP directory API, please join our Discord server