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

Update cwa_service.py

Browse files
Files changed (1) hide show
  1. cwa_service.py +92 -60
cwa_service.py CHANGED
@@ -5,94 +5,126 @@ 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, CWA_LOCAL_EQ_API
9
 
10
  TAIPEI_TZ = timezone(timedelta(hours=8))
11
 
12
- # ... (檔案上半部分的 Helper Functions 和其他 fetch 函式不變) ...
13
  # --- Helper Functions ---
14
  def _to_float(x):
15
- # ...
 
 
16
  return float(m.group()) if m else None
17
 
18
  def _parse_cwa_time(s: str) -> tuple[str, str]:
19
- # ...
20
- return (tw, utc)
 
 
 
 
 
 
21
 
22
  # --- 地震預警 (CWA_ALARM_API) ---
23
  def fetch_cwa_alarm_list(limit: int = 5) -> str:
24
- # ... (此函式內容不變) ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  return "\n\n".join(lines).strip()
26
 
27
  # --- 顯著有感地震 (E-A0015-001) ---
28
  def _parse_significant_earthquakes(obj: dict) -> pd.DataFrame:
29
- # ... (此函式內容不變) ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  return df
31
 
32
  def fetch_significant_earthquakes(days: int = 7, limit: int = 5) -> str:
33
- # ... (此函式內容不變) ...
34
- return "\n\n".join(lines)
35
-
36
-
37
- # --- [新功能] 小區域有感地震 (E-A0016-001) ---
38
- def _normalize_cwa_area_name(area_name: str) -> str:
39
- """自動校正縣市名稱,以符合 CWA API 查詢需求"""
40
- area_name = area_name.replace("台", "臺")
41
- if area_name.endswith("市") or area_name.endswith("縣"):
42
- return area_name
43
-
44
- major_cities = "臺北,新北,基隆,桃園,新竹,臺中,嘉義,臺南,高雄".split(',')
45
- if any(city in area_name for city in major_cities):
46
- return f"{area_name}市"
47
- else:
48
- return f"{area_name}縣"
49
-
50
- def fetch_local_earthquakes(area_name: str, limit: int = 3) -> str:
51
- """從 CWA 獲取指定地區的小區域有感地震報告"""
52
  if not CWA_API_KEY:
53
- return "❌ 查詢失敗:管理者尚未設定 CWA_API_KEY。"
54
 
55
- normalized_area = _normalize_cwa_area_name(area_name)
 
 
56
 
57
- params = {
58
- "Authorization": CWA_API_KEY,
59
- "format": "JSON",
60
- "limit": limit,
61
- "AreaName": normalized_area,
62
- }
63
  try:
64
- r = requests.get(CWA_LOCAL_EQ_API, params=params, timeout=15)
65
  r.raise_for_status()
66
  data = r.json()
67
-
68
- earthquakes = data.get("records", {}).get("Earthquake", [])
69
- if not earthquakes:
70
- return f"✅ 在「{normalized_area}」近期沒有小區域有感地震報告。"
71
 
72
- lines = [f"🚨 「{normalized_area}」近期小區域有感地震:", "-" * 20]
73
- for eq in earthquakes:
74
- info = eq.get("earthquakeInfo", {})
75
- epi = info.get("epiCenter", {})
76
- mag = info.get("magnitude", {})
77
- depth = info.get("depth", {})
78
 
79
- # 提取各地區震度資訊
80
- intensity_areas = eq.get("intensity", {}).get("shakingArea", [])
81
- area_strs = []
82
- for area in intensity_areas:
83
- # 僅顯示有回報測站的地區
84
- if area.get("areaIntensity") and float(area.get("areaIntensity", 0)) > 0:
85
- area_strs.append(f"{area.get('areaDesc')} {area.get('areaIntensity')}級")
86
- intensity_str = "、".join(area_strs) if area_strs else "無具體震度回報"
87
-
88
  lines.append(
89
- f"報告: {eq.get('reportContent', '—')}\n"
90
- f"時間: {info.get('originTime', '—')}\n"
91
- # f"地點: {epi.get('location', '—')}\n"
92
- f"規模: M{mag.get('magnitudeValue', '—')} | 深度: {depth.get('value', '—')} km\n"
93
- f"主要影響區域: {intensity_str}"
94
  )
95
  return "\n\n".join(lines)
96
 
97
  except Exception as e:
98
- 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
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
  # --- 地震預警 (CWA_ALARM_API) ---
30
  def fetch_cwa_alarm_list(limit: int = 5) -> str:
31
+ """抓 CWA 地震預警並格式化輸出。"""
32
+ try:
33
+ r = requests.get(CWA_ALARM_API, timeout=10)
34
+ r.raise_for_status()
35
+ payload = r.json()
36
+ except Exception as e:
37
+ return f"❌ 地震預警查詢失敗:{e}"
38
+
39
+ items = payload.get("data", [])
40
+ if not items:
41
+ return "✅ 目前沒有地震預警。"
42
+
43
+ def _key(it):
44
+ try:
45
+ return datetime.fromisoformat(it.get("originTime", "").replace("Z", "+00:00"))
46
+ except:
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"
68
+ f"時間: {tw_str}(台灣)\n"
69
+ f"預警地區: {areas}"
70
+ )
71
  return "\n\n".join(lines).strip()
72
 
73
  # --- 顯著有感地震 (E-A0015-001) ---
74
  def _parse_significant_earthquakes(obj: dict) -> pd.DataFrame:
75
+ quakes = obj.get("records", {}).get("Earthquake", [])
76
+ rows = []
77
+ for q in quakes:
78
+ ei = q.get("EarthquakeInfo", {})
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:
93
+ time_series = pd.to_datetime(df["Time"], errors="coerce")
94
+ if pd.api.types.is_datetime64_any_dtype(time_series):
95
+ df["Time"] = time_series.dt.tz_localize("UTC").dt.tz_convert(TAIPEI_TZ)
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"
124
+ f"震級: M{mag_str} | 深度: {depth_str} km\n"
125
+ f"報告: {row['URL'] or ''}"
 
126
  )
127
  return "\n\n".join(lines)
128
 
129
  except Exception as e:
130
+ return f"❌ 顯著地震查詢失敗:{e}"