cwadayi commited on
Commit
788698d
·
verified ·
1 Parent(s): 1fd8ba8

Update weather_service.py

Browse files
Files changed (1) hide show
  1. weather_service.py +70 -74
weather_service.py CHANGED
@@ -1,89 +1,85 @@
1
  # weather_service.py
2
  import requests
3
- from config import CWA_API_KEY
4
- from datetime import datetime
5
 
6
- CWA_FORECAST_API = "https://opendata.cwa.gov.tw/api/v1/rest/datastore/F-C0032-001"
7
 
8
- def _normalize_location_name(loc_name: str) -> str:
9
- """自動校正縣市名稱,以符合 API 要求 (例如 台北 -> 臺北市)"""
10
- loc_name = loc_name.replace("台", "臺")
11
- if loc_name.endswith("市") or loc_name.endswith("縣"):
12
- return loc_name
13
 
14
- major_cities = "臺北,桃園,新竹,臺中,嘉義,臺南,高雄,基隆".split(',')
15
- if any(city in loc_name for city in major_cities):
16
- return f"{loc_name}市"
 
17
 
18
- return f"{loc_name}縣"
19
-
20
- def _format_time_period(start_str: str, end_str: str) -> str:
21
- """將時間格式化為易讀的時段名稱"""
22
- start_dt = datetime.fromisoformat(start_str)
23
- start_hour = start_dt.hour
24
 
25
- if start_hour >= 18 or start_hour < 6:
26
- return f"{start_dt.strftime('%m/%d')} 晚上至隔日清晨"
27
- elif start_hour >= 6 and start_hour < 18:
28
- return f"{start_dt.strftime('%m/%d')} 白天"
29
- return f"{start_dt.strftime('%m/%d %H:%M')}"
30
-
31
-
32
- def fetch_forecast_by_location(location_name: str) -> str:
33
- """根據地點名稱查詢未來 36 小時天氣預報。"""
34
- if not CWA_API_KEY:
35
- return "❌ 天氣預報查詢失敗:管理者尚未設定 CWA_API_KEY。"
36
-
37
- normalized_loc = _normalize_location_name(location_name)
38
 
39
- params = {
40
- "Authorization": CWA_API_KEY,
41
- "format": "JSON",
42
- "locationName": normalized_loc,
43
- "elementName": ["Wx", "PoP", "MinT", "MaxT"],
44
- }
45
 
46
- try:
47
- r = requests.get(CWA_FORECAST_API, params=params, timeout=10)
48
- r.raise_for_status()
49
- data = r.json()
 
50
 
51
- if not data.get("records") or not data["records"]["location"]:
52
- return f"找不到「{location_name}」的預報資訊。\n請確認是否為台灣的縣市名稱 (例如: 臺北市, 花蓮縣)。"
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- loc_data = data["records"]["location"][0]
55
- elements = {elem["elementName"]: elem["time"] for elem in loc_data["weatherElement"]}
56
-
57
- forecasts = []
58
- for i in range(3):
59
- try:
60
- start_time = elements["Wx"][i]["startTime"]
61
- end_time = elements["Wx"][i]["endTime"]
62
- wx = elements["Wx"][i]["parameter"]["parameterName"]
63
- pop = elements["PoP"][i]["parameter"]["parameterName"]
64
- min_t = elements["MinT"][i]["parameter"]["parameterName"]
65
- max_t = elements["MaxT"][i]["parameter"]["parameterName"]
66
-
67
- period_name = _format_time_period(start_time, end_time)
68
-
69
- forecast_str = (
70
- f"➢ {period_name}:\n"
71
- f" 天氣:{wx}\n"
72
- f" 氣溫:{min_t}°C - {max_t}°C\n"
73
- f" 降雨機率:{pop}%"
74
- )
75
- forecasts.append(forecast_str)
76
- except (IndexError, KeyError):
77
- continue
78
 
79
- if not forecasts:
80
- return f"無法解析「{location_name}」的預報資料。"
 
 
 
 
 
81
 
82
- return f"📍 {loc_data['locationName']} 未來 36 小時天氣預報:\n\n" + "\n\n".join(forecasts)
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
- except requests.exceptions.HTTPError as e:
85
- if e.response.status_code == 401:
86
- return " 預報查詢失敗:CWA 授權金鑰 (API Key) 無效。"
87
- return f"❌ 預報查詢失敗:{e}"
 
