From a615450896afc0c332282fe32cb058b0f74fab5f Mon Sep 17 00:00:00 2001 From: skettiks Date: Thu, 21 May 2026 14:57:41 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D1=84=D0=B8=D0=BA=D1=81=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D1=80=D0=BE=D0=B1=D0=BB=D0=B5=D0=BC=D1=8B=20?= =?UTF-8?q?=D1=81=D0=B1=D0=BE=D1=80=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapters/web/RecommendationController.kt | 31 ++++++++++--------- .../services/RecommendationService.kt | 23 +++++++++++--- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/com/project/movienight/adapters/web/RecommendationController.kt b/src/main/kotlin/com/project/movienight/adapters/web/RecommendationController.kt index 5f306b0..6d5bdd9 100644 --- a/src/main/kotlin/com/project/movienight/adapters/web/RecommendationController.kt +++ b/src/main/kotlin/com/project/movienight/adapters/web/RecommendationController.kt @@ -5,9 +5,9 @@ import com.project.movienight.adapters.web.dto.response.RecommendationResponse import com.project.movienight.application.ports.input.AcceptRecommendationCommand import com.project.movienight.application.ports.input.AcceptRecommendationUseCase import com.project.movienight.application.ports.input.GetRecommendationsUseCase +import com.project.movienight.application.ports.input.RecommendationQuery import com.project.movienight.application.ports.input.RejectRecommendationCommand import com.project.movienight.application.ports.input.RejectRecommendationUseCase -import com.project.movienight.application.ports.input.RecommendationQuery import com.project.movienight.config.JellyfinIntegrationProperties import com.project.movienight.domain.model.ContentType import org.springframework.web.bind.annotation.GetMapping @@ -36,20 +36,21 @@ class RecommendationController( @RequestParam(required = false, defaultValue = "false") libraryOnly: Boolean, @RequestParam(required = false, defaultValue = "10") limit: Int, ): List = - getRecommendationsUseCase.recommend( - RecommendationQuery( - userId = userId, - contentType = contentType?.let { runCatching { ContentType.valueOf(it.uppercase()) }.getOrNull() }, - mood = mood, - libraryOnly = libraryOnly, - limit = limit, - ), - ).map { recommendation -> - RecommendationResponse.fromDomain( - recommendation = recommendation, - watchUrl = buildWatchUrl(recommendation.film.jellyfinItemId), - ) - } + getRecommendationsUseCase + .recommend( + RecommendationQuery( + userId = userId, + contentType = contentType?.let { runCatching { ContentType.valueOf(it.uppercase()) }.getOrNull() }, + mood = mood, + libraryOnly = libraryOnly, + limit = limit, + ), + ).map { recommendation -> + RecommendationResponse.fromDomain( + recommendation = recommendation, + watchUrl = buildWatchUrl(recommendation.film.jellyfinItemId), + ) + } @PostMapping("/{filmId}/accept") fun accept( diff --git a/src/main/kotlin/com/project/movienight/application/services/RecommendationService.kt b/src/main/kotlin/com/project/movienight/application/services/RecommendationService.kt index 1f9b104..cff99d2 100644 --- a/src/main/kotlin/com/project/movienight/application/services/RecommendationService.kt +++ b/src/main/kotlin/com/project/movienight/application/services/RecommendationService.kt @@ -4,9 +4,9 @@ import com.project.movienight.adapters.metrics.BusinessMetricsService import com.project.movienight.application.ports.input.AcceptRecommendationCommand import com.project.movienight.application.ports.input.AcceptRecommendationUseCase import com.project.movienight.application.ports.input.GetRecommendationsUseCase +import com.project.movienight.application.ports.input.RecommendationQuery import com.project.movienight.application.ports.input.RejectRecommendationCommand import com.project.movienight.application.ports.input.RejectRecommendationUseCase -import com.project.movienight.application.ports.input.RecommendationQuery import com.project.movienight.application.ports.output.FilmLibraryRepositoryPort import com.project.movienight.application.ports.output.FilmRatingRepositoryPort import com.project.movienight.application.ports.output.FilmRepositoryPort @@ -179,7 +179,15 @@ class RecommendationService( preferences?.eras.orEmpty().forEach { profile.add(feature("era", it), PREFERENCE_ERA_WEIGHT) } preferences?.castAndDirectors.orEmpty().forEach { profile.add(feature("person", it), PREFERENCE_PERSON_WEIGHT) } preferences?.moods.orEmpty().forEach { profile.add(feature("mood", it), PREFERENCE_MOOD_WEIGHT) } - preferences?.contentTypes.orEmpty().forEach { profile.add(feature("type", it.name), PREFERENCE_CONTENT_TYPE_WEIGHT) } + preferences + ?.contentTypes + .orEmpty() + .forEach { + profile.add( + feature("type", it.name), + PREFERENCE_CONTENT_TYPE_WEIGHT, + ) + } ratings.forEach { rating -> val film = filmsById[rating.filmId] ?: return@forEach @@ -309,7 +317,13 @@ class RecommendationService( film: Film, preferences: UserPreferences?, ): Double { - val preferredGenres = preferences?.weightedGenres.orEmpty().keys.map(::normalize).toSet() + val preferredGenres = + preferences + ?.weightedGenres + .orEmpty() + .keys + .map(::normalize) + .toSet() val filmGenres = film.genres.map(::normalize).toSet() return when { preferredGenres.isEmpty() -> BASE_DIVERSITY_SCORE @@ -423,7 +437,8 @@ class RecommendationService( return dot / (leftNorm * rightNorm) } - private fun roundScore(score: Double): Double = kotlin.math.round(score * SCORE_ROUNDING_FACTOR) / SCORE_ROUNDING_FACTOR + private fun roundScore(score: Double): Double = + kotlin.math.round(score * SCORE_ROUNDING_FACTOR) / SCORE_ROUNDING_FACTOR private fun Iterable.averageOrNull(): Double? { val values = toList()