cwadayi commited on
Commit
c99e36b
·
verified ·
1 Parent(s): 22f516b

Update cwa_service.py

Browse files
Files changed (1) hide show
  1. cwa_service.py +25 -41
cwa_service.py CHANGED
@@ -1,4 +1,4 @@
1
- # cwa_service.py
2
  import requests
3
  import re
4
  import pandas as pd
@@ -14,50 +14,35 @@ def _to_float(x):
14
  return float(m.group()) if m else None
15
 
16
  def _parse_cwa_time(s: str) -> tuple[str, str]:
17
- """
18
- [修正]
19
- 使其能夠處理兩種 CWA API 可能的時間格式:
20
- 1. ISO 8601 格式 (e.g., '2025-08-19T03:55:47Z')
21
- 2. 本地時間格式 (e.g., '2025-08-19 09:26:11')
22
- """
23
  if not s: return ("未知", "未知")
24
-
25
  dt_utc = None
26
- # 嘗試解析 ISO 8601 格式
27
  try:
28
  dt_utc = datetime.fromisoformat(s.replace("Z", "+00:00"))
29
  except ValueError:
30
- # 若失敗,嘗試解析 YYYY-MM-DD HH:MM:SS 格式,並假設其為台北時間
31
  try:
32
  dt_local = datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
33
  dt_local = dt_local.replace(tzinfo=TAIPEI_TZ)
34
  dt_utc = dt_local.astimezone(timezone.utc)
35
  except Exception:
36
- return (s, "未知") # 若兩種格式都失敗,回傳原始字串
37
-
38
  if dt_utc:
39
  tw_str = dt_utc.astimezone(TAIPEI_TZ).strftime("%Y-%m-%d %H:%M")
40
  utc_str = dt_utc.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M")
41
  return (tw_str, utc_str)
42
-
43
  return (s, "未知")
44
 
45
  def fetch_cwa_alarm_list(limit: int = 5) -> str:
46
- """抓 CWA 地震預警並格式化輸出。"""
47
  try:
48
  r = requests.get(CWA_ALARM_API, timeout=10)
49
  r.raise_for_status()
50
  payload = r.json()
51
  except Exception as e:
52
  return f"❌ 地震預警查詢失敗:{e}"
53
-
54
  items = payload.get("data", [])
55
  if not items: return "✅ 目前沒有地震預警。"
56
-
57
  def _key(it):
58
  try: return datetime.fromisoformat(it.get("originTime", "").replace("Z", "+00:00"))
59
  except: return datetime.min.replace(tzinfo=timezone.utc)
60
-
61
  items = sorted(items, key=_key, reverse=True)
62
  lines = ["🚨 地震預警(最新):", "-" * 20]
63
  for it in items[:limit]:
@@ -67,22 +52,16 @@ def fetch_cwa_alarm_list(limit: int = 5) -> str:
67
  identifier = str(it.get('identifier', '—')).replace('{', '{{').replace('}', '}}')
68
  msg_type = str(it.get('msgType', '—')).replace('{', '{{').replace('}', '}}')
69
  msg_no = str(it.get('msgNo', '—')).replace('{', '{{').replace('}', '}}')
70
-
71
- # [修正] 將讀取欄位從 alertAreas 改為 locationDesc
72
  location_desc_list = it.get('locationDesc')
73
- if isinstance(location_desc_list, list) and location_desc_list:
74
- areas_str = ", ".join(str(area) for area in location_desc_list)
75
- else:
76
- areas_str = "—"
77
  areas = areas_str.replace('{', '{{').replace('}', '}}')
78
-
79
  mag_str = f"{mag:.1f}" if mag is not None else "—"
80
  depth_str = f"{depth:.0f}" if depth is not None else "—"
81
  lines.append(
82
  f"事件: {identifier} | 類型: {msg_type}#{msg_no}\n"
83
  f"規模/深度: M{mag_str} / {depth_str} km\n"
84
  f"時間: {tw_str}(台灣)\n"
85
- f"地點: {areas}" # 標籤改為「地點」更符合 locationDesc 的語意
86
  )
87
  return "\n\n".join(lines).strip()
88
 
@@ -105,7 +84,8 @@ def _parse_significant_earthquakes(obj: dict) -> pd.DataFrame:
105
  })
106
  df = pd.DataFrame(rows)
107
  if not df.empty and "Time" in df.columns:
108
- df["Time"] = pd.to_datetime(df["Time"], errors="coerce").dt.tz_convert(TAIPEI_TZ)
 
109
  return df
110
 
111
  def fetch_significant_earthquakes(days: int = 7, limit: int = 5) -> str:
@@ -135,21 +115,25 @@ def fetch_significant_earthquakes(days: int = 7, limit: int = 5) -> str:
135
  return f"❌ 顯著地震查詢失敗:{e}"
136
 
137
  def fetch_latest_significant_earthquake() -> dict | None:
138
- if not CWA_API_KEY: raise ValueError("錯誤:尚未設定 CWA_API_KEY Secret。")
139
- params = {"Authorization": CWA_API_KEY, "format": "JSON", "limit": 1, "orderby": "OriginTime desc"}
140
- r = requests.get(CWA_SIGNIFICANT_API, params=params, timeout=15)
141
- r.raise_for_status()
142
- data = r.json()
143
- df = _parse_significant_earthquakes(data)
144
- if df.empty: return None
 
