Detection Algorithm¶
This page explains how the subscription detector identifies recurring payments from your transaction data.
Overview¶
The detection process has two main paths:
- Known subscriptions - Matched immediately by pattern (Netflix, Spotify, etc.)
- Pattern detection - Analyzed for recurring monthly patterns
Transactions
│
├─► Known subscription patterns ─► Immediate match
│
└─► Pattern detection ─► Monthly analysis ─► Tolerance check
Step-by-Step Process¶
1. Parse Transactions¶
Transactions are loaded from bank export files. Each transaction has:
- Date - When the payment occurred
- Text - Payee/merchant name
- Amount - Payment amount (negative = expense)
2. Apply Grouping Rules¶
Before detection, grouping rules from your config are applied. This combines transactions with varying names into a single subscription:
groups:
- name: "Google Workspace"
patterns:
- "GOOGLE\\*GSUITE"
- "Google GSUITE_"
- "Google Workspa"
All matching transactions are renamed to "Google Workspace" before detection.
3. Detect Known Subscriptions¶
The tool has 70+ built-in patterns for common services. These are matched immediately and bypass the normal detection rules:
| Rule | Pattern Detection | Known Subscriptions |
|---|---|---|
| Minimum occurrences | 2+ required | 1 is enough |
| Complete months only | Yes | No - includes current month |
| Tolerance check | Yes | No |
| Monthly pattern check | Yes | No |
Built-in patterns include:
- Video: Netflix, Disney+, HBO Max, Amazon Prime, etc.
- Music: Spotify, Apple Music, Tidal, YouTube Music, etc.
- Gaming: Xbox Game Pass, PlayStation Plus, etc.
- Productivity: Adobe, Microsoft 365, Dropbox, etc.
- And many more...
This means if you just subscribed to Netflix today, it will be detected immediately - no need to wait for 2+ months of history.
You can add your own patterns or disable defaults:
4. Analyze Data Coverage¶
The tool determines which months have complete data:
- Complete month: A past month, or current month if today is the last day
- Incomplete month: The current month (still accumulating transactions)
Why exclude incomplete months from pattern detection?
If today is January 15th and your subscription usually charges on the 20th, the algorithm shouldn't conclude "no payment this month" - the payment just hasn't happened yet. By excluding the current month, we avoid false negatives.
However, incomplete months are included when:
- Checking for known subscriptions (they match immediately)
- Determining if a subscription is ACTIVE (payment in current month = active)
- Calculating the latest payment amount
5. Pattern Detection¶
For remaining transactions, the algorithm looks for recurring patterns:
Grouping¶
Transactions are grouped by payee name (case-insensitive).
Minimum Occurrences¶
A group needs 2+ occurrences to be considered a subscription.
Expenses Only¶
Only negative amounts (expenses) are analyzed. Income is ignored.
Monthly Pattern Check¶
Each calendar month should have exactly 1 payment:
✓ Valid pattern:
Jan: 1 payment
Feb: 1 payment
Mar: 1 payment
✗ Invalid (multiple per month):
Jan: 2 payments
Feb: 1 payment
Note
Gaps are allowed - a subscription doesn't need to appear every month, but when it does appear, it should be once per month.
Tolerance Check¶
Consecutive payments must be within the tolerance threshold (default: 35%):
Payment 1: 100 kr
Payment 2: 110 kr → 10% change ✓ (within 35%)
Payment 3: 150 kr → 36% change ✗ (exceeds 35%)
This catches subscriptions with minor price changes while filtering out variable expenses like groceries.
You can adjust the tolerance:
# Strict: only allow 10% variation
./subscription-detector --tolerance 0.10 ...
# Relaxed: allow 50% variation
./subscription-detector --tolerance 0.50 ...
6. Determine Status¶
Each subscription is marked as ACTIVE or STOPPED:
| Condition | Status |
|---|---|
| Payment in current month | ACTIVE |
| Within 5-day grace period after expected date | ACTIVE |
| Last payment was 2+ months ago | STOPPED |
| Past grace period, no current month payment | STOPPED |
The typical day is calculated as the average day-of-month across all payments. This is used to determine the grace period.
7. Apply Exclusions¶
Finally, exclusion rules from your config are applied:
exclude:
- "Tokyo Ramen" # Always exclude
- pattern: "Old Service"
before: "2025-01-01" # Only exclude before this date
Summary Statistics¶
After detection, the tool calculates:
| Statistic | Description |
|---|---|
| Average amount | Mean of all payments |
| Latest amount | Most recent payment (used for totals) |
| Min/Max amount | Price range across all payments |
| Typical day | Average day-of-month |
| Start date | First payment date |
| Last date | Most recent payment date |
Example¶
Given these transactions:
2025-01-15 Netflix -99 kr
2025-02-15 Netflix -99 kr
2025-03-15 Netflix -99 kr
2025-01-10 Grocery -250 kr
2025-02-22 Grocery -180 kr
2025-03-05 Grocery -320 kr
Netflix: ✓ Detected as subscription
- Monthly pattern: 1 per month ✓
- Tolerance: 0% variation ✓
Grocery: ✗ Not detected
- Monthly pattern: 1 per month ✓
- Tolerance: 38% variation between 180→250 ✗