Skip to content

perf(spp_programs): fetch fund balance once per approval batch#127

Open
kneckinator wants to merge 1 commit into19.0from
ken/optimize_spp_program_phase_4
Open

perf(spp_programs): fetch fund balance once per approval batch#127
kneckinator wants to merge 1 commit into19.0from
ken/optimize_spp_program_phase_4

Conversation

@kneckinator
Copy link
Contributor

Summary

  • Fetch fund balance once per approval batch instead of per entitlement
  • Track remaining balance via running total in Python during the approval loop
  • Applies to both DefaultCashEntitlementManager and SppCashEntitlementManager

Changes

File Change
managers/entitlement_manager_base.py Move check_fund_balance() call before the loop, replace fund_balance with remaining_balance computed from running total
managers/entitlement_manager_cash.py Same optimization
tests/test_fund_balance.py 4 tests: verify single call via mock, insufficient fund early stop, running total correctness

Before

for rec in entitlements:  # N entitlements
    fund_balance = self.check_fund_balance(...)  # 2 SQL queries each = 2N queries

After

fund_balance = self.check_fund_balance(...)  # 2 SQL queries, once
for rec in entitlements:
    remaining_balance = fund_balance - amt  # Python arithmetic

Context

Phase 4 of 9 in the spp_programs performance optimization effort. Independent of other phases.

Test Plan

  • ./scripts/test_single_module.sh spp_programs passes
  • Verify approval still stops when funds are insufficient (test_fund_balance_insufficient_stops_early)
  • Verify all entitlements approved when funds are sufficient (test_fund_balance_running_total_correct)

Both DefaultCashEntitlementManager and SppCashEntitlementManager called
check_fund_balance() (2 SQL queries) per entitlement inside the approval
loop. Now the balance is fetched once before the loop and tracked via a
running total in Python.
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a key performance enhancement to the entitlement approval process within the spp_programs module. By centralizing the fund balance check to a single operation per batch and managing the running balance in application memory, the change aims to reduce database load and improve the efficiency of approving entitlements. This is a targeted optimization as part of a larger performance initiative.

Highlights

  • Performance Optimization: The fund balance is now fetched once per approval batch instead of per individual entitlement, significantly reducing database queries.
  • In-Memory Balance Tracking: Remaining balance is tracked via a running total in Python during the approval loop, avoiding repeated database calls.
  • Broad Applicability: These optimizations have been applied to both DefaultCashEntitlementManager and SppCashEntitlementManager.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a significant performance optimization by fetching the fund balance only once per approval batch, which is a great improvement. The changes are implemented correctly in both DefaultCashEntitlementManager and SppCashEntitlementManager, and are well-covered by new tests. However, I've found a critical issue where the new logic can cause a crash if an empty list of entitlements is processed. I've provided suggestions to fix this. I've also suggested adding a test case to cover this scenario.

entitlements.mapped("journal_id.currency_id")

# Fetch fund balance once for the whole batch instead of per entitlement
fund_balance = self.check_fund_balance(entitlements[0].cycle_id.program_id.id)

Choose a reason for hiding this comment

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

critical

This line will raise an IndexError if entitlements is an empty list. You should handle this case to prevent a crash. A simple way is to use a conditional expression to avoid accessing the list if it's empty.

Suggested change
fund_balance = self.check_fund_balance(entitlements[0].cycle_id.program_id.id)
fund_balance = self.check_fund_balance(entitlements[0].cycle_id.program_id.id) if entitlements else 0.0

entitlements.mapped("journal_id.currency_id")

# Fetch fund balance once for the whole batch instead of per entitlement
fund_balance = self.check_fund_balance(entitlements[0].cycle_id.program_id.id)

Choose a reason for hiding this comment

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

critical

This line will raise an IndexError if entitlements is an empty list. You should handle this case to prevent a crash. A simple way is to use a conditional expression to avoid accessing the list if it's empty.

Suggested change
fund_balance = self.check_fund_balance(entitlements[0].cycle_id.program_id.id)
fund_balance = self.check_fund_balance(entitlements[0].cycle_id.program_id.id) if entitlements else 0.0

Comment on lines +9 to +15
class TestFundBalanceOptimization(TransactionCase):
"""Test that fund balance is fetched once per approval batch, not per entitlement.

The approve_entitlements methods previously called check_fund_balance()
(2 SQL queries) inside the per-entitlement loop. Now the balance is
fetched once and tracked via a running total in Python.
"""

Choose a reason for hiding this comment

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

medium

It's great that you've added tests for this optimization. However, there's a critical edge case that is not covered: when approve_entitlements is called with an empty list of entitlements. The current implementation would crash with an IndexError. I've left comments on the implementation files with a fix. It would be beneficial to add a test case to cover this scenario and prevent future regressions.

You could add a test like this:

    def test_approve_entitlements_with_empty_list(self):
        """approve_entitlements should not fail with an empty list of entitlements."""
        manager = self._create_default_manager()
        # For DefaultCashEntitlementManager
        state_err, message = manager.approve_entitlements(self.env["spp.entitlement"])
        self.assertEqual(state_err, 0)
        self.assertEqual(message, "")

        # For SppCashEntitlementManager
        cash_manager = self._create_cash_manager()
        state_err, message = cash_manager.approve_entitlements(self.env["spp.entitlement"])
        self.assertEqual(state_err, 0)
        self.assertEqual(message, "")

@codecov
Copy link

codecov bot commented Mar 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.02%. Comparing base (1a91296) to head (e93ca4d).
⚠️ Report is 40 commits behind head on 19.0.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0     #127      +/-   ##
==========================================
- Coverage   70.14%   70.02%   -0.13%     
==========================================
  Files         739      783      +44     
  Lines       43997    46599    +2602     
==========================================
+ Hits        30863    32630    +1767     
- Misses      13134    13969     +835     
Flag Coverage Δ
spp_api_v2 79.96% <ø> (ø)
spp_api_v2_change_request 66.66% <ø> (ø)
spp_api_v2_cycles 71.12% <ø> (ø)
spp_api_v2_data 64.41% <ø> (ø)
spp_api_v2_entitlements 70.19% <ø> (ø)
spp_api_v2_gis 71.52% <ø> (ø)
spp_api_v2_products 66.27% <ø> (ø)
spp_api_v2_service_points 70.94% <ø> (ø)
spp_api_v2_simulation 71.12% <ø> (ø)
spp_api_v2_vocabulary 57.26% <ø> (ø)
spp_audit 64.19% <ø> (ø)
spp_base_common 90.26% <ø> (ø)
spp_case_entitlements 97.61% <ø> (ø)
spp_case_programs 97.14% <ø> (ø)
spp_cel_event 85.11% <ø> (?)
spp_claim_169 58.11% <ø> (?)
spp_dci_client_dr 55.87% <ø> (?)
spp_dci_client_ibr 60.17% <ø> (?)
spp_programs 46.05% <100.00%> (+0.53%) ⬆️
spp_security 66.66% <ø> (ø)

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

Files with missing lines Coverage Δ
...ograms/models/managers/entitlement_manager_base.py 49.78% <100.00%> (+12.18%) ⬆️
...ograms/models/managers/entitlement_manager_cash.py 59.69% <100.00%> (+0.20%) ⬆️

... and 45 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

1 participant