cwadayi commited on
Commit
fa70216
·
verified ·
1 Parent(s): 111608a

Update cwa_service.py

Browse files
Files changed (1) hide show
  1. cwa_service.py +102 -15
cwa_service.py CHANGED
@@ -9,37 +9,125 @@ from config import CWA_API_KEY, CWA_ALARM_API, CWA_SIGNIFICANT_API, CWA_LOCAL_EQ
9
 
10
  TAIPEI_TZ = timezone(timedelta(hours=8))
11
 
12
- # ... (檔案上半部分的 Helper Functions 和其他 fetch 函式不變) ...
13
  def _to_float(x):
14
- # ...
 
 
15
  return float(m.group()) if m else None
16
 
17
  def _parse_cwa_time(s: str) -> tuple[str, str]:
18
- # ...
19
- return (tw, utc)
 
 
 
 
 
 
20
 
21
  def _normalize_cwa_area_name(area_name: str) -> str:
22
- # ...
23
- return f"{area_name}縣"
 
 
 
 
 
 
 
 
24
 
 
25
  def fetch_cwa_alarm_list(limit: int = 5) -> str:
26
- # ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  return "\n\n".join(lines).strip()
28
 
 
29
  def _parse_significant_earthquakes(obj: dict) -> pd.DataFrame:
30
- # ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  return df
32
 
33
  def fetch_significant_earthquakes(days: int = 7, limit: int = 5) -> str:
34
- # ...
35
- return "\n\n".join(lines)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  # --- 小區域有感地震 (E-A0016-001) ---
38
  def fetch_local_earthquakes(area_name: str = "", limit: int = 5) -> str:
39
- """
40
- [修改] 從 CWA 獲取小區域有感地震報告。
41
- 如果 area_name 為空,則查詢全台灣。
42
- """
43
  if not CWA_API_KEY: return "❌ 查詢失敗:管理者尚未設定 CWA_API_KEY。"
44
 
45
  params = {"Authorization": CWA_API_KEY, "format": "JSON", "limit": limit}
@@ -62,7 +150,6 @@ def fetch_local_earthquakes(area_name: str = "", limit: int = 5) -> str:
62
 
63
  lines = [title, "-" * 20]
64
  for eq in earthquakes:
65
- # [修正] 使用正確的欄位名稱來解析資料
66
  info = eq.get("earthquakeInfo", {})
67
  epi = info.get("epicenter", {})
68
  mag = info.get("magnitude", {})
 
9
 
10
  TAIPEI_TZ = timezone(timedelta(hours=8))
11
 
12
+ # --- Helper Functions ---
13
  def _to_float(x):
14
+ if x is None: return None
15
+ s = str(x).strip()
16
+ m = re.search(r"[-+]?\d+(?:\.\d+)?", s)
17
  return float(m.group()) if m else None
18
 
19
  def _parse_cwa_time(s: str) -> tuple[str, str]:
20
+ if not s: return ("未知", "未知")
21
+ try:
22
+ dt = datetime.fromisoformat(s.replace("Z", "+00:00"))
23
+ tw = dt.astimezone(TAIPEI_TZ).strftime("%Y-%m-%d %H:%M")
24
+ utc = dt.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M")
25
+ return (tw, utc)
26
+ except Exception:
27
+ return (s, "未知")
28
 
29
  def _normalize_cwa_area_name(area_name: str) -> str:
30
+ """自動校正縣市名稱,以符合 CWA API 查詢需求"""
31
+ area_name = area_name.replace("台", "臺")
32
+ if area_name.endswith("市") or area_name.endswith("縣"):
33
+ return area_name
34
+
35
+ major_cities = "臺北,新北,基隆,桃園,新竹,臺中,嘉義,臺南,高雄".split(',')
36
+ if any(city in area_name for city in major_cities):
37
+ return f"{area_name}市"
38
+ else:
39
+ return f"{area_name}縣"
40
 
41
+ # --- 地震預警 (CWA_ALARM_API) ---
42
  def fetch_cwa_alarm_list(limit: int = 5) -> str:
