Skip to content

Add financial insights mcp tools#69

Merged
RadCod3 merged 5 commits intomainfrom
feat/68-add-insight-tools
Jan 31, 2026
Merged

Add financial insights mcp tools#69
RadCod3 merged 5 commits intomainfrom
feat/68-add-insight-tools

Conversation

@RadCod3
Copy link
Owner

@RadCod3 RadCod3 commented Jan 31, 2026

Resolves #68

Implement comprehensive financial analysis capabilities, including:
- Add insight API methods to FireflyClient for expense, income, and
transfer data.
- Implement InsightService to coordinate business logic and model
transformations.
- Create new MCP tools for detailed expense, income, and transfer
analysis.
- Provide a get_financial_summary tool for a high-level overview of net
position.
- Define structured Pydantic models for all insight requests and
results.
- Implement comprehensive integration tests for expense, income, and
transfer insights.
- Add automated seed transaction generation and cleanup logic to
conftest.py.
- Introduce insight request factory fixtures for standardized test data.
- Register the insights pytest marker in pyproject.toml.
@coderabbitai
Copy link

coderabbitai bot commented Jan 31, 2026

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added comprehensive financial insights and analytics capabilities with support for expense, income, and transfer analysis
    • New methods to filter insights by date ranges and accounts with flexible grouping options (by account type, budget, etc.)
    • Financial summary feature calculating net position across all transaction types
  • Tests

    • Added integration tests for insights functionality with comprehensive coverage
  • Chores

    • Updated service configuration to include new insights capabilities

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds Insights: Firefly client insight methods, new insight request/result models, an InsightService, MCP tools/server for insights, test fixtures and integration tests with seeded transactions, and a pytest marker for insight tests.

Changes

Cohort / File(s) Summary
Pytest config
pyproject.toml
Adds pytest marker insights: insight analysis tests for test categorization.
Client: Firefly insight endpoints
src/lampyrid/clients/firefly.py
Adds _build_insight_params and ~10 async methods calling Firefly insight endpoints; returns new InsightTotal/InsightGroup/InsightTransfer models and handles API errors.
Models: Insight types
src/lampyrid/models/lampyrid_models.py
Adds request models (GetExpenseInsightRequest, GetIncomeInsightRequest, GetTransferInsightRequest, GetFinancialSummaryRequest), result models (ExpenseInsightResult, IncomeInsightResult, TransferInsightResult, FinancialSummary), and shared types (InsightEntry, TransferInsightEntry).
Service: Insight orchestration
src/lampyrid/services/insights.py, src/lampyrid/services/__init__.py
Adds InsightService with transformers and async methods to fetch/aggregate expense, income, transfer insights and compute financial summaries; exports InsightService.
Tools / MCP server
src/lampyrid/tools/insights.py, src/lampyrid/tools/__init__.py
Adds create_insights_server registering four MCP tools (expense, income, transfer, financial summary) and wires the insights server into main composition.
Tests: fixtures, conftest, integration
tests/fixtures/insights.py, tests/conftest.py, tests/integration/test_insights.py
Adds factories for insight requests, seeds transactions in test setup with cleanup fixture, and extensive integration tests covering groupings, filters, totals, and edge cases.

Sequence Diagram(s)

sequenceDiagram
    participant Client as MCP Client
    participant Tool as Insight Tool
    participant Service as InsightService
    participant FClient as FireflyClient
    participant API as Firefly API

    Client->>Tool: get_expense_insight(req)
    Tool->>Service: get_expense_insight(req)

    rect rgba(100, 150, 200, 0.5)
    Note over Service,FClient: Service composes params and calls client endpoints
    Service->>FClient: get_expense_total(start,end,accounts)
    FClient->>API: GET /v1/insight/expense/total?...
    API-->>FClient: InsightTotal
    Service->>FClient: get_expense_by_expense_account(...)
    FClient->>API: GET /v1/insight/expense/expense?...
    API-->>FClient: InsightGroup
    end

    rect rgba(150, 100, 200, 0.5)
    Note over Service: Transform API models -> internal entries/results
    Service->>Service: _entries_from_insight_total(...)
    Service->>Service: _entries_from_insight_group(...)
    Service->>Service: _get_total_and_currency(...)
    end

    Service-->>Tool: ExpenseInsightResult
    Tool-->>Client: ExpenseInsightResult
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through ledgers, nibbling clues,
Gathered sums from income, transfers, dues,
I grouped the carrots, tallied each heap,
Insights in paw—no secrets to keep,
Hop, compute, and dance—analytics complete! 🥕📊

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add financial insights mcp tools' is concise and directly related to the main changeset, which implements MCP tools for accessing Firefly III insight endpoints.
Description check ✅ Passed The description 'Resolves #68' is minimal but directly related to the changeset, referencing the linked issue that defines the objectives for adding insight tools.
Linked Issues check ✅ Passed The PR implements a subset of Firefly insight endpoints as required by issue #68: expense insights (total, by expense/asset account, by budget, no-budget), income insights (total, by revenue/asset account), and transfer insights (total, by asset account), with corresponding MCP tools, models, and services.
Out of Scope Changes check ✅ Passed All changes are focused on implementing insight tools and supporting infrastructure (models, services, fixtures, tests). Test configuration updates (seed transactions, imports) are appropriately scoped to support the new insight testing requirements.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/68-add-insight-tools

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai bot changed the title @coderabbit Add comprehensive financial insights functionality Jan 31, 2026
@codecov-commenter
Copy link

