feat: 添加 AI 记账服务

This commit is contained in:
2026-01-29 22:51:48 +08:00
parent 07ad052f6d
commit 991453b10d

View File

@@ -175,6 +175,15 @@ func (s *AIBookkeepingService) GenerateDailyInsight(ctx context.Context, userID
* 暴多:问是不是中了彩票没通知。
* 暴少:怀疑用户是不是被外星人绑架了(没机会花钱)。
* 是 0颁发“诺贝尔省钱学奖”或者问是不是在修炼辟谷。
- 看到 'UpcomingRecurring' (即将到来的固定支出):
* 如果有,务必提醒用户:“别光顾着浪,过两天还有[内容]要扣款呢!”。
- 看到 'DebtRatio' > 0.5 (高负债):
* 开启“恐慌模式”,提醒用户天台风大,要勒紧裤腰带。
- 看到 'MaxSingleSpend' (最大单笔支出):
* 直接点名该笔交易:“你那个[金额]元的[备注]是金子做的吗?”
- 看到 'SavingsProgress' (存钱进度):
* 进度慢:催促一下,“存钱罐都要饿瘦了”。
* 进度快:狠狠夸奖,“离首富又近了一步”。
- **关键原则:怎么有趣怎么来!不要在乎字数,哪怕只说一句“牛逼”也行,只要符合当时的情境和人设!**
2. "budget": 预算建议(字数不限)
@@ -1041,10 +1050,22 @@ func (s *AIBookkeepingService) handleQueryIntent(ctx context.Context, intent str
personaMode := "balance" // 默认平衡
healthScore := 60 // 默认及格
// 简单估算健康分 (需与前端算法保持一致性趋势)
// 升级版健康分计算 (结合负债率)
if fc.TotalAssets > 0 {
// 基础分:资产/负债比
ratio := (fc.TotalAssets - fc.TotalLiabilities) / fc.TotalAssets
healthScore = 40 + int(ratio*50)
// 负债率惩罚
if fc.DebtRatio > 0.5 {
healthScore -= 20
}
if fc.DebtRatio > 0.8 {
healthScore -= 20 // 严重扣分
}
} else if fc.TotalLiabilities > 0 {
// 资不抵债
healthScore = 10
}
if healthScore > 80 {
@@ -1145,7 +1166,13 @@ func (s *AIBookkeepingService) generateSpendingAdvice(ctx context.Context, messa
if fc.TotalAssets > 0 {
ratio := (fc.TotalAssets - fc.TotalLiabilities) / fc.TotalAssets
healthScore = 40 + int(ratio*50)
if fc.DebtRatio > 0.5 {
healthScore -= 20
}
} else if fc.TotalLiabilities > 0 {
healthScore = 10
}
if healthScore > 80 {
personaMode = "rich"
} else if healthScore <= 40 {
@@ -1488,18 +1515,22 @@ type FinancialContext struct {
TotalBalance float64 `json:"total_balance"` // 净资产 (资产 - 负债)
TotalAssets float64 `json:"total_assets"` // 总资产
TotalLiabilities float64 `json:"total_liabilities"` // 总负债
DebtRatio float64 `json:"debt_ratio"` // 负债率 (0-1)
AccountSummary []AccountBrief `json:"account_summary"` // 账户摘要
// 最近消费
Last30DaysSpend float64 `json:"last_30_days_spend"` // 近30天支出
Last7DaysSpend float64 `json:"last_7_days_spend"` // 近7天支出
TodaySpend float64 `json:"today_spend"` // 今日支出
MaxSingleSpend *TransactionBrief `json:"max_single_spend"` // 本月最大单笔支出
TopCategories []CategorySpend `json:"top_categories"` // 消费大类TOP3
RecentTransactions []TransactionBrief `json:"recent_transactions"` // 最近5笔交易
UpcomingRecurring []string `json:"upcoming_recurring"` // 未来7天固定支出提醒
// 预算信息
// 预算与目标
ActiveBudgets []BudgetBrief `json:"active_budgets"` // 活跃预算
BudgetWarnings []string `json:"budget_warnings"` // 预算警告
SavingsProgress []string `json:"savings_progress"` // 存钱进度摘要
// 历史对比
LastMonthSpend float64 `json:"last_month_spend"` // 上月同期支出
@@ -1734,6 +1765,72 @@ func (s *AIBookkeepingService) GetUserFinancialContext(ctx context.Context, user
}
}
// 计算负债率
if fc.TotalAssets+fc.TotalLiabilities > 0 {
fc.DebtRatio = fc.TotalLiabilities / (fc.TotalAssets + fc.TotalLiabilities)
}
// 5. 获取存钱罐进度 (PiggyBank)
var piggyBanks []models.PiggyBank
if err := s.db.Where("user_id = ?", userID).Find(&piggyBanks).Error; err == nil {
for _, pb := range piggyBanks {
progress := 0.0
if pb.TargetAmount > 0 {
progress = pb.CurrentAmount / pb.TargetAmount * 100
}
status := "进行中"
if progress >= 100 {
status = "已达成"
}
fc.SavingsProgress = append(fc.SavingsProgress, fmt.Sprintf("%s: %.0f/%.0f (%.1f%%) - %s", pb.Name, pb.CurrentAmount, pb.TargetAmount, progress, status))
}
}
// 6. 获取未来7天即将到期的定期事务 (RecurringTransaction)
var recurrings []models.RecurringTransaction
next7Days := now.AddDate(0, 0, 7)
if err := s.db.Where("user_id = ? AND is_active = ? AND type = ?", userID, true, models.TransactionTypeExpense).
Where("next_occurrence BETWEEN ? AND ?", now, next7Days).
Find(&recurrings).Error; err == nil {
for _, r := range recurrings {
days := int(r.NextOccurrence.Sub(now).Hours() / 24)
msg := fmt.Sprintf("%s 还有 %d 天扣款 %.2f元", r.Note, days, r.Amount)
if r.Note == "" {
// 获取分类名
var cat models.Category
s.db.First(&cat, r.CategoryID)
msg = fmt.Sprintf("%s 还有 %d 天扣款 %.2f元", cat.Name, days, r.Amount)
}
fc.UpcomingRecurring = append(fc.UpcomingRecurring, msg)
}
}
// 7. 计算本月最大单笔支出
if len(transactions) > 0 {
var maxTx *models.Transaction
for _, tx := range transactions {
if tx.Type == models.TransactionTypeExpense {
if maxTx == nil || tx.Amount > maxTx.Amount {
currentTx := tx // 创建副本以避免取地址问题
maxTx = &currentTx
}
}
}
if maxTx != nil {
catName := "其他"
if maxTx.Category.ID != 0 {
catName = maxTx.Category.Name
}
fc.MaxSingleSpend = &TransactionBrief{
Amount: maxTx.Amount,
Category: catName,
Note: maxTx.Note,
Date: maxTx.TransactionDate.Format("01-02"),
Type: string(maxTx.Type),
}
}
}
return fc, nil
}