Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 167 additions & 73 deletions src/lampyrid/models/lampyrid_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ def utc_now():


class Account(BaseModel):
id: str = Field(..., examples=['2'])
name: str = Field(..., examples=['Cash'])
currency_code: Optional[str] = Field(None, examples=['GBP'])
current_balance: Optional[float] = Field(None, examples=[1000.0])
id: str = Field(..., description='Unique identifier for the account', examples=['2'])
name: str = Field(..., description='Display name of the account', examples=['Cash'])
currency_code: Optional[str] = Field(
None, description='Currency code (ISO 4217) for the account', examples=['GBP']
)
current_balance: Optional[float] = Field(
None, description='Current account balance as a decimal number', examples=[1000.0]
)

@classmethod
def from_account_read(cls, account_read: 'AccountRead') -> 'Account':
Expand All @@ -44,11 +48,21 @@ def from_account_read(cls, account_read: 'AccountRead') -> 'Account':


class Budget(BaseModel):
id: str = Field(..., examples=['2'])
name: str = Field(..., examples=['Groceries'])
active: Optional[bool] = Field(None, examples=[True])
notes: Optional[str] = Field(None, examples=['Monthly grocery budget'])
order: Optional[int] = Field(None, examples=[1])
id: str = Field(..., description='Unique identifier for the budget', examples=['2'])
name: str = Field(..., description='Display name of the budget', examples=['Groceries'])
active: Optional[bool] = Field(
None,
description='Whether this budget is currently active for new transactions',
examples=[True],
)
notes: Optional[str] = Field(
None,
description='Optional notes or description about this budget',
examples=['Monthly grocery budget'],
)
order: Optional[int] = Field(
None, description='Display order for sorting budgets', examples=[1]
)

@classmethod
def from_budget_read(cls, budget_read: 'BudgetRead') -> 'Budget':
Expand Down Expand Up @@ -145,106 +159,157 @@ def to_transaction_split_store(self) -> TransactionSplitStore:


class ListAccountRequest(BaseModel):
type: AccountTypeFilter = Field(..., description='Type of account to filter by')
type: AccountTypeFilter = Field(
...,
description='Account type: asset (your accounts), expense (spending categories), revenue (income sources), liability (debts), or all',
)


class SearchAccountRequest(BaseModel):
query: str = Field(..., description='Search query for account names')
query: str = Field(
..., description='Text to search for in account names (supports partial matching)'
)
type: AccountTypeFilter = Field(
AccountTypeFilter.all, description='Optional type filter for accounts'
AccountTypeFilter.all,
description='Limit search to specific account type (asset, expense, revenue, liability, or all)',
)


class GetAccountRequest(BaseModel):
id: str = Field(..., description='ID of the account to retrieve')
id: str = Field(
..., description='Unique identifier of the account (from list_accounts or search_accounts)'
)


class CreateWithdrawalRequest(BaseModel):
amount: float = Field(..., description='Amount of the withdrawal')
description: str = Field(..., description='Description of the withdrawal')
date: datetime = Field(default_factory=utc_now, description='Date and time of the withdrawal')
amount: float = Field(
..., description='Amount to withdraw as positive number (e.g., 25.50 for $25.50 expense)'
)
description: str = Field(
..., description='What this expense was for (e.g., "Grocery shopping at Whole Foods")'
)
date: datetime = Field(
default_factory=utc_now,
description='When the expense occurred (defaults to current time if not specified)',
)
source_id: str = Field(
...,
description='ID of the source account for the withdrawal. This must always be an asset account.',
description='ID of your account the money comes from (checking, savings, cash, etc.). Must be an asset account you own.',
)
destination_name: Optional[str] = Field(
default=None,
description='Name of the destination account for the withdrawal. This account is automatically created if it does not exist. Leave it blank for cash withdrawals.',
description='Where the money went ("Groceries", "Gas Station", "ATM"). Creates expense account if new. Leave blank for cash withdrawals.',
)
budget_id: Optional[str] = Field(
None, description='ID of the budget to allocate this withdrawal to'
None, description='Budget to track this expense against (from list_budgets)'
)
budget_name: Optional[str] = Field(
None,
description='Name of the budget to allocate this withdrawal to. If the budget name is unknown, the ID will be used or the value will be ignored.',
description='Name of budget if ID is unknown. Will use ID if both provided.',
)


class CreateDepositRequest(BaseModel):
amount: float = Field(..., description='Amount of the deposit')
description: str = Field(..., description='Description of the deposit')
date: datetime = Field(default_factory=utc_now, description='Date and time of the deposit')
amount: float = Field(
..., description='Amount received as positive number (e.g., 2500.00 for $2500 salary)'
)
description: str = Field(
..., description='What this income was for (e.g., "Monthly salary", "Freelance payment")'
)
date: datetime = Field(
default_factory=utc_now,
description='When the income was received (defaults to current time if not specified)',
)
source_name: Optional[str] = Field(
default=None,
description='Name of the source account for the deposit. This account is automatically created if it does not exist.',
description='Where the money came from ("Employer", "Client Name", "Gift"). Creates revenue account if new.',
)
destination_id: str = Field(
...,
description='ID of the destination account for the deposit. This must always be an asset account.',
description='ID of your account receiving the money (checking, savings, etc.). Must be an asset account you own.',
)


