Recommendations split into TV and movies

This commit is contained in:
Eduard Urbach 2018-03-23 23:48:15 +01:00
parent 04ecbd737a
commit 6d3199a754
5 changed files with 53 additions and 64 deletions

View File

@ -12,6 +12,7 @@ const popular-company-height = 246px
padding 0.5rem 0.75rem padding 0.5rem 0.75rem
overflow hidden overflow hidden
ui-element ui-element
box-shadow shadow-light
.popular-company-header .popular-company-header
margin-bottom 0.5rem margin-bottom 0.5rem

View File

@ -11,8 +11,7 @@ import (
) )
const ( const (
maxRecommendations = 20 maxRecommendations = 10
worstGenreCount = 5
) )
// Anime shows a list of recommended anime. // Anime shows a list of recommended anime.
@ -26,51 +25,28 @@ func Anime(ctx *aero.Context) string {
} }
animeList := viewUser.AnimeList() animeList := viewUser.AnimeList()
genreItems := animeList.Genres()
genreAffinity := map[string]float64{}
worstGenres := []string{}
for genre, animeListItems := range genreItems {
affinity := 0.0
for _, item := range animeListItems {
// if item.Status == arn.AnimeListStatusDropped {
// affinity -= 5.0
// continue
// }
if item.Rating.Overall != 0 {
affinity += item.Rating.Overall
} else {
affinity += 5.0
}
}
genreAffinity[genre] = affinity
worstGenres = append(worstGenres, genre)
}
sort.Slice(worstGenres, func(i, j int) bool {
return genreAffinity[worstGenres[i]] < genreAffinity[worstGenres[j]]
})
if len(worstGenres) > worstGenreCount {
worstGenres = worstGenres[:worstGenreCount]
}
// Get all anime // Get all anime
recommendations := arn.AllAnime() var tv []*arn.Anime
var movies []*arn.Anime
allAnime := arn.AllAnime()
// Affinity maps an anime ID to a number that indicates how likely a user is going to enjoy that anime. // Affinity maps an anime ID to a number that indicates how likely a user is going to enjoy that anime.
affinity := map[string]float64{} affinity := map[string]float64{}
// Calculate affinity for each anime // Calculate affinity for each anime
for _, anime := range recommendations { for _, anime := range allAnime {
// Skip anime that are upcoming or tba // Skip anime that are upcoming or tba
if anime.Status == "upcoming" || anime.Status == "tba" { if anime.Status == "upcoming" || anime.Status == "tba" {
continue continue
} }
if anime.Type == "tv" {
tv = append(tv, anime)
} else if anime.Type == "movie" {
movies = append(movies, anime)
}
// Skip anime from my list (except planned anime) // Skip anime from my list (except planned anime)
existing := animeList.Find(anime.ID) existing := animeList.Find(anime.ID)
@ -78,48 +54,47 @@ func Anime(ctx *aero.Context) string {
continue continue
} }
// Skip anime that don't have one of the top genres for that user animeAffinity := anime.Score()
worstGenreFound := false
for _, genre := range anime.Genres {
if arn.Contains(worstGenres, genre) {
worstGenreFound = true
break
}
}
if worstGenreFound {
continue
}
animeAffinity := 0.0
// Planned anime go higher // Planned anime go higher
if existing != nil && existing.Status == arn.AnimeListStatusPlanned { if existing != nil && existing.Status == arn.AnimeListStatusPlanned {
animeAffinity += 75.0 animeAffinity += 15.0
} }
animeAffinity += float64(anime.Popularity.Total())
animeAffinity += anime.Rating.Overall * 80
affinity[anime.ID] = animeAffinity affinity[anime.ID] = animeAffinity
} }
// Sort // Sort
sort.Slice(recommendations, func(i, j int) bool { sort.Slice(tv, func(i, j int) bool {
affinityA := affinity[recommendations[i].ID] affinityA := affinity[tv[i].ID]
affinityB := affinity[recommendations[j].ID] affinityB := affinity[tv[j].ID]
if affinityA == affinityB { if affinityA == affinityB {
return recommendations[i].Title.Canonical < recommendations[j].Title.Canonical return tv[i].Title.Canonical < tv[j].Title.Canonical
}
return affinityA > affinityB
})
sort.Slice(movies, func(i, j int) bool {
affinityA := affinity[movies[i].ID]
affinityB := affinity[movies[j].ID]
if affinityA == affinityB {
return movies[i].Title.Canonical < movies[j].Title.Canonical
} }
return affinityA > affinityB return affinityA > affinityB
}) })
// Take the top 10 // Take the top 10
if len(recommendations) > maxRecommendations { if len(tv) > maxRecommendations {
recommendations = recommendations[:maxRecommendations] tv = tv[:maxRecommendations]
} }
return ctx.HTML(components.RecommendedAnime(recommendations, worstGenres, viewUser, user)) if len(movies) > maxRecommendations {
movies = movies[:maxRecommendations]
}
return ctx.HTML(components.RecommendedAnime(tv, movies, viewUser, user))
} }

View File

@ -1,4 +1,13 @@
component RecommendedAnime(animes []*arn.Anime, worstGenres []string, viewUser *arn.User, user *arn.User) component RecommendedAnime(animes []*arn.Anime, movies []*arn.Anime, viewUser *arn.User, user *arn.User)
h1(title="This is a machine-generated list of anime recommendations.")= "Recommended anime for " + viewUser.Nick h1= "Recommendations for " + viewUser.Nick
h2.recommendations-category
Icon("tv")
span TV series
AnimeGrid(animes, user) AnimeGrid(animes, user)
h2.recommendations-category
Icon("film")
span Movies
AnimeGrid(movies, user)

View File

@ -0,0 +1,5 @@
.recommendations-category
text-align left
font-size 1.8rem
border-bottom ui-border
padding-bottom 1rem

View File

@ -163,7 +163,6 @@ export class Application {
} }
// Don't ajaxify invalid links, links with a target or links that disable ajax specifically // Don't ajaxify invalid links, links with a target or links that disable ajax specifically
console.log(link.hash)
if(link.href === "" || link.href.includes("#") || link.target.length > 0 || link.dataset.ajax === "false") { if(link.href === "" || link.href.includes("#") || link.target.length > 0 || link.dataset.ajax === "false") {
continue continue
} }