Initial commit

This commit is contained in:
aj
2025-10-04 02:13:50 -04:00
commit d579029492
288 changed files with 100048 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using System.Transactions;
namespace MoneyMap.Models;
public class Card
{
[Key]
public int Id { get; set; }
[MaxLength(100)]
public string Issuer { get; set; } = string.Empty; // e.g., VISA, MC
[MaxLength(4)]
public string Last4 { get; set; } = string.Empty; // "1234"
[MaxLength(100)]
public string Owner { get; set; } = string.Empty; // optional
public ICollection<Transaction> Transactions { get; set; } = new List<Transaction>();
}
@@ -0,0 +1,11 @@
namespace MoneyMap.Models.Import;
public class TransactionCsvRow
{
public DateTime Date { get; set; }
public string Transaction { get; set; } = "";
public string Name { get; set; } = "";
public string Memo { get; set; } = "";
public decimal Amount { get; set; }
public string Category { get; set; } = "";
}
@@ -0,0 +1,32 @@
using CsvHelper.Configuration;
namespace MoneyMap.Models.Import;
public sealed class TransactionCsvRowMap : ClassMap<TransactionCsvRow>
{
public TransactionCsvRowMap(bool hasCategory)
{
Map(m => m.Date).Name("Date");
Map(m => m.Transaction).Name("Transaction");
Map(m => m.Name).Name("Name");
Map(m => m.Memo).Name("Memo");
Map(m => m.Amount).Name("Amount");
if (hasCategory)
{
Map(m => m.Category).Name("Category");
}
else
{
if (hasCategory)
{
Map(m => m.Category).Name("Category");
}
else
{
Map(m => m.Category).Constant(string.Empty).Optional();
}
}
}
}
+55
View File
@@ -0,0 +1,55 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MoneyMap.Models;
[Index(nameof(TransactionId), nameof(FileHashSha256), IsUnique = true)]
public class Receipt
{
[Key]
public long Id { get; set; }
// Link to transaction
public long TransactionId { get; set; }
public Transaction Transaction { get; set; } = null!;
// File metadata
[MaxLength(260)]
public string FileName { get; set; } = string.Empty;
[MaxLength(100)]
public string ContentType { get; set; } = "application/octet-stream";
[MaxLength(1024)]
public string StoragePath { get; set; } = string.Empty; // \\barge.lan\receipts\...\ or blob key
public long FileSizeBytes { get; set; }
[MaxLength(64)]
public string FileHashSha256 { get; set; } = string.Empty; // for dedupe
public DateTime UploadedAtUtc { get; set; } = DateTime.UtcNow;
// Parsed header fields (optional, set by parser job)
[MaxLength(200)]
public string? Merchant { get; set; }
public DateTime? ReceiptDate { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal? Subtotal { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal? Tax { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal? Total { get; set; }
[MaxLength(8)]
public string? Currency { get; set; }
// One receipt -> many parse attempts + many line items
public ICollection<ReceiptParseLog> ParseLogs { get; set; } = new List<ReceiptParseLog>();
public ICollection<ReceiptLineItem> LineItems { get; set; } = new List<ReceiptLineItem>();
}
+41
View File
@@ -0,0 +1,41 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MoneyMap.Models;
[Index(nameof(ReceiptId), nameof(LineNumber))]
public class ReceiptLineItem
{
[Key]
public long Id { get; set; }
public long ReceiptId { get; set; }
public Receipt Receipt { get; set; } = null!;
public int LineNumber { get; set; }
[MaxLength(300)]
public string Description { get; set; } = string.Empty;
// ReceiptLineItem
[Column(TypeName = "decimal(18,4)")]
public decimal? Quantity { get; set; } // was missing
[MaxLength(16)]
public string? Unit { get; set; } // ea, lb, gal, etc.
[Column(TypeName = "decimal(18,4)")]
public decimal? UnitPrice { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal? LineTotal { get; set; }
[MaxLength(64)]
public string? Sku { get; set; }
[MaxLength(100)]
public string? Category { get; set; } // optional per-line categorization
}
+43
View File
@@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MoneyMap.Models;
[Index(nameof(ReceiptId), nameof(StartedAtUtc))]
public class ReceiptParseLog
{
[Key]
public long Id { get; set; }
public long ReceiptId { get; set; }
public Receipt Receipt { get; set; } = null!;
// Provider metadata as strings for flexibility
[MaxLength(50)]
public string Provider { get; set; } = string.Empty; // e.g., "OpenAI", "Azure", "Google", "Tesseract"
[MaxLength(100)]
public string Model { get; set; } = string.Empty; // e.g., "gpt-4o-mini"
[MaxLength(100)]
public string? ProviderJobId { get; set; }
public DateTime StartedAtUtc { get; set; } = DateTime.UtcNow;
public DateTime? CompletedAtUtc { get; set; }
public bool Success { get; set; }
// ReceiptParseLog
[Column(TypeName = "decimal(5,4)")]
public decimal? Confidence { get; set; } // 0.00000.9999 is plenty
// Store full provider JSON payload for re-parsing/debug (keep out of hot paths)
public string RawProviderPayloadJson { get; set; } = "{}";
// Optional extracted text path if you persist a .txt alongside the image/PDF
[MaxLength(1024)]
public string? ExtractedTextPath { get; set; }
public string? Error { get; set; }
}
+46
View File
@@ -0,0 +1,46 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Transactions;
namespace MoneyMap.Models;
[Index(nameof(Date), nameof(Amount), nameof(Name), nameof(Memo), nameof(CardId), IsUnique = true)]
public class Transaction
{
[Key]
public long Id { get; set; }
[Required]
public DateTime Date { get; set; }
[MaxLength(20)]
public string TransactionType { get; set; } = string.Empty; // "DEBIT"/"CREDIT" if present
[MaxLength(200)]
public string Name { get; set; } = string.Empty;
[MaxLength(500)]
public string Memo { get; set; } = string.Empty;
[Column(TypeName = "decimal(18,2)")]
public decimal Amount { get; set; } // negative = debit, positive = credit
[MaxLength(100)]
public string Category { get; set; } = string.Empty;
public string Notes { get; set; } = string.Empty;
// Card link + convenience
[ForeignKey(nameof(Card))]
public int CardId { get; set; }
public Card? Card { get; set; }
[MaxLength(4)]
public string? CardLast4 { get; set; } // parsed from Memo if present
public ICollection<Receipt> Receipts { get; set; } = new List<Receipt>();
[NotMapped] public bool IsCredit => Amount > 0;
[NotMapped] public bool IsDebit => Amount < 0;
}