class CreateTransferRequest(BaseModel):
amount: float = Field(..., description='Amount of the transfer')
description: str = Field(..., description='Description of the transfer')
date: datetime = Field(default_factory=utc_now, description='Date and time of the transfer')
amount: float = Field(
..., description='Amount to move as positive number (e.g., 500.00 to move $500)'
)
description: str = Field(
...,
description='Purpose of the transfer (e.g., "Transfer to savings", "Credit card payment")',
)
date: datetime = Field(
default_factory=utc_now,
description='When the transfer occurred (defaults to current time if not specified)',
)
source_id: str = Field(
...,
description='ID of the source account for the transfer. This must always be an asset account.',
description='ID of your account the money comes from. Must be an asset account you own.',
)
destination_id: str = Field(
...,
description='ID of the destination account for the transfer. This must always be an asset account.',
description='ID of your account receiving the money. Must be an asset account you own.',
)


class GetTransactionsRequest(BaseModel):
start_date: Optional[date] = Field(
None, description='Start date for transaction range (YYYY-MM-DD), inclusive'
None,
description='Start date for filtering transactions (YYYY-MM-DD format). If not specified, returns recent transactions.',
)
end_date: Optional[date] = Field(
None, description='End date for transaction range (YYYY-MM-DD), inclusive'
None,
description='End date for filtering transactions (YYYY-MM-DD format). If not specified, returns up to current date.',
)
transaction_type: Optional[TransactionTypeFilter] = Field(
None, description='Optional filter on transaction type'
None,
description='Filter by transaction type: withdrawal (expenses), deposit (income), or transfer (between accounts)',
)
page: Optional[int] = Field(
1,
description='Page number to retrieve (1-based). Use for browsing large result sets.',
ge=1,
)
limit: Optional[int] = Field(
50, description='Maximum number of transactions to return per page (1-500)', ge=1, le=500
)
page: Optional[int] = Field(1, description='Page number for pagination', ge=1)
limit: Optional[int] = Field(50, description='Number of items per page', ge=1, le=500)


class SearchTransactionsRequest(BaseModel):
query: str = Field(
..., description='Search query to find transactions (e.g., "groceries", "salary")'
...,
description='Text to search for in transaction descriptions, account names, and other transaction fields',
)
page: Optional[int] = Field(
1,
description='Page number to retrieve (1-based). Use for browsing large result sets.',
ge=1,
)
limit: Optional[int] = Field(
50, description='Maximum number of transactions to return per page (1-500)', ge=1, le=500
)
page: Optional[int] = Field(1, description='Page number for pagination', ge=1)
limit: Optional[int] = Field(50, description='Number of items per page', ge=1, le=500)


class DeleteTransactionRequest(BaseModel):
id: str = Field(..., description='ID of the transaction to delete')
id: str = Field(..., description='Unique identifier of the transaction to permanently remove')


class GetTransactionRequest(BaseModel):
id: str = Field(..., description='ID of the transaction to retrieve')
id: str = Field(..., description='Unique identifier of the transaction to get details for')


class TransactionListResponse(BaseModel):
"""Response model for transaction listings."""

transactions: List[Transaction] = Field(..., description='List of transactions')
total_count: Optional[int] = Field(None, description='Total number of transactions available')
current_page: int = Field(..., description='Current page number')
per_page: int = Field(..., description='Number of items per page')
transactions: List[Transaction] = Field(
..., description='Array of transaction objects matching the request'
)
total_count: Optional[int] = Field(
None,
description='Total transactions available across all pages (if pagination metadata available)',
)
current_page: int = Field(..., description='Current page number in the result set')
per_page: int = Field(..., description='Number of transactions included in this page')

