audio streaming app plyr.fm
38
fork

Configure Feed

Select the types of activity you want to include in your feed.

rank tag chips by total play count instead of track count (#1220)

Tags are now ordered by the sum of play_count across all tracks with
that tag. This surfaces genres with actual listener engagement rather
than just the most frequently applied tags.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

authored by

nate nowack
Claude Opus 4.6
and committed by
GitHub
96a29020 31b1b90c

+18 -6
+16 -5
backend/src/backend/api/tracks/tags.py
··· 27 27 28 28 29 29 class TagWithCount(BaseModel): 30 - """tag with track count for autocomplete.""" 30 + """tag with track count and total plays for ranking.""" 31 31 32 32 name: str 33 33 track_count: int 34 + total_plays: int 34 35 35 36 36 37 class TagDetail(BaseModel): ··· 133 134 returns tags sorted by track count (most used first). 134 135 use `q` parameter for prefix search (case-insensitive). 135 136 """ 136 - # build query: tags with their track counts 137 + # build query: tags with track counts and total plays, ranked by engagement 137 138 query = ( 138 - select(Tag.name, func.count(TrackTag.track_id).label("track_count")) 139 + select( 140 + Tag.name, 141 + func.count(TrackTag.track_id).label("track_count"), 142 + func.coalesce(func.sum(Track.play_count), 0).label("total_plays"), 143 + ) 139 144 .outerjoin(TrackTag, Tag.id == TrackTag.tag_id) 145 + .outerjoin(Track, TrackTag.track_id == Track.id) 140 146 .group_by(Tag.id, Tag.name) 141 - .order_by(func.count(TrackTag.track_id).desc(), Tag.name) 147 + .order_by(func.coalesce(func.sum(Track.play_count), 0).desc(), Tag.name) 142 148 .limit(limit) 143 149 ) 144 150 ··· 149 155 result = await db.execute(query) 150 156 rows = result.all() 151 157 152 - return [TagWithCount(name=row.name, track_count=row.track_count) for row in rows] 158 + return [ 159 + TagWithCount( 160 + name=row.name, track_count=row.track_count, total_plays=row.total_plays 161 + ) 162 + for row in rows 163 + ] 153 164 154 165 155 166 class RecommendedTag(BaseModel):
+2 -1
frontend/src/lib/components/TagFilter.svelte
··· 7 7 interface Tag { 8 8 name: string; 9 9 track_count: number; 10 + total_plays: number; 10 11 } 11 12 12 13 interface Props { ··· 27 28 .toSorted((a, b) => { 28 29 const aSelected = selectedTags.has(a.name) ? 1 : 0; 29 30 const bSelected = selectedTags.has(b.name) ? 1 : 0; 30 - return bSelected - aSelected || b.track_count - a.track_count; 31 + return bSelected - aSelected || b.total_plays - a.total_plays; 31 32 }) 32 33 ); 33 34