category_fetcher: compose from partial cache coverage

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.
This commit is contained in:
pax 2026-04-09 19:23:57 -05:00
parent af9b68273c
commit 165733c6e0

View File

@ -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 -----