145
 
146
- latest_eq_data = df.iloc[0].to_dict()
147
-
148
- quakes = data.get("records", {}).get("Earthquake", [])
149
- if quakes:
150
- latest_eq_data["ImageURL"] = quakes[0].get("ReportImageURI")
151
 
152
- if pd.notna(latest_eq_data.get("Time")):
153
- latest_eq_data["TimeStr"] = latest_eq_data["Time"].strftime('%Y-%m-%d %H:%M')
154
 
155
- return latest_eq_data
 
 
 
 
1
+ # cwa_service.py (Corrected for tz-naive error)
2
  import requests
3
  import re
4
  import pandas as pd
 
14
  return float(m.group()) if m else None
15
 
16
  def _parse_cwa_time(s: str) -> tuple[str, str]:
 
 
 
 
 
 
17
  if not s: return ("未知", "未知")
 
18
  dt_utc = None
 
19
  try:
20
  dt_utc = datetime.fromisoformat(s.replace("Z", "+00:00"))
21
  except ValueError:
 
22
  try:
23
  dt_local = datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
24
  dt_local = dt_local.replace(tzinfo=TAIPEI_TZ)
25
  dt_utc = dt_local.astimezone(timezone.utc)
26
  except Exception:
27
+ return (s, "未知")
 
28
  if dt_utc:
29
  tw_str = dt_utc.astimezone(TAIPEI_TZ).strftime("%Y-%m-%d %H:%M")
30
  utc_str = dt_utc.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M")
31
  return (tw_str, utc_str)
 
32
  return (s, "未知")
33
 
34
  def fetch_cwa_alarm_list(limit: int = 5) -> str:
 
35
  try:
36
  r = requests.get(CWA_ALARM_API, timeout=10)
37
  r.raise_for_status()
38
  payload = r.json()
39
  except Exception as e:
40
  return f"❌ 地震預警查詢失敗:{e}"
 
41
  items = payload.get("data", [])
42
  if not items: return "✅ 目前沒有地震預警。"
 
43
  def _key(it):
44
  try: return datetime.fromisoformat(it.get("originTime", "").replace("Z", "+00:00"))
45
  except: return datetime.min.replace(tzinfo=timezone.utc)
 
46
  items = sorted(items, key=_key, reverse=True)
47
  lines = ["🚨 地震預警(最新):", "-" * 20]
48
  for it in items[:limit]:
 
52
  identifier = str(it.get('identifier', '—')).replace('{', '{{').replace('}', '}}')
53
  msg_type = str(it.get('msgType', '—')).replace('{', '{{').replace('}', '}}')
54
  msg_no = str(it.get('msgNo', '—')).replace('{', '{{').replace('}', '}}')
 
 
55
  location_desc_list = it.get('locationDesc')
56
+ areas_str = ", ".join(str(area) for area in location_desc_list) if isinstance(location_desc_list, list) and location_desc_list else "—"
 
 
 
57
  areas = areas_str.replace('{', '{{').replace('}', '}}')
 
58
  mag_str = f"{mag:.1f}" if mag is not None else "—"
59
  depth_str = f"{depth:.0f}" if depth is not None else "—"
60
  lines.append(
61
  f"事件: {identifier} | 類型: {msg_type}#{msg_no}\n"
62
  f"規模/深度: M{mag_str} / {depth_str} km\n"
63
  f"時間: {tw_str}(台灣)\n"
64
+ f"地點: {areas}"
65
  )
66
  return "\n\n".join(lines).strip()
67
 
 
84
  })
85
  df = pd.DataFrame(rows)
86
  if not df.empty and "Time" in df.columns:
87
+ # [修正] 加入 utc=True 參數,確保所有時間都有 UTC 時區資訊
88
+ df["Time"] = pd.to_datetime(df["Time"], errors="coerce", utc=True).dt.tz_convert(TAIPEI_TZ)
89
  return df
90
 
91
  def fetch_significant_earthquakes(days: int = 7, limit: int = 5) -> str:
 
115
  return f"❌ 顯著地震查詢失敗:{e}"
116
 
117
  def fetch_latest_significant_earthquake() -> dict | None:
118
+ try:
119
+ if not CWA_API_KEY: raise ValueError("錯誤:尚未設定 CWA_API_KEY Secret。")
120
+ params = {"Authorization": CWA_API_KEY, "format": "JSON", "limit": 1, "orderby": "OriginTime desc"}
121
+ r = requests.get(CWA_SIGNIFICANT_API, params=params, timeout=15)
122
+ r.raise_for_status()
123
+ data = r.json()
124
+ df = _parse_significant_earthquakes(data)
125
+ if df.empty: return None
126
 
127
+ latest_eq_data = df.iloc[0].to_dict()
128
+
129
+ quakes = data.get("records", {}).get("Earthquake", [])
130
+ if quakes:
131
+ latest_eq_data["ImageURL"] = quakes[0].get("ReportImageURI")
132
 
133
+ if pd.notna(latest_eq_data.get("Time")):
134
+ latest_eq_data["TimeStr"] = latest_eq_data["Time"].strftime('%Y-%m-%d %H:%M')
135
 
136
+ return latest_eq_data
137
+ except Exception as e:
138
+ # 將錯誤傳遞給上層函式,以便在 LINE 上顯示
139
+ raise e