cwadayi commited on
Commit
eae9f2e
·
verified ·
1 Parent(s): a5944ef

Update cwa_service.py

Browse files
Files changed (1) hide show
  1. cwa_service.py +47 -23
cwa_service.py CHANGED
@@ -5,7 +5,7 @@ import requests
5
  import re
6
  import pandas as pd
7
  from datetime import datetime, timedelta, timezone
8
- from config import CWA_API_KEY, CWA_ALARM_API, CWA_SIGNIFICANT_API
9
 
10
  TAIPEI_TZ = timezone(timedelta(hours=8))
11
 
@@ -26,6 +26,18 @@ def _parse_cwa_time(s: str) -> tuple[str, str]:
26
  except Exception:
27
  return (s, "未知")
28
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  # --- 地震預警 (CWA_ALARM_API) ---
30
  def fetch_cwa_alarm_list(limit: int = 5) -> str:
31
  """抓 CWA 地震預警並格式化輸出。"""
@@ -47,21 +59,17 @@ def fetch_cwa_alarm_list(limit: int = 5) -> str:
47
  return datetime.min.replace(tzinfo=timezone.utc)
48
 
49
  items = sorted(items, key=_key, reverse=True)
50
-
51
  lines = ["🚨 地震預警(最新):", "-" * 20]
52
  for it in items[:limit]:
53
  mag = _to_float(it.get("magnitudeValue"))
54
  depth = _to_float(it.get("depth"))
55
  tw_str, _ = _parse_cwa_time(it.get("originTime", ""))
56
-
57
  identifier = str(it.get('identifier', '—')).replace('{', '{{').replace('}', '}}')
58
  msg_type = str(it.get('msgType', '—')).replace('{', '{{').replace('}', '}}')
59
  msg_no = str(it.get('msgNo', '—')).replace('{', '{{').replace('}', '}}')
60
  areas = str(it.get('alertAreas') or '—').replace('{', '{{').replace('}', '}}')
61
-
62
  mag_str = f"{mag:.1f}" if mag is not None else "—"
63
  depth_str = f"{depth:.0f}" if depth is not None else "—"
64
-
65
  lines.append(
66
  f"事件: {identifier} | 類型: {msg_type}#{msg_no}\n"
67
  f"震級/深度: M{mag_str} / {depth_str} km\n"
@@ -79,14 +87,10 @@ def _parse_significant_earthquakes(obj: dict) -> pd.DataFrame:
79
  epic = ei.get("Epicenter", {})
80
  mag_info = ei.get("Magnitude", {})
81
  rows.append({
82
- "ID": q.get("EarthquakeNo"),
83
- "Time": ei.get("OriginTime"),
84
- "Lat": _to_float(epic.get("EpicenterLatitude")),
85
- "Lon": _to_float(epic.get("EpicenterLongitude")),
86
- "Depth": _to_float(ei.get("FocalDepth")),
87
- "Magnitude": _to_float(mag_info.get("MagnitudeValue")),
88
- "Location": epic.get("Location"),
89
- "URL": q.get("Web"),
90
  })
91
  df = pd.DataFrame(rows)
92
  if not df.empty and "Time" in df.columns:
@@ -96,28 +100,21 @@ def _parse_significant_earthquakes(obj: dict) -> pd.DataFrame:
96
  return df
97
 
98
  def fetch_significant_earthquakes(days: int = 7, limit: int = 5) -> str:
99
- if not CWA_API_KEY:
100
- return "❌ 顯著地震查詢失敗:管理者尚未設定 CWA_API_KEY。"
101
-
102
  now = datetime.now(timezone.utc)
103
  time_from = (now - timedelta(days=days)).strftime("%Y-%m-%d")
104
  params = {"Authorization": CWA_API_KEY, "format": "JSON", "timeFrom": time_from}
105
-
106
  try:
107
  r = requests.get(CWA_SIGNIFICANT_API, params=params, timeout=15)
108
  r.raise_for_status()
109
  data = r.json()
110
  df = _parse_significant_earthquakes(data)
111
- if df.empty:
112
- return f"✅ 過去 {days} 天內沒有顯著有感地震報告。"
113
-
114
  df = df.sort_values(by="Time", ascending=False).head(limit)
115
-
116
  lines = [f"🚨 CWA 最新顯著有感地震 (近{days}天內):", "-" * 20]
117
  for _, row in df.iterrows():
118
  mag_str = f"{row['Magnitude']:.1f}" if pd.notna(row['Magnitude']) else "—"
119
  depth_str = f"{row['Depth']:.0f}" if pd.notna(row['Depth']) else "—"
120
-
121
  lines.append(
122
  f"時間: {row['Time'].strftime('%Y-%m-%d %H:%M') if pd.notna(row['Time']) else '—'}\n"
123
  f"地點: {row['Location'] or '—'}\n"
@@ -125,6 +122,33 @@ def fetch_significant_earthquakes(days: int = 7, limit: int = 5) -> str:
125
  f"報告: {row['URL'] or '無'}"
126
  )
127
  return "\n\n".join(lines)
128
-
129
  except Exception as e:
130
  return f"❌ 顯著地震查詢失敗:{e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  import re
6
  import pandas as pd
7
  from datetime import datetime, timedelta, timezone
8
+ from config import CWA_API_KEY, CWA_ALARM_API, CWA_SIGNIFICANT_API, CWA_LOCAL_EQ_API
9
 
10
  TAIPEI_TZ = timezone(timedelta(hours=8))
11
 
 
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 地震預警並格式化輸出。"""
 
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"
 
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:
 
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"
 
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 = 3) -> str:
130
+ """從 CWA 獲取指定地區的小區域有感地震報告"""
131
+ if not CWA_API_KEY: return "❌ 查詢失敗:管理者尚未設定 CWA_API_KEY。"
132
+ normalized_area = _normalize_cwa_area_name(area_name)
133
+ params = {"Authorization": CWA_API_KEY, "format": "JSON", "limit": limit, "AreaName": normalized_area}
134
+ try:
135
+ r = requests.get(CWA_LOCAL_EQ_API, params=params, timeout=15)
136
+ r.raise_for_status()
137
+ data = r.json()
138
+ earthquakes = data.get("records", {}).get("Earthquake", [])
139
+ if not earthquakes: return f"✅ 在「{normalized_area}」近期沒有小區域有感地震報告。"
140
+ lines = [f"🚨 「{normalized_area}」近期小區域有感地震:", "-" * 20]
141
+ for eq in earthquakes:
142
+ info = eq.get("earthquakeInfo", {})
143
+ intensity_areas = eq.get("intensity", {}).get("shakingArea", [])
144
+ area_strs = [f"{area.get('areaDesc')} {area.get('areaIntensity')}級" for area in intensity_areas if area.get("areaIntensity") and float(area.get("areaIntensity", 0)) > 0]
145
+ intensity_str = "、".join(area_strs) if area_strs else "無具體震度回報"
146
+ lines.append(
147
+ f"報告: {eq.get('reportContent', '—')}\n"
148
+ f"時間: {info.get('originTime', '—')}\n"
149
+ f"規模: M{info.get('magnitude', {}).get('magnitudeValue', '—')} | 深度: {info.get('depth', {}).get('value', '—')} km\n"
150
+ f"主要影響區域: {intensity_str}"
151
+ )
152
+ return "\n\n".join(lines)
153
+ except Exception as e:
154
+ return f"❌ 小區域地震查詢失敗:{e}"