From b5f46a7646e5be7a76bd40c69b0797a017544cb6 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Sun, 15 Feb 2026 20:02:50 -0500 Subject: [PATCH] Cleanup: Remove redundant AI categorization pages ReviewAISuggestions and ReviewAISuggestionsWithProposals were a two-page workflow superseded by AICategorizePreview, which handles batch approval, tabs, and TempData storage in a single page. Co-Authored-By: Claude Opus 4.6 --- MoneyMap/Pages/ReviewAISuggestions.cshtml | 126 ------------- MoneyMap/Pages/ReviewAISuggestions.cshtml.cs | 114 ------------ .../ReviewAISuggestionsWithProposals.cshtml | 168 ------------------ ...ReviewAISuggestionsWithProposals.cshtml.cs | 157 ---------------- 4 files changed, 565 deletions(-) delete mode 100644 MoneyMap/Pages/ReviewAISuggestions.cshtml delete mode 100644 MoneyMap/Pages/ReviewAISuggestions.cshtml.cs delete mode 100644 MoneyMap/Pages/ReviewAISuggestionsWithProposals.cshtml delete mode 100644 MoneyMap/Pages/ReviewAISuggestionsWithProposals.cshtml.cs diff --git a/MoneyMap/Pages/ReviewAISuggestions.cshtml b/MoneyMap/Pages/ReviewAISuggestions.cshtml deleted file mode 100644 index 7534394..0000000 --- a/MoneyMap/Pages/ReviewAISuggestions.cshtml +++ /dev/null @@ -1,126 +0,0 @@ -@page -@model MoneyMap.Pages.ReviewAISuggestionsModel -@{ - ViewData["Title"] = "AI Categorization Suggestions"; - ViewData["Breadcrumbs"] = new List<(string Label, string? Url)> - { - ("Transactions", Url.Page("/Transactions")), - ("AI Suggestions", null) - }; -} - -
-

AI Categorization Suggestions

- -
- -@if (!string.IsNullOrEmpty(Model.SuccessMessage)) -{ - -} - -@if (!string.IsNullOrEmpty(Model.ErrorMessage)) -{ - -} - -
-
-
How AI Categorization Works
-

- This tool uses AI to analyze your uncategorized transactions and suggest: -

-
    -
  • Category - The most appropriate expense category
  • -
  • Merchant Name - A normalized merchant name (e.g., "Walmart" from "WAL-MART #1234")
  • -
  • Pattern Rule - An optional rule to auto-categorize similar transactions in the future
  • -
-

- Cost: Approximately $0.00015 per transaction (~1.5 cents per 100 transactions) -

-
-
- -
-
- Uncategorized Transactions (@Model.TotalUncategorized) -
- -
-
-
- @if (!Model.Transactions.Any()) - { -

No uncategorized transactions found. Great job!

- } - else - { -

- Showing the @Model.Transactions.Count most recent uncategorized transactions. - Click "Generate AI Suggestions" to analyze up to 20 transactions. -

- -
- - - - - - - - - - - - @foreach (var item in Model.Transactions) - { - - - - - - - - } - -
DateNameMemoAmountActions
@item.Transaction.Date.ToString("yyyy-MM-dd")@item.Transaction.Name@item.Transaction.Memo - - @item.Transaction.Amount.ToString("C") - - - - Edit - -
-
- } -
-
- -
-
- Quick Tips -
-
-
    -
  • AI suggestions are based on transaction name, memo, amount, and date
  • -
  • You can accept, reject, or modify each suggestion
  • -
  • Creating rules helps auto-categorize future transactions
  • -
  • High confidence suggestions (>80%) are more reliable
  • -
  • You can manually edit any transaction from the Transactions page
  • -
