feat(spp_metric_service): breakdown expansion + SQL column support (re-land from #76)#279
feat(spp_metric_service): breakdown expansion + SQL column support (re-land from #76)#279gonzalesedwin1123 wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces demographic breakdown expansion and SQL column compilation support for metric disaggregation. Key changes include updating model names, refining the age group dimension to align with UNICEF/WHO buckets, automatically expanding groups to members for individual-level dimensions, and adding a to_sql_column method to compile dimensions into SQL expressions. The reviewer feedback focuses on performance and code quality optimizations, such as using recordset subtraction instead of lambda filtering, batch-fetching membership IDs to prevent N+1 queries, checking _fields on Odoo recordsets instead of using hasattr, guarding against falsy values early, and removing redundant SQL parameter wrapping.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| group_ids = records.filtered("is_group").ids | ||
| individual_ids = set(records.filtered(lambda r: not r.is_group).ids) |
There was a problem hiding this comment.
Using filtered with a lambda function can be slow and is less idiomatic in Odoo. We can use recordset subtraction (records - groups) to get the individual records, which is much faster and cleaner.
| group_ids = records.filtered("is_group").ids | |
| individual_ids = set(records.filtered(lambda r: not r.is_group).ids) | |
| groups = records.filtered("is_group") | |
| group_ids = groups.ids | |
| individual_ids = set((records - groups).ids) |
| for membership in memberships: | ||
| individual_ids.add(membership.individual.id) |
There was a problem hiding this comment.
| # Odoo recordset (Many2one). Empty recordset means the field is unset. | ||
| if not value: | ||
| return self.default_value or "unknown" | ||
| if hasattr(value, "code") and value.code: |
There was a problem hiding this comment.
Using hasattr(value, "code") on an Odoo recordset is inefficient and can be unreliable because it triggers dynamic attribute lookup. It is safer and much faster to check if the field exists in the model's _fields dictionary.
| if hasattr(value, "code") and value.code: | |
| if "code" in value._fields and value.code: |
| :param raw_value: The raw dimension value (typically a code string) | ||
| :returns: display_name or None if not found | ||
| """ | ||
| # Only handle simple field paths (one segment, no dotted traversal |
There was a problem hiding this comment.
If raw_value is falsy (e.g., None or False), performing a database search comodel.search([("code", "=", raw_value)]) is unnecessary and inefficient. We should guard against falsy values early in the method.
| # Only handle simple field paths (one segment, no dotted traversal | |
| if not raw_value: | |
| return None | |
| # Only handle simple field paths (one segment, no dotted traversal |
| result = SQLColumnResult( | ||
| expression=SQL( | ||
| "CASE WHEN %s.%s = FALSE THEN %s ELSE %s END", | ||
| SQL.identifier(alias), | ||
| SQL.identifier("is_group"), | ||
| result.expression, | ||
| SQL("%s", default), | ||
| ), | ||
| joins=result.joins, | ||
| alias_counter=result.alias_counter, | ||
| ) |
There was a problem hiding this comment.
Wrapping default in SQL("%s", default) is redundant and inconsistent with how parameters are passed elsewhere in this file. You can pass default directly as a parameter to the parent SQL object.
| result = SQLColumnResult( | |
| expression=SQL( | |
| "CASE WHEN %s.%s = FALSE THEN %s ELSE %s END", | |
| SQL.identifier(alias), | |
| SQL.identifier("is_group"), | |
| result.expression, | |
| SQL("%s", default), | |
| ), | |
| joins=result.joins, | |
| alias_counter=result.alias_counter, | |
| ) | |
| result = SQLColumnResult( | |
| expression=SQL( | |
| "CASE WHEN %s.%s = FALSE THEN %s ELSE %s END", | |
| SQL.identifier(alias), | |
| SQL.identifier("is_group"), | |
| result.expression, | |
| default, | |
| ), | |
| joins=result.joins, | |
| alias_counter=result.alias_counter, | |
| ) |
| result = SQLColumnResult( | ||
| expression=SQL( | ||
| "CASE WHEN %s.%s = TRUE THEN %s ELSE %s END", | ||
| SQL.identifier(alias), | ||
| SQL.identifier("is_group"), | ||
| result.expression, | ||
| SQL("%s", default), | ||
| ), | ||
| joins=result.joins, | ||
| alias_counter=result.alias_counter, | ||
| ) |
There was a problem hiding this comment.
Wrapping default in SQL("%s", default) is redundant and inconsistent with how parameters are passed elsewhere in this file. You can pass default directly as a parameter to the parent SQL object.
| result = SQLColumnResult( | |
| expression=SQL( | |
| "CASE WHEN %s.%s = TRUE THEN %s ELSE %s END", | |
| SQL.identifier(alias), | |
| SQL.identifier("is_group"), | |
| result.expression, | |
| SQL("%s", default), | |
| ), | |
| joins=result.joins, | |
| alias_counter=result.alias_counter, | |
| ) | |
| result = SQLColumnResult( | |
| expression=SQL( | |
| "CASE WHEN %s.%s = TRUE THEN %s ELSE %s END", | |
| SQL.identifier(alias), | |
| SQL.identifier("is_group"), | |
| result.expression, | |
| default, | |
| ), | |
| joins=result.joins, | |
| alias_counter=result.alias_counter, | |
| ) |
- recordset subtraction instead of lambda filter for individuals - batch individual ids from memberships (avoids N+1) - check _fields membership instead of hasattr on recordsets - early return for falsy raw_value before code search
|
gemini-code-assist disposition: Applied (commit 778e037): recordset subtraction for individuals, batched Deferred: the two |
|
Thank you for the detailed disposition. The changes you've implemented, particularly the shift to |
Re-lands the spp_metric_service portion of reverted PR #76 (revert: #271). Wave 2 — stacked on #275 (spp_cel_domain); merge that first. The base is set to
reland/cel-domainso this diff shows only spp_metric_service; GitHub retargets to 19.0 when #275 merges.Summary
Verification
./spp t spp_metric_serviceon an integration state with feat(spp_cel_domain): SQL CASE compiler, read-only smart-op lookup, translator cache tests (re-land from #76) #275 merged: 161 passed, 0 failed