和泰標籤撈取程式碼 Review — 2026-05-21 (v3)

⏱ 約 7 分鐘閱讀📌 範圍: `和泰標籤撈取程式碼/` 共 4 個 .py 檔 + `20260518_...📌 對應 Andy/Xians 三項要求: (1) 各標籤邏輯複檢、(2) P12M vs P36M 差異佐證、(3)...📌 v3 更新: 補入新交付規則(Tags 人數 ≥200)、搭乘計程車分級規格、客戶 76...

一、先回答 Andy 早上問的兩個關鍵問題

Q1:「出沒地帶 226 萬人只有 162 萬人有標籤」是合併算錯還是真的沒消費?

結論:合併 bug,CK 上午已修。Excel 已驗證修復有效。

修復後 P36M 數字:

已不再是 162 萬。修法在 3_資料整合.py:42-46(stream_filter chunk 邊界 bug)。

Q2:自炊者比例過高

不是 bug,是定義門檻太鬆。整套標籤體系都有「一年買過一次就上低標」的共通設計問題,詳見 §三 D2。


二、Bug 清單

🔴 P0-1:百貨公司消費時間 — 白天/晚上 label 反了

位置2_標籤專案資料撈取加速版.py:181-186 註解定義:白天 06:00–18:00 / 晚上 18:00–06:00

when dayofweek in (0, 6) and (hour>=18 or hour<6) ... then '假日白天'   -- 18-06 標白天 ❌
when dayofweek in (0, 6) and hour>=6 and hour<18  then '假日晚上'         -- 06-18 標晚上 ❌
when dayofweek not in (0, 6) and (hour>=18 or hour<6) ... then '平日白天' -- 同上 ❌
when dayofweek not in (0, 6) and hour>=6 and hour<18  then '平日晚上'     -- 同上 ❌

Excel 佐證:百貨「假日白天」16% 違反直覺——假日午後是百貨黃金時段不可能最少。 客戶回饋佐證:和泰驗證的同事亦表示「與百貨消費時段認知不符合」。 修法:把四個分支「白天」「晚上」字串對調。

🔴 P0-2:DB 帳密明碼寫死在原始檔

位置2_*.py:20-25 + 2_*.py:62-68 + 3_*.py:14-19

明碼包含使用者帳號與密碼(Redshift production)。zip 已外傳 Andy/Xians,請 CK 知會 IT 立刻換密碼,並把交付版改成 os.getenv() 或 placeholder。

🔴 P0-3:低頻品類 fall-through bug

位置3_資料整合.py:58-72

Excel 鐵證:西裝 P36M = 5,324 < 2024 P12M = 7,703(ratio 0.69x)。三年合併後比任一年都少——邏輯上不可能,除非合併把資料丟掉。

機制:目前是 per-member fall-through。2024 買過西裝的會員 A,只要在 2026 有任何發票(即使一杯咖啡),他的所有標籤就只從 2026 取。2026 沒買西裝 → 標籤消失。

受害範圍(P36M / max(P12M) < 1.15 的低頻 tag):

Tag P36/max 三年趨勢
西裝 0.69x 7,703→5,033→3,883
共享汽車 1.03x 41,067→34,876→31,809
旅宿愛好者 1.04x 48,293→45,911→43,100
品酒族 1.05x 58,759→57,295→51,537
新生兒家庭 1.11x 138,750→134,361→131,607

對照:高頻黏著類標籤(消費時間/出沒地帶/工作時段/折扣敏感)P36/max 都在 1.27-1.28 — 這才是合理區間。

修法:per-member fall-through → per-(member, tag_type) fall-through。對每個 tag_type 獨立做年度回補。

🔴 P0-4:搭乘計程車 vax_no 過窄,2024/2025 完全壞掉

位置2_*.py:2814-2833and vax_no = '83118125'

年度 人數
2024 6
2025 1
2026 107,009

2024/2025 只撈到個位數——不是低消費,是這個 vax_no 在那兩年沒在 dw.r_invoice 出現。現行 P36M 搭乘計程車實際上等於 2026 single year,不是三年合併。

請 CK 確認:Uber 是否在某時點才換成此統編?或還有其他統編需要加入?

🔴 P0-5:搭乘計程車 tags 改為分級規格(客戶新規)

位置2_*.py:2814-2833

現行tags 是「1, 2, 3, ..., 100+」的計次字串,下游難用,且大量 (Tag_type, Tags) 組合人數 < 200(見 P0-6)。

客戶指定新規格

年度發票 < 4 張    → 低
年度發票 4–10 張   → 中
年度發票 10–25 張  → 高
年度發票 >= 25 張  → 特高

修改 SQL

select
    member_id,
    '搭乘計程車' as tag_type,
    case
        when inv_count >= 25 then '特高'
        when inv_count >= 10 and inv_count < 25 then '高'
        when inv_count >= 4 and inv_count < 10 then '中'
        when inv_count < 4 then '低'
    end as tags
from (
    select member_id, count(distinct inv_id) as inv_count
    from dw.r_invoice r
    where r.inv_date >= '{start_time}' and r.inv_date < '{end_time}'
      and r.create_time < '{create_time}'
      and vax_no = '83118125'   -- 連同 P0-4 一起補擴大名單
    group by member_id
);

🔴 P0-6:新交付規則 — Tags 人數 < 200 不應交付

規則:單一 (Tag_type, Tags) 組合人數 < 200 的不交付給和泰。

P36M 影響範圍:281 個組合需排除,分佈:

Tag_type < 200 的組合數 範例
出沒地帶 73 屏東縣霧台鄉 (6)、台東縣金峰鄉 (4)、新北市烏來區 (74) …
工作時段消費區域 73 同上偏鄉小區
非工作時段消費區域 71 同上偏鄉小區
搭乘計程車 61 「97」(27 人)、「84」(29)…改為 P0-5 分級後自動消化
汽機車品牌 3 PEUGEOT (55)、AUDI (90)、BMW (195)

實作建議:在 4_資料彙整.py / 4_資料彙整_年度.py 加入:

report = report[report['Tags人數'] >= 200]

注意:BMW 195 距 200 僅 5 人差距,若重跑後仍 < 200,需告知客戶;目前 BMW 不交付。

🟡 P1-1:SQL 邏輯運算子優先序錯(潛在 bug)

位置2_*.py:182, 185

when dayofweek in (0, 6) and (hour>=18 or hour<6) or hour is null then '假日白天'

SQL AND 優先於 OR,實際解析為:

(dayofweek in (0,6) AND (hour>=18 OR hour<6)) OR (hour IS NULL)

→ 只要 hour IS NULL 就一律落入「假日白天」分支(即使是平日)。

影響取決於 invhdr.inv_time 是否可為 null。請 CK 跟 DBA 確認。

🟡 P1-2:註解 vs 程式碼閾值不一致(至少三處)

Tag 註解 程式碼
顧客消費力 (2_*.py:1164) >=130k 高 / 45k~130k 中 / <45k 低 4 級:>=330k 特高 / >=200k 高 / >=100k 中 / <100k 低
品酒族 (2_*.py:1222) 年度發票 >=4 高 / <4 低(計次) 4 級(計金額:8100/3770/1900)
車輛充電 (2_*.py:465) 年度金額 >=6000 高 / <6000 低 >=250 高 / <250 低

車輛充電的 250 元門檻是極度偏低(一張充電發票就過),請 CK 確認 250 是不是寫錯。

請 CK 全面校對所有 tag 的註解 vs code,並更新交付給和泰的標籤說明書。

🟡 P1-3:折扣敏感度 LIKE '%優惠%' 關鍵字過寬

位置2_*.py:1198

product_name like '%買一送一%' or product_name like '%優惠%' or product_name like '%折扣%'

「優惠」會中「優惠價」「會員優惠」等非真正打折商品 → 86.4% of total 被標折扣敏感,幾乎沒有區辨力。客戶驗證亦質疑此標數量過多。

建議:改用更精準詞(「買X送X」「N折」「特價」),或加 amount 比對基準價。

🟡 P1-4:環保綠能族 LIKE '%環保%' 同樣過寬

位置2_*.py:1217 「環保袋」「環保餐具」「環保標章」都中 → 48.5% of total。建議改用 category_id 或更嚴格詞表。

🟡 P1-5:連鎖餐廳沒有 HAVING,導致外食族 < 連鎖餐廳 反直覺

位置2_*.py:726-746

客戶疑問:外食族 88,463 < 連鎖餐廳 487,839,「外食族照理應該 > 連鎖餐廳」。

SQL 對比

兩者門檻不對等 → 連鎖餐廳數字遠遠膨脹。

建議:連鎖餐廳加 having inv_count >= 27(與「<27 低」的最低 bucket 對齊),讓兩個 tag 的「低」閥值一致。預估改後連鎖餐廳人數會從 89.3% 大幅下降,外食族 > 連鎖餐廳 自然成立。

🟢 P2-1:phone 共用一支電話的 Tag 衝突

位置3_資料整合.py:91-97 若 cell_no 在 public.member 對應到多個 member_id,可能出現「同 phone 同 Tag_type 有兩個不同 Tags 值」。目前 dedup key (Phone, Tag_type, Tags) 不會丟資料,但下游若假設「一支 phone × 一個 Tag_type → 一個 Tags 值」會踩坑。

🟢 P2-2:商圈消費頻次 tags 是 integer 而非 string

位置2_*.py:1156 其他 tag 的 tags 都是 string,這個是 integer 1-50。pandas concat 會 upcast 為 object,現況不會炸,但下游若用 .str.contains 處理會踩坑。


三、設計層級議題

D2:整套標籤「低 = 有買過一次就算」

整體設計選擇,造成多個 tag 覆蓋率異常高,「低」這個 bucket 訊號強度接近 0:

Tag P36M % 「低」實際意涵
自炊者 91.5% 一年買過一次柴米油鹽
連鎖餐廳 89.3% 一年踏進過一次餐廳(見 P1-5)
折扣敏感度 86.4% 一年買過一次「優惠」品名商品
機車族 65% 一年加過一次 ≤300 元油
環保綠能族 48.5% 一年買過一次「環保」品名商品

請 Andy/Xians 評估


四、客戶 76 萬批次回饋逐項回應

客戶手上的測試批次母體為 76 萬人。注意:以下回應假設客戶手上的是修復前版本;若是修復後,數據結構應接近我們的 P36M 226 萬版本。請 CK 先確認客戶用的是哪個版本,必要時重新交付。

客戶反映 我們的判斷
消費時間 731,499 / 76 萬母體 = 96%,不是每人都有? 待確認版本。修復後應為 100%。差距可能來自 (a) 母體含非 09 開頭手機被 3_*.py:84-85 過濾,(b) 母體含無發票的會員
顧客消費力 507,103 < 消費時間 731,499 這在邏輯上不該發生——兩者都來自 public.invhdr 同一張表,母體應該相同。極有可能是修復前的 stream_filter bug 殘留。重跑後應與消費時間一致
自炊者 499,569 過多、非自炊者也被貼 D2 共通問題。一年買過一次食材就上「低」標。需拉高門檻或重新定義
折扣敏感度 495,955 過多 P1-3LIKE '%優惠%' 抓到「優惠價」這類非折扣商品
百貨公司消費時間 與認知不符 P0-1 已確認。白天/晚上 label 寫反
外食族 88,463 < 連鎖餐廳 487,839(反直覺) P1-5 新發現。連鎖餐廳沒 HAVING,「低」門檻是 ≥1 張,外食族 ≥48 張,兩個 tag 的低標不對等
車輛充電 67,626 ≈ 汽機車品牌 69,067 不是邏輯關聯,是巧合。兩個 query 完全獨立(充電通路 vs 原廠保養廠)。我們 P36M 看到差距 14k,較合理。但 P1-2 已發現車輛充電門檻為 250 元(註解寫 6000),請 CK 確認門檻是否寫錯
新生兒家庭:同事買奶粉沒被貼 category_id 範圍 (239-247 共 9 個) 可能不涵蓋 Costco/Momo/大樹的細項。請 CK 用同事 member_id 抽樣:(a) 查發票是否被歸到這 9 個 category_id,(b) 查 dim.category 看 239-247 涵蓋哪些品項,(c) 視情況加入「奶粉/尿布/嬰兒副食品」LIKE 規則或擴大 category_id 名單

五、給 Xians / Andy 的下一步建議

今天必做

本週內

待和泰回覆


Review 方法:靜態 code review + Excel 數字交叉驗證 + 客戶回饋逐項對照 + 與團隊聊天記錄交叉驗證。所有結論已二次自檢,未經驗證的推論已標明「請 CK/DBA 確認」字樣。