| name | data-insights |
| description | Generate actionable insights from transaction data. Use when implementing insight generators, anomaly detection, or data analysis features for financial/tax tracking applications. |
Data Insights Skill
This skill provides patterns for generating actionable insights from transaction data, specifically designed for tax and expense tracking applications.
When to Use This Skill
- Implementing insight generators for dashboards
- Detecting spending anomalies and patterns
- Computing aggregated statistics over time ranges
- Building ranked insight cards with drill-down capabilities
Core Insight Types
1. Quiet Leaks (Small Recurring Expenses)
Detects small, frequent purchases that add up significantly.
Trigger Rules:
- Transaction appears 3+ times in the range
- Individual amounts ≤ $20
- Cumulative total ≥ $50
Severity Calculation:
severity = Math.min(10, Math.floor(cumulativeTotal / 25))
Query Pattern:
// Group by merchant, count occurrences, sum totals
const quietLeaks = transactions
.filter(t => parseFloat(t.totalAmount) <= 20)
.reduce((acc, t) => {
const key = t.merchant || 'Unknown';
if (!acc[key]) acc[key] = { count: 0, total: 0, transactions: [] };
acc[key].count++;
acc[key].total += parseFloat(t.totalAmount);
acc[key].transactions.push(t.id);
return acc;
}, {});
// Filter to those with 3+ occurrences and $50+ total
return Object.entries(quietLeaks)
.filter(([_, data]) => data.count >= 3 && data.total >= 50)
.map(([merchant, data]) => ({
type: 'QUIET_LEAK',
title: `${merchant} adds up`,
summary: `${data.count} purchases totaling $${data.total.toFixed(2)}`,
severityScore: Math.min(10, Math.floor(data.total / 25)),
supportingTransactionIds: data.transactions
}));
2. Tax Drag (High Tax Rate Merchants)
Identifies merchants or categories with unusually high effective tax rates.
Trigger Rules:
- Effective tax rate > 9% (above typical sales tax)
- At least $100 spent at merchant
Severity Calculation:
severity = Math.min(10, Math.floor((effectiveTaxRate - 0.08) * 100))
Query Pattern:
const taxDrag = transactions.reduce((acc, t) => {
const key = t.merchant || 'Unknown';
const taxRate = parseFloat(t.taxAmount) / parseFloat(t.totalAmount);
if (!acc[key]) acc[key] = { totalSpent: 0, totalTax: 0, transactions: [] };
acc[key].totalSpent += parseFloat(t.totalAmount);
acc[key].totalTax += parseFloat(t.taxAmount);
acc[key].transactions.push(t.id);
return acc;
}, {});
return Object.entries(taxDrag)
.filter(([_, data]) => {
const effectiveRate = data.totalTax / data.totalSpent;
return effectiveRate > 0.09 && data.totalSpent >= 100;
})
.map(([merchant, data]) => {
const effectiveRate = data.totalTax / data.totalSpent;
return {
type: 'TAX_DRAG',
title: `High tax burden at ${merchant}`,
summary: `${(effectiveRate * 100).toFixed(1)}% effective tax rate on $${data.totalSpent.toFixed(2)}`,
severityScore: Math.min(10, Math.floor((effectiveRate - 0.08) * 100)),
supportingTransactionIds: data.transactions
};
});
3. Spikes/Anomalies (Unusual Spending)
Detects month-over-month jumps, duplicates, or unusual amounts.
Trigger Rules:
- Single transaction > 2x average transaction
- Month-over-month increase > 50%
- Duplicate-like entries (same merchant + amount within 24h)
Severity Calculation:
// For outliers
severity = Math.min(10, Math.floor((amount / average - 1) * 2))
// For MoM spikes
severity = Math.min(10, Math.floor((percentIncrease - 50) / 10))
Query Pattern:
const average = transactions.reduce((sum, t) => sum + parseFloat(t.totalAmount), 0) / transactions.length;
// Find outliers
const outliers = transactions
.filter(t => parseFloat(t.totalAmount) > average * 2)
.map(t => ({
type: 'SPIKE',
title: `Unusual expense: $${parseFloat(t.totalAmount).toFixed(2)}`,
summary: `${t.merchant || 'Unknown'} - ${((parseFloat(t.totalAmount) / average - 1) * 100).toFixed(0)}% above average`,
severityScore: Math.min(10, Math.floor((parseFloat(t.totalAmount) / average - 1) * 2)),
supportingTransactionIds: [t.id]
}));
// Find duplicates
const potentialDuplicates = findDuplicates(transactions);
Output Format
All insight generators must return this standardized format:
interface Insight {
type: 'QUIET_LEAK' | 'TAX_DRAG' | 'SPIKE' | 'DUPLICATE' | 'MOM_INCREASE';
title: string; // Short, attention-grabbing headline
summary: string; // 1-2 sentence explanation
severityScore: number; // 1-10 scale for ranking
supportingTransactionIds: string[]; // For drill-down
}
Implementation Structure
src/lib/insights/
├── index.ts # Main getInsights() function
├── types.ts # Insight interface definitions
├── generators/
│ ├── quiet-leaks.ts # detectQuietLeaks()
│ ├── tax-drag.ts # detectTaxDrag()
│ └── spikes.ts # detectSpikes()
└── __tests__/
├── quiet-leaks.test.ts
├── tax-drag.test.ts
└── spikes.test.ts
Best Practices
- Pure Functions: Each insight generator should be a pure function taking transactions and returning insights
- Configurable Thresholds: Make trigger thresholds configurable via parameters
- Efficient Queries: Compute all insights in a single pass when possible
- Test with Edge Cases: Empty data, single transaction, all same merchant, etc.
- Sort by Severity: Return insights sorted by severityScore descending