Repository navigation
Prototype: Add Nearby Books carousel Lit component (#13159) - #13526
Conversation
de5df7d to
e4a16d7
Compare
e4a16d7 to
2fa6561
Compare
…o avoid template import restriction
|
@lokesh could you review this |
|
@Sravan1011 Thanks for the writeup. Directionally this is a good idea, but I want to redirect the effort before you build more. On the component. I'd push back on the original issue's lean toward creating a new Lit component. We have a primitive UI For now we should go through macros.QueryCarousel / books/custom_carousel.html instead. You get safe_mode, lazy loading, CacheableMacro, the analytics key, $_() on the strings and the real card for free. RawQueryCarousel.html already takes a books_data param for pre-fetched docs (plugins/openlibrary/partials.py:514 is the precedent), so the shape I'd want is a Python helper that owns the neighbour query, feeding the existing carousel. That deletes almost all 289 lines of OlNearbyBooks.js. Which leaves the part actually worth prototyping. The neighbour logic needs work. Issues1. Wrong anchor — survives the switch A DDC number is a shelf position. For "show me what's next to this book" to mean anything, two numbers have to agree: the spot you're standing on, and the number the shelf is sorted by. The shelf is sorted by Solr's ddc_sort. That value lives on the work, and it's picked by gathering the DDCs of every edition of that work and taking the longest string (work.py:492-503). The PR ignores that and computes its own number from the one edition you happen to be viewing. So take a work whose editions carry 813, 813.52, and 813.52 F. Solr files that work on the shelf at 813.52 F. You open the edition catalogued as plain 813, and the PR asks "what sits either side of 813?" It gets a real answer, just not this book's neighbours. You're standing in the wrong aisle. The fix is to stop computing anything: read the work's indexed ddc_sort straight from Solr and anchor on that. Then the anchor is on the same axis as the sort by definition, and normalize_ddc / choose_sorting_ddc leave the request path entirely. 2. Inclusive bounds — survives the switch The two queries are "everything up to and including X" and "everything from X onward." X is a member of both. If X is a rare classification with three works, nobody notices. But 813.54 (20th-century American fiction) has tens of thousands of works sitting at exactly that value. So:
Neither query ever escapes the bucket. You never see 813.53 or 813.6. And because Solr breaks ties by internal doc id, reversing the descending list doesn't line it up with the ascending one, so the two sets are different arbitrary tens. Dedupe by key doesn't merge them. Net result: twenty essentially random books that share one number, presented as a shelf. It looks like it's working, which is exactly why it needs catching now. And it fails hardest on the classifications the most books have. The fix has two parts, and the second is a design decision rather than a bug fix:
-- Let me know if you have any questions on this feedback. Once we work through it, I'll get his up on a testing server so we can see what it looks like with real data. Thanks! |
|
@Sravan1011 let me know if you have any questions on my feedback and if you are still interested on iterating on this PR |
|
Ya ya Im |
|
I'm working on this will update in some time, I have done it just need to push those |
ca7b203 to
aac32c7
Compare
|
Thanks for the detailed review -- confirmed working locally now, screenshot below. Pushed a fix for all the blocking items: 1. Solr syntax error in the "after" query. Fixed -- switched to Solr's exclusive 2. Carousel ignored the fetched books / title-link & load-more pointed at an unrelated empty query. Fixed at the root in 3. Extra Solr calls on every book-page render. Fixed -- 4. Non-numeric DDCs ( 5. PR description. Updated -- no more references to the On your earlier feedback (wrong anchor / inclusive bounds): both addressed as part of the above --
ScreenshotNearby Books carousel rendering on Happy to make any further adjustments -- let me know if you'd like anything else changed before this goes up on the testing server. |
|
@lokesh can you check now |
Port NearbyBooksPartial onto the Jinja carousel (RawQueryCarousel and custom_carousel were converted upstream in internetarchive#13590).
- Validate work_key/language/limit with pydantic patterns and bounds, and escape both values before they reach Solr - Read the anchor ddc_sort through /select (docValues) instead of /get - Memoize gather_nearby_books_async for 5 minutes; Solr failures return None so they are not cached - Only render the placeholder when a loaded edition has a numeric DDC - Reuse lazy-carousel.js and RawQueryCarouselPlaceholder.html.jinja via a config 'partial' key; drop nearby-books.js, the Templetor macro and the duplicated CSS - Drop the no-op convert_iso_to_marc call and the stray helpers.py change
The heading class painted every heading in link blue, including the plain Nearby Books heading and the home page's Welcome heading. Move the link colour onto the anchor so unlinked headings use the heading ink.
Apply the same borrowable-or-public cut the other book-page carousels use. The range queries are open-ended and distance-sorted, so Solr just walks further along the shelf; on production the nearest readable neighbours still sit within the same call-number neighbourhood. The clause is joined with AND on purpose. A bare clause after an AND chain is only a SHOULD for Solr's classic parser and would boost readable works rather than filter to them.
The template gated the placeholder on a numeric DDC among the editions loaded on the page, but the partial anchors on the work's Solr ddc_sort, which aggregates every edition. Popular works with many editions often keep their Dewey numbers on editions outside the page's slice, so the carousel vanished on testing for books like The Mystery of the Blue Train even though the partial had a full shelf for them. Let the partial decide: it answers empty after one memoized key lookup for works with no shelf position, and the loader removes the box.
The carousel's docs come straight from Solr, so they carried no lending state and every card fell through to "Not in Library" even though Solr said each work was borrowable. The other book-page carousels go through work_search_async, which attaches archive.org availability to each doc; do the same here, outside the memoized fetch so lending state is never pinned for five minutes.
lokesh
left a comment
There was a problem hiding this comment.
After putting it on our testing server, I made one big change to filter to readable books. Neighbours must be borrowable or public, matching the other carousels on the book page.
Otherwise, looking good! We'll do some more testing within the team and make a final call. Thanks!
|
@mekarpeles cc: @cdrini Original issue: #13526 This exploration is ready for vetting.
|
|
@lokesh so I need to any more addition to this or good to go for a merge then ? |
|
@Sravan1011 No more changes at this time. Thanks for all your work so far! Staff will test out the experience and report back. |
|
@lokesh @mekarpeles any further update on this issue |
|
@lokesh @mekarpeles any update on the latest tests happend with this pr |
|
@lokesh @mekarpeles any update on testing pr and merge this |
Fires BookCarousel|Impression|<key> once, when half of a loaded carousel is on screen, so clicks can be compared per impression across carousels. Empty partials and fallback messages report nothing.
…otype # Conflicts: # openlibrary/plugins/openlibrary/partials.py # openlibrary/templates/books/custom_carousel.html.jinja
|
@Sravan1011 The PR looks good. I put it on the testing server and everything is working as expected. I'm going to go ahead and merge it! I did pushed two commits to your branch:
The impression tracking is there so we can judge whether Nearby Books earns its place on the book page. The existing click events (CoverClick, BorrowClick, etc.) are already reported per carousel key. With impressions alongside them, we can look at clicks per impression for nearby-books and compare it fairly with "Books like this one" (related-subjects-carousel) and "More by this author" (related-authors-carousel). It sits lowest on the page, so raw click counts would undersell it. |
|
Yes @lokesh sir can you merge this. I'll be working on another issue too |
"Nearby" read as geographic; the carousel shows Dewey shelf neighbours.
|
I pushed a small change on top: renamed the carousel from "Nearby Books" to "On the Same Shelf" (9ae81fa). "Nearby" read a little like it might be geographic, books near you, so the new name makes the library shelf idea clearer. |
@lokesh thank you so much sir for guiding and correcting me in while working on this issue . It was great working with you. |


Summary
This PR implements a "Nearby Books" carousel, addressing issue #13159.
The feature simulates the digital equivalent of physical shelf-browsing ("shelf adjacency"): when viewing a work's page, it shows other works with numerically/lexically close Dewey Decimal Classification (
ddc_sort) values, filtered by the edition's language.Key Implementation Details
Neighbour lookup (
openlibrary/plugins/openlibrary/partials.py)gather_nearby_books_async(work_key, language, limit):ddc_sort(fetched from Solr viasolr.get_async), not a DDC recomputed from whichever single edition happens to be viewed --ddc_sortis picked as the longest DDC string across all of a work's editions (seework.py), so the shelf position needs to come from that same value or the browsed position won't match the shelf's own sort order.ddc_sort, or it isn't numeric (e.g.[Fic],[E]) -- those sort lexically after every number and would otherwise show up as false neighbours.ddc_sort, plus strict/exclusive ({/}) ranges below and above it. The exclusive bounds keep a popularddc_sortvalue (tens of thousands of works can share one) from filling both the "before" and "after" buckets with ties on itself.ddc_sortvalues, filtered bytype:work, the edition's language, the standard safe-mode filter (-subject:"content_warning:cover"), and afieldsallowlist.Lazy-loaded FastAPI partial (
openlibrary/fastapi/partials.py, modeled onlazy_carousel_partial)GET /partials/NearbyBooks.json?work_key=...&language=...fetches the neighbours and renders them through the existingmacros.RawQueryCarousel(lazy=False, books_data=...)-- the same primitivebooks/custom_carousel.htmlcarousel used elsewhere, so safe-mode, the analytics key, and$_()translation all come for free. Returns{"partials": ""}when there's nothing to show.This work is not done in
prepare_book_page/on the book-page request path, since that runs before every book page renders (our busiest page) and would add several synchronous Solr calls to it. Instead it's fetched asynchronously after the page loads.Template integration (
openlibrary/templates/type/edition/view.html,openlibrary/macros/NearbyBooksLoadingIndicator.html)Renders a lightweight placeholder (work key + the edition's language, no Solr call needed) below
RelatedWorksCarousel. A small JS module (openlibrary/plugins/openlibrary/js/nearby-books.js, mirroringlazy-carousel.js) fetches the real carousel HTML from the partial once the placeholder scrolls into view and swaps it in, with a retry affordance on failure.RawQueryCarousel.html/custom_carousel.htmlfixRawQueryCarouselpreviously always built a title-link/load-more config fromquery, even when a caller passes pre-fetchedbooks_datawith no query of its own (our case). Fixed so that when there's no real query behind the carousel, the title renders as plain (non-link) text and load-more is omitted, instead of both pointing at an unrelated, empty search.Screenshot
Nearby Books carousel on an edition page in local dev, showing real neighbouring works ordered by
ddc_sortaround 818.402 (Mark Twain):Testing & Verification
ruff check/ruff format --diff: clean on all changed Python files.npx eslint: clean on the new/changed JS.npx stylelint: clean on the changed CSS.openlibrary/plugins/openlibrary/tests/test_partials.pycover: noddc_sort-> empty, non-numericddc_sort-> empty, and the anchor/strict-bounds query shape./partials/NearbyBooks.jsonreturns real neighbours anchored on the work'sddc_sort, and the carousel renders on the edition page (screenshot above).