perf: batch broker enrichment lookups concurrently (#986)#1005
Conversation
Per-row instrument and quote lookups in broker comparison, portfolio, dividend/loan, option-position, and open-order tools issued one network round trip per row in serial. Large accounts or broad symbol comparisons were therefore prone to broker rate-limit hits, slow MCP responses, and timeout paths. This change adds a small ``batch_fetch`` helper (``gather_bounded`` plus ``dedupe_preserving_order``) and rewires the six flagged call sites: - broker_comparison: Robinhood and Schwab per-symbol quotes now fan out via ``asyncio.gather`` with bounded concurrency - robinhood_account.get_positions: dedupes instrument URLs and resolves symbols concurrently - robinhood_dividend.get_dividends / get_stock_loan_payments: per-row instrument lookups deduped and gathered through a shared module helper - options.positions.get_open_option_positions_with_details: per-position option-instrument fetches deduped by option_id and gathered - robinhood_trading.get_all_open_stock_orders: per-order symbol lookups deduped by instrument URL and gathered Adds ``test_batch_fetch.py`` covering the helpers and a regression test per call site for duplicate keys plus a 50-row payload exercise of the concurrency cap. Two existing tests were tightened to map URL → symbol via a callable side_effect rather than relying on serial call order, which is no longer guaranteed under gather. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Claude finished @jwesleye's task —— View job Code Review for PR #986: Batch Broker Enrichment LookupsTodo List:
🎯 Overall Assessment: EXCELLENT ⭐⭐⭐⭐⭐This is a high-quality performance optimization that addresses a real N+1 query problem affecting broker API calls. The implementation is well-architected, thoroughly tested, and follows excellent software engineering practices. ✅ Code Quality & Best PracticesExcellent Architecture:
Strong Type Safety:
Good Error Handling:
✅ Performance ConsiderationsSignificant Improvements:
Well-Tuned Concurrency:
✅ Security AssessmentNo security concerns identified. The changes:
✅ Test Coverage - ExceptionalComprehensive Unit Tests (14 new tests in
|
Closes #986
Changes
tools/batch_fetch.pyhelper:gather_bounded(bounded concurrent gather withreturn_exceptions=True) anddedupe_preserving_order. Helpers do not callexecute_with_retrythemselves so existing module-level mocks keep working.broker_comparison_tools: Robinhood and Schwab per-symbol quote loops now fan out viagather_bounded.robinhood_account_tools.get_positions: dedupes instrument URLs, resolves symbols concurrently, builds a URL→symbol map.robinhood_dividend_tools.get_dividends/get_stock_loan_payments: shared module-local_resolve_instrumentshelper deduplicates and fans out instrument lookups; both functions now share the same URL→instrument map within a request.options/positions.get_open_option_positions_with_details:_extract_option_id+_resolve_option_instrumentsdedupes byoption_idand gathers fetches.robinhood_trading_tools.get_all_open_stock_orders: dedupes instrument URLs and gathers symbol lookups.tests/unit/test_batch_fetch.py: helper unit tests + one regression per flagged call site for duplicate keys, plus a 50-row payload that exercises the concurrency cap.test_get_positions_success,test_stock_loan_payments_success) to map URL → symbol via callableside_effect, since concurrent gather no longer guarantees serial call order.Test plan
uv run pytest tests/unit/test_batch_fetch.py -q --tb=short— 14 passeduv run pytest tests/unit/ -q --tb=line— 1386 passed, 69 skippeduv run ruff check src/open_stocks_mcp/tools/ tests/unit/test_batch_fetch.py tests/unit/test_account_tools.py tests/unit/test_analytics_tools.py— cleanuv run ruff format --check src/open_stocks_mcp/tools/ tests/unit/test_batch_fetch.py tests/unit/test_account_tools.py tests/unit/test_analytics_tools.py— cleanuv run mypy src/— no issues found in 88 source files