From dee78a0623fb1b2e57f1138ff6625b0acc6bc77a Mon Sep 17 00:00:00 2001 From: Priveetee Date: Fri, 17 Apr 2026 12:41:37 +0200 Subject: [PATCH 1/2] feat: source shorts discovery from related seed videos --- .../HomeRecommendationCandidateService.kt | 10 +++++++++- ...omeRecommendationShortsCandidateService.kt | 19 +++++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/dev/typetype/server/services/HomeRecommendationCandidateService.kt b/src/main/kotlin/dev/typetype/server/services/HomeRecommendationCandidateService.kt index 650649e..8b846b7 100644 --- a/src/main/kotlin/dev/typetype/server/services/HomeRecommendationCandidateService.kt +++ b/src/main/kotlin/dev/typetype/server/services/HomeRecommendationCandidateService.kt @@ -25,7 +25,7 @@ class HomeRecommendationCandidateService( if (serviceId != YOUTUBE_SERVICE_ID) { return HomeRecommendationCandidatePool(subscriptions = emptyList(), discovery = emptyList()) } - return shortsCandidateService.fetch(userId, serviceId, profile, signalContext, this) + return shortsCandidateService.fetch(userId, profile, signalContext, this) } val subscriptions = fetchSubscriptionCandidates(userId, mode) .map { HomeRecommendationTaggedVideo(it, HomeRecommendationSourceTag.SUBSCRIPTION) } @@ -81,4 +81,12 @@ class HomeRecommendationCandidateService( source: HomeRecommendationSourceTag, ): List = searchCandidateFetcher.fetchSearchCandidates(serviceId, queries, maxQueries, perQueryLimit, source) + + suspend fun fetchRelatedCandidates( + seedUrls: List, + source: HomeRecommendationSourceTag, + seedLimit: Int, + relatedPerSeedLimit: Int, + ): List = + relatedCandidateService.fetch(seedUrls, source, seedLimit, relatedPerSeedLimit) } diff --git a/src/main/kotlin/dev/typetype/server/services/HomeRecommendationShortsCandidateService.kt b/src/main/kotlin/dev/typetype/server/services/HomeRecommendationShortsCandidateService.kt index e2f975e..2a342ba 100644 --- a/src/main/kotlin/dev/typetype/server/services/HomeRecommendationShortsCandidateService.kt +++ b/src/main/kotlin/dev/typetype/server/services/HomeRecommendationShortsCandidateService.kt @@ -3,7 +3,6 @@ package dev.typetype.server.services class HomeRecommendationShortsCandidateService { suspend fun fetch( userId: String, - serviceId: Int, profile: HomeRecommendationProfile, signalContext: HomeRecommendationSignalContext, candidateService: HomeRecommendationCandidateService, @@ -15,24 +14,28 @@ class HomeRecommendationShortsCandidateService { .take(HomeRecommendationShortsSources.SUBSCRIPTION_LIMIT) .map { HomeRecommendationTaggedVideo(it, HomeRecommendationSourceTag.SUBSCRIPTION) } .toList() - val discoveryCandidates = candidateService.fetchTrendingCandidates(serviceId) + val relatedFromSubscriptions = candidateService.fetchRelatedCandidates( + seedUrls = subscriptions.map { it.video.url }, + source = HomeRecommendationSourceTag.DISCOVERY_THEME, + seedLimit = HomeRecommendationCandidateLimits.SUBSCRIPTION_SEED_LIMIT, + relatedPerSeedLimit = HomeRecommendationCandidateLimits.RELATED_PER_SEED_LIMIT, + ) .asSequence() .map { it.copy(video = it.video.copy(isShortFormContent = it.video.isShortFormContent || it.video.duration in 1L..85L)) } .filter { it.video.isShortFormContent } .toList() - val exploration = candidateService.fetchSearchCandidates( - serviceId = serviceId, - queries = HomeRecommendationShortsQueryFactory.fromProfile(profile), - maxQueries = HomeRecommendationShortsSources.SEARCH_QUERY_LIMIT, - perQueryLimit = HomeRecommendationShortsSources.SEARCH_PER_QUERY_LIMIT, + val relatedFromFavorites = candidateService.fetchRelatedCandidates( + seedUrls = signalContext.favoriteUrls, source = HomeRecommendationSourceTag.DISCOVERY_EXPLORATION, + seedLimit = HomeRecommendationCandidateLimits.FAVORITE_SEED_LIMIT, + relatedPerSeedLimit = HomeRecommendationCandidateLimits.RELATED_PER_SEED_LIMIT, ).asSequence() .map { it.copy(video = it.video.copy(isShortFormContent = it.video.isShortFormContent || it.video.duration in 1L..85L)) } .filter { it.video.isShortFormContent } .toList() val discovery = HomeRecommendationDiscoveryAssembler().build( profile = profile, - candidates = discoveryCandidates + exploration, + candidates = relatedFromSubscriptions + relatedFromFavorites, explorationCap = HomeRecommendationShortsSources.DISCOVERY_CAP, ) val dedupedSubscriptions = HomeRecommendationShortsDeduplicator.apply( From 0f2aca93c1de0e1adbffaadcbb17e5fe4865c9bf Mon Sep 17 00:00:00 2001 From: Priveetee Date: Fri, 17 Apr 2026 12:41:44 +0200 Subject: [PATCH 2/2] test: align shorts route assertion with seed pipeline --- .../dev/typetype/server/HomeRecommendationShortsRoutesTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/kotlin/dev/typetype/server/HomeRecommendationShortsRoutesTest.kt b/src/test/kotlin/dev/typetype/server/HomeRecommendationShortsRoutesTest.kt index ba29f03..b9f675d 100644 --- a/src/test/kotlin/dev/typetype/server/HomeRecommendationShortsRoutesTest.kt +++ b/src/test/kotlin/dev/typetype/server/HomeRecommendationShortsRoutesTest.kt @@ -96,7 +96,6 @@ class HomeRecommendationShortsRoutesTest { } assertEquals(HttpStatusCode.OK, response.status) val body = response.bodyAsText() - assertTrue(body.contains("/v/s1") || body.contains("/v/s2")) assertTrue(!body.contains("/v/l1")) }