codecov-commenter commented Jan 31, 2026

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 98.75000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.62%. Comparing base (8bbc3bc) to head (09d9547).

Files with missing lines Patch % Lines
src/lampyrid/services/insights.py 97.67% 2 Missing ⚠️
src/lampyrid/clients/firefly.py 98.50% 1 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #69      +/-   ##
==========================================
+ Coverage   97.53%   97.62%   +0.09%     
==========================================
  Files          17       19       +2     
  Lines        2841     3080     +239     
==========================================
+ Hits         2771     3007     +236     
- Misses         70       73       +3     
Flag Coverage Δ
integration 97.62% <98.75%> (+0.09%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@tests/integration/test_insights.py`:
- Around line 55-60: The snapshot expectations in
tests/integration/test_insights.py disagree with the seeded withdrawal amounts
in tests/conftest.py (seeded withdrawals sum to 145), causing failures; either
update the snapshot expected totals and derived net positions in test_insights
(search for expected 'total_expenses' and related summary/net position values)
to match the actual seeded sum (145 and corresponding currency_code) or change
the seed withdrawals in tests/conftest.py to the values used in the current
snapshots and then regenerate all snapshots to keep them consistent; ensure
every affected snapshot entry (expense totals, summary totals, and net position
assertions) is updated consistently across the file.
- Line 96: The snapshot assertions in tests/integration/test_insights.py include
hard-coded ID literals (e.g., the dict with 'id': '6', 'name': 'Test Expense 2')
which makes tests brittle; update those expected snapshots to either use a
flexible matcher like IsStr(min_length=1) for 'id' fields or substitute IDs from
the test fixtures (where available) instead of literal strings; apply the same
change to the other hard-coded ID occurrences called out in the review (lines
referenced: occurrences around the shown dict and the other listed locations) so
all expected data uses matchers or fixture-supplied IDs rather than fixed
numeric strings.
🧹 Nitpick comments (6)
src/lampyrid/models/lampyrid_models.py (1)

783-815: Consider adding date range validation.

The request models don't validate that start_date <= end_date. While the API might handle this gracefully, adding a model validator would provide clearer error messages to users.

📝 Optional: Add date validation
 class GetExpenseInsightRequest(BaseModel):
     """Request for expense insight analysis."""

     model_config = ConfigDict(extra='forbid')

     start_date: date = Field(..., description='Start date for the analysis period (YYYY-MM-DD)')
     end_date: date = Field(..., description='End date for the analysis period (YYYY-MM-DD)')
     # ... other fields ...
+
+    `@model_validator`(mode='after')
+    def validate_date_range(self):
+        """Ensure start_date is not after end_date."""
+        if self.start_date > self.end_date:
+            raise ValueError('start_date must be on or before end_date')
+        return self

Similar validation could be added to the other insight request models.

src/lampyrid/services/insights.py (2)

89-97: Minor: Redundant conditional check.

On line 96, the check if entries else 'USD' is redundant because the function returns early on line 93-94 when entries is empty.

🧹 Simplify the conditional
     def _get_total_and_currency(
         self, entries: list[InsightEntry] | list[TransferInsightEntry]
     ) -> tuple[float, str]:
         """Calculate total amount and get primary currency from entries."""
         if not entries:
             return 0.0, 'USD'
         total = sum(e.amount for e in entries)
-        currency = entries[0].currency_code if entries else 'USD'
+        currency = entries[0].currency_code
         return total, currency

261-278: Multi-currency aggregation may produce misleading results.

When the user has transactions in multiple currencies, this implementation sums all amounts regardless of currency and uses the first available currency code. This could produce confusing results (e.g., summing USD and EUR amounts).

Consider either:

  1. Documenting this limitation in the API response/docstrings
  2. Returning separate totals per currency
  3. Filtering to a single currency if the API supports it

This is acceptable for an initial implementation but worth noting for future enhancement.

tests/fixtures/insights.py (1)

23-30: Extract a shared month-range helper to reduce duplication.

The default date-range logic is repeated in all four factories; centralizing it will keep behavior consistent and lower maintenance overhead.

♻️ Proposed refactor
+def _resolve_month_range(start: date | None, end: date | None) -> tuple[date, date]:
+    """Resolve missing start/end dates to the current month."""
+    if start is None:
+        start = date.today().replace(day=1)
+    if end is None:
+        next_month = start.replace(day=28) + timedelta(days=4)
+        end = next_month.replace(day=1) - timedelta(days=1)
+    return start, end
+
 def make_get_expense_insight_request(
     start: date | None = None,
     end: date | None = None,
@@
 ) -> GetExpenseInsightRequest:
     """Create a GetExpenseInsightRequest for testing."""
-    if start is None:
-        # Default to current month
-        start = date.today().replace(day=1)
-    if end is None:
-        # Default to end of current month
-        next_month = start.replace(day=28) + timedelta(days=4)
-        end = next_month.replace(day=1) - timedelta(days=1)
+    start, end = _resolve_month_range(start, end)
@@
 def make_get_income_insight_request(
@@
 ) -> GetIncomeInsightRequest:
     """Create a GetIncomeInsightRequest for testing."""
-    if start is None:
-        # Default to current month
-        start = date.today().replace(day=1)
-    if end is None:
-        # Default to end of current month
-        next_month = start.replace(day=28) + timedelta(days=4)
-        end = next_month.replace(day=1) - timedelta(days=1)
+    start, end = _resolve_month_range(start, end)
@@
 def make_get_transfer_insight_request(
@@
 ) -> GetTransferInsightRequest:
     """Create a GetTransferInsightRequest for testing."""
-    if start is None:
-        # Default to current month
-        start = date.today().replace(day=1)
-    if end is None:
-        # Default to end of current month
-        next_month = start.replace(day=28) + timedelta(days=4)
-        end = next_month.replace(day=1) - timedelta(days=1)
+    start, end = _resolve_month_range(start, end)
@@
 def make_get_financial_summary_request(
@@
 ) -> GetFinancialSummaryRequest:
     """Create a GetFinancialSummaryRequest for testing."""
-    if start is None:
-        # Default to current month
-        start = date.today().replace(day=1)
-    if end is None:
-        # Default to end of current month
-        next_month = start.replace(day=28) + timedelta(days=4)
-        end = next_month.replace(day=1) - timedelta(days=1)
+    start, end = _resolve_month_range(start, end)

Also applies to: 48-55, 71-78, 93-100

tests/integration/test_insights.py (1)

16-23: Align request date range with the seed month to avoid boundary flakes.

Seeds are created using date.today() at session start, while tests compute date.today() at run time. If a session crosses a month boundary, tests can query a different month than the seeded data. Consider a shared fixture for the seed period or freezing time for these tests.

tests/conftest.py (1)

202-310: Consider gating seed transaction creation for parallel execution.

While pytest-xdist is not currently configured, the session-scoped _seed_transaction_ids initialization could cause duplicate seed data if parallel execution is added. Each xdist worker would independently create transactions in the shared Firefly III backend since if not _seed_transaction_ids: is worker-local. The existing cleanup fixture helps but won't prevent duplication across workers.

To future-proof, consider using pytest-xdist worker detection (via worker_id fixture) to create seed data only on worker 0, or tag transactions with a run-specific ID for filtering.

@RadCod3 RadCod3 changed the title Add comprehensive financial insights functionality Add financial insights mcp tools Jan 31, 2026
@RadCod3 RadCod3 force-pushed the feat/68-add-insight-tools branch from 56589b8 to 09d9547 Compare January 31, 2026 10:13
@RadCod3 RadCod3 merged commit eb2b12b into main Jan 31, 2026
5 checks passed
@RadCod3 RadCod3 deleted the feat/68-add-insight-tools branch January 31, 2026 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add insight tools

2 participants