subscription-detection
npx machina-cli add skill peerjakobsen/smartspender/subscription-detection --openclawSubscription Detection
Purpose
Provides rules for detecting recurring charges (subscriptions) from categorized transaction data. Identifies services the user pays for on a regular basis.
Before Detection — Check Learnings
Before applying automatic detection, read learnings/subscriptions.md:
- Confirmed Subscriptions — If a merchant is listed here, always flag it as a subscription (skip automatic detection)
- Not Subscriptions — If a merchant is listed here, never flag it as a subscription (even if it appears recurring)
Only proceed with automatic detection for merchants not found in either list.
Detection Criteria
A series of transactions qualifies as a subscription when all five criteria are met:
| # | Criterion | Rule |
|---|---|---|
| 1 | Same merchant | Normalized merchant name matches across transactions |
| 2 | Regular frequency | Consistent interval between charges (monthly, yearly, weekly) |
| 3 | Amount tolerance | Amounts within ±5% of each other |
| 4 | Minimum occurrences | At least 3 transactions matching the pattern |
| 5 | Recency | Most recent occurrence within the expected interval + 7 days |
Frequency Detection
Determine the frequency by measuring the average interval between transactions:
| Frequency | Expected Interval | Tolerance |
|---|---|---|
| weekly | 7 days | ±2 days |
| monthly | 28–31 days | ±5 days |
| quarterly | 85–95 days | ±10 days |
| yearly | 355–375 days | ±15 days |
If the interval doesn't match any frequency, the charges may be irregular purchases rather than a subscription.
Amount Tolerance
Allow ±5% variation in the recurring amount to account for:
- Price adjustments
- Currency conversion fluctuations
- Tax changes
- Service tier changes
Calculate: |amount_new - amount_avg| / amount_avg <= 0.05
If the amount changes by more than 5%, flag it as a potential price increase rather than disqualifying it as a subscription.
Annual Cost Calculation
Once a subscription is detected:
- Monthly:
amount × 12 - Quarterly:
amount × 4 - Yearly:
amount × 1 - Weekly:
amount × 52
Store in the annual_cost field of subscriptions.csv.
Subscription Status
| Status | Meaning |
|---|---|
| active | Recurring charges detected within expected interval |
| paused | No charge in the last expected interval, but not confirmed cancelled |
| cancelled | User explicitly cancelled via /smartspender:cancel |
Examples
Netflix Detection
Given these transactions in categorized.csv:
2025-11-01 Netflix -149.00 Abonnementer
2025-12-01 Netflix -149.00 Abonnementer
2026-01-01 Netflix -149.00 Abonnementer
Step 1: Group by merchant
- Merchant "Netflix" has 3 transactions
Step 2: Check minimum occurrences
- 3 occurrences ≥ 3 minimum — passes
Step 3: Measure intervals
- Nov 1 → Dec 1 = 30 days
- Dec 1 → Jan 1 = 31 days
- Average = 30.5 days — matches monthly (28–31 days ±5 days)
Step 4: Check amount tolerance
- All amounts are -149.00 — 0% variance — passes
Step 5: Check recency (assuming today is Feb 1, 2026)
- Last charge: Jan 1, 2026
- Expected next: ~Feb 1, 2026
- Within expected interval + 7 days — passes
Result:
subscription_id: "sub-netflix-001"
merchant: Netflix
category: Streaming
amount: 149.00
frequency: monthly
annual_cost: 1788.00
first_seen: 2025-11-01
last_seen: 2026-01-01
status: active
Edge Cases
Variable Subscription Amounts
Some services (e.g., electricity, phone bills) have variable amounts:
- If the merchant is a known subscription (from the categorization skill) AND has regular frequency, detect it even if amounts vary more than 5%
- Store the most recent amount, not the average
Free Trial Conversions
A single charge from a service with no prior history is not a subscription yet. Wait for the minimum 3 occurrences before detecting.
Annual Subscriptions
Annual subscriptions may only have 1 occurrence within the data window. If the merchant is a known subscription service (e.g., Adobe CC annual plan), flag it as a potential subscription with frequency: yearly even with fewer than 3 occurrences.
Merged Households
If a service appears from multiple accounts (e.g., both lønkonto and budgetkonto), treat each account's charges separately. Don't merge cross-account charges into one subscription.
Stopped Subscriptions
If a previously detected subscription has no charge in the last expected interval + 7 days:
- Change status to
paused - Do not delete the row — the user may want to see it was detected
Learning from Corrections
When a user corrects subscription detection, record the correction in learnings/subscriptions.md:
If user says something IS a subscription (false negative):
- Open
learnings/subscriptions.md - Add a row to the "Confirmed Subscriptions" table:
- Date: Today's date (YYYY-MM-DD)
- Pattern: Normalized merchant pattern (e.g.,
*MERCHANT*) - Merchant: Normalized merchant name
- Frequency: Expected frequency (monthly, yearly, etc.)
- Note: Why this is a subscription
If user says something is NOT a subscription (false positive):
- Open
learnings/subscriptions.md - Add a row to the "Not Subscriptions" table:
- Date: Today's date (YYYY-MM-DD)
- Pattern: Normalized merchant pattern
- Merchant: Normalized merchant name
- Reason: Why this isn't a subscription (e.g., "one-time purchase", "irregular spending")
Example corrections:
## Confirmed Subscriptions
| 2026-01-15 | *HEADSPACE* | Headspace | monthly | User confirmed meditation app subscription |
## Not Subscriptions
| 2026-01-15 | *AMAZON* | Amazon | Regular purchases, not a subscription |
Related Skills
- See
skills/categorization/SKILL.mdfor how merchants are identified - See
skills/data-schemas/SKILL.mdfor the subscriptions.csv structure - See
learnings/subscriptions.mdfor user corrections to subscription detection
Source
git clone https://github.com/peerjakobsen/smartspender/blob/main/skills/subscription-detection/SKILL.mdView on GitHub Overview
subscription-detection identifies subscriptions by analyzing categorized transactions. It uses a five-criterion rule set to flag recurring charges, assigns a status, and computes annual costs to help users understand regular payments.
How This Skill Works
Before automatic detection, check the learnings lists for confirmed and not-subscriptions. The detector then groups transactions by normalized merchant, applies five criteria (same merchant, regular frequency, amount tolerance within ±5%, minimum 3 occurrences, and recency within interval + 7 days), and assigns a frequency and status. If a merchant isn’t on the learnings lists, automatic detection proceeds and yields fields like subscription_id, merchant, frequency, amount, annual_cost, first_seen, last_seen, and status.
When to Use It
- You want to identify services you regularly pay for from your categorized transactions
- A merchant appears multiple times with similar amounts at regular intervals
- You’re evaluating merchants not listed in the learnings (confirmed/not subscriptions) for automatic detection
- You need to compute the annual_cost once a subscription is detected
- You want to determine the subscription status (active, paused, cancelled)
Quick Start
- Step 1: Group transactions by normalized merchant name
- Step 2: Exclude merchants listed as confirmed or not-subscriptions in learnings
- Step 3: Run the detection to produce subscription_id, merchant, frequency, amount, annual_cost, first_seen, last_seen, and status
Best Practices
- Normalize merchant names before attempting matches
- Apply all five criteria: same merchant, regular frequency, amount tolerance, minimum occurrences, recency
- Determine frequency using the average interval and map to weekly/monthly/quarterly/yearly with tolerance
- Respect learnings: if a merchant is in confirmed or not-subscriptions lists, handle accordingly
- Store and surface annual_cost and status for downstream analytics
Example Use Cases
- Netflix — monthly subscription detected (amount 149.00, annual_cost 1788.00)
- Spotify — monthly subscription detected (amount 9.99, annual_cost 119.88)
- Gym membership — monthly subscription detected (amount 29.99, annual_cost 359.88)
- Dropbox Plus — monthly subscription detected (amount 5.99, annual_cost 71.88)
- Mobile plan — weekly charges detected (amount 12.00, annual_cost 624.00)