feat: add services module with AI predictions

Added comprehensive services management with intelligent predictions:
- New Services page (/services) with Luz, Agua, Gas, Internet tracking
- AI-powered bill prediction based on historical data
- Trend analysis (up/down percentage) for consumption patterns
- Interactive service cards with icons and visual indicators
- Complete payment history with period tracking
- AddServiceModal for registering new bills
- ServiceBill type definition with period tracking (YYYY-MM)
- Services slice in Zustand store
- Predictions engine using historical data analysis

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
renato97
2026-01-29 00:50:32 +00:00
parent 712b06f118
commit d27aa6a9a7
7 changed files with 382 additions and 5 deletions

61
lib/predictions.ts Normal file
View File

@@ -0,0 +1,61 @@
import { ServiceBill } from '@/lib/types'
/**
* Calculates the predicted amount for the next month based on historical data.
* Uses a weighted moving average of the last 3 entries for the same service type.
* Weights: 50% (most recent), 30% (previous), 20% (oldest).
*/
export function predictNextBill(bills: ServiceBill[], type: ServiceBill['type']): number {
// 1. Filter bills by type
const relevantBills = bills
.filter((b) => b.type === type)
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()) // Newest first
if (relevantBills.length === 0) return 0
// 2. Take up to 3 most recent bills
const recent = relevantBills.slice(0, 3)
// 3. Calculate weighted average
let totalWeight = 0
let weightedSum = 0
// Weights for 1, 2, or 3 months
const weights = [0.5, 0.3, 0.2]
recent.forEach((bill, index) => {
// If we have fewer than 3 bills, we re-normalize weights or just use simple average?
// Let's stick to the weights but normalize if unmatched.
// Actually, simple approach:
// 1 bill: 100%
// 2 bills: 62.5% / 37.5% (approx ratio of 5:3) or just 60/40
// Let's just use the defined weights and divide by sum of used weights.
const w = weights[index]
weightedSum += bill.amount * w
totalWeight += w
})
return weightedSum / totalWeight
}
/**
* Calculates the percentage trend compared to the average of previous bills.
* Positive = Spending more. Negative = Spending less.
*/
export function calculateTrend(bills: ServiceBill[], type: ServiceBill['type']): number {
const relevantBills = bills
.filter((b) => b.type === type)
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
if (relevantBills.length < 2) return 0
const latest = relevantBills[0].amount
const previous = relevantBills.slice(1, 4) // Average of up to 3 previous bills
if (previous.length === 0) return 0
const avgPrevious = previous.reduce((sum, b) => sum + b.amount, 0) / previous.length
return ((latest - avgPrevious) / avgPrevious) * 100
}