protected override MalPositiveFeedbackInput GetRecSourceInputFromRequest(MalUserListEntries animeList, GetMalRecsRequest recRequest) { if (recRequest.TargetScore != null) { return(new MalPositiveFeedbackInput(animeList, recRequest.TargetScore.Value)); } else { return(new MalPositiveFeedbackInput(animeList, recRequest.TargetFraction.Value)); } }
protected override MalAnimeRecsInput GetRecSourceInputFromRequest(MalUserListEntries animeList, GetMalRecsRequest recRequest) { if (recRequest.TargetScore != null) { return(new MalAnimeRecsInput(animeList, targetScore: recRequest.TargetScore.Value)); } else { return(new MalAnimeRecsInput(animeList, targetFraction: (double)recRequest.TargetFraction.Value)); } }
public DTO.GetMalRecsResponse GetRecommendations(MalUserListEntries animeList, GetMalRecsRequest recRequest, CancellationToken cancellationToken) { // Ignore the cancellation token - we're only cancelling on service shut down, and getting recommendations should be quick. // If getting recommendations takes longer than the final connection drain time limit, something is wrong. TInput recSourceInput = GetRecSourceInputFromRequest(animeList, recRequest); TRecommendationResults recResults = UnderlyingRecSource.GetRecommendations(recSourceInput, recRequest.NumRecsDesired); List <TDtoRec> dtoRecs = new List <TDtoRec>(); Dictionary <int, DTO.MalAnime> animes = new Dictionary <int, DTO.MalAnime>(); foreach (TRecommendation rec in recResults) { TDtoRec dtoRec = new TDtoRec() { MalAnimeId = rec.ItemId, }; animes[rec.ItemId] = new DTO.MalAnime(rec.ItemId, Animes[rec.ItemId].Title, Animes[rec.ItemId].Type); SetSpecializedRecommendationProperties(dtoRec, rec); dtoRecs.Add(dtoRec); } TResponse response = new TResponse(); response.RecommendationType = RecommendationType; response.Recommendations = dtoRecs; SetSpecializedExtraResponseProperties(response, recResults); HashSet <int> extraAnimesToReturn = GetExtraAnimesToReturn(recResults); foreach (int extraAnimeId in extraAnimesToReturn) { if (!animes.ContainsKey(extraAnimeId)) { animes[extraAnimeId] = new DTO.MalAnime(extraAnimeId, Animes[extraAnimeId].Title, Animes[extraAnimeId].Type); } } response.Animes = animes.Values.ToList(); return(response); }
protected override MalUserListEntries GetRecSourceInputFromRequest(MalUserListEntries animeList, GetMalRecsRequest recRequest) { return(animeList); }
/// <summary> /// Converts the user's anime list into the input used by the rec source. /// </summary> /// <param name="animeList"></param> /// <param name="recRequest"></param> /// <param name="caster"></param> /// <returns></returns> protected abstract TInput GetRecSourceInputFromRequest(MalUserListEntries animeList, GetMalRecsRequest recRequest);
public async Task <MalTrainingData> LoadMalTrainingDataAsync(CancellationToken cancellationToken) { // Load all anime, users, and entries in parallel, then combine them into training data. try { Dictionary <int, MalAnime> animes; Dictionary <int, mal_user> dbUsers; IList <mal_list_entry_slim> dbEntrySlurp; using (CancellationTokenSource faultCanceler = new CancellationTokenSource()) using (CancellationTokenSource faultOrUserCancel = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, faultCanceler.Token)) { Task <Dictionary <int, MalAnime> > animeTask = AsyncUtils.EnsureExceptionsWrapped( () => SlurpAnimeAsync(faultOrUserCancel.Token)); CancellableTask cancellableAnimeTask = new CancellableTask(animeTask, faultCanceler); Task <Dictionary <int, mal_user> > userTask = AsyncUtils.EnsureExceptionsWrapped( () => SlurpUsersAsync(faultOrUserCancel.Token)); CancellableTask cancellableUserTask = new CancellableTask(userTask, faultCanceler); Task <IList <mal_list_entry_slim> > entryTask = AsyncUtils.EnsureExceptionsWrapped( () => SlurpEntriesAsync(faultOrUserCancel.Token)); CancellableTask cancellableEntryTask = new CancellableTask(entryTask, faultCanceler); await AsyncUtils.WhenAllCancelOnFirstExceptionDontWaitForCancellations(cancellableEntryTask, cancellableAnimeTask, cancellableUserTask).ConfigureAwait(false); animes = animeTask.Result; dbUsers = userTask.Result; dbEntrySlurp = entryTask.Result; } Logging.Log.Debug("Processing list entries from the database."); long entryCount = 0; Dictionary <int, List <ReadOnlyMalListEntryDictionary.ListEntryAndAnimeId> > entriesByUser = new Dictionary <int, List <ReadOnlyMalListEntryDictionary.ListEntryAndAnimeId> >(); foreach (mal_list_entry_slim dbEntry in dbEntrySlurp) { entryCount++; mal_user dbUser; if (!dbUsers.TryGetValue(dbEntry.mal_user_id, out dbUser) || !animes.ContainsKey(dbEntry.mal_anime_id)) { // Entry for an anime or user that wasn't in the database...there must have been an update going on between the time we got users, anime, and list entries continue; } List <ReadOnlyMalListEntryDictionary.ListEntryAndAnimeId> animeList; if (!entriesByUser.TryGetValue(dbEntry.mal_user_id, out animeList)) { animeList = new List <ReadOnlyMalListEntryDictionary.ListEntryAndAnimeId>(); entriesByUser[dbEntry.mal_user_id] = animeList; } animeList.Add(new ReadOnlyMalListEntryDictionary.ListEntryAndAnimeId( animeId: dbEntry.mal_anime_id, entry: new MalListEntry( rating: (byte?)dbEntry.rating, status: (CompletionStatus)dbEntry.mal_list_entry_status_id, numEpisodesWatched: dbEntry.num_episodes_watched ) )); } Dictionary <int, MalUserListEntries> users = new Dictionary <int, MalUserListEntries>(dbUsers.Count); foreach (int userId in entriesByUser.Keys) { List <ReadOnlyMalListEntryDictionary.ListEntryAndAnimeId> animeList = entriesByUser[userId]; animeList.Capacity = animeList.Count; ReadOnlyMalListEntryDictionary listEntries = new ReadOnlyMalListEntryDictionary(animeList); users[userId] = new MalUserListEntries(listEntries, animes, dbUsers[userId].mal_name, okToRecommendPredicate: null); } Logging.Log.DebugFormat("Done processing {0} list entries.", entryCount); return(new MalTrainingData(users, animes)); } catch (Exception ex) when(!(ex is OperationCanceledException)) { throw new Exception(string.Format("Error loading MAL training data: {0}", ex.Message), ex); } }
public async Task <GetMalRecsResponse> GetMalRecsAsync(GetMalRecsRequest request, CancellationToken cancellationToken) { request.AssertArgumentNotNull("request"); request.RecSourceName.AssertArgumentNotNull("request.RecSourceName"); request.AnimeList.AssertArgumentNotNull("request.AnimeList"); request.AnimeList.Entries.AssertArgumentNotNull("request.AnimeList.Entries"); if (request.TargetScore == null && request.TargetFraction == null) { Error error = new Error(ErrorCodes.InvalidArgument, "Payload.TargetScore or Payload.TargetFraction must be set."); throw new RecServiceErrorException(error); } string targetScoreString; if (request.TargetFraction != null) { targetScoreString = request.TargetFraction.Value.ToString("P2"); } else { targetScoreString = request.TargetScore.Value.ToString(); } Logging.Log.InfoFormat("Request for {0} MAL recs using rec source {1}. User has {2} anime list entries. Target score is {3}.", request.NumRecsDesired, request.RecSourceName, request.AnimeList.Entries.Count, targetScoreString); // Acquire read lock on rec sources const int lockTimeoutInSeconds = 3; using (CancellationTokenSource lockTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(lockTimeoutInSeconds))) using (CancellationTokenSource lockCancel = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, lockTimeout.Token)) { bool gotLock = false; try { using (var recSourcesLock = await m_recSourcesLockAsync.EnterReadLockAsync(lockCancel.Token).ConfigureAwait(false)) { gotLock = true; // Get rec source by name if (!m_recSources.ContainsKey(request.RecSourceName)) { Error error = new Error(errorCode: ErrorCodes.NoSuchRecSource, message: string.Format("No rec source called \"{0}\" is loaded.", request.RecSourceName)); throw new RecServiceErrorException(error); } ITrainableJsonRecSource recSource = m_recSources[request.RecSourceName]; // Convert DTO anime list to RecEngine anime list Dictionary <int, AnimeRecs.RecEngine.MAL.MalListEntry> entries = new Dictionary <int, RecEngine.MAL.MalListEntry>(); foreach (AnimeRecs.RecService.DTO.MalListEntry dtoEntry in request.AnimeList.Entries) { AnimeRecs.RecEngine.MAL.MalListEntry recEngineEntry = new RecEngine.MAL.MalListEntry(dtoEntry.Rating, dtoEntry.Status, dtoEntry.NumEpisodesWatched); entries[dtoEntry.MalAnimeId] = recEngineEntry; } MalUserListEntries animeList = new MalUserListEntries(ratings: entries, animes: m_animes, malUsername: null, prerequisites: m_prereqs); Stopwatch timer = Stopwatch.StartNew(); GetMalRecsResponse response = recSource.GetRecommendations(animeList, request, cancellationToken); timer.Stop(); Logging.Log.InfoFormat("Got recommendations from rec source {0}. Took {1}.", request.RecSourceName, timer.Elapsed); return(response); } } catch (OperationCanceledException) { // If we couldn't get a read lock within 3 seconds, a reload/retrain is probably going on if (!gotLock) { Error error = new Error(errorCode: ErrorCodes.Maintenance, message: "The rec service is currently undergoing maintenance and cannot respond to rec requests."); throw new RecServiceErrorException(error); } else { throw; } } } }