Add budget, loan, payee, and scheduled transaction tools to AI chat
Integrate 6 new tool chains into the chat service: get_budget, get_budget_summary, get_loan_details, calculate_loan_payoff, list_payees, and get_scheduled_transactions. Also raises the search_transactions page_size cap from 25 to 50 and the upcoming bills display from 5 to 10. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -158,6 +158,160 @@
|
||||
Deleted {{ data.type }} category "{{ data.name }}"
|
||||
</v-alert>
|
||||
</div>
|
||||
|
||||
<!-- Budget -->
|
||||
<div v-else-if="type === 'budget' && data?.items">
|
||||
<div class="text-subtitle-2 mb-1">
|
||||
Budget for {{ data.year }}-{{ String(data.month).padStart(2, '0') }}
|
||||
</div>
|
||||
<v-table density="compact" class="text-caption rounded-lg mb-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Category</th>
|
||||
<th class="text-right">Budgeted</th>
|
||||
<th class="text-right">Actual</th>
|
||||
<th class="text-right">Remaining</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in data.items" :key="item.id">
|
||||
<td>{{ item.categoryName }}</td>
|
||||
<td class="text-right">{{ formatCurrency(item.budgetedAmount) }}</td>
|
||||
<td class="text-right">{{ formatCurrency(item.actualAmount) }}</td>
|
||||
<td class="text-right" :class="item.budgetedAmount - item.actualAmount >= 0 ? 'text-success' : 'text-error'">
|
||||
{{ formatCurrency(item.budgetedAmount - item.actualAmount) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</div>
|
||||
|
||||
<!-- Budget Summary -->
|
||||
<div v-else-if="type === 'budget_summary'">
|
||||
<v-card variant="tonal" density="compact" class="mb-2 pa-3">
|
||||
<div class="text-subtitle-2 mb-2">
|
||||
Budget Summary — {{ data.year }}-{{ String(data.month).padStart(2, '0') }}
|
||||
</div>
|
||||
<div class="d-flex justify-space-between mb-1">
|
||||
<span class="text-caption text-medium-emphasis">Total Budgeted</span>
|
||||
<span class="text-caption">{{ formatCurrency(data.totalBudgeted) }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-space-between mb-1">
|
||||
<span class="text-caption text-medium-emphasis">Total Spent</span>
|
||||
<span class="text-caption">{{ formatCurrency(data.totalActual) }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-space-between mb-1">
|
||||
<span class="text-caption text-medium-emphasis">Remaining</span>
|
||||
<span class="text-caption" :class="data.remaining >= 0 ? 'text-success' : 'text-error'">
|
||||
{{ formatCurrency(data.remaining) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex justify-space-between">
|
||||
<span class="text-caption text-medium-emphasis">Over Budget</span>
|
||||
<span class="text-caption" :class="data.overBudgetCount > 0 ? 'text-error' : 'text-success'">
|
||||
{{ data.overBudgetCount }} categories
|
||||
</span>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<!-- Loan Details -->
|
||||
<div v-else-if="type === 'loan_details'">
|
||||
<v-card variant="tonal" density="compact" class="mb-2 pa-3">
|
||||
<div class="text-subtitle-2 mb-2">{{ data.accountName }}</div>
|
||||
<div class="d-flex justify-space-between mb-1">
|
||||
<span class="text-caption text-medium-emphasis">Original Balance</span>
|
||||
<span class="text-caption">{{ formatCurrency(data.originalBalance) }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-space-between mb-1">
|
||||
<span class="text-caption text-medium-emphasis">Current Balance</span>
|
||||
<span class="text-caption text-error">{{ formatCurrency(data.currentBalance) }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-space-between mb-1">
|
||||
<span class="text-caption text-medium-emphasis">Interest Rate</span>
|
||||
<span class="text-caption">{{ data.interestRate?.toFixed(2) }}%</span>
|
||||
</div>
|
||||
<div class="d-flex justify-space-between mb-1">
|
||||
<span class="text-caption text-medium-emphasis">Monthly Payment</span>
|
||||
<span class="text-caption">{{ formatCurrency(data.monthlyPayment) }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-space-between mb-1">
|
||||
<span class="text-caption text-medium-emphasis">Maturity Date</span>
|
||||
<span class="text-caption">{{ formatDate(data.maturityDate) }}</span>
|
||||
</div>
|
||||
<div v-if="data.escrowAmount" class="d-flex justify-space-between">
|
||||
<span class="text-caption text-medium-emphasis">Escrow</span>
|
||||
<span class="text-caption">{{ formatCurrency(data.escrowAmount) }}</span>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<!-- Loan Payoff Scenario -->
|
||||
<div v-else-if="type === 'loan_payoff'">
|
||||
<v-card variant="tonal" density="compact" class="mb-2 pa-3">
|
||||
<div class="text-subtitle-2 mb-2">Payoff Scenario</div>
|
||||
<div class="d-flex justify-space-between mb-1">
|
||||
<span class="text-caption text-medium-emphasis">Original Payoff</span>
|
||||
<span class="text-caption">{{ formatDate(data.originalPayoffDate) }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-space-between mb-1">
|
||||
<span class="text-caption text-medium-emphasis">New Payoff</span>
|
||||
<span class="text-caption text-success">{{ formatDate(data.newPayoffDate) }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-space-between mb-1">
|
||||
<span class="text-caption text-medium-emphasis">Interest Saved</span>
|
||||
<span class="text-caption text-success">{{ formatCurrency(data.interestSaved) }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-space-between">
|
||||
<span class="text-caption text-medium-emphasis">Months Saved</span>
|
||||
<span class="text-caption text-success">{{ data.monthsSaved }}</span>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<!-- Payees -->
|
||||
<div v-else-if="type === 'payees' && Array.isArray(data)">
|
||||
<v-table density="compact" class="text-caption rounded-lg mb-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Default Category</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="payee in data" :key="payee.id">
|
||||
<td>{{ payee.name }}</td>
|
||||
<td class="text-medium-emphasis">{{ payee.defaultCategoryName || '-' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</div>
|
||||
|
||||
<!-- Scheduled Transactions -->
|
||||
<div v-else-if="type === 'scheduled_transactions' && Array.isArray(data)">
|
||||
<v-table density="compact" class="text-caption rounded-lg mb-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Payee</th>
|
||||
<th class="text-right">Amount</th>
|
||||
<th>Frequency</th>
|
||||
<th>Next Due</th>
|
||||
<th>Account</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="txn in data" :key="txn.id">
|
||||
<td>{{ txn.payeeName || 'Unknown' }}</td>
|
||||
<td class="text-right text-no-wrap" :class="txn.amount >= 0 ? 'text-success' : 'text-error'">
|
||||
{{ formatCurrency(txn.amount) }}
|
||||
</td>
|
||||
<td>{{ txn.frequency }}</td>
|
||||
<td class="text-no-wrap">{{ formatDate(txn.nextDueDate) }}</td>
|
||||
<td>{{ txn.accountName }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -17,6 +17,10 @@ public class ChatService : IChatService
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly IReportService _reportService;
|
||||
private readonly IDashboardService _dashboardService;
|
||||
private readonly IBudgetService _budgetService;
|
||||
private readonly ILoanService _loanService;
|
||||
private readonly IPayeeService _payeeService;
|
||||
private readonly IScheduledTransactionService _scheduledTransactionService;
|
||||
private readonly ILogger<ChatService> _logger;
|
||||
|
||||
private static readonly JsonSerializerOptions OllamaJsonOptions = new()
|
||||
@@ -33,7 +37,7 @@ public class ChatService : IChatService
|
||||
private const string SystemPromptTemplate =
|
||||
"""
|
||||
You are a helpful personal finance assistant for Purrse.
|
||||
You can search transactions, view spending reports, check account balances, update transaction categories, and manage categories (list, create, delete).
|
||||
You can search transactions, view spending reports, check account balances, update transaction categories, manage categories (list, create, delete), view budgets and compare budget vs actuals, check loan details and payoff scenarios, list payees, and view scheduled/recurring transactions.
|
||||
When the user asks about their finances, use the available tools to look up real data.
|
||||
Always present financial amounts formatted as currency.
|
||||
When showing transactions, include the date, payee, amount, and category.
|
||||
@@ -48,6 +52,10 @@ public class ChatService : IChatService
|
||||
IAccountService accountService,
|
||||
IReportService reportService,
|
||||
IDashboardService dashboardService,
|
||||
IBudgetService budgetService,
|
||||
ILoanService loanService,
|
||||
IPayeeService payeeService,
|
||||
IScheduledTransactionService scheduledTransactionService,
|
||||
ILogger<ChatService> logger)
|
||||
{
|
||||
_db = db;
|
||||
@@ -57,6 +65,10 @@ public class ChatService : IChatService
|
||||
_accountService = accountService;
|
||||
_reportService = reportService;
|
||||
_dashboardService = dashboardService;
|
||||
_budgetService = budgetService;
|
||||
_loanService = loanService;
|
||||
_payeeService = payeeService;
|
||||
_scheduledTransactionService = scheduledTransactionService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -427,6 +439,12 @@ public class ChatService : IChatService
|
||||
"list_categories" => await ExecuteListCategoriesAsync(userId),
|
||||
"create_category" => await ExecuteCreateCategoryAsync(userId, args),
|
||||
"delete_category" => await ExecuteDeleteCategoryAsync(userId, args),
|
||||
"get_budget" => await ExecuteGetBudgetAsync(userId, args),
|
||||
"get_budget_summary" => await ExecuteGetBudgetSummaryAsync(userId, args),
|
||||
"get_loan_details" => await ExecuteGetLoanDetailsAsync(userId, args),
|
||||
"calculate_loan_payoff" => await ExecuteCalculateLoanPayoffAsync(userId, args),
|
||||
"list_payees" => await ExecuteListPayeesAsync(userId),
|
||||
"get_scheduled_transactions" => await ExecuteGetScheduledTransactionsAsync(userId),
|
||||
_ => ("error", new { error = $"Unknown tool: {name}" }, $"Error: Unknown tool '{name}'")
|
||||
};
|
||||
}
|
||||
@@ -459,7 +477,7 @@ public class ChatService : IChatService
|
||||
Status: null,
|
||||
SearchText: ParseStringArg(args, "search_text"),
|
||||
Page: 1,
|
||||
PageSize: Math.Min(ParseIntArg(args, "page_size") ?? 25, 25)
|
||||
PageSize: Math.Min(ParseIntArg(args, "page_size") ?? 25, 50)
|
||||
);
|
||||
|
||||
var result = await _transactionService.SearchAsync(userId, searchRequest);
|
||||
@@ -532,7 +550,7 @@ public class ChatService : IChatService
|
||||
if (dashboard.UpcomingBills.Count > 0)
|
||||
{
|
||||
summary.AppendLine("Upcoming bills:");
|
||||
foreach (var bill in dashboard.UpcomingBills.Take(5))
|
||||
foreach (var bill in dashboard.UpcomingBills.Take(10))
|
||||
{
|
||||
summary.AppendLine($"- {bill.PayeeName}: {bill.Amount:C} due {bill.DueDate:yyyy-MM-dd}");
|
||||
}
|
||||
@@ -657,6 +675,161 @@ public class ChatService : IChatService
|
||||
return ("category_deleted", result, $"Deleted category '{category.Name}'.");
|
||||
}
|
||||
|
||||
private async Task<(string, object, string)> ExecuteGetBudgetAsync(Guid userId, Dictionary<string, object> args)
|
||||
{
|
||||
var year = ParseIntArg(args, "year") ?? DateTime.UtcNow.Year;
|
||||
var month = ParseIntArg(args, "month") ?? DateTime.UtcNow.Month;
|
||||
|
||||
var budget = await _budgetService.GetByMonthAsync(userId, year, month);
|
||||
if (budget == null)
|
||||
return ("budget", new { items = Array.Empty<object>(), year, month }, $"No budget found for {year}-{month:D2}.");
|
||||
|
||||
var summary = new StringBuilder();
|
||||
summary.AppendLine($"Budget for {year}-{month:D2}:");
|
||||
foreach (var item in budget.Items)
|
||||
{
|
||||
var remaining = item.BudgetedAmount - item.ActualAmount;
|
||||
var status = remaining >= 0 ? "under" : "over";
|
||||
summary.AppendLine($"- {item.CategoryName}: Budgeted {item.BudgetedAmount:C}, Actual {item.ActualAmount:C}, {Math.Abs(remaining):C} {status} budget");
|
||||
}
|
||||
|
||||
return ("budget", budget, summary.ToString());
|
||||
}
|
||||
|
||||
private async Task<(string, object, string)> ExecuteGetBudgetSummaryAsync(Guid userId, Dictionary<string, object> args)
|
||||
{
|
||||
var year = ParseIntArg(args, "year") ?? DateTime.UtcNow.Year;
|
||||
var month = ParseIntArg(args, "month") ?? DateTime.UtcNow.Month;
|
||||
|
||||
var budget = await _budgetService.GetByMonthAsync(userId, year, month);
|
||||
if (budget == null)
|
||||
return ("budget_summary", new { year, month, totalBudgeted = 0m, totalActual = 0m, remaining = 0m, overBudgetCount = 0 },
|
||||
$"No budget found for {year}-{month:D2}.");
|
||||
|
||||
var totalBudgeted = budget.Items.Sum(i => i.BudgetedAmount);
|
||||
var totalActual = budget.Items.Sum(i => i.ActualAmount);
|
||||
var remaining = totalBudgeted - totalActual;
|
||||
var overBudgetCount = budget.Items.Count(i => i.ActualAmount > i.BudgetedAmount);
|
||||
|
||||
var result = new { year, month, totalBudgeted, totalActual, remaining, overBudgetCount };
|
||||
|
||||
var monthName = new DateTime(year, month, 1).ToString("MMM yyyy");
|
||||
var summaryText = $"Budget for {monthName}: {totalBudgeted:C} budgeted, {totalActual:C} spent, {remaining:C} remaining. {overBudgetCount} categories over budget.";
|
||||
|
||||
return ("budget_summary", result, summaryText);
|
||||
}
|
||||
|
||||
private async Task<(string, object, string)> ExecuteGetLoanDetailsAsync(Guid userId, Dictionary<string, object> args)
|
||||
{
|
||||
var accountName = ParseStringArg(args, "account_name")
|
||||
?? throw new ArgumentException("account_name is required");
|
||||
|
||||
var accounts = await _accountService.GetAllAsync(userId);
|
||||
var account = accounts.FirstOrDefault(a => a.Name.Equals(accountName, StringComparison.OrdinalIgnoreCase))
|
||||
?? throw new ArgumentException($"Account '{accountName}' not found");
|
||||
|
||||
var loan = await _loanService.GetLoanDetailAsync(userId, account.Id)
|
||||
?? throw new KeyNotFoundException($"No loan details found for account '{accountName}'");
|
||||
|
||||
// Return without amortization schedule to keep payload small
|
||||
var result = new
|
||||
{
|
||||
loan.Id,
|
||||
loan.AccountId,
|
||||
loan.AccountName,
|
||||
loan.OriginalBalance,
|
||||
loan.CurrentBalance,
|
||||
loan.InterestRate,
|
||||
loan.TermMonths,
|
||||
loan.MonthlyPayment,
|
||||
loan.OriginationDate,
|
||||
loan.MaturityDate,
|
||||
loan.EscrowAmount,
|
||||
loan.ExtraPayment
|
||||
};
|
||||
|
||||
var summary = new StringBuilder();
|
||||
summary.AppendLine($"Loan details for {loan.AccountName}:");
|
||||
summary.AppendLine($"- Original Balance: {loan.OriginalBalance:C}");
|
||||
summary.AppendLine($"- Current Balance: {loan.CurrentBalance:C}");
|
||||
summary.AppendLine($"- Interest Rate: {loan.InterestRate:F2}%");
|
||||
summary.AppendLine($"- Monthly Payment: {loan.MonthlyPayment:C}");
|
||||
summary.AppendLine($"- Maturity Date: {loan.MaturityDate:yyyy-MM-dd}");
|
||||
if (loan.EscrowAmount.HasValue)
|
||||
summary.AppendLine($"- Escrow: {loan.EscrowAmount.Value:C}");
|
||||
|
||||
return ("loan_details", result, summary.ToString());
|
||||
}
|
||||
|
||||
private async Task<(string, object, string)> ExecuteCalculateLoanPayoffAsync(Guid userId, Dictionary<string, object> args)
|
||||
{
|
||||
var accountName = ParseStringArg(args, "account_name")
|
||||
?? throw new ArgumentException("account_name is required");
|
||||
|
||||
var accounts = await _accountService.GetAllAsync(userId);
|
||||
var account = accounts.FirstOrDefault(a => a.Name.Equals(accountName, StringComparison.OrdinalIgnoreCase))
|
||||
?? throw new ArgumentException($"Account '{accountName}' not found");
|
||||
|
||||
var request = new PayoffScenarioRequest(
|
||||
ExtraMonthlyPayment: ParseDecimalArg(args, "extra_monthly_payment"),
|
||||
LumpSumPayment: ParseDecimalArg(args, "lump_sum_payment"),
|
||||
LumpSumDate: ParseDateArg(args, "lump_sum_date")
|
||||
);
|
||||
|
||||
var scenario = await _loanService.CalculatePayoffScenarioAsync(userId, account.Id, request);
|
||||
|
||||
// Return without full schedule to keep payload small
|
||||
var result = new
|
||||
{
|
||||
scenario.OriginalPayoffDate,
|
||||
scenario.NewPayoffDate,
|
||||
scenario.TotalInterestOriginal,
|
||||
scenario.TotalInterestNew,
|
||||
scenario.InterestSaved,
|
||||
scenario.MonthsSaved
|
||||
};
|
||||
|
||||
var summary = new StringBuilder();
|
||||
summary.AppendLine($"Payoff scenario for {accountName}:");
|
||||
summary.AppendLine($"- Original Payoff Date: {scenario.OriginalPayoffDate:yyyy-MM-dd}");
|
||||
summary.AppendLine($"- New Payoff Date: {scenario.NewPayoffDate:yyyy-MM-dd}");
|
||||
summary.AppendLine($"- Interest Saved: {scenario.InterestSaved:C}");
|
||||
summary.AppendLine($"- Months Saved: {scenario.MonthsSaved}");
|
||||
|
||||
return ("loan_payoff", result, summary.ToString());
|
||||
}
|
||||
|
||||
private async Task<(string, object, string)> ExecuteListPayeesAsync(Guid userId)
|
||||
{
|
||||
var payees = await _payeeService.GetAllAsync(userId);
|
||||
var active = payees.Where(p => p.IsActive).ToList();
|
||||
|
||||
var summary = new StringBuilder();
|
||||
summary.AppendLine($"You have {active.Count} active payees:");
|
||||
foreach (var payee in active)
|
||||
{
|
||||
var category = payee.DefaultCategoryName != null ? $" (default category: {payee.DefaultCategoryName})" : "";
|
||||
summary.AppendLine($"- {payee.Name}{category}");
|
||||
}
|
||||
|
||||
return ("payees", active, summary.ToString());
|
||||
}
|
||||
|
||||
private async Task<(string, object, string)> ExecuteGetScheduledTransactionsAsync(Guid userId)
|
||||
{
|
||||
var scheduled = await _scheduledTransactionService.GetAllAsync(userId);
|
||||
var active = scheduled.Where(s => s.IsActive).ToList();
|
||||
|
||||
var summary = new StringBuilder();
|
||||
summary.AppendLine($"You have {active.Count} active scheduled transactions:");
|
||||
foreach (var txn in active)
|
||||
{
|
||||
summary.AppendLine($"- {txn.PayeeName ?? "Unknown"}: {txn.Amount:C} ({txn.Frequency}), next due {txn.NextDueDate:yyyy-MM-dd}, account: {txn.AccountName}");
|
||||
}
|
||||
|
||||
return ("scheduled_transactions", active, summary.ToString());
|
||||
}
|
||||
|
||||
private static List<OllamaTool> BuildToolDefinitions()
|
||||
{
|
||||
return new List<OllamaTool>
|
||||
@@ -680,7 +853,7 @@ public class ChatService : IChatService
|
||||
["min_amount"] = new() { Type = "number", Description = "Minimum transaction amount" },
|
||||
["max_amount"] = new() { Type = "number", Description = "Maximum transaction amount" },
|
||||
["search_text"] = new() { Type = "string", Description = "General text search across payee, memo, etc." },
|
||||
["page_size"] = new() { Type = "integer", Description = "Number of results to return (max 25, default 25)" }
|
||||
["page_size"] = new() { Type = "integer", Description = "Number of results to return (max 50, default 25)" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -821,6 +994,109 @@ public class ChatService : IChatService
|
||||
Required = new List<string> { "name" }
|
||||
}
|
||||
}
|
||||
},
|
||||
new()
|
||||
{
|
||||
Type = "function",
|
||||
Function = new OllamaFunction
|
||||
{
|
||||
Name = "get_budget",
|
||||
Description = "Get budget for a specific month with budgeted vs actual amounts per category.",
|
||||
Parameters = new OllamaFunctionParameters
|
||||
{
|
||||
Type = "object",
|
||||
Properties = new Dictionary<string, OllamaPropertySchema>
|
||||
{
|
||||
["year"] = new() { Type = "integer", Description = "Year (defaults to current year)" },
|
||||
["month"] = new() { Type = "integer", Description = "Month number 1-12 (defaults to current month)" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new()
|
||||
{
|
||||
Type = "function",
|
||||
Function = new OllamaFunction
|
||||
{
|
||||
Name = "get_budget_summary",
|
||||
Description = "Get high-level budget vs actual totals for a month. Shows total budgeted, total spent, remaining, and count of categories over budget.",
|
||||
Parameters = new OllamaFunctionParameters
|
||||
{
|
||||
Type = "object",
|
||||
Properties = new Dictionary<string, OllamaPropertySchema>
|
||||
{
|
||||
["year"] = new() { Type = "integer", Description = "Year (defaults to current year)" },
|
||||
["month"] = new() { Type = "integer", Description = "Month number 1-12 (defaults to current month)" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new()
|
||||
{
|
||||
Type = "function",
|
||||
Function = new OllamaFunction
|
||||
{
|
||||
Name = "get_loan_details",
|
||||
Description = "Get loan details for an account including balance, interest rate, monthly payment, and maturity date.",
|
||||
Parameters = new OllamaFunctionParameters
|
||||
{
|
||||
Type = "object",
|
||||
Properties = new Dictionary<string, OllamaPropertySchema>
|
||||
{
|
||||
["account_name"] = new() { Type = "string", Description = "The name of the loan account" }
|
||||
},
|
||||
Required = new List<string> { "account_name" }
|
||||
}
|
||||
}
|
||||
},
|
||||
new()
|
||||
{
|
||||
Type = "function",
|
||||
Function = new OllamaFunction
|
||||
{
|
||||
Name = "calculate_loan_payoff",
|
||||
Description = "Calculate loan payoff scenarios with extra payments. Shows new payoff date, interest saved, and months saved.",
|
||||
Parameters = new OllamaFunctionParameters
|
||||
{
|
||||
Type = "object",
|
||||
Properties = new Dictionary<string, OllamaPropertySchema>
|
||||
{
|
||||
["account_name"] = new() { Type = "string", Description = "The name of the loan account" },
|
||||
["extra_monthly_payment"] = new() { Type = "number", Description = "Extra amount to pay each month" },
|
||||
["lump_sum_payment"] = new() { Type = "number", Description = "One-time lump sum payment amount" },
|
||||
["lump_sum_date"] = new() { Type = "string", Description = "Date for lump sum payment in YYYY-MM-DD format" }
|
||||
},
|
||||
Required = new List<string> { "account_name" }
|
||||
}
|
||||
}
|
||||
},
|
||||
new()
|
||||
{
|
||||
Type = "function",
|
||||
Function = new OllamaFunction
|
||||
{
|
||||
Name = "list_payees",
|
||||
Description = "List all active payees with their default categories.",
|
||||
Parameters = new OllamaFunctionParameters
|
||||
{
|
||||
Type = "object",
|
||||
Properties = new Dictionary<string, OllamaPropertySchema>()
|
||||
}
|
||||
}
|
||||
},
|
||||
new()
|
||||
{
|
||||
Type = "function",
|
||||
Function = new OllamaFunction
|
||||
{
|
||||
Name = "get_scheduled_transactions",
|
||||
Description = "List all active scheduled/recurring transactions with payee, amount, frequency, and next due date.",
|
||||
Parameters = new OllamaFunctionParameters
|
||||
{
|
||||
Type = "object",
|
||||
Properties = new Dictionary<string, OllamaPropertySchema>()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user