Refactor: Simplify receipt storage path handling

Store only filename in database instead of full relative path.
GetReceiptPhysicalPath now combines config base path with filename.
AIReceiptParser uses ReceiptManager for consistent path resolution.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-11 16:54:27 -05:00
parent de5ee33a77
commit 541bbf01af
2 changed files with 21 additions and 11 deletions

View File

@@ -13,10 +13,12 @@ namespace MoneyMap.Services
public class AIReceiptParser : IReceiptParser public class AIReceiptParser : IReceiptParser
{ {
private readonly MoneyMapContext _db; private readonly MoneyMapContext _db;
private readonly IWebHostEnvironment _environment; private readonly IReceiptManager _receiptManager;
private readonly IPdfToImageConverter _pdfConverter; private readonly IPdfToImageConverter _pdfConverter;
private readonly OpenAIVisionClient _openAIClient; private readonly OpenAIVisionClient _openAIClient;
private readonly ClaudeVisionClient _claudeClient; private readonly ClaudeVisionClient _claudeClient;
private readonly OllamaVisionClient _ollamaClient;
private readonly LlamaCppVisionClient _llamaCppClient;
private readonly IMerchantService _merchantService; private readonly IMerchantService _merchantService;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly ILogger<AIReceiptParser> _logger; private readonly ILogger<AIReceiptParser> _logger;
@@ -24,19 +26,23 @@ namespace MoneyMap.Services
public AIReceiptParser( public AIReceiptParser(
MoneyMapContext db, MoneyMapContext db,
IWebHostEnvironment environment, IReceiptManager receiptManager,
IPdfToImageConverter pdfConverter, IPdfToImageConverter pdfConverter,
OpenAIVisionClient openAIClient, OpenAIVisionClient openAIClient,
ClaudeVisionClient claudeClient, ClaudeVisionClient claudeClient,
OllamaVisionClient ollamaClient,
LlamaCppVisionClient llamaCppClient,
IMerchantService merchantService, IMerchantService merchantService,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
ILogger<AIReceiptParser> logger) ILogger<AIReceiptParser> logger)
{ {
_db = db; _db = db;
_environment = environment; _receiptManager = receiptManager;
_pdfConverter = pdfConverter; _pdfConverter = pdfConverter;
_openAIClient = openAIClient; _openAIClient = openAIClient;
_claudeClient = claudeClient; _claudeClient = claudeClient;
_ollamaClient = ollamaClient;
_llamaCppClient = llamaCppClient;
_merchantService = merchantService; _merchantService = merchantService;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_logger = logger; _logger = logger;
@@ -52,10 +58,12 @@ namespace MoneyMap.Services
return ReceiptParseResult.Failure("Receipt not found."); return ReceiptParseResult.Failure("Receipt not found.");
var selectedModel = model ?? "gpt-4o-mini"; var selectedModel = model ?? "gpt-4o-mini";
var isLlamaCpp = selectedModel.StartsWith("llamacpp:");
var isOllama = selectedModel.StartsWith("ollama:");
var isClaude = selectedModel.StartsWith("claude-"); var isClaude = selectedModel.StartsWith("claude-");
var provider = isClaude ? "Anthropic" : "OpenAI"; var provider = isLlamaCpp ? "LlamaCpp" : (isOllama ? "Ollama" : (isClaude ? "Anthropic" : "OpenAI"));
var filePath = Path.Combine(_environment.WebRootPath, receipt.StoragePath.Replace("/", Path.DirectorySeparatorChar.ToString())); var filePath = _receiptManager.GetReceiptPhysicalPath(receipt);
if (!File.Exists(filePath)) if (!File.Exists(filePath))
return ReceiptParseResult.Failure("Receipt file not found on disk."); return ReceiptParseResult.Failure("Receipt file not found on disk.");
@@ -96,7 +104,10 @@ namespace MoneyMap.Services
promptText += "\n\nRespond ONLY with valid JSON, no other text."; promptText += "\n\nRespond ONLY with valid JSON, no other text.";
// Call appropriate vision API // Call appropriate vision API
var client = isClaude ? (IAIVisionClient)_claudeClient : _openAIClient; IAIVisionClient client = isLlamaCpp ? _llamaCppClient
: isOllama ? _ollamaClient
: isClaude ? _claudeClient
: _openAIClient;
var visionResult = await client.AnalyzeImageAsync(base64Data, mediaType, promptText, selectedModel); var visionResult = await client.AnalyzeImageAsync(base64Data, mediaType, promptText, selectedModel);
if (!visionResult.IsSuccess) if (!visionResult.IsSuccess)

View File

@@ -132,9 +132,8 @@ namespace MoneyMap.Services
await file.CopyToAsync(fileStream); await file.CopyToAsync(fileStream);
} }
// Store relative path in database // Store just the filename in database (base path comes from config)
var relativePath = _configuration["Receipts:StoragePath"] ?? "receipts"; var relativeStoragePath = storedFileName;
var relativeStoragePath = $"{relativePath}/{storedFileName}";
// Create receipt record // Create receipt record
var receipt = new Receipt var receipt = new Receipt
@@ -327,8 +326,8 @@ namespace MoneyMap.Services
public string GetReceiptPhysicalPath(Receipt receipt) public string GetReceiptPhysicalPath(Receipt receipt)
{ {
// StoragePath is like "receipts/filename.jpg" // StoragePath is just the filename, combine with configured base path
return Path.Combine(_environment.WebRootPath, receipt.StoragePath.Replace("/", Path.DirectorySeparatorChar.ToString())); return Path.Combine(GetReceiptsBasePath(), receipt.StoragePath);
} }
public async Task<Receipt?> GetReceiptAsync(long receiptId) public async Task<Receipt?> GetReceiptAsync(long receiptId)