cwadayi commited on
Commit
e9a6115
·
verified ·
1 Parent(s): 02b1e2b

Update cwa_service.py

Browse files
Files changed (1) hide show
  1. cwa_service.py +60 -93
cwa_service.py CHANGED
@@ -5,127 +5,94 @@ 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
 
 
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
- # [修正] 在格式化前,先對來自 API 的字串進行轉義,避免其中的 { } 字元造成錯誤
58
- identifier = str(it.get('identifier', '—')).replace('{', '{{').replace('}', '}}')
59
- msg_type = str(it.get('msgType', '—')).replace('{', '{{').replace('}', '}}')
60
- msg_no = str(it.get('msgNo', '—')).replace('{', '{{').replace('}', '}}')
61
- areas = str(it.get('alertAreas') or '—').replace('{', '{{').replace('}', '}}')
62
-
63
- mag_str = f"{mag:.1f}" if mag is not None else "—"
64
- depth_str = f"{depth:.0f}" if depth is not None else "—"
65
-
66
- lines.append(
67
- f"事件: {identifier} | 類型: {msg_type}#{msg_no}\n"
68
- f"震級/深度: M{mag_str} / {depth_str} km\n"
69
- f"時間: {tw_str}(台灣)\n"
70
- f"預警地區: {areas}"
71
- )
72
  return "\n\n".join(lines).strip()
73
 
74
  # --- 顯著有感地震 (E-A0015-001) ---
75
  def _parse_significant_earthquakes(obj: dict) -> pd.DataFrame:
76
- quakes = obj.get("records", {}).get("Earthquake", [])
77
- rows = []
78
- for q in quakes:
79
- ei = q.get("EarthquakeInfo", {})
80
- epic = ei.get("Epicenter", {})
81
- mag_info = ei.get("Magnitude", {})
82
- rows.append({
83
- "ID": q.get("EarthquakeNo"),
84
- "Time": ei.get("OriginTime"),
85
- "Lat": _to_float(epic.get("EpicenterLatitude")),
86
- "Lon": _to_float(epic.get("EpicenterLongitude")),
87
- "Depth": _to_float(ei.get("FocalDepth")),
88
- "Magnitude": _to_float(mag_info.get("MagnitudeValue")),
89
- "Location": epic.get("Location"),
90
- "URL": q.get("Web"),
91
- })
92
- df = pd.DataFrame(rows)
93
- if not df.empty and "Time" in df.columns:
94
- time_series = pd.to_datetime(df["Time"], errors="coerce")
95
- if pd.api.types.is_datetime64_any_dtype(time_series):
96
- df["Time"] = time_series.dt.tz_localize("UTC").dt.tz_convert(TAIPEI_TZ)
97
  return df
98
 
99
  def fetch_significant_earthquakes(days: int = 7, limit: int = 5) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  if not CWA_API_KEY:
101
- return "❌ 顯著地震查詢失敗:管理者尚未設定 CWA_API_KEY。"
102
 
103
- now = datetime.now(timezone.utc)
104
- time_from = (now - timedelta(days=days)).strftime("%Y-%m-%d")
105
- params = {"Authorization": CWA_API_KEY, "format": "JSON", "timeFrom": time_from}
106
 
 
 
 
 
 
 
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:
113
- return f"✅ 過去 {days} 天內沒有顯著有感地震報告。"
114
-
115
- df = df.sort_values(by="Time", ascending=False).head(limit)
116
 
117
- lines = [f"🚨 CWA 最新顯著有感地震 (近{days}天內):", "-" * 20]
118
- for _, row in df.iterrows():
119
- mag_str = f"{row['Magnitude']:.1f}" if pd.notna(row['Magnitude']) else "—"
120
- depth_str = f"{row['Depth']:.0f}" if pd.notna(row['Depth']) else "—"
 
 
 
 
 
 
121
 
 
 
 
 
 
 
 
 
 
122
  lines.append(
123
- f"時間: {row['Time'].strftime('%Y-%m-%d %H:%M') if pd.notna(row['Time']) else '—'}\n"
124
- f"地點: {row['Location'] or '—'}\n"
125
- f"震級: M{mag_str} | 深度: {depth_str} km\n"
126
- f"報告: {row['URL'] or ''}"
 
127
  )
128
  return "\n\n".join(lines)
129
 
130
  except Exception as e:
131
- 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
 
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}"