my-tide-env / api_docs.py
alwaysgood's picture
Update api_docs.py
23760df verified
"""
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```")