Add documentation for: - FinancialAuditService responsibilities and methods - Audit flag types and severities - /api/audit endpoint with query parameters and response - Version bump to 1.4 with recent changes section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1747 lines
62 KiB
Markdown
1747 lines
62 KiB
Markdown
# MoneyMap Architecture Documentation
|
|
|
|
## Project Overview
|
|
|
|
MoneyMap is an ASP.NET Core 8.0 Razor Pages application designed for personal finance tracking. It allows users to import bank transaction CSV files, categorize expenses, attach receipt images/PDFs, and parse receipts using AI (OpenAI GPT-4o-mini Vision API).
|
|
|
|
## Technology Stack
|
|
|
|
- **Framework**: ASP.NET Core 8.0 (Razor Pages)
|
|
- **Database**: SQL Server with Entity Framework Core 9.0
|
|
- **Libraries**:
|
|
- CsvHelper (33.1.0) - CSV parsing
|
|
- Magick.NET (14.8.2) - PDF to image conversion
|
|
- PdfPig (0.1.11) - PDF processing
|
|
- OpenAI API - Receipt OCR and parsing
|
|
|
|
## Architecture
|
|
|
|
MoneyMap follows a clean, service-oriented architecture:
|
|
|
|
```
|
|
┌─────────────────────────────────────────────┐
|
|
│ Razor Pages (UI Layer) │
|
|
│ Index, Upload, Transactions, ViewReceipt │
|
|
│ EditTransaction, CategoryMappings, etc. │
|
|
└────────────────┬────────────────────────────┘
|
|
│
|
|
┌────────────────┴────────────────────────────┐
|
|
│ Service Layer (Business Logic) │
|
|
│ Core Transaction Services: │
|
|
│ - TransactionImporter │
|
|
│ - CardResolver │
|
|
│ - TransactionCategorizer │
|
|
│ - TransactionService (NEW) │
|
|
│ - TransactionStatisticsService (NEW) │
|
|
│ Entity Management Services: │
|
|
│ - AccountService (NEW) │
|
|
│ - CardService (NEW) │
|
|
│ - MerchantService (EXPANDED) │
|
|
│ Receipt Services: │
|
|
│ - ReceiptMatchingService (NEW) │
|
|
│ - ReceiptManager │
|
|
│ - AIReceiptParser │
|
|
│ Reference & Dashboard: │
|
|
│ - ReferenceDataService (NEW) │
|
|
│ - DashboardService │
|
|
└────────────────┬────────────────────────────┘
|
|
│
|
|
┌────────────────┴────────────────────────────┐
|
|
│ Data Access Layer (EF Core) │
|
|
│ MoneyMapContext │
|
|
└────────────────┬────────────────────────────┘
|
|
│
|
|
┌────────────────┴────────────────────────────┐
|
|
│ SQL Server Database │
|
|
│ Cards, Accounts, Transactions, Receipts, │
|
|
│ ReceiptLineItems, ReceiptParseLogs, │
|
|
│ CategoryMappings, Merchants, Budgets │
|
|
└─────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Core Domain Models
|
|
|
|
### Account (Models/Account.cs)
|
|
Represents a bank account (checking, savings, credit card account).
|
|
|
|
**Properties:**
|
|
- `Id` (int) - Primary key
|
|
- `Issuer` (string, 100) - Institution name
|
|
- `Last4` (string, 4) - Last 4 digits of account number
|
|
- `Owner` (string, 100) - Account owner name (optional)
|
|
- `DisplayLabel` (string, computed) - Formatted label for display
|
|
- `Cards` - Collection of cards linked to this account
|
|
- `Transactions` - Collection of transactions for this account
|
|
|
|
### Card (Models/Card.cs)
|
|
Represents a payment card (credit/debit) linked to an account.
|
|
|
|
**Properties:**
|
|
- `Id` (int) - Primary key
|
|
- `AccountId` (int) - Foreign key to Account
|
|
- `Issuer` (string, 100) - Card issuer (VISA, MC, etc.)
|
|
- `Last4` (string, 4) - Last 4 digits of card number
|
|
- `Owner` (string, 100) - Cardholder name (optional)
|
|
- `DisplayLabel` (string, computed) - Formatted label for display
|
|
- `Transactions` - Navigation property to transactions
|
|
|
|
### Merchant (Models/Merchant.cs)
|
|
Represents a merchant/vendor where transactions occur.
|
|
|
|
**Properties:**
|
|
- `Id` (int) - Primary key
|
|
- `Name` (string, 100) - Merchant name
|
|
- `Transactions` - Collection of transactions with this merchant
|
|
- `CategoryMappings` - Collection of category mapping rules
|
|
|
|
### Transaction (Models/Transaction.cs)
|
|
Core entity representing a bank transaction.
|
|
|
|
**Properties:**
|
|
- `Id` (long) - Primary key
|
|
- `Date` (DateTime) - Transaction date
|
|
- `TransactionType` (string, 20) - "DEBIT"/"CREDIT"
|
|
- `Name` (string, 200) - Transaction name from CSV
|
|
- `Memo` (string, 500) - Transaction description
|
|
- `Amount` (decimal) - Amount (negative = debit, positive = credit)
|
|
- `Category` (string, 100) - Expense category
|
|
- `MerchantId` (int?) - Foreign key to Merchant
|
|
- `Notes` (string) - User notes
|
|
- `AccountId` (int) - Foreign key to Account (required)
|
|
- `CardId` (int?) - Foreign key to Card (optional)
|
|
- `TransferToAccountId` (int?) - Foreign key for transfers
|
|
- `Last4` (string, 4) - Cached last 4 digits from memo
|
|
- `Receipts` - Collection of attached receipts
|
|
|
|
**Unique Index:** Date + Amount + Name + Memo + AccountId + CardId (prevents duplicates)
|
|
|
|
**Computed Properties:**
|
|
- `IsCredit` - Returns true if Amount > 0
|
|
- `IsDebit` - Returns true if Amount < 0
|
|
- `IsTransfer` - Returns true if TransferToAccountId is set
|
|
- `PaymentMethodLabel` - Formatted payment method string
|
|
|
|
### Receipt (Models/Receipt.cs)
|
|
Stores uploaded receipt files (images/PDFs) linked to transactions.
|
|
|
|
**Properties:**
|
|
- `Id` (long) - Primary key
|
|
- `TransactionId` (long?) - Foreign key to Transaction (nullable to support unmapped receipts)
|
|
- `FileName` (string, 260) - Original file name (sanitized)
|
|
- `ContentType` (string, 100) - MIME type
|
|
- `StoragePath` (string, 1024) - Relative path in wwwroot
|
|
- `FileSizeBytes` (long) - File size
|
|
- `FileHashSha256` (string, 64) - SHA256 hash for deduplication
|
|
- `UploadedAtUtc` (DateTime) - Upload timestamp
|
|
|
|
**Parsed Fields (populated by AI parser):**
|
|
- `Merchant` (string, 200) - Merchant name extracted from receipt
|
|
- `ReceiptDate` (DateTime?) - Date on receipt
|
|
- `DueDate` (DateTime?) - Payment due date (for bills only)
|
|
- `Subtotal`, `Tax`, `Total` (decimal?) - Monetary amounts
|
|
- `Currency` (string, 8) - Currency code
|
|
|
|
**Navigation Properties:**
|
|
- `ParseLogs` - Collection of parse attempts
|
|
- `LineItems` - Collection of receipt line items
|
|
|
|
**Unique Index:** TransactionId + FileHashSha256 (prevents duplicate uploads, filtered WHERE TransactionId IS NOT NULL)
|
|
|
|
### ReceiptLineItem (Models/ReceiptLineItem.cs)
|
|
Individual line items extracted from receipts.
|
|
|
|
**Properties:**
|
|
- `Id` (long) - Primary key
|
|
- `ReceiptId` (long) - Foreign key to Receipt
|
|
- `LineNumber` (int) - Sequential line number
|
|
- `Description` (string, 300) - Item description
|
|
- `Quantity` (decimal?) - Quantity purchased (null for services/fees)
|
|
- `UnitPrice` (decimal?) - Price per unit
|
|
- `LineTotal` (decimal) - Total for this line
|
|
|
|
### ReceiptParseLog (Models/ReceiptParseLog.cs)
|
|
Logs each receipt parsing attempt for auditing.
|
|
|
|
**Properties:**
|
|
- `Id` (long) - Primary key
|
|
- `ReceiptId` (long) - Foreign key to Receipt
|
|
- `Provider` (string, 50) - "OpenAI"
|
|
- `Model` (string, 100) - Model used (e.g., "gpt-4o-mini")
|
|
- `StartedAtUtc`, `CompletedAtUtc` (DateTime)
|
|
- `Success` (bool) - Parse success status
|
|
- `Confidence` (decimal?) - AI confidence score
|
|
- `Error` (string?) - Error message if failed
|
|
- `RawProviderPayloadJson` (string?) - Full API response
|
|
|
|
### CategoryMapping (Services/TransactionCategorizer.cs)
|
|
Pattern-based rules for auto-categorization with merchant linking.
|
|
|
|
**Properties:**
|
|
- `Id` (int) - Primary key
|
|
- `Category` (string) - Category name
|
|
- `Pattern` (string) - Merchant name pattern to match
|
|
- `MerchantId` (int?) - Foreign key to Merchant (optional)
|
|
- `Priority` (int) - Higher priority checked first (default 0)
|
|
|
|
### Budget (Models/Budget.cs)
|
|
Represents a spending budget for a category or total spending.
|
|
|
|
**Properties:**
|
|
- `Id` (int) - Primary key
|
|
- `Category` (string?, 100) - Category name (null = total spending budget)
|
|
- `Amount` (decimal) - Budget limit amount
|
|
- `Period` (BudgetPeriod) - Weekly, Monthly, or Yearly
|
|
- `StartDate` (DateTime) - When budget becomes effective (used for period boundaries)
|
|
- `IsActive` (bool) - Whether budget is currently active
|
|
- `Notes` (string?, 500) - Optional notes
|
|
|
|
**Computed Properties:**
|
|
- `IsTotalBudget` - Returns true if Category is null
|
|
- `DisplayName` - Returns Category or "Total Spending"
|
|
|
|
**Unique Index:** Category + Period (filtered WHERE IsActive = 1, prevents duplicate active budgets)
|
|
|
|
**BudgetPeriod Enum:**
|
|
- `Weekly` (0) - 7-day periods from StartDate
|
|
- `Monthly` (1) - Monthly periods based on StartDate day-of-month
|
|
- `Yearly` (2) - Annual periods from StartDate anniversary
|
|
|
|
## Service Layer
|
|
|
|
### TransactionImporter (Pages/Upload.cshtml.cs)
|
|
**Interface:** `ITransactionImporter`
|
|
|
|
**Responsibility:** Import transactions from CSV files.
|
|
|
|
**Key Methods:**
|
|
- `ImportAsync(Stream csvStream, ImportContext context)`
|
|
- Parses CSV using CsvHelper
|
|
- Resolves card for each transaction (auto or manual)
|
|
- Deduplicates against database and current batch
|
|
- Returns `ImportOperationResult` with stats
|
|
|
|
**Workflow:**
|
|
1. Read CSV with flexible header mapping
|
|
2. For each row:
|
|
- Resolve card using CardResolver
|
|
- Map CSV row to Transaction entity
|
|
- Check for duplicates (database + in-memory batch)
|
|
- Add non-duplicates to batch
|
|
3. Bulk save to database
|
|
4. Return import statistics
|
|
|
|
**Location:** Pages/Upload.cshtml.cs:92-180
|
|
|
|
### CardResolver (Pages/Upload.cshtml.cs)
|
|
**Interface:** `ICardResolver`
|
|
|
|
**Responsibility:** Determine which card a transaction belongs to.
|
|
|
|
**Key Methods:**
|
|
- `ResolveCardAsync(string? memo, ImportContext context)`
|
|
- Auto mode: Extract last 4 digits from memo or filename
|
|
- Manual mode: Use user-selected card
|
|
- Auto-creates cards if not found
|
|
- Returns `CardResolutionResult`
|
|
|
|
**Card Extraction Logic:**
|
|
- From memo: Regex pattern `\b(?:\.|\s)(\d{4,6})\b` extracts last 4 digits
|
|
- From filename: Split by `-`, `_`, or space; find 4-digit numeric segment
|
|
- Auto-create: If card doesn't exist, creates new Card with Owner="Unknown"
|
|
|
|
**Location:** Pages/Upload.cshtml.cs:190-248
|
|
|
|
### TransactionCategorizer (Services/TransactionCategorizer.cs)
|
|
**Interface:** `ITransactionCategorizer`
|
|
|
|
**Responsibility:** Auto-categorize transactions based on merchant name patterns.
|
|
|
|
**Key Methods:**
|
|
- `CategorizeAsync(string merchantName, decimal? amount = null)`
|
|
- Matches merchant name against CategoryMapping patterns (case-insensitive)
|
|
- Special logic: Gas stations with small purchases (<-$20) → "Convenience Store"
|
|
- Assigns merchant to transaction if mapping has MerchantId
|
|
- Returns category string or empty if no match
|
|
- `GetAllMappingsAsync()` - Retrieve all mappings for management UI
|
|
- `SeedDefaultMappingsAsync()` - One-time seed of default category rules
|
|
|
|
**Default Categories (200+ patterns):**
|
|
- Online shopping (Amazon, Sephora, Kohls, etc.)
|
|
- Walmart (separate: Online, Pickup/Grocery)
|
|
- Pizza chains (Domino's, Papa John's, etc.)
|
|
- Brick/mortar stores (Dollar General, Kroger, Target, etc.)
|
|
- Restaurants (McDonald's, Starbucks, Olive Garden, etc.)
|
|
- School expenses
|
|
- Health (pharmacies, clinics, dental)
|
|
- Gas & Auto (high priority, 100)
|
|
- Utilities/Services (Verizon, Comcast, etc.)
|
|
- Entertainment (Netflix, Hulu, Steam, etc.)
|
|
- Banking fees (highest priority, 200)
|
|
- Mortgage, Car Payment, Insurance
|
|
- Credit Card payments
|
|
- Ice Cream/Treats
|
|
- Government/DMV
|
|
- Home Improvement
|
|
- Software/Subscriptions
|
|
|
|
**Location:** Services/TransactionCategorizer.cs:31-231
|
|
|
|
### TransactionService (Services/TransactionService.cs)
|
|
**Interface:** `ITransactionService`
|
|
|
|
**Responsibility:** Core transaction operations including duplicate detection, retrieval, and deletion.
|
|
|
|
**Key Methods:**
|
|
- `IsDuplicateAsync(Transaction transaction)`
|
|
- Checks if a transaction already exists in the database
|
|
- Matches on: Date, Amount, Name, Memo, AccountId, and CardId
|
|
- Used during CSV import to prevent duplicate entries
|
|
- Returns boolean indicating if duplicate exists
|
|
|
|
- `GetTransactionByIdAsync(long id, bool includeRelated = false)`
|
|
- Retrieves a transaction by ID
|
|
- Optional parameter to include related entities (Card, Account, Merchant, Receipts, etc.)
|
|
- Returns Transaction entity or null if not found
|
|
|
|
- `DeleteTransactionAsync(long id)`
|
|
- Deletes a transaction and all related data
|
|
- Cascades to Receipts, ParseLogs, and LineItems
|
|
- Returns boolean indicating success
|
|
|
|
**Design Pattern:**
|
|
- Consolidates duplicate detection logic previously scattered across multiple PageModels
|
|
- Single source of truth for transaction CRUD operations
|
|
- Improves testability by separating business logic from UI layer
|
|
|
|
**Location:** Services/TransactionService.cs
|
|
|
|
### ReceiptMatchingService (Services/ReceiptMatchingService.cs)
|
|
**Interface:** `IReceiptMatchingService`
|
|
|
|
**Responsibility:** Match receipts to transactions using intelligent scoring based on date, merchant, and amount.
|
|
|
|
**Key Methods:**
|
|
- `FindMatchingTransactionsAsync(ReceiptMatchCriteria criteria)`
|
|
- Finds candidate transactions that match receipt criteria
|
|
- Uses multi-stage filtering and scoring algorithm
|
|
- Returns sorted list of `TransactionMatch` objects with relevance scores
|
|
|
|
- `GetTransactionIdsWithReceiptsAsync()`
|
|
- Returns set of transaction IDs that already have receipts
|
|
- Used to exclude already-mapped transactions from matching
|
|
|
|
**Matching Algorithm:**
|
|
1. **Date Filtering:**
|
|
- Regular receipts: ±3 days from receipt date
|
|
- Bills with due dates: From receipt date to due date + 5 days (for auto-pay delays)
|
|
- No date: Skip date filter
|
|
|
|
2. **Merchant Matching (Word-Based Scoring):**
|
|
- Exact match: Score = 1000
|
|
- Word overlap: Count matching words between receipt merchant and transaction name/merchant
|
|
- Splits on spaces, hyphens, underscores, periods
|
|
- Uses case-insensitive comparison
|
|
- Prioritizes merchant name over transaction description
|
|
|
|
3. **Amount Tolerance:**
|
|
- ±10% tolerance from receipt total
|
|
- Marks exact matches and close matches separately
|
|
- Filters out transactions outside tolerance range
|
|
|
|
4. **Fallback:**
|
|
- If no matches found and no receipt date: Returns 50 most recent unmapped transactions
|
|
|
|
**DTOs:**
|
|
- `ReceiptMatchCriteria` - Input criteria (receipt date, due date, total, merchant, exclusions)
|
|
- `TransactionMatch` - Output with transaction details and match quality flags (IsExactAmount, IsCloseAmount)
|
|
|
|
**Design Pattern:**
|
|
- Extracts complex 140+ line matching algorithm from Receipts PageModel
|
|
- Enables unit testing of matching logic
|
|
- Centralizes receipt-to-transaction correlation rules
|
|
|
|
**Location:** Services/ReceiptMatchingService.cs
|
|
|
|
### AccountService (Services/AccountService.cs)
|
|
**Interface:** `IAccountService`
|
|
|
|
**Responsibility:** Account management including retrieval, validation, and deletion.
|
|
|
|
**Key Methods:**
|
|
- `GetAccountByIdAsync(int id, bool includeRelated = false)`
|
|
- Retrieves account by ID with optional related data (cards, transactions)
|
|
- Returns Account entity or null
|
|
|
|
- `GetAllAccountsWithStatsAsync()`
|
|
- Gets all accounts with transaction counts
|
|
- Returns list of `AccountWithStats` DTOs
|
|
- Sorted by owner, institution, last4
|
|
|
|
- `GetAccountDetailsAsync(int id)`
|
|
- Gets comprehensive account details including linked cards and stats
|
|
- Returns `AccountDetails` DTO with cards and transaction counts
|
|
|
|
- `CanDeleteAccountAsync(int id)`
|
|
- Validates if account can be deleted (no transactions)
|
|
- Returns `DeleteValidationResult`
|
|
|
|
- `DeleteAccountAsync(int id)`
|
|
- Deletes account after validation
|
|
- Returns `DeleteResult` with success/failure message
|
|
|
|
**DTOs:**
|
|
- `AccountWithStats` - Account with transaction count
|
|
- `AccountDetails` - Full account details with cards and statistics
|
|
- `DeleteValidationResult` - Validation result with reason if cannot delete
|
|
- `DeleteResult` - Operation result with success status and message
|
|
|
|
**Design Pattern:**
|
|
- Consolidates account deletion validation logic from multiple PageModels
|
|
- Provides single source of truth for account operations
|
|
- Improves testability
|
|
|
|
**Location:** Services/AccountService.cs
|
|
|
|
### CardService (Services/CardService.cs)
|
|
**Interface:** `ICardService`
|
|
|
|
**Responsibility:** Card management including retrieval, validation, and deletion.
|
|
|
|
**Key Methods:**
|
|
- `GetCardByIdAsync(int id, bool includeRelated = false)`
|
|
- Retrieves card by ID with optional related data (account, transactions)
|
|
- Returns Card entity or null
|
|
|
|
- `GetAllCardsWithStatsAsync()`
|
|
- Gets all cards with transaction counts
|
|
- Returns list of `CardWithStats` DTOs
|
|
- Sorted by owner, last4
|
|
|
|
- `CanDeleteCardAsync(int id)`
|
|
- Validates if card can be deleted (no transactions)
|
|
- Returns `DeleteValidationResult`
|
|
|
|
- `DeleteCardAsync(int id)`
|
|
- Deletes card after validation
|
|
- Returns `DeleteResult` with success/failure message
|
|
|
|
**DTOs:**
|
|
- `CardWithStats` - Card with transaction count
|
|
|
|
**Design Pattern:**
|
|
- Consolidates card deletion validation logic from Cards and AccountDetails pages
|
|
- Mirrors AccountService for consistency
|
|
- Enables reusable card management operations
|
|
|
|
**Location:** Services/CardService.cs
|
|
|
|
### MerchantService (Services/MerchantService.cs) - EXPANDED
|
|
**Interface:** `IMerchantService`
|
|
|
|
**Responsibility:** Merchant management including creation, retrieval, update, and deletion.
|
|
|
|
**Existing Methods:**
|
|
- `FindByNameAsync(string name)` - Find merchant by exact name match
|
|
- `GetOrCreateAsync(string name)` - Get existing or create new merchant
|
|
- `GetOrCreateIdAsync(string? name)` - Get or create merchant and return ID
|
|
|
|
**New Methods:**
|
|
- `GetMerchantByIdAsync(int id, bool includeRelated = false)`
|
|
- Retrieves merchant by ID with optional related data (transactions, category mappings)
|
|
|
|
- `GetAllMerchantsWithStatsAsync()`
|
|
- Gets all merchants with transaction and mapping counts
|
|
- Returns list of `MerchantWithStats` DTOs
|
|
- Sorted alphabetically by name
|
|
|
|
- `UpdateMerchantAsync(int id, string newName)`
|
|
- Updates merchant name with duplicate checking
|
|
- Returns `MerchantUpdateResult`
|
|
|
|
- `DeleteMerchantAsync(int id)`
|
|
- Deletes merchant and reports unlinked transactions/mappings
|
|
- Returns `MerchantDeleteResult` with counts
|
|
|
|
**DTOs:**
|
|
- `MerchantWithStats` - Merchant with transaction and mapping counts
|
|
- `MerchantUpdateResult` - Update operation result
|
|
- `MerchantDeleteResult` - Delete operation result with impact statistics
|
|
|
|
**Design Pattern:**
|
|
- Expands existing service with full CRUD operations
|
|
- Used by EditTransaction for merchant creation
|
|
- Used by Merchants page for management UI
|
|
|
|
**Location:** Services/MerchantService.cs
|
|
|
|
### BudgetService (Services/BudgetService.cs)
|
|
**Interface:** `IBudgetService`
|
|
|
|
**Responsibility:** Budget management including CRUD operations and spending calculations.
|
|
|
|
**Key Methods:**
|
|
- `GetAllBudgetsAsync(bool activeOnly = true)` - Get all budgets, optionally filtered to active only
|
|
- `GetBudgetByIdAsync(int id)` - Get budget by ID
|
|
- `CreateBudgetAsync(Budget budget)` - Create new budget with duplicate validation
|
|
- `UpdateBudgetAsync(Budget budget)` - Update existing budget
|
|
- `DeleteBudgetAsync(int id)` - Delete budget
|
|
|
|
- `GetAllBudgetStatusesAsync(DateTime? asOfDate = null)` - Get spending status for all active budgets
|
|
- `GetBudgetStatusAsync(int budgetId, DateTime? asOfDate = null)` - Get spending status for specific budget
|
|
|
|
- `GetAvailableCategoriesAsync()` - Get list of categories for budget creation
|
|
- `GetPeriodBoundaries(BudgetPeriod period, DateTime startDate, DateTime asOfDate)` - Calculate period start/end dates
|
|
|
|
**Period Boundary Logic:**
|
|
- **Weekly:** 7-day periods starting from StartDate
|
|
- **Monthly:** Periods run from StartDate's day-of-month (e.g., 15th to 14th)
|
|
- **Yearly:** Annual periods from StartDate anniversary
|
|
|
|
**DTOs:**
|
|
- `BudgetOperationResult` - CRUD operation result with success/message
|
|
- `BudgetStatus` - Budget with calculated spending data:
|
|
- `Budget` - The budget entity
|
|
- `PeriodStart`, `PeriodEnd` - Current period boundaries
|
|
- `Spent` - Total spending in period
|
|
- `Remaining` - Budget amount minus spent
|
|
- `PercentUsed` - Percentage of budget consumed
|
|
- `IsOverBudget` - Whether spending exceeds budget
|
|
- `StatusClass` - CSS class (success/warning/danger)
|
|
|
|
**Design Pattern:**
|
|
- Follows service layer pattern with interface
|
|
- Uses DTOs for complex return types
|
|
- Calculates spending by querying transactions within period boundaries
|
|
- Excludes transfers from spending calculations
|
|
|
|
**Location:** Services/BudgetService.cs
|
|
|
|
### TransactionStatisticsService (Services/TransactionStatisticsService.cs)
|
|
**Interface:** `ITransactionStatisticsService`
|
|
|
|
**Responsibility:** Calculate transaction statistics and aggregates.
|
|
|
|
**Key Methods:**
|
|
- `CalculateStatsAsync(IQueryable<Transaction> query)`
|
|
- Calculates statistics for any filtered set of transactions
|
|
- Returns `TransactionStats` DTO with counts and totals
|
|
- Computes: total count, total debits, total credits, net amount
|
|
|
|
- `GetCategorizationStatsAsync()`
|
|
- Gets categorization statistics for entire database
|
|
- Returns `CategorizationStats` with categorized/uncategorized counts
|
|
|
|
- `GetCardStatsForAccountAsync(int accountId)`
|
|
- Gets transaction counts for all cards linked to an account
|
|
- Returns list of `CardStats` DTOs
|
|
|
|
**DTOs:**
|
|
- `TransactionStats` - Aggregate statistics (count, debits, credits, net)
|
|
- `CategorizationStats` - Categorization summary
|
|
- `CardStats` - Card with transaction count
|
|
|
|
**Design Pattern:**
|
|
- Consolidates statistics calculation from Transactions, Recategorize, and AccountDetails pages
|
|
- Enables consistent statistics across the application
|
|
- Improves performance by centralizing query logic
|
|
|
|
**Location:** Services/TransactionStatisticsService.cs
|
|
|
|
### ReferenceDataService (Services/ReferenceDataService.cs)
|
|
**Interface:** `IReferenceDataService`
|
|
|
|
**Responsibility:** Provide reference/lookup data for dropdowns and filters.
|
|
|
|
**Key Methods:**
|
|
- `GetAvailableCategoriesAsync()`
|
|
- Gets all distinct categories from transactions
|
|
- Returns sorted list of category names
|
|
- Excludes empty/whitespace categories
|
|
|
|
- `GetAvailableMerchantsAsync()`
|
|
- Gets all merchants sorted by name
|
|
- Returns list of Merchant entities
|
|
|
|
- `GetAvailableCardsAsync(bool includeAccount = true)`
|
|
- Gets all cards with optional account information
|
|
- Returns sorted list of Card entities
|
|
- Sorted by owner, last4
|
|
|
|
- `GetAvailableAccountsAsync()`
|
|
- Gets all accounts sorted by institution and last4
|
|
- Returns list of Account entities
|
|
|
|
**Design Pattern:**
|
|
- Centralizes repetitive reference data queries used across multiple pages
|
|
- Reduces code duplication in Transactions, EditTransaction, Upload pages
|
|
- Provides consistent sorting and filtering
|
|
|
|
**Location:** Services/ReferenceDataService.cs
|
|
|
|
### DashboardService (Pages/Index.cshtml.cs)
|
|
**Interface:** `IDashboardService`
|
|
|
|
**Responsibility:** Aggregate dashboard statistics and data.
|
|
|
|
**Key Methods:**
|
|
- `GetDashboardDataAsync(int topCategoriesCount, int recentTransactionsCount)`
|
|
- Orchestrates calls to specialized providers
|
|
- Returns `DashboardData` DTO
|
|
|
|
**Sub-Services:**
|
|
|
|
#### IDashboardStatsCalculator
|
|
- `CalculateAsync()` - Compute aggregate stats:
|
|
- Total transactions, credits, debits, uncategorized count
|
|
- Total receipts, total cards
|
|
|
|
#### ITopCategoriesProvider
|
|
- `GetTopCategoriesAsync(int count, int lastDays)`
|
|
- Aggregates spending by category for last N days (default 90)
|
|
- Returns top N categories by total spend
|
|
- Includes transaction count and average per transaction
|
|
- Excludes transfers
|
|
|
|
#### IRecentTransactionsProvider
|
|
- `GetRecentTransactionsAsync(int count)`
|
|
- Fetches most recent N transactions (default 20)
|
|
- Includes card label, receipt count
|
|
- Sorted by date descending, then ID descending
|
|
|
|
**Location:** Pages/Index.cshtml.cs:63-250
|
|
|
|
### ReceiptManager (Services/ReceiptManager.cs)
|
|
**Interface:** `IReceiptManager`
|
|
|
|
**Responsibility:** Handle receipt file uploads, storage, and transaction mapping.
|
|
|
|
**Key Methods:**
|
|
- `UploadReceiptAsync(long transactionId, IFormFile file)`
|
|
- Validates file (10MB max, allowed extensions: jpg, jpeg, png, pdf, gif, heic)
|
|
- Computes SHA256 hash for deduplication
|
|
- Checks for duplicates (same hash or same filename + size)
|
|
- Saves file to `wwwroot/receipts/{transactionId}_{guid}{ext}`
|
|
- Sanitizes filename (removes non-ASCII like ®, ™, ©)
|
|
- Creates Receipt entity in database
|
|
- Returns `ReceiptUploadResult` with duplicate warnings
|
|
|
|
- `UploadUnmappedReceiptAsync(IFormFile file)`
|
|
- Same as UploadReceiptAsync but for receipts without initial transaction mapping
|
|
- TransactionId is null until later mapped
|
|
|
|
- `MapReceiptToTransactionAsync(long receiptId, long transactionId)`
|
|
- Links an unmapped receipt to a transaction
|
|
- Returns success boolean
|
|
|
|
- `DeleteReceiptAsync(long receiptId)`
|
|
- Deletes physical file from disk
|
|
- Removes database record (cascades to ParseLogs and LineItems)
|
|
|
|
- `GetReceiptPhysicalPath(Receipt receipt)` - Resolves storage path
|
|
- `GetReceiptAsync(long receiptId)` - Retrieves receipt with transaction
|
|
|
|
**Duplicate Detection:**
|
|
- SHA256 hash matching (identical files)
|
|
- Filename + size matching (possible duplicates)
|
|
- Returns `DuplicateWarning` list with existing receipts
|
|
- User can confirm upload anyway via modal dialog
|
|
|
|
**Security Features:**
|
|
- File size validation (10MB limit)
|
|
- Extension whitelist
|
|
- Filename sanitization
|
|
- SHA256 deduplication
|
|
|
|
**Location:** Services/ReceiptManager.cs:23-199
|
|
|
|
### OpenAIReceiptParser (Services/OpenAIReceiptParser.cs)
|
|
**Interface:** `IReceiptParser`
|
|
|
|
**Responsibility:** Parse receipts using OpenAI GPT-4o-mini Vision API.
|
|
|
|
**Key Methods:**
|
|
- `ParseReceiptAsync(long receiptId)`
|
|
- Loads receipt file from disk
|
|
- Converts PDFs to PNG images using ImageMagick (220 DPI)
|
|
- Calls OpenAI Vision API with structured prompt
|
|
- Parses JSON response (merchant, date, due date, amounts, line items)
|
|
- Updates Receipt entity with extracted data
|
|
- Replaces existing line items
|
|
- Logs parse attempt in ReceiptParseLog
|
|
- Attempts auto-mapping if receipt is unmapped
|
|
- Returns `ReceiptParseResult`
|
|
|
|
**API Configuration:**
|
|
- Model: `gpt-4o-mini`
|
|
- Temperature: 0.1 (deterministic)
|
|
- Max tokens: 2000
|
|
- API key: Environment variable `OPENAI_API_KEY` or config `OpenAI:ApiKey`
|
|
|
|
**Prompt Strategy:**
|
|
- Structured JSON request with schema example
|
|
- Extracts: merchant, date, dueDate (for bills), subtotal, tax, total, confidence
|
|
- Line items with: description, quantity, unitPrice, lineTotal
|
|
- Special handling: Services/fees have null quantity (not products)
|
|
- Due date extraction: For bills (utility, credit card, etc.), extracts payment due date
|
|
|
|
**PDF Handling:**
|
|
- ImageMagick converts first page to PNG at 220 DPI
|
|
- Flattens alpha channel (white background)
|
|
- TrueColor 8-bit RGB output
|
|
|
|
**Auto-Mapping Integration:**
|
|
- After successful parse of unmapped receipts, triggers ReceiptAutoMapper
|
|
- Attempts to automatically link receipt to matching transaction
|
|
- Silently fails if auto-mapping unsuccessful (parsing still successful)
|
|
|
|
**Location:** Services/OpenAIReceiptParser.cs:23-342
|
|
|
|
### ReceiptAutoMapper (Services/ReceiptAutoMapper.cs)
|
|
**Interface:** `IReceiptAutoMapper`
|
|
|
|
**Responsibility:** Automatically map parsed receipts to matching transactions using smart matching rules.
|
|
|
|
**Key Methods:**
|
|
- `AutoMapReceiptAsync(long receiptId)`
|
|
- Attempts to match a single receipt to a transaction
|
|
- Uses date range, merchant name, and amount matching
|
|
- Returns `ReceiptAutoMapResult` with status (Success, NoMatch, MultipleMatches, AlreadyMapped, NotParsed, Failed)
|
|
|
|
- `AutoMapUnmappedReceiptsAsync()`
|
|
- Bulk processes all unmapped parsed receipts
|
|
- Returns `BulkAutoMapResult` with counts (total, mapped, no match, multiple matches)
|
|
|
|
**Matching Algorithm:**
|
|
1. **Date Range Filtering:**
|
|
- For bills with due dates: `billDate` to `dueDate + 5 days` (accounts for auto-pay delays, weekends, bank processing)
|
|
- For regular receipts: `receiptDate ± 3 days` (accounts for transaction posting delays)
|
|
- No date = no match (can't narrow down effectively)
|
|
|
|
2. **Merchant Filtering (optional):**
|
|
- Matches if merchant name or transaction name contains receipt merchant
|
|
- Case-insensitive contains matching
|
|
|
|
3. **Amount Matching (optional):**
|
|
- If receipt has total: matches transactions within ±$0.10 tolerance
|
|
- Accounts for rounding differences and tips
|
|
|
|
4. **Receipt Exclusion:**
|
|
- Excludes transactions that already have receipts attached
|
|
- Uses HashSet for performance
|
|
|
|
**Auto-Mapping Triggers:**
|
|
- Automatically triggered after successful receipt parsing (if receipt is unmapped)
|
|
- Can be manually triggered via "Auto-Map Unmapped Receipts" button on Receipts page
|
|
|
|
**Result Status Types:**
|
|
- `Success` - Single match found, receipt mapped automatically
|
|
- `NoMatch` - No matching transactions found
|
|
- `MultipleMatches` - Multiple potential matches found (requires manual selection)
|
|
- `AlreadyMapped` - Receipt already linked to a transaction
|
|
- `NotParsed` - Receipt has no parsed data (merchant, date, or total missing)
|
|
- `Failed` - Error during mapping process
|
|
|
|
**Location:** Services/ReceiptAutoMapper.cs
|
|
|
|
### FinancialAuditService (Services/FinancialAuditService.cs)
|
|
**Interface:** `IFinancialAuditService`
|
|
|
|
**Responsibility:** Generate comprehensive financial audit data for AI analysis via the `/api/audit` endpoint.
|
|
|
|
**Key Methods:**
|
|
- `GenerateAuditAsync(DateTime startDate, DateTime endDate, bool includeTransactions = false)`
|
|
- Aggregates all financial data for the specified period
|
|
- Returns `FinancialAuditResponse` with summary, budgets, categories, merchants, trends, accounts, and flags
|
|
|
|
**Response Structure:**
|
|
- `Summary` - High-level stats (income, expenses, net, average daily spend, uncategorized count)
|
|
- `Budgets` - All active budget statuses with period info and over-budget flags
|
|
- `SpendingByCategory` - Category breakdown with budget correlation
|
|
- `TopMerchants` - Top 20 merchants by spending
|
|
- `MonthlyTrends` - Month-by-month income/expense/net with top categories per month
|
|
- `Accounts` - Per-account transaction summaries
|
|
- `Flags` - AI-friendly alerts (over-budget, spending increases, uncategorized, etc.)
|
|
- `Transactions` - Optional detailed transaction list
|
|
|
|
**Audit Flags Generated:**
|
|
- `OverBudget` - Budgets exceeded (Alert)
|
|
- `HighBudgetUtilization` - Budgets at 80%+ (Warning)
|
|
- `Uncategorized` - Transactions without categories (Warning)
|
|
- `NegativeCashFlow` - Spending exceeds income (Alert)
|
|
- `HighCategoryConcentration` - Category >30% of total spending (Info)
|
|
- `SpendingIncrease` - Month-over-month increase >20% (Warning)
|
|
- `NoBudget` - Top spending categories without budgets (Info)
|
|
|
|
**Design Pattern:**
|
|
- Orchestrates existing services (IBudgetService) and new queries
|
|
- Single comprehensive endpoint for AI consumption
|
|
- Excludes transfers from spending calculations
|
|
|
|
**Location:** Services/FinancialAuditService.cs
|
|
|
|
### TransactionAICategorizer (Services/TransactionAICategorizer.cs)
|
|
**Interface:** `ITransactionAICategorizer`
|
|
|
|
**Responsibility:** AI-powered categorization for uncategorized transactions.
|
|
|
|
**Key Methods:**
|
|
- `ProposeCategorizationAsync(Transaction transaction)`
|
|
- Analyzes transaction details (name, memo, amount, date)
|
|
- Calls OpenAI GPT-4o-mini with categorization prompt
|
|
- Returns `AICategoryProposal` with category, merchant, pattern, and confidence
|
|
- Auto-suggests rule creation for high confidence (≥70%)
|
|
|
|
- `ProposeBatchCategorizationAsync(List<Transaction> transactions)`
|
|
- Processes transactions in batches of 5 to avoid rate limits
|
|
- Returns list of proposals for review
|
|
|
|
- `ApplyProposalAsync(long transactionId, AICategoryProposal proposal, bool createRule)`
|
|
- Updates transaction category and merchant
|
|
- Optionally creates CategoryMapping rule for future auto-categorization
|
|
- Returns `ApplyProposalResult` with success status
|
|
|
|
**API Configuration:**
|
|
- Model: `gpt-4o-mini`
|
|
- Temperature: 0.1 (deterministic)
|
|
- Max tokens: 300
|
|
- API key: Environment variable `OPENAI_API_KEY` or config `OpenAI:ApiKey`
|
|
- Cost: ~$0.00015 per transaction (~$0.015 per 100 transactions)
|
|
|
|
**Prompt Strategy:**
|
|
- Provides transaction details (name, memo, amount, date)
|
|
- Requests JSON response with category, canonical_merchant, pattern, confidence, reasoning
|
|
- Includes common category examples for context
|
|
- High confidence threshold (≥70%) suggests automatic rule creation
|
|
|
|
**CategoryMapping Enhancements:**
|
|
- `Confidence` (decimal?) - AI confidence score (0.0-1.0)
|
|
- `CreatedBy` (string?) - "AI" or "User"
|
|
- `CreatedAt` (DateTime?) - Rule creation timestamp
|
|
|
|
**Location:** Services/TransactionAICategorizer.cs
|
|
|
|
## Data Access Layer
|
|
|
|
### MoneyMapContext (Data/MoneyMapContext.cs)
|
|
EF Core DbContext managing all database entities.
|
|
|
|
**DbSets:**
|
|
- `Accounts` - Bank accounts
|
|
- `Cards` - Payment cards
|
|
- `Merchants` - Merchants/vendors
|
|
- `Transactions` - Bank transactions
|
|
- `Receipts` - Receipt files
|
|
- `ReceiptParseLogs` - Parse attempt logs
|
|
- `ReceiptLineItems` - Receipt line items
|
|
- `CategoryMappings` - Categorization rules
|
|
|
|
**Key Configuration:**
|
|
- **Transactions ↔ Cards**: Restrict delete (can't delete card with transactions)
|
|
- **Transactions ↔ Accounts**: Restrict delete (can't delete account with transactions)
|
|
- **Receipts ↔ Transactions**: Cascade delete (deleting transaction removes receipts)
|
|
- **ReceiptParseLogs ↔ Receipts**: Cascade delete
|
|
- **ReceiptLineItems ↔ Receipts**: Cascade delete
|
|
|
|
**Performance Indexes:**
|
|
- Transaction.Date, Transaction.Amount, Transaction.Category (filtering)
|
|
- Card: (Issuer, Last4, Owner) composite
|
|
- Transaction: (Date, Amount, Name, Memo, AccountId, CardId) unique constraint
|
|
- Receipt: (TransactionId, FileHashSha256) unique constraint
|
|
|
|
**Location:** Data/MoneyMapContext.cs:9-107
|
|
|
|
## Razor Pages (UI Layer)
|
|
|
|
### Index.cshtml / IndexModel (Dashboard)
|
|
**Route:** `/`
|
|
|
|
**Purpose:** Main dashboard showing financial overview.
|
|
|
|
**Displays:**
|
|
- Aggregate stats (total transactions, credits, debits, uncategorized, receipts, cards)
|
|
- Top 8 spending categories (last 90 days) with average per transaction
|
|
- Recent 20 transactions with card labels and receipt counts
|
|
|
|
**Dependencies:** `IDashboardService`
|
|
|
|
**Location:** Pages/Index.cshtml.cs:12-250
|
|
|
|
### Upload.cshtml / UploadModel (CSV Import)
|
|
**Route:** `/Upload`
|
|
|
|
**Purpose:** Import bank transactions from CSV files.
|
|
|
|
**Features:**
|
|
- Card selection mode: Auto (extract from memo/filename) or Manual (user selects)
|
|
- Duplicate detection (unique constraint on Date+Amount+Name+Memo+AccountId+CardId)
|
|
- Auto-creates cards if not found in Auto mode
|
|
- Displays import stats (total, inserted, skipped)
|
|
|
|
**Form Inputs:**
|
|
- CSV file
|
|
- Card mode (radio: Auto/Manual)
|
|
- Card dropdown (if Manual mode)
|
|
|
|
**Dependencies:** `ITransactionImporter`
|
|
|
|
**Location:** Pages/Upload.cshtml.cs:17-333
|
|
|
|
### Transactions.cshtml / TransactionsModel
|
|
**Route:** `/Transactions`
|
|
|
|
**Purpose:** View and search all transactions with advanced filtering.
|
|
|
|
**Features:**
|
|
- Search by name, memo, category, notes, or merchant
|
|
- Filter by card, category, merchant, date range
|
|
- Quick date range selectors (30/60/90 days, last year, this month, last month)
|
|
- Sort by date, amount, name, category
|
|
- Pagination with preserved filters
|
|
- Transaction ID display for easy reference
|
|
- Receipt count indicator with badge
|
|
- Upload CSV button in header
|
|
- Edit transaction link
|
|
|
|
**Stats Display:**
|
|
- Total transaction count
|
|
- Current page showing count
|
|
- Total spent (debits sum)
|
|
- Total income (credits sum)
|
|
- Net amount with color coding
|
|
|
|
**Location:** Pages/Transactions.cshtml.cs
|
|
|
|
### Receipts.cshtml / ReceiptsModel
|
|
**Route:** `/Receipts`
|
|
|
|
**Purpose:** Manage receipts with upload, mapping, and auto-mapping capabilities.
|
|
|
|
**Features:**
|
|
- Upload receipts without initial transaction mapping (unmapped receipts)
|
|
- Duplicate detection with blocking modal dialog
|
|
- SHA256 hash matching (identical files)
|
|
- Filename + size matching (possible duplicates)
|
|
- User confirmation required to proceed
|
|
- View all receipts (mapped and unmapped) with status badges
|
|
- Manual receipt-to-transaction mapping via modal selector
|
|
- Auto-map unmapped receipts button (bulk operation)
|
|
- Smart transaction matching with filters
|
|
- Receipt info display: merchant, date, due date (for bills), total
|
|
- Delete receipts
|
|
- View receipt details
|
|
|
|
**Transaction Selector Modal:**
|
|
- Displays up to 50 matching transactions in scrollable table
|
|
- Date range filtering:
|
|
- Bills with due dates: bill date to due date + 5 days
|
|
- Regular receipts: receipt date ± 3 days
|
|
- Merchant-based relevance sorting (exact match > contains > no match)
|
|
- Amount matching with green highlighting (±$0.10 tolerance)
|
|
- Auto-scroll to first amount match when modal opens
|
|
- Manual transaction ID entry option
|
|
- Link to open Transactions page for searching
|
|
|
|
**Receipt List Columns:**
|
|
- Uploaded timestamp
|
|
- File name with icon (image/PDF)
|
|
- Receipt info (merchant, date, due date, total)
|
|
- Mapped transaction details or "Unmapped" badge
|
|
- Actions: View, Map (if unmapped), Delete
|
|
|
|
**Dependencies:** `IReceiptManager`, `IReceiptAutoMapper`
|
|
|
|
**Location:** Pages/Receipts.cshtml.cs
|
|
|
|
### EditTransaction.cshtml / EditTransactionModel
|
|
**Route:** `/EditTransaction/{id}`
|
|
|
|
**Purpose:** Edit transaction details.
|
|
|
|
**Features:**
|
|
- Update category (select existing or create new)
|
|
- Update merchant (select existing or create new)
|
|
- Update notes
|
|
- Upload/delete receipts
|
|
- View receipt thumbnails with parsed data
|
|
- Trigger AI receipt parsing
|
|
|
|
**Location:** Pages/EditTransaction.cshtml.cs:27-202
|
|
|
|
### ViewReceipt.cshtml / ViewReceiptModel
|
|
**Route:** `/ViewReceipt/{id}`
|
|
|
|
**Purpose:** View receipt details and parsed data.
|
|
|
|
**Displays:**
|
|
- Receipt image/PDF preview
|
|
- File metadata (name, size, upload date)
|
|
- Parsed merchant, date, amounts
|
|
- Line items table (description, quantity, unit price, total)
|
|
- Parse logs (provider, model, confidence, timestamp, errors)
|
|
- Actions: Re-parse, Delete
|
|
|
|
**Dependencies:** `IReceiptManager`, `IReceiptParser`
|
|
|
|
**Location:** Pages/ViewReceipt.cshtml.cs:16-126
|
|
|
|
### CategoryMappings.cshtml / CategoryMappingsModel
|
|
**Route:** `/CategoryMappings`
|
|
|
|
**Purpose:** Manage category mapping rules.
|
|
|
|
**Features:**
|
|
- View all category patterns grouped by category
|
|
- Add/edit/delete mappings
|
|
- Assign merchant to mapping (creates normalized merchant name)
|
|
- Priority management (higher = checked first)
|
|
- Seed default mappings
|
|
- Export/import mappings as JSON
|
|
|
|
**Dependencies:** `ITransactionCategorizer`
|
|
|
|
**Location:** Pages/CategoryMappings.cshtml.cs:16-91
|
|
|
|
### Merchants.cshtml / MerchantsModel
|
|
**Route:** `/Merchants`
|
|
|
|
**Purpose:** Manage merchant entities.
|
|
|
|
**Features:**
|
|
- View all merchants
|
|
- Edit merchant names
|
|
- Delete merchants (with confirmation)
|
|
- See transaction count per merchant
|
|
|
|
**Location:** Pages/Merchants.cshtml.cs
|
|
|
|
### Budgets.cshtml / BudgetsModel
|
|
**Route:** `/Budgets`
|
|
|
|
**Purpose:** Manage spending budgets and track budget status.
|
|
|
|
**Features:**
|
|
- Create budgets for specific categories or total spending
|
|
- Support for Weekly, Monthly, and Yearly periods
|
|
- Real-time spending vs budget tracking with progress bars
|
|
- Color-coded status (green/yellow/red based on % used)
|
|
- Pause/resume budgets without deleting
|
|
- Edit budget amounts and periods
|
|
- View active and inactive budgets separately
|
|
|
|
**Budget Types:**
|
|
- **Category Budget:** Track spending for a specific category (e.g., "Dining Out")
|
|
- **Total Budget:** Track all spending combined (Category = null)
|
|
|
|
**Period Boundaries:**
|
|
- Periods are calculated relative to the budget's StartDate
|
|
- Monthly budgets run from the StartDate's day (e.g., if StartDate is Jan 15, periods run 15th to 14th)
|
|
- Weekly budgets run in 7-day increments from StartDate
|
|
- Yearly budgets run from the StartDate anniversary
|
|
|
|
**Dependencies:** `IBudgetService`
|
|
|
|
**Location:** Pages/Budgets.cshtml.cs
|
|
|
|
### Recategorize.cshtml / RecategorizeModel
|
|
**Route:** `/Recategorize`
|
|
|
|
**Purpose:** Bulk recategorize uncategorized transactions.
|
|
|
|
**Features:**
|
|
- Lists all transactions without a category
|
|
- Applies TransactionCategorizer rules
|
|
- Updates matching transactions
|
|
- Reports counts (total, categorized, still uncategorized)
|
|
|
|
**Dependencies:** `ITransactionCategorizer`
|
|
|
|
**Location:** Pages/Recategorize.cshtml.cs:16-87
|
|
|
|
### ReviewAISuggestions.cshtml / ReviewAISuggestionsModel
|
|
**Route:** `/ReviewAISuggestions`
|
|
|
|
**Purpose:** AI-powered categorization suggestions for uncategorized transactions.
|
|
|
|
**Features:**
|
|
- Lists up to 50 most recent uncategorized transactions
|
|
- Generate AI suggestions button (processes up to 20 at a time)
|
|
- Cost transparency (~$0.00015 per transaction)
|
|
- Link to view uncategorized transactions
|
|
|
|
**Dependencies:** `ITransactionAICategorizer`
|
|
|
|
**Location:** Pages/ReviewAISuggestions.cshtml.cs
|
|
|
|
### ReviewAISuggestionsWithProposals.cshtml / ReviewAISuggestionsWithProposalsModel
|
|
**Route:** `/ReviewAISuggestionsWithProposals`
|
|
|
|
**Purpose:** Review and apply AI categorization proposals.
|
|
|
|
**Features:**
|
|
- Display AI proposals with confidence scores
|
|
- Color-coded confidence indicators (green ≥80%, yellow 60-79%, red <60%)
|
|
- Individual actions: Accept (with/without rule), Reject, Edit Manually
|
|
- Bulk action: Apply all high-confidence suggestions (≥80%)
|
|
- Shows AI reasoning for each suggestion
|
|
- Stores proposals in session for review workflow
|
|
|
|
**Dependencies:** `ITransactionAICategorizer`
|
|
|
|
**Location:** Pages/ReviewAISuggestionsWithProposals.cshtml.cs
|
|
|
|
## API Endpoints
|
|
|
|
### Financial Audit API
|
|
**Route:** `GET /api/audit`
|
|
|
|
**Purpose:** Provide comprehensive financial data for AI analysis and auditing.
|
|
|
|
**Query Parameters:**
|
|
| Parameter | Type | Default | Description |
|
|
|-----------|------|---------|-------------|
|
|
| `startDate` | DateTime? | Today - 90 days | Start of audit period |
|
|
| `endDate` | DateTime? | Today | End of audit period |
|
|
| `includeTransactions` | bool | false | Include detailed transaction list |
|
|
|
|
**Example Requests:**
|
|
```
|
|
GET /api/audit
|
|
GET /api/audit?startDate=2025-01-01&endDate=2025-12-31
|
|
GET /api/audit?startDate=2025-11-01&includeTransactions=true
|
|
```
|
|
|
|
**Response:** JSON `FinancialAuditResponse` object containing:
|
|
- `generatedAt` - Timestamp of audit generation
|
|
- `periodStart`, `periodEnd` - Audit date range
|
|
- `summary` - High-level financial statistics
|
|
- `budgets` - Active budget statuses
|
|
- `spendingByCategory` - Category breakdown with budget correlation
|
|
- `topMerchants` - Top 20 merchants by spending
|
|
- `monthlyTrends` - Month-by-month trends
|
|
- `accounts` - Per-account summaries
|
|
- `flags` - AI-friendly alerts and observations
|
|
- `transactions` - Detailed transaction list (optional)
|
|
|
|
**Authentication:** None (localhost/personal use only)
|
|
|
|
**Use Cases:**
|
|
- AI-powered financial analysis and recommendations
|
|
- Spending pattern identification
|
|
- Budget compliance monitoring
|
|
- External tool integration
|
|
|
|
**Implementation:** Minimal API endpoint in Program.cs, backed by `IFinancialAuditService`
|
|
|
|
**Location:** Program.cs:99-112
|
|
|
|
## Configuration
|
|
|
|
### appsettings.json
|
|
```json
|
|
{
|
|
"ConnectionStrings": {
|
|
"MoneyMapDb": "Server=...;Database=MoneyMap;..."
|
|
},
|
|
"OpenAI": {
|
|
"ApiKey": "sk-..." // Optional, can use OPENAI_API_KEY env var
|
|
},
|
|
"Receipts": {
|
|
"StoragePath": "receipts" // Relative to wwwroot
|
|
}
|
|
}
|
|
```
|
|
|
|
### Program.cs Dependency Injection
|
|
```csharp
|
|
// Database
|
|
builder.Services.AddDbContext<MoneyMapContext>(options =>
|
|
options.UseSqlServer(...));
|
|
|
|
// Transaction Services
|
|
builder.Services.AddScoped<ITransactionImporter, TransactionImporter>();
|
|
builder.Services.AddScoped<ICardResolver, CardResolver>();
|
|
builder.Services.AddScoped<ITransactionCategorizer, TransactionCategorizer>();
|
|
|
|
// Dashboard Services
|
|
builder.Services.AddScoped<IDashboardService, DashboardService>();
|
|
builder.Services.AddScoped<IDashboardStatsCalculator, DashboardStatsCalculator>();
|
|
builder.Services.AddScoped<ITopCategoriesProvider, TopCategoriesProvider>();
|
|
builder.Services.AddScoped<IRecentTransactionsProvider, RecentTransactionsProvider>();
|
|
|
|
// Receipt Services
|
|
builder.Services.AddScoped<IReceiptManager, ReceiptManager>();
|
|
builder.Services.AddHttpClient<IReceiptParser, OpenAIReceiptParser>();
|
|
builder.Services.AddScoped<IReceiptAutoMapper, ReceiptAutoMapper>();
|
|
builder.Services.AddScoped<IMerchantService, MerchantService>();
|
|
```
|
|
|
|
**Location:** Program.cs:1-44
|
|
|
|
## Database Schema (SQL Server)
|
|
|
|
### Accounts Table
|
|
```sql
|
|
Id (int, PK)
|
|
Issuer (nvarchar(100), NOT NULL)
|
|
Last4 (nvarchar(4), NOT NULL)
|
|
Owner (nvarchar(100))
|
|
INDEX: (Issuer, Last4, Owner)
|
|
```
|
|
|
|
### Cards Table
|
|
```sql
|
|
Id (int, PK)
|
|
AccountId (int, FK → Accounts.Id, RESTRICT)
|
|
Issuer (nvarchar(100), NOT NULL)
|
|
Last4 (nvarchar(4), NOT NULL)
|
|
Owner (nvarchar(100))
|
|
INDEX: (Issuer, Last4, Owner)
|
|
```
|
|
|
|
### Merchants Table
|
|
```sql
|
|
Id (int, PK)
|
|
Name (nvarchar(100), NOT NULL)
|
|
```
|
|
|
|
### Transactions Table
|
|
```sql
|
|
Id (bigint, PK)
|
|
Date (datetime2, NOT NULL)
|
|
TransactionType (nvarchar(20))
|
|
Name (nvarchar(200), NOT NULL)
|
|
Memo (nvarchar(500))
|
|
Amount (decimal(18,2), NOT NULL)
|
|
Category (nvarchar(100))
|
|
MerchantId (int, FK → Merchants.Id, SET NULL)
|
|
Notes (nvarchar(max))
|
|
AccountId (int, FK → Accounts.Id, RESTRICT)
|
|
CardId (int, FK → Cards.Id, RESTRICT)
|
|
TransferToAccountId (int, FK → Accounts.Id, RESTRICT)
|
|
Last4 (nvarchar(4))
|
|
UNIQUE INDEX: (Date, Amount, Name, Memo, AccountId, CardId)
|
|
INDEX: Date, Amount, Category
|
|
```
|
|
|
|
### Receipts Table
|
|
```sql
|
|
Id (bigint, PK)
|
|
TransactionId (bigint, FK → Transactions.Id, CASCADE, NULLABLE)
|
|
FileName (nvarchar(260), NOT NULL)
|
|
ContentType (nvarchar(100), DEFAULT 'application/octet-stream')
|
|
StoragePath (nvarchar(1024), NOT NULL)
|
|
FileSizeBytes (bigint, NOT NULL)
|
|
FileHashSha256 (nvarchar(64), NOT NULL)
|
|
UploadedAtUtc (datetime2, NOT NULL)
|
|
Merchant (nvarchar(200))
|
|
ReceiptDate (datetime2)
|
|
DueDate (datetime2) -- For bills: payment due date
|
|
Subtotal (decimal(18,2))
|
|
Tax (decimal(18,2))
|
|
Total (decimal(18,2))
|
|
Currency (nvarchar(8))
|
|
UNIQUE INDEX: (TransactionId, FileHashSha256) WHERE TransactionId IS NOT NULL
|
|
```
|
|
|
|
### ReceiptParseLogs Table
|
|
```sql
|
|
Id (bigint, PK)
|
|
ReceiptId (bigint, FK → Receipts.Id, CASCADE)
|
|
Provider (nvarchar(50), NOT NULL)
|
|
Model (nvarchar(100), NOT NULL)
|
|
StartedAtUtc (datetime2, NOT NULL)
|
|
CompletedAtUtc (datetime2)
|
|
Success (bit, NOT NULL)
|
|
Confidence (decimal(18,2))
|
|
Error (nvarchar(max))
|
|
ProviderJobId (nvarchar(100))
|
|
ExtractedTextPath (nvarchar(1024))
|
|
RawProviderPayloadJson (nvarchar(max))
|
|
```
|
|
|
|
### ReceiptLineItems Table
|
|
```sql
|
|
Id (bigint, PK)
|
|
ReceiptId (bigint, FK → Receipts.Id, CASCADE)
|
|
LineNumber (int, NOT NULL)
|
|
Description (nvarchar(300), NOT NULL)
|
|
Quantity (decimal(18,4))
|
|
UnitPrice (decimal(18,4))
|
|
LineTotal (decimal(18,2), NOT NULL)
|
|
Unit (nvarchar(16))
|
|
Sku (nvarchar(64))
|
|
Category (nvarchar(100))
|
|
```
|
|
|
|
### CategoryMappings Table
|
|
```sql
|
|
Id (int, PK)
|
|
Category (nvarchar(max), NOT NULL)
|
|
Pattern (nvarchar(max), NOT NULL)
|
|
MerchantId (int, FK → Merchants.Id, SET NULL)
|
|
Priority (int, NOT NULL, DEFAULT 0)
|
|
Confidence (decimal(18,2), NULL) -- AI confidence score
|
|
CreatedBy (nvarchar(max), NULL) -- "AI" or "User"
|
|
CreatedAt (datetime2, NULL) -- Rule creation timestamp
|
|
```
|
|
|
|
### Budgets Table
|
|
```sql
|
|
Id (int, PK)
|
|
Category (nvarchar(100), NULL) -- NULL = total spending budget
|
|
Amount (decimal(18,2), NOT NULL)
|
|
Period (int, NOT NULL) -- 0=Weekly, 1=Monthly, 2=Yearly
|
|
StartDate (datetime2, NOT NULL)
|
|
IsActive (bit, NOT NULL)
|
|
Notes (nvarchar(500), NULL)
|
|
UNIQUE INDEX: (Category, Period) WHERE IsActive = 1
|
|
```
|
|
|
|
## Key Workflows
|
|
|
|
### 1. Import Transactions from CSV
|
|
|
|
```
|
|
User uploads CSV file
|
|
↓
|
|
UploadModel.OnPostAsync()
|
|
↓
|
|
TransactionImporter.ImportAsync()
|
|
↓
|
|
For each CSV row:
|
|
CardResolver.ResolveCardAsync() → Get/Create Card
|
|
Check for duplicates (DB + batch)
|
|
Add to batch if unique
|
|
↓
|
|
DbContext.SaveChangesAsync()
|
|
↓
|
|
Display import stats (inserted, skipped)
|
|
```
|
|
|
|
### 2. Auto-Categorize Transaction
|
|
|
|
```
|
|
Transaction inserted with Name="KROGER #123"
|
|
↓
|
|
TransactionCategorizer.CategorizeAsync("KROGER #123")
|
|
↓
|
|
Load CategoryMappings (ordered by Priority DESC)
|
|
↓
|
|
Check special case: Gas station with small purchase?
|
|
↓
|
|
Iterate mappings:
|
|
If "KROGER".Contains(pattern.ToUpper()) → Match!
|
|
Return "Brick/mortar store"
|
|
If mapping has MerchantId → Assign to transaction
|
|
↓
|
|
Update Transaction.Category and Transaction.MerchantId
|
|
```
|
|
|
|
### 3. Upload and Parse Receipt (Traditional Flow - From Transaction)
|
|
|
|
```
|
|
User attaches receipt to transaction
|
|
↓
|
|
EditTransactionModel.OnPostUploadReceiptAsync()
|
|
↓
|
|
ReceiptManager.UploadReceiptAsync()
|
|
- Validate file (size, extension)
|
|
- Compute SHA256 hash
|
|
- Check for duplicate (TransactionId + Hash)
|
|
- Save to wwwroot/receipts/
|
|
- Create Receipt entity
|
|
↓
|
|
User clicks "Parse Receipt"
|
|
↓
|
|
ViewReceiptModel.OnPostParseAsync()
|
|
↓
|
|
OpenAIReceiptParser.ParseReceiptAsync()
|
|
- Load receipt file
|
|
- If PDF: Convert to PNG with ImageMagick
|
|
- Encode as base64
|
|
- Call OpenAI Vision API with structured prompt
|
|
- Parse JSON response
|
|
- Update Receipt (merchant, date, due date, amounts)
|
|
- Replace ReceiptLineItems
|
|
- Create ReceiptParseLog
|
|
↓
|
|
Display parsed data
|
|
```
|
|
|
|
### 3a. Upload, Parse, and Auto-Map Receipt (New Unmapped Flow)
|
|
|
|
```
|
|
User visits /Receipts page
|
|
↓
|
|
User uploads receipt without transaction
|
|
↓
|
|
ReceiptsModel.OnPostUploadAsync()
|
|
↓
|
|
ReceiptManager.UploadUnmappedReceiptAsync()
|
|
- Validate file (size, extension)
|
|
- Compute SHA256 hash
|
|
- Check for duplicates (hash, filename+size)
|
|
↓
|
|
IF duplicates found:
|
|
- Delete uploaded file temporarily
|
|
- Store duplicate warnings in TempData
|
|
- Redirect to GET with modal
|
|
↓
|
|
User sees blocking modal:
|
|
Option A: Cancel (don't upload)
|
|
Option B: Upload Anyway (re-upload with confirmation)
|
|
↓
|
|
IF no duplicates OR confirmed:
|
|
- Save to wwwroot/receipts/
|
|
- Create Receipt entity (TransactionId = null)
|
|
↓
|
|
User clicks "Parse Receipt"
|
|
↓
|
|
OpenAIReceiptParser.ParseReceiptAsync()
|
|
- Parse receipt (merchant, date, due date, total, line items)
|
|
- Update Receipt entity
|
|
- Trigger ReceiptAutoMapper.AutoMapReceiptAsync()
|
|
↓
|
|
ReceiptAutoMapper.AutoMapReceiptAsync()
|
|
- Find matching transactions:
|
|
* Date filter (bill date to due date+5 OR receipt date ±3)
|
|
* Merchant filter (contains, case-insensitive)
|
|
* Amount filter (±$0.10 tolerance)
|
|
* Exclude transactions with existing receipts
|
|
↓
|
|
IF single match found:
|
|
- Auto-map receipt to transaction
|
|
- Return Success
|
|
ELSE IF multiple matches:
|
|
- Return MultipleMatches (requires manual selection)
|
|
ELSE:
|
|
- Return NoMatch
|
|
↓
|
|
IF auto-mapping failed (no match or multiple matches):
|
|
User clicks "Map" button on receipt row
|
|
↓
|
|
ReceiptsModel.FindMatchingTransactionsForReceipt()
|
|
- Apply same filtering logic
|
|
- Sort by merchant relevance (exact > contains > none)
|
|
- Return up to 50 matches
|
|
↓
|
|
Modal displays transactions in table:
|
|
- Green highlighting for amount matches
|
|
- Auto-scroll to first amount match
|
|
- Radio button selection
|
|
- Manual ID entry option
|
|
↓
|
|
User selects transaction
|
|
↓
|
|
ReceiptsModel.OnPostMapToTransactionAsync()
|
|
- Links receipt to transaction
|
|
- Redirect to receipts page
|
|
```
|
|
|
|
### 4. Dashboard Aggregation
|
|
|
|
```
|
|
User visits homepage (/)
|
|
↓
|
|
IndexModel.OnGet()
|
|
↓
|
|
DashboardService.GetDashboardDataAsync()
|
|
↓
|
|
Parallel data fetching:
|
|
- DashboardStatsCalculator → Total txns, credits, debits, uncategorized, receipts, cards
|
|
- TopCategoriesProvider → Top 8 categories by spend (last 90 days) with average per transaction
|
|
- RecentTransactionsProvider → Latest 20 transactions
|
|
↓
|
|
Return DashboardData DTO
|
|
↓
|
|
Render dashboard view
|
|
```
|
|
|
|
### 5. AI-Powered Categorization (Phase 1 - Manual Review)
|
|
|
|
```
|
|
User visits /ReviewAISuggestions
|
|
↓
|
|
ReviewAISuggestionsModel.OnGetAsync()
|
|
- Loads up to 50 recent uncategorized transactions
|
|
↓
|
|
User clicks "Generate AI Suggestions"
|
|
↓
|
|
ReviewAISuggestionsModel.OnPostGenerateSuggestionsAsync()
|
|
- Fetches up to 20 uncategorized transactions
|
|
- Calls TransactionAICategorizer.ProposeBatchCategorizationAsync()
|
|
↓
|
|
For each transaction (batches of 5):
|
|
TransactionAICategorizer.ProposeCategorizationAsync()
|
|
- Builds prompt with transaction details
|
|
- Calls OpenAI GPT-4o-mini API
|
|
- Parses JSON response
|
|
- Returns AICategoryProposal
|
|
↓
|
|
Store proposals in session
|
|
Redirect to /ReviewAISuggestionsWithProposals
|
|
↓
|
|
User reviews proposals with confidence scores
|
|
↓
|
|
User actions:
|
|
Option A: Apply + Create Rule
|
|
- Updates transaction category and merchant
|
|
- Creates CategoryMapping rule (CreatedBy="AI")
|
|
- Future similar transactions auto-categorized
|
|
Option B: Apply (No Rule)
|
|
- Updates transaction only
|
|
- No rule created
|
|
Option C: Reject
|
|
- Removes proposal from session
|
|
Option D: Edit Manually
|
|
- Redirects to EditTransaction page
|
|
↓
|
|
Proposal applied
|
|
Remove from session
|
|
Display success message
|
|
```
|
|
|
|
## Design Patterns
|
|
|
|
### 1. Service Layer Pattern
|
|
All business logic isolated in service interfaces/implementations:
|
|
- Testable (mock services in unit tests)
|
|
- Reusable across pages
|
|
- Single Responsibility Principle
|
|
|
|
### 2. Repository Pattern (via EF Core DbContext)
|
|
- MoneyMapContext encapsulates data access
|
|
- LINQ queries in service layer
|
|
- Change tracking and unit of work handled by EF Core
|
|
|
|
### 3. Result Pattern
|
|
Services return result objects instead of throwing exceptions:
|
|
- `ImportOperationResult` (success/failure with data or error message)
|
|
- `CardResolutionResult`
|
|
- `ReceiptUploadResult`
|
|
- `ReceiptParseResult`
|
|
|
|
Benefits: Explicit error handling, no try-catch clutter, clear success/failure paths
|
|
|
|
### 4. Dependency Injection
|
|
All services registered in Program.cs:
|
|
- Scoped lifetime for per-request services
|
|
- HttpClient factory for IReceiptParser
|
|
- Testability via interface injection
|
|
|
|
### 5. Data Transfer Objects (DTOs)
|
|
Separate DTOs for data transfer between layers:
|
|
- `DashboardData` (aggregates stats + categories + recent transactions)
|
|
- `ImportContext` (encapsulates import parameters)
|
|
- `TransactionKey` (value object for deduplication)
|
|
- Page model record types (TopCategoryRow, RecentTxnRow, etc.)
|
|
|
|
### 6. Strategy Pattern
|
|
CardResolver uses strategy based on CardSelectMode:
|
|
- Auto strategy: Extract from memo/filename
|
|
- Manual strategy: Use user selection
|
|
|
|
## Security Considerations
|
|
|
|
### 1. File Upload Security
|
|
- File size limit: 10MB
|
|
- Extension whitelist: jpg, jpeg, png, pdf, gif, heic
|
|
- Filename sanitization: Remove non-ASCII characters (®, ™, ©)
|
|
- SHA256 hash validation for deduplication
|
|
- Files stored outside database (wwwroot/receipts/)
|
|
|
|
### 2. SQL Injection Prevention
|
|
- Parameterized queries via EF Core
|
|
- LINQ expressions compile to safe SQL
|
|
|
|
### 3. API Key Management
|
|
- OpenAI API key from environment variable (preferred) or config
|
|
- Not hardcoded in source
|
|
|
|
### 4. Delete Constraints
|
|
- Restrict delete on Cards (can't delete if transactions exist)
|
|
- Restrict delete on Accounts (can't delete if transactions exist)
|
|
- Cascade delete on Receipts (deleting transaction removes receipts)
|
|
|
|
## Performance Optimizations
|
|
|
|
### 1. Database Indexes
|
|
- Transactions: Date, Amount, Category (for filtering/sorting)
|
|
- Unique indexes on duplicate detection columns
|
|
- Composite index on Cards (Issuer, Last4, Owner)
|
|
|
|
### 2. Batch Processing
|
|
- CSV import: Bulk insert with single SaveChangesAsync()
|
|
- In-memory duplicate checking (HashSet) avoids N+1 queries
|
|
|
|
### 3. Pagination
|
|
- Transactions list uses pagination to limit result sets
|
|
- Dashboard limits top categories (8) and recent transactions (20)
|
|
|
|
### 4. AsNoTracking
|
|
- Read-only queries use `.AsNoTracking()` to reduce EF overhead
|
|
- TopCategoriesProvider, RecentTransactionsProvider
|
|
|
|
### 5. PDF Conversion
|
|
- ImageMagick runs on ThreadPool (Task.Run) to avoid blocking
|
|
- First page only for multi-page PDFs
|
|
|
|
## Testing Considerations
|
|
|
|
### Unit Testing Strategy
|
|
All service interfaces are mockable:
|
|
```csharp
|
|
// Example: Test TransactionImporter
|
|
var mockCardResolver = new Mock<ICardResolver>();
|
|
var mockDb = new Mock<MoneyMapContext>();
|
|
var importer = new TransactionImporter(mockDb.Object, mockCardResolver.Object);
|
|
```
|
|
|
|
### Integration Testing
|
|
- In-memory database for DbContext tests
|
|
- Test CSV import end-to-end
|
|
- Validate categorization rules
|
|
|
|
### External Dependencies
|
|
- OpenAI API: Mock IReceiptParser for tests
|
|
- File system: Mock IWebHostEnvironment
|
|
|
|
## Future Enhancements
|
|
|
|
### Potential Features
|
|
1. **Multi-user support**: Add authentication/authorization
|
|
2. **Recurring transactions**: Auto-detect and predict recurring expenses
|
|
3. **Data export**: Export transactions to Excel/CSV
|
|
4. **Charts/graphs**: Spending trends over time
|
|
5. **Mobile app**: React Native or .NET MAUI
|
|
6. **Bank API integration**: Direct import from bank APIs (Plaid, Yodlee)
|
|
7. **Receipt search**: Full-text search on parsed line items
|
|
8. **Split transactions**: Divide single transaction across categories
|
|
9. **Tags**: Tag transactions with multiple labels
|
|
|
|
### Technical Debt
|
|
1. Move inline service implementations to separate files (Upload.cshtml.cs is 333 lines)
|
|
2. Add logging (ILogger) to all services
|
|
3. Add retry logic for OpenAI API calls (Polly)
|
|
4. Implement background job processing for receipt parsing (Hangfire)
|
|
5. Add integration tests for critical workflows
|
|
6. Implement caching for CategoryMappings (IMemoryCache)
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
**1. Duplicate Transaction Errors**
|
|
- **Cause**: Unique constraint violation (Date + Amount + Name + Memo + AccountId + CardId)
|
|
- **Solution**: Transactions are skipped automatically during import
|
|
|
|
**2. Receipt Parsing Failures**
|
|
- **Cause**: OpenAI API key missing or invalid
|
|
- **Solution**: Set `OPENAI_API_KEY` environment variable or `OpenAI:ApiKey` in appsettings.json
|
|
- **Cause**: PDF conversion error
|
|
- **Solution**: Ensure Magick.NET is properly installed
|
|
|
|
**3. Card Not Found**
|
|
- **Cause**: Auto mode can't extract last 4 digits from memo or filename
|
|
- **Solution**: Use Manual mode and select card from dropdown, or rename CSV file to include card digits (e.g., `transactions-1234.csv`)
|
|
|
|
**4. Slow Dashboard Load**
|
|
- **Cause**: Large transaction dataset without indexes
|
|
- **Solution**: Ensure migrations have run (indexes on Date, Amount, Category)
|
|
|
|
**5. File Upload Fails**
|
|
- **Cause**: File exceeds 10MB or unsupported format
|
|
- **Solution**: Resize/compress image, or use supported format
|
|
|
|
## Deployment
|
|
|
|
### Database Migration
|
|
```bash
|
|
# Create migration
|
|
dotnet ef migrations add MigrationName
|
|
|
|
# Update database
|
|
dotnet ef database update
|
|
```
|
|
|
|
### Production Configuration
|
|
1. Update connection string in appsettings.json (Azure SQL, AWS RDS, etc.)
|
|
2. Set `OPENAI_API_KEY` environment variable
|
|
3. Configure `Receipts:StoragePath` (consider Azure Blob Storage)
|
|
4. Enable HTTPS and HSTS
|
|
5. Set `ASPNETCORE_ENVIRONMENT=Production`
|
|
|
|
### File Storage Considerations
|
|
- **Local**: wwwroot/receipts (current implementation)
|
|
- **Production**: Azure Blob Storage, AWS S3, or network share
|
|
- Modify ReceiptManager to use cloud storage SDK
|
|
- Update StoragePath format (blob URL instead of relative path)
|
|
|
|
## Conclusion
|
|
|
|
MoneyMap demonstrates a well-architected ASP.NET Core application with clear separation of concerns, testable services, and modern AI integration. The service layer pattern allows for easy maintenance and extension, while the Result pattern provides explicit error handling. The OpenAI receipt parsing feature showcases practical LLM integration for automating tedious data entry tasks.
|
|
|
|
---
|
|
|
|
**Last Updated:** 2025-12-15
|
|
**Version:** 1.4
|
|
**Framework:** ASP.NET Core 8.0 / EF Core 9.0
|
|
|
|
## Recent Changes (v1.4)
|
|
|
|
### Financial Audit API
|
|
- **New Endpoint**: `GET /api/audit` - Comprehensive financial data for AI analysis
|
|
- **FinancialAuditService**: New service orchestrating financial data aggregation
|
|
- **Audit Response**: Summary stats, budgets, categories, merchants, trends, accounts
|
|
- **AI Flags**: Pre-computed observations (over-budget, spending increases, uncategorized)
|
|
- **Configurable Period**: Query parameters for date range and transaction inclusion
|
|
|
|
### Technical Details
|
|
- Implemented using Minimal API pattern (no controllers needed)
|
|
- DTOs in `Models/Api/FinancialAuditModels.cs`
|
|
- Excludes transfers from spending calculations
|
|
- Correlates category spending with budget limits
|
|
|
|
## Recent Changes (v1.3)
|
|
|
|
### Budget Tracking Feature
|
|
- **Budget Model**: New `Budget` entity with support for Weekly, Monthly, and Yearly periods
|
|
- **Category & Total Budgets**: Create budgets for specific categories or track total spending
|
|
- **Period Boundaries**: Smart calculation of period start/end dates based on budget start date
|
|
- **Budget Service**: Full CRUD operations plus spending calculations via `IBudgetService`
|
|
- **Budgets Page**: New `/Budgets` page for managing budgets with progress bars
|
|
- **Dashboard Integration**: Budget status displayed on dashboard with color-coded progress
|
|
- **Pause/Resume**: Deactivate budgets without deleting them
|
|
|
|
### Technical Details
|
|
- Added `Budgets` table with filtered unique index on (Category, Period) WHERE IsActive = 1
|
|
- `BudgetPeriod` enum: Weekly (0), Monthly (1), Yearly (2)
|
|
- Period boundary calculations handle edge cases (month boundaries, leap years)
|
|
- Spending calculated by summing debits within period, excluding transfers
|
|
|
|
## Recent Changes (v1.2)
|
|
|
|
### Receipt Management Enhancements
|
|
- **Unmapped Receipts**: Receipts can now be uploaded without initial transaction mapping
|
|
- **Duplicate Detection**: SHA256 hash and filename+size matching with blocking modal confirmation
|
|
- **Auto-Mapping**: Smart automatic receipt-to-transaction matching using date, merchant, and amount
|
|
- **Due Date Support**: Bills (utility, credit card) can have due dates for expanded matching windows
|
|
- **Manual Mapping UI**: Enhanced modal with transaction table, relevance sorting, amount highlighting, and auto-scroll
|
|
- **Date Range Intelligence**:
|
|
- Bills: bill date to due date + 5 days (accounts for auto-pay processing)
|
|
- Regular receipts: receipt date ± 3 days
|
|
|
|
### UI Improvements
|
|
- **Transactions Page**: Added search, quick date filters, transaction ID column, Upload CSV button in header
|
|
- **Navigation**: Moved Upload CSV from menu to Transactions page header
|
|
- **Receipts Page**: New dedicated page for receipt management with mapping workflow
|
|
- **Transaction Selector**: Scrollable table with green amount match highlighting and auto-scroll to best match
|
|
|
|
### Technical Improvements
|
|
- Made `Receipt.TransactionId` nullable with filtered unique index
|
|
- Added `Receipt.DueDate` column for bill payment tracking
|
|
- Enhanced OpenAI prompt to extract due dates from bills
|
|
- Implemented `ReceiptAutoMapper` service with intelligent matching algorithm
|
|
- Updated `ReceiptManager` with unmapped receipt support and duplicate detection
|
|
- Added `MerchantService` for merchant management
|