一句話:對月捐/混合捐者而言,電話通路整體並未失效(接通率 54.5%),所以不該用「打不通比例」整批清名單 —— 「未接通」幾乎都是時段/可及性問題(號碼無效僅 0.4%),不是空號。真正能零誤殺、立即停撥的只有一小群「打很多次、從沒接通」的人;更大的機會在「久未聯繫名單」與「把量能往高價值層/高接受度活動傾斜」。
Bottom line: for Regular/Mixed donors the phone channel is not broadly dead (54.5% connect rate), so do not purge lists by "Not-Reached %" — "Not Reached" is almost entirely a timing/availability issue (invalid numbers only 0.4%), not dead lines. The only zero-false-removal group safe to stop immediately is the small set called many times yet never connected. The bigger upside is the "long-neglected" list and tilting capacity toward higher-value tiers and higher-acceptance campaigns.
主母體 51,306 人=依 GPEA 官方三欄捐款分類篩出(定義見第 10 節)。三欄含義:
The 51,306 main cohort is filtered by GPEA's official three-column classification (defs in §10):
| 分類欄位Classification field | 分組Group | 人數Count |
|---|---|---|
| 捐款類型Giving Type | 月捐 RegularRegular | 35,814 |
| 混合捐 MixedMixed | 15,492 | |
| 價值層Value Tier | 一般捐者General Donor | 36,592 |
| 中額潛力Middle Prospect | 12,758 | |
| 中額捐者Middle Donor | 1,956 | |
| 捐款狀態Giving Status | Active | 34,669 |
| Reactivated | 9,698 | |
| At Risk | 4,982 | |
| New | 1,957 |
為何把單筆捐(3,268 人)分開? 經查證,這群人 100% 沒有生效中的定期定額(3,267/3,268)。他們之所以也標為 Active/New/Reactivated,是因為這些狀態對單筆捐者改以「單次捐款的時近度」判定(New 1,431、Active 1,340、Reactivated 497;無 At Risk)—— 與月捐者「定期定額仍在扣款」的 Active 是兩種不同的『活躍』。混在一起會把「最近捐過一次的人」誤當成「活躍月捐者」,故另列第 8 節。
Why separate the 3,268 Single donors? Verified: this group has essentially no active recurring donation (3,267/3,268). They are labelled Active/New/Reactivated because for single-gift donors those statuses are driven by single-gift recency (New 1,431, Active 1,340, Reactivated 497; no At Risk) — a different kind of "active" than a monthly donor's live recurring payment. Blending them would misread "someone who gave once recently" as an "active monthly donor", so they are reported separately in §8.
依「先給答案、再給證據」原則前置。標 Placed first per BLUF. Items marked 估算 Est. 者含假設、非實測。 rest on an assumption, not a measurement.
| # | 建議Recommendation | 優先Pri. |
|---|---|---|
| 1 | 先停撥 1,127 位空撥對象(≥5 通、0 接通,7,965 通)並轉多管道。唯一零誤殺、可立即執行。低接通名單整體不可直接清除 —— 須先做閾值敏感度+時段接通分析。Stop the 1,127 wasted-dial contacts (≥5 calls, 0 connects, 7,965 dials) and move to other channels. The only zero-risk, immediate action. Do not purge the whole low-connect list — run threshold-sensitivity + time-of-day analysis first. | 高 High |
| 2 | 把撥打時段移到傍晚+中午、優先週三至週五:傍晚 17–19 點接通率 59–68%(上午 9–10 點僅 51–53%)卻撥打量最低。不增加人力即可拉高接通率 —— 先 A/B 驗證再全面調整。Shift dialling to evening + noon, favour Wed–Fri: 17–19h connects at 59–68% (vs 51–53% at 09–10h) yet has the lowest volume. Lifts connect rate with no extra headcount — A/B test before full rollout. | 高 High |
| 3 | 處理 2,276 位「5 年未致電」名單:先扣除已被勿擾排除的約 522 人(22.9%),其餘約 1,750 位無勿擾旗標者列為優先觸及測試池 —— 零疲勞、零空號包袱的新機會。Work the 2,276 "never-called-in-5y" list: remove the ~522 (22.9%) with a do-not-contact flag; treat the remaining ~1,750 as a priority test pool — fresh, no fatigue or dead-number baggage. | 高 High |
| 4 | 量能向中額/中額潛力層+黃金時段傾斜:中額層接通後答應率 67.6%(一般層 1.47 倍 估算)、最好接通,且金額更大 —— 每次升級月加碼 $452、單筆均 $3,338,約一般層 2.5–3.4 倍。停撥/降頻規則須分層套用。Tilt capacity toward Middle / Middle-Prospect tiers + prime hours: Middle acceptance-when-reached 67.6% (1.47× General Est.), easiest to reach, and bigger amounts — monthly upgrade uplift $452, one-off avg $3,338, ~2.5–3.4× General. Apply stop/throttle rules by tier. | 高 High |
| 5 | Active 狀態但電話難觸及者改走多管道(簡訊/Email/LINE),不是從名單移除 —— 接通難易只代表電話不通,不代表捐款不健康。Active-status but hard-to-reach donors → switch channels (SMS/Email/LINE), do not remove — low reachability means the phone isn't working, not that giving is unhealthy. | 中 Med |
| 6 | 升級邀請設每月配額上限+建立準實驗對照追蹤退捐率 —— 升級拒絕量大(約 4 萬通 估算),有關係侵蝕風險但缺對照無法定量。Cap monthly upgrade-ask quota + build a quasi-experimental control to track churn — upgrade declines are large (~40k calls Est.), a relationship-erosion risk that can't be quantified without a control. | 中 Med |
| 7 | 單筆捐者另策對待:3,268 人捐款傾向明顯較低(見第 8 節),電訪優先級應較低,或改以低成本管道(Email/自動化)邀請轉為月捐。Treat Single donors separately: 3,268 people, markedly lower propensity (§8) — lower phone priority, or convert-to-monthly via low-cost channels (Email/automation). | 中 Med |
| 8 | 建立傾向分數:以「價值層級 × 活動類型 × 個人接通/答應史」排序名單,取代平均撥打。Build a propensity score: rank lists by value tier × campaign type × personal connect/accept history, instead of uniform dialling. | 中 Med |
以兩種立場檢視同一份已驗證數據 —— 電訪營運(座席工時/名單衛生/撥打效率)與 募款策略(捐者價值/活動組合/傾向)—— 互相挑戰後留下的共識洞察。所有數字皆引用已驗證資料,推算一律標 估算 並寫明假設。立場取捨未一致處列於末段。(本節以月捐/混合捐主母體為準;單筆捐見第 8 節。)
The same verified data examined from two stances — Operations (agent hours / list hygiene / dial efficiency) and Fundraising strategy (donor value / campaign mix / propensity) — challenged against each other; the consensus insights remain. All figures cite verified data; any projection is marked Est. with its assumption. Unresolved trade-offs are listed at the end. (Main Regular/Mixed cohort; Single donors in §8.)
近五年 293,632 通中,未接通佔 38.8%(114,018 通),遠高於拒絕的 19.8%(58,179 通);接通率僅 54.5%。但號碼無效只佔 0.4%,兩者量級差近百倍。絕大多數未接通是時段/可及性問題,不是該清的空號。
Of 293,632 calls in 5y, Not Reached is 38.8% (114,018), far above Decline at 19.8% (58,179); connect rate is 54.5%. But invalid numbers are only 0.4% — nearly 100× smaller. The vast majority of "Not Reached" is a timing/availability issue, not a dead number to purge.
以「未接通 >70%」定義的低接通名單有 7,316 人(已撥打的 14.9%),但這是自設切點、門檻一動數字就變。真正命中率確定為零的硬子集,是 1,127 位撥 ≥5 通、0 次接通者,累積 7,965 通空撥 —— 僅佔總撥打的 2.72%(估算)。
The low-connect list (Not-Reached >70%) is 7,316 people (14.9% of those dialled), but that's a self-set threshold — move it and the count moves. The truly zero-hit hard subset is 1,127 people called ≥5 times with 0 connects — 7,965 wasted dials, just 2.72% of all dials (Est.).
依捐款狀態,低接通比例 Active 15.7%、Reactivated 10.6%、At Risk 9.9% —— 最該擔心流失的 At Risk 反而最容易接通,仍穩定捐的 Active 反而最難接通。證明「>70% 未接通」純是電話可達性指標,與捐款健康脫鉤。
By status, the low-connect share is Active 15.7%, Reactivated 10.6%, At Risk 9.9% — the at-risk donors are the easiest to reach, while steadily-giving Active donors are the hardest. This proves "Not-Reached >70%" is purely a reachability metric, decoupled from giving health.
接通後答應率隨層上升:一般 46.1%、中額潛力 55.4%、中額 67.6%(中額約一般層 1.47 倍 估算)。難觸及比例:一般 15.1%、中額潛力 12.4%、中額 6.2%(中額僅一般層 41% 估算)。但無各層成交絕對量,只能定方向。
Acceptance-when-reached rises by tier: General 46.1%, Middle Prospect 55.4%, Middle 67.6% (Middle ~1.47× General Est.). Low-connect share: General 15.1%, Middle Prospect 12.4%, Middle 6.2% (Middle only 41% of General Est.). No per-tier absolute conversion counts, so direction only.
兩活動接通率不同(升級 49.9% vs 特別專案 46.7%),直接比全體同意率(14.6% vs 18.6%)會把接通難易度誤算進成效。固定在接通者基礎:特別專案接通後同意率 39.9%、升級 29.2%,差 10.7 個百分點。但這只是單通命中率:升級的代理存續年限均值 2.44 年(中位 3)約為特別專案 1.27 年(中位 1)近兩倍(代理指標)。金額面見第 7 節。
Connect rates differ (Upgrade 49.9% vs Special Appeal 46.7%), so comparing all-call yes-rates (14.6% vs 18.6%) bakes reachability into the result. On the reached-basis: Special Appeal accepts 39.9% vs Upgrade 29.2% — a 10.7-pt gap. But that's single-call hit rate only: the proxy "years experienced" averages Upgrade 2.44 (median 3) vs Special Appeal 1.27 (median 1) (Proxy). Money comparison in §7.
升級全體拒絕率 28.5% 高於特別專案 22.4%,且升級佔總致電約七成(143,024 通)。換算拒絕量約 40,762 通(估算),是其同意量近兩倍。反覆對同一捐款人發升級被拒,有侵蝕關係風險;但無退捐/流失欄位、也無未致電對照組。
Upgrade's all-call decline rate (28.5%) exceeds Special Appeal's (22.4%), and Upgrade is ~70% of all calls (143,024). That's ~40,762 declines (Est.), nearly 2× its accepts. Repeatedly asking the same donor to upgrade and being refused risks eroding the relationship — but there's no churn field and no never-called control.
每位撥打中位與平均皆 6 通,但最大值 82、11–20 通有 1,806 人、≥21 通 17 人,合計 1,823 人被撥 ≥11 通(約被撥者 3.7% 估算)。疲勞與超撥風險集中此尾。但無按次數帶的同意產出,無法證明高頻撥打邊際同意已歸零。
Median and mean dials per donor are both 6, but the max is 82; 1,806 people got 11–20 calls and 17 got ≥21 — 1,823 dialled ≥11 times (~3.7% of those called Est.). Fatigue/over-dial risk concentrates in this tail. No accept-by-frequency-band data exists to prove marginal acceptance has hit zero.
主母體 51,306 人中,2,276 人五年內完全沒被撥打過。但這不是乾淨的純機會名單:其中 522 人(22.9%)帶勿擾旗標(DoNotCall 483/拒絕聯絡 19/拒絕募款邀請 510;遠高於有撥打組的 6.8% 基準),須維持不撥。扣除後約 1,750 人是真正未觸及、可啟用對象。
Of the 51,306 cohort, 2,276 were never dialled in 5y. But it's not a clean opportunity list: 522 (22.9%) carry a suppression flag (DoNotCall 483 / Do-Not-Contact 19 / Fundraising Opt-Out 510 — far above the 6.8% baseline among those called) and must stay excluded. The remaining ~1,750 are genuinely untouched and activatable.
本節數字由 Salesforce 匯出資料直接計算(月捐/混合捐母體)。低接通名單(對應 ticket 的 Cold List)= 近 5 年「Not Reached」佔比 > 70%;其餘為可接通名單(Warm List)。同列「全體被打過」與「撥打 ≥5 通(較穩健)」兩口徑。Computed directly from the Salesforce export (Regular/Mixed cohort). Low-connect list (the ticket's "Cold List") = Not-Reached share > 70% over 5y; the rest is the reachable "Warm List". Shown for "all dialled" and "≥5 calls (more robust)".
| 口徑Basis | 低接通Low-connect | 可接通Reachable | 合計Total | 低接通占比Low-connect % |
|---|---|---|---|---|
| 全體近 5 年被電訪者All dialled in 5y | 7,316 | 41,714 | 49,030 | 14.9% |
| 撥打 ≥5 通者(較穩健)≥5 calls (robust) | 4,936 | 30,167 | 35,103 | 14.1% |
| 近 5 年從未被電訪Never dialled in 5y | 2,276(另成一類「久未聯繫」)2,276 (separate "long-neglected" class) | 4.4% | ||
兩口徑都落在 ~14–15%:約每 7 位被電訪者有 1 位電話接通率過低。但此切點敏感、且接通難易與捐款脫鉤,不可直接清除(見第 3 節①②③)。Both bases land at ~14–15%: roughly 1 in 7 dialled donors has a too-low connect rate. But the threshold is sensitive and reachability is decoupled from giving — don't purge (see §3 ①②③).
低接通以「一般捐者」最多(3,776 人 / 15.1%);越高價值越打得通(中額僅 6.2%)。Low-connect is concentrated in General donors (3,776 / 15.1%); higher value = easier to reach (Middle only 6.2%).
| Giving Status | 低接通Low-connect | 該狀態 ≥5 通人數≥5-calls in status | 低接通占比Low-connect % |
|---|---|---|---|
| Active | 3,797 | 24,159 | 15.7% |
| Reactivated | 863 | 8,167 | 10.6% |
| At Risk | 273 | 2,762 | 9.9% |
| New | 3 | 15 | — |
最難接通的是 Active、最容易接通的是 At Risk —— 印證「接通難易 ≠ 捐款健康」。Hardest to reach = Active; easiest = At Risk — confirming reachability ≠ giving health.
可直接使用的名單檔:exports/20260617-1812-tw-tfr-per-donor-list-v2.csv(每位捐款人一列,含 giving_type 可區分月捐/混合/單筆),欄位 list_segment(Cold=低接通/Warm=可接通/Never Called)、not_reached_pct、各結果次數、redundant_flag(空撥)、suppressed(勿擾)。可在 Excel/Looker 篩 not_reached_pct > 70 或 redundant_flag = Y。
Ready-to-use list file: exports/20260617-1812-tw-tfr-per-donor-list-v2.csv (one row per donor, with giving_type to separate Regular/Mixed/Single), columns list_segment (Cold/Warm/Never Called), not_reached_pct, per-outcome counts, redundant_flag (wasted-dial), suppressed (do-not-contact). Filter not_reached_pct > 70 or redundant_flag = Y in Excel/Looker.
中位 6 通/平均 6 通/最多 82 通。大宗落在 6–10 通;過度撥打(≥11 通)僅 1,823 人。另一端 6,128 人只被打 1–2 通+2,276 人完全沒被打 —— 「久未聯繫」比「過度撥打」更大。Median 6 / mean 6 / max 82. Bulk at 6–10; over-dialling (≥11) is only 1,823 people. At the other end, 6,128 got just 1–2 calls + 2,276 never called — "neglect" is a bigger problem than "over-dialling".
| 通話結果Outcome | 通數Calls | % | 意義Meaning |
|---|---|---|---|
| Yes | 79,173 | 27.0% | 當下接受 askaccepted the ask |
| No | 58,179 | 19.8% | 有接通但拒絕(本次 Decline 口徑)reached but declined (our "Decline") |
| Call Back | 22,621 | 7.7% | 有接通、請改期reached, asked to call back |
| Not Reached | 114,018 | 38.8% | 沒人接/忙線/語音信箱(非空號)no answer / busy / voicemail (not a dead line) |
| Invalid / Other | 19,641 | 6.7% | 號碼無效僅 0.4%(1,094 通)invalid numbers only 0.4% (1,094) |
接通率(Yes+No+Call Back)= 54.5%。電話可達性相當健康 —— 假設一對整體不成立的關鍵證據。Connect rate (Yes+No+Call Back) = 54.5%. Reachability is healthy — the key evidence that Hypothesis 1 fails for the whole cohort.
用實際撥打時間 Last_Call_Date__c(轉台灣時間)計算各時段接通率。注意:不可用 CreatedDate —— 經查它混入批次匯入(00:00/08:00 有人工尖峰),非真實撥打時間。下圖為撥打量 ≥2,000 的時段。Connect rate by hour from actual call time Last_Call_Date__c (Taiwan time). Note: CreatedDate is unusable — it is contaminated by batch imports (artificial 00:00/08:00 spikes), not real call time. Hours with ≥2,000 dials shown.
| 星期Weekday | 接通率Connect % | 撥打量Volume |
|---|---|---|
| 週三 / 週四Wed / Thu | 57.4% / 57.3% | 54,741 / 54,343 |
| 週五Fri | 56.3% | 52,887 |
| 週一 / 週二Mon / Tue | 53.7% / 54.1% | 56,935 / 59,312 |
| 週六(量少)Sat (low vol) | 61.2% | 5,115 |
| 週日(最差)Sun (worst) | 52.6% | 3,579 |
關鍵:光靠時段就有 17 個百分點的接通率落差(09:00 的 51% → 18:00 的 68%),而最會接通的傍晚 17–19 點目前撥打量最低(約 1 萬 vs 中午時段 3.5 萬)。把更多量能從上午 9–10 點移到傍晚 17–19 點+中午 12 點、優先週三至週五,可在不增加人力下拉高接通率。限制:傍晚/週六量少可能有選擇偏誤,且須遵守電訪時段法規 —— 建議小規模 A/B 驗證後再全面調整。
Key: time of day alone swings the connect rate 17 points (51% at 09:00 → 68% at 18:00), yet the best window (17–19h) currently has the lowest dial volume (~10k vs ~35k at midday). Moving capacity from 09–10h to 17–19h + 12:00, favouring Wed–Fri, raises the connect rate with no extra headcount. Caveats: low evening/Saturday volume may carry selection bias, and calling-hour regulations apply — A/B test before a full shift.
假設原文:近 5 年 >70% 標記「Not Reached」的捐者,代表電話通路已失效,應移出 TFR、改數位/線下。
驗證結論:對「全體」 ❌ 推翻;對特定子集 ✅ 成立(但仍不可僅憑此切點清名單,見第 3 節①②)。
The hypothesis: donors with >70% "Not Reached" over 5y mean the phone channel is dead and should be removed from TFR and targeted via digital/offline.
Verdict: ❌ refuted for the whole cohort; ✅ true only for a specific subset (but still don't purge by this threshold alone — see §3 ①②).
驗證結論:✅ 成立。兩類接通率接近(~47–50%),但接通後答應率差很多 —— Special Appeal 39.9% vs Upgrade 29.2%(1.37 倍)。活動類型可作預測特徵。務必用「接通後」基礎比,不可用全體率。
Verdict: ✅ confirmed. Connect rates are close (~47–50%), but acceptance-when-reached differs a lot — Special Appeal 39.9% vs Upgrade 29.2% (1.37×). Campaign type is a usable predictive feature. Always compare on the reached-basis, not all-calls.
| 指標Metric | Upgrade | Special Appeal |
|---|---|---|
| 近 5 年通數Calls (5y) | 143,024 | 59,334 |
| Yes(占全部)Yes (of all) | 14.6% | 18.6% |
| No(占全部)No (of all) | 28.5% | 22.4% |
| Not Reached | 46.2% | 46.7% |
| 接通率Connect rate | 49.9% | 46.7% |
| 接通後答應率Acceptance-when-reached | 29.2% | 39.9% |
「Upgrade」含 Annual Upgrade/New Donor Upgrade/Middle Donor Upgrade/Middle Donor Conversion(依 ticket 定義)。解讀:把人升級到更高月捐的阻力(婉拒 28.5%)比請他為特定議題加碼更大。但答應率高 ≠ 價值高(見第 7 節)。"Upgrade" covers Annual / New Donor / Middle Donor Upgrade / Middle Donor Conversion (per the ticket). Reading: resistance to raising a monthly gift (28.5% declines) is higher than asking for a one-off cause gift. But higher yes-rate ≠ higher value (see §7).
以「接通後答應率(Yes ÷ 接通數)」當捐款傾向代理指標。兩面向都呈現可用於排序名單的梯度:
Using acceptance-when-reached (Yes ÷ reached) as a propensity proxy. Both cuts show a gradient usable for list ranking:
中額捐者接通後答應率 67.6%,是一般捐者 46.1% 的 1.47 倍 估算。電訪量能應優先配給中額/中額潛力層。Middle-tier acceptance-when-reached is 67.6%, 1.47× General's 46.1% Est. Prioritise call capacity to Middle / Middle-Prospect tiers.
兩種「Yes」對應兩種金額:Upgrade 的「Yes」=把原本的定期定額加碼,價值是每月增加金額(Change Amount,存於 Recurring Donation Item);Special Appeal 的「Yes」=新增一筆單次捐款。金額為新台幣。Two kinds of "Yes" map to two kinds of money: an Upgrade "Yes" raises the existing recurring gift — value = the monthly increase (Change Amount, on the Recurring Donation Item); a Special Appeal "Yes" creates a new one-off gift. Amounts in TWD.
覆蓋率限制:79,179 通「Yes」中,Upgrade 有 47% 連得到加碼紀錄、Special Appeal 有 32% 連得到單次捐款紀錄,其餘無金額連結。金額是「有連到紀錄」子集的下限,不是 TFR 總募得金額;「口頭答應」與「系統入帳」之間有缺口。
Coverage limit: of 79,179 "Yes" calls, 47% of Upgrades link to an uplift record and 32% of Special Appeals link to a one-off; the rest have no money link. Figures are a lower bound on the linked subset, not total TFR revenue — there is a gap between a verbal "Yes" and a recorded gift.
| 活動類型Campaign type | Yes 通數Yes calls | 價值型態與金額(已連結子集)Value type & amount (linked subset) |
|---|---|---|
| Upgrade | 20,856 | 定期加碼:9,793 筆(47%)、每月平均 +$183、月加碼合計 +$1.79M → 年化約 +$21.5M 新增定期收入Recurring uplift: 9,793 (47%), avg +$183/mo, +$1.79M/mo total → ~+$21.5M annualized new recurring revenue |
| Special Appeal | 11,064 | 一次性現金:3,545 筆(32%)、平均 $1,851、合計 $6.56MOne-off cash: 3,545 (32%), avg $1,851, $6.56M total |
| 其他(回流/維護)Other (reactivation/care) | 47,259 | 幾乎無加碼或新單筆 —— 其「Yes」多為維持/續扣既有定期,非新增金額almost no uplift or new one-off — these "Yes" mostly sustain existing recurring gifts, not new money |
| 價值層Value tier | Upgrade 每月加碼均額Avg monthly uplift | Special Appeal 單筆均額Avg one-off gift |
|---|---|---|
| 一般捐者General | $133 | $1,345 |
| 中額潛力Mid Prospect | $221 | $1,983 |
| 中額捐者Middle | $452 | $3,338 |
金額面的關鍵反轉:Upgrade 雖然接通後答應率較低(29.2% vs 39.9%),但帶來「每月複利」的定期加碼 —— 年化約 +$21.5M,遠大於 Special Appeal 的一次性 $6.56M。所以「答應率高」不等於「價值高」。Upgrade = 長期定期成長引擎;Special Appeal = 即時現金。
The value reversal: Upgrade has a lower acceptance-when-reached (29.2% vs 39.9%), but it delivers compounding monthly recurring uplift — ~+$21.5M annualized, far above Special Appeal's one-off $6.56M. So "higher yes-rate" does not mean "higher value". Upgrade = long-term recurring growth engine; Special Appeal = immediate cash.
金額也證實層級梯度更陡:中額捐者每次升級月加碼 $452,是一般捐者 $133 的 3.4 倍 估算;單筆均 $3,338,是一般層 2.5 倍。Amounts confirm a steeper tier gradient: Middle's monthly upgrade uplift $452 is 3.4× General's $133 Est.; one-off avg $3,338 is 2.5× General's.
資料來源改用 BigQuery 倉儲表 donor_deveopment_performance(已含 TFR 結果 × RD 加碼金額 × 逐月留存 × 退捐)—— LTV 所需資料倉儲早已具備,不需重建。Source: BigQuery warehouse table donor_deveopment_performance (already has TFR outcome × RD uplift × monthly retention × cancellation) — the LTV data already exists in the warehouse; no rebuild needed.
| 升級後留存(第 N 個月仍在扣款)Post-upgrade retention (still paying at month N) | M3 | M6 | M9 | M12 |
|---|---|---|---|---|
| 仍在扣款比例% still paying 已驗證 | 94.4% | 93.2% | 90.0% | 86.9% |
一次成功升級的價值 ≈ 第一年 NT$1,580(並延續到第 2 年以後)。算法:每月加碼約 $156(倉儲口徑;直接拉 Salesforce 為 $183,同量級)× 第一年實際扣款約 10.1 個月 = ~NT$1,580 第一年增量 估算。因 87% 升級者滿 12 個月仍在扣款,這筆加碼會持續進帳 —— 多年累積是第一年的數倍。對比 Special Appeal 一次性:平均每筆 NT$2,138、每位捐者約 1.23 筆 = ~NT$2,630/人(一次性、不複利)。
One successful upgrade ≈ NT$1,580 in year 1 (and continues into year 2+). Method: monthly uplift ~$156 (warehouse; Salesforce direct gives $183, same order) × ~10.1 actual paid months in year 1 = ~NT$1,580 first-year increment Est. Because 87% of upgraders are still paying at month 12, the uplift keeps accruing — the multi-year total is several times the first year. Versus a Special Appeal one-off: avg NT$2,138/gift × ~1.23 gifts/donor = ~NT$2,630/donor (one-off, no compounding).
結論:升級的「每月複利」價值約在第 2 年追過特別專案的一次性金額;3 年期一次升級的累積增量(粗估 NT$4,000+)明顯高於一筆特別專案。限制:第一年增量穩健,跨年 LTV 含留存外推假設(估算);倉儲電話留存欄位歷有資料缺口,已挑非缺口子集計算。Conclusion: the upgrade's compounding value overtakes the special-appeal one-off by ~year 2; over 3 years one upgrade (rough NT$4,000+ cumulative) clearly beats one special-appeal gift. Caveat: the first-year increment is robust; multi-year LTV carries a retention-extrapolation assumption (Est.); the warehouse telephone-retention field has historical gaps — a non-gap subset was used.
| 維度Dimension | 平均Mean | 中位Median |
|---|---|---|
| 經歷過幾個「年度的 Upgrade 活動」Upgrade campaign-years experienced | 2.44 | 3 |
| 經歷過幾個「年度的 Special Appeal 活動」Special Appeal campaign-years experienced | 1.27 | 1 |
以「有被該類活動電訪的不同年份數」作為活動波次代理(多數活動名稱不帶年份/波次,故用年份計,非真實波數)。Uses "distinct years with a call of that campaign type" as a wave proxy (most campaign names lack year/wave granularity, so years are used — not true wave counts).
為何單獨看:這 3,268 位歷來只有單次捐款、且 100% 沒有生效中定期定額的捐款者,其 Active/New/Reactivated 狀態是依「單次捐款時近度」判定,與月捐者「定期定額仍在扣款」是兩種不同的『活躍』,混在一起會誤導。
Why separate: these 3,268 donors gave only single gifts historically and have essentially no active recurring donation; their Active/New/Reactivated status is set by single-gift recency, a different "active" than a monthly donor's live recurring payment — blending would mislead.
| Giving Status | 人數Count | 對單筆捐者的意義Meaning for single donors |
|---|---|---|
| New | 1,431 | 近期首次捐款的新單筆捐者(最多)recent first-time single donor (largest) |
| Active | 1,340 | 近期有單次捐款、判為活躍recent single gift → flagged active |
| Reactivated | 497 | 沉睡後又單次捐款回流lapsed then gave a single gift again |
| At Risk | 0 | —(屬定期定額降額情境,單筆不適用)— (a recurring-downgrade state; N/A to single) |
有定期定額者僅 1/3,268。換言之,這群的「活躍」完全來自單次捐款的時近度,不是月捐。Only 1/3,268 has a recurring donation. Their "active" is entirely single-gift recency, not monthly giving.
| 指標Metric | 單筆捐者Single | 月捐/混合(對照)Regular/Mixed (ref) |
|---|---|---|
| 人數People | 3,268 | 51,306 |
| 近 5 年曾被電訪比例% ever called in 5y | 47% | 96% |
| 近 5 年從未被電訪Never called in 5y | 1,723 (53%) | 2,276 (4%) |
| 近 5 年通數Calls (5y) | 3,222 | 293,632 |
| 接通率Connect rate | 48.9% | 54.5% |
| 接通後答應率Acceptance-when-reached | 28.0% | 49.5% |
| 低接通占比(已撥打者)Low-connect % (of dialled) | 32.6% | 14.9% |
建議:單筆捐者不宜與月捐者放同一電訪策略 —— 電訪優先級應較低;要轉化為月捐,用低成本管道(Email/自動化/一次性轉月捐 ask)測試更划算,把高成本真人電訪量能留給月捐/混合捐主母體與中額層。
Recommendation: don't put Single donors on the same phone strategy as monthly donors — lower phone priority; to convert them to monthly, test low-cost channels (Email/automation/one-tap convert-to-monthly), reserving costly live calls for the Regular/Mixed cohort and the Middle tier.
Recurring_Item__c.Change_Amount__c(47%)、Special Appeal 用 Donation__c.Amount__c(32%)。仍缺:(a) 約半數~六成「Yes」無金額連結;(b) 跨年完整 LTV(加碼維持時長、單筆回頭率)需追後續扣款序列。Amounts linked but partial: Upgrade via Recurring_Item__c.Change_Amount__c (47%), Special Appeal via Donation__c.Amount__c (32%). Still missing: (a) ~half–60% of "Yes" have no money link; (b) full multi-year LTV (uplift persistence, one-off repeat) needs the subsequent payment sequence.Market__c=Taiwan ∧ Giving_Status__c∈{Active,Reactivated,New,At Risk} ∧ Giving_Type__c∈{Regular, Mixed Giving Donor} ∧ Value_Tier__c∈{General, Middle Donor Prospect, Middle Donor}。單筆捐=同條件但 Giving_Type__c='Single Giving Donor'。Main cohort filter (Salesforce Contact): Market__c=Taiwan ∧ Giving_Status__c∈{Active,Reactivated,New,At Risk} ∧ Giving_Type__c∈{Regular, Mixed Giving Donor} ∧ Value_Tier__c∈{General, Middle Donor Prospect, Middle Donor}. Single = same but Giving_Type__c='Single Giving Donor'.donation-flow.md。Giving Type=依捐款方式的歷史紀錄;Giving Status=生命週期(月捐/混合捐四種要求 RD 生效中,單筆捐依單次時近度);Value Tier=依金額分級。Definition source: GPEA 2025 Classification, in Obsidian wiki donation-flow.md. Giving Type = by historical giving mode; Giving Status = lifecycle (the four Regular/Mixed states require an active RD; for Single it's single-gift recency); Value Tier = by gift size.Case(單一 Master record type),每通電話一筆。TFR call object = Salesforce Case (single Master record type), one row per call.TFR_Call_Outcome__c(Yes/No/Call Back/Not Reached/Invalid Data/Other)。活動類型=Campaign_Name__c 字串前綴(99.85%;標準 Campaign lookup 多空)。金額:Upgrade 用 Recurring_Item__c.Change_Amount__c、單筆用 Donation__c.Amount__c。LTV/留存=BigQuery donor_deveopment_performance。Outcome = TFR_Call_Outcome__c. Campaign type = Campaign_Name__c prefix (99.85% populated; standard Campaign lookup mostly empty). Amounts: Upgrade via Recurring_Item__c.Change_Amount__c, one-off via Donation__c.Amount__c. LTV/retention = BigQuery donor_deveopment_performance.Last_Call_Date__c(非 CreatedDate)。Windows: outcomes last 5y (≥2021-06-17); campaign history last 10y (≥2016-01-01). Low-connect = Not Reached > 70%. Reached = Yes+No+Call Back. Time-of-day uses Last_Call_Date__c (not CreatedDate).exports/20260617-1810-cohort-cases-10y-v2.csv、…-cohort-contacts-v2.csv(含勿擾旗標);金額 …-1850-tfr-yes-amounts-v2.csv;時段 …-1910-tfr-callkitime.csv。Exports: exports/20260617-1810-cohort-cases-10y-v2.csv, …-cohort-contacts-v2.csv (with suppression flags); amounts …-1850-tfr-yes-amounts-v2.csv; time-of-day …-1910-tfr-callkitime.csv.exports/20260617-1812-tfr-analysis-v2.py(主母體=Regular+Mixed 過濾;單筆捐=Single 過濾);LTV 查 BigQuery donor_deveopment_performance。Aggregation script exports/20260617-1812-tfr-analysis-v2.py (main = Regular+Mixed filter; Single = Single filter); LTV via BigQuery donor_deveopment_performance.產生:2026-06-17 · 資料擷取自 Salesforce 正式環境 00D2v000001f7liEAA · 分類定義 Obsidian wiki donation-flow.md · 對應 Asana 任務「TW Active RG Donor TFR Cold / Warm List Analysis & Donation Propensity」(MCW-7491)。Generated 2026-06-17 · data from Salesforce production 00D2v000001f7liEAA · classification defs Obsidian wiki donation-flow.md · Asana task "TW Active RG Donor TFR Cold / Warm List Analysis & Donation Propensity" (MCW-7491).
本頁為彙總分析(不含個資/聯絡方式/捐款金額),僅供內部募款決策參考。Aggregate analysis (no PII / contact details / individual donation amounts), for internal fundraising decisions only.
目的:看完這個分頁,下一個人(或 AI)就能把「報告」分頁上的每一個數字從頭重算一次。下方依序給:環境與存取 → 來源物件/欄位 → 三步重現流程 → 每個數字的來源對照表 → 完整查詢與程式。所有 SQL/SOQL/指令可直接複製執行。
Goal: after reading this tab, the next person (or AI) can recompute every number on the Report tab from scratch. Below, in order: environment & access → source objects/fields → 3-step pipeline → number-by-number lineage table → full queries & code. All SQL/SOQL/commands are copy-runnable.
00D2v000001f7liEAA (greenpeace-ea.my.salesforce.com). CLI = Salesforce CLI sf; queries run with --target-org gpea-prod. Read-only.gpea-data, dataset report_table. CLI = bq, auth uchen@greenpeace.org.a0J2u000001NjoNEAS (object Market__c, Name='Taiwan'). Contact.Market__c 是 lookup,不是字串,故 filter 用此 ID。exports/;彙總主腳本 20260617-1812-tfr-analysis-v2.py。| 物件Object | 關鍵欄位Key fields |
|---|---|
Case(TFR 通話,每通一筆)(TFR call, 1/row) | TFR_Call_Outcome__c (Yes/No/Call Back/Not Reached/Invalid Data/Other) · Campaign_Name__c (Upgrade/Special Appeal 分類來源) · ContactId · CreatedDate · Last_Call_Date__c (真實撥打時間) · Result_Recurring_Item__c → Recurring_Item__c.Change_Amount__c (升級加碼) · Result_One_off_Donation__c → Donation__c.Amount__c (單筆) |
Contact | Giving_Status__c · Giving_Type__c · Value_Tier__c · Market__c · DoNotCall · Do_Not_Contact__c · Fundraising_Appeals_Opt_Out__c · Has_Recurring_Donation__c · Active_Recurring_Donation_Count__c |
BigQuerydonor_deveopment_performance | category (Upgrade/Special Appeal/…) · type (Upgrade/Oneoff/Regular) · amount (升級=加碼額) · original_amount · mon_1..mon_12 (逐月是否仍扣款) · since_anchor · cancel_date · market='Taiwan' |
定義:5 年窗 = CreatedDate ≥ 2021-06-17;10 年窗 = ≥ 2016-01-01。接通 = Yes+No+Call Back。低接通 = Not Reached 佔比 > 70%。主母體 = Regular+Mixed;單筆捐 = Single。Definitions: 5y window = CreatedDate ≥ 2021-06-17; 10y = ≥ 2016-01-01. Reached = Yes+No+Call Back. Low-connect = Not-Reached share > 70%. Main cohort = Regular+Mixed; Single = Single.
第 1 步 — 從 Salesforce 匯出(Bulk API)Step 1 — export from Salesforce (Bulk API)
# cohort contacts (+ suppression flags) → 20260617-1810-cohort-contacts-v2.csv sf data export bulk --target-org gpea-prod -r csv --output-file cohort-contacts-v2.csv -q " SELECT Id, Giving_Status__c, Giving_Type__c, Value_Tier__c, DoNotCall, Do_Not_Contact__c, Fundraising_Appeals_Opt_Out__c FROM Contact WHERE Market__c='a0J2u000001NjoNEAS' AND Giving_Status__c IN ('Active','Reactivated','New','At Risk') AND Giving_Type__c IN ('Regular Giving Donor','Mixed Giving Donor','Single Giving Donor') AND Value_Tier__c IN ('General Donor','Middle Donor Prospect','Middle Donor')" # 10y TFR cases → 20260617-1810-cohort-cases-10y-v2.csv sf data export bulk --target-org gpea-prod -r csv --output-file cohort-cases-10y-v2.csv -q " SELECT ContactId, TFR_Call_Outcome__c, Campaign_Name__c, CreatedDate FROM Case WHERE TFR_Call_Outcome__c!=null AND CreatedDate>=2016-01-01T00:00:00Z AND Contact.Market__c='a0J2u000001NjoNEAS' AND Contact.Giving_Status__c IN ('Active','Reactivated','New','At Risk') AND Contact.Giving_Type__c IN ('Regular Giving Donor','Mixed Giving Donor','Single Giving Donor') AND Contact.Value_Tier__c IN ('General Donor','Middle Donor Prospect','Middle Donor')"
第 2 步 — 跑彙總腳本Step 2 — run the aggregation script
# Run in the same folder as the Step-1 CSVs. Single-donor view (§8) = re-run # with Giving_Type filtered to Single in Step 1, or filter cohort dict to that type. python3 tfr-analysis.py # the full script (no local files needed): #!/usr/bin/env python3 """ TW donor TFR Cold/Warm analysis v2 — cohort now includes Single Giving Donor (SG/Oneoff) alongside Regular and Mixed (per stakeholder 2026-06-17). One pass: stats JSON + per-donor list CSV + suppression breakdown. Definitions: Decline=No; Reached=Yes+No+CallBack; Cold=Not-Reached share of last-5y calls>70%; outcome window last 5y; lifespan 10y. """ import csv, json, collections, statistics BASE = "./" # run in the same folder as the two Step-1 CSVs CONTACTS = BASE + "cohort-contacts-v2.csv" # from Step 1 CASES = BASE + "cohort-cases-10y-v2.csv" # from Step 1 OUT = BASE + "stats.json" PERDONOR = BASE + "per-donor-list.csv" FIVE_Y = "2021-06-17" OUTCOMES = ["Yes", "No", "Call Back", "Not Reached", "Invalid Data", "Other"] def classify(name): n = name.lower() if "special appeal" in n: return "Special Appeal" if "annual upgrade" in n or "new donor upgrade" in n or "middle donor upgrade" in n or "md upgrade" in n or "middle donor conversion" in n: return "Upgrade" return "Other" cohort = {} with open(CONTACTS) as f: for r in csv.DictReader(f): cohort[r["Id"][:15]] = r print(f"cohort: {len(cohort):,}") def newd(): return {"oc": collections.Counter(), "up": collections.Counter(), "sa": collections.Counter(), "upy": set(), "say": set(), "last": ""} D = collections.defaultdict(newd) with open(CASES) as f: for r in csv.DictReader(f): cid = r["ContactId"][:15] if cid not in cohort: continue date = r["CreatedDate"][:10]; yr = date[:4] subj = classify(r["Campaign_Name__c"].strip()) d = D[cid] if subj == "Upgrade": d["upy"].add(yr) elif subj == "Special Appeal": d["say"].add(yr) if date > d["last"]: d["last"] = date if date >= FIVE_Y: oc = r["TFR_Call_Outcome__c"].strip() or "Other" if oc not in OUTCOMES: oc = "Other" d["oc"][oc] += 1 if subj == "Upgrade": d["up"][oc] += 1 elif subj == "Special Appeal": d["sa"][oc] += 1 d5 = {c: d for c, d in D.items() if sum(d["oc"].values()) > 0} S = {"generated": "2026-06-17", "cohort_total": len(cohort), "donors_called_10y": len(D), "donors_called_5y": len(d5), "never_called_5y": len(cohort) - len(d5)} # breakdowns def brk(field): c = collections.Counter(v[field] for v in cohort.values()) return dict(c) S["by_giving_type"] = brk("Giving_Type__c") S["by_value_tier"] = brk("Value_Tier__c") S["by_giving_status"] = brk("Giving_Status__c") # overall outcome ov = collections.Counter() for d in d5.values(): ov.update(d["oc"]) tot = sum(ov.values()) S["total_calls_5y"] = tot S["outcome_n"] = {k: ov.get(k, 0) for k in OUTCOMES} S["outcome_pct"] = {k: round(100*ov.get(k, 0)/tot, 1) for k in OUTCOMES} S["reach_rate_pct"] = round(100*(ov["Yes"]+ov["No"]+ov["Call Back"])/tot, 1) # calls per donor def band(n): return "1-2" if n<=2 else "3-5" if n<=5 else "6-10" if n<=10 else "11-20" if n<=20 else "21+" cd = collections.Counter() cpd = [] for d in d5.values(): n = sum(d["oc"].values()); cpd.append(n); cd[band(n)] += 1 S["calls_per_donor_band"] = {b: cd.get(b, 0) for b in ["1-2","3-5","6-10","11-20","21+"]} S["calls_per_donor"] = {"median": statistics.median(cpd), "mean": round(statistics.mean(cpd),1), "max": max(cpd)} # cold list def nr(d): n=sum(d["oc"].values()); return d["oc"]["Not Reached"]/n if n else 0 def reached(d): return d["oc"]["Yes"]+d["oc"]["No"]+d["oc"]["Call Back"] cold_all = [c for c,d in d5.items() if nr(d)>0.70] ge5 = {c:d for c,d in d5.items() if sum(d["oc"].values())>=5} cold_ge5 = [c for c,d in ge5.items() if nr(d)>0.70] S["cold"] = {"all": {"cold": len(cold_all), "total": len(d5), "pct": round(100*len(cold_all)/len(d5),1)}, "ge5": {"cold": len(cold_ge5), "total": len(ge5), "pct": round(100*len(cold_ge5)/len(ge5),1)}} cset = set(cold_ge5) bt=collections.Counter(); btt=collections.Counter(); bs=collections.Counter(); bst=collections.Counter() for c,d in ge5.items(): t=cohort[c]["Value_Tier__c"]; s=cohort[c]["Giving_Status__c"]; btt[t]+=1; bst[s]+=1 if c in cset: bt[t]+=1; bs[s]+=1 S["cold_by_tier"]={t:{"cold":bt[t],"total":btt[t],"pct":round(100*bt[t]/btt[t],1)} for t in btt} S["cold_by_status"]={s:{"cold":bs[s],"total":bst[s],"pct":round(100*bs[s]/bst[s],1)} for s in bst} wasted=[(c,sum(d["oc"].values())) for c,d in d5.items() if sum(d["oc"].values())>=5 and reached(d)==0] S["redundant"]={"donors":len(wasted),"calls":sum(n for _,n in wasted)} # upgrade vs appeal up=collections.Counter(); sa=collections.Counter() for d in d5.values(): up.update(d["up"]); sa.update(d["sa"]) def blk(c): t=sum(c.values()); r=c["Yes"]+c["No"]+c["Call Back"] return {"calls":t,"pct":{k:round(100*c.get(k,0)/t,1) for k in OUTCOMES}, "reached_pct":round(100*r/t,1) if t else 0, "yes_of_reached_pct":round(100*c["Yes"]/r,1) if r else 0} S["upgrade_vs_appeal"]={"upgrade":blk(up),"special_appeal":blk(sa)} # propensity by tier prop={} for tier in ["General Donor","Middle Donor Prospect","Middle Donor"]: y=n=cb=0 for c,d in d5.items(): if cohort[c]["Value_Tier__c"]==tier: y+=d["oc"]["Yes"]; n+=d["oc"]["No"]; cb+=d["oc"]["Call Back"] r=y+n+cb; prop[tier]={"reached":r,"yes":y,"yes_pct":round(100*y/r,1) if r else 0} S["propensity_by_tier"]=prop # lifespan upl=[len(d["upy"]) for d in D.values()]; sal=[len(d["say"]) for d in D.values()] S["lifespan"]={"upgrade_mean":round(statistics.mean(upl),2),"upgrade_median":statistics.median(upl), "sa_mean":round(statistics.mean(sal),2),"sa_median":statistics.median(sal)} # suppression on never-called def T(v): return str(v).lower()=="true" never=[c for c in cohort if c not in d5] def supc(ids,field): return sum(1 for i in ids if T(cohort[i].get(field,""))) nev_any=sum(1 for i in never if T(cohort[i]["DoNotCall"]) or T(cohort[i]["Do_Not_Contact__c"]) or T(cohort[i]["Fundraising_Appeals_Opt_Out__c"])) called_any=sum(1 for i in d5 if T(cohort[i]["DoNotCall"]) or T(cohort[i]["Do_Not_Contact__c"]) or T(cohort[i]["Fundraising_Appeals_Opt_Out__c"])) S["suppression_never_called"]={"n":len(never),"DoNotCall":supc(never,"DoNotCall"), "Do_Not_Contact":supc(never,"Do_Not_Contact__c"),"FR_OptOut":supc(never,"Fundraising_Appeals_Opt_Out__c"), "any":nev_any,"any_pct":round(100*nev_any/len(never),1),"activatable":len(never)-nev_any} S["suppression_called_baseline_pct"]=round(100*called_any/len(d5),1) json.dump(S, open(OUT,"w"), ensure_ascii=False, indent=1) # per-donor CSV with open(PERDONOR,"w",newline="") as f: w=csv.writer(f) w.writerow(["contact_id","giving_status","giving_type","value_tier","calls_5y","yes","decline_no","call_back","not_reached","not_reached_pct","reached_pct","list_segment","redundant_flag","upgrade_calls_5y","special_appeal_calls_5y","upgrade_years_10y","special_appeal_years_10y","last_call_date","suppressed"]) for cid,info in cohort.items(): d=D.get(cid); supp="Y" if (T(info["DoNotCall"]) or T(info["Do_Not_Contact__c"]) or T(info["Fundraising_Appeals_Opt_Out__c"])) else "" if not d or sum(d["oc"].values())==0: w.writerow([cid,info["Giving_Status__c"],info["Giving_Type__c"],info["Value_Tier__c"],0,0,0,0,0,"","","Never Called (5y)","",0,0,len(d["upy"]) if d else 0,len(d["say"]) if d else 0,d["last"] if d else "",supp]); continue c5=sum(d["oc"].values()); nrn=d["oc"]["Not Reached"]; rc=reached(d) seg="Cold" if nrn/c5>0.70 else "Warm" w.writerow([cid,info["Giving_Status__c"],info["Giving_Type__c"],info["Value_Tier__c"],c5,d["oc"]["Yes"],d["oc"]["No"],d["oc"]["Call Back"],nrn,round(100*nrn/c5,1),round(100*rc/c5,1),seg,"Y" if (c5>=5 and rc==0) else "",sum(d["up"].values()),sum(d["sa"].values()),len(d["upy"]),len(d["say"]),d["last"],supp]) # print print(json.dumps(S, ensure_ascii=False, indent=1))
第 3 步 — 金額、LTV、時段(額外查詢)Step 3 — amounts, LTV, time-of-day (extra queries)
# §7 金額:升級加碼 + 單筆 → 20260617-1850-tfr-yes-amounts-v2.csv sf data export bulk --target-org gpea-prod -r csv -q " SELECT Contact.Value_Tier__c, Campaign_Name__c, Result_Recurring_Item__r.Change_Amount__c, Result_One_off_Donation__r.Amount__c FROM Case WHERE TFR_Call_Outcome__c='Yes' AND CreatedDate>=2021-06-17T00:00:00Z AND Contact.Market__c='a0J2u000001NjoNEAS' AND Contact.Giving_Type__c IN ('Regular Giving Donor','Mixed Giving Donor') ...同上cohort" # 升級價值=Change_Amount(加碼額,非全額);單筆價值=Donation.Amount。各別 groupby 活動/層級。 # §7 LTV / 留存(BigQuery) bq query --use_legacy_sql=false " SELECT AVG(mon_3) m3, AVG(mon_6) m6, AVG(mon_9) m9, AVG(mon_12) m12, AVG(amount) avg_uplift FROM \`gpea-data.report_table.donor_deveopment_performance\` WHERE market='Taiwan' AND category='Upgrade' AND type='Upgrade'" # 第一年實扣月數 = SUM(AVG(mon_2..mon_12)) where since_anchor>=12 → 10.12;LTV ≈ uplift×月數。 # §5 時段:用 Last_Call_Date__c(非 CreatedDate,後者混批次匯入)→ 20260617-1910-tfr-callkitime.csv sf data export bulk --target-org gpea-prod -r csv -q " SELECT Last_Call_Date__c, TFR_Call_Outcome__c FROM Case WHERE TFR_Call_Outcome__c!=null AND Last_Call_Date__c!=null AND CreatedDate>=2021-06-17T00:00:00Z AND ...同上cohort(Regular+Mixed)" # Python: Last_Call_Date__c[:19] 解析 +8 小時(台灣);接通率 = reached/total 按小時與星期 groupby。
第 3 步的兩段彙總程式(無本機檔案,貼上即可跑)Step-3 aggregation snippets (no local files needed)
# §7 金額:升級加碼(Change_Amount) + 單筆(Donation) → 依活動 / 依層級 import csv, collections def classify(n): n=n.lower() if "special appeal" in n: return "Special Appeal" if any(s in n for s in ["annual upgrade","new donor upgrade","middle donor upgrade","md upgrade","middle donor conversion"]): return "Upgrade" return "Other" def f(x): try: return float(x) except: return None rows=list(csv.DictReader(open("tfr-yes-amounts.csv"))) # from Step-3 SOQL export agg=collections.defaultdict(lambda:{"yes":0,"chg_n":0,"chg_sum":0.0,"oo_n":0,"oo_sum":0.0}) for r in rows: key=classify(r["Campaign_Name__c"]) # for by-tier: key=r["Contact.Value_Tier__c"] g=agg[key]; g["yes"]+=1 chg=f(r["Result_Recurring_Item__r.Change_Amount__c"]) # upgrade uplift (the delta) oo =f(r["Result_One_off_Donation__r.Amount__c"]) # special-appeal one-off if chg is not None: g["chg_n"]+=1; g["chg_sum"]+=chg if oo is not None: g["oo_n"]+=1; g["oo_sum"]+=oo # Upgrade 月加碼均 = chg_sum/chg_n (= +183); 月總 = chg_sum; 年化 = chg_sum*12 (~21.5M) # Special Appeal 單筆均 = oo_sum/oo_n (= 1,851); 總 = oo_sum (= 6.56M) # §5 時段接通率:用真實撥打時間 Last_Call_Date__c(非 CreatedDate) from datetime import datetime, timedelta REACHED={"Yes","No","Call Back"} byh=collections.defaultdict(lambda:[0,0]); bywd=collections.defaultdict(lambda:[0,0]) # [reached,total] for r in csv.DictReader(open("tfr-callkitime.csv")): # from Step-3 SOQL export try: dt=datetime.strptime(r["Last_Call_Date__c"][:19],"%Y-%m-%dT%H:%M:%S")+timedelta(hours=8) except: continue rc=1 if r["TFR_Call_Outcome__c"].strip() in REACHED else 0 byh[dt.hour][0]+=rc; byh[dt.hour][1]+=1 bywd[dt.weekday()][0]+=rc; bywd[dt.weekday()][1]+=1 # 每小時接通率 = byh[h][0]/byh[h][1](09:00=51.1% ... 18:00=68.4%);星期同理用 bywd
「來源」欄:SOQL=Salesforce 查詢;script=第 2 步腳本欄位;bq=BigQuery;calc=由前述數字算得(估算)。"Source": SOQL = Salesforce query; script = field from the Step-2 script; bq = BigQuery; calc = derived from the above (estimate).
| 章節§ | 數字Number | 怎麼拿到的How obtained |
|---|---|---|
| 1 | 51,306 | SOQL COUNT(Id) on Contact, cohort filter (Regular+Mixed) |
| 1 | 35,814 / 15,492 | SOQL GROUP BY Giving_Type__c |
| 1 | 36,592 / 12,758 / 1,956 | SOQL GROUP BY Value_Tier__c |
| 1 | 34,669 / 9,698 / 4,982 / 1,957 | SOQL GROUP BY Giving_Status__c |
| 1·5 | 293,632 | script: count cases with CreatedDate ≥ 2021-06-17 (Regular+Mixed) |
| 1·5 | Yes 27.0% (79,173) / No 19.8% / Call Back 7.7% / Not Reached 38.8% / Invalid 0.4% | script overall_outcome (count by TFR_Call_Outcome__c, ÷ total) |
| 1·5 | 54.5% connect | calc: (Yes+No+Call Back) ÷ total |
| 1·4 | 7,316 (14.9%) / 4,936 (14.1%) | script cold: per-donor Not-Reached share > 0.70 (all-called / ≥5-calls) |
| 1·6 | 1,127 / 7,965 | script redundant: ≥5 calls AND reached=0; wasted = sum of their calls |
| 1·8 | 2,276 never called | calc: cohort − donors with ≥1 call in 5y |
| 1·3 | 522 (22.9%); baseline 6.8% | merge suppression CSV with never-called set; any of DoNotCall/Do_Not_Contact/Fundraising_Appeals_Opt_Out |
| 3·7 | 46.1% / 55.4% / 67.6% | script propensity_by_tier: Yes ÷ (Yes+No+Call Back) per Value_Tier |
| 4 | cold by tier 15.1 / 12.4 / 6.2% | script cold_by_tier (≥5-calls basis) |
| 4 | cold by status 15.7 / 10.6 / 9.9% | script cold_by_status |
| 5 | calls/donor 6,128 / 13,919 / 27,160 / 1,806 / 17; median 6; max 82 | script calls_per_donor_band |
| 6 | Upgrade 143,024 / Special Appeal 59,334 | script: count by classify(Campaign_Name__c) |
| 6 | Upgrade 29.2% / Special Appeal 39.9% (accept-when-reached) | script upgrade_vs_appeal: Yes ÷ reached per subject |
| 7 | +$183/mo · +$1.79M/mo · ~+$21.5M annualized (Upgrade) | amounts CSV: AVG/SUM of Result_Recurring_Item__r.Change_Amount__c for Upgrade-Yes; ×12 |
| 7 | $1,851 · $6.56M (Special Appeal) | amounts CSV: AVG/SUM of Result_One_off_Donation__r.Amount__c |
| 7 | by-tier $133/$221/$452 · $1,345/$1,983/$3,338 | amounts CSV grouped by Value_Tier__c |
| 7 | retention 94.4 / 93.2 / 90.0 / 86.9% | bq: AVG(mon_3/6/9/12), category='Upgrade' type='Upgrade', market='Taiwan' |
| 7 | uplift $156 · 10.1 months · LTV ≈ NT$1,580 | bq AVG(amount); SUM AVG(mon_2..12) where since_anchor≥12; calc product |
| 7 | Special Appeal $2,138 × 1.23 = ~$2,630 | bq: category='Special Appeal' type='Oneoff'; gifts ÷ distinct donors |
| 7 | lifespan 2.44 / 1.27 | script: distinct call-years per donor per subject, then AVG |
| 5 | hour reach 51.1%→68.4%; weekday 52.6–61.2% | callkitime CSV: parse Last_Call_Date__c +8h; reached ÷ total by hour / weekday |
| 8 | SG 3,268; New 1,431 / Active 1,340 / Reactivated 497 / At Risk 0 | SOQL Single filter; GROUP BY Giving_Status__c |
| 8 | SG no-RD 3,267/3,268 | SOQL GROUP BY Has_Recurring_Donation__c (Single) |
| 8 | SG 47% called · 28.0% accept · 32.6% low-connect | same script logic, cohort filtered to Giving_Type__c='Single Giving Donor' |
| — | 2.72% / 1.47× / 1.48× / 3.4× / 3.7% / 40,762 估算 Est. | calc: ratios/products of the verified numbers above (formula stated inline in the Report tab) |
Last_Call_Date__c,不可用 CreatedDate —— 後者混入批次匯入(台灣時間 00:00/08:00 假尖峰),會讓時段分析失真。Use Last_Call_Date__c for time-of-day, never CreatedDate — the latter is batch-import-contaminated (fake 00:00/08:00 TW spikes).Recurring_Item__c.Change_Amount__c(連結率 47%),不可用全額 Result_Recurring_Donation__r.Amount__c(只 7% 連結且是全額非增量)。Value an upgrade by the uplift Recurring_Item__c.Change_Amount__c (47% linked), not the full Result_Recurring_Donation__r.Amount__c (only 7% linked and it's the full amount, not the delta).Campaign_Name__c 字串前綴(覆蓋率 99.85%),標準 Campaign__c lookup 多為空、不可用。Campaign type comes from the Campaign_Name__c string prefix (99.85% populated); the standard Campaign__c lookup is mostly empty — unusable.Active_Recurring_Donation_Count__c>0 ≈ 5.1 萬,但 having_active_gpsea_rd__c 只 1,426 —— 本分析以官方三欄分類為準,勿用後者判定活躍。"Active RD" fields disagree: Active_Recurring_Donation_Count__c>0 ≈ 51k but having_active_gpsea_rd__c only 1,426 — use the official three-column classification, not the latter flag.since_anchor≥12 的成熟子集。Amounts are a lower bound: only 47% (Upgrade) / 32% (one-off) of "Yes" link to a money record; the warehouse telephone-retention field has gaps — LTV uses the mature since_anchor≥12 subset.完整可執行腳本與原始 CSV 皆在 deliverable 的 exports/;本附錄之指令為精簡版(cohort 過濾條件以「…同上」省略,請套用第 1 步的完整 WHERE)。Full runnable scripts and raw CSVs are in the deliverable's exports/; commands here are abbreviated (cohort filters shown as "…same as above" — apply the full WHERE from Step 1).