关于 recurring-charge-detector
用于从交易记录中识别周期性支出和收入,并跟踪状态及年化金额的 Skill。至少需要 3 次记录才会将序列标记为活跃,并区分漏付与确认取消。
主要功能
- 01按商户和账户聚类交易
- 02识别常见周期
- 03检查金额稳定性
- 04计算预计日期和年化金额
- 05跟踪休眠及取消状态变化
适用场景
- 01审查订阅和周期性支出
- 02根据交易记录估算年度金额
- 03发现漏记的周期性支出或收入
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ lyndonkl/ claude --skill recurring-charge-detector安装后在 Claude Code 中输入/recurring-charge-detector确认已加载。
详细介绍
周期性收费检测器
目录
概览
订阅审计说到底就是换着方式问同一个问题:哪些收费会定期出现?此技能会按商户 + 周期 + 典型金额对历史交易进行聚类,要求至少有 3 笔确认交易后才升级,并跟踪每个聚类的生命周期(active → suspected_dormant → cancelled)。
它会计算每笔有效周期性支出的年化成本——这是订阅审计中最实用的单个数字(“你每年在 Netflix 上要花 $191”)。
输入约定
调用方提供:
transactions— 数组,包含{id, account_id, date, merchant, amount_cents, category, subcategory}。existing_recurring— 当前的recurring.json状态。lookback_days(默认值为 540,约 18 个月)— 仅对该时间窗口内的交易进行聚类。today— 用于计算状态的 ISO 日期。
周期
| 周期 | 间隔(天) | 容差 |
|---|---|---|
weekly | 7 | ±2 |
biweekly | 14 | ±3 |
monthly | 30 | ±4 |
quarterly | 91 | ±10 |
semiannual | 182 | ±14 |
annual | 365 | ±21 |
如果交易间隔不符合上述任何周期及其对应容差,则标记为 cadence: "irregular",且不要升级。
工作流
Recurring Detection Progress:
- [ ] Step 1: Group transactions by (merchant, account_id)
- [ ] Step 2: For each group with >= 3 transactions, sort by date
- [ ] Step 3: Compute inter-arrival deltas; classify cadence
- [ ] Step 4: Check amount stability (±10% or fixed)
- [ ] Step 5: Promote to active if cadence + amount + occurrences pass
- [ ] Step 6: Compute next_expected_date and annualized cost
- [ ] Step 7: Diff against existing_recurring; emit transitions
- [ ] Step 8: Detect dormant (missed expected date by > tolerance)
第 1 步 — 分组
按 (merchant, account_id) 分组。订阅可能分散在多个账户中;将每个账户视为独立序列。(一笔从信用卡 A 转到信用卡 B 的 Netflix 收费会显示为两个聚类;Agent 层会对其进行协调。)
第 2 步 — 排序和阈值
丢弃少于 3 笔交易的分组。此类分组暂时不能升级——将其作为 candidates[] 提供,并附上 evidence_count,让用户了解正在累积的记录。
第 3 步 — 周期分类
计算间隔 d_i = date[i+1] - date[i](天)。使用上表对周期进行分类。接受规则:
- 至少 80% 的间隔落在该周期的容差范围内。
- 最多允许一个较大的间隔(
> 2× nominal),但前提是之后重新回到原周期——这在“跳过一个月”模式中很常见。
如果多个周期都可能符合,则选择拟合度更高的周期(间隔标准差最小)。
第 4 步 — 金额稳定性
计算该聚类金额的均值 μ 和标准差 σ。
σ / |μ|≤ 0.05 → “fixed”(例如,每月 Netflix 收费 $15.99)。σ / |μ|≤ 0.20 → “variable_low”(水电费等金额会随季节变化的费用)。σ / |μ|≤ 0.40 → “variable_high”(例如,生鲜配送——标记出来,但允许通过)。σ / |μ|> 0.40 → 拒绝将其视为周期性收费(可能是聚类错误的一般商户)。
记录 amount_cents_typical = round(μ) 和 amount_cents_variance = round(σ)。
第 5 步 — 升级
满足以下条件时,升级为 status: "active":
occurrences ≥ 3。cadence为命名周期之一(不是irregular)。- 金额稳定性为
fixed或variable_low(variable_high可在附带警告的情况下通过)。
第 6 步 — 前向预测
next_expected_date = last_seen + cadence_interval。每出现一笔新交易时都进行更新。
annualized_cost_cents:
| 周期 | 倍数 |
|---|---|
| weekly | 52 |
| biweekly | 26 |
| monthly | 12 |
| quarterly | 4 |
| semiannual | 2 |
| annual | 1 |
annualized_cost_cents = amount_cents_typical × multiplier。对于流入款项(工资),这是年化收入;对于流出款项,这是年化成本。
第 7 步 — 与现有数据比较差异
对于每条现有的 recurring.json 记录,查找匹配的新聚类(按 merchant + account_id + 周期匹配)。更新字段:
last_seen、occurrences、amount_cents_typical、next_expected_date。- 如果金额与之前的
amount_cents_typical相差超过 15% → 发出event: "amount_changed",并提供旧值和新值。
对于现有数据中没有的新聚类 → 发出 event: "new_recurring"。
步骤 8 — 检测休眠
对于每个活跃条目,如果 today > next_expected_date + tolerance 且时间窗口内不存在匹配的交易:
- 首次错过 →
status: "suspected_dormant"、event: "missed_expected"。 - 连续错过两次 → 提议将
status: "cancelled",并设定event: "likely_cancelled"。
将其切换为 cancelled 前,Agent 层会先确认。
状态转换
candidate (< 3 occurrences)
↓ [3rd occurrence on cadence]
active
↓ [missed expected date] ↓ [user confirms]
suspected_dormant ─ [resumed] → active paused
↓ [missed twice in a row]
likely_cancelled
↓ [user confirms]
cancelled
输出契约
{
"active": [
{
"id": "rec_netflix",
"merchant": "Netflix",
"account_id": "acc_cc_001",
"category": "entertainment.streaming",
"amount_cents_typical": 1599,
"amount_cents_variance": 0,
"cadence": "monthly",
"first_seen": "2024-03-14",
"last_seen": "2026-01-14",
"occurrences": 23,
"next_expected_date": "2026-02-14",
"annualized_cost_cents": 19188,
"status": "active"
}
],
"candidates": [
{
"merchant": "Blue Bottle Coffee",
"account_id": "acc_cc_001",
"evidence_count": 2,
"needed": 3,
"reason": "below promotion threshold"
}
],
"events": [
{ "type": "new_recurring", "id": "rec_chatgpt", "evidence_count": 3 },
{ "type": "amount_changed", "id": "rec_netflix",
"old_cents": 1499, "new_cents": 1599, "delta_pct": 6.7 },
{ "type": "missed_expected", "id": "rec_amazon_prime",
"expected_date": "2026-01-10", "today": "2026-01-20",
"suggested_status": "suspected_dormant" }
],
"summary": {
"active_count": 27,
"annualized_cost_total_cents": 487200,
"candidates": 4,
"dormant": 1
}
}
防护措施
- 至少需要三笔。 两笔相隔一个月的同商户扣款并不足够——第二笔可能只是一次性的订阅试用。
- 按商户聚类,而不是按描述聚类。 使用分类器提供的
merchant字段,而不是description_raw。否则,同一商户字符串的不同显示形式会将一笔真实的周期性交易拆成两个虚假的周期性交易。 - 流入也要计算。 薪资属于周期性
income.salary,应予以跟踪。现金流预测器需要同时考虑流入和流出。 - 金额可变的水电燃气等账单。 对这类账单允许使用
variable_low。不要将金额波动噪声传递给预测器——使用amount_cents_typical(平均值)。 - 税损收割/再平衡交易表面上可能看起来具有周期性(相同证券、规律模式)。除非周期为一年或更长,否则应将
category: savings_investment.*的经纪账户交易排除在周期性认定之外。 - 休眠 ≠ 已取消。 显示休眠状态;让用户确认是否取消。有些商户确实会合理地跳过一个月。