Private
Public Access
1
0

Add AI categorization with Ollama: settings, auto-classify on sync/import, and manual classify from Transactions

Adds local LLM-based transaction categorization using Ollama:
- Settings UI to configure Ollama URL, model, confidence threshold
- Auto-categorization during bank sync and file import
- Manual "Classify Uncategorized" button on Transactions screen that respects active filters
- Payee default category learning loop for classified transactions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-08 18:41:12 -05:00
parent 57d1051213
commit 1c7abb7ffc
18 changed files with 2423 additions and 3 deletions
@@ -0,0 +1,49 @@
using Purrse.Core.Enums;
namespace Purrse.Core.DTOs;
// Settings
public record AiCategorizationSettingsResponse(
bool IsEnabled,
string OllamaUrl,
string ModelName,
double ConfidenceThreshold,
int TimeoutSeconds,
bool UpdatePayeeDefaults);
public record UpdateAiCategorizationSettingsRequest(
bool IsEnabled,
string OllamaUrl,
string ModelName,
double ConfidenceThreshold,
int TimeoutSeconds,
bool UpdatePayeeDefaults);
// Connection test
public record OllamaConnectionTestResponse(
bool Success,
string? ErrorMessage,
List<string>? AvailableModels);
// Ollama API communication (internal)
public record OllamaChatRequest(string Model, List<OllamaChatMessage> Messages, bool Stream, string Format);
public record OllamaChatMessage(string Role, string Content);
public record OllamaChatResponse(OllamaChatMessage? Message, bool Done);
public record OllamaTagsResponse(List<OllamaModelInfo>? Models);
public record OllamaModelInfo(string Name, string? Model);
// Classify uncategorized (manual trigger from Transactions screen)
public record ClassifyUncategorizedRequest(
Guid? AccountId,
DateTime? StartDate,
DateTime? EndDate,
string? SearchText,
TransactionStatus? Status);
public record ClassifyUncategorizedResponse(
int TotalUncategorized,
int Classified,
int Skipped);
// Classification result
public record TransactionClassification(int TransactionIndex, Guid? CategoryId, string? CategoryName, double Confidence);
@@ -0,0 +1,13 @@
using Purrse.Core.DTOs;
using Purrse.Core.Models;
namespace Purrse.Core.Interfaces.Services;
public interface IAiCategorizationService
{
Task<AiCategorizationSettingsResponse> GetSettingsAsync(Guid userId);
Task<AiCategorizationSettingsResponse> UpdateSettingsAsync(Guid userId, UpdateAiCategorizationSettingsRequest request);
Task<OllamaConnectionTestResponse> TestConnectionAsync(Guid userId);
Task<List<TransactionClassification>> ClassifyTransactionsAsync(Guid userId, List<Transaction> uncategorizedTransactions);
Task<ClassifyUncategorizedResponse> ClassifyUncategorizedAsync(Guid userId, ClassifyUncategorizedRequest request);
}
@@ -0,0 +1,16 @@
namespace Purrse.Core.Models;
public class AiCategorizationSettings
{
public Guid Id { get; set; }
public Guid UserId { get; set; }
public bool IsEnabled { get; set; }
public string OllamaUrl { get; set; } = "http://localhost:11434";
public string ModelName { get; set; } = "llama3.1:8b";
public double ConfidenceThreshold { get; set; } = 0.7;
public int TimeoutSeconds { get; set; } = 30;
public bool UpdatePayeeDefaults { get; set; } = true;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
public User User { get; set; } = null!;
}
+1
View File
@@ -14,4 +14,5 @@ public class User
public ICollection<Account> Accounts { get; set; } = new List<Account>();
public ICollection<SyncConnection> SyncConnections { get; set; } = new List<SyncConnection>();
public BankSyncSettings? BankSyncSettings { get; set; }
public AiCategorizationSettings? AiCategorizationSettings { get; set; }
}