iT邦幫忙

2025 iThome 鐵人賽

DAY 14
0
自我挑戰組

IT工具與自我學IT的過程分享系列 第 14

Day 5|只在「該吵時」吵你:票數差異比對與通知策略

  • 分享至 

  • xImage
  •  

Day 5|只在「該吵時」吵你:票數差異比對與通知策略

讓通知像咖啡一樣剛剛好——該提神時出現,不該吵時安靜 ☕️ /images/emoticon/emoticon07.gif

今天把重點放在兩件事:

  1. 如何判斷票況「真的有變化」(diff)
  2. 何時要通知、何時要忍住(policy:always / on-change / 合併視窗)

一張圖秒懂:diff → policy → 通知

[diff 流程]
https://ithelp.ithome.com.tw/upload/images/20250927/20178823jJ0lgAXpKJ.png

  • 先把「上一次結果」與「這一次結果」做比對(changed / added / removed)
  • 然後套用通知策略:
    • Always:每次檢查都報告(最「吵」)
    • On-change:只有有變化才通知(多數情境最舒適)
    • On-change + 合併視窗:在一段短時間(例如 15 秒)內,把多個變化合併成一則(最不洗版)

例子:合併視窗(coalesce window)怎麼救你的訊息中心?

[合併視窗時間軸]
https://ithelp.ithome.com.tw/upload/images/20250927/20178823IizBwR6G9p.png

  • 若 5s、9s、12s 連續發生變化,同屬 15 秒視窗 → 只發一則通知
  • 28s、31s 落在下一個視窗 → 合併成一則
  • 45s、52s 第三個視窗 → 一則

結果:7 次變化 → 3 則通知。訊息不洗版,但關鍵你都知道


三種策略比較(概念示意)

[策略比較:每小時通知量]
https://ithelp.ithome.com.tw/upload/images/20250927/20178823zq59OpNAAe.png

  • Always:假設 60 次 / 小時(每分鐘一次都叫你)
  • On-change:假設實際變化 12 次 / 小時 → 通知 12 則
  • On-change + 合併(30s):同一分鐘內的多次變化併成 1 則 → 假設剩 6 則

數字僅為示意,但邏輯很直觀:越聰明的策略 = 越少噪音


可直接用:差異比對(diff)實作

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}

可直接用:通知策略(Always / On-change / 合併視窗)

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)

小劇場:三位使用者如何選擇策略?

  1. 會議滿檔的「小芸」:policy="coalesce", coalesce_sec=30 → 一段時間只收一則,不洗版。

  2. 隨時盯著手機的「阿豪」:policy="on_change" → 有變化就叫我,立即。

  3. 正在蹲秒殺補票的「阿傑」:policy="always"(短時間內)→ 手刀模式,等有票就衝。

30 秒 Smoke Test(模擬「變化 → 通知」)

# 假設這是每次檢查解析出的「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

今天作業(3 步)

  1. 把你的實際活動頁跑出兩次結果,丟進 diff_areas 看看差異。
  2. 換三種策略各跑一遍,感受通知頻率。
  3. 試著把 coalesce_sec 改成 15 / 30 / 60 秒,找出你最舒服的節奏。

明天預告(Day 6)

來把通知內容變更漂亮、更好懂:座位圖、價位/區域重點、以及「只通知我在乎的區域」的格式設計。 /images/emoticon/emoticon07.gif


上一篇
Day 4|如何「看穿」 ibon 頁面:HTML → jsonData → 票區
下一篇
Day 6|把訊息變好懂:LINE 推播排版(含座位圖與區塊)
系列文
IT工具與自我學IT的過程分享16
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言