43
+ """抓 CWA 地震預警並格式化輸出。"""
44
+ try:
45
+ r = requests.get(CWA_ALARM_API, timeout=10)
46
+ r.raise_for_status()
47
+ payload = r.json()
48
+ except Exception as e:
49
+ return f"❌ 地震預警查詢失敗:{e}"
50
+
51
+ items = payload.get("data", [])
52
+ if not items:
53
+ return "✅ 目前沒有地震預警。"
54
+
55
+ def _key(it):
56
+ try:
57
+ return datetime.fromisoformat(it.get("originTime", "").replace("Z", "+00:00"))
58
+ except:
59
+ 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]:
64
+ mag = _to_float(it.get("magnitudeValue"))
65
+ depth = _to_float(it.get("depth"))
66
+ tw_str, _ = _parse_cwa_time(it.get("originTime", ""))
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
+ areas = str(it.get('alertAreas') or '—').replace('{', '{{').replace('}', '}}')
71
+ mag_str = f"{mag:.1f}" if mag is not None else "—"
72
+ depth_str = f"{depth:.0f}" if depth is not None else "—"
73
+ lines.append(
74
+ f"事件: {identifier} | 類型: {msg_type}#{msg_no}\n"
75
+ f"震級/深度: M{mag_str} / {depth_str} km\n"
76
+ f"時間: {tw_str}(台灣)\n"
77
+ f"預警地區: {areas}"
78
+ )
79
  return "\n\n".join(lines).strip()
80
 
81
+ # --- 顯著有感地震 (E-A0015-001) ---
82
  def _parse_significant_earthquakes(obj: dict) -> pd.DataFrame:
83
+ quakes = obj.get("records", {}).get("Earthquake", [])
84
+ rows = []
85
+ for q in quakes:
86
+ ei = q.get("EarthquakeInfo", {})
87
+ epic = ei.get("Epicenter", {})
88
+ mag_info = ei.get("Magnitude", {})
89
+ rows.append({
90
+ "ID": q.get("EarthquakeNo"), "Time": ei.get("OriginTime"),
91
+ "Lat": _to_float(epic.get("EpicenterLatitude")), "Lon": _to_float(epic.get("EpicenterLongitude")),
92
+ "Depth": _to_float(ei.get("FocalDepth")), "Magnitude": _to_float(mag_info.get("MagnitudeValue")),
93
+ "Location": epic.get("Location"), "URL": q.get("Web"),
94
+ })
95
+ df = pd.DataFrame(rows)
96
+ if not df.empty and "Time" in df.columns:
97
+ time_series = pd.to_datetime(df["Time"], errors="coerce")
98
+ if pd.api.types.is_datetime64_any_dtype(time_series):
99
+ df["Time"] = time_series.dt.tz_localize("UTC").dt.tz_convert(TAIPEI_TZ)
100
  return df
101
 
102
  def fetch_significant_earthquakes(days: int = 7, limit: int = 5) -> str:
103
+ if not CWA_API_KEY: return "❌ 顯著地震查詢失敗:管理者尚未設定 CWA_API_KEY。"
104
+ now = datetime.now(timezone.utc)
105
+ time_from = (now - timedelta(days=days)).strftime("%Y-%m-%d")
106
+ params = {"Authorization": CWA_API_KEY, "format": "JSON", "timeFrom": time_from}
107
+ try:
108
+ r = requests.get(CWA_SIGNIFICANT_API, params=params, timeout=15)
109
+ r.raise_for_status()
110
+ data = r.json()
111
+ df = _parse_significant_earthquakes(data)
112
+ if df.empty: return f"✅ 過去 {days} 天內沒有顯著有感地震報告。"
113
+ df = df.sort_values(by="Time", ascending=False).head(limit)
114
+ lines = [f"🚨 CWA 最新顯著有感地震 (近{days}天內):", "-" * 20]
115
+ for _, row in df.iterrows():
116
+ mag_str = f"{row['Magnitude']:.1f}" if pd.notna(row['Magnitude']) else "—"
117
+ depth_str = f"{row['Depth']:.0f}" if pd.notna(row['Depth']) else "—"
118
+ lines.append(
119
+ f"時間: {row['Time'].strftime('%Y-%m-%d %H:%M') if pd.notna(row['Time']) else '—'}\n"
120
+ f"地點: {row['Location'] or '—'}\n"
121
+ f"震級: M{mag_str} | 深度: {depth_str} km\n"
122
+ f"報告: {row['URL'] or '無'}"
123
+ )
124
+ return "\n\n".join(lines)
125
+ except Exception as e:
126
+ return f"❌ 顯著地震查詢失敗:{e}"
127
 
128
  # --- 小區域有感地震 (E-A0016-001) ---
129
  def fetch_local_earthquakes(area_name: str = "", limit: int = 5) -> str:
130
+ """從 CWA 獲取小區域有感地震報告。如果 area_name 為空,則查詢全台灣。"""
 
 
 
131
  if not CWA_API_KEY: return "❌ 查詢失敗:管理者尚未設定 CWA_API_KEY。"
132
 
133
  params = {"Authorization": CWA_API_KEY, "format": "JSON", "limit": limit}
 
150
 
151
  lines = [title, "-" * 20]
152
  for eq in earthquakes:
 
153
  info = eq.get("earthquakeInfo", {})
154
  epi = info.get("epicenter", {})
155
  mag = info.get("magnitude", {})