-
-
diff --git a/MoneyMap/Pages/ReviewAISuggestions.cshtml.cs b/MoneyMap/Pages/ReviewAISuggestions.cshtml.cs deleted file mode 100644 index 8c94f8e..0000000 --- a/MoneyMap/Pages/ReviewAISuggestions.cshtml.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.EntityFrameworkCore; -using MoneyMap.Data; -using MoneyMap.Models; -using MoneyMap.Services; - -namespace MoneyMap.Pages; - -public class ReviewAISuggestionsModel : PageModel -{ - private readonly MoneyMapContext _db; - private readonly ITransactionAICategorizer _aiCategorizer; - - public ReviewAISuggestionsModel(MoneyMapContext db, ITransactionAICategorizer aiCategorizer) - { - _db = db; - _aiCategorizer = aiCategorizer; - } - - public List Transactions { get; set; } = new(); - public bool IsGenerating { get; set; } - public int TotalUncategorized { get; set; } - - [TempData] - public string? SuccessMessage { get; set; } - - [TempData] - public string? ErrorMessage { get; set; } - - public async Task OnGetAsync() - { - // Get uncategorized transactions - var uncategorized = await _db.Transactions - .Include(t => t.Merchant) - .Where(t => string.IsNullOrEmpty(t.Category)) - .OrderByDescending(t => t.Date) - .Take(50) // Limit to 50 most recent - .ToListAsync(); - - TotalUncategorized = uncategorized.Count; - Transactions = uncategorized.Select(t => new TransactionWithProposal - { - Transaction = t, - Proposal = null // Will be populated via AJAX or on generate - }).ToList(); - } - - public async Task OnPostGenerateSuggestionsAsync() - { - // Get uncategorized transactions - var uncategorized = await _db.Transactions - .Where(t => string.IsNullOrEmpty(t.Category)) - .OrderByDescending(t => t.Date) - .Take(20) // Limit to 20 for cost control - .ToListAsync(); - - if (!uncategorized.Any()) - { - ErrorMessage = "No uncategorized transactions found."; - return RedirectToPage(); - } - - // Generate proposals - var proposals = await _aiCategorizer.ProposeBatchCategorizationAsync(uncategorized); - - // Store proposals in session for review - HttpContext.Session.SetString("AIProposals", System.Text.Json.JsonSerializer.Serialize(proposals)); - - SuccessMessage = $"Generated {proposals.Count} AI suggestions. Review them below."; - return RedirectToPage("ReviewAISuggestionsWithProposals"); - } - - public async Task OnPostApplyProposalAsync(long transactionId, string category, string? merchant, string? pattern, decimal confidence, bool createRule) - { - var proposal = new AICategoryProposal - { - TransactionId = transactionId, - Category = category, - CanonicalMerchant = merchant, - Pattern = pattern, - Confidence = confidence, - CreateRule = createRule - }; - - var result = await _aiCategorizer.ApplyProposalAsync(transactionId, proposal, createRule); - - if (result.Success) - { - SuccessMessage = result.RuleCreated - ? "Transaction categorized and rule created!" - : "Transaction categorized!"; - } - else - { - ErrorMessage = result.ErrorMessage ?? "Failed to apply suggestion."; - } - - return RedirectToPage(); - } - - public IActionResult OnPostRejectProposalAsync(long transactionId) - { - // Just refresh the page, removing this transaction from view - SuccessMessage = "Suggestion rejected."; - return RedirectToPage(); - } - - public class TransactionWithProposal - { - public Transaction Transaction { get; set; } = null!; - public AICategoryProposal? Proposal { get; set; } - } -} diff --git a/MoneyMap/Pages/ReviewAISuggestionsWithProposals.cshtml b/MoneyMap/Pages/ReviewAISuggestionsWithProposals.cshtml deleted file mode 100644 index 222f6ff..0000000 --- a/MoneyMap/Pages/ReviewAISuggestionsWithProposals.cshtml +++ /dev/null @@ -1,168 +0,0 @@ -@page -@model MoneyMap.Pages.ReviewAISuggestionsWithProposalsModel -@{ - ViewData["Title"] = "Review AI Suggestions"; - ViewData["Breadcrumbs"] = new List<(string Label, string? Url)> - { - ("Transactions", Url.Page("/Transactions")), - ("AI Suggestions", Url.Page("/ReviewAISuggestions")), - ("Review Proposals", null) - }; -} - -
-

Review AI Suggestions

-
-
- -
- Back -
-
- -@if (!string.IsNullOrEmpty(Model.SuccessMessage)) -{ - -} - -@if (!string.IsNullOrEmpty(Model.ErrorMessage)) -{ - -} - -@if (!Model.Proposals.Any()) -{ -
-
No suggestions remaining
-

- All AI suggestions have been processed. - Generate more suggestions -

-
-} -else -{ -
- Review each suggestion below. You can accept the AI's proposal, reject it, or modify it before applying. - High confidence suggestions (≥80%) are generally very reliable. -
- - @foreach (var item in Model.Proposals) - { - var confidenceClass = item.Proposal.Confidence >= 0.8m ? "success" : - item.Proposal.Confidence >= 0.6m ? "warning" : "danger"; - var confidencePercent = (item.Proposal.Confidence * 100).ToString("F0"); - -
-
-
- @item.Transaction.Name - @item.Transaction.Date.ToString("yyyy-MM-dd") - @confidencePercent% Confidence -
- - @item.Transaction.Amount.ToString("C") - -
-
-
-
-
Transaction Details
-
-
Memo:
-
@item.Transaction.Memo
- -
Amount:
-
@item.Transaction.Amount.ToString("C")
-
-
-
-
AI Suggestion
-
-
Category:
-
@item.Proposal.Category
- - @if (!string.IsNullOrWhiteSpace(item.Proposal.CanonicalMerchant)) - { -
Merchant:
-
@item.Proposal.CanonicalMerchant
- } - - @if (!string.IsNullOrWhiteSpace(item.Proposal.Pattern)) - { -
Pattern:
-
@item.Proposal.Pattern
- } - - @if (!string.IsNullOrWhiteSpace(item.Proposal.Reasoning)) - { -
Reasoning:
-
@item.Proposal.Reasoning
- } -
-
-
- -
- -
-
- - -
- -
- - - - - - - -
- -
- - - - - - - -
- - - Edit Manually - -
-
-
- } -} - -
-
- Understanding Confidence Scores -
-
-
    -
  • ≥80% - High confidence, very reliable
  • -
  • 60-79% - Medium confidence, review recommended
  • -
  • <60% - Low confidence, manual review strongly recommended
  • -
-
-
diff --git a/MoneyMap/Pages/ReviewAISuggestionsWithProposals.cshtml.cs b/MoneyMap/Pages/ReviewAISuggestionsWithProposals.cshtml.cs deleted file mode 100644 index 15cb11d..0000000 --- a/MoneyMap/Pages/ReviewAISuggestionsWithProposals.cshtml.cs +++ /dev/null @@ -1,157 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.EntityFrameworkCore; -using MoneyMap.Data; -using MoneyMap.Models; -using MoneyMap.Services; -using System.Text.Json; - -namespace MoneyMap.Pages; - -public class ReviewAISuggestionsWithProposalsModel : PageModel -{ - private readonly MoneyMapContext _db; - private readonly ITransactionAICategorizer _aiCategorizer; - - public ReviewAISuggestionsWithProposalsModel(MoneyMapContext db, ITransactionAICategorizer aiCategorizer) - { - _db = db; - _aiCategorizer = aiCategorizer; - } - - public List Proposals { get; set; } = new(); - - [TempData] - public string? SuccessMessage { get; set; } - - [TempData] - public string? ErrorMessage { get; set; } - - public async Task OnGetAsync() - { - // Load proposals from session - var proposalsJson = HttpContext.Session.GetString("AIProposals"); - if (string.IsNullOrWhiteSpace(proposalsJson)) - { - ErrorMessage = "No AI suggestions found. Please generate suggestions first."; - return RedirectToPage("ReviewAISuggestions"); - } - - var proposals = JsonSerializer.Deserialize>(proposalsJson); - if (proposals == null || !proposals.Any()) - { - ErrorMessage = "Failed to load AI suggestions."; - return RedirectToPage("ReviewAISuggestions"); - } - - // Load transactions for these proposals - var transactionIds = proposals.Select(p => p.TransactionId).ToList(); - var transactions = await _db.Transactions - .Include(t => t.Merchant) - .Where(t => transactionIds.Contains(t.Id)) - .ToListAsync(); - - Proposals = proposals.Select(p => new TransactionWithProposal - { - Transaction = transactions.FirstOrDefault(t => t.Id == p.TransactionId)!, - Proposal = p - }).Where(x => x.Transaction != null).ToList(); - - return Page(); - } - - public async Task OnPostApplyProposalAsync(long transactionId, string category, string? merchant, string? pattern, decimal confidence, bool createRule) - { - var proposal = new AICategoryProposal - { - TransactionId = transactionId, - Category = category, - CanonicalMerchant = merchant, - Pattern = pattern, - Confidence = confidence, - CreateRule = createRule - }; - - var result = await _aiCategorizer.ApplyProposalAsync(transactionId, proposal, createRule); - - if (result.Success) - { - // Remove this proposal from session - var proposalsJson = HttpContext.Session.GetString("AIProposals"); - if (!string.IsNullOrWhiteSpace(proposalsJson)) - { - var proposals = JsonSerializer.Deserialize>(proposalsJson); - if (proposals != null) - { - proposals.RemoveAll(p => p.TransactionId == transactionId); - HttpContext.Session.SetString("AIProposals", JsonSerializer.Serialize(proposals)); - } - } - - SuccessMessage = result.RuleCreated - ? "Transaction categorized and rule created!" - : "Transaction categorized!"; - } - else - { - ErrorMessage = result.ErrorMessage ?? "Failed to apply suggestion."; - } - - return RedirectToPage(); - } - - public IActionResult OnPostRejectProposalAsync(long transactionId) - { - // Remove this proposal from session - var proposalsJson = HttpContext.Session.GetString("AIProposals"); - if (!string.IsNullOrWhiteSpace(proposalsJson)) - { - var proposals = JsonSerializer.Deserialize>(proposalsJson); - if (proposals != null) - { - proposals.RemoveAll(p => p.TransactionId == transactionId); - HttpContext.Session.SetString("AIProposals", JsonSerializer.Serialize(proposals)); - } - } - - SuccessMessage = "Suggestion rejected."; - return RedirectToPage(); - } - - public async Task OnPostApplyAllAsync() - { - var proposalsJson = HttpContext.Session.GetString("AIProposals"); - if (string.IsNullOrWhiteSpace(proposalsJson)) - { - ErrorMessage = "No AI suggestions found."; - return RedirectToPage("ReviewAISuggestions"); - } - - var proposals = JsonSerializer.Deserialize>(proposalsJson); - if (proposals == null || !proposals.Any()) - { - ErrorMessage = "Failed to load AI suggestions."; - return RedirectToPage("ReviewAISuggestions"); - } - - int applied = 0; - foreach (var proposal in proposals.Where(p => p.Confidence >= 0.8m)) - { - var result = await _aiCategorizer.ApplyProposalAsync(proposal.TransactionId, proposal, proposal.CreateRule); - if (result.Success) - applied++; - } - - // Clear session - HttpContext.Session.Remove("AIProposals"); - - SuccessMessage = $"Applied {applied} high-confidence suggestions (≥80%)."; - return RedirectToPage("ReviewAISuggestions"); - } - - public class TransactionWithProposal - { - public Transaction Transaction { get; set; } = null!; - public AICategoryProposal Proposal { get; set; } = null!; - } -}