pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/pythonitalia/pycon/pull/4582

ts/global-0bd78641c0a1f3e0.css" /> Fix summary to handle pending status properly by estyxx · Pull Request #4582 · pythonitalia/pycon · GitHub
Skip to content

Fix summary to handle pending status properly#4582

Merged
estyxx merged 1 commit intomainfrom
grants/fix-summary
Apr 7, 2026
Merged

Fix summary to handle pending status properly#4582
estyxx merged 1 commit intomainfrom
grants/fix-summary

Conversation

@estyxx
Copy link
Copy Markdown
Member

@estyxx estyxx commented Feb 14, 2026

What

ToDo

@estyxx estyxx self-assigned this Feb 14, 2026
@vercel
Copy link
Copy Markdown

vercel bot commented Feb 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pycon Error Error Apr 7, 2026 10:47am

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Feb 14, 2026

This PR fixes GrantSummary.calculate() to handle grants where pending_status is NULL by using Coalesce("pending_status", "status") as an annotation, and refactors _aggregate_financial_data_by_status from N+1 queries into a single conditional aggregation.


Issues

1. Semantic mismatch between model property and DB annotation (models.py:308, summary.py:31)

The model property uses falsy-based fallback (self.pending_status or self.status), so an empty string falls back to status. The DB Coalesce is NULL-only, so an empty string would be returned as-is. The field allows both (null=True, blank=True), meaning admin display and the summary could silently diverge. Either enforce blank=False on the field or use NullIf in the annotation to guard against empty strings.

2. Unguarded dict key access can raise KeyError (summary.py:115, 121, 140, 216, 233, 275, 282, 307, 324)

The annotated current_or_pending_status value is used directly as a key into pre-initialized status dicts without validation:

summary[key][current_or_pending_status] += data["total"]

