Skip to content

Fix hold notifications falsely reporting availability for ODL titles (PP-3907)#3182

Merged
jonathangreen merged 2 commits intomainfrom
bugfix/hold-notification-wrong-pool-selection
Mar 31, 2026
Merged

Fix hold notifications falsely reporting availability for ODL titles (PP-3907)#3182
jonathangreen merged 2 commits intomainfrom
bugfix/hold-notification-wrong-pool-selection

Conversation

@jonathangreen
Copy link
Copy Markdown
Member

@jonathangreen jonathangreen commented Mar 27, 2026

Description

Fix a bug in best_lendable_pool() where hold notifications falsely reported availability for ODL titles. When a patron's hold became ready (position=0), the method ignored existing holds and selected a different pool based on availability metrics, causing checkout to fail with "no available copies."

Two issues were fixed:

  1. Missing hold check: After checking for existing loans, the method now checks for existing holds. If the patron has an existing hold on a pool, that pool is returned immediately.
  2. Broken pool comparison: The original or-based comparison allowed a pool with 0 available copies and a short queue to beat a pool with many available copies. Replaced with holistic logic that compares availability first, then hold queue ratio normalized by owned licenses.

Motivation and Context

Users were receiving "Your hold is available!" push notifications, but when they tapped to borrow the book, the checkout failed. This primarily affected titles with multiple LicensePools for the same work (from different collections), multiple licenses per LicensePool, and high circulation volume.

How Has This Been Tested?

  • New tests
  • Ran tests in CI
  • No mypy issues

Checklist

  • I have updated the documentation accordingly.
  • All new and existing tests passed.

@jonathangreen jonathangreen added the bug Something isn't working label Mar 27, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.28%. Comparing base (7394d16) to head (6e23d0e).
⚠️ Report is 11 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3182   +/-   ##
=======================================
  Coverage   93.27%   93.28%           
=======================================
  Files         496      496           
  Lines       45971    46004   +33     
  Branches     6290     6300   +10     
=======================================
+ Hits        42881    42915   +34     
  Misses       2002     2002           
+ Partials     1088     1087    -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Expand the hold loop to handle all active holds, not just ready ones.
Any non-expired hold now triggers early return, bypassing library
filtering and borrowing-policy checks. This prevents patrons from
ending up with holds on multiple pools for the same work.

Hold filtering (excluding expired ready holds) and position ordering
are pushed into the SQL query. The pools_with_holds tiebreaker is
removed since holds are now resolved via early return.

Tests are renamed from test_borrow_* to test_best_lendable_pool_*,
simplified to use default_patron directly instead of Flask request
context, and DRYed up with a second_pool() helper and a
best_lendable_pool partial on LoanFixture.
@jonathangreen jonathangreen force-pushed the bugfix/hold-notification-wrong-pool-selection branch from 3f1a7cc to 98fc7d1 Compare March 30, 2026 19:07
The expired-hold test used identical metrics for both pools, making the
selected pool depend on nondeterministic iteration order from the DB
query. Give the pools distinct hold-queue ratios so normal selection
deterministically picks the expected pool.
@jonathangreen jonathangreen marked this pull request as ready for review March 30, 2026 19:34
@jonathangreen jonathangreen requested a review from a team March 30, 2026 19:34
Copy link
Copy Markdown
Contributor

@tdilauro tdilauro left a comment

Choose a reason for hiding this comment

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

🎉

@jonathangreen jonathangreen merged commit f4338f5 into main Mar 31, 2026
20 checks passed
@jonathangreen jonathangreen deleted the bugfix/hold-notification-wrong-pool-selection branch March 31, 2026 13:42
jonathangreen added a commit that referenced this pull request Mar 31, 2026
## Description

Add a warning-level log when every license in a pool fails checkout in
`OPDS2WithODLApi._checkout()`.

This targets the scenario where the system believes copies are available
but the distributor disagrees, the patron gets a "hold available"
notification, but checkout fails because no licenses can actually be
checked out.

## Motivation and Context

Patrons are receiving "Your hold is available!" push notifications but
when they open the app to borrow the book, it fails. PR #3182 fixes a
known bug in `best_lendable_pool()`, but I'm not confident it covers all
cases. This logging will help diagnose the root cause if the issue
persists (PP-3907).

## How Has This Been Tested?

- Logging-only change, exercised by existing tests
- All pre-commit hooks pass

## Checklist

- [x] I have updated the documentation accordingly.
- [x] All new and existing tests passed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants