Skip to content

Commit eb2b12b

Browse files
authored
Merge pull request #69 from RadCod3/feat/68-add-insight-tools
Add financial insights mcp tools
2 parents 8bbc3bc + 09d9547 commit eb2b12b

File tree

11 files changed

+1618
-9
lines changed

11 files changed

+1618
-9
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ markers = [
6464
"accounts: account-related tests",
6565
"transactions: transaction-related tests",
6666
"budgets: budget-related tests",
67+
"insights: insight analysis tests",
6768
]
6869

6970
[tool.datamodel-codegen]

src/lampyrid/clients/firefly.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
BudgetLimitArray,
1818
BudgetSingle,
1919
BudgetStore,
20+
InsightGroup,
21+
InsightTotal,
22+
InsightTransfer,
2023
TransactionArray,
2124
TransactionSingle,
2225
TransactionStore,
@@ -302,3 +305,161 @@ async def get_available_budgets(
302305
self._handle_api_error(r)
303306
r.raise_for_status()
304307
return AvailableBudgetArray.model_validate(r.json())
308+
309+
# =========================================================================
310+
# Insight API Methods
311+
# =========================================================================
312+
313+
def _build_insight_params(
314+
self,
315+
start_date: date,
316+
end_date: date,
317+
account_ids: Optional[list[int]] = None,
318+
) -> Dict[str, Any]:
319+
"""Build common parameters for insight API calls."""
320+
params: Dict[str, Any] = {
321+
'start': start_date.strftime('%Y-%m-%d'),
322+
'end': end_date.strftime('%Y-%m-%d'),
323+
}
324+
if account_ids:
325+
params['accounts[]'] = account_ids
326+
return params
327+
328+
# Expense Insight Methods
329+
330+
async def get_expense_total(
331+
self,
332+
start_date: date,
333+
end_date: date,
334+
account_ids: Optional[list[int]] = None,
335+
) -> InsightTotal:
336+
"""Get total expenses for a period."""
337+
params = self._build_insight_params(start_date, end_date, account_ids)
338+
r = await self._client.get('/api/v1/insight/expense/total', params=params)
339+
self._handle_api_error(r)
340+
r.raise_for_status()
341+
return InsightTotal.model_validate(r.json())
342+
343+
async def get_expense_by_expense_account(
344+
self,
345+
start_date: date,
346+
end_date: date,
347+
account_ids: Optional[list[int]] = None,
348+
) -> InsightGroup:
349+
"""Get expenses grouped by expense account (vendor/payee)."""
350+
params = self._build_insight_params(start_date, end_date, account_ids)
351+
r = await self._client.get('/api/v1/insight/expense/expense', params=params)
352+
self._handle_api_error(r)
353+
r.raise_for_status()
354+
return InsightGroup.model_validate(r.json())
355+
356+
async def get_expense_by_asset_account(
357+
self,
358+
start_date: date,
359+
end_date: date,
360+
account_ids: Optional[list[int]] = None,
361+
) -> InsightGroup:
362+
"""Get expenses grouped by asset account (source)."""
363+
params = self._build_insight_params(start_date, end_date, account_ids)
364+
r = await self._client.get('/api/v1/insight/expense/asset', params=params)
365+
self._handle_api_error(r)
366+
r.raise_for_status()
367+
return InsightGroup.model_validate(r.json())
368+
369+
async def get_expense_by_budget(
370+
self,
371+
start_date: date,
372+
end_date: date,
373+
account_ids: Optional[list[int]] = None,
374+
budget_ids: Optional[list[int]] = None,
375+
) -> InsightGroup:
376+
"""Get expenses grouped by budget."""
377+
params = self._build_insight_params(start_date, end_date, account_ids)
378+
if budget_ids:
379+
params['budgets[]'] = budget_ids
380+
r = await self._client.get('/api/v1/insight/expense/budget', params=params)
381+
self._handle_api_error(r)
382+
r.raise_for_status()
383+
return InsightGroup.model_validate(r.json())
384+
385+
async def get_expense_no_budget(
386+
self,
387+
start_date: date,
388+
end_date: date,
389+
account_ids: Optional[list[int]] = None,
390+
) -> InsightTotal:
391+
"""Get expenses without any budget assigned."""
392+
params = self._build_insight_params(start_date, end_date, account_ids)
393+
r = await self._client.get('/api/v1/insight/expense/no-budget', params=params)
394+
self._handle_api_error(r)
395+
r.raise_for_status()
396+
return InsightTotal.model_validate(r.json())
397+
398+
# Income Insight Methods
399+
400+
async def get_income_total(
401+
self,
402+
start_date: date,
403+
end_date: date,
404+
account_ids: Optional[list[int]] = None,
405+
) -> InsightTotal:
406+
"""Get total income for a period."""
407+
params = self._build_insight_params(start_date, end_date, account_ids)
408+
r = await self._client.get('/api/v1/insight/income/total', params=params)
409+
self._handle_api_error(r)
410+
r.raise_for_status()
411+
return InsightTotal.model_validate(r.json())
412+
413+
async def get_income_by_revenue_account(
414+
self,
415+
start_date: date,
416+
end_date: date,
417+
account_ids: Optional[list[int]] = None,
418+
) -> InsightGroup:
419+
"""Get income grouped by revenue account (income source)."""
420+
params = self._build_insight_params(start_date, end_date, account_ids)
421+
r = await self._client.get('/api/v1/insight/income/revenue', params=params)
422+
self._handle_api_error(r)
423+
r.raise_for_status()
424+
return InsightGroup.model_validate(r.json())
425+
426+
async def get_income_by_asset_account(
427+
self,
428+
start_date: date,
429+
end_date: date,
430+
account_ids: Optional[list[int]] = None,
431+
) -> InsightGroup:
432+
"""Get income grouped by asset account (receiving account)."""
433+
params = self._build_insight_params(start_date, end_date, account_ids)
434+
r = await self._client.get('/api/v1/insight/income/asset', params=params)
435+
self._handle_api_error(r)
436+
r.raise_for_status()
437+
return InsightGroup.model_validate(r.json())
438+
439+
# Transfer Insight Methods
440+
441+
async def get_transfer_total(
442+
self,
443+
start_date: date,
444+
end_date: date,
445+
account_ids: Optional[list[int]] = None,
446+
) -> InsightTotal:
447+
"""Get total transfers for a period."""
448+
params = self._build_insight_params(start_date, end_date, account_ids)
449+
r = await self._client.get('/api/v1/insight/transfer/total', params=params)
450+
self._handle_api_error(r)
451+
r.raise_for_status()
452+
return InsightTotal.model_validate(r.json())
453+
454+
async def get_transfer_by_asset_account(
455+
self,
456+
start_date: date,
457+
end_date: date,
458+
account_ids: Optional[list[int]] = None,
459+
) -> InsightTransfer:
460+
"""Get transfers grouped by asset account with in/out breakdown."""
461+
params = self._build_insight_params(start_date, end_date, account_ids)
462+
r = await self._client.get('/api/v1/insight/transfer/asset', params=params)
463+
self._handle_api_error(r)
464+
r.raise_for_status()
465+
return InsightTransfer.model_validate(r.json())

0 commit comments

Comments
 (0)