feat: 添加 AI 记账服务
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user