@classmethod
def from_transaction_array(
Expand All @@ -267,30 +332,44 @@ def from_transaction_array(
class ListBudgetsRequest(BaseModel):
"""Request for listing budgets."""

active: Optional[bool] = Field(None, description='Filter budgets by active status')
active: Optional[bool] = Field(
None,
description='Show only active budgets (true), inactive budgets (false), or all budgets (not specified)',
)


class GetBudgetRequest(BaseModel):
"""Request for getting a single budget by ID."""

id: str = Field(..., description='ID of the budget to retrieve')
id: str = Field(..., description='Unique identifier of the budget to get details for')


class BudgetSpending(BaseModel):
"""Budget spending information for a specific period."""

budget_id: str = Field(..., description='ID of the budget')
budget_name: str = Field(..., description='Name of the budget')
spent: float = Field(..., description='Amount spent in this budget during the period')
budgeted: Optional[float] = Field(None, description='Budgeted amount for this period')
remaining: Optional[float] = Field(None, description='Remaining budget amount')
percentage_spent: Optional[float] = Field(None, description='Percentage of budget spent')
budget_id: str = Field(..., description='Unique identifier of the budget')
budget_name: str = Field(..., description='Display name of the budget')
spent: float = Field(
..., description='Total amount spent from this budget in the specified period'
)
budgeted: Optional[float] = Field(
None, description='Amount allocated to this budget for the period'
)
remaining: Optional[float] = Field(
None, description='Money left in this budget (budgeted minus spent)'
)
percentage_spent: Optional[float] = Field(
None,
description='Percentage of allocated budget used (0-100+, can exceed 100 if overspent)',
)


class GetBudgetSpendingRequest(BaseModel):
"""Request for getting budget spending data."""

budget_id: str = Field(..., description='ID of the budget to get spending for')
budget_id: str = Field(
..., description='Unique identifier of the budget to analyze spending for'
)
start_date: Optional[date] = Field(
None, description='Start date for spending period (YYYY-MM-DD), inclusive'
)
Expand All @@ -302,12 +381,15 @@ class GetBudgetSpendingRequest(BaseModel):
class BudgetSummary(BaseModel):
"""Summary of all budgets with spending information."""

budgets: List[BudgetSpending] = Field(..., description='List of budget spending data')
total_budgeted: Optional[float] = Field(None, description='Total budgeted amount')
total_spent: float = Field(..., description='Total amount spent across all budgets')
total_remaining: Optional[float] = Field(None, description='Total remaining budget')
budgets: List[BudgetSpending] = Field(..., description='Spending analysis for each budget')
total_budgeted: Optional[float] = Field(None, description='Sum of all allocated budget amounts')
total_spent: float = Field(..., description='Sum of all spending across budgets')
total_remaining: Optional[float] = Field(
None, description='Total money left across all budgets'
)
available_budget: Optional[float] = Field(
None, description='Available budget amount not allocated to specific budgets'
None,
description='Unallocated money available for new budgets or unexpected expenses',
)


Expand All @@ -325,20 +407,22 @@ class GetBudgetSummaryRequest(BaseModel):
class AvailableBudget(BaseModel):
"""Available budget information for a period."""

amount: float = Field(..., description='Available budget amount')
currency_code: str = Field(..., description='Currency code for the amount')
start_date: date = Field(..., description='Start date of the budget period')
end_date: date = Field(..., description='End date of the budget period')
amount: float = Field(..., description='Total unallocated budget available for the period')
currency_code: str = Field(..., description='Currency code (ISO 4217) for the budget amount')
start_date: date = Field(..., description='Beginning of the budget period this amount covers')
end_date: date = Field(..., description='End of the budget period this amount covers')


class GetAvailableBudgetRequest(BaseModel):
"""Request for getting available budget."""

start_date: Optional[date] = Field(
None, description='Start date for budget period (YYYY-MM-DD), defaults to current month'
None,
description='Start date for budget analysis (YYYY-MM-DD format). Defaults to beginning of current month.',
)
end_date: Optional[date] = Field(
None, description='End date for budget period (YYYY-MM-DD), defaults to current month'
None,
description='End date for budget analysis (YYYY-MM-DD format). Defaults to end of current month.',
)


Expand All @@ -356,24 +440,34 @@ class CreateBulkTransactionsRequest(BaseModel):
class UpdateTransactionRequest(BaseModel):
"""Update an existing transaction."""

transaction_id: str = Field(..., description='ID of the transaction to update')
amount: Optional[float] = Field(None, description='New amount for the transaction')
description: Optional[str] = Field(None, description='New description for the transaction')
date: Optional[datetime] = Field(None, description='New date for the transaction')
source_id: Optional[str] = Field(None, description='New source account ID')
destination_id: Optional[str] = Field(None, description='New destination account ID')
transaction_id: str = Field(..., description='Unique identifier of the transaction to modify')
amount: Optional[float] = Field(None, description='New transaction amount (positive number)')
description: Optional[str] = Field(
None, description='New description for what the transaction was for'
)
date: Optional[datetime] = Field(
None, description='New date/time when the transaction occurred'
)
source_id: Optional[str] = Field(
None, description='New source account ID (where money comes from)'
)
destination_id: Optional[str] = Field(
None, description='New destination account ID (where money goes to)'
)
budget_id: Optional[str] = Field(
None, description='New budget ID (set to None to clear budget)'
None, description='New budget ID to assign, or None to remove budget assignment'
)
category_name: Optional[str] = Field(
None, description='New category name for transaction classification'
)
category_name: Optional[str] = Field(None, description='New category name')


class BulkUpdateTransactionsRequest(BaseModel):
"""Update multiple transactions in one operation."""

updates: List[UpdateTransactionRequest] = Field(
...,
description='List of transaction updates to apply',
description='Array of transaction modifications to apply in a single operation',
min_length=1,
max_length=50,
)
Loading