recurring-charge-detector について
取引履歴から定期的な支出と収入を検出し、状態と年換算額を追跡するスキルです。アクティブ判定には最低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) でグループ化します。サブスクリプションが複数の口座に分かれる場合があるため、各口座を独立した系列として扱います。(Netflix の請求がクレジットカード A からクレジットカード B に移った場合は、2 つのクラスターとして表示されます。エージェント層がこれらを照合します。)
ステップ 2 — 並べ替えとしきい値
取引が 3 件未満のグループは除外します。まだ確定できないため、evidence_count を付けて candidates[] として提示し、蓄積中であることがユーザーにわかるようにします。
ステップ 3 — 周期の分類
日数単位で差分 d_i = date[i+1] - date[i] を計算します。上記の表を使って周期を分類します。判定ルールは次のとおりです。
- 差分の 80% 以上が、周期の許容範囲内に収まること。
- 元の周期で再び発生している場合に限り、大きな差分(
> 2× nominal)を最大 1 回まで許容します。これは「1 か月飛ばし」のパターンでは一般的です。
複数の周期が当てはまりそうな場合は、適合度がより高いもの(差分の標準偏差が最小のもの)を選びます。
ステップ 4 — 金額の安定性
クラスター内の金額の平均 μ と標準偏差 σ を計算します。
σ / |μ|≤ 0.05 → 「固定」(例:Netflix の毎月 15.99 ドル)。σ / |μ|≤ 0.20 → 「変動小」(季節によって金額が変動する公共料金など)。σ / |μ|≤ 0.40 → 「変動大」(例:食料品の配達。警告を付けますが、許可します)。σ / |μ|> 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"。 - 2 回連続で未検出 →
status: "cancelled"をevent: "likely_cancelled"とともに提案します。
cancelled に切り替える前に、エージェント層が確認します。
ステータス遷移
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
}
}
ガードレール
- 最低でも 3 件必要です。 同じ加盟店で 1 か月離れた 2 件の請求だけでは不十分です。2 件目は、単発のサブスクリプションのトライアルかもしれません。
- 説明ではなく加盟店でクラスタリングします。
description_rawではなく、カテゴリ分類器のmerchantフィールドを使用します。同じ加盟店名でも表示が異なると、実際には定期的な取引が 2 つの架空の取引に分かれてしまいます。 - 入金も対象です。 給与は定期的な
income.salaryなので、追跡します。キャッシュフロー予測には、収入と支出の両方が必要です。 - 金額が変動する公共料金。 公共料金には
variable_lowを許容します。変動によるノイズを予測器に伝播させず、平均値であるamount_cents_typicalを使用します。 - タックスロスハーベスティング/リバランス取引は、同じ証券で規則的に発生するため、表面的には定期的に見えることがあります。
category: savings_investment.*の証券口座の取引は、周期が年 1 回以上でない限り、定期取引への昇格対象外として扱います。 - 休止状態はキャンセル済みとは異なります。 休止状態を提示し、キャンセルはユーザーに確認してもらいます。正当な理由で 1 か月分をスキップする加盟店もあります。