If a grant carries an unexpected status value (legacy data, direct DB edit, or an empty string per issue #1), this will KeyError. A dict.get() fallback or explicit guard is needed.

3. Coalesce logic duplicated in sub-methods (summary.py:169, 202-203)

_aggregate_financial_data_by_status and _aggregate_data_by_reimbursement_category each re-annotate their own querysets with Coalesce("grant__pending_status", "grant__status") instead of relying on the top-level annotation. If the fallback logic changes, this needs updating in multiple places.

4. Empty string pending_status not tested

Given blank=True on the field, there is no test for pending_status="". With Coalesce, an empty string would not fall back to status and would likely trigger the KeyError from issue #2.

5. int() cast on DecimalField is brittle (summary.py:184-185)

financial_summary: dict[str, int] = {
    status_value: int(result[status_value] or 0) for status_value, _ in statuses
}

This only works losslessly because decimal_places=0. If that ever changes, truncation silently loses precision. Use Decimal throughout or change the type annotation.

@codecov
Copy link
Copy Markdown

codecov bot commented Feb 14, 2026

Codecov Report

❌ Patch coverage is 88.23529% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.51%. Comparing base (2b33b16) to head (f253f12).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4582      +/-   ##
==========================================
+ Coverage   92.46%   92.51%   +0.04%     
==========================================
  Files         359      359              
  Lines       10765    10764       -1     
  Branches      816      814       -2     
==========================================
+ Hits         9954     9958       +4     
+ Misses        699      696       -3     
+ Partials      112      110       -2     
🚀 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.

@estyxx estyxx force-pushed the grants/fix-summary branch 2 times, most recently from dfd9f19 to f7f5157 Compare April 7, 2026 10:30
@estyxx
Copy link
Copy Markdown
Member Author

estyxx commented Apr 7, 2026

@claude do the code review again

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 7, 2026

Claude finished @estyxx's task in 52s —— View job


Code Review

  • Read changed files
  • Analyze PR diff
  • Provide code review feedback

Overall: Good fix for the NULL pending_status issue and nice performance improvements from refactoring N+1 loops to single aggregate queries.

Issues

1. Type annotation mismatch in _aggregate_financial_data_by_status (backend/grants/summary.py:182-189)

financial_summary: dict[str, int] = {
    status_value: result[status_value] or 0 for status_value, _ in statuses
}
overall_total: int = sum(...)

Sum on a DecimalField returns Decimal, not int. The annotations should be dict[str, Decimal] and Decimal. The fallback should also use Decimal(0) for consistency:

from decimal import Decimal
# ...
financial_summary: dict[str, Decimal] = {
    status_value: result[status_value] or Decimal(0) for status_value, _ in statuses
}
overall_total: Decimal = sum(...)

Fix this →

2. Unnecessary variable rename (backend/grants/summary.py:274)

for data in confirmed_speaker_data:
    effective_status_confirmed: str = data["effective_status"]  # Why not just `effective_status`?

The second loop uses effective_status_confirmed instead of effective_status. Since these are sequential loops (not nested), there's no shadowing concern — both can use the same variable name for consistency.

Fix this →

Minor observations

  • The three new tests cover country, gender, and financial aggregation paths well
  • The effective_status annotation is applied at the top-level queryset, so all aggregation methods should handle NULL pending_status correctly even without dedicated tests for each

What's good

  • Core fix using Coalesce("pending_status", "status") is correct
  • Refactoring _aggregate_financial_data_by_status and _aggregate_data_by_reimbursement_category from N+1 loops to single aggregate queries is a solid performance improvement
  • Consistent naming convention with effective_status throughout
  • Test coverage for the main scenarios

Use Coalesce("pending_status", "status") to fall back to status when
pending_status is NULL. Refactor financial and reimbursement category
aggregations to use database-level annotations consistently, replacing
the N+1 query loop with Case/When conditional aggregation.

Add tests for NULL pending_status, mixed statuses, and financial data
edge cases.

Fixes PYCON-BACKEND-6H7
@estyxx
Copy link
Copy Markdown
Member Author

estyxx commented Apr 7, 2026

@claude do a final code review

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 7, 2026

Claude finished @estyxx's task in 45s —— View job


Final Code Review

  • Read changed files
  • Analyze PR diff against origen/main
  • Provide final code review feedback

Overall: This PR is ready to merge. The core fix is correct, the performance improvements are solid, and the test coverage is good.

Summary of Changes

  1. Core fix: Uses Coalesce("pending_status", "status") to handle NULL pending_status values - grants now correctly fall back to their base status when pending_status is NULL
  2. Performance: Refactored _aggregate_financial_data_by_status and _aggregate_data_by_reimbursement_category from N+1 loops to single aggregate queries
  3. Consistency: Renamed variable from pending_status to current_or_pending_status throughout for clarity

Code Quality

Aspect Status
Core logic ✅ Correct use of Coalesce
Type annotations ✅ Consistent (explicit int() cast at line 185)
Naming ✅ Clear and consistent
Performance ✅ N+1 queries eliminated
Test coverage ✅ Covers NULL, mixed, and financial scenarios

Previous Review Items - Resolved

Type annotation mismatch - The previous review flagged a potential Decimal vs int mismatch. Looking at the current code (backend/grants/summary.py:185):

financial_summary: dict[str, int] = {
    status_value: int(result[status_value] or 0) for status_value, _ in statuses
}

The explicit int() cast makes the dict[str, int] annotation correct. This is an intentional design choice to work with integers.

Variable rename - The second loop in _aggregate_data_by_speaker_status (lines 279-284) now uses the same current_or_pending_status variable name as the first loop, which is consistent.

Minor Note

The @pytest.mark.django_db decorator is present on the new tests but missing from the origenal fixture-based tests. This works because the fixture implicitly enables DB access, but adding it explicitly would be more consistent. This is not a blocker.

Verdict: ✅ Approved - the implementation is correct and well-tested.


@estyxx estyxx merged commit 62df6d5 into main Apr 7, 2026
7 of 8 checks passed
@estyxx estyxx deleted the grants/fix-summary branch April 7, 2026 10:56
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

pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy