diff --git a/internal/service/ai_bookkeeping_service.go b/internal/service/ai_bookkeeping_service.go index 429dc2d..3737dd8 100644 --- a/internal/service/ai_bookkeeping_service.go +++ b/internal/service/ai_bookkeeping_service.go @@ -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"` // 预算警告 + // 预算与目标 + 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 = ¤tTx + } + } + } + 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 }