using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; using MoneyMap.Data; using MoneyMap.Services; namespace MoneyMap.Pages { public class RecategorizeModel : PageModel { private readonly MoneyMapContext _db; private readonly ITransactionCategorizer _categorizer; private readonly ITransactionStatisticsService _statsService; private readonly IConfiguration _config; public RecategorizeModel( MoneyMapContext db, ITransactionCategorizer categorizer, ITransactionStatisticsService statsService, IConfiguration config) { _db = db; _categorizer = categorizer; _statsService = statsService; _config = config; } public RecategorizeStats Stats { get; set; } = new(); public string AIProvider => _config["AI:CategorizationProvider"] ?? "OpenAI"; [TempData] public string? SuccessMessage { get; set; } public async Task OnGetAsync() { await LoadStatsAsync(); } public async Task OnPostRecategorizeAllAsync() { var result = await RecategorizeTransactionsAsync(includeAlreadyCategorized: true); SuccessMessage = $"Processed {result.Total} transactions: {result.Updated} updated, {result.AlreadyCorrect} already correct, {result.NoMatch} no match found."; return RedirectToPage(); } public async Task OnPostRecategorizeUncategorizedAsync() { var result = await RecategorizeTransactionsAsync(includeAlreadyCategorized: false); SuccessMessage = $"Processed {result.Total} uncategorized transactions: {result.Updated} categorized, {result.NoMatch} still need manual categorization."; return RedirectToPage(); } private async Task LoadStatsAsync() { var categorizationStats = await _statsService.GetCategorizationStatsAsync(); Stats = new RecategorizeStats { TotalTransactions = categorizationStats.TotalTransactions, Categorized = categorizationStats.Categorized, Uncategorized = categorizationStats.Uncategorized }; } private async Task RecategorizeTransactionsAsync(bool includeAlreadyCategorized) { var query = _db.Transactions.AsQueryable(); if (!includeAlreadyCategorized) { query = query.Where(t => string.IsNullOrWhiteSpace(t.Category)); } var transactions = await query.ToListAsync(); int total = transactions.Count; int updated = 0; int alreadyCorrect = 0; int noMatch = 0; foreach (var txn in transactions) { var result = await _categorizer.CategorizeAsync(txn.Name, txn.Amount); if (string.IsNullOrWhiteSpace(result.Category)) { noMatch++; continue; } if (txn.Category == result.Category && txn.MerchantId == result.MerchantId) { alreadyCorrect++; continue; } txn.Category = result.Category; txn.MerchantId = result.MerchantId; updated++; } if (updated > 0) { await _db.SaveChangesAsync(); } return new RecategorizeResult { Total = total, Updated = updated, AlreadyCorrect = alreadyCorrect, NoMatch = noMatch }; } public class RecategorizeStats { public int TotalTransactions { get; set; } public int Categorized { get; set; } public int Uncategorized { get; set; } } private class RecategorizeResult { public int Total { get; set; } public int Updated { get; set; } public int AlreadyCorrect { get; set; } public int NoMatch { get; set; } } } }