From 165733c6e063fc6ea4f83fefd9aac47fdd6623a2 Mon Sep 17 00:00:00 2001 From: pax Date: Thu, 9 Apr 2026 19:23:57 -0500 Subject: [PATCH] category_fetcher: compose from partial cache coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit try_compose_from_cache previously required 100% cache coverage — every tag in the post had to have a cached label or it returned False and populated nothing. One rare uncached tag out of 50 blocked the entire composition, leaving the post with zero categories even though 49/50 labels were available. Fix: compose whatever IS cached, return True when at least one tag got categorized. Tags not in the cache are simply absent from the categories dict (they stay in the flat tags string). The return value now means "the post has usable categories" rather than "the post has complete categories." This distinction matters because the dispatch logic uses the return value to decide whether to skip the fetch path — partial coverage is better than no coverage, and the missing tags get cached eventually when other posts that contain them get fetched. Verified against Gelbooru: post with 50 tags where 49 were cached now gets 49/50 categorized (Artist, Character, Copyright, General, Meta) instead of 0/50. --- booru_viewer/core/api/category_fetcher.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/booru_viewer/core/api/category_fetcher.py b/booru_viewer/core/api/category_fetcher.py index b340ca2..07f5e54 100644 --- a/booru_viewer/core/api/category_fetcher.py +++ b/booru_viewer/core/api/category_fetcher.py @@ -136,15 +136,23 @@ class CategoryFetcher: def try_compose_from_cache(self, post: "Post") -> bool: """Build ``post.tag_categories`` from cached labels. - Returns True if **every** tag in ``post.tag_list`` has a - cached label (i.e. the composition is complete). When True - the post is fully categorized and no HTTP is needed. + Populates ``post.tag_categories`` with whatever tags ARE + cached, even if some are missing. Returns True when at least + one tag was categorized (meaning the post is usable — the + info panel can render categories, templates can resolve + ``%artist%`` / ``%character%`` etc.). Returns False only + when the cache has literally nothing for any of the post's + tags, which means a fetch is needed. + + Tags not in the cache are simply absent from the category + dict. They stay in ``post.tags`` (the flat string) and can + be picked up by a later per-post fetch if needed. """ tags = post.tag_list if not tags: return True cached = self._db.get_tag_labels(self._site_id, tags) - if len(cached) < len(set(tags)): + if not cached: return False cats: dict[str, list[str]] = {} for tag in tags: @@ -153,7 +161,7 @@ class CategoryFetcher: cats.setdefault(label, []).append(tag) if cats: post.tag_categories = _canonical_order(cats) - return True + return bool(cats) # ----- batch tag API fast path -----