88
  except Exception as e:
89
- return f"❌ 預報查詢失敗:{e}"
 
 
1
  # weather_service.py
2
  import requests
3
+ from config import CWA_API_KEY, DATASET_IDS
 
4
 
5
+ CWA_API_BASE_URL = "https://opendata.cwa.gov.tw/api/v1/rest/datastore/"
6
 
7
+ def _make_cwa_api_request(data_id, params=None):
8
+ """一個通用的 CWA API 請求函式"""
9
+ if not CWA_API_KEY:
10
+ raise ValueError("錯誤:尚未設定 CWA_API_KEY Secret。")
 
11
 
12
+ api_url = f"{CWA_API_BASE_URL}{data_id}"
13
+ base_params = {"Authorization": CWA_API_KEY, "format": "JSON"}
14
+ if params:
15
+ base_params.update(params)
16
 
17
+ response = requests.get(api_url, params=base_params, timeout=10)
18
+ response.raise_for_status() # 如果請求失敗 (e.g., 401, 404, 500), 會在此拋出錯誤
 
 
 
 
19
 
20
+ data = response.json()
21
+ if not data.get('success'):
22
+ raise ValueError("API 回應不成功或資料格式有誤。")
 
 
 
 
 
 
 
 
 
 
23
 
24
+ return data.get('records', {})
 
 
 
 
 
25
 
26
+ def _parse_forecast_data(records, location):
27
+ """解析 F-C0032-001 的預報資料並格式化為文字"""
28
+ location_records = records.get('location', [])
29
+ if not location_records:
30
+ return f"找不到「{location}」的預報資訊。\n請確認是否為台灣的縣市名稱 (例如: 臺北市, 花蓮縣)。"
31
 
32
+ weather_elements = location_records[0].get('weatherElement', [])
33
+
34
+ # 用字典來重組資料,以時間為主鍵
35
+ forecasts = {}
36
+ for element in weather_elements:
37
+ element_name = element['elementName']
38
+ for time_period in element['time']:
39
+ time_key = f"{time_period['startTime']} ~ {time_period['endTime']}"
40
+ if time_key not in forecasts:
41
+ forecasts[time_key] = {}
42
+
43
+ value = time_period['parameter']['parameterName']
44
+ unit = time_period['parameter'].get('parameterUnit', '')
45
+ forecasts[time_key][element_name] = f"{value}{unit}"
46
 
47
+ # 格式化輸出
48
+ result = f"📍 {location} 未來 36 小時天氣預報:\n"
49
+ for time_period, values in sorted(forecasts.items()):
50
+ # 將��間字串格式化得更易讀
51
+ start_time_str = time_period.split(' ~ ')[0]
52
+ start_dt = datetime.fromisoformat(start_time_str)
53
+ period_name = f"{start_dt.strftime('%m/%d %H:%M')}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
+ result += (
56
+ f"\n➢ 時段:{period_name} 開始\n"
57
+ f" 天氣:{values.get('Wx', 'N/A')}\n"
58
+ f" 氣溫:{values.get('MinT', '?')}°C - {values.get('MaxT', '?')}°C\n"
59
+ f" 降雨機率:{values.get('PoP', '?')}%"
60
+ )
61
+ return result
62
 
63
+ def fetch_forecast_by_location(location_name: str) -> str:
64
+ """
65
+ 主要的天氣預報查詢函式,由 command_handler 呼叫。
66
+ 它會處理地點名稱的 '台'/'臺' 轉換,並呼叫輔助函式。
67
+ """
68
+ try:
69
+ # 進行地點名稱的簡易處理,提高 API 成功率
70
+ processed_location = location_name.replace("台", "臺")
71
+ if not (processed_location.endswith("市") or processed_location.endswith("縣")):
72
+ # 根據 Gradio 程式碼的縣市列表,大部分地點都是縣
73
+ if processed_location in ["臺北", "新北", "基隆", "桃園", "新竹", "臺中", "嘉義", "臺南", "高雄"]:
74
+ processed_location += "市"
75
+ else:
76
+ processed_location += "縣"
77
 
78
+ records = _make_cwa_api_request(
79
+ DATASET_IDS["天氣預報"],
80
+ {"locationName": processed_location}
81
+ )
82
+ return _parse_forecast_data(records, processed_location)
83
  except Exception as e:
84
+ return f"❌ 天氣預報查詢失敗:{e}"
85
+