feat(explore): more variance in the related rail (stronger MMR diversification)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 21s
CI / backend-lint-and-test (push) Successful in 26s
CI / integration (push) Successful in 3m24s

Operator wants the Explore "related" rail to span more — the #1188 diversifier
was tuned conservatively. Push all three knobs so it reaches further across
clusters instead of clumping near the anchor:

- MMR lam 0.55 → 0.40 — weight the diversity penalty harder (the main dial).
- candidate pool min(200, max(limit*5, 60)) → min(400, max(limit*8, 100)) — a
  wider nearest-cosine pool so MMR has genuinely distinct neighbourhoods to pick
  from, not just the near-dupes.
- pHash dup_threshold 6 → 8 — collapse more near-duplicate reposts/clones,
  freeing rail slots for distinct picks.

Still deterministic (same set per image, just more spread) and relevance-anchored
via the lam*sim-to-anchor term. Backend-only; no migration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ttrj5P7upUTueSfoJcxEqa
This commit is contained in:
2026-07-01 00:46:56 -04:00
parent 7cdce0c474
commit ef3318aac1
+10 -3
View File
@@ -289,7 +289,7 @@ def _gallery_images(rows, artists: dict[int, dict]) -> list[GalleryImage]:
]
def _diversify_similar(src, rows, limit, *, dup_threshold=6, lam=0.55):
def _diversify_similar(src, rows, limit, *, dup_threshold=8, lam=0.40):
"""Trim a nearest-cosine candidate pool down to `limit` diverse picks.
1. pHash collapse: drop any candidate whose perceptual hash is within
@@ -300,6 +300,11 @@ def _diversify_similar(src, rows, limit, *, dup_threshold=6, lam=0.55):
the most relevant up top but pushes the selection to SPAN clusters
instead of returning 40 variations of one image.
`lam` is the variance dial: lower = weight the diversity penalty harder, so
the rail reaches further across clusters (operator wanted MORE variance,
2026-07-01 — dropped 0.55→0.40, dup 6→8, paired with a wider pool in
`similar()`).
Falls back to nearest-order (`rows[:limit]`) on any failure or a small pool.
"""
if len(rows) <= 1:
@@ -658,8 +663,10 @@ class GalleryService:
return []
# Over-fetch so diversification has clusters to spread across — without a
# wide pool there's nothing but the near-dupes to choose from.
pool_n = min(200, max(limit * 5, 60))
# wide pool there's nothing but the near-dupes to choose from. Widened
# (5×→8×, cap 200→400) so the stronger MMR has genuinely distinct
# neighbourhoods to reach into for more variance (operator, 2026-07-01).
pool_n = min(400, max(limit * 8, 100))
distance = ImageRecord.siglip_embedding.cosine_distance(src.siglip_embedding)
eff = _effective_date_col()
stmt = select(ImageRecord, Post.post_date, eff.label("eff"))