browse_counts_cache.py: after FTS counts, _merge_community_tag_counts() queries
accepted tags (upvotes>=2) grouped by (domain,category,subcategory) and adds
distinct recipe_id counts to the cached keyword-set totals. Skips silently
when community Postgres is unavailable.
store.py: fetch_recipes_by_ids() fetches corpus recipes by explicit ID list,
used by the FTS fallback when a subcategory returns zero FTS results.
recipes.py (browse endpoint): when FTS total==0 for a subcategory, queries
community store for accepted tag IDs and serves those recipes directly.
Sets community_tagged=True in the response so the UI can surface context.
Refs kiwi#118.
Multiple concurrent users browsing the 3.2M recipe corpus would cause FTS5 page
cache contention and slow per-request queries. Solution: pre-compute counts for
all category/subcategory keyword sets into a small SQLite cache.
- browse_counts_cache.py: refresh(), load_into_memory(), is_stale() helpers
- config.py: BROWSE_COUNTS_PATH setting (default DATA_DIR/browse_counts.db)
- main.py: warms in-memory cache on startup; runs nightly refresh task every 24h
- infer_recipe_tags.py: auto-refreshes cache after a successful tag run so the
app picks up updated FTS counts without a restart