Skip to content

perf(spp_programs): replace cycle computed fields with SQL aggregation#119

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

perf(spp_programs): replace cycle computed fields with SQL aggregation#119
kneckinator wants to merge 1 commit into19.0from
ken/optimize_spp_program_phase_3

Conversation

@kneckinator
Copy link
Contributor

Summary

Replace Python iteration over entitlement recordsets with SQL aggregation for 4 cycle computed fields, eliminating the need to load all entitlements into memory just to compute summary statistics.

Changes

Method Before After
_compute_total_amount sum(ent.initial_amount for ent in rec.entitlement_ids) — loads every entitlement record SELECT cycle_id, SUM(initial_amount) ... GROUP BY cycle_id
_compute_total_entitlements_count len(rec.entitlement_ids) + len(rec.inkind_entitlement_ids) — loads both recordsets Two SELECT cycle_id, COUNT(*) ... GROUP BY cycle_id queries
_compute_show_approve_entitlement any(ent.state == "pending_validation" for ent in ...) — loads all, iterates in Python SELECT DISTINCT cycle_id ... WHERE state = 'pending_validation' with UNION for inkind
_compute_all_entitlements_approved all(ent.state == "approved" for ent in ...) — loads all, iterates in Python Two set queries: cycles with entitlements vs cycles with non-approved entitlements

All SQL queries accept multiple cycle IDs (WHERE cycle_id IN %s) so Odoo's batch recomputation is efficient across cycles.

Context

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

Test Plan

  • ./scripts/test_single_module.sh spp_programs passes
  • 13 new tests in test_cycle_computed_fields.py covering:
    • Empty cycles, single entitlements, mixed states
    • Multi-cycle batching (verifies SQL handles IN clause correctly)
    • All four computed fields independently tested

total_amount, total_entitlements_count, show_approve_entitlements_button,
and all_entitlements_approved now use SQL queries instead of iterating
over loaded entitlement recordsets. This avoids loading all entitlements
into memory just to compute summary statistics.
@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 significantly improves performance in the spp_programs module by refactoring four cycle computed fields. It replaces inefficient Python iterations over large recordsets with optimized SQL aggregation queries, thereby reducing memory usage and computation time, especially for batch operations. This change is part of a larger performance optimization effort.

Highlights

  • Optimized _compute_total_amount: Replaced Python sum() over entitlements with a direct SQL SUM() aggregation for calculating the total amount.
  • Optimized _compute_total_entitlements_count: Replaced Python len() calls with two SQL COUNT() aggregations for cash and in-kind entitlements to count total entitlements.
  • Optimized _compute_show_approve_entitlement: Replaced Python any() iteration with a UNION of SELECT DISTINCT cycle_id SQL queries to efficiently find cycles with pending validation.
  • Optimized _compute_all_entitlements_approved: Replaced Python all() iteration with SQL queries to identify cycles with any entitlements and cycles with unapproved entitlements, then performing set comparisons.
  • Batch Processing Support: All new SQL queries are designed to efficiently handle multiple cycle IDs using WHERE cycle_id IN %s, supporting Odoo's batch recomputation.
  • New Test Coverage: Added comprehensive unit tests for the optimized computed fields, covering various scenarios including empty cycles, mixed states, and multi-cycle batching.
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 provides a significant performance improvement by replacing Python-based iterations with more efficient SQL aggregations for several computed fields. The changes are well-implemented and accompanied by a comprehensive new test suite that covers various scenarios, including multi-cycle batching. My review includes one suggestion to adopt a more robust and standardized pattern for these computed fields to enhance readability and ensure correct behavior with new or empty recordsets.

Comment on lines +241 to +256
if not self.ids:
for rec in self:
rec.total_amount = 0
return
self.env.cr.execute(
"""
SELECT cycle_id, COALESCE(SUM(initial_amount), 0)
FROM spp_entitlement
WHERE cycle_id IN %s
GROUP BY cycle_id
""",
(tuple(self.ids),),
)
totals = dict(self.env.cr.fetchall())
for rec in self:
rec.total_amount = sum(entitlement.initial_amount for entitlement in rec.entitlement_ids)
rec.total_amount = totals.get(rec.id, 0)

Choose a reason for hiding this comment

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

medium

The current implementation for handling new records (without IDs) is a bit complex. A more robust and common pattern in Odoo for computed fields that use SQL is to first set a default value for all records, then filter for records with IDs to perform the query. This makes the code clearer and safer, especially for empty recordsets. This same feedback applies to _compute_total_entitlements_count, _compute_show_approve_entitlement, and _compute_all_entitlements_approved as well.

Suggested change
if not self.ids:
for rec in self:
rec.total_amount = 0
return
self.env.cr.execute(
"""
SELECT cycle_id, COALESCE(SUM(initial_amount), 0)
FROM spp_entitlement
WHERE cycle_id IN %s
GROUP BY cycle_id
""",
(tuple(self.ids),),
)
totals = dict(self.env.cr.fetchall())
for rec in self:
rec.total_amount = sum(entitlement.initial_amount for entitlement in rec.entitlement_ids)
rec.total_amount = totals.get(rec.id, 0)
self.total_amount = 0
recs_with_ids = self.filtered('id')
if not recs_with_ids:
return
# Query for records with IDs
self.env.cr.execute(
"""
SELECT cycle_id, COALESCE(SUM(initial_amount), 0)
FROM spp_entitlement
WHERE cycle_id IN %s
GROUP BY cycle_id
""",
(tuple(recs_with_ids.ids),),
)
totals = dict(self.env.cr.fetchall())
for rec in recs_with_ids:
rec.total_amount = totals.get(rec.id, 0)

@codecov
Copy link

codecov bot commented Mar 19, 2026

Codecov Report

❌ Patch coverage is 66.66667% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.96%. Comparing base (1a91296) to head (93c6b59).
⚠️ Report is 6 commits behind head on 19.0.

Files with missing lines Patch % Lines
spp_programs/models/cycle.py 66.66% 12 Missing ⚠️

❌ Your patch check has failed because the patch coverage (66.66%) is below the target coverage (70.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0     #119      +/-   ##
==========================================
- Coverage   70.14%   69.96%   -0.19%     
==========================================
  Files         739      783      +44     
  Lines       43997    46622    +2625     
==========================================
+ Hits        30863    32619    +1756     
- Misses      13134    14003     +869     
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 45.74% <66.66%> (+0.22%) ⬆️
spp_security 66.66% <ø> (ø)

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

Files with missing lines Coverage Δ
spp_programs/models/cycle.py 46.57% <66.66%> (+2.82%) ⬆️

... and 44 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