重构存款预算
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Failing after 44s
Docker Build & Deploy / Deploy to Production (push) Has been skipped
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 3s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s

This commit is contained in:
SunCheng
2026-01-20 19:11:05 +08:00
parent 44d9fbb0f6
commit 0ffeb41605
13 changed files with 1591 additions and 583 deletions

View File

@@ -217,6 +217,8 @@ public interface ITransactionRecordRepository : IBaseRepository<TransactionRecor
/// <param name="type">交易类型</param>
/// <returns>影响行数</returns>
Task<int> UpdateCategoryNameAsync(string oldName, string newName, TransactionType type);
Task<Dictionary<(string, TransactionType), decimal>> GetAmountGroupByClassifyAsync(DateTime startTime, DateTime endTime);
}
public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository<TransactionRecord>(freeSql), ITransactionRecordRepository
@@ -276,7 +278,7 @@ public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository<Tran
var dateEnd = dateStart.AddMonths(1);
query = query.Where(t => t.OccurredAt >= dateStart && t.OccurredAt < dateEnd);
}
// 按日期范围筛选
query = query.WhereIf(startDate.HasValue, t => t.OccurredAt >= startDate!.Value)
.WhereIf(endDate.HasValue, t => t.OccurredAt <= endDate!.Value);
@@ -379,7 +381,7 @@ public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository<Tran
var expense = g.Where(t => t.Type == TransactionType.Expense).Sum(t => Math.Abs(t.Amount));
var saving = 0m;
if(!string.IsNullOrEmpty(savingClassify))
if (!string.IsNullOrEmpty(savingClassify))
{
saving = g.Where(t => savingClassify.Split(',').Contains(t.Classify)).Sum(t => Math.Abs(t.Amount));
}
@@ -649,19 +651,19 @@ public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository<Tran
// 计算每个候选账单的相关度分数
var scoredResults = candidates
.Select(record =>
.Select(record =>
{
var matchedCount = keywords.Count(keyword => record.Reason.Contains(keyword, StringComparison.OrdinalIgnoreCase));
var matchRate = (double)matchedCount / keywords.Count;
// 额外加分:完全匹配整个摘要(相似度更高)
var exactMatchBonus = keywords.Any(k => record.Reason.Equals(k, StringComparison.OrdinalIgnoreCase)) ? 0.2 : 0.0;
// 长度相似度加分:长度越接近,相关度越高
var avgKeywordLength = keywords.Average(k => k.Length);
var lengthSimilarity = 1.0 - Math.Min(1.0, Math.Abs(record.Reason.Length - avgKeywordLength) / Math.Max(record.Reason.Length, avgKeywordLength));
var lengthBonus = lengthSimilarity * 0.1;
var score = matchRate + exactMatchBonus + lengthBonus;
return (record, score);
})
@@ -695,9 +697,9 @@ public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository<Tran
.Where(t => Math.Abs(t.Amount) >= minAmount && Math.Abs(t.Amount) <= maxAmount)
.Take(50)
.ToListAsync();
return list.OrderBy(t => Math.Abs(Math.Abs(t.Amount) - absAmount))
.ThenBy(x=> Math.Abs((x.OccurredAt - currentRecord.OccurredAt).TotalSeconds))
.ThenBy(x => Math.Abs((x.OccurredAt - currentRecord.OccurredAt).TotalSeconds))
.ToList();
}
@@ -757,6 +759,21 @@ public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository<Tran
.GroupBy(t => t.OccurredAt.Date)
.ToDictionary(g => g.Key, g => g.Sum(x => Math.Abs(x.Amount)));
}
public async Task<Dictionary<(string, TransactionType), decimal>> GetAmountGroupByClassifyAsync(DateTime startTime, DateTime endTime)
{
var result = await FreeSql.Select<TransactionRecord>()
.Where(t => t.OccurredAt >= startTime && t.OccurredAt < endTime)
.GroupBy(t => new { t.Classify, t.Type })
.ToListAsync(g => new
{
g.Key.Classify,
g.Key.Type,
TotalAmount = g.Sum(g.Value.Amount - g.Value.RefundAmount)
});
return result.ToDictionary(x => (x.Classify, x.Type), x => x.TotalAmount);
}
}
/// <summary>