Spaces:
Sleeping
Sleeping
| """ | |
| API 문서 생성 모듈 | |
| API 엔드포인트 정보와 문서 생성 함수를 포함합니다. | |
| """ | |
| import gradio as gr | |
| import json | |
| # API 엔드포인트별 상세 정보 정의 | |
| API_ENDPOINTS = { | |
| "tide_level": { | |
| "path": "/api/tide_level", | |
| "title": "특정 시간 조위 조회", | |
| "description": "지정한 관측소(`station_id`)의 특정 시간(`target_time`)에 대한 예측 조위 정보를 반환합니다. `target_time`을 지정하지 않으면 현재 시간에 가장 가까운 데이터를 반환합니다.", | |
| "parameters": [ | |
| {"name": "station_id", "type": "string", "required": True, "description": "조회할 관측소의 고유 ID입니다. (예: 'DT_0001')"}, | |
| {"name": "target_time", "type": "string", "required": False, "description": "조회할 시간입니다. ISO 8601 형식(YYYY-MM-DDTHH:MM:SS)을 권장하며, 생략 시 현재 시간으로 자동 설정됩니다."} | |
| ], | |
| "example_params": {"station_id": "DT_0001", "target_time": "2025-08-10T09:00:00"}, | |
| "example_params_current": {"station_id": "DT_0001"}, | |
| "response_example": { | |
| "success": True, | |
| "timestamp": "2025-08-10T11:50:13.854658+09:00", | |
| "meta": { | |
| "obs_post_id": "DT_0001", | |
| "obs_post_name": "인천", | |
| "obs_lat": "37.452", | |
| "obs_lon": "126.592", | |
| "data_type": "prediction" | |
| }, | |
| "data": { | |
| "record_time": "2025-08-10T11:50:00+09:00", | |
| "record_time_kst": "2025-08-10 11:50:00 KST", | |
| "tide_level": 151.9, | |
| "residual_value": None, | |
| "harmonic_value": 151.9, | |
| "data_source": "harmonic_only", | |
| "confidence": "medium", | |
| "note": "잔차 예측이 없어 조화 예측만 제공됩니다", | |
| "query_time": "2025-08-10 11:50:12 KST", | |
| "matched_time_diff_seconds": 12.357893 | |
| } | |
| } | |
| }, | |
| "tide_series": { | |
| "path": "/api/tide_series", | |
| "title": "시계열 조위 데이터 조회", | |
| "description": "지정된 기간 동안의 시계열 조위 데이터를 조회합니다. 공공 API와 유사한 형식으로 반환되며, 간격(interval)을 지정할 수 있습니다.", | |
| "parameters": [ | |
| {"name": "station_id", "type": "string", "required": True, "description": "조회할 관측소의 고유 ID입니다."}, | |
| {"name": "start_time", "type": "string", "required": False, "description": "조회 시작 시간입니다. 생략 시 현재 시간부터 시작합니다."}, | |
| {"name": "end_time", "type": "string", "required": False, "description": "조회 종료 시간입니다. 생략 시 시작 시간으로부터 24시간 후까지 조회합니다."}, | |
| {"name": "interval", "type": "integer", "required": False, "description": "데이터 간격(분 단위). 기본값: 60분, 최소값: 5분"} | |
| ], | |
| "example_params": {"station_id": "DT_0001", "start_time": "2025-08-10T00:00:00", "end_time": "2025-08-11T00:00:00", "interval": 60}, | |
| "response_example": { | |
| "success": True, | |
| "timestamp": "2025-08-10T00:30:00.123456+09:00", | |
| "meta": { | |
| "obs_post_id": "DT_0001", | |
| "obs_post_name": "인천", | |
| "start_time": "2025-08-10T00:00:00+09:00", | |
| "end_time": "2025-08-11T00:00:00+09:00", | |
| "interval_minutes": 60, | |
| "total_records": 25 | |
| }, | |
| "data": { | |
| "tidal_obs": [ | |
| { | |
| "record_time": "2025-08-10 00:00", | |
| "pred_tide": 125.3, | |
| "harmonic_tide": 125.3, | |
| "residual_tide": None | |
| }, | |
| { | |
| "record_time": "2025-08-10 01:00", | |
| "pred_tide": 142.7, | |
| "harmonic_tide": 142.7, | |
| "residual_tide": None | |
| } | |
| ] | |
| } | |
| } | |
| }, | |
| "extremes": { | |
| "path": "/api/extremes", | |
| "title": "만조/간조 정보 조회", | |
| "description": "특정 날짜의 만조(high tide)와 간조(low tide) 정보를 조회합니다. 주요 만조/간조와 부차 만조/간조를 구분하여 제공합니다.", | |
| "parameters": [ | |
| {"name": "station_id", "type": "string", "required": True, "description": "조회할 관측소의 고유 ID입니다."}, | |
| {"name": "date", "type": "string", "required": False, "description": "조회할 날짜 (YYYY-MM-DD 형식). 생략 시 오늘 날짜로 설정됩니다."}, | |
| {"name": "include_secondary", "type": "boolean", "required": False, "description": "부차 만조/간조 포함 여부. 기본값: false"} | |
| ], | |
| "example_params": {"station_id": "DT_0001", "date": "2025-08-10", "include_secondary": True}, | |
| "response_example": { | |
| "success": True, | |
| "timestamp": "2025-08-10T00:35:00.123456+09:00", | |
| "meta": { | |
| "obs_post_id": "DT_0001", | |
| "obs_post_name": "인천", | |
| "date": "2025-08-10", | |
| "include_secondary": True | |
| }, | |
| "data": { | |
| "high_tides": [ | |
| { | |
| "time": "2025-08-10T06:15:00+09:00", | |
| "time_kst": "2025-08-10 06:15:00 KST", | |
| "level": 812.5, | |
| "type": "primary" | |
| }, | |
| { | |
| "time": "2025-08-10T18:45:00+09:00", | |
| "time_kst": "2025-08-10 18:45:00 KST", | |
| "level": 798.3, | |
| "type": "primary" | |
| } | |
| ], | |
| "low_tides": [ | |
| { | |
| "time": "2025-08-10T00:30:00+09:00", | |
| "time_kst": "2025-08-10 00:30:00 KST", | |
| "level": 98.2, | |
| "type": "primary" | |
| }, | |
| { | |
| "time": "2025-08-10T12:45:00+09:00", | |
| "time_kst": "2025-08-10 12:45:00 KST", | |
| "level": 112.7, | |
| "type": "primary" | |
| } | |
| ] | |
| } | |
| } | |
| }, | |
| "alert": { | |
| "path": "/api/alert", | |
| "title": "위험 수위 체크", | |
| "description": "향후 지정된 시간 동안 주의 수위 또는 경고 수위에 도달하는지 확인합니다. 위험 시점과 예상 수위를 반환합니다.", | |
| "parameters": [ | |
| {"name": "station_id", "type": "string", "required": True, "description": "체크할 관측소의 고유 ID입니다."}, | |
| {"name": "hours_ahead", "type": "integer", "required": False, "description": "확인할 시간 범위(시간 단위). 기본값: 24시간, 최대: 72시간"}, | |
| {"name": "warning_level", "type": "number", "required": False, "description": "주의 수위(cm). 기본값: 700cm"}, | |
| {"name": "danger_level", "type": "number", "required": False, "description": "경고 수위(cm). 기본값: 750cm"} | |
| ], | |
| "example_params": {"station_id": "DT_0001", "hours_ahead": 24, "warning_level": 700, "danger_level": 750}, | |
| "response_example": { | |
| "success": True, | |
| "timestamp": "2025-08-10T00:40:00.123456+09:00", | |
| "meta": { | |
| "obs_post_id": "DT_0001", | |
| "obs_post_name": "인천", | |
| "check_period": { | |
| "start": "2025-08-10T00:40:00+09:00", | |
| "end": "2025-08-11T00:40:00+09:00" | |
| }, | |
| "warning_level": 700, | |
| "danger_level": 750 | |
| }, | |
| "data": { | |
| "alert_status": "DANGER", | |
| "max_level": 812.5, | |
| "max_level_time": "2025-08-10T06:15:00+09:00", | |
| "warning_events": [ | |
| { | |
| "time": "2025-08-10T05:30:00+09:00", | |
| "level": 702.3, | |
| "type": "warning_exceeded" | |
| } | |
| ], | |
| "danger_events": [ | |
| { | |
| "time": "2025-08-10T06:00:00+09:00", | |
| "level": 755.8, | |
| "type": "danger_exceeded" | |
| } | |
| ], | |
| "recommendation": "경고 수위 초과 예상. 해안가 접근 주의 필요" | |
| } | |
| } | |
| }, | |
| "compare": { | |
| "path": "/api/compare", | |
| "title": "다중 관측소 비교", | |
| "description": "여러 관측소의 조위를 동시에 비교합니다. 지정한 시간의 각 관측소별 조위 정보를 한 번에 조회할 수 있습니다.", | |
| "parameters": [ | |
| {"name": "station_ids", "type": "array", "required": True, "description": "비교할 관측소 ID 목록 (배열 형태). 예: ['DT_0001', 'DT_0002']"}, | |
| {"name": "target_time", "type": "string", "required": False, "description": "비교할 시간. 생략 시 현재 시간으로 설정됩니다."} | |
| ], | |
| "example_params": {"station_ids": ["DT_0001", "DT_0002", "DT_0003"], "target_time": "2025-08-10T09:00:00"}, | |
| "response_example": { | |
| "success": True, | |
| "timestamp": "2025-08-10T00:45:00.123456+09:00", | |
| "meta": { | |
| "target_time": "2025-08-10T09:00:00+09:00", | |
| "station_count": 3 | |
| }, | |
| "data": { | |
| "comparisons": [ | |
| { | |
| "station_id": "DT_0001", | |
| "station_name": "인천", | |
| "tide_level": 425.3, | |
| "data_time": "2025-08-10T09:00:00+09:00" | |
| }, | |
| { | |
| "station_id": "DT_0002", | |
| "station_name": "안흥", | |
| "tide_level": 312.8, | |
| "data_time": "2025-08-10T09:00:00+09:00" | |
| }, | |
| { | |
| "station_id": "DT_0003", | |
| "station_name": "보령", | |
| "tide_level": 298.5, | |
| "data_time": "2025-08-10T09:00:00+09:00" | |
| } | |
| ], | |
| "statistics": { | |
| "max_level": 425.3, | |
| "max_station": "인천", | |
| "min_level": 298.5, | |
| "min_station": "보령", | |
| "avg_level": 345.5 | |
| } | |
| } | |
| } | |
| }, | |
| "health": { | |
| "path": "/api/health", | |
| "title": "시스템 상태 확인", | |
| "description": "API 서버 및 연결된 시스템의 상태를 확인합니다. 데이터베이스 연결, API 키 설정 등을 점검합니다.", | |
| "parameters": [], | |
| "example_params": {}, | |
| "response_example": { | |
| "success": True, | |
| "timestamp": "2025-08-10T00:50:00.123456+09:00", | |
| "status": "healthy", | |
| "services": { | |
| "api_server": "running", | |
| "supabase": "connected", | |
| "gemini_api": "configured", | |
| "predictions": "available" | |
| }, | |
| "uptime": "2 hours 15 minutes", | |
| "version": "1.0.0" | |
| } | |
| } | |
| } | |
| def copy_to_clipboard(text): | |
| """클립보드에 텍스트 복사 (JavaScript 실행)""" | |
| return f""" | |
| <script> | |
| navigator.clipboard.writeText('{text}'); | |
| alert('URL이 클립보드에 복사되었습니다!'); | |
| </script> | |
| """ | |
| def generate_api_docs(endpoint_key: str): | |
| """엔드포인트별 상세 API 문서를 생성합니다.""" | |
| base_url = "https://alwaysgood-my-tide-env.hf.space" | |
| endpoint_info = API_ENDPOINTS[endpoint_key] | |
| # 섹션 제목 | |
| gr.Markdown("---") | |
| gr.Markdown(f"## 📚 API 사용 안내서") | |
| gr.Markdown(f"### `{endpoint_info['path']}` : {endpoint_info['title']}") | |
| gr.Markdown(endpoint_info['description']) | |
| # 기본 정보 | |
| gr.Markdown("- **Method**: `GET`") | |
| gr.Markdown(f"- **URL**: `{base_url}{endpoint_info['path']}`") | |
| gr.Markdown("") | |
| # 요청 파라미터 테이블 | |
| if endpoint_info['parameters']: | |
| gr.Markdown("### 요청 파라미터 (Query Parameters)") | |
| # 테이블 헤더 | |
| table_md = "|파라미터 (Parameter)|타입 (Type)|필수 (Required)|설명 (Description)|\n" | |
| table_md += "|---|---|---|---|\n" | |
| # 파라미터 정보 | |
| for param in endpoint_info['parameters']: | |
| required_text = "**Yes**" if param['required'] else "No" | |
| table_md += f"|`{param['name']}`|`{param['type']}`|{required_text}|{param['description']}|\n" | |
| gr.Markdown(table_md) | |
| gr.Markdown("") | |
| # 사용 예시 | |
| gr.Markdown("### 사용 예시 (Usage Examples)") | |
| # Python 예시 | |
| python_code = f'''import requests | |
| import json | |
| BASE_URL = "{base_url}" | |
| ''' | |
| # 엔드포인트별 특별 처리 | |
| if endpoint_key == "tide_level": | |
| python_code += f''' | |
| # 1. 현재 조위 조회 (인천 관측소) | |
| params_now = {{ | |
| "station_id": "DT_0001" | |
| }} | |
| response_now = requests.get(f"{{BASE_URL}}{endpoint_info['path']}", params=params_now) | |
| print("--- 현재 조위 조회 결과 ---") | |
| print(response_now.json()) | |
| # 2. 특정 시간 조위 조회 (2025년 8월 10일 오전 9시) | |
| params_specific_time = {{ | |
| "station_id": "DT_0001", | |
| "target_time": "2025-08-10T09:00:00" | |
| }} | |
| response_specific = requests.get(f"{{BASE_URL}}{endpoint_info['path']}", params=params_specific_time) | |
| print("\\n--- 특정 시간 조위 조회 결과 ---") | |
| print(response_specific.json())''' | |
| elif endpoint_key == "compare": | |
| python_code += f''' | |
| # 여러 관측소 동시 비교 | |
| params = {{ | |
| "station_ids": ["DT_0001", "DT_0002", "DT_0003"], | |
| "target_time": "2025-08-10T09:00:00" | |
| }} | |
| response = requests.get(f"{{BASE_URL}}{endpoint_info['path']}", params=params) | |
| if response.status_code == 200: | |
| data = response.json() | |
| print("--- 관측소별 조위 비교 ---") | |
| for station in data['data']['comparisons']: | |
| print(f"{{station['station_name']}}: {{station['tide_level']}}cm") | |
| else: | |
| print(f"Error: {{response.status_code}}")''' | |
| elif endpoint_key == "health": | |
| python_code += f''' | |
| # 시스템 상태 확인 | |
| response = requests.get(f"{{BASE_URL}}{endpoint_info['path']}") | |
| if response.status_code == 200: | |
| data = response.json() | |
| print(f"시스템 상태: {{data['status']}}") | |
| print(f"서비스 상태: {{data['services']}}") | |
| else: | |
| print(f"Error: {{response.status_code}}")''' | |
| else: | |
| # 일반적인 경우 | |
| python_code += f''' | |
| # 요청 파라미터 설정 | |
| params = {json.dumps(endpoint_info['example_params'], indent=4, ensure_ascii=False)} | |
| response = requests.get(f"{{BASE_URL}}{endpoint_info['path']}", params=params) | |
| if response.status_code == 200: | |
| data = response.json() | |
| print(json.dumps(data, indent=2, ensure_ascii=False)) | |
| else: | |
| print(f"Error: {{response.status_code}}")''' | |
| gr.Markdown("#### **Python (`requests` 사용)**") | |
| gr.Markdown(f"```python\n{python_code.strip()}\n```") | |
| # curl 예시 | |
| # URL 파라미터 생성 | |
| if endpoint_key == "compare": | |
| # 배열 파라미터는 특별 처리 | |
| curl_params = "&".join([f"station_ids={sid}" for sid in endpoint_info['example_params']['station_ids']]) | |
| if 'target_time' in endpoint_info['example_params']: | |
| curl_params += f"&target_time={endpoint_info['example_params']['target_time']}" | |
| elif endpoint_key == "health": | |
| curl_params = "" | |
| else: | |
| curl_params = "&".join([f"{k}={v}" for k, v in endpoint_info['example_params'].items()]) | |
| curl_url = f"{base_url}{endpoint_info['path']}" | |
| if curl_params: | |
| curl_url += f"?{curl_params}" | |
| curl_code = f'# 요청 예시\ncurl -X GET "{curl_url}"' | |
| # 특별 케이스 추가 | |
| if endpoint_key == "tide_level": | |
| curl_code = f'''# 현재 조위 조회 | |
| curl -X GET "{base_url}{endpoint_info['path']}?station_id=DT_0001" | |
| # 특정 시간 조위 조회 | |
| curl -X GET "{base_url}{endpoint_info['path']}?station_id=DT_0001&target_time=2025-08-10T09:00:00"''' | |
| gr.Markdown("#### **curl (Command Line)**") | |
| gr.Markdown(f"```bash\n{curl_code}\n```") | |
| # JavaScript 예시 | |
| if endpoint_key == "compare": | |
| js_code = f'''const stationIds = ['DT_0001', 'DT_0002', 'DT_0003']; | |
| const params = new URLSearchParams(); | |
| stationIds.forEach(id => params.append('station_ids', id)); | |
| params.append('target_time', '2025-08-10T09:00:00'); | |
| const url = `{base_url}{endpoint_info['path']}?${{params}}`; | |
| fetch(url) | |
| .then(response => response.json()) | |
| .then(data => {{ | |
| console.log('비교 결과:', data); | |
| data.data.comparisons.forEach(station => {{ | |
| console.log(`${{station.station_name}}: ${{station.tide_level}}cm`); | |
| }}); | |
| }}) | |
| .catch(error => {{ | |
| console.error('Error:', error); | |
| }});''' | |
| elif endpoint_key == "health": | |
| js_code = f'''const url = '{base_url}{endpoint_info['path']}'; | |
| fetch(url) | |
| .then(response => response.json()) | |
| .then(data => {{ | |
| console.log('시스템 상태:', data.status); | |
| console.log('서비스:', data.services); | |
| }}) | |
| .catch(error => {{ | |
| console.error('Error:', error); | |
| }});''' | |
| else: | |
| # 일반 케이스 | |
| params_str = "&".join([f"{k}={v}" for k, v in endpoint_info.get('example_params_current', endpoint_info['example_params']).items()]) | |
| js_code = f'''const stationId = 'DT_0001'; | |
| const url = `{base_url}{endpoint_info['path']}?{params_str}`; | |
| fetch(url) | |
| .then(response => response.json()) | |
| .then(data => {{ | |
| console.log(data); | |
| }}) | |
| .catch(error => {{ | |
| console.error('Error:', error); | |
| }});''' | |
| gr.Markdown("#### **JavaScript (`fetch` API)**") | |
| gr.Markdown(f"```javascript\n{js_code}\n```") | |
| # 브라우저 직접 접속 - 복사 버튼 추가 | |
| gr.Markdown("#### **웹 브라우저**") | |
| gr.Markdown("아래 주소를 복사하여 웹 브라우저 주소창에 붙여넣기만 해도 결과를 확인할 수 있습니다.") | |
| browser_params = "" | |
| if endpoint_key != "health": | |
| if endpoint_key == "compare": | |
| browser_params = "?station_ids=DT_0001&station_ids=DT_0002" | |
| else: | |
| browser_params = "?" + "&".join([f"{k}={v}" for k, v in endpoint_info.get('example_params_current', {'station_id': 'DT_0001'}).items()]) | |
| browser_url = f"{base_url}{endpoint_info['path']}{browser_params}" | |
| with gr.Row(): | |
| url_textbox = gr.Textbox( | |
| value=browser_url, | |
| interactive=False, | |
| show_label=False, | |
| scale=4 | |
| ) | |
| copy_btn = gr.Button("📋 복사", scale=1, size="sm") | |
| # 복사 버튼 클릭 이벤트 | |
| copy_btn.click( | |
| fn=lambda x: x, # 단순히 URL을 반환 | |
| inputs=[url_textbox], | |
| outputs=[], | |
| js=f"""(x) => {{ | |
| navigator.clipboard.writeText('{browser_url}'); | |
| alert('URL이 클립보드에 복사되었습니다!'); | |
| return x; | |
| }}""" | |
| ) | |
| # 응답 형식 | |
| gr.Markdown("") | |
| gr.Markdown("### 응답 형식 (Response Format)") | |
| # 성공 응답 테이블 | |
| gr.Markdown("#### **성공 (200 OK)**") | |
| # 응답 구조를 테이블로 표시 | |
| if endpoint_key == "tide_level": | |
| # 메타 정보 테이블 | |
| gr.Markdown("##### **Meta 정보**") | |
| meta_table = "|필드 (Field)|타입 (Type)|설명 (Description)|\n" | |
| meta_table += "|---|---|---|\n" | |
| meta_table += "|`success`|`boolean`|요청 성공 여부|\n" | |
| meta_table += "|`timestamp`|`string`|응답 생성 시간 (ISO 8601 형식)|\n" | |
| meta_table += "|`meta.obs_post_id`|`string`|관측소 ID (요청한 station_id와 동일)|\n" | |
| meta_table += "|`meta.obs_post_name`|`string`|관측소 이름|\n" | |
| meta_table += "|`meta.obs_lat`|`string`|관측소 위도|\n" | |
| meta_table += "|`meta.obs_lon`|`string`|관측소 경도|\n" | |
| meta_table += "|`meta.data_type`|`string`|데이터 타입 (prediction/observation)|\n" | |
| gr.Markdown(meta_table) | |
| gr.Markdown("##### **Data 정보**") | |
| data_table = "|필드 (Field)|타입 (Type)|설명 (Description)|\n" | |
| data_table += "|---|---|---|\n" | |
| data_table += "|`data.record_time`|`string`|데이터 기록 시간 (ISO 8601)|\n" | |
| data_table += "|`data.record_time_kst`|`string`|데이터 기록 시간 (KST 형식)|\n" | |
| data_table += "|`data.tide_level`|`number`|**예측 조위 높이 (cm)**|\n" | |
| data_table += "|`data.residual_value`|`number/null`|잔차 예측값|\n" | |
| data_table += "|`data.harmonic_value`|`number`|조화 예측값|\n" | |
| data_table += "|`data.data_source`|`string`|데이터 소스 (harmonic_only/combined)|\n" | |
| data_table += "|`data.confidence`|`string`|예측 신뢰도 (high/medium/low)|\n" | |
| data_table += "|`data.note`|`string`|추가 설명|\n" | |
| data_table += "|`data.query_time`|`string`|쿼리 실행 시간|\n" | |
| data_table += "|`data.matched_time_diff_seconds`|`number`|요청 시간과 데이터 시간 차이(초)|\n" | |
| gr.Markdown(data_table) | |
| elif endpoint_key == "tide_series": | |
| gr.Markdown("##### **응답 구조**") | |
| series_table = "|필드 (Field)|타입 (Type)|설명 (Description)|\n" | |
| series_table += "|---|---|---|\n" | |
| series_table += "|`success`|`boolean`|요청 성공 여부|\n" | |
| series_table += "|`meta.obs_post_id`|`string`|관측소 ID|\n" | |
| series_table += "|`meta.obs_post_name`|`string`|관측소 이름|\n" | |
| series_table += "|`meta.start_time`|`string`|조회 시작 시간|\n" | |
| series_table += "|`meta.end_time`|`string`|조회 종료 시간|\n" | |
| series_table += "|`meta.interval_minutes`|`integer`|데이터 간격(분)|\n" | |
| series_table += "|`meta.total_records`|`integer`|전체 레코드 수|\n" | |
| series_table += "|`data.tidal_obs`|`array`|시계열 조위 데이터 배열|\n" | |
| series_table += "|`data.tidal_obs[].record_time`|`string`|기록 시간|\n" | |
| series_table += "|`data.tidal_obs[].pred_tide`|`number`|예측 조위(cm)|\n" | |
| series_table += "|`data.tidal_obs[].harmonic_tide`|`number`|조화 예측값|\n" | |
| series_table += "|`data.tidal_obs[].residual_tide`|`number/null`|잔차 예측값|\n" | |
| gr.Markdown(series_table) | |
| elif endpoint_key == "extremes": | |
| gr.Markdown("##### **응답 구조**") | |
| extremes_table = "|필드 (Field)|타입 (Type)|설명 (Description)|\n" | |
| extremes_table += "|---|---|---|\n" | |
| extremes_table += "|`success`|`boolean`|요청 성공 여부|\n" | |
| extremes_table += "|`meta.obs_post_id`|`string`|관측소 ID|\n" | |
| extremes_table += "|`meta.obs_post_name`|`string`|관측소 이름|\n" | |
| extremes_table += "|`meta.date`|`string`|조회 날짜|\n" | |
| extremes_table += "|`meta.include_secondary`|`boolean`|부차 만조/간조 포함 여부|\n" | |
| extremes_table += "|`data.high_tides`|`array`|만조 정보 배열|\n" | |
| extremes_table += "|`data.high_tides[].time`|`string`|만조 시간|\n" | |
| extremes_table += "|`data.high_tides[].level`|`number`|만조 높이(cm)|\n" | |
| extremes_table += "|`data.high_tides[].type`|`string`|만조 타입 (primary/secondary)|\n" | |
| extremes_table += "|`data.low_tides`|`array`|간조 정보 배열|\n" | |
| extremes_table += "|`data.low_tides[].time`|`string`|간조 시간|\n" | |
| extremes_table += "|`data.low_tides[].level`|`number`|간조 높이(cm)|\n" | |
| extremes_table += "|`data.low_tides[].type`|`string`|간조 타입 (primary/secondary)|\n" | |
| gr.Markdown(extremes_table) | |
| elif endpoint_key == "alert": | |
| gr.Markdown("##### **응답 구조**") | |
| alert_table = "|필드 (Field)|타입 (Type)|설명 (Description)|\n" | |
| alert_table += "|---|---|---|\n" | |
| alert_table += "|`success`|`boolean`|요청 성공 여부|\n" | |
| alert_table += "|`meta.obs_post_id`|`string`|관측소 ID|\n" | |
| alert_table += "|`meta.obs_post_name`|`string`|관측소 이름|\n" | |
| alert_table += "|`meta.check_period.start`|`string`|확인 시작 시간|\n" | |
| alert_table += "|`meta.check_period.end`|`string`|확인 종료 시간|\n" | |
| alert_table += "|`meta.warning_level`|`number`|주의 수위(cm)|\n" | |
| alert_table += "|`meta.danger_level`|`number`|경고 수위(cm)|\n" | |
| alert_table += "|`data.alert_status`|`string`|경보 상태 (SAFE/WARNING/DANGER)|\n" | |
| alert_table += "|`data.max_level`|`number`|기간 중 최고 수위|\n" | |
| alert_table += "|`data.max_level_time`|`string`|최고 수위 시간|\n" | |
| alert_table += "|`data.warning_events`|`array`|주의 수위 초과 이벤트|\n" | |
| alert_table += "|`data.danger_events`|`array`|경고 수위 초과 이벤트|\n" | |
| alert_table += "|`data.recommendation`|`string`|권고 사항|\n" | |
| gr.Markdown(alert_table) | |
| elif endpoint_key == "compare": | |
| gr.Markdown("##### **응답 구조**") | |
| compare_table = "|필드 (Field)|타입 (Type)|설명 (Description)|\n" | |
| compare_table += "|---|---|---|\n" | |
| compare_table += "|`success`|`boolean`|요청 성공 여부|\n" | |
| compare_table += "|`meta.target_time`|`string`|비교 시간|\n" | |
| compare_table += "|`meta.station_count`|`integer`|비교 관측소 수|\n" | |
| compare_table += "|`data.comparisons`|`array`|관측소별 조위 정보 배열|\n" | |
| compare_table += "|`data.comparisons[].station_id`|`string`|관측소 ID|\n" | |
| compare_table += "|`data.comparisons[].station_name`|`string`|관측소 이름|\n" | |
| compare_table += "|`data.comparisons[].tide_level`|`number`|조위 높이(cm)|\n" | |
| compare_table += "|`data.comparisons[].data_time`|`string`|데이터 시간|\n" | |
| compare_table += "|`data.statistics.max_level`|`number`|최고 조위|\n" | |
| compare_table += "|`data.statistics.max_station`|`string`|최고 조위 관측소|\n" | |
| compare_table += "|`data.statistics.min_level`|`number`|최저 조위|\n" | |
| compare_table += "|`data.statistics.min_station`|`string`|최저 조위 관측소|\n" | |
| compare_table += "|`data.statistics.avg_level`|`number`|평균 조위|\n" | |
| gr.Markdown(compare_table) | |
| elif endpoint_key == "health": | |
| gr.Markdown("##### **응답 구조**") | |
| health_table = "|필드 (Field)|타입 (Type)|설명 (Description)|\n" | |
| health_table += "|---|---|---|\n" | |
| health_table += "|`success`|`boolean`|요청 성공 여부|\n" | |
| health_table += "|`timestamp`|`string`|응답 시간|\n" | |
| health_table += "|`status`|`string`|시스템 상태 (healthy/degraded/error)|\n" | |
| health_table += "|`services.api_server`|`string`|API 서버 상태|\n" | |
| health_table += "|`services.supabase`|`string`|Supabase 연결 상태|\n" | |
| health_table += "|`services.gemini_api`|`string`|Gemini API 상태|\n" | |
| health_table += "|`services.predictions`|`string`|예측 서비스 상태|\n" | |
| health_table += "|`uptime`|`string`|서비스 가동 시간|\n" | |
| health_table += "|`version`|`string`|API 버전|\n" | |
| gr.Markdown(health_table) | |
| # 응답 예시 (JSON) | |
| gr.Markdown("##### **응답 예시**") | |
| response_json = json.dumps(endpoint_info['response_example'], indent=2, ensure_ascii=False) | |
| gr.Markdown(f"```json\n{response_json}\n```") | |
| # 실패 응답 (파라미터가 필요한 경우에만) | |
| if any(p['required'] for p in endpoint_info['parameters']): | |
| gr.Markdown("#### **실패 (422 Unprocessable Entity - 파라미터 누락 시)**") | |
| error_response = '''{ | |
| "detail": [ | |
| { | |
| "type": "missing", | |
| "loc": [ | |
| "query", | |
| "station_id" | |
| ], | |
| "msg": "Field required", | |
| "input": null | |
| } | |
| ] | |
| }''' | |
| gr.Markdown(f"```json\n{error_response}\n```") |