讓通知像咖啡一樣剛剛好——該提神時出現,不該吵時安靜 ☕️
今天把重點放在兩件事:
[diff 流程]
[合併視窗時間軸]
結果:7 次變化 → 3 則通知。訊息不洗版,但關鍵你都知道。
[策略比較:每小時通知量]
數字僅為示意,但邏輯很直觀:越聰明的策略 = 越少噪音。
def diff_areas(old: list, new: list):
"""輸入兩個清單(含 code/name/status),回傳變化。"""
old_map = {a["code"]: a for a in old}
new_map = {a["code"]: a for a in new}
changed, added, removed = [], [], []
for code, a in new_map.items():
if code not in old_map:
added.append(a); continue
if old_map[code]["status"] != a["status"]:
changed.append({"from": old_map[code], "to": a})
for code, a in old_map.items():
if code not in new_map:
removed.append(a)
return {"changed": changed, "added": added, "removed": removed}
import time
def should_notify(change: dict, policy="on_change", last_sent_ts=None, coalesce_sec=15):
"""
policy: "always" | "on_change" | "coalesce"
last_sent_ts: 上次送出通知的 unix time(用於合併視窗)
coalesce_sec: 視窗長度(秒)
回傳 (need_notify: bool, new_last_sent_ts: float)
"""
now = time.time()
has_change = bool(change["changed"] or change["added"] or change["removed"])
if policy == "always":
return True, now
if policy == "on_change":
return has_change, (now if has_change else last_sent_ts)
if policy == "coalesce":
# 僅在有變化時考慮;若在視窗內已經發過,這次就合併不再送
if not has_change:
return False, last_sent_ts
if last_sent_ts is None or now - last_sent_ts >= coalesce_sec:
# 視窗外 → 送一次、開新視窗
return True, now
else:
# 視窗內 → 合併(不重送)
return False, last_sent_ts
# 預設策略
return has_change, (now if has_change else last_sent_ts)
有些區域顯示「熱賣中」沒有數字;或你只在乎從 0 → 有票這種躍遷。加個閾值更貼心。
def is_value_event(prev_status: str, new_status: str, focus_min=1):
"""
僅在『從沒票 → 有票(≥ focus_min)』或『數量顯著變動』時回傳 True。
prev/new_status 可為數字字串或「熱賣中/已售完」。
"""
def to_int(s):
return int(s) if (isinstance(s, str) and s.isdigit()) else None
a, b = to_int(prev_status), to_int(new_status)
if a is None and b is not None and b >= focus_min:
return True
if a is not None and b is not None and abs(b - a) >= focus_min:
return True
return False
def format_change_message(event, change):
ev = event["event"]
lines = [
f"🎫 {ev['title']}",
f"📍 {ev['venue']} 🗓 {ev['datetime']}",
"",
"🔔 變化摘要:"
]
for c in change["changed"][:5]:
lines.append(f" - {c['to']['name']}: {c['from']['status']} → {c['to']['status']}")
for a in change["added"][:3]:
lines.append(f" + 新增區域:{a['name']}({a['status']})")
for r in change["removed"][:3]:
lines.append(f" - 移除區域:{r['name']}")
if sum(map(len,[change["changed"], change["added"], change["removed"]])) > 8:
lines.append("…(其餘變化已省略)")
return "\n".join(lines)
會議滿檔的「小芸」:policy="coalesce", coalesce_sec=30 → 一段時間只收一則,不洗版。
隨時盯著手機的「阿豪」:policy="on_change" → 有變化就叫我,立即。
正在蹲秒殺補票的「阿傑」:policy="always"(短時間內)→ 手刀模式,等有票就衝。
# 假設這是每次檢查解析出的「areas_cache」快照(簡化版)
snapshots = [
[{"code":"A1","name":"A1","status":"已售完"},
{"code":"A2","name":"A2","status":"已售完"}],
[{"code":"A1","name":"A1","status":"5"},
{"code":"A2","name":"A2","status":"已售完"}],
[{"code":"A1","name":"A1","status":"2"},
{"code":"A2","name":"A2","status":"1"}],
]
last = snapshots[0]
last_sent = None
for i in range(1, len(snapshots)):
new = snapshots[i]
chg = diff_areas(last, new)
need, last_sent = should_notify(chg, policy="coalesce", last_sent_ts=last_sent, coalesce_sec=30)
print(f"step {i}, change? {bool(chg['changed'] or chg['added'] or chg['removed'])}, notify? {need}")
last = new
來把通知內容變更漂亮、更好懂:座位圖、價位/區域重點、以及「只通知我在乎的區域」的格式設計。